Android自定義控件:時間滾輪控件的繪製

背景

Android開發中我們會常常用到類似時間滾輪的控件,這類控件到了UI設計師手中常常會被修改成各種樣子,與其從網上蕩個類似的Demo漫無目的地修改,不如寫個我們自己的demo,對其瞭如指掌,修改樣式自然不成問題。下圖是我們的滾輪控件實現效果:
這裏寫圖片描述

原理

我覺得對於看似複雜的控件,我們需要一步一步拆解,將功能拆解開,對應到我們所學的知識點上,然後再將這些知識點組裝起來,功能也就自然出來了。

此處我們需要繼承一個View,根據Touch事件,將數據按照一定的規則繪製在View上。

這裏首先就是要複寫View的onTouchEvent方法,捕獲手指移動的距離,根據移動距離來設置顯示的數據。

同時當我們鬆手時,應當有一個判斷標準來當前中間欄應該顯示的數據,並設置一個動畫,讓這個數據自動移動到中間位置。

關於顯示文字大小的變化效果,這裏參考了網上的一些代碼,使用拉動距離與控件高度一半的關係,此處我們使用了一個開口向下的拋物線的函數。

說了這麼多廢話,先上一張示意圖吧:

這裏寫圖片描述

代碼

引用控件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
    <com.vonchenchen.uistudy.ui.TimePicker.WheelPicker
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#ff9e9e9e"/>
</LinearLayout>

實現代碼

控件代碼比較簡單,重要部分已做註釋,即粘即用

/**
 * Created by vonchenchen on 2016/3/25 0025.
 */
public class WheelPicker extends View {

    public static final String TAG = "TimePicker";

    private Context mContext;

    private int mViewHeight;
    private int mViewWidth;

    /** 中間條目寬度的 */
    private int mCenterItemHeight;

    private int mCenterY;

    private float mLastDownY;  //使用偏移量爲了
    private float mMoveDistanceY = 0;
    /** 當前數組索引 */
    private int mCurrentDataIndex = 0;
    /** 繪製文本畫筆 */
    private Paint mTextPaint;
    /** 繪製直線畫筆 */
    private Paint mLinePaint;
    /** 文字最大尺寸 */
    private int mMaxTextSize;
    /** 間隔長度比率 */
    private float mGapRatio = 0;
    /** 是否允許滑動 */
    private boolean mIsTouchEnable = true;

    /** 是否允許下拉 */
    private boolean mIsTouchDownEnable = true;
    /** 是否允許上拉 */
    private boolean mIsTouchUpEnable = true;

    private ArrayList<String> mDataList = new ArrayList<String>();

    public WheelPicker(Context context) {
        super(context);
        init(context);
    }

    public WheelPicker(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context){
        this.mContext = context;

        initData();

        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.FILL);
        mTextPaint.setTextAlign(Paint.Align.CENTER);
        mTextPaint.setColor(0xff1f1f1f);

        mLinePaint = new Paint();
        mLinePaint.setStyle(Paint.Style.FILL);
        mLinePaint.setTextAlign(Paint.Align.CENTER);
        mLinePaint.setColor(0xff1f1f1f);
    }

    private void initData(){
        for(int i = 0; i < 24; i++){
            mDataList.add(i+"");
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mViewHeight = getMeasuredHeight();
        mViewWidth = getMeasuredWidth();

        mCenterY = mViewHeight/2;
        mMaxTextSize = mViewHeight/8;
        mCenterItemHeight = mViewHeight/4;
        mGapRatio = 0.7f;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if(mIsTouchEnable) {
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                    onActionDown(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    onActionMove(event);
                    break;
                case MotionEvent.ACTION_UP:
                    onActionUp(event);
                    break;
            }
        }

        return true;
    }

    private void onActionDown(MotionEvent event){
        mLastDownY  = event.getY();
        //mMoveDistanceY = 0;
    }

    private void onActionMove(MotionEvent event){
        float currentY = event.getY();
        float diff = currentY - mLastDownY;

        if(mIsTouchUpEnable && mIsTouchDownEnable) {
            mMoveDistanceY += diff;
        }else if(!mIsTouchUpEnable && mIsTouchDownEnable){   //拉到第一個 只能向下滑動
            if(diff > 0){
                mMoveDistanceY += diff;
                mIsTouchUpEnable = true;
            }
        }else if(mIsTouchUpEnable && !mIsTouchDownEnable){   //拉到最後一個 只能向上滑動
            if(diff < 0){
                mMoveDistanceY += diff;
                mIsTouchDownEnable = true;
            }
        }

        mLastDownY = currentY;

        invalidate();
    }

    private void onActionUp(MotionEvent event){

        returnBack(mMoveDistanceY, 0);
    }

    private void drawText(Canvas canvas, int currentIndex, int position){

        float length = mCenterY + mMoveDistanceY;

        if(position == 0){                   //繪製當前中間的文字
            float scale = getScale((int) mMoveDistanceY);
            mTextPaint.setTextSize((float) (mMaxTextSize * scale));
            mTextPaint.setColor(getAlphaTextColor(scale));
            Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
            int diff = (fontMetricsInt.bottom + fontMetricsInt.top)/2;

            float baseY = length-diff;
            canvas.drawText(mDataList.get(currentIndex), mViewWidth / 2, baseY, mTextPaint);

            if(mMoveDistanceY > mCenterItemHeight/2){            //向下拉 index減
                if(mCurrentDataIndex > 0) {
                    mCurrentDataIndex--;
                }else{
                    mIsTouchDownEnable = false;                   //禁止下拉
                }
                mMoveDistanceY = 0;
            }else if(mMoveDistanceY < -mCenterItemHeight/2){    //向上拉 index加
                if(mCurrentDataIndex < mDataList.size()-1) {
                    mCurrentDataIndex++;
                }else{
                    mIsTouchUpEnable = false;                     //禁止上拉
                }
                mMoveDistanceY = 0;
            }
        }else {                                    //繪製上下文字
            int realIndex = currentIndex + position;

            if(realIndex >= 0 && realIndex < mDataList.size()){
                length = length + mCenterItemHeight * mGapRatio * position;

                float scale = getScale((int) length - mCenterY);
                mTextPaint.setTextSize((float) (mMaxTextSize * scale));
                mTextPaint.setColor(getAlphaTextColor(scale));
                Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
                int diff = (fontMetricsInt.bottom + fontMetricsInt.top)/2;

                canvas.drawText(mDataList.get(realIndex), mViewWidth/2 , length-diff, mTextPaint);
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for(int i=-3; i<=3; i++){
            drawText(canvas, mCurrentDataIndex, i);
        }
        canvas.drawLine(0, mCenterY-mCenterItemHeight/2, mViewWidth, mCenterY-mCenterItemHeight/2, mLinePaint);
        canvas.drawLine(0, mCenterY + mCenterItemHeight / 2, mViewWidth, mCenterY + mCenterItemHeight / 2, mLinePaint);
    }

    private float getScale(int lengthToCenter){
        float ret = (float) (1 - Math.pow(((float)lengthToCenter)/mCenterY, 2));
        if(ret < 0){
            ret = 0;
        }
        return ret;
    }

    private void returnBack(float start, float end){
        mIsTouchEnable = false;
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
        valueAnimator.setDuration(100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                mMoveDistanceY = (float) valueAnimator.getAnimatedValue();
                if (Math.abs(mMoveDistanceY) <= 3) {
                    mMoveDistanceY = 0;
                    mIsTouchEnable = true;
                }
                invalidate();
            }
        });
        valueAnimator.start();
    }

    /**
     * 根據比率設置字體顏色
     * @param scale
     * @return
     */
    private int getAlphaTextColor(float scale){
        int data = (int) (255 * scale);
        return (data << 24);
    }

    public String getTimeData(){
        return mDataList.get(mCurrentDataIndex);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章