仿瓜子二手車雙向滑動的價格選擇控件

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

項目中的酒店模塊有個雙向滑動的價格選擇器控件潘鲫,感覺不是很滿意,所以就趁著剛發(fā)完版本這段空閑時間自己重新自定義了一個儒飒,效果和瓜子二手車中的價格選擇器有點相似,啰嗦了半天,客官消消氣剃允,小的這就上圖:

range_price.gif
從效果圖上可以大致發(fā)現(xiàn),此控件的一些特點
1齐鲤、可以靈活設(shè)置步長值斥废,開發(fā)者只需要傳入最小值、最大值以及每一步代表的數(shù)值即可
2给郊、當(dāng)滑動距離小于步長距離的一半時松開會自動回彈到上一個位置處牡肉,相反則會自動回彈到下一個位置處
3、當(dāng)兩圓不在兩極端位置時(即:兩圓在起始位置和終點位置之間)淆九,當(dāng)滑動左邊圓圈靠近到右邊圓時统锤,繼續(xù)右滑則左邊圓位于之前右邊圓的位置不再動毛俏,而右邊圓則會繼續(xù)向右邊滑動;滑動右邊圓時同理
4饲窿、當(dāng)滑動左邊圓到達最右邊圓的終點位置時再向左滑動煌寇,右邊圓不動,左邊圓繼續(xù)向左滑動逾雄,右邊圓情況同理
5阀溶、允許兩圓相重合,重合時則代表的數(shù)值相同
這樣說可能還是不太好理解鸦泳,我們把每個圓用不同的顏色來區(qū)分開银锻,如下圖所示
range_bar.gif
這樣再看是不是很直觀了
之前對于自定義View也寫了很多文章了,如果有興趣的話可以到我的CSDN博客中去了解下做鹰,其實一般的自定義控件都需要這幾步击纬,首先,對需求仔細(xì)研究思考并且在自己的本子上畫畫草圖進行結(jié)構(gòu)分析(很有用)誊垢,然后就是自定義屬性了掉弛,假如你打算開源的話,那么自定義屬性是必不可少的喂走,這樣便于使用者根據(jù)自己需要進行靈活設(shè)置殃饿,接著就是測量了,確定控件的寬高芋肠,緊接著就可以進行繪制了乎芳,當(dāng)然了如果是繼承ViewGroup的話則還需要去確定子View的位置,如果涉及到手勢滑動等操作帖池,最后還需要對手勢滑動事件進行一系列的處理等等奈惑。當(dāng)然了,這只是一個大致的步驟睡汹,因人而異吧肴甸,那么我們就按這個大致的步驟一點點的分析吧

自定義屬性

<?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>

屬性有點多哈,具體的就不再詳細(xì)說了囚巴,對著上面的效果圖再加上在下這拙劣的英文相信大家都能見(委)文(屈)識(各)意(位)了原在。
淡淡的憂傷.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);
        //初始化屬性值,同時將每一個屬性的默認(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();
        //初始化畫筆
        initPaints();
    }

如果開發(fā)者忘記在布局文件中去設(shè)置這些自定義屬性的話彤叉,我們需要給每個屬性一個默認(rèn)值庶柿,這里在styles.xml文件中進行統(tǒng)一配置各個屬性默認(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>

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

測量

@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);
    }

測量的時候需要對寬高的不同Mode進行不同的處理沸柔,主要有三種方式EXACTLY循衰、AT_MOST和UNSPECIFIED這幾種模式相信大家都很熟悉了,這里不再啰嗦了褐澎。

onSizeChanged方法

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //控件實際寬度 = 寬度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)圓角矩形進度條
        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)圓角矩形進度條
        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;
        //每一份對應(yīng)的距離
        perSlice = (realWidth - strokeRadius*2) / slice;
    }

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

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

繪制

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

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

下面分別展示下這些組件的繪制澡罚,其實主要是坐標(biāo)的確定,只要坐標(biāo)知道了肾请,那么繪制起來就一氣呵成了留搔。其中,繪制小三角形用的是path進行連線繪制

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+"萬以下";
        } 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) {
            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);
        }
    }

然后是對手勢滑動的處理

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

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

接著需要對起始位置以及終點位置的邊界進行處理奸柬,防止越界生年,兩圓總不能滑出起始位置或者終點位置吧,其實對邊界的處理還是很簡單的廓奕,比如左邊圓滑動坐標(biāo)小于起始點的坐標(biāo)時抱婉,我們就把起始點的坐標(biāo)重新賦值給這個圓的圓心坐標(biāo),右邊臨界點的判斷也是類似

       //防止越界處理
        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;
                }
            }
        }

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

            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                isShowRectDialog = true;
                if (leftCircleObj.cx == rightCircleObj.cx) {//兩圓圈重合的情況
                    if (touchLeftCircle) {
                        //極端情況的優(yōu)化處理仪或,滑動左邊圓到達最右邊時,再次滑動時設(shè)置為左滑士骤,即:繼續(xù)讓左邊圓向左滑動
                        if (leftCircleObj.cx == getWidth() - getPaddingRight() - strokeRadius) {
                            touchLeftCircle = true;
                            leftCircleObj.cx = (int) moveX;
                        }else {
                            //當(dāng)滑動左邊圓在中間某處與右邊圓重合時范删,此時再次繼續(xù)滑動則左邊圓處于右邊圓位置處不動,右邊圓改為向右滑動
                            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) {
                        //滑動左邊圓圈時拷肌,如果位置等于或者超過右邊圓的位置時到旦,設(shè)置右邊圓圈的坐標(biāo)給左邊圓圈,就相當(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)滑動的距離小于步長的一半時若锁,松開滑動搁骑,圓需要回彈到上一個位置處,很多細(xì)節(jié)都是需要處理的,在寫的過程中會遇到很多奇葩的問題仲器,需要有足夠的耐心慢慢去調(diào)試煤率,最后我們需要在手指抬起也就是up時將數(shù)據(jù)回調(diào)給UI進行展示

            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;
        }

最后,我們再來看下布局文件以及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);
            }
        });

我們可以看到開發(fā)者使用起來也比較方便乏冀,這里不但需要一個最大值還需要一個最小值蝶糯,這樣設(shè)計的目的是,有的需求并不是從0到某一個數(shù)值辆沦,比如昼捍,也有可能是從50-1000這樣的,同時還需要告訴程序小圓每移動一下代表的數(shù)值是多少肢扯,然后根據(jù)這些數(shù)據(jù)可以算出此控件在此數(shù)據(jù)范圍內(nèi)一共可以分多少份端三,對于除不盡的話我們會增加一份來表示剩下的一點數(shù)據(jù)。這里還要感謝Nipuream老鐵的文章給的靈感鹃彻,感興趣的話可以點擊這里查看大牛文章郊闯,今天就先寫到這吧,同事都早已下班回去嗨皮的過雙休了蛛株,我也要撤了团赁,代碼寫的有些匆忙,難免會存在些問題谨履,如有問題歡迎大家提出欢摄,我們共同交流處理

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

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

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

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

2018/10/25更新說明

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末仪吧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鞠眉,更是在濱河造成了極大的恐慌薯鼠,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件械蹋,死亡現(xiàn)場離奇詭異出皇,居然都是意外死亡,警方通過查閱死者的電腦和手機哗戈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門郊艘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人唯咬,你說我怎么就攤上這事纱注。” “怎么了胆胰?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵狞贱,是天一觀的道長。 經(jīng)常有香客問我蜀涨,道長瞎嬉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任厚柳,我火速辦了婚禮氧枣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘别垮。我一直安慰自己挑胸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布宰闰。 她就那樣靜靜地躺著茬贵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪移袍。 梳的紋絲不亂的頭發(fā)上解藻,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音葡盗,去河邊找鬼螟左。 笑死啡浊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胶背。 我是一名探鬼主播巷嚣,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钳吟!你這毒婦竟也來了廷粒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤红且,失蹤者是張志新(化名)和其女友劉穎坝茎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暇番,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡嗤放,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了壁酬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片次酌。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舆乔,靈堂內(nèi)的尸體忽然破棺而出和措,到底是詐尸還是另有隱情,我是刑警寧澤蜕煌,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布派阱,位于F島的核電站,受9級特大地震影響斜纪,放射性物質(zhì)發(fā)生泄漏贫母。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一盒刚、第九天 我趴在偏房一處隱蔽的房頂上張望腺劣。 院中可真熱鬧,春花似錦因块、人聲如沸橘原。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趾断。三九已至,卻和暖如春吩愧,著一層夾襖步出監(jiān)牢的瞬間芋酌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工雁佳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脐帝,地道東北人同云。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亡电,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,114評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料旱易? 從這篇文章中你...
    hw1212閱讀 12,723評論 2 59
  • 暖陽朗乾坤,和風(fēng)拂春意荡含。枯草伴風(fēng)舞届垫,落葉隨氣離释液。冰雪終消融,綠息欲臨地装处。裸樹浴陽光误债,萬物吸潤氣。群鳥嬉一樹妄迁,啼鳴揚...
    靜淵尋找詩意的蝶閱讀 95評論 0 0
  • XJ facetime水凝瘦臉貼 提拉緊致v臉面膜 產(chǎn)品主要成分:高分子聚合物 杜松果 大茴香類精油 甘油 ...
    mopeacename閱讀 196評論 0 0
  • 有一年登淘,我和老板在珠海過關(guān)去澳門的時候箫老,被一個乞丐扯住了。乞丐身強力壯黔州,一副不給錢不讓你走的樣子耍鬓。 那個時候我23...
    祁國瑜閱讀 308評論 1 2