前言
在開發AndroidUI時,如果想要做出自己想要的特定的外觀效果,就不可能只依賴Android原生組件,雖然說隨著Android的版本更迭其原生組件的美觀度有了很大的提升,但是對于一個完整的App來說,它們很難與我們想要的整體效果保持一致,所以這個時候我們就需要自定義組件來實現特定的效果。
本文將介紹實現基本的自定義組件的方法,并完成一個自定義的EditText做為示例。
自定義組件基本步驟
-
繼承View的子類
在AndroidUI中,所有的布局、組件等界面元素都是繼承自View類,這個類定義了一個界面元素的標準行為,包括確定位置,確定尺寸,繪制外觀樣式等,當我們自定義組件時,就需要重寫onMeasure,onLayout,onDraw方法。當我們需要的組件與原生組件的功能相似時,我們可以直接繼承自具體的View的子類,如ImageButton,當我們需要完全重新定義一個組件時,需要做的工作就比較多,這時就需要繼承View或ViewGroup。本文暫時只討論前者。
-
為自定義View添加屬性
這里的屬性是指配置UI組件時的屬性,我們在自定義組件時,原生View的屬性往往不能滿足我們的配置需求,我們可能需要一些更多樣化或更精確的控制,這時我們就需要為View添加屬性,這樣我們定義的組件就會變的通用。
-
編寫代碼覆蓋View中的繪制方法
即覆蓋View中的onMeasure,onLayout,onDraw等方法。
-
自定義事件響應方法和部分回調方法
onFinishInflate() 回調方法,當應用從XML加載該組件并用它構建界面之后調用的方法
onMeasure() 檢測View組件及其子組件的大小
onLayout() 當該組件需要分配其子組件的位置、大小時
onSizeChange() 當該組件的大小被改變時
onDraw() 當組件將要繪制它的內容時
onKeyDown 當按下某個鍵盤時
onKeyUp 當松開某個鍵盤時
onTouchEvent 當發生觸屏事件時
onWindowFocusChanged(boolean) 當該組件得到、失去焦點時
onAtrrachedToWindow() 當把該組件放入到某個窗口時
onDetachedFromWindow() 當把該組件從某個窗口上分離時觸發的方法
onWindowVisibilityChanged(int): 當包含該組件的窗口的可見性發生改變時觸發的方法
自定義Edittext示例
本例的目的不在于創建一個十分美觀的組件,主要是為了通過一個簡單的示例說明上述內容的具體實現方法,幫助大家理解自定義組件的基本原理和方法。明白了基本的自定義組件實現方法,創建出炫酷的組件還會遠嗎?
-
右下角字數統計
由于要在下劃線下顯示字數統計,所以下劃線的繪制位置要修改到字的上端。另外,由于字數統計的存在,還需要增加EditText的bottom padding,把字顯示在多出來的這塊padding上。
-
自定義屬性
設置了一個自定義屬性lineColor,用于設置下劃線顏色,可以在xml布局文件中直接進行設置。
-
效果圖
-
示例代碼
MyEditText.java
public class MyEditText extends EditText {
// 畫下劃線的畫筆
private Paint paint;
// 繪制計數的畫筆
private TextPaint textPaint;
// 下劃線的開始y坐標
private int lineY;
// 下劃線的顏色
private int lineColor;
// 當前輸入的字符數
private int count;
// 用來獲取計數結果的字符串
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);
// 初始化字符計數的畫筆
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);
// 繪制右下角的計數
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="請輸入內容"
android:background="@null"
/>
</LinearLayout>