1. 概述
自定義View這東西很多哥們比較畏懼,如果你認(rèn)為他比較難,關(guān)鍵還是缺少實(shí)踐寫(xiě)得少;如果你認(rèn)為很簡(jiǎn)單,那可能是你沒(méi)有遇到過(guò)那些奇葩的效果,需要高等數(shù)學(xué)和各種算法。當(dāng)然我想要做的就是讓大家覺(jué)得很簡(jiǎn)單,如果你做了一兩年Android開(kāi)發(fā),給你一個(gè)效果根本沒(méi)法下手,會(huì)比較尷尬。自定義View是第一個(gè)坎,系統(tǒng)架構(gòu),數(shù)據(jù)結(jié)構(gòu)算法,內(nèi)存優(yōu)化,NDK后面還會(huì)有很多坎,每一個(gè)坎都需要花一定的時(shí)間。
對(duì)于自定義View其實(shí)有一些套路,比如onMeasure(),onDarw(),onTouch(),自定義屬性,我們需要知道是用來(lái)干什么的,剩下就是一些邏輯代碼了,其實(shí)也很簡(jiǎn)單。當(dāng)然后面講自定義ViewGroup需要看源碼,后面的事后面再說(shuō),今天這里我們主講自定義View的一些基礎(chǔ)知識(shí)。
2. 自定義View方法簡(jiǎn)介
系統(tǒng)給我們提供了很多控件,比如TextView,ImageView,Button等等,這些其實(shí)也可以說(shuō)是自定義View,只不過(guò)這些是Google工程師已經(jīng)寫(xiě)好,提供給我們用的而已。

我們自己實(shí)現(xiàn)一個(gè)TextView的效果算做是入門(mén),這期主要介紹幾個(gè)方法,下期再寫(xiě)邏輯代碼:
- onMeasure():用于測(cè)量,你的控件占多大的地方由這個(gè)方法指定;
- onDarw():用于繪制,你的控件呈現(xiàn)給用戶(hù)長(zhǎng)什么樣子由這個(gè)方法決定;
- onTouch():用于觸摸,處理與用戶(hù)交互,比如你手指拖動(dòng)應(yīng)該是什么效果由這個(gè)方法決定;
- 自定義屬性:用于配置,布局中android:text="1"就顯示1,android:text="2"就是顯示2.
2.1. onMeasure()方法
對(duì)于onMeasure我們不得不說(shuō)一下測(cè)量模式,發(fā)現(xiàn)網(wǎng)上很多人寫(xiě)的文章有些許差異。當(dāng)然測(cè)量模式與父View有一定的關(guān)系,這里我們先不關(guān)注,如果目前實(shí)在不懂那需要記住,分別是UNSPECIFIED,EXACTLY,AT_MOST。至于怎么獲取怎么用我們下期再說(shuō)。(后面會(huì)分析View的繪制流程源碼,會(huì)有些許差別)
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
- UNSPECIFIED :任意大小,想要多大就多大,盡可能大,一般我們不會(huì)遇到,如ListView,RecyclerView,ScrollView測(cè)量子View的時(shí)候給的就是UNSPECIFIED ,一般開(kāi)發(fā)中不需要關(guān)注它;
- EXACTLY :一個(gè)確定的值,比如在布局中你是這樣寫(xiě)的layout_width="100dp","match_parent","fill_parent";
- AT_MOST:包裹內(nèi)容,比如在布局中你是這樣寫(xiě)的layout_width="wrap_content"。
ScrollView嵌套ListView會(huì)出現(xiàn)顯示不全的現(xiàn)象,怎么解決這個(gè)問(wèn)題的?網(wǎng)上很多解決方案copy就行,面試官問(wèn)你為什么會(huì)出現(xiàn)這現(xiàn)象為什么這么解決? 恩...GG。
2.2. onDarw()方法
主要用來(lái)繪制效果,里面會(huì)有一個(gè)參數(shù)那就是canvas畫(huà)布,利用canvas就可以畫(huà)各式各樣的效果,如:canvas.drawCircle()畫(huà)圓形,canvas.drawBitmap()畫(huà)bitmap,我們這里肯定是需要畫(huà)文字,那就是drawText()畫(huà)文本。
@Override
protected void onDraw(Canvas canvas) {
// 畫(huà)圓
canvas.drawCircle();
// 畫(huà)bitmap
canvas.drawBitmap();
// 畫(huà)文本
canvas.drawText();
// ......
}
2.3. onTouch()方法
用來(lái)處理觸摸事件與用戶(hù)進(jìn)行交互,比如我早期寫(xiě)過(guò)QQ5.0,6.0側(cè)滑效果,字母索引列表效果等等。MotionEvent.ACTION_DOWN(手指按下)、MotionEvent.ACTION_MOVE(手指移動(dòng))、ACTION_UP(手指抬起)。當(dāng)然還有一個(gè)比較難以處理的,就是事件分發(fā)問(wèn)題,要看過(guò)源碼才行,后面再說(shuō)。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
2.3. 自定義屬性
自定義屬性就是做到配置不寫(xiě)死,比如我在TextView中text配置什么就顯示什么,textColor指定什么顏色就顯示什么顏色等等,這些都是自定義屬性。首先在res下的values目錄下新建一個(gè)attrs.xml文件,其他名稱(chēng)可以嗎?是可以的如attr.xml或者lqbz.xml但是原則上一看attrs就知道是自定義屬性。
<resources>
// 自定義TextView
<declare-styleable name="TextView">
// name 是名稱(chēng),format是格式 color(顏色),string(文本),dimension(sp,dp)...
<attr name="textColor" format="color"/>
<attr name="text" format="string"/>
<attr name="textSize" format="dimension"/>
</declare-styleable>
</resources>
在布局文件中使用
<com.darren.view.TextView
// app: 自定義屬性
app:text="自定義文本"
app:textColor="@color/colorAccent"
app:textSize="18sp"
// android: 系統(tǒng)自帶的屬性
android:layout_width="wrap_content"
android:layout_height="match_parent" />
代碼中獲取,我們可以去仿照TextView的源碼寫(xiě)
public TextView(Context context) {
this(context,null);
}
public TextView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取TypedArray
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.TextView);
// 獲取文本
mText = typedArray.getText(R.styleable.TextView_text);
// 獲取文字顏色
mTextColor = typedArray.getColorStateList(R.styleable.TextView_textColor);
// 獲取文字大小
mTextSize = typedArray.getDimensionPixelSize(R.styleable.TextView_textSize,mTextSize);
// 回收
typedArray.recycle();
}
萬(wàn)丈高樓平地起,萬(wàn)事開(kāi)頭難。下期我們?cè)賮?lái)寫(xiě)邏輯代碼就算是入了門(mén),以后所有的自定義效果也離不開(kāi)這幾個(gè)方法,離不開(kāi)這些套路。
所有分享大綱:Android進(jìn)階之旅 - 自定義View篇