任何的開(kāi)發(fā)都不應(yīng)該是簡(jiǎn)單地界面視圖的堆砌,即使是堆砌也應(yīng)該是有序的、可復(fù)用的。下面我們來(lái)講解如何實(shí)現(xiàn)一個(gè)自定義視圖。
繼承一個(gè)View
Android里定義的所有視圖類都是繼承自View的。你的自定義視圖也可以直接繼承View,但是這樣的話就需要自己實(shí)現(xiàn)很多已經(jīng)在Android SDK里實(shí)現(xiàn)好的代碼。時(shí)間很寶貴,所以還是在別人實(shí)現(xiàn)好的基礎(chǔ)上來(lái)實(shí)現(xiàn)我們需要的部分是最合適的,比如我們要改造一個(gè)按鈕就直接繼承Button好了。不過(guò)作為講解我們繼承View來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的餅圖。
class PieChart : View {
private var _context: Context ? = null
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
this._context = context
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
}
}
這只是一個(gè)簡(jiǎn)單的例子。我們要定義一個(gè)PieChart的視圖,它繼承了View。里面給出了三個(gè)構(gòu)造函數(shù)。
添加自定義屬性
要在界面中添加一個(gè)內(nèi)置的視圖,你只需要在XML的布局中定義出對(duì)應(yīng)的界面和行為等屬性。一個(gè)良好的自定義的視圖也是可以在XML中定義并添加樣式。要實(shí)現(xiàn)這個(gè)效果,你需要:
- 在
<declare-styleable>中添加自定義視圖的屬性。 - 在XML布局文件中定義這些屬性的值。
- 在運(yùn)行時(shí)獲取屬性值。
- 在自定義視圖中使用這些屬性。
添加一個(gè)資源文件來(lái)存放自定義視圖的屬性。路徑為:res/values/attrs.xml。attrs.xml文件是這樣的:
<resources>
<declare-styleable name="PieChart">
<attr name="showText" format="boolean" />
<attr name="labelPosition" format="enum">
<enum name="left" value="0" />
<enum name="right" value="1" />
</attr>
</declare-styleable>
</resources>
上面的代碼定義了兩個(gè)自定義屬性:showText和labelPosition。并且這兩個(gè)屬性都屬于名稱為PicChart的節(jié)點(diǎn)下。一般這個(gè)節(jié)點(diǎn)的名稱就是你的自定義視圖的名稱,當(dāng)然你也可以不這么干。
下面看看定義好的屬性該如何使用。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="test.demo.myapplication.customViews.CustoViewmActivity">
<test.demo.myapplication.customViews.PieChart
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:labelPosition="left"
app:showText="true"/>
</LinearLayout>
在XML布局中直接使用一個(gè)自定義View就是這樣的test.demo.myapplication.customViews.PieChart。如果在PieChart里有一個(gè)內(nèi)部類PieView要使用的話,那么就是這樣的:test.demo.myapplication.customViews.PieChart$PieView。用$分割。
使用自定義屬性
前文我們?cè)赬ML布局文件里使用的屬性都會(huì)從資源文件的bundle里讀取出來(lái),并作為一個(gè)AttributeSet實(shí)例傳入View的構(gòu)造函數(shù)里。
雖然可以直接讀取AttributeSet的數(shù)據(jù),但是不推薦這樣。一般都是把AttributeSet傳入obtainStyledAttributes()方法里,并使用這個(gè)方法的返回值TypedArray。如:
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
this._context = context
var a = context.theme.obtainStyledAttributes(attrs, R.styleable.PieChart, 0, 0)
try {
_showText = a.getBoolean(R.styleable.PieChart_showText, false) as Boolean
} finally {
a.recycle()
}
}
注意:TypedArray是共享的資源,使用之后必須回收。
添加屬性和事件
屬性是控制視圖行為和圍觀的一個(gè)很有效的途徑,但是視圖初始化之后就只能讀取了。要提供動(dòng)態(tài)行為,就暴露屬性的一個(gè)getter和一個(gè)setter。下面的代碼里展示了PieChart是如何暴露showText屬性的:
fun isShowText():Boolean {
return _showText
}
fun setShowText(showText:Boolean) {
_showText = showText
invalidate()
requestLayout()
}
注意setShowText調(diào)用了invalidate()和requestLayout()兩個(gè)方法。這些調(diào)用才是視圖動(dòng)態(tài)行為的保證。改變了視圖的屬性之后,你必須invalidate了視圖才能改變它的外觀。這幾個(gè)方法用來(lái)通知系統(tǒng)視圖需要重繪。同樣的,屬性的更改如果影響到視圖的外觀,比如大小或者形狀,你也需要請(qǐng)求一個(gè)新的布局。這些方法如果不調(diào)用的話會(huì)出現(xiàn)很難找到的bug。
自定義視圖也支持添加事件。比如PieChart暴露一個(gè)自定義的事件OnCurrentItemChanged。這個(gè)事件可以用來(lái)通知監(jiān)聽(tīng)器用戶改變了當(dāng)前的關(guān)注的圖塊??聪麓a是什么樣的:
class PieChart : View {
public interface OnCurrentItemChangedListener {
fun OnCurrentItemChanged(source: PieChart, currentItem: Int)
}
fun setCurrentItem(currentItem: Int, scrollIntoView: Boolean) {
_currentItem = currentItem
if (_currentItemChangedListener != null) {
_currentItemChangedListener!!.OnCurrentItemChanged(this, currentItem)
}
if (scrollIntoView) {
centerOnCurrentItem()
}
invalidate()
}
fun setCurrentItemChangedListener(listener: OnCurrentItemChangedListener) {
_currentItemChangedListener = listener
}
}
首先自定義一個(gè)接口OnCurrentItemChangedListener為以后使用。在方法setCurrentItemChangedListener中設(shè)置listener。并在事件發(fā)生的時(shí)候調(diào)用listener,通知監(jiān)聽(tīng)器。
這樣一個(gè)自定義的視圖就完成了。