Android原生绘图进度条+简单自定义属性代码生成器
零、前言
1.感覺切拼字符串是個很有意思的事,好的拼接方式可以自動生成一些很實用的東西
2.本文自定義控件并不是很高大上的東西,目的在于計錄自定義控件的書寫規范與行文流程
3.建議大家自定義控件時自定義屬性有自己專屬前綴,有利無害,何樂不為
4.本文是根據鴻洋在慕課網上的教程敲的:詳見,自己修改并優化了一點邏輯和顯示效果
先看一下效果:
一、簡單自定義屬性生成器
1.玩安卓的應該都寫過自定義控件的自定義屬性:如下
我寫著寫著感覺好枯燥,基本上流程相似,也沒有什么技術難度,想:這種事不就應該交給機器嗎?
2.通過attrs.xml自動生成相應代碼
秉承著能用代碼解決的問題,絕對不動手。能夠靠智商解決的問題,絕對不靠體力的大無畏精神:
寫了一個小工具,將代碼里的內容自動生成一下:基本上就是字符串的切割和拼裝,工具附在文尾
使用方法與注意點:
1.拷貝到AndroidStudio的test里,將attrs.xml的文件路徑設置一下,運行
2.自定義必須符合命名規則,如z_pb_on_height,專屬前綴如z_,單詞間下劃線連接即可
3.它并不是什么高大上的東西,只是簡單的字符串切割拼組,只適用簡單的自定義屬性[dimension|color|boolean|string](不過一般的自定義屬性也夠用了)
在開篇之前:先看一下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進行賦值
一、水平的進度條
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,y0, 0,//右上圓角x,y0, 0,//右下圓角x,ymPbOnHeight / 2, mPbOnHeight / 2//左下圓角x,y};mFloat8Right = new float[]{0, 0,//左上圓角x,ymPbBgHeight / 2, mPbBgHeight / 2,//右上圓角x,ymPbBgHeight / 2, mPbBgHeight / 2,//右下圓角x,y0, 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.測量:
@Overrideprotected 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.繪制:
@Overrideprotected 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,ymPbBgHeight / 2, mPbBgHeight / 2,//右上圓角x,ymPbBgHeight / 2, mPbBgHeight / 2,//右下圓角x,ymPbBgHeight / 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.TolyProgressBarandroid: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);}@Overrideprotected 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);}@Overrideprotected 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.更多關于我
| 張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
| 我的github | 我的簡書 | 我的CSDN | 個人網站 |
3.聲明
1----本文由張風捷特烈原創,轉載請注明
2----歡迎廣大編程愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這里,我在此感謝你的喜歡與支持
附錄:簡單自定義屬性生成器
public class Attrs2Code {@Testpublic 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原生绘图进度条+简单自定义属性代码生成器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Koa2基础
- 下一篇: Android Pie 引入 Keyst