自定义View——流程进度展示

我尽量不打错别字,用词准确,不造成阅读障碍

本文介绍一个自定义进度流程展示的view,注重易用性和适应性,使用简单且不需要做过多知识点准备,好上手。

效果如下:

流程进度

根据指定的步骤数可自行绘制,每个步骤的图标可以不一样。

一.自定义属性

<!--流程进度2-->
<declare-styleable name="AuditProgressViewTwo">
    <!--所有步骤数,从1开始,不是0,比如5-->
    <attr name="all_count" format="integer" />
    <!--现在完成了几个步骤,从1开始,不是0,比如4-->
    <attr name="current_position" format="integer" />
    <!--完成步骤的文字颜色,比如粉红-->
    <attr name="complete_color" format="reference|color" />
    <!--未完成步骤的文字颜色,比如灰色-->
    <attr name="not_complete_color" format="reference|color" />
    <!--连线颜色,完成步骤中的连线颜色-->
    <attr name="line_complete_color" format="reference|color" />
</declare-styleable>

注释详细,看看就明白,关于连线颜色是指到达状态下的连线颜色,未到达状态下的连线颜色是“未完成步骤的文字颜色”,效果图里面因为都是灰色,所以看不出来,应该改成蓝色好了,可图都截完了,懒~!

二.需求分析

到代码实施部分了,考虑需要什么;我希望使用者只引入一个控件就好了,所以最好把所有数据和资源都给我,我在一个控件里给你处理,这样就打算让使用者给我三个数组资源——1:"未达到"状态下的图标样式数组,2:"达到"状态下的图标样式数组,3:步骤文字描述数组。在Activity里给我就好,像这样:

public class AuditProgressActivity extends AppCompatActivity {

  	//未完成状态下的图标样式
	private int notCompleteImageArray[] = {R.drawable.audit_uncomplete,    			 R.drawable.audit_uncomplete, R.drawable.audit_uncomplete, R.drawable.audit_uncomplete,   R.drawable.audit_uncomplete};
	//完成状态下的图标样式
	private int completeImageArray[] = {R.drawable.audit_complete, R.drawable.audit_complete,  R.drawable.audit_complete, R.drawable.audit_complete, R.drawable.audit_complete};
	//文字数组
	private String text[] = {"一帆风顺", "一帆风顺", "风顺", "帆风顺", "风顺"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audit_progress);
        AuditProgressViewTwo auditProgressViewTwo = findViewById(R.id.apvt_my);
        //三个set设置资源
        auditProgressViewTwo.setCompleteImageSource(completeImageArray);
        auditProgressViewTwo.setNotCompleteImageSource(notCompleteImageArray);
        auditProgressViewTwo.setTextSource(text);
        auditProgressViewTwo.build();
    }
}

通过三个set来设置资源。

然后是工具分析,要画图片,需要一个画笔。画文字,需要一个画笔。画连接线,需要一个画笔。确定了要画的步骤个数之后,在一个for循环里处理就好。

三.重要代码实施

刚开始的代码是这样的:

public class AuditProgressViewTwo extends View {

    private int allCount;           //所有进度数目,比如5个
    private int currentPosition;    //当前进度所在位置,比如在第4个进度
    private int lineCompleteColor;

    private TextPaint textPaint;    //写文字的画笔
    private Paint bitmapPaint;      //画图片的画笔
    private Paint linePaint;        //画线的画笔

    private int completeColor = Color.parseColor("#FF0000");      //完成颜色,主要用于文字
    private int notCompleteColor = Color.parseColor("#757575");   //未完成颜色,主要用于文字

    private ArrayList<Bitmap> completeBitmap = new ArrayList<>();        //完成的Bitmap
    private ArrayList<Bitmap> notCompleteBitmap = new ArrayList<>();     //未完成的Bitmap

    private int notCompleteImageArray[] = {R.drawable.audit_uncomplete, R.drawable.audit_uncomplete, R.drawable.audit_uncomplete, R.drawable.audit_uncomplete, R.drawable.audit_uncomplete};
    private int completeImageArray[] = {R.drawable.audit_complete, R.drawable.audit_complete, R.drawable.audit_complete, R.drawable.audit_complete, R.drawable.audit_complete};
    private String text[] = {"步骤一", "步骤二", "步骤三", "步骤四", "步骤五"};

    public AuditProgressViewTwo(Context context) {
        this(context, null);
    }

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

    public AuditProgressViewTwo(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AuditProgressViewTwo);
        allCount = typedArray.getInteger(R.styleable.AuditProgressViewTwo_all_count, 5);
        currentPosition = typedArray.getInteger(R.styleable.AuditProgressViewTwo_current_position, 4);
        completeColor = typedArray.getInteger(R.styleable.AuditProgressViewTwo_complete_color, Color.BLACK);
        notCompleteColor = typedArray.getInteger(R.styleable.AuditProgressViewTwo_not_complete_color, Color.GRAY);
        lineCompleteColor = typedArray.getInteger(R.styleable.AuditProgressViewTwo_line_complete_color, Color.BLACK);
        typedArray.recycle();

        if (allCount > completeImageArray.length) {
            throw new RuntimeException(getClass().getName() + "资源数组图标个数不能小于全部流程个数");
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

       //设置画笔
        textPaint = new TextPaint();
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setAntiAlias(true);
        textPaint.setColor(notCompleteColor);
        textPaint.setTextSize(30);

       //设置画笔
        bitmapPaint = new Paint();
        bitmapPaint.setStyle(Paint.Style.FILL);
        bitmapPaint.setAntiAlias(true);

        //设置画笔
        linePaint = new Paint();
        linePaint.setColor(completeColor);
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setStrokeWidth(2.0f);

       //单独的list放数据,主要方便后期操作
        completeBitmap.clear();
        notCompleteBitmap.clear();
        for (int i = 0; i < completeImageArray.length; i++) {
            completeBitmap.add(i, BitmapFactory.decodeResource(getResources(), completeImageArray[i]));
        }
        for (int i = 0; i < notCompleteImageArray.length; i++) {
            notCompleteBitmap.add(i, BitmapFactory.decodeResource(getResources(), notCompleteImageArray[i]));
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < allCount; i++) {
            if (i < currentPosition) {
                int bitmapWidthHalf = completeBitmap.get(i).getWidth() / 2;
                canvas.drawBitmap(completeBitmap.get(i), i * 2 * 80 - bitmapWidthHalf, 0, bitmapPaint);
                linePaint.setColor(lineCompleteColor);
                textPaint.setColor(completeColor);
            } else {

                canvas.drawBitmap(notCompleteBitmap.get(i), i * 2 * 80, 0, bitmapPaint);
                linePaint.setColor(notCompleteColor);
                textPaint.setColor(notCompleteColor);
               
            }
          
           if (i > 0) {
                    float startX = i *  notCompleteBitmap.get(i-1).getWidth()+(i-1)*160;
                    float stopX = i * (notCompleteBitmap.get(i-1).getWidth()+160);
                    float startY = notCompleteBitmap.get(i).getHeight() / 2;
                    canvas.drawLine(startX, startY, stopX, startY, linePaint);
                }
          

            //写文字部分
            canvas.drawText(text[i], i * 2 * 80, completeBitmap.get(i).getHeight()+70, textPaint);
        }
    }

    /**
     * 设置完成进度的图片数组
     *
     * @param complete 数据源
     */
    public void setCompleteImageSource(int[] complete) {
        this.completeImageArray = complete;
    }

    /**
     * 设置未完成进度的图片数组
     *
     * @param notComplete 数据源
     */
    public void setNotCompleteImageSource(int[] notComplete) {
        this.notCompleteImageArray = notComplete;
    }

    /**
     * 设置文字描述数组
     *
     * @param textSource 数据源
     */
    public void setTextSource(String[] textSource) {
        this.text = textSource;
    }

刚开始没想着一步到位,先实现主要效果,那个"i * 2 * 80"是我随便写的边距,160就是2*80嘛,效果很不好:在xml文件中引人:

<com.study.longl.myselfviewdemo.AuditProgress.AuditProgressViewTwo
        android:id="@+id/apvt_my"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:paddingTop="20dp"
        app:all_count="5"
        app:complete_color="@color/colorAccent"
        app:current_position="4"
        app:line_complete_color="@color/white"
        app:not_complete_color="@color/gray" />

效果如下:

流程进度草图1

效果不好,但是无非就是计算的位置有问题,起码图片画的都对,没出什么问题。可是看起来边距也不对,图片应该在文字中间,如果文字比图片宽,是不是图片距边缘就应该有一个距离?反而文字可以顶在边缘。所以干脆我就把整个控件的宽按照步骤个数均分几等分,然后将图片个文字都水平居中,再设置一下两者垂直间距就OK了。

就像这样:

步骤流程2

改良后如下:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //设置控件可使用范围的矩形,加入适配padding属性的效果
        viewRectF = new RectF(getPaddingLeft(), getPaddingTop(), getRight() - getLeft() - getPaddingRight(), getBottom() - getTop() - getPaddingBottom());
        //...省略老代码
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < allCount; i++) {
            float smallRectWidth = viewRectF.width() / allCount; //均分
            if (i < currentPosition) {
                int bitmapWidthHalf = completeBitmap.get(i).getWidth() / 2;
                canvas.drawBitmap(completeBitmap.get(i), (i * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf, getPaddingTop(), bitmapPaint);
                linePaint.setColor(lineCompleteColor);
                textPaint.setColor(completeColor);
                if (i < allCount - 1) {
                    //画线,之所以写在这里是为了防止每个item图标大小不一样的情况
                    float startX = (i * smallRectWidth + smallRectWidth / 2) + bitmapWidthHalf;
                    float stopX = ((i + 1) * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf;
                    float startY = completeBitmap.get(i).getHeight() / 2 + getPaddingTop();
                    canvas.drawLine(startX, startY, stopX, startY, linePaint);
                }
            } else {
                int bitmapWidthHalf = notCompleteBitmap.get(i).getWidth() / 2;
                canvas.drawBitmap(notCompleteBitmap.get(i), (i * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf, getPaddingTop(), bitmapPaint);
                linePaint.setColor(notCompleteColor);
                textPaint.setColor(notCompleteColor);
                if (i < allCount - 1) {
                    //画线,之所以写在这里是为了防止每个item图标大小不一样的情况
                    float startX = (i * smallRectWidth + smallRectWidth / 2) + bitmapWidthHalf;
                    float stopX = ((i + 1) * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf;
                    float startY = notCompleteBitmap.get(i).getHeight() / 2 + getPaddingTop();
                    canvas.drawLine(startX, startY, stopX, startY, linePaint);
                }
            }

            //写文字部分
            textPaint.getTextBounds(text[i], 0, text[i].length(), textRect);
            int textWidthHalf = textRect.width() / 2;
            float x = (i * smallRectWidth + smallRectWidth / 2) - textWidthHalf;
            float y = completeBitmap.get(i).getHeight() + getPaddingTop() + 70;
            canvas.drawText(text[i], x, y, textPaint);
        }
    }

上面的写法已经是接近最终版了,兼容了padding并对齐了文字与图片的纵向排列,运行结果与开篇的效果图片一样:

流程进度

最后简化一下代码,去掉默认数据加上判空处理并增加动态改变当前步骤的能力,完整代码如下:

public class AuditProgressViewTwo extends View {

    private int allCount;           //所有进度数目,比如5个
    private int currentPosition;    //当前进度所在位置,比如在第3个进度
    private int lineCompleteColor;

    private TextPaint textPaint;    //写文字的画笔
    private Paint bitmapPaint;      //画图片的画笔
    private Paint linePaint;        //画线的画笔

    private Rect textRect;        //承接文字宽度的矩形
    private RectF viewRectF;        //整个view的矩形

    private int completeColor = Color.parseColor("#FF0000");      //完成颜色,主要用于文字
    private int notCompleteColor = Color.parseColor("#757575");   //未完成颜色,主要用于文字

    private ArrayList<Bitmap> completeBitmap = new ArrayList<>();        //完成的Bitmap
    private ArrayList<Bitmap> notCompleteBitmap = new ArrayList<>();     //未完成的Bitmap

    private int notCompleteImageArray[];
    private int completeImageArray[];
    private String text[];

    public AuditProgressViewTwo(Context context) {
        this(context, null);
    }

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

    public AuditProgressViewTwo(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AuditProgressViewTwo);
        allCount = typedArray.getInteger(R.styleable.AuditProgressViewTwo_all_count, 5);
        currentPosition = typedArray.getInteger(R.styleable.AuditProgressViewTwo_current_position, 4);
        completeColor = typedArray.getInteger(R.styleable.AuditProgressViewTwo_complete_color, Color.BLACK);
        notCompleteColor = typedArray.getInteger(R.styleable.AuditProgressViewTwo_not_complete_color, Color.GRAY);
        lineCompleteColor = typedArray.getInteger(R.styleable.AuditProgressViewTwo_line_complete_color, Color.BLACK);
        typedArray.recycle();
        if (completeImageArray != null) {
            if (allCount > completeImageArray.length) {
                throw new RuntimeException(getClass().getName() + "资源数组图标个数不能小于全部流程个数");
            }
        }
        textRect = new Rect();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewRectF = new RectF(getPaddingLeft(), getPaddingTop(), getRight() - getLeft() - getPaddingRight(), getBottom() - getTop() - getPaddingBottom());

        textPaint = new TextPaint();
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setAntiAlias(true);
        textPaint.setColor(notCompleteColor);
        textPaint.setTextSize(30);

        bitmapPaint = new Paint();
        bitmapPaint.setStyle(Paint.Style.FILL);
        bitmapPaint.setAntiAlias(true);

        linePaint = new Paint();
        linePaint.setColor(completeColor);
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setStrokeWidth(2.0f);

        completeBitmap.clear();
        notCompleteBitmap.clear();
        if (completeImageArray != null && notCompleteImageArray != null) {
            for (int i = 0; i < completeImageArray.length; i++) {
                completeBitmap.add(i, BitmapFactory.decodeResource(getResources(), completeImageArray[i]));
            }
            for (int i = 0; i < notCompleteImageArray.length; i++) {
                notCompleteBitmap.add(i, BitmapFactory.decodeResource(getResources(), notCompleteImageArray[i]));
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (completeImageArray != null && notCompleteImageArray != null) {
            for (int i = 0; i < allCount; i++) {
                float smallRectWidth = viewRectF.width() / allCount;
                int bitmapWidthHalf;
                float startY = 10f;
                if (i < currentPosition) {
                    bitmapWidthHalf = completeBitmap.get(i).getWidth() / 2;
                    canvas.drawBitmap(completeBitmap.get(i), (i * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf, getPaddingTop(), bitmapPaint);
                    linePaint.setColor(lineCompleteColor);
                    textPaint.setColor(completeColor);
                    if (i < allCount - 1) {
                        startY = notCompleteBitmap.get(i).getHeight() / 2 + getPaddingTop();
                    }
                } else {
                    bitmapWidthHalf = notCompleteBitmap.get(i).getWidth() / 2;
                    canvas.drawBitmap(notCompleteBitmap.get(i), (i * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf, getPaddingTop(), bitmapPaint);
                    linePaint.setColor(notCompleteColor);
                    textPaint.setColor(notCompleteColor);
                    if (i < allCount - 1) {
                        startY = notCompleteBitmap.get(i).getHeight() / 2 + getPaddingTop();
                    }
                }

                if (i < allCount - 1) {
                    float startX = (i * smallRectWidth + smallRectWidth / 2) + bitmapWidthHalf;
                    float stopX = ((i + 1) * smallRectWidth + smallRectWidth / 2) - bitmapWidthHalf;
                    canvas.drawLine(startX, startY, stopX, startY, linePaint);
                }

                //写文字部分
                textPaint.getTextBounds(text[i], 0, text[i].length(), textRect);
                int textWidthHalf = textRect.width() / 2;
                float x = (i * smallRectWidth + smallRectWidth / 2) - textWidthHalf;
                float y = completeBitmap.get(i).getHeight() + getPaddingTop() + 70;
                canvas.drawText(text[i], x, y, textPaint);
            }
        }
    }

    /**
     * 设置完成进度的图片数组
     *
     * @param complete 数据源
     */
    public void setCompleteImageSource(int[] complete) {
        this.completeImageArray = complete;
    }

    /**
     * 设置未完成进度的图片数组
     *
     * @param notComplete 数据源
     */
    public void setNotCompleteImageSource(int[] notComplete) {
        this.notCompleteImageArray = notComplete;
    }

    /**
     * 设置文字描述数组
     *
     * @param textSource 数据源
     */
    public void setTextSource(String[] textSource) {
        this.text = textSource;
    }

    public void setAllCount(int allCount) {
        this.allCount = allCount;
    }

    public void setCurrentPosition(int currentPosition) {
        this.currentPosition = currentPosition;
        invalidate();
    }

    //build启动刷新
    public void build() {
        invalidate();
    }
}

流程进度
ps:
1.其实这个真的很简单了,简单到本不打算写的,就是距离计算的问题,可是适合初学者学习。
2.开动脑筋,还可以改成垂直方向的,原理一样。
3.这里注意数据源与xml文件中的allCount属性和currentPosition属性要对上,否则很可能会崩,我也想过根据数据源个数进行for循环,这样就不需要在xml设置allCount属性了,但是考虑使用场景多样,比如虽然有五个数据,但是实际我就想显示一个,这种需求也许有呢,所以还是交给使用者设置比较好,你随意设置。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值