Android自定义简单步骤指示器

一,背景

写Android也有些年头了,今年也打算把自己绘制过的自定义View从简单到复杂做一个整合,温故而知新,也希望能给从事Android开发的弟兄们一个参考,喜欢就多多支持,入下图,本文主要讲解如何绘制一个圆角矩形的指示器。

二,绘制流程

  • 首先,回忆一下我们自定义View必走的三部曲,测量宽高(onMeasure),布置布局(onLayout),绘制内容(onDraw),无论我们绘制多炫酷复杂效果的View都离不开这三个步骤
  • 那么回到主题,我们首先要初始化两支画笔,一支用于绘制被选中的tab(紫色),一直用于绘制未被选中的tab(白色),并将画笔的样式设置为填充内部
ini 复制代码
    mUnSelectedPaint = new Paint();
    mUnSelectedPaint.setColor(mUnSelectedColor);
    mUnSelectedPaint.setStyle(Paint.Style.FILL);
    mUnSelectedPaint.setStrokeWidth(10f);

    mSelectedPaint = new Paint();
    mSelectedPaint.setColor(mSelectedColor);
    mSelectedPaint.setStyle(Paint.Style.FILL);
    mSelectedPaint.setStrokeWidth(10f);
  • 测量宽高,这里我没有把宽高写死也没有自定义宽高的逻辑,所以直接走Android默认的测量逻辑,仅仅记录View的宽度,稍后用于计算每个tab的宽度
java 复制代码
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
    }
  • 因为我们是自定义View而并非是ViewGroup,所以没有子View需要布置(所谓布置就是将子View进行摆放),就没有onLayout这个步骤(onLayout是ViewGroup的方法)

  • 绘制圆角矩形,先通过View的总宽度减去每个tab之间的边距,就能得到可用的总宽度,可用总宽度除以tab的个数,就能得到每个tab的宽度,循环遍历绘制所有的tab,left进行累加,就能计算出每个tab开始的x坐标,y坐标=left+width,然后根据左右,上下的坐标绘制一个个圆角矩形

ini 复制代码
    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        //计算每个item的大小
        float useWidth = mWidth - (mHorizontalSpace * (mCount - 1));
        float width = useWidth / mCount;

        //绘制圆角矩形
        float left = 0;
        for (int i = 0; i < mCount; i++) {
            RectF rect = new RectF(left, 0, left + width, mHeight);
            left = rect.right + mHorizontalSpace;
            canvas.drawRoundRect(rect, mRound, mRound, mCurrentCount >= i ? mSelectedPaint : mUnSelectedPaint);
        }
    }
  • 完整代码
java 复制代码
/**
 * create by lijianhui
 * on 2022-7-7
 * <p>
 * description: 引导进度条
 */
public class GuideProgressView extends View {
    private final Paint mUnSelectedPaint;
    private final Paint mSelectedPaint;
    private float mWidth;
    private float mHeight = DensityUtil.dp2px(5);
    private float mHorizontalSpace = DensityUtil.dp2px(6);
    private float mRound = DensityUtil.dp2px(3);
    private int mCount = 4;
    private int mCurrentCount = 0;
    private int mSelectedColor = Color.parseColor("#A599FF");
    private int mUnSelectedColor = Color.WHITE;

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

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

    public GuideProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mUnSelectedPaint = new Paint();
        mUnSelectedPaint.setColor(mUnSelectedColor);
        mUnSelectedPaint.setStyle(Paint.Style.FILL);
        mUnSelectedPaint.setStrokeWidth(10f);

        mSelectedPaint = new Paint();
        mSelectedPaint.setColor(mSelectedColor);
        mSelectedPaint.setStyle(Paint.Style.FILL);
        mSelectedPaint.setStrokeWidth(10f);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mWidth = MeasureSpec.getSize(widthMeasureSpec);
    }

    /**
     * 绘制
     *
     * @param canvas 画布
     */
    @SuppressLint("DrawAllocation")
    @Override
    protected void onDraw(Canvas canvas) {
        //计算每个item的大小
        float useWidth = mWidth - (mHorizontalSpace * (mCount - 1));
        float width = useWidth / mCount;

        //绘制圆角矩形
        float left = 0;
        for (int i = 0; i < mCount; i++) {
            RectF rect = new RectF(left, 0, left + width, mHeight);
            left = rect.right + mHorizontalSpace;
            canvas.drawRoundRect(rect, mRound, mRound, mCurrentCount >= i ? mSelectedPaint : mUnSelectedPaint);
        }
    }

    /**
     * 设置高度
     *
     * @param height 高度
     */
    public void setGuideItemHeight(float height) {
        this.mHeight = height;
    }

    /**
     * 设置水平间距
     *
     * @param horizontalSpace 水平间距
     */
    public void setHorizontalSpace(float horizontalSpace) {
        this.mHorizontalSpace = horizontalSpace;
    }

    /**
     * 设置圆角
     *
     * @param round 圆角大小
     */
    public void setGuideItemRound(float round) {
        this.mRound = round;
    }

    /**
     * 设置个数
     *
     * @param count 个数
     */
    public void setGuideItemCount(int count) {
        this.mCount = count;
        invalidate();
    }

    /**
     * 选中下一个
     */
    public void selectedNext() {
        mCurrentCount++;
        invalidate();
    }

    /**
     * 获取当前步骤
     *
     * @return 步骤数
     */
    public int getCurrentPos() {
        return mCurrentCount;
    }

    /**
     * 设置选中的颜色
     *
     * @param selectedColor 颜色
     */
    public void setSelectedGuideItemColor(int selectedColor) {
        this.mSelectedColor = selectedColor;
    }

    /**
     * 设置未选中的颜色
     *
     * @param unSelectedColor 颜色
     */
    public void setUnSelectedGuideItemColor(int unSelectedColor) {
        this.mUnSelectedColor = unSelectedColor;
    }
}
相关推荐
JohnnyDeng941 天前
【Android】Android 包体积优化:R8/ProGuard 深度配置全攻略
android·性能优化·kotlin·jetpack
故渊at1 天前
第九板块:Android 多媒体体系 | 第二十四篇:Camera Service 与 HAL3 成像流水线
android·camera·多媒体体系·hal3
Jinkxs1 天前
Python基础 - 初识内置函数 Python自带的便捷工具
android·java·python
私人珍藏库1 天前
【Android】VLLO-韩国热门手机剪辑APP
android·app·工具·软件·多功能
Cloud_Shy6181 天前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 40 - 43)
android·开发语言·人工智能·笔记·python·学习方法
AFinalStone1 天前
Android12 U盘插拔链路源码全解析(五):Framework层(下) StorageManagerService
android·frameworks
林九生1 天前
【实用技巧】MySQL 绿色版一键路径更新脚本详解 —— update_path.bat 深度解析
android·数据库·mysql
故渊at1 天前
第十三板块:Android 综合架构与未来演进 | 第三十一篇:Android 架构演进与 Fuchsia OS 的挑战
android·架构·宏内核·微内核·fuchsia·ipc 性能博弈
aqi001 天前
一文速览 HarmonyOS 6.1.1 推出的十个新特性
android·华为·harmonyos·鸿蒙·harmony
matrixmind11 天前
aiomysql:异步场景下的 MySQL 驱动
android·数据库·mysql·其他