1. 程式人生 > >Android 自定義數字選擇器,可以根據自己的需求更改

Android 自定義數字選擇器,可以根據自己的需求更改

實現效果如下:
在這裡插入圖片描述

還是以往的套路,先把那些專案所需要的給展示出來。
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();
            }
        });
    }
}