*本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布
項目中的酒店模塊有個雙向滑動的價格選擇器控件潘鲫,感覺不是很滿意,所以就趁著剛發(fā)完版本這段空閑時間自己重新自定義了一個儒飒,效果和瓜子二手車中的價格選擇器有點相似,啰嗦了半天,客官消消氣剃允,小的這就上圖:
1齐鲤、可以靈活設(shè)置步長值斥废,開發(fā)者只需要傳入最小值、最大值以及每一步代表的數(shù)值即可
2给郊、當(dāng)滑動距離小于步長距離的一半時松開會自動回彈到上一個位置處牡肉,相反則會自動回彈到下一個位置處
3、當(dāng)兩圓不在兩極端位置時(即:兩圓在起始位置和終點位置之間)淆九,當(dāng)滑動左邊圓圈靠近到右邊圓時统锤,繼續(xù)右滑則左邊圓位于之前右邊圓的位置不再動毛俏,而右邊圓則會繼續(xù)向右邊滑動;滑動右邊圓時同理
4饲窿、當(dāng)滑動左邊圓到達最右邊圓的終點位置時再向左滑動煌寇,右邊圓不動,左邊圓繼續(xù)向左滑動逾雄,右邊圓情況同理
5阀溶、允許兩圓相重合,重合時則代表的數(shù)值相同
這樣說可能還是不太好理解鸦泳,我們把每個圓用不同的顏色來區(qū)分開银锻,如下圖所示
之前對于自定義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ì)說了囚巴,對著上面的效果圖再加上在下這拙劣的英文相信大家都能見(委)文(屈)識(各)意(位)了原在。接著我們看下如何去獲取這些自定義的屬性
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效果根據(jù)上面的效果圖我們的產(chǎn)品要求點擊下面的價格赴精,例如¥0 - ¥150佩捞,那么相應(yīng)的上面雙向滑動的價格控件中的兩個小圓也要相應(yīng)移動到指定數(shù)值對應(yīng)的坐標(biāo)位置處,所以蕾哟,這里我又對外開放了一個setCircleMoveCoordinateByValue(int minData, int maxData)方法一忱,只需要把數(shù)值區(qū)間傳給我們的滑動控件就行了啊奄,兩個小圓會滑動到指定位置處。感興趣的話可以到github上查看最新代碼掀潮,如果你也在使用此控件并且用的不爽的話,歡迎提出琼富,我會盡力減少你的困擾