Android自定义横向可拖动进度条

1,521 阅读4分钟

因为公司业务需要,最近用到了可拖动进度条,第一反应是使用SeekBar,用完调试才发现无法满足公司业务需求和UI的设计效果,于是大致浏览了一下自定义可拖动进度条,便用自定义view手写了一个可拖动进度条。

功能:

  • 1、可控制拖动与禁止拖动进度条;
  • 2、可自定义进度条高度、进度指示器圆点半径;
  • 3、可自定义进度条背景颜色、进度条进度颜色、进度指示器圆点颜色;
  • 4、可自定义禁止拖动状态下 进度条进度颜色、进度指示器圆点颜色。

可拖动状态下示例:

禁止拖动状态下示例:

话不多说,上代码。

1、自定义view部分代码:

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.Nullable;

/**
 * 自定义可拖动的进度条
 */
public class DragProgressView extends View {

    // 灰色背景线段的画笔
    private Paint bgPaint;
    // 实际进度绿色线段的画笔
    private Paint progressPaint;
    // 圆点指示器的画笔
    private Paint circlePaint;
    // 进度条的最大宽度
    private float maxProgress;
    // 进度条当前的宽度
    private float currentProgress;
    // 当前View的宽度
    private int width;
    // 当前View的高度
    private int height;
    // 距离左边的内边距
    private int paddingLeft;
    // 距离右边的内边距
    private int paddingRight;
    // 是否可拖动
    private boolean isDrag = true;
    // 圆点指示器的半径 默认10dp
    private int mCircleRadius;
    // 进度条高度 默认10dp
    private int progressHeight;
    // 进度条背景颜色
    private int progressBackgroundColor;
    // 进度条颜色
    private int progressColor;
    // 圆形指示器颜色
    private int circleColor;
    // 圆形指示器阴影颜色
    private int circleShadowColor;
    // 进度条不可拖动时颜色
    private int progressColorN;
    // 圆形指示器不可拖动时颜色
    private int circleColorN;
    // 圆形指示器阴影不可拖动时颜色
    private int circleShadowColorN;

    private Context mContext;

    private String TAG = getClass().getSimpleName();

    public DragProgressView(Context context) {
        super(context);
    }

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

    public DragProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getAttrs(context, attrs);
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 进度条背景画笔
        bgPaint = new Paint();
        bgPaint.setColor(progressBackgroundColor);  // 进度条背景颜色
        bgPaint.setStyle(Paint.Style.FILL_AND_STROKE);      // 填充且描边
        bgPaint.setAntiAlias(true);                 // 抗锯齿
        bgPaint.setStrokeCap(Paint.Cap.ROUND);      // 线冒的头是圆的
        bgPaint.setStrokeWidth(progressHeight);     // 进度条背景高度  dp转px

        // 设置进度画笔
        progressPaint = new Paint();
        progressPaint.setColor(progressColor);          // 进度条进度颜色
        progressPaint.setStyle(Paint.Style.FILL_AND_STROKE);    // 填充且描边
        progressPaint.setAntiAlias(true);               // 抗锯齿
        progressPaint.setStrokeCap(Paint.Cap.ROUND);    // 线冒的头圆的
        progressPaint.setStrokeWidth(progressHeight);   // 进度条进度高度 3dp转px

        // 圆点指示器
        circlePaint = new Paint();
        circlePaint.setAntiAlias(true);         // 设置抗锯齿
        circlePaint.setColor(circleColor);      // 圆点指示器颜色
        circlePaint.setShadowLayer(dip2px(mContext, 2), 0, 0, circleShadowColor); // 圆点指示器阴影颜色
        circlePaint.setStyle(Paint.Style.FILL); // 填充
    }

    /**
     * 初始化高度、颜色等可自定义参数
     */
    private void getAttrs(Context context, AttributeSet attrs) {
        mContext = context;
        Resources resources = context.getResources();
        progressBackgroundColor = resources.getColor(R.color.text_colorc);
        progressColor = resources.getColor(R.color.toolbar_background);
        circleColor = resources.getColor(R.color.toolbar_background);
        circleShadowColor = resources.getColor(R.color.white);
        if (attrs != null) {
            TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.dragProgressView);
            isDrag = mTypedArray.getBoolean(R.styleable.dragProgressView_isDrag, true); // 默认可拖动
            mCircleRadius = mTypedArray.getLayoutDimension(R.styleable.dragProgressView_circleRadius, dip2px(context, 10));
            progressHeight = mTypedArray.getLayoutDimension(R.styleable.dragProgressView_progressHeight, dip2px(context, 10));
            progressBackgroundColor = mTypedArray.getColor(R.styleable.dragProgressView_progressBackgroundColor, progressBackgroundColor);
            progressColor = mTypedArray.getColor(R.styleable.dragProgressView_progressColor, progressColor);
            circleColor = mTypedArray.getColor(R.styleable.dragProgressView_circleColor, circleColor);
            circleShadowColor = mTypedArray.getColor(R.styleable.dragProgressView_circleShadowColor, circleShadowColor);
            progressColorN = mTypedArray.getColor(R.styleable.dragProgressView_progressColorN, progressColor);
            circleColorN = mTypedArray.getColor(R.styleable.dragProgressView_circleColorN, circleColor);
            circleShadowColorN = mTypedArray.getColor(R.styleable.dragProgressView_circleShadowColorN, circleShadowColor);
            mTypedArray.recycle();
        } else {
            isDrag = true;
            mCircleRadius = dip2px(context, 10);
            progressHeight = dip2px(context, 10);
            progressColorN = progressColor;
            circleColorN = circleColor;
            circleShadowColorN = circleShadowColor;
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int minHeight = mCircleRadius * 2 + (dip2px(mContext, 2) * 2);
        int height = resolveSize(minHeight, heightMeasureSpec);
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        // 初始化几个距离参数
        width = getWidth();     // view的宽度
        height = getHeight();   // view的高度

        // 让左边距至少为半个圆点指示器的距离
        paddingLeft = getPaddingLeft(); // 距离左边的距离
        if (getPaddingLeft() < mCircleRadius) {
            paddingLeft = mCircleRadius;
        }
        // 让右边距至少为半个圆点指示器的距离
        paddingRight = getPaddingRight();   // 距离右边的距离
        if (getPaddingRight() < mCircleRadius) {
            paddingRight = mCircleRadius;
        }

        // 如果当前进度小于左边距
        setCurrentProgress();
        // 最大进度长度等于View的宽度-(左边的内边距+右边的内边距)
        maxProgress = width - paddingLeft - paddingRight;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isDrag) {
            progressPaint.setColor(progressColor);  // 进度条颜色
            circlePaint.setColor(circleColor);      // 指示器颜色
        } else {
            progressPaint.setColor(progressColorN); // 进度条颜色
            circlePaint.setColor(circleColorN);     // 指示器颜色
        }
        // 绘制进度条背景
        // 从(左边距,View高度的一半)开始,到(View宽度-右边距,View高度的一半)还将绘制灰色背景线段
        canvas.drawLine(paddingLeft, height / 2, width - paddingRight, height / 2, bgPaint);
        // 绘制进度条进度
        // 从(左边距,View高度的一半)开始,到(现在的触摸到的进度宽度,View高度的一半)还将绘制灰色背景线段
        canvas.drawLine(paddingLeft, height / 2, currentProgress, height / 2, progressPaint);
        // 要支持阴影下过必须关闭硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE, null);// 发光效果不支持硬件加速
        // 绘制圆点指示器
        canvas.drawCircle(currentProgress, getHeight() / 2, mCircleRadius, circlePaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:   // 按下
                // 设置进度值
                setMotionProgress(event);
                return true;
            case MotionEvent.ACTION_MOVE:   // 移动
                // 获取当前触摸点,赋值给当前进度
                setMotionProgress(event);
                return true;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 根据用户手势计算进度值
     * @param event 用户手势操作事件
     */
    private void setMotionProgress(MotionEvent event) {
        if (!isDrag) {
            return;
        }
        // 获取当前触摸点,赋值给当前进度
        currentProgress = (int) event.getX();
        // 如果当前进度小于左边距
        setCurrentProgress();
        // 实际百分比进度数值
        float result = ((currentProgress - paddingLeft) * 100) / maxProgress;
        // 进行空值判断
        if (onProgressListener != null) {
            onProgressListener.onProgressChanged((int) result);
        }
        invalidate();
    }

    // 设置当前进度条进度,从1到100
    public void setProgress(int progress) {
        if (progress > 100 || progress < 0) {
            return;
        }
        setCurrentProgress();
        //设置当前进度的宽度
        currentProgress = ((progress * maxProgress) / 100) + paddingLeft;
        if (onProgressListener != null) {
            onProgressListener.onProgressChanged(progress);
        }
        invalidate();
    }

    // 如果当前进度超出边界,将当前进度赋值为边界极值
    private void setCurrentProgress() {
        if (currentProgress < paddingLeft) {    // 如果当前进度小于左边距
            currentProgress = paddingLeft;
        } else if (currentProgress > width - paddingRight) {  // 如果当前进度大于 宽度 - 右边距
            currentProgress = width - paddingRight;
        }
    }

    /**
     * 获取当前是否可拖动状态
     * @return isDrag true 可拖动; false 不可拖动。
     */
    public boolean isDrag() {
        return isDrag;
    }

    /**
     * 设置当前是否可拖动,立即重绘
     * @param drag true 可拖动; false 不可拖动。
     */
    public void setDrag(boolean drag) {
        isDrag = drag;
        invalidate();
    }

    private OnProgressListener onProgressListener;

    public interface OnProgressListener {
        void onProgressChanged(int progress);
    }

    // 设置拖动进度监听
    public void setOnProgressListener(OnProgressListener onProgressListener) {
        this.onProgressListener = onProgressListener;
    }

    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        float scale = 1;
        scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 根据手机的分辨率从 px(像素) 的单位 转成为 sp
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }
}

2、资源文件values目录下attrs文件中(如果没有attrs就创建一个)添加一下代码:

<!-- DragProgressView可拖动进度条样式 -->
<declare-styleable name="dragProgressView">
    <attr name="isDrag" format="boolean" />
    <attr name="circleRadius" format="dimension" />
    <attr name="progressHeight" format="dimension" />
    <attr name="progressBackgroundColor" format="color" />
    <attr name="progressColor" format="color" />
    <attr name="circleColor" format="color" />
    <attr name="circleShadowColor" format="color" />
    <attr name="progressColorN" format="color" />
    <attr name="circleColorN" format="color" />
    <attr name="circleShadowColorN" format="color" />
</declare-styleable>

3、布局xml中使用

<com.example.view.DragProgressView
    android:id="@+id/db_progress"
    android:layout_marginTop="40dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="15dp"
    android:layout_marginRight="15dp"
    apps:isDrag="true"
    apps:circleRadius="10dp"
    apps:progressHeight="10dp"
    apps:progressBackgroundColor="@color/text_colorc"
    apps:progressColor="@color/toolbar_background"
    apps:circleColor="@color/toolbar_background"
    apps:circleShadowColor="@color/settings_chart_progress_circle_shadow"
    apps:progressColorN="@color/text_color8"
    apps:circleColorN="@color/text_color8"
    apps:circleShadowColorN="@color/white" />

4、在Activity或Fragment中使用

DragProgressView dbProgress = findViewById(R.id.db_progress);
dbProgress.setOnProgressListener(new DragProgressView.OnProgressListener() {
    @Override
    public void onProgressChanged(int progress) {
        Log.v("progress", "...dbProgress拖动进度:" + progress);
    }
});

// chartProgress 从服务器获取或者自定义的显示默认值,默认进度为0,没有此种需求的可忽略此行
dbProgress.setProgress(chartProgress);

boolean isDrag = true;
// 设置是否可拖动,默认可拖动,没有禁止拖动需求的可忽略此行
dbProgress.setDrag(isDrag);

以上便是自定义view实现可拖动横向进度条的全部代码了,代码中有详细解释,如果不满足业务需求,可在此基础上继续修改。欢迎各位有相关业务需求的参考学习,转载请注明该地址。