欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android自定義View實(shí)現(xiàn)價格區(qū)間選擇控件

 更新時間:2022年11月25日 15:32:25   作者:newki  
這篇文章主要為大家詳細(xì)介紹了Android如何利用自定義View實(shí)現(xiàn)價格區(qū)間選擇控件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下

前言

之前我們的復(fù)習(xí)中,我們已經(jīng)對原生 Canvas 的繪制有了詳細(xì)的了解,我們對事件的處理也有了簡單的了解,這一期我們就對繪制與事件的處理做更進(jìn)一步的實(shí)現(xiàn)。

如圖,我們需要做這么一個區(qū)間的選擇控件,此控件也是我們常用的控件,在一些篩選頁面,根據(jù)價格,數(shù)值進(jìn)行一些篩選的時候,我們需要設(shè)置一個最小值和一個最大值。然后取一段中間的區(qū)間值。

而這個控件的實(shí)現(xiàn)就是典型的自定義繪制與自定義事件處理的標(biāo)志性實(shí)現(xiàn)。我愿稱之為自定義View的筑基練習(xí),如果大家能從頭到尾實(shí)現(xiàn)一遍,那么對自定義流程基本上已經(jīng)駕輕就熟了。

慣例我們分析一下實(shí)現(xiàn)步驟:

  • 左邊右邊的控制圓分別實(shí)現(xiàn),雖然一般情況下它們的屬性都是相同的,但是為了防止左右不同的圓,我們做好兼容處理。
  • 中間的圓角矩形進(jìn)度條,我們也分為默認(rèn)的底色和選中的顏色。
  • 對事件的處理,左右的圓形控件的移動處理。
  • 其他的文本顯示。
  • 自定義屬性的抽取與回調(diào)處理。

思路我們已經(jīng)有了,下面一步一步的來實(shí)現(xiàn)吧! Let's go

1、繪制靜態(tài)的圖形

關(guān)于靜態(tài)的效果繪制,我們已經(jīng)駕輕就熟了。 測量,畫筆,矩陣的初始化,繪制,一套流程下來,都已經(jīng)是固定的模板了。

進(jìn)度矩形條,左右圓形的一些資源,我們就能實(shí)現(xiàn)一個靜態(tài)的繪制。

需要定義的變量如下:

    private int mRangLineHeight = getResources().getDimensionPixelSize(R.dimen.d_4dp);  //圓角矩形線的高度
    private int mRangLineCornerRadius;   //圓角矩形線的圓角半徑
    private int mRangLineDefaultColor = Color.parseColor("#CDCDCD");  //默認(rèn)顏色
    private int mRangLineCheckedColor = Color.parseColor("#0689FD");  //選中顏色

    private int mCircleRadius = getResources().getDimensionPixelSize(R.dimen.d_14dp); //圓半徑
    private int mCircleStrokeWidth = getResources().getDimensionPixelSize(R.dimen.d_1d5dp); //圓邊框的大小

    private int mLeftCircleBGColor = Color.parseColor("#0689FD");  //左邊實(shí)心圓顏色
    private int mLeftCircleStrokeColor = Color.parseColor("#FFFFFF");  //左邊圓邊框的顏色

    private int mRightCircleBGColor = Color.parseColor("#0689FD");  //右邊實(shí)心圓顏色
    private int mRightCircleStrokeColor = Color.parseColor("#FFFFFF");  //右邊圓邊框的顏色

    private float mLeftCircleCenterX;    //左右兩個圓心位置
    private float mLeftCircleCenterY;
    private float mRightCircleCenterX;
    private float mRightCircleCenterY;

    private RectF mDefaultCornerLineRect;      //默認(rèn)顏色的圓角矩形
    private RectF mSelectedCornerLineRect;     //選中顏色的圓角矩形

    private Paint mLeftCirclePaint;        //各種畫筆
    private Paint mLeftCircleStrokePaint;
    private Paint mRightCirclePaint;
    private Paint mRightCircleStrokePaint;
    private Paint mDefaultLinePaint;
    private Paint mSelectedLinePaint;

畫筆與Rect的初始化:

 private void initPaint() {

        //初始化左邊實(shí)心圓
        mLeftCirclePaint = new Paint();
        mLeftCirclePaint.setAntiAlias(true);
        mLeftCirclePaint.setDither(true);
        mLeftCirclePaint.setStyle(Paint.Style.FILL);
        mLeftCirclePaint.setColor(mLeftCircleBGColor);

        //初始化左邊圓的邊框
        mLeftCircleStrokePaint = new Paint();
        mLeftCircleStrokePaint.setAntiAlias(true);
        mLeftCircleStrokePaint.setDither(true);
        mLeftCircleStrokePaint.setStyle(Paint.Style.STROKE);
        mLeftCircleStrokePaint.setColor(mLeftCircleStrokeColor);
        mLeftCircleStrokePaint.setStrokeWidth(mCircleStrokeWidth);

        //初始化右邊實(shí)心圓
        mRightCirclePaint = new Paint();
        mRightCirclePaint.setAntiAlias(true);
        mRightCirclePaint.setDither(true);
        mRightCirclePaint.setStyle(Paint.Style.FILL);
        mRightCirclePaint.setColor(mRightCircleBGColor);

        //初始化右邊圓的邊框
        mRightCircleStrokePaint = new Paint();
        mRightCircleStrokePaint.setAntiAlias(true);
        mRightCircleStrokePaint.setDither(true);
        mRightCircleStrokePaint.setStyle(Paint.Style.STROKE);
        mRightCircleStrokePaint.setColor(mRightCircleStrokeColor);
        mRightCircleStrokePaint.setStrokeWidth(mCircleStrokeWidth);

        //默認(rèn)顏色的圓角矩形線
        mDefaultCornerLineRect = new RectF();

        //中間選中顏色的圓角矩形
        mSelectedCornerLineRect = new RectF();

        mDefaultLinePaint = new Paint();
        mDefaultLinePaint.setAntiAlias(true);
        mDefaultLinePaint.setDither(true);

        mSelectedLinePaint = new Paint();
        mSelectedLinePaint.setAntiAlias(true);
        mSelectedLinePaint.setDither(true);

    }

關(guān)于測量還是按我們前面文字介紹說的來,我們先確定測量的模式與寬高,再計算一個最小的寬高,然后根據(jù)xml里面定義的測量模式來確定測量的寬高。

具體實(shí)現(xiàn)如下:

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

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int finalWidth, finalHeight;

        //計算的寬度與高度
        int calWidthSize = getPaddingLeft() + mCircleRadius * 2 + getPaddingRight() + mCircleStrokeWidth * 2;
        int calHeightSize = getPaddingTop() + mCircleRadius * 2 + mCircleStrokeWidth * 2 + getPaddingBottom();

        if (widthMode == MeasureSpec.EXACTLY) {
            //如果是精確模式使用測量的寬度
            finalWidth = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //如果是WrapContent使用計算的寬度
            finalWidth = Math.min(widthSize, calWidthSize);
        } else {
            //其他模式使用計算的寬度
            finalWidth = calWidthSize;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            //如果是精確模式使用測量的高度
            finalHeight = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //如果是WrapContent使用計算的高度
            finalHeight = Math.min(heightSize, calHeightSize);
        } else {
            //其他模式使用計算的高度
            finalHeight = calHeightSize;
        }

        //確定測量寬高
        setMeasuredDimension(finalWidth, finalHeight);
    }

內(nèi)部有詳細(xì)的注釋,推薦大家寬度使用固定的數(shù)組,高度wrap_content。

測量完成之后當(dāng)顯示出來了,我們就可以對圓形和矩陣進(jìn)行一些賦值操作。

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //左邊圓的圓心坐標(biāo)
        mLeftCircleCenterX = getPaddingLeft() + strokeRadius;
        mLeftCircleCenterY = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius;

        //右邊圓的圓心坐標(biāo)
        mRightCircleCenterX = w - getPaddingRight() - strokeRadius;
        mRightCircleCenterY = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius;

        //默認(rèn)圓角矩形進(jìn)度條
        mRangLineCornerRadius = mRangLineHeight / 2;//圓角半徑
        mDefaultCornerLineRect.left = getPaddingLeft() + strokeRadius;
        mDefaultCornerLineRect.top = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius - mRangLineCornerRadius;
        mDefaultCornerLineRect.right = w - getPaddingRight() - strokeRadius;
        mDefaultCornerLineRect.bottom = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius + mRangLineCornerRadius;

        //選中狀態(tài)圓角矩形進(jìn)度條
        mSelectedCornerLineRect.left = mLeftCircleCenterX;
        mSelectedCornerLineRect.top = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius - mRangLineCornerRadius;
        mSelectedCornerLineRect.right = mRightCircleCenterX;
        mSelectedCornerLineRect.bottom = getPaddingTop() /*+ rectDialogHeightAndSpace */ + strokeRadius + mRangLineCornerRadius;

    }

我們確定了圓角進(jìn)度矩形線條的rect,和左右限制圓的圓心和大小,我們就可以使用對的畫筆進(jìn)行繪制出靜態(tài)的數(shù)據(jù)來。

    //左側(cè)的控制圓與邊框
    private void drawLeftCircle(Canvas canvas) {
        //實(shí)心圓
        canvas.drawCircle(mLeftCircleCenterX, mLeftCircleCenterY, mCircleRadius, mLeftCirclePaint);
        //空心圓
        canvas.drawCircle(mLeftCircleCenterX, mLeftCircleCenterY, mCircleRadius, mLeftCircleStrokePaint);
    }

    //右側(cè)的控制圓與邊框
    private void drawRightCircle(Canvas canvas) {
        //實(shí)心圓
        canvas.drawCircle(mRightCircleCenterX, mRightCircleCenterY, mCircleRadius, mRightCirclePaint);
        //空心圓
        canvas.drawCircle(mRightCircleCenterX, mRightCircleCenterY, mCircleRadius, mRightCircleStrokePaint);
    }

    //中心的圓角矩形進(jìn)度條-默認(rèn)的底色
    private void drawDefaultCornerRectLine(Canvas canvas) {
        mDefaultLinePaint.setColor(mRangLineDefaultColor);
        canvas.drawRoundRect(mDefaultCornerLineRect, mRangLineCornerRadius, mRangLineCornerRadius, mDefaultLinePaint);
    }

    //中心的圓角矩形進(jìn)度條-已經(jīng)選中的顏色
    private void drawSelectedRectLine(Canvas canvas) {
        mSelectedLinePaint.setColor(mRangLineCheckedColor);
        canvas.drawRoundRect(mSelectedCornerLineRect, mRangLineCornerRadius, mRangLineCornerRadius, mSelectedLinePaint);
    }

這幾個東西繪制出來,我們的效果就如下所示:

為了方便顯示大小,我在控件里加一個灰色的背景為了方便觀看整個控件的大小。

靜態(tài)的實(shí)現(xiàn)之后我們就要開始讓兩邊的限制圓形動起來。

2、讓兩邊的限制圓動起來

我們在 onDraw 的方法中可以得知,動態(tài)的成員變量就是兩個圓的X軸坐標(biāo)即為 mLeftCircleCenterX 和 mRightCircleCenterX ,那么中間的進(jìn)度線條的繪制則是根據(jù) mSelectedCornerLineRect 的矩陣來繪制的。矩陣的left 和 right 也是根據(jù) mLeftCircleCenterX 和 mRightCircleCenterX 來計算的。

所以我們的最終目的就是動態(tài)的記錄當(dāng)前事件中的 mLeftCircleCenterX 和 mRightCircleCenterX 值,但是有左右兩個控制圓,我們怎么判斷移動的是哪一個圓呢?

先上一個判斷方法。

     /**
     * 判斷當(dāng)前移動的是左側(cè)限制圓,還是右側(cè)限制圓
     *
     * @param downX 按下的坐標(biāo)點(diǎn)
     * @return true表示按下的左側(cè),false表示按下的右側(cè)
     */
    private boolean checkTouchCircleLeftOrRight(float downX) {
        //用一個取巧的方法,如果當(dāng)前按下的為X坐標(biāo),那么左側(cè)圓心X的坐標(biāo)減去按下的X坐標(biāo),如果大于右側(cè)的圓心X減去X坐標(biāo),那么就說明在左側(cè),否則就在右側(cè)
        return !(Math.abs(mLeftCircleCenterX - downX) - Math.abs(mRightCircleCenterX - downX) > 0);
    }

當(dāng)我們移動的時候我們怎么計算呢?通常常用的方法是把進(jìn)度線條分為幾份,計算每一份的長度。我們把這些變量提取出來:

    private int mStrokeRadius;  //半徑+邊框的總值

    private int slice = 5; //代表整體進(jìn)度分為多少份
    private float perSlice;   //每一份所占的長度
    private int maxValue = 100;  //最大值,默認(rèn)為100
    private int minValue = 0;    //最小值,默認(rèn)為0

    private float downX;
    private boolean touchLeftCircle;

通過入口方法對其賦值,并且顯示出來后對每一份長度進(jìn)行計算:

    /**
     * 設(shè)置數(shù)據(jù)與回調(diào)處理
     */
    public void setupData(int minValue, int maxValue, int sliceValue) {
        this.minValue = minValue;
        this.maxValue = maxValue;

        int num = (maxValue - minValue) / sliceValue;
        slice = (maxValue - minValue) % sliceValue == 0 ? num : num + 1;
        invalidate();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        int realWidth = w - getPaddingLeft() - getPaddingRight();
        mStrokeRadius = mCircleRadius + mCircleStrokeWidth;

        //計算每一份對應(yīng)的距離
        perSlice = (realWidth - mStrokeRadius * 2) * 1f / slice;

到處我們就能寫OnTouch方法了,這是核心方法,我們慢一點(diǎn)來。

我們先只對按下的事件做處理:

 @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {

            //按下的時候記錄當(dāng)前操作的是左側(cè)限制圓還是右側(cè)的限制圓
            downX = event.getX();
            touchLeftCircle = checkTouchCircleLeftOrRight(downX);

            if (touchLeftCircle) {
                //如果是左側(cè)
                //如果超過右側(cè)最大值則不處理
                if (downX + perSlice > mRightCircleCenterX) {
                    return false;
                }

                mLeftCircleCenterX = downX;
            } else {
                //如果是右側(cè)
                //如果超過左側(cè)最小值則不處理
                if (downX - perSlice < mLeftCircleCenterX) {
                    return false;
                }

                mRightCircleCenterX = downX;
            }

        } 

        //中間的進(jìn)度矩形是根據(jù)兩邊圓心點(diǎn)動態(tài)計算的
        mSelectedCornerLineRect.left = mLeftCircleCenterX;
        mSelectedCornerLineRect.right = mRightCircleCenterX;

        //全部的事件處理完畢,變量賦值完成之后,開始重繪
        invalidate();

        return true;
    }

按下的過程中對,最大最小值做判斷,并且賦值進(jìn)度矩陣的 left 和 right ,那么我們就能實(shí)現(xiàn)指定的點(diǎn)擊效果,如下圖所示:

這只是點(diǎn)擊呢,效果太挫了,我們想要按著滑動怎么辦?那我們就需要重寫Move事件。

3、動態(tài)滑動并計算當(dāng)前的區(qū)間值

滑動相對來說是比較難得,我們要處理兩個限制圓的滾動,并且當(dāng)它們兩個圓碰撞在一起的時候,我們要處理交換的邏輯,并且還需要注意滑動邊界的處理。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {

            //按下的時候記錄當(dāng)前操作的是左側(cè)限制圓還是右側(cè)的限制圓
            downX = event.getX();
            touchLeftCircle = checkTouchCircleLeftOrRight(downX);

            if (touchLeftCircle) {
                //如果是左側(cè)
                //如果超過右側(cè)最大值則不處理
                if (downX + perSlice > mRightCircleCenterX) {
                    return false;
                }

                mLeftCircleCenterX = (int) downX;
            } else {
                //如果是右側(cè)
                //如果超過左側(cè)最小值則不處理
                if (downX - perSlice < mLeftCircleCenterX) {
                    return false;
                }

                mRightCircleCenterX = downX;
            }

        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {

            float moveX = event.getX();

            if (mLeftCircleCenterX + perSlice > mRightCircleCenterX) {
                //兩圓重合的情況下的處理
                if (touchLeftCircle) {
                    // 左側(cè)到最右邊
                    if (mLeftCircleCenterX == getWidth() - getPaddingRight() - mStrokeRadius) {
                        touchLeftCircle = true;
                        mLeftCircleCenterX = getWidth() - getPaddingRight() - mStrokeRadius;

                    } else {
                        //交換右側(cè)滑動
                        touchLeftCircle = false;
                        mRightCircleCenterX = (int) moveX;
                    }
                } else {
                    //右側(cè)到最左邊
                    if (mRightCircleCenterX == getPaddingLeft() + mStrokeRadius) {
                        touchLeftCircle = false;
                        mRightCircleCenterX = getPaddingLeft() + mStrokeRadius;

                    } else {
                        //交換左側(cè)滑動
                        touchLeftCircle = true;
                        mLeftCircleCenterX = (int) moveX;
                    }
                }

            } else {
                //如果是正常的移動
                if (touchLeftCircle) {
                    //滑動左邊限制圓,如果左邊圓超過右邊圓,那么把右邊圓賦值給左邊圓,如果沒超過就賦值當(dāng)前的moveX
                    mLeftCircleCenterX = mLeftCircleCenterX - mRightCircleCenterX >= 0 ? mRightCircleCenterX : moveX;
                } else {
                    //滑動右邊限制圓,如果右邊圓超過左邊圓,那么把左邊圓賦值給右邊圓,如果沒超過就賦值當(dāng)前的moveX
                    mRightCircleCenterX = mRightCircleCenterX - mLeftCircleCenterX <= 0 ? mLeftCircleCenterX : moveX;
                }

            }


        } 

        //對所有的手勢效果進(jìn)行過濾操作,不能超過最大最小值
        limitMinAndMax();

        //中間的進(jìn)度矩形是根據(jù)兩邊圓心點(diǎn)動態(tài)計算的
        mSelectedCornerLineRect.left = mLeftCircleCenterX;
        mSelectedCornerLineRect.right = mRightCircleCenterX;

        //全部的事件處理完畢,變量賦值完成之后,開始重繪
        invalidate();

        return true;
    }

主要需要處理的是交換身位的方法,當(dāng)兩個圓相撞的時候,需要賦值處理,交換X的賦值,然后切換 touchLeftCircle 的值,然后對另一個圓進(jìn)行移動。

需要注意的是我們一定要在賦值之前對最大值與最小值進(jìn)行校驗(yàn),以免滑到天邊去了。

    private void limitMinAndMax() {
        //如果是操作的左側(cè)限制圓,超過最小值了
        if (mLeftCircleCenterX < getPaddingLeft() + mStrokeRadius) {
            mLeftCircleCenterX = getPaddingLeft() + mStrokeRadius;
        }

        //如果是操作的左側(cè)限制圓,超過最大值了
        if (mLeftCircleCenterX > getWidth() - getPaddingRight() - mStrokeRadius) {
            mLeftCircleCenterX = getWidth() - getPaddingRight() - mStrokeRadius;
        }

        //如果是操作的右側(cè)限制圓,超過最大值了
        if (mRightCircleCenterX > getWidth() - getPaddingRight() - mStrokeRadius) {
            mRightCircleCenterX = getWidth() - getPaddingRight() - mStrokeRadius;
        }

        //如果是操作的右側(cè)限制圓,超過最小值了
        if (mRightCircleCenterX < getPaddingLeft() + mStrokeRadius) {
            mRightCircleCenterX = getPaddingLeft() + mStrokeRadius;
        }

    }

此時大致的效果已經(jīng)出來了,效果如圖:

4、計算當(dāng)前值與回調(diào)處理

我們一直都是計算的是兩個圓的中心點(diǎn)X軸的計算,那么我們真正選中的值是多少呢?總不能把X軸坐標(biāo)給調(diào)用者吧。所以我們需要通過滑動的百分比動態(tài)的計算具體的值。

    //根據(jù)移動的距離計算當(dāng)前的值
    private int getPercentMax(float distance) {
        //計算此時的位置坐標(biāo)對應(yīng)的距離能分多少份
        int lineLength = getWidth() - getPaddingLeft() - getPaddingRight() - mStrokeRadius * 2;
        distance = distance <= 0 ? 0 : (distance >= lineLength ? lineLength : distance);

        //計算滑動的百分比
        float percentage = distance / lineLength;

        return (int) (percentage * maxValue);

    }

那我們需要在Move事件中一直回調(diào)嗎?沒必要,我們在取消的時候,或者說事件完畢的時候,當(dāng)用戶選好了區(qū)間之后,我們回調(diào)一次即可。

    if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {

        //計算當(dāng)前的左側(cè)右側(cè)真正的限制值
        int moveLeftData = getPercentMax(mLeftCircleCenterX - getPaddingLeft() - mStrokeRadius);
        int moveRightData = getPercentMax(mRightCircleCenterX - getPaddingLeft() - mStrokeRadius);
        //順便賦值當(dāng)前的真正值,便于后面的回調(diào)
       int leftValue = Math.min(moveLeftData, maxValue);
       int rightValue = Math.min(moveRightData, maxValue);

        if (mListener != null) mListener.onMoveValue(leftValue, rightValue);
    }

    //回調(diào)區(qū)間值的監(jiān)聽
    private OnRangeValueListener mListener;

    public interface OnRangeValueListener {
        void onMoveValue(int leftValue, int rightValue);
    }

我們在Activity中通過setup方法就可以設(shè)置值并監(jiān)聽到最后的區(qū)間事件

    override fun init() {

        findViewById<RangeView>(R.id.range_view).setupData(0, 100, 1) { leftValue, rightValue ->

            toast("leftValue:$leftValue rightValue:$rightValue")
        }

    }

效果:

5、實(shí)時文本顯示與后續(xù)的擴(kuò)展

這么看起來倒是似模似樣了,我們的需求是在拖動的時候?qū)崟r在頂部展示一個彈窗,展示當(dāng)前的值,這怎么搞?

其實(shí)就是在頂部繪制一個圓角矩形,在矩形內(nèi)部繪制文本,然后我們通過左右限制圓的位置計算出中間 的位置,讓頂部的圓角矩形在中間位置顯示不就行了嘛。開干

先定義需要用到的成員變量:

    private int mTopDialogTextSize = getResources().getDimensionPixelSize(R.dimen.d_12dp);  //頂部文字的大小
    private int mTopDialogTextColor = Color.parseColor("#000000");  //頂部文字的顏色
    private int mTopDialogWidth = getResources().getDimensionPixelSize(R.dimen.d_70dp);  //頂部描述信息彈窗的寬度
    private int mTopDialogCornerRadius = getResources().getDimensionPixelSize(R.dimen.d_15dp);  //頂部描述信息彈窗圓角半徑
    private int mTopDialogBGColor = Color.parseColor("#0689FD");  //頂部框的顏色
    private int mTopDialogSpaceToProgress = getResources().getDimensionPixelSize(R.dimen.d_2dp); //頂部描述信息彈窗距離進(jìn)度條的間距(配置)

    private int mRealDialogDistanceSpace;  //頂部彈窗與進(jìn)度條的間距(頂部彈窗與進(jìn)度的真正距離)計算得出


    private Path mTrianglePath;     //畫小三角形路徑
    private int mTriangleLength = 15;  //等邊三角形邊長
    private int mTriangleHeight;     //等邊三角形的高

    private Paint textPaint;

然后我們在初始化畫筆與資源的時候,初始化文本的畫筆和頂部彈窗的矩陣:

  private void initPaint() {

       //頂部圓角矩形
        mTopDialogRect = new RectF();
        //畫小三角形指針
        mTrianglePath = new Path();
        //小三角形的高
        mTriangleHeight = (int) Math.sqrt(mTriangleLength * mTriangleLength - mTriangleLength / 2 * (mTriangleLength / 2));

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);
        textPaint.setTextSize(mTopDialogTextSize);
        textPaint.setColor(mTopDialogTextColor);
  }

由于我們加了頂部的高度,那么我們就需要在測量的時候也要把高度加上去

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

        //計算的寬度與高度
        int calWidthSize = getPaddingLeft() + mCircleRadius * 2 + getPaddingRight() + mCircleStrokeWidth * 2;
        int calHeightSize = getPaddingTop() + mTopDialogCornerRadius * 2 + mTriangleHeight + mTopDialogSpaceToProgress
                + mCircleRadius * 2 + mCircleStrokeWidth * 2 + getPaddingBottom();

        ...        
    }

顯示的時候我們計算真正的高度,我們比設(shè)置的再高出一點(diǎn)點(diǎn)方便展示。并且對頂部彈窗的矩陣賦值

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        ...

        mRealDialogDistanceSpace = mTopDialogCornerRadius * 2 + mTopDialogSpaceToProgress;

        //數(shù)值描述圓角矩形
        mTopDialogRect.left = w / 2 - mTopDialogWidth / 2;
        mTopDialogRect.top = getPaddingTop();
        mTopDialogRect.right = w / 2 + mTopDialogWidth / 2;
        mTopDialogRect.bottom = getPaddingTop() + mTopDialogCornerRadius * 2;

        ...
    }

然后我們就能繪制頂部的彈窗背景與內(nèi)部的文本,再繪制彈窗下面的小三角指針

 //頂部的文字框
    private void drawTopTextRectDialog(Canvas canvas) {
        if (leftValue == minValue && (rightValue == maxValue || rightValue < maxValue)) {
            textDesc = "低于 " + rightValue;
        } else if (leftValue > minValue && rightValue == maxValue) {
            textDesc = "高于 " + leftValue;
        } else if (leftValue > minValue && rightValue < maxValue) {
            if (leftValue == rightValue) {
                textDesc = "低于 " + rightValue;
            } else
                textDesc = leftValue + "-" + rightValue;
        }


        if (isShowRectDialog) {
            //繪制圓角矩形框
            mSelectedLinePaint.setShader(null);
            mSelectedLinePaint.setColor(mTopDialogBGColor);
            canvas.drawRoundRect(mTopDialogRect, mTopDialogCornerRadius, mTopDialogCornerRadius, mSelectedLinePaint);

            //繪制文本
            textPaint.setColor(mTopDialogTextColor);
            textPaint.setTextSize(mTopDialogTextSize);
            float textWidth = textPaint.measureText(textDesc);
            float textLeft = mTopDialogRect.left + mTopDialogWidth / 2 - textWidth / 2;
            canvas.drawText(textDesc, textLeft, getPaddingTop() + mTopDialogCornerRadius + mTopDialogTextSize / 4, textPaint);
        }
    }

    //頂部文字框下面的三角箭頭
    private void drawSmallTriangle(Canvas canvas) {
        if (isShowRectDialog) {
            mTrianglePath.reset();
            mTrianglePath.moveTo(mTopDialogRect.left + mTopDialogWidth / 2 - mTriangleLength / 2, getPaddingTop() + mTopDialogCornerRadius * 2);
            mTrianglePath.lineTo(mTopDialogRect.left + mTopDialogWidth / 2 + mTriangleLength / 2, getPaddingTop() + mTopDialogCornerRadius * 2);
            mTrianglePath.lineTo(mTopDialogRect.left + mTopDialogWidth / 2, getPaddingTop() + mTopDialogCornerRadius * 2 + mTriangleHeight);
            mTrianglePath.close();
            canvas.drawPath(mTrianglePath, mSelectedLinePaint);
        }
    }

由于要繪制的參數(shù)是頂部的矩陣,所以我們在onTouch中,還需要對頂部的矩陣進(jìn)行重新動態(tài)賦值,才能讓他動起來:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
     
     ...

         } else if (event.getAction() == MotionEvent.ACTION_MOVE) {


            //計算當(dāng)前的左側(cè)右側(cè)真正的限制值
            int moveLeftData = getPercentMax(mLeftCircleCenterX - getPaddingLeft() - mStrokeRadius);
            int moveRightData = getPercentMax(mRightCircleCenterX - getPaddingLeft() - mStrokeRadius);
            //順便賦值當(dāng)前的真正值,可以讓頂部文字顯示
            leftValue = Math.min(moveLeftData, maxValue);
            rightValue = Math.min(moveRightData, maxValue);
         } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {

            //計算當(dāng)前的左側(cè)右側(cè)真正的限制值
            int moveLeftData = getPercentMax(mLeftCircleCenterX - getPaddingLeft() - mStrokeRadius);
            int moveRightData = getPercentMax(mRightCircleCenterX - getPaddingLeft() - mStrokeRadius);
            //賦值方便回調(diào)
            leftValue = Math.min(moveLeftData, maxValue);
            rightValue = Math.min(moveRightData, maxValue);

            if (mListener != null) mListener.onMoveValue(leftValue, rightValue);

            //移除消息,并重新開始延時關(guān)閉頂部彈窗
            removeCallbacks(dismissRunnable);
            postDelayed(dismissRunnable, 1000);
        }

     ...

        //頂部的文本框矩陣也要居中布局
        mTopDialogRect.left = (mRightCircleCenterX + mLeftCircleCenterX) / 2 - mTopDialogWidth / 2;
        mTopDialogRect.right = (mRightCircleCenterX + mLeftCircleCenterX) / 2 + mTopDialogWidth / 2;

        //全部的事件處理完畢,變量賦值完成之后,開始重繪
        invalidate();

        return true;
    }


    //頂部彈窗的顯示
    Runnable dismissRunnable = new Runnable() {
        @Override
        public void run() {
            if (!isRectDialogShowing) {
                isShowRectDialog = false;
            }
            postInvalidate();
        }
    };

總體來說繪制和動態(tài)賦值矩陣的left right并不算太難,相比兩個圓的觸摸事件要相對簡單一點(diǎn)。

實(shí)現(xiàn)的效果就是如下:

后記

當(dāng)然后面如果我們有更對的需求還能繼續(xù)繪制一些東西,例如:

如果我們想在圖片紅框處添加固定的最小值和最大值,也簡單,我們直接drawText到指定的位置即可,我們不是已經(jīng)有進(jìn)度條Rect的 left 和 right 了嗎?就可以很方便的添加文本。

由于是我自用的一個控件,目前需求并沒有更多的要求就并沒有進(jìn)行更多的擴(kuò)展,輪子已經(jīng)在這里了,如果大家有興趣也可以很方便的修改的。大家跟著一起復(fù)習(xí)一遍,是不是感覺自定義的繪制和自定義的事件已經(jīng)入門了呢 - -!

到此這篇關(guān)于Android自定義View實(shí)現(xiàn)價格區(qū)間選擇控件的文章就介紹到這了,更多相關(guān)Android區(qū)間選擇控件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論