高仿Uber的类型选择控件

使用过Uber的朋友应该都发现了它的选择控件,感觉很人性化。之前的项目中也用到了,当时是通过重写SeekBar的onDraw方法来实现的。实现之后发现在魅族手机上,监听滑动过程时onStartTrackingTouch中获取的progress总是有问题,不能实现当时滑动时thumb动态变化的需求,随后又重写了SeekBar的onTouchEvent方法,动态对OnSeekBarChangeListener进行了调用。最后虽然实现了需求,但这个控件却有很多不足之处,所以这次直接继承View来实现了一款类似的等级选择控件。

效果图:

实现过程:

对于这种控件,其实主要就是重写onDraw方法,绘制点,绘制连线,绘制thumb。然后重写onTouchEvent,在滑动过程中动态改变属性并进行刷新View即可。

属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LevelLayout">
<!-- the count of step that equals levels sub one-->
<attr name="stepCount" format="integer"/>
<!-- the default position of chosen level -->
<attr name="defaultPos" format="integer"/>
<!-- the icon of level -->
<attr name="thumb" format="reference"/>
<!-- the width of level's icon -->
<attr name="thumbWidth" format="dimension"/>
<!-- the height of level's icon -->
<attr name="thumbHeight" format="dimension"/>
<!-- the width of line between level and level -->
<attr name="lineWidth" format="dimension"/>
<!-- the color of line between level and level -->
<attr name="lineColor" format="color"/>
<!-- the radius of point where is located level -->
<attr name="pointRadius" format="dimension"/>
<!-- the color of point where is located level -->
<attr name="pointColor" format="color"/>
<!-- the speed of thumb scrolling -->
<attr name="scrollDuration" format="integer"/>
</declare-styleable>
</resources>

onDraw过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight() - getPaddingTop() - getPaddingBottom();
int startX = getPaddingLeft();
int centerHeight = height / 2;

// set paint of line
Paint linePaint = new Paint();
linePaint.setStrokeWidth(lineWidth);
linePaint.setColor(lineColor);
linePaint.setAntiAlias(true);

// set paint of point
Paint pointPaint = new Paint();
pointPaint.setStrokeWidth(pointRadius);
pointPaint.setColor(pointColor);
pointPaint.setAntiAlias(true);

// get the length of step
stepLength = (getWidth() - getPaddingLeft() - getPaddingRight() - thumbWidth) * 1.0f / stepCount;
if (thumb != null) {
final Bitmap bitmap = Bitmap.createScaledBitmap(drawableToBitmap(thumb), (int) thumbWidth, (int) thumbHeight, true);
float bitmapX = startX += bitmap.getWidth() / 2;
canvas.drawLine(startX, centerHeight, startX + stepLength*stepCount, centerHeight, linePaint);
for (int i = 0; i < stepCount; i++) {
if (defaultPos == i) {
bitmapX = startX - bitmap.getWidth() / 2 + offset;
}
canvas.drawCircle(startX, centerHeight, pointRadius, pointPaint);
startX += stepLength;

}
canvas.drawCircle(startX, centerHeight, pointRadius, pointPaint);
if (defaultPos == stepCount) {
bitmapX = startX - bitmap.getWidth() / 2 + offset;
}
canvas.drawBitmap(bitmap, bitmapX, (getHeight() - thumbHeight) / 2, pointPaint);
}
}

滑动过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
oldPosition = defaultPos;
startPos = event.getX();
startX = event.getX();
defaultPos = (int)((startPos - getPaddingLeft()) / stepLength);
listener.onStartTrackingTouch();
break;
case MotionEvent.ACTION_MOVE:
// avoid thumb scrolls to extra area
if (event.getX() > 0 && event.getX() < getWidth()-thumbWidth/2) {
offset += (event.getX() - startX);
startX = event.getX();
Log.i("out", "X===" + event.getX() + " offset==" + offset);
if (Math.abs(offset) > DEFAULT_CLICK_RANGE) {
listener.onLevelChanged((int) (event.getX() - thumbWidth / 2 - getPaddingLeft()));
invalidate();
}
}
break;
case MotionEvent.ACTION_UP:
// get latest position of thumb
defaultPos = (int) ((event.getX() - getPaddingLeft()) / stepLength);
// avoid thumb scrolls to extra area
if (event.getX() > 0 && event.getX() < getWidth()) {
// get the distance of up and down
offset = event.getX() - startPos;
if (Math.abs(offset) < DEFAULT_CLICK_RANGE) { // thumb's click event
offset = (event.getX() - getPaddingLeft()) % stepLength;
if (offset > stepLength / 2) {
defaultPos++;
}
final float distance = (oldPosition - defaultPos) * stepLength;
if (distance != 0) {
ValueAnimator animator = ValueAnimator.ofFloat(distance, 0).setDuration(scrollDuration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
offset = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.start();
} else {
listener.onLevelClick();
}
} else if (offset > DEFAULT_CLICK_RANGE) { // scroll from left to right
offset = offset % stepLength;
if (offset > stepLength / 2 && defaultPos < stepCount) {
defaultPos++;
}
} else { // scroll from right to left
offset = offset % stepLength;
if (Math.abs(offset) < stepLength / 2 && defaultPos < stepCount) {
defaultPos++;
}
}
listener.onStopTrackingTouch();
}
offset = 0;
invalidate();
break;
default:
break;
}

return true;
}

使用:

和其他自定义控件一样,在布局文件中直接引用即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<com.sxu.levellayout.LevelView
android:id="@+id/level_view"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#FFFFFF"
level:thumb="@drawable/star_3"
level:thumbWidth="30dp"
level:thumbHeight="30dp"
level:pointRadius="6dp"
level:pointColor="#eeeeee"
level:lineWidth="4dp"
level:lineColor="#eeeeee"
level:stepCount="3"
level:defaultPos="2"/>

当然,对于这些自定义属性也可以动态进行设置:

1
2
3
4
5
6
7
8
9
10
levelView.setThumb(getResources().getDrawable(icon[3]));
levelView.setThumbWidth(90);
levelView.setThumbHeight(90);
levelView.setPointColor(Color.parseColor("#FF0000"));
levelView.setLineColor(Color.parseColor("#00FF00"));
levelView.setLineWidth(4);
levelView.setPointRadius(50);
levelView.setDefaultPos(1);
levelView.setStepCount(2);
levelView.setScrollDuration(300);

对于滑动过程中需要做的工作,只需要实现OnLevelChangeListener接口即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface OnLevelChangeListener {
/**
* It is called when thumb scrolling.
* @param progress means current position
*/
void onLevelChanged(int progress);

/**
* It is called when thumb is pressed.
*/
void onStartTrackingTouch();

/**
* It is called when thumb is up.
*/
void onStopTrackingTouch();

/**
* It is called when thumb is clicked.
*/
void onLevelClick();
}

源码下载:

LevelView源码

PS: 由于作者水平有限,如意见或建议,欢迎吐槽。。。

,