Android 滑动数字选择器是一种用户界面控件,它允许用户从一系列数字中选择一个值。用户可以通过滑动手势或点击手势来选择数字。以下是一些关于 Android 滑动数字选择器的信息和链接:
-
Android NumberPicker:这是 Android 框架提供的原生数字选择器控件。它可以通过 XML 或代码创建,并支持自定义样式和属性。官方文档链接:https://developer.android.com/reference/android/widget/NumberPicker
-
Android WheelPicker:这是一个第三方的数字选择器库,它提供了多种样式和配置选项。它可以通过 Gradle 或手动导入方式添加到项目中。GitHub 链接:GitHub - AigeStudio/WheelPicker: Simple and fantastic wheel view in realistic effect for android.
-
Android ScrollableNumberPicker:这是另一个第三方的数字选择器库,它支持水平和垂直滚动模式,并提供了多种自定义选项。它可以通过 Gradle 或手动导入方式添加到项目中。GitHub 链接:https://github.com/michaelbel/ScrollableNumberPicker
一、以下是一个简单的 Android NumberPicker 示例代码:
NumberPicker numberPicker = findViewById(R.id.numberPicker);numberPicker.setMinValue(1);numberPicker.setMaxValue(10);numberPicker.setValue(5);numberPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { // 处理数值变化事件 }});
二、以下是我个人写的一个滚动选择器
项目结构:
2.先定义两个类
NumPicker
package com.example.myapplication;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.Button;import android.widget.TextView;public class NumPicker { private Activity mActivity; private TextView tvCancel; private Button 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.BottomSheetDialog); mDialog.setContentView(mActivity.getLayoutInflater().inflate(R.layout.picker_layout, 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); }}
NumPickView
package com.example.myapplication;import static java.lang.Math.abs;import static java.lang.Math.min;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.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.LinearInterpolator;import androidx.annotation.Nullable;import java.util.ArrayList;import java.util.List;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"; //高 private int mHeight; //宽 private int mWidth; //二分之一高 private int middleHeight; //二分之一宽 private int middleWidht; //单位高度 private int mUnitHeight; //数据 private List 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 = 6; j < total; j++) { if (j < 10) {mData.add("0" + 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); } } 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(); } 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); } } @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; } 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; } 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); }}
主布局文件activity_main
dialog中需要的布局文件,也就是数字选择器的布局picker_layout
剩下最后activity中的主代码了:
package com.example.myapplication;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;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 dundong = this.findViewById(R.id.btn); final NumPicker np = new NumPicker(MainActivity.this); np.setOnCancelListener(new NumPicker.OnCancelClickListener() { @Override public void onClick() { np.dismiss(); } }); np.setOnComfirmListener(new NumPicker.onComfirmClickListener() { @Override public void onClick(int num) { np.dismiss(); num = num + 6; Toast.makeText(MainActivity.this, "你选择了"+num, Toast.LENGTH_SHORT).show(); } }); dundong.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { np.show(); } }); }}
运行效果
来源地址:https://blog.csdn.net/ming0gy/article/details/130247604