仿瓜子二手車(chē)雙向滑動(dòng)的價(jià)格選擇控件

*本篇文章已授權(quán)微信公眾號(hào) guolin_blog (郭霖)獨(dú)家發(fā)布

項(xiàng)目中的酒店模塊有個(gè)雙向滑動(dòng)的價(jià)格選擇器控件,感覺(jué)不是很滿(mǎn)意,所以就趁著剛發(fā)完版本這段空閑時(shí)間自己重新自定義了一個(gè),效果和瓜子二手車(chē)中的價(jià)格選擇器有點(diǎn)相似,啰嗦了半天,客官消消氣,小的這就上圖:

range_price.gif
從效果圖上可以大致發(fā)現(xiàn),此控件的一些特點(diǎn)
1、可以靈活設(shè)置步長(zhǎng)值,開(kāi)發(fā)者只需要傳入最小值、最大值以及每一步代表的數(shù)值即可
2、當(dāng)滑動(dòng)距離小于步長(zhǎng)距離的一半時(shí)松開(kāi)會(huì)自動(dòng)回彈到上一個(gè)位置處,相反則會(huì)自動(dòng)回彈到下一個(gè)位置處
3、當(dāng)兩圓不在兩極端位置時(shí)(即:兩圓在起始位置和終點(diǎn)位置之間),當(dāng)滑動(dòng)左邊圓圈靠近到右邊圓時(shí),繼續(xù)右滑則左邊圓位于之前右邊圓的位置不再動(dòng),而右邊圓則會(huì)繼續(xù)向右邊滑動(dòng);滑動(dòng)右邊圓時(shí)同理
4、當(dāng)滑動(dòng)左邊圓到達(dá)最右邊圓的終點(diǎn)位置時(shí)再向左滑動(dòng),右邊圓不動(dòng),左邊圓繼續(xù)向左滑動(dòng),右邊圓情況同理
5、允許兩圓相重合,重合時(shí)則代表的數(shù)值相同
這樣說(shuō)可能還是不太好理解,我們把每個(gè)圓用不同的顏色來(lái)區(qū)分開(kāi),如下圖所示
range_bar.gif
這樣再看是不是很直觀(guān)了
之前對(duì)于自定義View也寫(xiě)了很多文章了,如果有興趣的話(huà)可以到我的CSDN博客中去了解下,其實(shí)一般的自定義控件都需要這幾步,首先,對(duì)需求仔細(xì)研究思考并且在自己的本子上畫(huà)畫(huà)草圖進(jìn)行結(jié)構(gòu)分析(很有用),然后就是自定義屬性了,假如你打算開(kāi)源的話(huà),那么自定義屬性是必不可少的,這樣便于使用者根據(jù)自己需要進(jìn)行靈活設(shè)置,接著就是測(cè)量了,確定控件的寬高,緊接著就可以進(jìn)行繪制了,當(dāng)然了如果是繼承ViewGroup的話(huà)則還需要去確定子View的位置,如果涉及到手勢(shì)滑動(dòng)等操作,最后還需要對(duì)手勢(shì)滑動(dòng)事件進(jìn)行一系列的處理等等。當(dāng)然了,這只是一個(gè)大致的步驟,因人而異吧,那么我們就按這個(gè)大致的步驟一點(diǎn)點(diǎn)的分析吧

自定義屬性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RangeBarView">
        <attr name="rect_line_height" format="dimension"/>
        <attr name="rect_line_default_color" format="color"/>
        <attr name="rect_line_checked_color" format="color"/>
        <attr name="circle_radius" format="dimension"/>
        <attr name="circle_stroke_width" format="dimension"/>
        <attr name="left_circle_solid_color" format="color"/>
        <attr name="left_circle_stroke_color" format="color"/>
        <attr name="right_circle_solid_color" format="color"/>
        <attr name="right_circle_stroke_color" format="color"/>
        <attr name="range_text_size" format="dimension"/>
        <attr name="range_text_color" format="color"/>
        <attr name="view_text_space" format="dimension"/>
        <attr name="rect_price_desc_dialog_width" format="dimension"/>
        <attr name="rect_price_desc_dialog_color" format="color"/>
        <attr name="rect_price_desc_dialog_corner_radius" format="dimension"/>
        <attr name="rect_price_desc_text_size" format="dimension"/>
        <attr name="rect_price_desc_text_color" format="color"/>
        <attr name="rect_price_desc_space_to_progress" format="dimension"/>
    </declare-styleable>
</resources>

屬性有點(diǎn)多哈,具體的就不再詳細(xì)說(shuō)了,對(duì)著上面的效果圖再加上在下這拙劣的英文相信大家都能見(jiàn)(委)文(屈)識(shí)(各)意(位)了。
淡淡的憂(yōu)傷.png

接著我們看下如何去獲取這些自定義的屬性

   public RangeBarView(Context context) {
        this(context, null);
    }

    public RangeBarView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RangeBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化屬性值,同時(shí)將每一個(gè)屬性的默認(rèn)值設(shè)置在styles.xml文件中
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RangeBarView, 0, R.style.default_range_bar_value);
        int count = typedArray.getIndexCount();
        for (int i = 0; i < count; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.RangeBarView_rect_line_default_color:
                    rectLineDefaultColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_cdcd));
                    break;
                case R.styleable.RangeBarView_rect_line_checked_color:
                    rectLineCheckedColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_275D9D));
                    break;
                case R.styleable.RangeBarView_rect_line_height:
                    rectLineHeight = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.rect_line_height));
                    break;
                case R.styleable.RangeBarView_circle_radius:
                    circleRadius = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.circle_radius));
                    break;
                case R.styleable.RangeBarView_circle_stroke_width:
                    circleStrokeWidth = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.circle_stroke_width));
                    break;
                case R.styleable.RangeBarView_left_circle_solid_color:
                    leftCircleSolidColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_fff));
                    break;
                case R.styleable.RangeBarView_left_circle_stroke_color:
                    leftCircleStrokeColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_cdcd));
                    break;
                case R.styleable.RangeBarView_right_circle_solid_color:
                    rightCircleSolidColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_fff));
                    break;
                case R.styleable.RangeBarView_right_circle_stroke_color:
                    rightCircleStrokeColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_cdcd));
                    break;
                case R.styleable.RangeBarView_range_text_size:
                    textSize = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.item_text_size));
                    break;
                case R.styleable.RangeBarView_range_text_color:
                    textColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_333));
                    break;
                case R.styleable.RangeBarView_view_text_space:
                    spaceDistance = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.view_and_text_space));
                    break;
                case R.styleable.RangeBarView_rect_price_desc_dialog_width:
                    rectDialogWidth = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.rect_dialog_width));
                    break;
                case R.styleable.RangeBarView_rect_price_desc_dialog_color:
                    rectDialogColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_275D9D));
                    break;
                case R.styleable.RangeBarView_rect_price_desc_dialog_corner_radius:
                    rectDialogCornerRadius = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.rect_dialog_corner_radius));
                    break;
                case R.styleable.RangeBarView_rect_price_desc_text_size:
                    rectDialogTextSize = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.rect_dialog_text_size));
                    break;
                case R.styleable.RangeBarView_rect_price_desc_text_color:
                    rectDialogTextColor = typedArray.getColor(attr, ContextCompat.getColor(context, R.color.color_fff));
                    break;
                case R.styleable.RangeBarView_rect_price_desc_space_to_progress:
                    rectDialogSpaceToProgress = typedArray.getDimensionPixelSize(attr, getResources().getDimensionPixelSize(R.dimen.rect_dialog_space_to_progress));
                    break;
            }
        }
        typedArray.recycle();
        //初始化畫(huà)筆
        initPaints();
    }

如果開(kāi)發(fā)者忘記在布局文件中去設(shè)置這些自定義屬性的話(huà),我們需要給每個(gè)屬性一個(gè)默認(rèn)值,這里在styles.xml文件中進(jìn)行統(tǒng)一配置各個(gè)屬性默認(rèn)值

<style name="default_range_bar_value">
        <item name="rect_line_height">5dp</item>
        <item name="rect_line_default_color">#CDCDCD</item>
        <item name="rect_line_checked_color">#275D9D</item>
        <item name="circle_radius">10dp</item>
        <item name="circle_stroke_width">2dp</item>
        <item name="left_circle_solid_color">#D10773</item>
        <item name="left_circle_stroke_color">#275D9D</item>
        <item name="right_circle_solid_color">#4499FF</item>
        <item name="right_circle_stroke_color">#275D9D</item>
        <item name="range_text_size">16sp</item>
        <item name="range_text_color">#333333</item>
        <item name="view_text_space">10dp</item>
        <item name="rect_price_desc_dialog_width">85dp</item>
        <item name="rect_price_desc_dialog_color">#275D9D</item>
        <item name="rect_price_desc_dialog_corner_radius">15dp</item>
        <item name="rect_price_desc_text_size">12sp</item>
        <item name="rect_price_desc_text_color">#FFFFFF</item>
        <item name="rect_price_desc_space_to_progress">5dp</item>
    </style>

如果沒(méi)有在xml布局文件中設(shè)置自定義屬性的話(huà),代碼是不會(huì)走到for循環(huán)中的,到此,自定義屬性啰嗦完了,不過(guò)還是要提下注意點(diǎn),首先獲取完自定義屬性后要記得將TypedArray對(duì)象回收,即typedArray.recycle();其次,我們可以在此構(gòu)造方法中進(jìn)行畫(huà)筆等的初始化工作,所以,相信大家都知道為什么不能在onDraw方法中去實(shí)例化畫(huà)筆等對(duì)象了(因?yàn)閛nDraw會(huì)被多次調(diào)用,這樣就會(huì)new出來(lái)大量的對(duì)象,導(dǎo)致內(nèi)存抖動(dòng)厲害,造成頻繁的GC,使主線(xiàn)程阻塞,頁(yè)面卡頓)

測(cè)量

@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 width, height;
        int wSize = getPaddingLeft() + circleRadius*2 + getPaddingRight() + circleStrokeWidth*2;
        int hSize = getPaddingTop() + rectDialogCornerRadius*2 + triangleHeight + rectDialogSpaceToProgress + circleRadius*2 + circleStrokeWidth*2 + spaceDistance + textSize + getPaddingBottom();

        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(widthSize, wSize);
        }else {
            width = wSize;
        }

        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(heightSize, hSize);
        }else {
            height = hSize;
        }
        Log.e("TAG", "寬onMeasure----> "+width);
        setMeasuredDimension(width, height);
    }

測(cè)量的時(shí)候需要對(duì)寬高的不同Mode進(jìn)行不同的處理,主要有三種方式EXACTLY、AT_MOST和UNSPECIFIED這幾種模式相信大家都很熟悉了,這里不再啰嗦了。

onSizeChanged方法

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //控件實(shí)際寬度 = 寬度w(包含內(nèi)邊距的) - paddingLeft - paddingRight;
        Log.e("TAG", "寬----> "+w);

        realWidth = w - getPaddingLeft() - getPaddingRight();
        strokeRadius = circleRadius + circleStrokeWidth;
        rectDialogHeightAndSpace = rectDialogCornerRadius*2 + rectDialogSpaceToProgress;
        //左邊圓的圓心坐標(biāo)
        leftCircleObj = new CirclePoint();
        leftCircleObj.cx = getPaddingLeft() + strokeRadius;
        leftCircleObj.cy = getPaddingTop() + rectDialogHeightAndSpace + strokeRadius;
        //右邊圓的圓心坐標(biāo)
        rightCircleObj = new CirclePoint();
        rightCircleObj.cx = w - getPaddingRight() - strokeRadius;
        rightCircleObj.cy = getPaddingTop() + rectDialogHeightAndSpace + strokeRadius;
        //默認(rèn)圓角矩形進(jìn)度條
        rectLineCornerRadius = rectLineHeight / 2;//圓角半徑
        defaultCornerLineRect.left = getPaddingLeft() + strokeRadius;
        defaultCornerLineRect.top = getPaddingTop() + rectDialogHeightAndSpace + strokeRadius - rectLineCornerRadius;
        defaultCornerLineRect.right = w - getPaddingRight() - strokeRadius;
        defaultCornerLineRect.bottom = getPaddingTop() + rectDialogHeightAndSpace + strokeRadius + rectLineCornerRadius;
        //選中狀態(tài)圓角矩形進(jìn)度條
        selectedCornerLineRect.left = leftCircleObj.cx;
        selectedCornerLineRect.top = getPaddingTop() + rectDialogHeightAndSpace + strokeRadius - rectLineCornerRadius;
        selectedCornerLineRect.right = rightCircleObj.cx;
        selectedCornerLineRect.bottom = getPaddingTop() + rectDialogHeightAndSpace + strokeRadius + rectLineCornerRadius;
        //數(shù)值描述圓角矩形
        numberDescRect.left = w / 2 - rectDialogWidth/2;
        numberDescRect.top = getPaddingTop();
        numberDescRect.right = w / 2 + rectDialogWidth/2;
        numberDescRect.bottom = getPaddingTop() + rectDialogCornerRadius*2;
        //每一份對(duì)應(yīng)的距離
        perSlice = (realWidth - strokeRadius*2) / slice;
    }

我們可以在onSizeChanged方法中配置控件的初始狀態(tài)等,在這里我們可以看到我們實(shí)例化了兩個(gè)對(duì)象leftCircleObj = new CirclePoint();和rightCircleObj = new CirclePoint();正所謂一切事物皆對(duì)象(面向?qū)ο缶幊?/strong>),按照我之前的寫(xiě)法會(huì)分別畫(huà)左邊圓和右邊圓,這樣無(wú)疑產(chǎn)生了很多重復(fù)代碼,因?yàn)閮蓚€(gè)圓本質(zhì)上是一樣的。通過(guò)對(duì)象的方式便于管理,使用起來(lái)也很方便,當(dāng)然了,也是從閱讀很多優(yōu)秀大牛寫(xiě)的自定義控件中學(xué)到的。此控件不是那么復(fù)雜,所以在設(shè)計(jì)圓對(duì)象的時(shí)候只聲明了圓心坐標(biāo)倆變量,不僅如此,比如我們也可以將滑動(dòng)數(shù)據(jù)等統(tǒng)一放到一個(gè)對(duì)象中進(jìn)行管理

private class CirclePoint{
        //圓的圓心坐標(biāo)
        public int cx;
        public int cy;
    }

繪制

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //繪制中間圓角矩形線(xiàn)
        drawDefaultCornerRectLine(canvas);
        //繪制兩圓之間的圓角矩形
        drawSelectedRectLine(canvas);
        //畫(huà)左邊圓以及圓的邊框
        drawLeftCircle(canvas);
        //畫(huà)右邊圓以及圓的邊框
        drawRightCircle(canvas);
        //繪制文字
        drawBottomText(canvas);
        //繪制描述信息圓角矩形彈窗
        drawRectDialog(canvas);
        //繪制描述信息彈窗中的文字
        drawTextOfRectDialog(canvas);
        //繪制小三角形
        drawSmallTriangle(canvas);
    }

下面分別展示下這些組件的繪制,其實(shí)主要是坐標(biāo)的確定,只要坐標(biāo)知道了,那么繪制起來(lái)就一氣呵成了。其中,繪制小三角形用的是path進(jìn)行連線(xiàn)繪制

private void drawDefaultCornerRectLine(Canvas canvas) {
        canvas.drawRoundRect(defaultCornerLineRect, rectLineCornerRadius, rectLineCornerRadius, defaultLinePaint);
    }

    private void drawSelectedRectLine(Canvas canvas) {
        canvas.drawRoundRect(selectedCornerLineRect, rectLineCornerRadius, rectLineCornerRadius, selectedLinePaint);
    }
    
    private void drawLeftCircle(Canvas canvas) {
        canvas.drawCircle(leftCircleObj.cx, leftCircleObj.cy, circleRadius, leftCirclePaint);
        canvas.drawCircle(leftCircleObj.cx, leftCircleObj.cy, circleRadius, leftCircleStrokePaint);
    }

    private void drawRightCircle(Canvas canvas) {
        canvas.drawCircle(rightCircleObj.cx, rightCircleObj.cy, circleRadius, rightCirclePaint);
        canvas.drawCircle(rightCircleObj.cx, rightCircleObj.cy, circleRadius, rightCircleStrokePaint);
    }

    private void drawBottomText(Canvas canvas) {
        textPaint.setColor(textColor);
        textPaint.setTextSize(textSize);
        for (int i = 0; i <=slice; i++) {
            int value = i*sliceValue > maxValue ? maxValue : i*sliceValue + minValue;
            String text = String.valueOf(value);
            float textWidth = textPaint.measureText(text);
            canvas.drawText(text, i*perSlice - textWidth/2 + (getPaddingLeft() + strokeRadius), getPaddingTop()+rectDialogHeightAndSpace+strokeRadius*2+spaceDistance+textSize/2, textPaint);
        }
    }

    private void drawRectDialog(Canvas canvas) {
        if (isShowRectDialog) {
            canvas.drawRoundRect(numberDescRect, rectDialogCornerRadius, rectDialogCornerRadius, selectedLinePaint);
        }
    }
    
    private void drawTextOfRectDialog(Canvas canvas) {
        if (leftValue == minValue && (rightValue == maxValue || rightValue < maxValue)) {
            textDesc = rightValue+"萬(wàn)以下";
        } else if (leftValue > minValue && rightValue == maxValue) {
            textDesc = leftValue+"萬(wàn)以上";
        } else if (leftValue > minValue && rightValue < maxValue) {
            if (leftValue == rightValue) {
                textDesc = rightValue+"萬(wàn)以下";
            }else
                textDesc = leftValue+"-"+rightValue+"萬(wàn)";
        }

        if (isShowRectDialog) {
            textPaint.setColor(rectDialogTextColor);
            textPaint.setTextSize(rectDialogTextSize);
            float textWidth = textPaint.measureText(textDesc);
            float textLeft = numberDescRect.left + rectDialogWidth/2 - textWidth/2;
            canvas.drawText(textDesc, textLeft, getPaddingTop()+rectDialogCornerRadius+rectDialogTextSize/4, textPaint);
        }
    }

    private void drawSmallTriangle(Canvas canvas) {
        if (isShowRectDialog) {
            trianglePath.reset();
            trianglePath.moveTo(numberDescRect.left + rectDialogWidth/2 - triangleLength/2, getPaddingTop() + rectDialogCornerRadius*2);
            trianglePath.lineTo(numberDescRect.left + rectDialogWidth/2 + triangleLength/2, getPaddingTop() + rectDialogCornerRadius*2);
            trianglePath.lineTo(numberDescRect.left + rectDialogWidth/2, getPaddingTop() + rectDialogCornerRadius*2+triangleHeight);
            trianglePath.close();
            canvas.drawPath(trianglePath, selectedLinePaint);
        }
    }

然后是對(duì)手勢(shì)滑動(dòng)的處理

對(duì)于手勢(shì)滑動(dòng)的處理還是比較麻煩和繁瑣的,首先我們需要確定當(dāng)前滑動(dòng)的是左邊圓還是右邊圓,可以通過(guò)如下方式進(jìn)行判斷,為了方便大家理解,我在代碼中寫(xiě)了詳細(xì)的注釋?zhuān)淮螣o(wú)意間的機(jī)會(huì)看到CodeCopyer大牛的文章中關(guān)于點(diǎn)擊屬于哪個(gè)位置用到了Region(Region表示多個(gè)圖形組成的區(qū)域范圍,一般判斷某一點(diǎn)(按下的坐標(biāo))是否在某一個(gè)區(qū)域范圍內(nèi)),又get到了一項(xiàng)技能,在這里表示感謝,感興趣的小伙伴可以用此方式實(shí)現(xiàn)下

private boolean checkIsLeftOrRight(float downX) {
        //如果按下的區(qū)域位于左邊區(qū)域,則按下坐標(biāo)downX的值就會(huì)比較小(即按下坐標(biāo)點(diǎn)在左邊),那么leftCircleObj.cx - downX的絕對(duì)值也會(huì)比較小
        //rightCircleObj.cx - downX絕對(duì)值肯定是大于leftCircleObj.cx - downX絕對(duì)值的,兩者相減肯定是小于0的
        if (Math.abs(leftCircleObj.cx - downX) - Math.abs(rightCircleObj.cx - downX) > 0) {//表示按下的區(qū)域位于右邊
            return false;
        }
        return true;
    }

接著需要對(duì)起始位置以及終點(diǎn)位置的邊界進(jìn)行處理,防止越界,兩圓總不能滑出起始位置或者終點(diǎn)位置吧,其實(shí)對(duì)邊界的處理還是很簡(jiǎn)單的,比如左邊圓滑動(dòng)坐標(biāo)小于起始點(diǎn)的坐標(biāo)時(shí),我們就把起始點(diǎn)的坐標(biāo)重新賦值給這個(gè)圓的圓心坐標(biāo),右邊臨界點(diǎn)的判斷也是類(lèi)似

       //防止越界處理
        if (touchLeftCircle) {
            if (leftCircleObj.cx > rightCircleObj.cx) {
                leftCircleObj.cx = rightCircleObj.cx;
            }else {
                if (leftCircleObj.cx < getPaddingLeft() + strokeRadius) {
                    leftCircleObj.cx = getPaddingLeft() + strokeRadius;
                }
                if (leftCircleObj.cx > getWidth() - getPaddingRight() - strokeRadius) {
                    leftCircleObj.cx = getWidth() - getPaddingRight() - strokeRadius;
                }
            }
        }else {
            if (leftCircleObj.cx > rightCircleObj.cx) {
                rightCircleObj.cx  = leftCircleObj.cx;
            }else {
                if (rightCircleObj.cx > getWidth() - getPaddingRight() - strokeRadius) {
                    rightCircleObj.cx = getWidth() - getPaddingRight() - strokeRadius;
                }

                if (rightCircleObj.cx < getPaddingLeft() + strokeRadius) {
                    rightCircleObj.cx = getPaddingLeft() + strokeRadius;
                }
            }
        }

以及兩圓相遇時(shí)的處理,總不能確認(rèn)過(guò)眼神就是對(duì)的人吧,這塊處理需要特別的注意,就像文章開(kāi)頭對(duì)控件的分析中說(shuō)到的幾種情況,①兩圓在起始位置處相遇②兩圓在終點(diǎn)位置處相遇③兩圓在中間某一處相遇。對(duì)于情況①和②情況比較相似,這里就統(tǒng)一啰嗦下,滑動(dòng)右邊圓在起始位置處和左邊圓相遇后,再次向右滑動(dòng),那么要保證左邊圓不動(dòng),右邊圓繼續(xù)向右滑動(dòng);當(dāng)左邊圓在終點(diǎn)處與右邊圓相遇時(shí)也是同樣道理。對(duì)于情況③當(dāng)滑動(dòng)左邊圓在中間某一處與右邊圓相遇時(shí),繼續(xù)向右滑動(dòng),注意此時(shí)左邊圓停留在之前右邊圓的位置處不動(dòng),而右邊圓則繼續(xù)向右滑動(dòng)(根據(jù)圖二可以很清晰的觀(guān)察出滑動(dòng)的規(guī)律),用代碼表示就是在move時(shí)進(jìn)行判斷處理

            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                isShowRectDialog = true;
                if (leftCircleObj.cx == rightCircleObj.cx) {//兩圓圈重合的情況
                    if (touchLeftCircle) {
                        //極端情況的優(yōu)化處理,滑動(dòng)左邊圓到達(dá)最右邊時(shí),再次滑動(dòng)時(shí)設(shè)置為左滑,即:繼續(xù)讓左邊圓向左滑動(dòng)
                        if (leftCircleObj.cx == getWidth() - getPaddingRight() - strokeRadius) {
                            touchLeftCircle = true;
                            leftCircleObj.cx = (int) moveX;
                        }else {
                            //當(dāng)滑動(dòng)左邊圓在中間某處與右邊圓重合時(shí),此時(shí)再次繼續(xù)滑動(dòng)則左邊圓處于右邊圓位置處不動(dòng),右邊圓改為向右滑動(dòng)
                            touchLeftCircle = false;
                            rightCircleObj.cx = (int) moveX;
                        }
                    }else {
                        if (rightCircleObj.cx == getPaddingLeft() + strokeRadius) {
                            touchLeftCircle = false;
                            rightCircleObj.cx = (int) moveX;
                        }else {
                            touchLeftCircle = true;
                            leftCircleObj.cx = (int) moveX;
                        }
                    }
                }else {
                    if (touchLeftCircle) {
                        //滑動(dòng)左邊圓圈時(shí),如果位置等于或者超過(guò)右邊圓的位置時(shí),設(shè)置右邊圓圈的坐標(biāo)給左邊圓圈,就相當(dāng)于左邊圓圈停留在右邊圓圈之前的位置上,然后移動(dòng)右邊圓圈
                        leftCircleObj.cx = leftCircleObj.cx - rightCircleObj.cx >= 0 ? rightCircleObj.cx : (int) moveX;
                    }else {
                        //同理
                        rightCircleObj.cx = rightCircleObj.cx - leftCircleObj.cx <= 0 ? leftCircleObj.cx : (int) moveX;
                    }
                }

                break;

其次還要保證在兩圓滑動(dòng)的過(guò)程中最上方顯示價(jià)格信息描述的圓角矩形彈窗始終位于兩圓的中間位置,還有就是文章開(kāi)頭分析的,當(dāng)滑動(dòng)的距離小于步長(zhǎng)的一半時(shí),松開(kāi)滑動(dòng),圓需要回彈到上一個(gè)位置處,很多細(xì)節(jié)都是需要處理的,在寫(xiě)的過(guò)程中會(huì)遇到很多奇葩的問(wèn)題,需要有足夠的耐心慢慢去調(diào)試,最后我們需要在手指抬起也就是up時(shí)將數(shù)據(jù)回調(diào)給UI進(jìn)行展示

            case MotionEvent.ACTION_UP:
                if (touchLeftCircle) {
                    int partsOfLeft = getSliceByCoordinate((int) event.getX());
                    leftCircleObj.cx = leftCircleObj.cx - rightCircleObj.cx >= 0 ? rightCircleObj.cx : partsOfLeft*perSlice+strokeRadius;
                }else {
                    int partsOfRight = getSliceByCoordinate((int) event.getX());
                    rightCircleObj.cx = rightCircleObj.cx - leftCircleObj.cx <= 0 ? leftCircleObj.cx : partsOfRight*perSlice+strokeRadius;
                }

                int leftData = getSliceByCoordinate(leftCircleObj.cx)*sliceValue + minValue;
                int rightData = getSliceByCoordinate(rightCircleObj.cx)*sliceValue + minValue;
                leftValue = leftData > maxValue ? maxValue : leftData;
                rightValue = rightData > maxValue ? maxValue : rightData;
                //回調(diào)
                if (listener != null) {
                    listener.onMoveValue(leftValue, rightValue);
                }
                break;
        }

最后,我們?cè)賮?lái)看下布局文件以及Activity中如何調(diào)用展示

<com.ch.custom.view.RangeBarView
        android:id="@+id/view_range_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        app:rect_line_default_color="@color/color_cdcd"
        app:rect_line_checked_color="@color/color_275D9D"
        app:left_circle_solid_color="@color/color_fff"
        app:left_circle_stroke_color="@color/color_cdcd"
        app:right_circle_solid_color="@color/color_fff"
        app:right_circle_stroke_color="@color/color_cdcd"
        app:circle_stroke_width="2dp"
        app:circle_radius="15dp"
        app:rect_line_height="3dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        />

以及Activity

        int minValue = 0;
        int maxValue = 100;
        int sliceValue = 20;
        tvLeftValue.setText(minValue+"");
        tvRightValue.setText(maxValue+"");
        rangeBarView.setDatas(minValue, maxValue, sliceValue, new RangeBarView.OnMoveValueListener() {
            @Override
            public void onMoveValue(int leftValue, int rightValue) {
                tvLeftValue.setText("左邊值為:" + leftValue + "--> "+leftValue);
                tvRightValue.setText("右邊值為:" + rightValue + "--> "+rightValue);
            }
        });

我們可以看到開(kāi)發(fā)者使用起來(lái)也比較方便,這里不但需要一個(gè)最大值還需要一個(gè)最小值,這樣設(shè)計(jì)的目的是,有的需求并不是從0到某一個(gè)數(shù)值,比如,也有可能是從50-1000這樣的,同時(shí)還需要告訴程序小圓每移動(dòng)一下代表的數(shù)值是多少,然后根據(jù)這些數(shù)據(jù)可以算出此控件在此數(shù)據(jù)范圍內(nèi)一共可以分多少份,對(duì)于除不盡的話(huà)我們會(huì)增加一份來(lái)表示剩下的一點(diǎn)數(shù)據(jù)。這里還要感謝Nipuream老鐵的文章給的靈感,感興趣的話(huà)可以點(diǎn)擊這里查看大牛文章,今天就先寫(xiě)到這吧,同事都早已下班回去嗨皮的過(guò)雙休了,我也要撤了,代碼寫(xiě)的有些匆忙,難免會(huì)存在些問(wèn)題,如有問(wèn)題歡迎大家提出,我們共同交流處理

github源碼下載地址:https://github.com/smileCH/RangeBar

最新更新說(shuō)明(以github上最新代碼為主)

昨天看到github上有小伙伴在使用此控件時(shí)遇到一些問(wèn)題,并給我提了issues,看到后我第一時(shí)間對(duì)此控件進(jìn)行了修復(fù),解決了設(shè)置padding值導(dǎo)致滑動(dòng)位置不準(zhǔn)確的問(wèn)題,優(yōu)化了滑動(dòng)松手后小圓自動(dòng)回彈的計(jì)算問(wèn)題以及開(kāi)發(fā)者設(shè)置的份數(shù)非常多的情況導(dǎo)致小圓無(wú)法滑動(dòng)到最大位置,這些問(wèn)題我都修復(fù)了,并且在代碼中標(biāo)注了日期以及修復(fù)思路,這樣大家就能快速定位代碼,加上注釋理解起來(lái)就會(huì)很順暢。大家可以放心用于商業(yè)項(xiàng)目中。

考慮到有的老鐵可能只需要單項(xiàng)的滑動(dòng),所以后期打算做成可以根據(jù)用戶(hù)的選擇來(lái)決定是使用雙向滑動(dòng)還是單項(xiàng)滑動(dòng),只需要在application中初始化的時(shí)候告訴我你要使用哪種方式就行,這樣才更靈活

2018/10/25更新說(shuō)明

最近我司對(duì)價(jià)格選擇又重新設(shè)計(jì)了UI效果
dialog_price.png

根據(jù)上面的效果圖我們的產(chǎn)品要求點(diǎn)擊下面的價(jià)格,例如¥0 - ¥150,那么相應(yīng)的上面雙向滑動(dòng)的價(jià)格控件中的兩個(gè)小圓也要相應(yīng)移動(dòng)到指定數(shù)值對(duì)應(yīng)的坐標(biāo)位置處,所以,這里我又對(duì)外開(kāi)放了一個(gè)setCircleMoveCoordinateByValue(int minData, int maxData)方法,只需要把數(shù)值區(qū)間傳給我們的滑動(dòng)控件就行了,兩個(gè)小圓會(huì)滑動(dòng)到指定位置處。感興趣的話(huà)可以到github上查看最新代碼,如果你也在使用此控件并且用的不爽的話(huà),歡迎提出,我會(huì)盡力減少你的困擾

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,802評(píng)論 25 709
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,918評(píng)論 2 59
  • 暖陽(yáng)朗乾坤,和風(fēng)拂春意??莶莅轱L(fēng)舞,落葉隨氣離。冰雪終消融,綠息欲臨地。裸樹(shù)浴陽(yáng)光,萬(wàn)物吸潤(rùn)氣。群鳥(niǎo)嬉一樹(shù),啼鳴揚(yáng)...
    靜淵尋找詩(shī)意的蝶閱讀 110評(píng)論 0 0
  • XJ facetime水凝瘦臉貼 提拉緊致v臉面膜 產(chǎn)品主要成分:高分子聚合物 杜松果 大茴香類(lèi)精油 甘油 ...
    mopeacename閱讀 213評(píng)論 0 0
  • 有一年,我和老板在珠海過(guò)關(guān)去澳門(mén)的時(shí)候,被一個(gè)乞丐扯住了。乞丐身強(qiáng)力壯,一副不給錢(qián)不讓你走的樣子。 那個(gè)時(shí)候我23...
    祁國(guó)瑜閱讀 335評(píng)論 1 2

友情鏈接更多精彩內(nèi)容