Zero, preface,

1. I feel that it is interesting to cut and piece strings together. A good way of joining together can automatically generate some very useful things

2. This custom control is not very high things, the purpose is to record the custom control writing specifications and the flow of the text 3. We suggest custom control custom attributes have their own exclusive prefix, harmless, why not 4. This article is based on hongyang’s moOCs tutorial: see, modified and optimized a little logic and display effect

First look at the effect:

A simple custom attribute generator

1. Android users should have written the custom properties of custom control: as follows

I write write feel very boring, basically the process is similar, there is no technical difficulty, think: this matter should not be handed over to the machine?

2. Use attrs. XML to automatically generate the corresponding code

Never use a problem you can solve in code. A problem can be solved by intelligence, not by physical courage:

I wrote a small tool to automatically generate the contents of the code: basically a string cutting and assembling tool attached to the end of the text

Usage methods and points for attention:

1. Copy to test in AndroidStudio, set attrs.xml file path, and run 2. The custom must comply with the naming rules, such as Z_PB_on_height, special prefixes such as z_, and underscores between words 3. It is not what big things, just a simple string together a cutting, and is only for simple custom properties [dimension | color | Boolean | string] (but common custom attributes are also will be enough)

Before we start: Let’s take a look at the writing style of custom controls in Android. After all, what’s the harm in keeping up with native controls

Take a look at the source code for LinearLayout:

1. The constructor that takes the most arguments, the others are called with 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); . }Copy the code
2. Custom attribute writing

1). First define the member variables of the custom attribute

2). If there are not many custom properties, one by one a.getxxx, the default value is written directly after 3). Look at the TextView source code, there are many custom attributes, it is to define the default value of the variable, and then use, and use the switch to a.getxxx to assign value

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();Copy the code

1. Horizontal progress bar

1. Customize the control properties: values/attrs.xml
<! -- Declare -- styleable name="TolyProgressBar"> <! -- Progress bar related --> <! - the background color - > < attr name = "z_pb_bg_color format =" "color" / > <! Background - high - > < attr name = "z_pb_bg_height format =" dimension "/" > <! - color - > < attr name = "z_pb_on_color format =" "color" / > <! Progress - high - > < attr name = "z_pb_on_height format =" dimension "/" > <! -- Text related --> <! - text color - > < attr name = "z_pb_txt_color format =" "color" / > <! - text size - > < attr name = "z_pb_txt_size format =" dimension "/" > <! <attr name="z_pb_txt_offset" format="dimension"/> <! < declare-styleable> <attr name="z_pb_txt_gone" format=" Boolean "/> </declare-styleable>Copy the code
2. Initial code: Will do some general processing
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[]{mFloat8Left = new float[]{mFloat8Left = new float[]{mFloat8Left = new float[]{mFloat8Left = new float[]{mPbOnHeight / 2, mPbOnHeight / 2, mPbOnHeight / 2, 0, x,y mPbOnHeight / 2, mPbOnHeight / 2, x,y}; MFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{mFloat8Right = new float[]{ MPbBgHeight / 2,// bottom right corner x,y 0, 0; } } 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()); }Copy the code
2. The measurement:
@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(); // The actual width of the progress bar}Copy the code
/** * @param heightMeasureSpec * @return */ private int measureHeight(int heightMeasureSpec) {int result = 0; int mode = MeasureSpec.getMode(heightMeasureSpec); int size = MeasureSpec.getSize(heightMeasureSpec); // android:layout_height="40dp" or "match_parent" result = size; // android:layout_height="40dp" or "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; }Copy the code
3. Draw:
@Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.translate(getPaddingLeft(), getHeight() / 2); parseBeforeDraw(); If (getProgress() == 100) {when (getProgress() == 100); //2.} if (mEndX >0) {drawProgress(Canvas); //3. } if (! MPbTxtGone) {// Draw the text 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(); }Copy the code
1).praseBeforeDraw()
*/ private void parseBeforeDraw() {mLostRight = false; Float radio = getProgress() * 1.f/getMax(); float radio = getProgress() * 1.f/getMax(); MProgressX = radio * mPBWidth; MEndX = mProgressX -mpbtxtoffset / 2; MText = getProgress() + "%"; mText = getProgress() + "%"; if (mProgressX + mTextWidth > mPBWidth) { mProgressX = mPBWidth - mTextWidth; mLostRight = true; } mTextWidth = (int) mPaint. MeasureText (mText); }Copy the code
2).whenOver()
/** * When the end is executed: */ private void whenOver() {mPbTxtGone = true; MFloat8Left = new float[]{mFloat8Left = new float[]{mFloat8Left = new float[]{mFloat8Left = new float[]{mFloat8Left = new float[]{mFloat8Left = new float[]{ MPbBgHeight / 2, y mPbBgHeight / 2, y mPbBgHeight / 2, y mPbBgHeight / 2, y mPbBgHeight / 2, y mPbBgHeight / 2, y mPbBgHeight / 2, y mPbBgHeight / 2, y }Copy the code
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); // Draw mPaint. SetStyle (Paint. Style.fill); mPaint.setColor(mPbOnColor); canvas.drawPath(mPath, mPaint); // Use path to draw a line with a round head at one end}Copy the code
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); DrawPath (mPath, mPaint); // Use path to draw a line with a round head at one end}Copy the code
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"/>
Copy the code

3. Circular progress bar
1. Customize attributes
<! -- Circular Progress bar --> < Declare -styleable name="TolyRoundProgressBar"> <! < declare-styleable> < declare-styleable> < declare-styleable>Copy the code
2. Code implementation:
/** * Author: Zhang Feng Jiete: <br/> * Time: 2018/11/90009:11:49 <br/> * Email: [email protected]<br/> * Description: Public class 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); // Let the progress be bigger 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); / / background mPaint setColor (mPbBgColor); mPaint.setStrokeWidth(mPbBgHeight); canvas.drawCircle(mPbRadius, mPbRadius, mPbRadius, mPaint); // Progress bar mPaint. SetColor (mPbOnColor); mPaint.setStrokeWidth(mPbOnHeight); Float sweepAngle = getProgress() * 1.0f/getMax() * 360; float sweepAngle = getProgress() * 1.0f/getMax() * 360; // Complete the Angle Canvas. DrawArc (0, 0, mPbRadius * 2, mPbRadius * 2, -90, sweepAngle, false, mPaint); / / text mPaint. SetStyle (Paint. Style. The 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(); }}Copy the code

Postscript: Jie wen standard

1. Growth record and Errata of this paper
Program source code The date of note
V0.1, The 2018-11-9 Android native drawing progress bar + simple custom property code generator
2. More about me
Pen name QQ WeChat hobby
Zhang Feng Jie te Li 1981462002 zdl1994328 language
My lot My Jane books My CSDN Personal website
3. The statement

1—- This article is originally written by Zhang Fengjie, please note if reproduced

2—- welcome the majority of programming enthusiasts to communicate with each other 3—- personal ability is limited, if there is something wrong welcome to criticize and testify, must be humble to correct 4—- see here, I thank you here for your love and support



Appendix: Simple custom property generator

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()); Public static HashMap<String, String> format(String preFix, String) 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 = ""; // Type String name = ""; String result = ""; String def = ""; // Default 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; } public static String upAChar(String STR) {String a =.} public static String upAChar(String STR) {String a = str.substring(0, 1); String tail = str.substring(1); return a.toUpperCase() + tail; }}Copy the code