Personal wechat public number practice.

Latest update 20210523.

  • Switch to AndroidX
  • [New] Set font size
  • [New] Set text color
  • [Optimization] Fine tune text drawing position

Custom View implementation of a good Android date and time picker, you can directly go to Github View, its dependence is as follows:

  1. Add the JitPack repository to the build.gradle file in the project root directory as follows:
allprojects {
	repositories {
		// ...
		maven { url 'https://www.jitpack.io'}}}Copy the code
  1. Add MDatePicker to the build.gradle file below your app as follows:
implementation 'com. Making. Jzmanu: MDatePickerSample: v1.0.6'
Copy the code
  1. MDatePicker is used like a normal Dialog, as follows:
MDatePicker.create(this)
    // Attach Settings (optional, default)
    .setCanceledTouchOutside(true)
    .setGravity(Gravity.BOTTOM)
    .setSupportTime(false)
    .setTwelveHour(true)
    // Result callback (required)
    .setOnDateResultListener(new MDatePickerDialog.OnDateResultListener() {
        @Override
        public void onDateResult(long date) {
            // date
        }
    })
    .build()
    .show();
Copy the code

The renderings are as follows:

Here is a brief description of the implementation process:

  1. The basic idea
  2. Baseline calculation
  3. How to implement scrolling
  4. The specific drawing
  5. The realization of the MDatePicker
  6. The setting of MDatePicker
  7. The use of MDatePicker

The basic idea

One of the most basic elements of date picker is a scroll wheel that can set data at will. Here, we also define a customized MPickerView as a container for date and time selection. The date or time selection can be completed by scrolling up and down. Both date and time use MPickerView to display data, the final date picker uses MPickerView to encapsulate, use Calendar to assemble date and time data, which is the most important is the implementation of MPickerView.

Baseline calculation

Text Baseline (Baseline) is the reference line for text rendering. Only when the text Baseline is determined, can the text be drawn to the desired position more accurately. Therefore, if the text rendering is involved, it must be drawn in accordance with the Baseline, and the left origin of text rendering is at the left end of the Baseline. The Y-axis direction is negative up and positive down, as follows:

Because the date or time that is finally selected is displayed in the middle of the drawn View, how do you calculate it in code?

 // Get the Baseline
 Paint.FontMetricsInt metricsInt = paint.getFontMetricsInt();
 float line = mHeight / 2.0 f + (metricsInt.bottom - metricsInt.top) / 2.0 f - metricsInt.descent;
Copy the code

How to implement scrolling

MPickerView middle position Draws a position in the given set of data, where the position drawn is always size/2 as the index of the data to draw:

public void setData(@NonNull List<String> data) {
    if(mData ! =null) {
        mData.clear();
        mData.addAll(data);
        // Draw the index of the center position
        mSelectPosition = data.size() / 2; }}Copy the code

So how to achieve the scrolling effect? Every time the finger slides a certain distance, upward sliding will move the top data to the bottom, and conversely, upward sliding will move the bottom data to the top, so as to simulate the data rolling, the key code is as follows:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mStartTouchY = event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            mMoveDistance += (event.getY() - mStartTouchY);
            if (mMoveDistance > RATE * mTextSizeNormal / 2) {// Slide down
                moveTailToHead();
                mMoveDistance = mMoveDistance - RATE * mTextSizeNormal;
            } else if (mMoveDistance < -RATE * mTextSizeNormal / 2) {// Slide up
                moveHeadToTail();
                mMoveDistance = mMoveDistance + RATE * mTextSizeNormal;
            }
            mStartTouchY = event.getY();
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            / /...
    }
    return true;
}
Copy the code

The specific drawing

The rendering of MPickerView is mainly the rendering of display data, which can be divided into the rendering of data in the upper, middle and lower positions. MSelectPosition (index); mSelectPosition (index); mSelectPosition (mSelectPosition);

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // Draw the middle position
    draw(canvas, 1.0, mPaintSelect);
    // Draw the above data
    for (int i = 1; i < mSelectPosition - 1; i++) {
        draw(canvas, -1, i, mPaintNormal);
    }
    // Draw the following data
    for (int i = 1; (mSelectPosition + i) < mData.size(); i++) {
        draw(canvas, 1, i, mPaintNormal);
    }
    invalidate();
}
Copy the code

Let’s look at the concrete implementation of the DRAW method:

private void draw(Canvas canvas, int type, int position, Paint paint) {
    float space = RATE * mTextSizeNormal * position + type * mMoveDistance;
    float scale = parabola(mHeight / 4.0 f, space);
    float size = (mTextSizeSelect - mTextSizeNormal) * scale + mTextSizeNormal;
    int alpha = (int) ((mTextAlphaSelect - mTextAlphaNormal) * scale + mTextAlphaNormal);
    paint.setTextSize(size);
    paint.setAlpha(alpha);

    float x = mWidth / 2.0 f;
    float y = mHeight / 2.0 f + type * space;
    Paint.FontMetricsInt fmi = paint.getFontMetricsInt();
    float baseline = y + (fmi.bottom - fmi.top) / 2.0 f - fmi.descent;
    canvas.drawText(mData.get(mSelectPosition + type * position), x, baseline, paint);
}
Copy the code

This completes the drawing of the data part. In addition, some additional effects can be drawn, such as dividing lines, drawing additional information such as year, month, day, hour and score, and some display effects can be adjusted according to the design. Please refer to the following:

/ /...
if (position == 0) {
    mPaintSelect.setTextSize(mTextSizeSelect);
    float startX;
    
    if (mData.get(mSelectPosition).length() == 4) {
        // The year is a four-digit number
        startX = mPaintSelect.measureText("0000") / 2 + x;
    } else {
        // Other two digits
        startX = mPaintSelect.measureText("00") / 2 + x;
    }

    // Year, month, day, hour, minute
    Paint.FontMetricsInt anInt = mPaintText.getFontMetricsInt();
    if(! TextUtils.isEmpty(mText)) canvas.drawText(mText, startX, mHeight /2.0 f + (anInt.bottom - anInt.top) / 2.0 f - anInt.descent, mPaintText);
    // Draw a partition line
    Paint.FontMetricsInt metricsInt = paint.getFontMetricsInt();
    float line = mHeight / 2.0 f + (metricsInt.bottom - metricsInt.top) / 2.0 f - metricsInt.descent;
    canvas.drawLine(0, line + metricsInt.ascent - 5, mWidth, line + metricsInt.ascent - 5, mPaintLine);
    canvas.drawLine(0, line + metricsInt.descent + 5, mWidth, line + metricsInt.descent + 5, mPaintLine);
    canvas.drawLine(0, dpToPx(mContext, 0.5 f), mWidth, dpToPx(mContext, 0.5 f), mPaintLine);
    canvas.drawLine(0, mHeight - dpToPx(mContext, 0.5 f), mWidth, mHeight - dpToPx(mContext, 0.5 f), mPaintLine);
}

Copy the code

The above code related coordinate calculation is related to the Baseline, the specific code implementation refer to the end of the article read the original, MPickerView implementation effect is as follows:

The realization of the MDatePicker

The implementation of MDatePickerDoialog is very simple, which is to define a Dialog, the year, month, day, hour, grade data through the relevant Calendar API to obtain the corresponding data, layout file is as follows:


      
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:minWidth="300dp"
    android:id="@+id/llDialog"
    android:orientation="vertical">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="40dp">
        <TextView
            android:id="@+id/tvDialogTopCancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginStart="12dp"
            android:text="@string/strDateCancel"
            android:textColor="#cf1010"
            android:textSize="15sp" />
        <TextView
            android:id="@+id/tvDialogTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="@string/strDateSelect"
            android:textColor="# 000000"
            android:textSize="16sp" />
        <TextView
            android:id="@+id/tvDialogTopConfirm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginEnd="12dp"
            android:text="@string/strDateConfirm"
            android:textColor="#cf1010"
            android:textSize="15sp" />
    </RelativeLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <com.manu.mdatepicker.MPickerView
            android:id="@+id/mpvDialogYear"
            android:layout_width="wrap_content"
            android:layout_height="160dp"
            android:layout_weight="1"
            tools:ignore="RtlSymmetry" />
        <com.manu.mdatepicker.MPickerView
            android:id="@+id/mpvDialogMonth"
            android:layout_width="0dp"
            android:layout_height="160dp"
            android:layout_weight="1" />
        <com.manu.mdatepicker.MPickerView
            android:id="@+id/mpvDialogDay"
            android:layout_width="0dp"
            android:layout_height="160dp"
            android:layout_weight="1" />
        <com.manu.mdatepicker.MPickerView
            android:id="@+id/mpvDialogHour"
            android:layout_width="0dp"
            android:layout_height="160dp"
            android:layout_weight="1" />
        <com.manu.mdatepicker.MPickerView
            android:id="@+id/mpvDialogMinute"
            android:layout_width="0dp"
            android:layout_height="160dp"
            android:layout_weight="1" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/llDialogBottom"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/tvDialogBottomConfirm"
            android:layout_width="0.0 dp"
            android:layout_height="match_parent"
            android:layout_weight="1.0"
            android:gravity="center"
            android:text="@string/strDateConfirm"
            android:textColor="#cf1010"
            android:textSize="16sp" />
        <View
            android:layout_width="0.5 dp"
            android:layout_height="match_parent"
            android:background="#dbdbdb" />
        <TextView
            android:id="@+id/tvDialogBottomCancel"
            android:layout_width="0.0 dp"
            android:layout_height="match_parent"
            android:layout_weight="1.0"
            android:gravity="center"
            android:text="@string/strDateCancel"
            android:textColor="#cf1010"
            android:textSize="16sp" />
    </LinearLayout>
</LinearLayout>
Copy the code

Create a Dialog that can pop up at the bottom and middle of the screen. Create a Dialog that can pop up at the bottom and middle of the screen.

public static class Builder {
    private Context mContext;
    private String mTitle;
    private int mGravity;
    private boolean isCanceledTouchOutside;
    private boolean isSupportTime;
    private boolean isTwelveHour;
    private float mConfirmTextSize;
    private float mCancelTextSize;
    private int mConfirmTextColor;
    private int mCancelTextColor;
    private OnDateResultListener mOnDateResultListener;

    public Builder(Context mContext) {
        this.mContext = mContext;
    }

    public Builder setTitle(String mTitle) {
        this.mTitle = mTitle;
        return this;
    }

    public Builder setGravity(int mGravity) {
        this.mGravity = mGravity;
        return this;
    }

    public Builder setCanceledTouchOutside(boolean canceledTouchOutside) {
        isCanceledTouchOutside = canceledTouchOutside;
        return this;
    }

    public Builder setSupportTime(boolean supportTime) {
        isSupportTime = supportTime;
        return this;
    }

    public Builder setTwelveHour(boolean twelveHour) {
        isTwelveHour = twelveHour;
        return this;
    }

    public Builder setConfirmStatus(float textSize, int textColor) {
        this.mConfirmTextSize = textSize;
        this.mConfirmTextColor = textColor;
        return this;
    }

    public Builder setCancelStatus(float textSize, int textColor) {
        this.mCancelTextSize = textSize;
        this.mCancelTextColor = textColor;
        return this;
    }

    public Builder setOnDateResultListener(OnDateResultListener onDateResultListener) {
        this.mOnDateResultListener = onDateResultListener;
        return this;
    }

    private void applyConfig(MDatePicker dialog) {
        if (this.mGravity == 0) this.mGravity = Gravity.CENTER;
        dialog.mContext = this.mContext;
        dialog.mTitle = this.mTitle;
        dialog.mGravity = this.mGravity;
        dialog.isSupportTime = this.isSupportTime;
        dialog.isTwelveHour = this.isTwelveHour;
        dialog.mConfirmTextSize = this.mConfirmTextSize;
        dialog.mConfirmTextColor = this.mConfirmTextColor;
        dialog.mCancelTextSize = this.mCancelTextSize;
        dialog.mCancelTextColor = this.mCancelTextColor;
        dialog.isCanceledTouchOutside = this.isCanceledTouchOutside;
        dialog.mOnDateResultListener = this.mOnDateResultListener;
    }

    public MDatePicker build(a) {
        MDatePicker dialog = new MDatePicker(mContext);
        applyConfig(dialog);
        returndialog; }}Copy the code

The setting of MDatePicker

The basic attributes of MDatePicker are as follows:

Set up the Set the method The default value
The title setTitle(String mTitle) Date selection
Display position setGravity(int mGravity) Gravity.CENTER
Click on the external area to cancel setCanceledTouchOutside(boolean canceledTouchOutside) false
Whether to support time setSupportTime(boolean supportTime) false
Whether to support 12-hour system setTwelveHour(boolean twelveHour) false
Whether to display only the year setOnlyYearMonth(boolean onlyYearMonth) false
Set the default value for the year setYearValue(int yearValue) The current year
Set the month defaults setMonthValue(int monthValue) The current month
Set the default value for days setDayValue(int dayValue) The number of days

The use of MDatePicker

MDatePicker is very simple to use as follows:

MDatePicker.create(this)
    // Attach Settings (optional, default)
    .setCanceledTouchOutside(true)
    .setGravity(Gravity.BOTTOM)
    .setSupportTime(false)
    .setTwelveHour(true)
    // Result callback (required)
    .setOnDateResultListener(new MDatePickerDialog.OnDateResultListener() {
        @Override
        public void onDateResult(long date) {
            // date
        }
    })
    .build()
    .show();
Copy the code

Please refer to the link below for details or click on the end of the article to read the original text. Welcome star!

For more information, see the wechat public number.