在很多安卓崗位的職位描述上犁享,都會提到一個“自定義控件”。這個東西上手其實并不難豹休,但真想做好自定義控件炊昆,要會的東西還挺多。下面我就分享個簡單的例子威根,給自己凤巨,也給需要的人。
需求:服務(wù)端傳過來的數(shù)據(jù)長這個樣子:
這是一個<important>甲方爸爸</important>特別<important>強(qiáng)調(diào)</important>的需求
在TextView中要顯示成這個樣子:
這是一個特別
的需求
下面就是我的進(jìn)化之路:
零洛搀、不使用自定義控件
其實就是簡單的文字替換敢茁,Android中有一個Spannable
public static Spanned important(String text, String color) {
if ( text.split("<important>").length < 2) return new SpannableString(important);
text = text.replace("/ important", "/font></html") .replace(" important", "html><font color=\"" + color + "\"");
return Html.fromHtml(text);
}
純屬一個方法,于是在那個頁面里姥卢,滿眼望去全是important:
tvName.setText(important(object.getString("name")));
tvJob.setText(important(object.getString("job")));
tvDevice.setText(important(object.getString("device")));
然后就引出了第一種自定義控件
一卷要、擴(kuò)展控件
最簡單的渣聚,是基于現(xiàn)有控件進(jìn)行控件擴(kuò)展。既然方法已經(jīng)寫好了僧叉,繼承原來的TextView寫一個新的控件奕枝,把setText()方法重寫一下就好啦:
public class ImportantTextView extends AppCompatTextView {
public ImportantTextView(Context context) {
super(context);
}
public ImportantTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ImportantTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setText(String text) {
setImportantText(text, "#ff8200");
}
public void setImportantText(String text, String color) {
if (text.split("<mportant>").length < 2) {
setText(text);
} else {
text = text.replace("/mportant", "/font></html").replace("em", "html><font color=\"" + color + "\"");
setText(Html.fromHtml(text));
}
}
}
補(bǔ)充:為什么繼承AppCompatTextView而不是TextView?
AppCompatTextView是在API level 23引入的瓶堕,繼承自TextView隘道,它是Android標(biāo)準(zhǔn)TextView的增強(qiáng),特點就是可以自適應(yīng)字體寬度大小變化郎笆。有一點要提的是谭梗,在android官方文檔中,xml里寫的傳統(tǒng)TextView已經(jīng)被編譯器替換成AppCompatTextView了宛蚓,不需要開發(fā)者再去手動替換激捏。
另外,如果自定義控件上面繼承TextView凄吏,會報錯的远舅。。痕钢。
但是图柏,就這么寫一個控件實在是拿不出手,其實就相當(dāng)于把外面的方法放到了控件里任连,太沒技術(shù)含量了
二蚤吹、重新繪制控件
先重寫onLayout():
略,因為沒寫~
再重寫onMeasure():
略随抠,因為也沒寫~
上面這兩個方法裁着,前者用來確定控件在父控件的位置,后者用來測量控件的寬高大小暮刃。
在這里我只用到了onDraw()方法:
@Override
protected void onDraw(Canvas canvas) {
if (text == null || text.isEmpty())
return; // 如果沒有輸入文字跨算,也就沒有意義畫出控件了
if (text.split("<important>").length < 2)
canvas.drawText(text, 0, getHeight(), mPaint); // 如果沒有重點標(biāo)簽或者標(biāo)簽只出了一個,也沒有必要進(jìn)行加工了
else {
String[] t = text.replace("/important", "")
.replace("important", "")
.split("<>");
float x = 0.0f;
for (int i = 0; i < t.length; i++) {
int textWidth = 0;
canvas.save();
if (i % 2 == 0) {
canvas.drawText(t[i], x, getY(), mPaint);
textWidth = getTextWidth(mPaint, t[i]);
} else {
canvas.drawText(t[i], x, getY(), mEMPaint);
textWidth = getTextWidth(mEMPaint, t[i]);
}
x = x + textWidth;
canvas.restore();
}
}
requestLayout();
}
在繪制的時候就直接把顏色涂到上面椭懊,這樣比之前的更顯技術(shù)含量~
沒完带膀,其實還有第三種
三确憨、基于控件容器的自定義控件
最簡單驹碍,效率一般柳琢,可讀性一般,功能性簡單盅抚,非通用方法
在這個需求要怎么寫呢漠魏?
先要有一個布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text4"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text5"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text6"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text7"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text8"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text9"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
而控件代碼如下:
public ImportantTextView3(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LayoutInflater.from(context).inflate(R.layout.layout_important, this, true);
tvTexts = new ArrayList<>();
tvTexts.add((TextView) findViewById(R.id.text1));
tvTexts.add((TextView) findViewById(R.id.text2));
tvTexts.add((TextView) findViewById(R.id.text3));
tvTexts.add((TextView) findViewById(R.id.text4));
tvTexts.add((TextView) findViewById(R.id.text5));
tvTexts.add((TextView) findViewById(R.id.text6));
tvTexts.add((TextView) findViewById(R.id.text7));
}
public void setText(String text) {
if (text == null || text.isEmpty())
return;
if (text.split("<important>").length < 2) {
tvTexts.get(0).setText(text);
} else {
String[] t = text.replace("/important", "").replace("important", "").split("<>");
int i = 0;
while (i < t.length && i < tvTexts.size() - 1) {
if (i % 2 != 0)
tvTexts.get(i).setTextColor(0xffff8200);
tvTexts.get(i).setText(t[i]);
i++;
}
if (i >= tvTexts.size() - 1) {
StringBuilder last = new StringBuilder();
for (; i < t.length; i++) {
last.append(t[i]);
}
tvTexts.get(tvTexts.size() - 1).setText(last);
}
}
}
簡單粗暴,就是獲得控件妄均,給控件賦值柱锹。這種自定義控件可讀性極強(qiáng)哪自,但復(fù)用性極差,其實就是把很多原生空間整合到了一個控件容器禁熏,然后在項目中以整體形式出現(xiàn)壤巷。
這個控件不適用于這個需求,但并不代表這個方法沒有用瞧毙,通常這種做法用在列表控件或者重復(fù)性的布局上胧华。
補(bǔ)充說明:
如果:這段<important>文字</important>是<important>這個<important>樣子的
我這里顯示:這段是
樣子的
但按照需求應(yīng)該:這段是<important>這個<important>樣子的
這個是不太符合需求的,不過這個是線上項目宙彪,服務(wù)端的大哥指著房頂中央空調(diào)跟我保證過不會有這種情況矩动,所以暫且忽略掉。
而且我也沒想好如果真是這樣該怎么處理释漆,希望看到的能回貼幫我想一下悲没,謝謝~
下面貼出第二套方案的完整代碼:
package com.myprj.important.ImportantTextView;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.text.Html;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatTextView;
import com.myprj.important.R;
public class ImportantTextView2 extends AppCompatTextView {
private Paint mPaint, mImportantPaint;
private String text;
public ImportantTextView2(Context context) {
this(context, null);
}
public ImportantTextView2(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ImportantTextView2(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint(context, attrs);
}
private void initPaint(Context context, AttributeSet attrs) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.importantTextView2);
int color = array.getColor(R.styleable.ImportantTextView2_color, getTextColors().getDefaultColor());
int importantColor = array.getColor(R.styleable.ImportantTextView2_importantColor, 0xffff8200);
mPaint = getPaintByColor(color);
mImportantPaint = getPaintByColor(importantColor);
array.recycle();
}
private Paint getPaintByColor(int color) {
Paint paint = new Paint();
paint.setColor(color);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setTextSize(getTextSize());
return paint;
}
@Override
protected void onDraw(Canvas canvas) {
if (text == null || text.isEmpty())
return;
if (text.split("<important>").length < 2)
canvas.drawText(text, 0, getHeight(), mPaint);
else {
String[] t = text.replace("/important", "").replace("important", "").split("<>");
float x = 0.0f;
for (int i = 0; i < t.length; i++) {
int textWidth = 0;
canvas.save();
if (i % 2 == 0) {
canvas.drawText(t[i], x, getY(), mPaint);
textWidth = getTextWidth(mPaint, t[i]);
} else {
canvas.drawText(t[i], x, getY(), mImportantPaint);
textWidth = getTextWidth(mImportantPaint, t[i]);
}
x = x + textWidth;
canvas.restore();
}
}
requestLayout();
}
public static int getTextWidth(Paint paint, String str) {
int iRet = 0;
if (str != null && str.length() > 0) {
int len = str.length();
float[] widths = new float[len];
paint.getTextWidths(str, widths);
for (int j = 0; j < len; j++) {
iRet += (int) Math.ceil(widths[j]);
}
}
return iRet;
}
public void setText(String text) {
this.text = text;
super.setText(text);
}
}