創(chuàng)建Android自定義視圖

任何的開(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è)效果,你需要:

  1. <declare-styleable>中添加自定義視圖的屬性。
  2. 在XML布局文件中定義這些屬性的值。
  3. 在運(yùn)行時(shí)獲取屬性值。
  4. 在自定義視圖中使用這些屬性。

添加一個(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è)自定義屬性:showTextlabelPosition。并且這兩個(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è)自定義的視圖就完成了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容