Android 自定义View实现多节点进度条功能

前言

最近项目有一个节点进度条的小需求,完成后,想分享出来希望可以帮到有需要的同学。

真机效果图

自定义View完整代码

开箱即用~,注释已经炒鸡详细了

/**
 * @description: 节点进度条
 * @author: DMingO
 * @date: 2020/4/15
 */
public class PointProcessBar extends View {
  /**
   * 未选中时的连线画笔
   */
  private Paint mLinePaint;
  /**
   * 选中时的连线画笔
   */
  private Paint mLineSelectedPaint;
  /**
   * 未选中时的文字画笔
   */
  private Paint mTextPaint;
  /**
   * 选中时的文字画笔
   */
  private Paint mTextSelPaint;
  /**
   * 未选中时的实心圆画笔
   */
  private Paint mCirclePaint;
  /**
   * 选中时的内部实心圆画笔
   */
  private Paint mCircleSelPaint;
  /**
   * 选中时的边框圆画笔
   */
  private Paint mCircleStrokeSelPaint;
  /**
   * 未选中时的线,节点圆的颜色
   */
  private int mColorUnselected = Color.parseColor("#1ca8b0d9");
  /**
   * 选中时的颜色
   */
  private int mColorSelected = Color.parseColor("#61A4E4");
  /**
   * 未选中的文字颜色
   */
  private int mColorTextUnselected = Color.parseColor("#5c030f09");
  /**
   * 绘制的节点个数,由底部节点标题数量控制
   */
  int circleCount ;
  /**
   * 连线的高度
   */
  float mLineHeight = 7f;
  //圆的直径
  float mCircleHeight = 50f;
  float mCircleSelStroke = 8f;
  float mCircleFillRadius = 15f;
  //文字大小
  float mTextSize = 35f;
  //文字离顶部的距离
  float mMarginTop = 40f;
  /**
   * 首个圆向中心偏移的距离
   */
  float marginLeft = 30f;
  /**
   * 最后一个圆向中心偏移的距离
   */
  float marginRight = marginLeft;
  /**
   * 每个节点相隔的距离
   */
  float divideWidth;
  int defaultHeight;
  /**
   * 节点底部的文字列表
   */
  List<String> textList = new ArrayList<>();
  /**
   * 文字同宽高的矩形,用来测量文字
   */
  List<Rect> mBounds;
  /**
   * 存储每个圆心在同一直线上的节点圆的 x 坐标值
   */
  List<Float> circleLineJunctions = new ArrayList<>();
  /**
   * 选中项集合
   */
  Set<Integer> selectedIndexSet = new HashSet<>();
  public PointProcessBar(Context context) {
    super(context);
  }
  public PointProcessBar(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initPaint();
  }
  public PointProcessBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }
  public PointProcessBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
  }
  /**
   * 初始化画笔属性
   */
  private void initPaint(){
    mLinePaint = new Paint();
    mLineSelectedPaint = new Paint();
    mCirclePaint = new Paint();
    mTextPaint = new Paint();
    mCircleStrokeSelPaint = new Paint();
    mTextSelPaint=new Paint();
    mCircleSelPaint = new Paint();
    mLinePaint.setColor(mColorDef);
    //设置填充
    mLinePaint.setStyle(Paint.Style.FILL);
    //笔宽像素
    mLinePaint.setStrokeWidth(mLineHeight);
    //锯齿不显示
    mLinePaint.setAntiAlias(true);
    mLineSelectedPaint.setColor(mColorSelected);
    mLineSelectedPaint.setStyle(Paint.Style.FILL);
    mLineSelectedPaint.setStrokeWidth(mLineHeight);
    mLineSelectedPaint.setAntiAlias(true);
    mCirclePaint.setColor(mColorDef);
    //设置填充
    mCirclePaint.setStyle(Paint.Style.FILL);
    //笔宽像素
    mCirclePaint.setStrokeWidth(1);
    //锯齿不显示
    mCirclePaint.setAntiAlias(true);
    //选中时外框空心圆圈画笔
    mCircleStrokeSelPaint.setColor(mColorSelected);
    mCircleStrokeSelPaint.setStyle(Paint.Style.STROKE);
    mCircleStrokeSelPaint.setStrokeWidth(mCircleSelStroke);
    mCircleStrokeSelPaint.setAntiAlias(true);
    //选中时的内部填充圆画笔
    mCircleSelPaint.setStyle(Paint.Style.FILL);
    mCircleSelPaint.setStrokeWidth(1);
    mCircleSelPaint.setAntiAlias(true);
    mCircleSelPaint.setColor(mColorSelected);
    //普通状态的文本 画笔
    mTextPaint.setTextSize(mTextSize);
    mTextPaint.setColor(mColorTextDef);
    mTextPaint.setAntiAlias(true);
    mTextPaint.setTextAlign(Paint.Align.CENTER);
    //选中后的文本画笔
    mTextSelPaint.setTextSize(mTextSize);
    mTextSelPaint.setColor(mColorSelected);
    mTextSelPaint.setAntiAlias(true);
    mTextSelPaint.setTextAlign(Paint.Align.CENTER);
  }
  /**
   * 测量文字的长宽,将文字视为rect矩形
   */
  private void measureText(){
    mBounds = new ArrayList<>();
    for(String name : textList){
      Rect mBound = new Rect();
      mTextPaint.getTextBounds(name, 0, name.length(), mBound);
      mBounds.add(mBound);
    }
  }

  /**
   * 测量view的高度
   */
  private void measureHeight(){
    if (mBounds!=null && mBounds.size()!=0) {
      defaultHeight = (int) (mCircleHeight + mMarginTop + mCircleSelStroke + mBounds.get(0).height()/2);
    } else {
      defaultHeight = (int) (mCircleHeight + mMarginTop+mCircleSelStroke);
    }
  }
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    //宽高都设置为wrap_content
    if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
      //宽设置为wrap_content
      setMeasuredDimension(widthSpecSize,defaultHeight);
    }else if(widthSpecMode == MeasureSpec.AT_MOST){
      setMeasuredDimension(widthSpecSize,heightSpecSize);
    }else if(heightSpecMode == MeasureSpec.AT_MOST){
      //高设置为wrap_content
      setMeasuredDimension(widthSpecSize, defaultHeight);
    }else{
      //宽高都设置为match_parent或具体的dp值
      setMeasuredDimension(widthSpecSize, heightSpecSize);
    }
  }
  @Override
  protected void onDraw(Canvas canvas) {
    //若未设置节点标题或者选中项的列表,则取消绘制
    if (textList == null || textList.isEmpty() ||
        selectedIndexSet == null || selectedIndexSet.isEmpty() ||
        mBounds == null || mBounds.isEmpty()) {
      return;
    }
    //画灰色圆圈的个数
    circleCount=textList.size();
    //每个圆相隔的距离(重要),可以通过这个调节节点间距
    divideWidth = (getWidth() - mCircleHeight ) / (circleCount - 1);
    //绘制文字和圆形
    for (int i=0; i < circleCount ;i++){
      float cx;
      float cy;
      float textX;
      if (i==0){
        //第一个节点,圆心需要向右偏移
        cx = mCircleHeight / 2 + i * divideWidth + marginLeft;
        cy = mCircleHeight / 2 + mCircleSelStroke;
        textX = cx;
        circleLineJunctions.add(cx + mCircleHeight / 2);
      }else if (i==textList.size()-1){
        //最后一个节点,圆心需要向左偏移
        cx = mCircleHeight / 2 + i * divideWidth - marginRight;
        cy = mCircleHeight / 2 + mCircleSelStroke;
        textX = cx;
        circleLineJunctions.add(cx - mCircleHeight / 2);
      }else {
        //中间部分的节点
        cx = mCircleHeight / 2 + i * divideWidth;
        cy = mCircleHeight / 2+mCircleSelStroke;
        textX = cx;
        circleLineJunctions.add(cx - mCircleHeight / 2);
        circleLineJunctions.add(cx + mCircleHeight / 2);
      }
      if (getSelectedIndexSet().contains(i)){
        //若当前位置节点被包含在选中项Set中,判定此节点被选中
        canvas.drawCircle(cx , cy, mCircleHeight / 2, mCircleStrokeSelPaint);
        canvas.drawCircle(cx, cy, mCircleFillRadius, mCircleSelPaint);
        canvas.drawText(textList.get(i), textX, (float) (mCircleHeight + mMarginTop +mCircleSelStroke+mBounds.get(i).height()/2.0), mTextSelPaint);
      }else {
        //若当前位置节点没有被包含在选中项Set中,判定此节点没有被选中
        canvas.drawCircle(cx , cy, mCircleHeight / 2, mCirclePaint);
        canvas.drawText(textList.get(i), textX, (float) (mCircleHeight + mMarginTop +mCircleSelStroke+mBounds.get(i).height()/2.0), mTextPaint);
      }
    }
    for(int i = 1 , j = 1 ; j <= circleLineJunctions.size() && ! circleLineJunctions.isEmpty() ; ++i , j=j+2){
      if(getSelectedIndexSet().contains(i)){
        canvas.drawLine(circleLineJunctions.get(j-1),mCircleHeight/2+mCircleSelStroke,
            circleLineJunctions.get(j) ,mCircleHeight/2+mCircleSelStroke,mLineSelectedPaint);
      }else {
        canvas.drawLine(circleLineJunctions.get(j-1),mCircleHeight/2+mCircleSelStroke,
            circleLineJunctions.get(j) ,mCircleHeight/2+mCircleSelStroke,mLinePaint);
      }
    }
  }
  /**
   * 供外部调用,显示控件
   * @param titles 底部标题内容列表
   * @param indexSet 选中项Set
   */
  public void show(List<String> titles , Set<Integer> indexSet){
    if(titles != null && ! titles.isEmpty()){
      this.textList = titles;
    }
    if(indexSet != null && ! indexSet.isEmpty()){
      this.selectedIndexSet = indexSet;
    }
    measureText();
    measureHeight();
    //绘制
    invalidate();
  }
  /**
   * 更新底部节点标题内容
   * @param textList 节点标题内容列表
   */
  public void refreshTextList(List<String> textList) {
    this.textList = textList;
    measureText();
    measureHeight();
    invalidate();
  }
  /**
   * 获取节点选中状态
   * @return 节点选中状态列表
   */
  public Set<Integer> getSelectedIndexSet() {
    return selectedIndexSet;
  }
  /**
   * 更新选中项
   * @param set 选中项Set
   */
  public void refreshSelectedIndexSet(Set<Integer> set) {
    this.selectedIndexSet = set;
    invalidate();
  }
}

注意点

  • 控件的节点总个数是与传入的节点底部标题列表中元素个数控制(相同)的,简而言之就是传入的标题列表中有多少个标题,节点就会绘制多少个
  • 控件通过show方法进行View的初始化和显示内容,传入节点标题列表和节点选中项集合,控制View的选中状态和显示的内容
  • 控件初始化显示后,可以通过refreshTextList(),refreshSelectedIndexSet() 更新标题和选中项
  • 具体不同的颜色,大小可以具体在View中调整

总结

可以看到效果不复杂,因此自定义View的代码行数不多,也很容易看懂,直接拿走代码即可在项目中食用啦。

由于不同项目设计稿会有不同,这里也仅仅给有需要的同学一个思路,可以改造具体实现代码~

到此这篇关于Android 自定义View实现多节点进度条功能的文章就介绍到这了,更多相关android 自定义view 进度条内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android 自定义view和属性动画实现充电进度条效果

    近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和自定义view的方式来完成这个功能,将它开源出来,供有需要的人了解一下相关的内容. 本次实现的功能类似下面的效果: 接下来便详细解析一下如何完成这个功能,了解其中的原理,这样就能举一反三,实现其他类似的动画效果了. 详细代码请看大屏幕 https://github.com/crazyandcoder

  • Android view自定义实现动态进度条

    Android  自定义view实现动态进度条 效果图: 这个是看了梁肖的demo,根据他的思路自己写了一个,但是我写的这个貌似计算还是有些问题,从上面的图就可以看出来,左侧.顶部.右侧的线会有被截掉的部分,有懂得希望能给说一下,改进一下,这个过程还是有点曲折的,不过还是觉得收获挺多的.比如通动画来进行动态的展示(之前做的都是通过Handler进行更新的所以现在换一种思路觉得特别好),还有圆弧的起止角度,矩形区域的计算等!关于绘制我们可以循序渐进,比如最开始先画圆,然后再画周围的线,最后设置动画

  • Android自定义View之圆形进度条式按钮

    介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图. 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态.而且他说圆形进度的功能已经实现了.那么我们只需要对中间的两个状态做处理就行了. 先来看看实现的效果图: 上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现: http://blog.csdn.net/lmj623565791/article/details/43371299 下面开始具体实现. 具体实

  • Android自定义View基础开发之图片加载进度条

    学会了Paint,Canvas的基本用法之后,我们就可以动手开始实践了,先写个简单的图片加载进度条看看. 按照惯例,先看效果图,再决定要不要往下看: 既然看到这里了,应该是想了解这个图片加载进度条了,我们先看具体用法,再看自定义View的实现: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.co

  • android自定义进度条渐变色View的实例代码

    最近在公司,项目不是很忙了,偶尔看见一个兄台在CSDN求助,帮忙要一个自定义的渐变色进度条,我当时看了一下进度条,感觉挺漂亮的,就尝试的去自定义view实现了一个,废话不说,先上图吧! 这个自定义的view,完全脱离了android自带的ProgressView,并且没使用一张图片,这样就能更好的降低程序代码上的耦合性! 下面我贴出代码  ,大概讲解一下实现思路吧! 复制代码 代码如下: package com.spring.progressview; import android.conten

  • Android 自定义View实现多节点进度条功能

    前言 最近项目有一个节点进度条的小需求,完成后,想分享出来希望可以帮到有需要的同学. 真机效果图 自定义View完整代码 开箱即用~,注释已经炒鸡详细了 /** * @description: 节点进度条 * @author: DMingO * @date: 2020/4/15 */ public class PointProcessBar extends View { /** * 未选中时的连线画笔 */ private Paint mLinePaint; /** * 选中时的连线画笔 */

  • Android自定义View实现加载进度条效果

    上一篇文章总结了下自定义View的几个步骤,如果还有不清楚的同学可以先去看看Android自定义View(一),这篇文章和大家分享一下自定义加载进度条,效果如下 下面就以水平的进度条为列进行讲解: 1.首先还是在attrs.xml文件中自定义我们需要的属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="GradientP

  • Android自定义View实现钟摆效果进度条PendulumView

    在网上看到了一个IOS组件PendulumView,实现了钟摆的动画效果.由于原生的进度条确实是不好看,所以想可以自定义View实现这样的效果,以后也可以用于加载页面的进度条. 废话不多说,先上效果图 底部黑边是录制时不小心录上的,可以忽略. 既然是自定义View我们就按标准的流程来,第一步,自定义属性 自定义属性 建立属性文件 在Android项目的res->values目录下新建一个attrs.xml文件,文件内容如下: <?xml version="1.0" enco

  • Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="MyProgress"> <attr name="out_color" form

  • Android自定义带圆点的半圆形进度条

    本文实例为大家分享了Android自定义带圆点的半圆形进度条,供大家参考,具体内容如下 仅限用于半圆形,如须要带圆点的圆形进度条,圆点会出现错位现象,此代码仅供,带圆点的圆形进度条有空研究一下!图片效果在下方, import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import and

  • Android自定义View仿支付宝输入六位密码功能

    跟选择银行卡界面类似,也是用一个PopupWindow,不过输入密码界面是一个自定义view,当输入六位密码完成后用回调在Activity中获取到输入的密码并以Toast显示密码.效果图如下: 自定义view布局效果图及代码如下: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/

  • Android实现文件解压带进度条功能

    解压的工具类 package com.example.videodemo.zip; public class ZipProgressUtil { /*** * 解压通用方法 * * @param zipFileString * 文件路径 * @param outPathString * 解压路径 * @param listener * 加压监听 */ public static void UnZipFile(final String zipFileString, final String out

  • Android 自定义view仿支付宝咻一咻功能

    支付宝上有一个咻一咻的功能,就是点击图片后四周有水波纹的这种效果,今天也写一个类似的功能. 效果如下所示: 思路: 就是几个圆的半径不断在变大,这个可以使用动画缩放实现,还有透明动画 还有就是这是好几个圆,然后执行的动画有个延迟效果,其实这些动画是放在一起执行的,熟悉属性动画的知道已经给我们提供了同步执行动画和顺序执行动画的实现api,也会会有人说这几个view就是在onDraw()方法中画几个圆,可能会说我还要继承容器view去onLayout()方法中这些子view添加在某个特定的区域,当然

  • Android 自定义View手写签名并保存图片功能

    GIF压缩有问题,运行很顺滑!!! 1.自定义View--支持设置画笔颜色,画笔宽度,画板颜色,清除画板,检查是否有签名,保存画板图片(复制粘贴可直接使用) /** * Created by YyyyQ on 2020/3/5. * 电子签名 */ public class SignatureView extends View { private Context context; //X轴起点 private float x; //Y轴起点 private float y; //画笔 priva

  • Android自定义view实现水波纹进度球效果

    今天我们要实现的这个view没有太多交互性的view,所以就继承view. 自定义view的套路,套路很深 1.获取我们自定义属性attrs(可省略) 2.重写onMeasure方法,计算控件的宽和高 3.重写onDraw方法,绘制我们的控件 这么看来,自定义view的套路很清晰嘛. 我们看下今天的效果图,其中一个是放慢的效果(时间调的长) 我们按照套路来. 一.自定义属性 <declare-styleable name="WaveProgressView"> <at

随机推荐