高仿Uber的类型选择控件

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

效果图:

实现过程:

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

属性:


<resources>
    <declare-styleable name="LevelLayout">
        
        <attr name="stepCount" format="integer"/>
        
        <attr name="defaultPos" format="integer"/>
        
        <attr name="thumb" format="reference"/>
        
        <attr name="thumbWidth" format="dimension"/>
        
        <attr name="thumbHeight" format="dimension"/>
        
        <attr name="lineWidth" format="dimension"/>
        
        <attr name="lineColor" format="color"/>
        
        <attr name="pointRadius" format="dimension"/>
        
        <attr name="pointColor" format="color"/>
        
        <attr name="scrollDuration" format="integer"/>
    declare-styleable>
resources>

onDraw过程:

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

滑动过程:

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

使用:

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

<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"/>

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

    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接口即可:

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: 由于作者水平有限,如意见或建议,欢迎吐槽。。。

你可能感兴趣的:(Android)