前言
在開發(fā)AndroidUI時(shí),如果想要做出自己想要的特定的外觀效果,就不可能只依賴Android原生組件,雖然說隨著Android的版本更迭其原生組件的美觀度有了很大的提升,但是對(duì)于一個(gè)完整的App來說,它們很難與我們想要的整體效果保持一致,所以這個(gè)時(shí)候我們就需要自定義組件來實(shí)現(xiàn)特定的效果。
本文將介紹實(shí)現(xiàn)基本的自定義組件的方法,并完成一個(gè)自定義的EditText做為示例。
自定義組件基本步驟
-
繼承View的子類
在AndroidUI中,所有的布局、組件等界面元素都是繼承自View類,這個(gè)類定義了一個(gè)界面元素的標(biāo)準(zhǔn)行為,包括確定位置,確定尺寸,繪制外觀樣式等,當(dāng)我們自定義組件時(shí),就需要重寫onMeasure,onLayout,onDraw方法。當(dāng)我們需要的組件與原生組件的功能相似時(shí),我們可以直接繼承自具體的View的子類,如ImageButton,當(dāng)我們需要完全重新定義一個(gè)組件時(shí),需要做的工作就比較多,這時(shí)就需要繼承View或ViewGroup。本文暫時(shí)只討論前者。
-
為自定義View添加屬性
這里的屬性是指配置UI組件時(shí)的屬性,我們?cè)谧远x組件時(shí),原生View的屬性往往不能滿足我們的配置需求,我們可能需要一些更多樣化或更精確的控制,這時(shí)我們就需要為View添加屬性,這樣我們定義的組件就會(huì)變的通用。
-
編寫代碼覆蓋View中的繪制方法
即覆蓋View中的onMeasure,onLayout,onDraw等方法。
-
自定義事件響應(yīng)方法和部分回調(diào)方法
onFinishInflate() 回調(diào)方法,當(dāng)應(yīng)用從XML加載該組件并用它構(gòu)建界面之后調(diào)用的方法
onMeasure() 檢測(cè)View組件及其子組件的大小
onLayout() 當(dāng)該組件需要分配其子組件的位置、大小時(shí)
onSizeChange() 當(dāng)該組件的大小被改變時(shí)
onDraw() 當(dāng)組件將要繪制它的內(nèi)容時(shí)
onKeyDown 當(dāng)按下某個(gè)鍵盤時(shí)
onKeyUp 當(dāng)松開某個(gè)鍵盤時(shí)
onTouchEvent 當(dāng)發(fā)生觸屏事件時(shí)
onWindowFocusChanged(boolean) 當(dāng)該組件得到、失去焦點(diǎn)時(shí)
onAtrrachedToWindow() 當(dāng)把該組件放入到某個(gè)窗口時(shí)
onDetachedFromWindow() 當(dāng)把該組件從某個(gè)窗口上分離時(shí)觸發(fā)的方法
onWindowVisibilityChanged(int): 當(dāng)包含該組件的窗口的可見性發(fā)生改變時(shí)觸發(fā)的方法
自定義Edittext示例
本例的目的不在于創(chuàng)建一個(gè)十分美觀的組件,主要是為了通過一個(gè)簡(jiǎn)單的示例說明上述內(nèi)容的具體實(shí)現(xiàn)方法,幫助大家理解自定義組件的基本原理和方法。明白了基本的自定義組件實(shí)現(xiàn)方法,創(chuàng)建出炫酷的組件還會(huì)遠(yuǎn)嗎?
-
右下角字?jǐn)?shù)統(tǒng)計(jì)
由于要在下劃線下顯示字?jǐn)?shù)統(tǒng)計(jì),所以下劃線的繪制位置要修改到字的上端。另外,由于字?jǐn)?shù)統(tǒng)計(jì)的存在,還需要增加EditText的bottom padding,把字顯示在多出來的這塊padding上。
-
自定義屬性
設(shè)置了一個(gè)自定義屬性lineColor,用于設(shè)置下劃線顏色,可以在xml布局文件中直接進(jìn)行設(shè)置。
-
效果圖

-
示例代碼
MyEditText.java
public class MyEditText extends EditText {
// 畫下劃線的畫筆
private Paint paint;
// 繪制計(jì)數(shù)的畫筆
private TextPaint textPaint;
// 下劃線的開始y坐標(biāo)
private int lineY;
// 下劃線的顏色
private int lineColor;
// 當(dāng)前輸入的字符數(shù)
private int count;
// 用來獲取計(jì)數(shù)結(jié)果的字符串
private StringBuffer countString;
public MyEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public MyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.MyEditText);
lineColor = array.getColor(R.styleable.MyEditText_lineColor, Color.BLUE);
array.recycle();
countString = new StringBuffer();
super.setHintTextColor(Color.LTGRAY);
// 初始化下劃線畫筆
paint = new Paint();
paint.setColor(lineColor);
paint.setAntiAlias(true);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
// 初始化字符計(jì)數(shù)的畫筆
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextSize(40);
textPaint.setColor(Color.LTGRAY);
super.setPadding(getPaddingLeft(),getPaddingTop(),getPaddingRight(),getPaddingBottom()+70);
addListener();
}
private void addListener() {
addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
countString.delete(0, countString.length());
count = s.length();
countString.append(count);
countString.append("字");
}
});
}
@Override
protected void onDraw(Canvas canvas) {
// 下劃線的高度
lineY = getScrollY()+getHeight()-getPaddingBottom()+5;
// 繪制下劃線
canvas.drawRect(getScrollX(), lineY, getScrollX()+getWidth()-getPaddingRight(), lineY, paint);
// 繪制右下角的計(jì)數(shù)
canvas.drawText(countString.toString(), getScrollX()+getWidth()-getPaddingRight() - textPaint.measureText(countString.toString()), lineY + 60, textPaint);
super.onDraw(canvas);
}
}
res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyEditText">
<attr name="lineColor" format="color"/>
</declare-styleable>
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="se.sx14.myedittext.MainActivity">
<se.sx14.myedittext.MyEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:lineColor="#18b4ed"
android:hint="請(qǐng)輸入內(nèi)容"
android:background="@null"
/>
</LinearLayout>