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:
- 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
- Add MDatePicker to the build.gradle file below your app as follows:
implementation 'com. Making. Jzmanu: MDatePickerSample: v1.0.6'
Copy the code
- 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:
- The basic idea
- Baseline calculation
- How to implement scrolling
- The specific drawing
- The realization of the MDatePicker
- The setting of MDatePicker
- 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.