软件编程
位置:首页>> 软件编程>> Android编程>> Android 自定义View实现多节点进度条功能

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

作者:DMingO  发布时间:2022-05-04 17:13:37 

标签:android,自定义,view,进度条

前言

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

真机效果图

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

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的代码行数不多,也很容易看懂,直接拿走代码即可在项目中食用啦。

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

来源:https://www.cnblogs.com/DMingO/archive/2020/05/05/12789840.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com