1.感受切拼字符串是個頗有意思的事,好的拼接方式能夠自動生成一些很實用的東西
2.本文自定義控件並非很高大上的東西,目的在於計錄自定義控件的書寫規範與行文流程
3.建議你們自定義控件時自定義屬性有本身專屬前綴,有利無害,何樂不爲
4.本文是根據鴻洋在慕課網上的教程敲的:詳見,本身修改並優化了一點邏輯和顯示效果android
我寫着寫着感受好枯燥,基本上流程類似,也沒有什麼技術難度,想:這種事不就應該交給機器嗎?git
秉承着
能用代碼解決的問題,絕對不動手。可以靠智商解決的問題,絕對不靠體力
的大無畏精神:
寫了一個小工具,將代碼裏的內容自動生成一下:基本上就是字符串的切割和拼裝,工具附在文尾
github
使用方法與注意點:
1.拷貝到AndroidStudio的test裏,將attrs.xml的文件路徑設置一下,運行
2.自定義必須符合命名規則,如z_pb_on_height
,專屬前綴如z_
,單詞間下劃線鏈接便可
3.它並非什麼高大上的東西,只是簡單的字符串切割拼組,只適用簡單的自定義屬性[dimension|color|boolean|string]
(不過通常的自定義屬性也夠用了)編程
在開篇以前:先看一下Android系統內自定義控件的書寫風格,畢竟跟原生看齊沒有什麼壞處
看一下LinearLayout的源碼:canvas
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);
...
}
複製代碼
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();
複製代碼
<!--自定義進度條-->
<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>
複製代碼
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());
}
複製代碼
@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;
}
複製代碼
@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();
}
複製代碼
/**
* 對數值進行計算以及控制的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);
}
複製代碼
/**
* 當結束是執行:
*/
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
};
}
複製代碼
/**
* 繪製左側:(進度條)
*
* @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繪製一端是圓頭的線
}
複製代碼
/**
* 繪製左側:(背景)
*
* @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繪製一端是圓頭的線
}
複製代碼
<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"/>
複製代碼
<!--圓形進度條-->
<declare-styleable name="TolyRoundProgressBar">
<!--進度條半徑-->
<attr name="z_pb_radius" format="dimension"/>
</declare-styleable>
複製代碼
/**
* 做者:張風捷特烈<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();
}
}
複製代碼
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--無 | 2018-11-9 | Android原生繪圖進度條+簡單自定義屬性代碼生成器 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人CSDN | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持bash
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;
}
}
複製代碼