美文网首页手机移动程序开发Android进阶之路Android技术知识
Android原生绘图进度条+简单自定义属性代码生成器

Android原生绘图进度条+简单自定义属性代码生成器

作者: e4e52c116681 | 来源:发表于2018-11-09 13:30 被阅读28次

    零、前言

    1.感觉切拼字符串是个很有意思的事,好的拼接方式可以自动生成一些很实用的东西
    2.本文自定义控件并不是很高大上的东西,目的在于计录自定义控件的书写规范与行文流程
    3.建议大家自定义控件时自定义属性有自己专属前缀,有利无害,何乐不为
    4.本文是根据鸿洋在慕课网上的教程敲的:详见,自己修改并优化了一点逻辑和显示效果

    先看一下效果:
    圆形进度条.gif 横向进度条.gif

    一、简单自定义属性生成器

    1.玩安卓的应该都写过自定义控件的自定义属性:如下
    自定义控件.png

    我写着写着感觉好枯燥,基本上流程相似,也没有什么技术难度,想:这种事不就应该交给机器吗?

    2.通过attrs.xml自动生成相应代码

    秉承着能用代码解决的问题,绝对不动手。能够靠智商解决的问题,绝对不靠体力的大无畏精神:
    写了一个小工具,将代码里的内容自动生成一下:基本上就是字符串的切割和拼装,工具附在文尾

    使用方法与注意点:
    1.拷贝到AndroidStudio的test里,将attrs.xml的文件路径设置一下,运行
    2.自定义必须符合命名规则,如z_pb_on_height,专属前缀如z_,单词间下划线连接即可
    3.它并不是什么高大上的东西,只是简单的字符串切割拼组,只适用简单的自定义属性[dimension|color|boolean|string](不过一般的自定义属性也够用了)

    自动生成.png

    在开篇之前:先看一下Android系统内自定义控件的书写风格,毕竟跟原生看齐没有什么坏处
    看一下LinearLayout的源码:

    1.构造方法使用最多参数的那个,其他用this(XXX)调用
     public LinearLayout(Context context) {
          this(context, null);
      }
    
      public LinearLayout(Context context, @Nullable AttributeSet attrs) {
          this(context, attrs, 0);
      }
    
      public LinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
    
      public LinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          ...
          }
    
    2.自定义属性的书写

    1).先将自定义属性的成员变量定义好
    2).如果自定义属性不是很多,一个一个a.getXXX,默认值直接写在后面就行了
    3).看了一下TextView的源码,自定义属性很多,它是先定义默认值的变量,再使用,而且用switch来对a.getXXX进行赋值

    final TypedArray a = context.obtainStyledAttributes(
                    attrs, com.android.internal.R.styleable.LinearLayout, defStyleAttr, defStyleRes);
    
            int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1);
            if (index >= 0) {
                setOrientation(index);
            }
    
            index = a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
            if (index >= 0) {
                setGravity(index);
            }
    
            boolean baselineAligned = a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
            if (!baselineAligned) {
                setBaselineAligned(baselineAligned);
            }
            ......
            a.recycle();
    

    一、水平的进度条

    条形进度条分析.png
    1.自定义控件属性:values/attrs.xml
        <!--自定义进度条-->
        <declare-styleable name="TolyProgressBar">
            <!--进度条相关-->
            <!--背景色-->
            <attr name="z_pb_bg_color" format="color"/>
            <!--背景高-->
            <attr name="z_pb_bg_height" format="dimension"/>
            <!--进度色-->
            <attr name="z_pb_on_color" format="color"/>
            <!--进度高-->
            <attr name="z_pb_on_height" format="dimension"/>
            
            <!--文本相关-->
            <!--文字颜色-->
            <attr name="z_pb_txt_color" format="color"/>
            <!--文字大小-->
            <attr name="z_pb_txt_size" format="dimension"/>
            <!--文字两边的空距-->
            <attr name="z_pb_txt_offset" format="dimension"/>
            <!--文字是否消失-->
            <attr name="z_pb_txt_gone" format="boolean"/>
        </declare-styleable>
    
    2.初始代码:将进行一些常规处理
    public class TolyProgressBar extends ProgressBar {
    
        private Paint mPaint;
        private int mPBWidth;
        private RectF mRectF;
        private Path mPath;
        private float[] mFloat8Left;//左边圆角数组
        private float[] mFloat8Right;//右边圆角数组
        
        private float mProgressX;//进度理论值
        private float mEndX;//进度条尾部
        private int mTextWidth;//文字宽度
        private boolean mLostRight;//是否不画右边
        private String mText;//文字
    
        private int mPbBgColor = 0xffC9C9C9;
        private int mPbOnColor = 0xff54F340;
        private int mPbOnHeight = dp(6);
        private int mPbBgHeight = dp(6);
        private int mPbTxtColor = 0xff525252;
        private int mPbTxtSize = sp(10);
        private int mPbTxtOffset = sp(10);
        private boolean mPbTxtGone= false;
    
        public TolyProgressBar(Context context) {
            this(context, null);
        }
    
        public TolyProgressBar(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public TolyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TolyProgressBar);
            mPbOnHeight = (int) a.getDimension(R.styleable.TolyProgressBar_z_pb_on_height, mPbOnHeight);
            mPbTxtOffset = (int) a.getDimension(R.styleable.TolyProgressBar_z_pb_txt_offset, mPbTxtOffset);
            mPbOnColor = a.getColor(R.styleable.TolyProgressBar_z_pb_on_color, mPbOnColor);
            mPbTxtSize = (int) a.getDimension(R.styleable.TolyProgressBar_z_pb_txt_size, mPbTxtSize);
            mPbTxtColor = a.getColor(R.styleable.TolyProgressBar_z_pb_txt_color, mPbTxtColor);
            mPbBgHeight = (int) a.getDimension(R.styleable.TolyProgressBar_z_pb_bg_height, mPbBgHeight);
            mPbBgColor = a.getColor(R.styleable.TolyProgressBar_z_pb_bg_color, mPbBgColor);
            mPbTxtGone =  a.getBoolean(R.styleable.TolyProgressBar_z_pb_txt_gone, mPbTxtGone);
            a.recycle();
    
            init();
        }
        
        private void init() {
             mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            mPaint.setTextSize(mPbTxtSize);
            mPaint.setColor(mPbOnColor);
            mPaint.setStrokeWidth(mPbOnHeight);
    
            mRectF = new RectF();
            mPath = new Path();
    
    
            mFloat8Left = new float[]{//仅左边两个圆角--为背景
                    mPbOnHeight / 2, mPbOnHeight / 2,//左上圆角x,y
                    0, 0,//右上圆角x,y
                    0, 0,//右下圆角x,y
                    mPbOnHeight / 2, mPbOnHeight / 2//左下圆角x,y
            };
    
            mFloat8Right = new float[]{
                    0, 0,//左上圆角x,y
                    mPbBgHeight / 2, mPbBgHeight / 2,//右上圆角x,y
                    mPbBgHeight / 2, mPbBgHeight / 2,//右下圆角x,y
                    0, 0//左下圆角x,y
            };
        }
        
    }
        
        private int sp(int sp) {
            return (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
        }
    
        private int dp(int dp) {
            return (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
        }
        
    
    2.测量:
        @Override
        protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = measureHeight(heightMeasureSpec);
            setMeasuredDimension(width, height);
            mPBWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();//进度条实际宽度
        }
    
        /**
         * 测量高度
         *
         * @param heightMeasureSpec
         * @return
         */
        private int measureHeight(int heightMeasureSpec) {
            int result = 0;
            int mode = MeasureSpec.getMode(heightMeasureSpec);
            int size = MeasureSpec.getSize(heightMeasureSpec);
    
            if (mode == MeasureSpec.EXACTLY) {
                //控件尺寸已经确定:如:
                // android:layout_height="40dp"或"match_parent"
                result = size;
            } else {
                int textHeight = (int) (mPaint.descent() - mPaint.ascent());
                result = getPaddingTop() + getPaddingBottom() + Math.max(
                        Math.max(mPbBgHeight, mPbOnHeight), Math.abs(textHeight));
    
                if (mode == MeasureSpec.AT_MOST) {//最多不超过
                    result = Math.min(result, size);
                }
            }
            return result;
        }
    
    3.绘制:
        @Override
        protected synchronized void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.save();
            canvas.translate(getPaddingLeft(), getHeight() / 2);
    
            parseBeforeDraw();//1.绘制前对数值进行计算以及控制的flag设置
    
            if (getProgress() == 100) {//进度达到100后文字消失
                whenOver();//2.
            }
            if (mEndX > 0) {//当进度条尾部>0绘制
                drawProgress(canvas);//3.
            }
            if (!mPbTxtGone) {//绘制文字
                mPaint.setColor(mPbTxtColor);
                int y = (int) (-(mPaint.descent() + mPaint.ascent()) / 2);
                canvas.drawText(mText, mProgressX, y, mPaint);
            } else {
                mTextWidth = 0 - mPbTxtOffset;
            }
            if (!mLostRight) {//绘制右侧
                drawRight(canvas);/4.
            }
    
            canvas.restore();
        }
    
    1).praseBeforeDraw()
    /**
     * 对数值进行计算以及控制的flag设置
     */
    private void parseBeforeDraw() {
        mLostRight = false;//lostRight控制是否绘制右侧
        float radio = getProgress() * 1.f / getMax();//当前百分比率
        mProgressX = radio * mPBWidth;//进度条当前长度
        mEndX = mProgressX - mPbTxtOffset / 2;       //进度条当前长度-文字间隔的左半
        mText = getProgress() + "%";
        if (mProgressX + mTextWidth > mPBWidth) {
            mProgressX = mPBWidth - mTextWidth;
            mLostRight = true;
        }
        //文字宽度
        mTextWidth = (int) mPaint.measureText(mText);
    }
    
    2).whenOver()
    /**
     * 当结束是执行:
     */
    private void whenOver() {
        mPbTxtGone = true;
        mFloat8Left = new float[]{//只有进度达到100时让进度圆角是四个
                mPbBgHeight / 2, mPbBgHeight / 2,//左上圆角x,y
                mPbBgHeight / 2, mPbBgHeight / 2,//右上圆角x,y
                mPbBgHeight / 2, mPbBgHeight / 2,//右下圆角x,y
                mPbBgHeight / 2, mPbBgHeight / 2//左下圆角x,y
        };
    }
    
    3).drawProgress()
    /**
     * 绘制左侧:(进度条)
     *
     * @param canvas
     */
    private void drawProgress(Canvas canvas) {
        mPath.reset();
        mRectF.set(0, mPbOnHeight / 2, mEndX, -mPbOnHeight / 2);
        mPath.addRoundRect(mRectF, mFloat8Left, Path.Direction.CW);//顺时针画
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mPbOnColor);
        canvas.drawPath(mPath, mPaint);//使用path绘制一端是圆头的线
    }
    
    4).drawRight()
    /**
     * 绘制左侧:(背景)
     *
     * @param canvas
     */
    private void drawRight(Canvas canvas) {
        float start = mProgressX + mPbTxtOffset / 2 + mTextWidth;
        mPaint.setColor(mPbBgColor);
        mPaint.setStrokeWidth(mPbBgHeight);
        mPath.reset();
        mRectF.set(start, mPbBgHeight / 2, mPBWidth, -mPbBgHeight / 2);
        mPath.addRoundRect(mRectF, mFloat8Right, Path.Direction.CW);//顺时针画
        canvas.drawPath(mPath, mPaint);//使用path绘制一端是圆头的线
    }
    
    xml里使用:
    <top.toly.reslib.my_design.logic.TolyProgressBar
        android:id="@+id/id_toly_pb2"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
    
        android:progress="20"
        app:z_pb_bg_color="@color/red"
        app:z_pb_bg_height="10dp"
    
        app:z_pb_on_color="#224ee3"
        app:z_pb_on_height="15dp"
    
        app:z_pb_txt_color="@color/rosybrown"
        app:z_pb_txt_offset="5dp"
        app:z_pb_txt_size="10dp"/>
    

    三、圆形进度条
    1.自定义属性
    <!--圆形进度条-->
    <declare-styleable name="TolyRoundProgressBar">
    <!--进度条半径-->
    <attr name="z_pb_radius" format="dimension"/>
    </declare-styleable>
    
    2.代码实现:
    /**
     * 作者:张风捷特烈<br/>
     * 时间:2018/11/9 0009:11:49<br/>
     * 邮箱:1981462002@qq.com<br/>
     * 说明:圆形进度条
     */
    public class TolyRoundProgressBar extends TolyProgressBar {
    
        private int mPbRadius = dp(30);//进度条半径
        private int mMaxPaintWidth;
    
        public TolyRoundProgressBar(Context context) {
            this(context, null);
        }
    
        public TolyRoundProgressBar(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public TolyRoundProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TolyRoundProgressBar);
            mPbRadius = (int) a.getDimension(R.styleable.TolyRoundProgressBar_z_pb_radius, mPbRadius);
            mPbOnHeight = (int) (mPbBgHeight * 1.8f);//让进度大一点
            a.recycle();
            
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setDither(true);
        }
    
        @Override
        protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            mMaxPaintWidth = Math.max(mPbBgHeight, mPbOnHeight);
            int expect = mPbRadius * 2 + mMaxPaintWidth + getPaddingLeft() + getPaddingRight();
            int width = resolveSize(expect, widthMeasureSpec);
            int height = resolveSize(expect, heightMeasureSpec);
            int realWidth = Math.min(width, height);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
    
            mPbRadius = (realWidth - getPaddingLeft() - getPaddingRight() - mMaxPaintWidth) / 2;
            setMeasuredDimension(realWidth, realWidth);
        }
    
        @Override
        protected synchronized void onDraw(Canvas canvas) {
    
            String txt = getProgress() + "%";
            float txtWidth = mPaint.measureText(txt);
            float txtHeight = (mPaint.descent() + mPaint.ascent()) / 2;
            canvas.save();
            canvas.translate(getPaddingLeft() + mMaxPaintWidth / 2, getPaddingTop() + mMaxPaintWidth / 2);
            drawDot(canvas);
            mPaint.setStyle(Paint.Style.STROKE);
            //背景
            mPaint.setColor(mPbBgColor);
            mPaint.setStrokeWidth(mPbBgHeight);
            canvas.drawCircle(mPbRadius, mPbRadius, mPbRadius, mPaint);
            //进度条
            mPaint.setColor(mPbOnColor);
            mPaint.setStrokeWidth(mPbOnHeight);
            float sweepAngle = getProgress() * 1.0f / getMax() * 360;//完成角度
            canvas.drawArc(
                    0, 0, mPbRadius * 2, mPbRadius * 2,
                    -90, sweepAngle, false, mPaint);
            //文字
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setColor(mPbTxtColor);
            canvas.drawText(txt, mPbRadius - txtWidth / 2, mPbRadius - txtHeight / 2, mPaint);
            canvas.restore();
        }
    
        /**
         * 绘制一圈点
         *
         * @param canvas
         */
        private void drawDot(Canvas canvas) {
            canvas.save();
            int num = 40;
            canvas.translate(mPbRadius, mPbRadius);
            for (int i = 0; i < num; i++) {
                canvas.save();
                int deg = 360 / num * i;
                canvas.rotate(deg);
                mPaint.setStrokeWidth(dp(3));
                mPaint.setColor(mPbBgColor);
                mPaint.setStrokeCap(Paint.Cap.ROUND);
                if (i * (360 / num) < getProgress() * 1.f / getMax() * 360) {
                    mPaint.setColor(mPbOnColor);
                }
                canvas.drawLine(0, mPbRadius * 3 / 4, 0, mPbRadius * 4 / 5, mPaint);
                canvas.restore();
            }
            canvas.restore();
        }
    }
    
    

    后记:捷文规范

    1.本文成长记录及勘误表
    项目源码 日期 备注
    V0.1--无 2018-11-9 Android原生绘图进度条+简单自定义属性代码生成器
    2.更多关于我
    笔名 QQ 微信 爱好
    张风捷特烈 1981462002 zdl1994328 语言
    我的github 我的简书 我的CSDN 个人网站
    3.声明

    1----本文由张风捷特烈原创,转载请注明
    2----欢迎广大编程爱好者共同交流
    3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    4----看到这里,我在此感谢你的喜欢与支持


    icon_wx_200.png

    附录:简单自定义属性生成器

    public class Attrs2Code {
        @Test
        public void main() {
            File file = new File("C:\\Users\\Administrator\\Desktop\\attrs.xml");
            initAttr("z_", file);
        }
    
        public static void initAttr(String preFix, File file) {
            HashMap<String, String> format = format(preFix, file);
            String className = format.get("className");
            String result = format.get("result");
            StringBuilder sb = new StringBuilder();
            sb.append("TypedArray a = context.obtainStyledAttributes(attrs, R.styleable." + className + ");\r\n");
            format.forEach((s, s2) -> {
                String styleableName = className + "_" + preFix + s;
                if (s.contains("_")) {
                    String[] partStrArray = s.split("_");
                    s = "";
                    for (String part : partStrArray) {
                        String partStr = upAChar(part);
                        s += partStr;
                    }
                }
                if (s2.equals("dimension")) {
                    // mPbBgHeight = (int) a.getDimension(R.styleable.TolyProgressBar_z_pb_bg_height, mPbBgHeight);
                    sb.append("m" + s + " = (int) a.getDimension(R.styleable." + styleableName + ", m" + s + ");\r\n");
                }
                if (s2.equals("color")) {
                    // mPbTxtColor = a.getColor(R.styleable.TolyProgressBar_z_pb_txt_color, mPbTxtColor);
                    sb.append("m" + s + " =  a.getColor(R.styleable." + styleableName + ", m" + s + ");\r\n");
                }
                if (s2.equals("boolean")) {
                    // mPbTxtColor = a.getColor(R.styleable.TolyProgressBar_z_pb_txt_color, mPbTxtColor);
                    sb.append("m" + s + " =  a.getBoolean(R.styleable." + styleableName + ", m" + s + ");\r\n");
                }
                if (s2.equals("string")) {
                    // mPbTxtColor = a.getColor(R.styleable.TolyProgressBar_z_pb_txt_color, mPbTxtColor);
                    sb.append("m" + s + " =  a.getString(R.styleable." + styleableName + ");\r\n");
                }
            });
            sb.append("a.recycle();\r\n");
            System.out.println(result);
            System.out.println(sb.toString());
        }
    
        /**
         * 读取文件+解析
         *
         * @param preFix 前缀
         * @param file   文件路径
         */
        public static HashMap<String, String> format(String preFix, File file) {
            HashMap<String, String> container = new HashMap<>();
            if (!file.exists() && file.isDirectory()) {
                return null;
            }
            FileReader fr = null;
            try {
                fr = new FileReader(file);
                //字符数组循环读取
                char[] buf = new char[1024];
                int len = 0;
                StringBuilder sb = new StringBuilder();
                while ((len = fr.read(buf)) != -1) {
                    sb.append(new String(buf, 0, len));
                }
                String className = sb.toString().split("<declare-styleable name=\"")[1];
                className = className.substring(0, className.indexOf("\">"));
                container.put("className", className);
                String[] split = sb.toString().split("<");
                String part1 = "private";
                String type = "";//类型
                String name = "";
                String result = "";
                String def = "";//默认值
    
                StringBuilder sb2 = new StringBuilder();
                for (String s : split) {
                    if (s.contains(preFix)) {
                        result = s.split(preFix)[1];
                        name = result.substring(0, result.indexOf("\""));
                        type = result.split("format=\"")[1];
                        type = type.substring(0, type.indexOf("\""));
                        container.put(name, type);
                        if (type.contains("color") || type.contains("dimension") || type.contains("integer")) {
                            type = "int";
                            def = "0";
                        }
                        if (result.contains("fraction")) {
                            type = "float";
                            def = "0.f";
                        }
                        if (result.contains("string")) {
                            type = "String";
                            def = "\"toly\"";
                        }
                        if (result.contains("boolean")) {
                            type = "boolean";
                            def = "false";
    
                        }
                        if (name.contains("_")) {
                            String[] partStrArray = name.split("_");
                            name = "";
                            for (String part : partStrArray) {
                                String partStr = upAChar(part);
                                name += partStr;
                            }
                            sb2.append(part1 + " " + type + " m" + name + "= " + def + ";\r\n");
                        }
                        container.put("result", sb2.toString());
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if (fr != null) {
                        fr.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return container;
        }
    
        /**
         * 将字符串仅首字母大写
         *
         * @param str 待处理字符串
         * @return 将字符串仅首字母大写
         */
        public static String upAChar(String str) {
            String a = str.substring(0, 1);
            String tail = str.substring(1);
            return a.toUpperCase() + tail;
        }
    }
    

    相关文章

      网友评论

        本文标题:Android原生绘图进度条+简单自定义属性代码生成器

        本文链接:https://www.haomeiwen.com/subject/rgtoxqtx.html