写点什么

Android 技术分享| 自定义 View 实现使用更方便的 SeekBar

作者:anyRTC开发者
  • 2022 年 1 月 18 日
  • 本文字数:3828 字

    阅读完需:约 13 分钟

前言


Android 中自带的 SeekBar 个人感觉用起来很麻烦,调整一些颜色之类的需要单独写一个 XML 文件,内容感觉也很啰嗦。刚好我们的白板 Demo 开发中需要用到 SeekBar,所以干脆实现了一个满足基本功能的 SeekBar,支持在 xml 布局中指定各种颜色属性,也支持代码动态设置颜色。用起来更顺心一些。


SeekBar 的代码请查看 Github 地址:白板Demo,Demo 地址请:点击这里即拿即用,非常方便;p(记得将res/values/styles.xml中的属性一并复制走)

效果

实现

由于不涉及到动画,这种自定义 View 做起来还是相当简单的。首先在res/values/styles.xml文件中定义好自己需要的属性,我这里贴上我定义的属性:


<declare-styleable name="SeekBarWidget">    <attr name="seek_maxProgress" format="integer" /><!-- 最大progress -->    <attr name="seek_minProgress" format="integer" /><!-- 最小progress -->    <attr name="seek_progress" format="integer" /><!-- 当前progress(默认值) -->    <attr name="seek_circleRadius" format="dimension" /><!-- seekBar中间圆形的半径 -->    <attr name="seek_circleStrokeWidth" format="dimension" /><!-- seekBar中间圆形外的border -->    <attr name="seek_lineHeight" format="dimension" /><!-- lineHeight有点词不达意,其实是进度条的高度 -->    <attr name="seek_backgroundColor" format="color" /><!-- 进度条的背景色 -->    <attr name="seek_circleStrokeColor" format="color" /><!-- border的颜色 -->    <attr name="seek_maxColor" format="color" /><!-- 进度条的前景色 -->    <attr name="seek_startColor" format="color" /><!-- 如果需要渐变色则设置此属性,否则这条属性置空即可 --></declare-styleable>
复制代码


属性定义好以后,在 View 的构造中解析一下:


public SeekBarWidget(Context context) {    this(context, null);}                                                                                                                           public SeekBarWidget(Context context, @Nullable AttributeSet attrs) {    this(context, attrs, 0);}                                                                                                                           public SeekBarWidget(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);                                                                                                                              if (null != attrs) {        TypedArray typedArray = getResources().obtainAttributes(attrs, R.styleable.SeekBarWidget);        minProgress = typedArray.getInt(R.styleable.SeekBarWidget_seek_minProgress, 0);        maxProgress = typedArray.getInt(R.styleable.SeekBarWidget_seek_maxProgress, 100) - minProgress;        progress = typedArray.getInt(R.styleable.SeekBarWidget_seek_progress, 0) - minProgress;        if (progress < 0) progress = minProgress;        circleRadius = typedArray.getDimension(R.styleable.SeekBarWidget_seek_circleRadius, 20f);        circleStrokeWidth = typedArray.getDimension(R.styleable.SeekBarWidget_seek_circleStrokeWidth, 5f);        lineHeight = typedArray.getDimension(R.styleable.SeekBarWidget_seek_lineHeight, 5f);        backgroundColor = typedArray.getColor(R.styleable.SeekBarWidget_seek_backgroundColor, Color.parseColor("#F0F0F0"));        circleStrokeColor = typedArray.getColor(R.styleable.SeekBarWidget_seek_circleStrokeColor, Color.WHITE);        int maxColor = typedArray.getColor(R.styleable.SeekBarWidget_seek_maxColor, Color.RED);        int startColor = typedArray.getColor(R.styleable.SeekBarWidget_seek_startColor, maxColor);        colorTransition = new ColorTransition(startColor, maxColor);        typedArray.recycle();        percentage = progress * 1.0f / maxProgress;        horizontalPadding = circleRadius * 2 + circleStrokeWidth * 2 + getPaddingStart() + getPaddingEnd();        mPaddingLeft = horizontalPadding - getPaddingEnd() - circleRadius - circleStrokeWidth;        return;    }    maxProgress = 100;    minProgress = 0;    circleRadius = 20;    circleStrokeWidth = 5;    lineHeight = 5;    backgroundColor = Color.parseColor("#F0F0F0");    colorTransition = new ColorTransition(Color.WHITE, Color.RED);    circleStrokeColor = Color.WHITE;
horizontalPadding = circleRadius * 2 + circleStrokeWidth * 2 + getPaddingStart() + getPaddingEnd(); mPaddingLeft = horizontalPadding - getPaddingEnd() - circleRadius - circleStrokeWidth;}
复制代码


接下来是onMeasureonDraw,由于我的应用场景宽高已经固定好了,所以没有写onMeasure,有需求的朋友可以自己实现一下~而绘制的思路很简单:


  1. 先绘制一条背景色的线,线的高度由外部指定。(设置 StrokeCap 为 ROUND)

  2. 再绘制一条前景色的线,线高度同样由外部指定,这里我读取的是同一个属性lineHeight,也可以根据需求不同设置不一样的高度;p

  3. 接下来绘制 seekBar 的圆,这里 border 也直接使用drawCircle来实现,先绘制 border。(circleRadius + circleStrokeWidth / 2f)

  4. 最后绘制中间的圆


onDraw:


@Overrideprotected void onDraw(Canvas canvas) {    final int width = (int) (getMeasuredWidth() - horizontalPadding);    final int height = getMeasuredHeight();
paint.setColor(backgroundColor); paint.setStrokeWidth(lineHeight); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setStrokeCap(Paint.Cap.ROUND); canvas.drawLine(mPaddingLeft, height >> 1, mPaddingLeft + width, height >> 1, paint);
//float percentage = progress / maxProgress; int currColor = colorTransition.getValue(percentage); paint.setColor(currColor); canvas.drawLine(mPaddingLeft, height >> 1, mPaddingLeft + width * percentage, height >> 1, paint);
// draw circle border paint.setColor(circleStrokeColor); canvas.drawCircle(mPaddingLeft + width * percentage, height >> 1, circleRadius + (circleStrokeWidth / 2f), paint); // draw circle inside color paint.setColor(currColor); canvas.drawCircle(mPaddingLeft + width * percentage, height >> 1, circleRadius, paint);}
复制代码


最后,处理一下 onTouch 事件。思路也很简单,从 down、move、up 事件中判断一下是否为横向滚动,是的话计算一下距离,更改 progress 进度并通知 View 重新绘制即可:


private float downX;private float downY;@Overridepublic boolean onTouchEvent(MotionEvent event) {    boolean intercept = false;    switch (event.getAction()) {        case MotionEvent.ACTION_DOWN:            downX = event.getX();            downY = event.getY();            intercept = true;            break;        case MotionEvent.ACTION_MOVE:            float moveX = event.getX();            float moveY = event.getY();            float xMove = Math.abs(moveX - downX) - Math.abs(moveY - downY);            if (xMove > 0f) {                float hX = moveX - downX;                boolean toLeft = hX < 0.0f;                float movePercent = Math.abs(hX) / getMeasuredWidth();                if (percentage < 1.0f && !toLeft) {                    percentage += movePercent;                } else if (percentage > 0f && toLeft) {                    percentage -= movePercent;                }
if (percentage < 0f) percentage = 0f; if (percentage > 1f) percentage = 1f;
progress = (int) Math.floor(percentage * maxProgress); } intercept = true; downX = moveX; downY = moveY; postInvalidate();
if (null != mListener && beforeProgress != getProgress()) { beforeProgress = getProgress(); mListener.onProgress(getProgress()); } break; case MotionEvent.ACTION_UP: break; } return intercept || super.onTouchEvent(event); //return true;}
@Overridepublic boolean dispatchTouchEvent(MotionEvent event) { getParent().requestDisallowInterceptTouchEvent(true); return super.dispatchTouchEvent(event);}
复制代码


OK,一个复制即用的自定义 SeekBar 便完成了。比官方原版的 SeekBar 使用起来顺眼多了(「・ω・)「


(欢迎下载 Demo 玩一玩!Demo地址)



发布于: 刚刚阅读数: 3
用户头像

实时交互,万物互联! 2020.08.10 加入

实时交互,万物互联,全球实时互动云服务商领跑者!

评论

发布
暂无评论
Android技术分享| 自定义View实现使用更方便的SeekBar