Android 自定義數字選擇器,可以根據自己的需求更改
阿新 • • 發佈:2018-12-27
實現效果如下:
還是以往的套路,先把那些專案所需要的給展示出來。
values下的資料夾,attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="NumPickView"> <attr name="totalNum" format="integer"/> <attr name="showNum" format="integer"/> <attr name="textColor" format="color"/> </declare-styleable> </resources>
下面要匯入style樣式:
<style name="dialogWindowAnimation"> <item name="android:windowEnterAnimation">@anim/num_picker_dialog_in</item> <item name="android:windowExitAnimation">@anim/num_picker_dialog_out</item> </style> <style name="time_dialog" parent="android:Theme.Dialog"> <item name="android:windowFrame">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowBackground">@color/background</item> </style>
接下來就是在res下面建立anim動畫,這個是彈出與消失時的動畫 num_picker_dialog_in.xml
<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:duration="1500" android:fromXDelta="0" android:fromYDelta="100%" android:toXDelta="0" android:toYDelta="0%" /> <alpha android:duration="1500" android:fromAlpha="0" android:toAlpha="1.0"/> </set>
num_picker_dialog_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1500"
android:fromXDelta="0"
android:fromYDelta="0%"
android:toXDelta="0"
android:toYDelta="150%" />
<alpha
android:duration="1500"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
下面就是彈出框的佈局了:記得空間的包名一定要改成你專案的包名,找到具體位置
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tvCancel"
android:layout_width="wrap_content"
android:layout_height="19dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:text="取消"
android:layout_marginLeft="16dp"/>
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="19dp"
android:layout_marginBottom="10dp"
android:text="請選擇"
android:layout_weight="1"
android:gravity="center"
android:layout_marginTop="10dp"
/>
<TextView
android:id="@+id/tvConfirm"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:text="確定"
android:layout_marginRight="16dp"/>
</LinearLayout>
<包名.NumPickView
android:id="@+id/numPickView"
android:layout_width="match_parent"
android:layout_height="260dp"
app:showNum="6"
app:textColor="#18B071"
app:totalNum="60"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"/>
</LinearLayout>
activity_main.xml 就用了一個點選事件,這個你們就自己新增吧
下面展示一下實現程式碼,其實很簡單的:
NumPickView
import android.animation.Animator;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
import java.util.List;
import static java.lang.Math.abs;
import static java.lang.Math.min;
/**
* Author uidq1152
* Date 2018-1-12 10:27
*/
public class NumPickView extends View {
private static final String TAG = "NumPickView";
private static final String DEF_TEXT_COLOR = "#FA6909";
private static final String DEF_START_COLOR = "#ECECEC";
/**
* D0D1D2
* 64666B
* 4C4E53
* 3A3D41
*/
//高
private int mHeight;
//寬
private int mWidth;
//二分之一高
private int middleHeight;
//二分之一寬
private int middleWidht;
//單位高度
private int mUnitHeight;
//資料
private List<String> mData = new ArrayList<>();
//當前位置
private int mCurrentPostion = 0;
//偏移量
private float pivot;
//畫筆
private Paint mPaint;
//字型的矩形
private Rect mRect;
//落點Y
private float downY;
//縮放擴大比例
private float mScale;
//滾輪狀態
private Status mStatus = Status.IDEL;
//遮罩效果
private LinearGradient mLg;
//數值估值器
private ValueAnimator mValueAnimator;
//字型大小
private int textSize;
//字型大小差
private int textStep;
//顯示個數
private int mShowNum;
//字型顏色
private int mTextColor = Color.parseColor(DEF_TEXT_COLOR);
//選擇監聽
private OnSelectNumListener mListener;
//顏色漸變計算器
private ArgbEvaluator mArgvEvlauator;
public NumPickView(Context context) {
super(context);
}
public NumPickView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public NumPickView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumPickView);
for (int i = 0; i < ta.getIndexCount(); i++) {
int index = ta.getIndex(i);
switch (index) {
case R.styleable.NumPickView_totalNum:
int total = ta.getInteger(index, 24);
for (int j = 0; j < total; j++) {
if (j < 10) {
mData.add("0" + String.valueOf(j));
} else {
mData.add(String.valueOf(j));
}
}
break;
case R.styleable.NumPickView_showNum:
mShowNum = ta.getInteger(index, 6);
break;
case R.styleable.NumPickView_textColor:
mTextColor = ta.getColor(index, mTextColor);
}
}
ta.recycle();
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
mRect = new Rect();
mArgvEvlauator = new ArgbEvaluator();
mValueAnimator = new ValueAnimator();
mValueAnimator.setDuration(300);
mValueAnimator.setInterpolator(new LinearInterpolator());
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
if (abs(pivot) > mUnitHeight) {
return;
}
pivot = value;
mScale = min(1, abs(pivot / mUnitHeight));
invalidate();
}
});
mValueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
if (mStatus == Status.UP && pivot != 0) {
mCurrentPostion = clamp(mCurrentPostion + 1);
} else if (mStatus == Status.DOWN && pivot != 0) {
mCurrentPostion = clamp(mCurrentPostion - 1);
}
invalidate();
pivot = 0;
mStatus = Status.IDEL;
mScale = 0;
if (mListener != null) {
mListener.onSelected(mCurrentPostion);
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
}
//展示個數
@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = getMeasuredHeight();
mWidth = getMeasuredWidth();
middleHeight = mHeight / 2;
middleWidht = mWidth / 2;
mUnitHeight = (mHeight - getPaddingTop() + getPaddingBottom()) / mShowNum;
textSize = mUnitHeight / 2;
textStep = mUnitHeight / 9;
}
@Override
protected void onDraw(Canvas canvas) {
//畫選中字型
drawText(canvas, mData.get(mCurrentPostion), 0, 1);
//畫除中間外上下字型
int num = mShowNum / 2;
for (int i = 1; i <= num; i++) {
drawText(canvas, mData.get(clamp(mCurrentPostion + i)), i, 1);
drawText(canvas, mData.get(clamp(mCurrentPostion - i)), i, -1);
}
}
/**
* 選中當前數值
*
* @param num index
*/
public void select(int num) {
if (num < 0 || num >= mData.size()) {
throw new IllegalArgumentException("The num must be in the range betwwen 0 and " + (mData.size() - 1));
}
mCurrentPostion = num;
if (mListener != null) {
mListener.onSelected(mCurrentPostion);
}
invalidate();
}
/**
* @param canvas
* @param text 要畫的 String
* @param level 選中為0級,每差一個 index 加一級
* @param direct 以選中的為基準的方向,direct < 0 在上方,direct > 0 在下方
*/
private void drawText(Canvas canvas, String text, int level, int direct) {
mPaint.reset();
mPaint.setShader(null);
//字的位置漸變數
float offset = direct * level * mUnitHeight;
//字型的大小變化
float step = (direct * mStatus.getValue() * mScale * textStep);
if (level == 0) {
//中間字型無論怎麼樣都是縮小的
mPaint.setColor(mTextColor);
mPaint.setTextSize(textSize - abs(step));
mPaint.getTextBounds(text, 0, text.length(), mRect);
canvas.drawText(text, middleWidht - mRect.width() / 2, mHeight / 2 + mRect.height() / 2 + pivot, mPaint);
} else {
//其他字型根據上下和滑動方向關係放大或縮小, 顏色漸變
int color = (int) mArgvEvlauator.evaluate(1 - abs(mRect.height() / 2 + offset + pivot)/middleHeight
, Color.parseColor(DEF_START_COLOR)
, mTextColor);
mPaint.setColor(color);
mPaint.setTextSize(textSize - textStep * level + step);
mPaint.getTextBounds(text, 0, text.length(), mRect);
canvas.drawText(text, middleWidht - mRect.width() / 2, middleHeight + mRect.height() / 2 + offset + pivot, mPaint);
}
}
/**
* distanceY > 0: 向下
* distanceY < 0: 向上
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = event.getY();
mValueAnimator.cancel();
break;
case MotionEvent.ACTION_MOVE:
pivot = event.getY() - downY;
if (pivot > 0) {
//向下
mStatus = Status.DOWN;
if (abs(pivot) > mUnitHeight) {
mCurrentPostion = clamp(mCurrentPostion - 1);
downY = event.getY();
pivot = 0;
} else {
invalidate();
}
} else {
//向上
mStatus = Status.UP;
if (abs(pivot) > mUnitHeight) {
mCurrentPostion = clamp(mCurrentPostion + 1);
downY = event.getY();
pivot = 0;
} else {
invalidate();
}
}
mScale = min(1, abs(pivot / mUnitHeight));
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (pivot == 0) {
//把點選事件統一為滑動事件處理,簡化流程。
pivot = 0.00001f;
}
if (abs(pivot) > mUnitHeight / 2) {
//需要過渡
int rest = (int) abs(mUnitHeight / 2 - pivot);
if (mStatus == Status.UP) {
mValueAnimator.setFloatValues(pivot, -rest);
} else if (mStatus == Status.DOWN) {
//這裡需要注意
mValueAnimator.setFloatValues(pivot, (int) pivot + rest + mUnitHeight / 2);
}
} else {
//過渡失敗,返回原數值,所以終點都是 0
if (mStatus == Status.UP) {
mValueAnimator.setFloatValues(pivot, 0);
} else if (mStatus == Status.DOWN) {
mValueAnimator.setFloatValues(pivot, 0);
}
}
if (mValueAnimator.getValues() == null || mValueAnimator.getValues().length == 0) {
return false;
}
mValueAnimator.start();
break;
}
return true;
}
public int getCurrentPostion() {
return mCurrentPostion;
}
/**
* 保證 index 合法化
*
* @param p 下標
* @return 合法後的下標
*/
private int clamp(int p) {
if (p > mData.size() - 1) {
return p - mData.size();
} else if (p < 0) {
return mData.size() - abs(p);
}
return p;
}
/**
* 設定滾輪監聽
*
* @param listener 監聽
*/
public void setOnSelectNumListener(OnSelectNumListener listener) {
this.mListener = listener;
}
/**
* 滾輪狀態
*/
private enum Status {
UP(1), DOWN(-1), IDEL(0);
int value;
Status(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/**
* 監聽介面
*/
public interface OnSelectNumListener {
void onSelected(int num);
}
}
NumPicker
import android.app.Activity;
import android.app.Dialog;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
/**
* Author cjet
* Date 2018-1-16 14:02
*/
public class NumPicker {
private Activity mActivity;
private TextView tvCancel;
private TextView tvComfirm;
private TextView tvTitle;
private NumPickView mNpv;
private Dialog mDialog;
private OnCancelClickListener mCancelListener;
private onComfirmClickListener mComfirmListener;
private int currentSelecedNum;
NumPicker(Activity activity) {
mActivity = activity;
initDialog();
}
private void initDialog() {
mDialog = new Dialog(mActivity, R.style.time_dialog);
mDialog.setContentView(mActivity.getLayoutInflater().inflate(R.layout.popu_num_picker, null));
Display dd = mActivity.getWindowManager().getDefaultDisplay();
DisplayMetrics dm = new DisplayMetrics();
dd.getMetrics(dm);
WindowManager.LayoutParams attributes = mDialog.getWindow().getAttributes();
mDialog.getWindow().setGravity(Gravity.BOTTOM);
attributes.height = (int) (dm.heightPixels * 0.4);
attributes.width = dm.widthPixels;
mDialog.getWindow().setWindowAnimations(R.style.dialogWindowAnimation);
tvCancel = mDialog.findViewById(R.id.tvCancel);
tvComfirm = mDialog.findViewById(R.id.tvConfirm);
tvTitle = mDialog.findViewById(R.id.tvTitle);
mNpv = mDialog.findViewById(R.id.numPickView);
currentSelecedNum = mNpv.getCurrentPostion();
setListener();
}
private void setListener() {
mNpv.setOnSelectNumListener(new NumPickView.OnSelectNumListener() {
@Override
public void onSelected(int num) {
currentSelecedNum = num;
}
});
tvComfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mComfirmListener != null) {
mComfirmListener.onClick(currentSelecedNum);
}
}
});
tvCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mCancelListener != null) {
mCancelListener.onClick();
}
}
});
}
public void show() {
if (mDialog != null) {
mDialog.show();
}
}
public void dismiss() {
if (mDialog != null) {
mDialog.cancel();
}
}
public void selecNum(int num) {
mNpv.select(num);
}
public void setOnCancelListener(OnCancelClickListener listener) {
this.mCancelListener = listener;
}
public void setOnComfirmListener(onComfirmClickListener listener) {
this.mComfirmListener = listener;
}
public void setTitle(String title) {
tvTitle.setText(title);
}
public interface OnCancelClickListener {
void onClick();
}
public interface onComfirmClickListener {
void onClick(int num);
}
}
接下來看一下在MainActivity怎麼操作的,你就明白了。
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = findViewById(R.id.btn);
final NumPicker np = new NumPicker(this);
np.setOnCancelListener(new NumPicker.OnCancelClickListener() {
@Override
public void onClick() {
np.dismiss();
}
});
np.setOnComfirmListener(new NumPicker.onComfirmClickListener() {
@Override
public void onClick(int num) {
Toast.makeText(MainActivity.this, num + "", Toast.LENGTH_SHORT).show();
np.dismiss();
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
np.show();
}
});
}
}