Android自定義view的官方步驟

概述

Android已經為我們提供了大量的View供我們使用,但是可能有時候這些組件不能滿足
我們的需求,這時候就需要自定義控件了。自定義控件對于初學者總是感覺是一種復雜
的技術。因為里面涉及到的知識點會比較多。但是任何復雜的技術后面都是一點點簡單
知識的積累。通過對自定義控件的學習去可以更深入的掌握android的相關知識點,所
以學習android自定義控件是很有必要的。記得以前學習總是想著去先理解很多知識點,然后再來學著自定義控件,但是每次寫自定義控件的時候總是不知道從哪里下手啊。后來在學習的過程中發現自己跟著去寫一些簡單的自定義控件,然后在這個過程中遇到了沒有掌握的知識點再去學習。不僅自定義控件的能力有所提高。其它的知識也有了很好的鞏固和認識。所以,今天寫的是怎么去自定義一個控件。而不是里面涉及到的細化知識點。一個東西我們先知道怎么用,再去問為什么。

自定義控件需要考慮的點

根據Android Developers官網的介紹,自定義控件你需要以下的步驟。(根據你的需要,某些步驟可以省略)

1、創建View

2、處理View的布局

3、繪制View

4、與用戶進行交互

5、優化已定義的View

上面列出的五項就是android官方給出的自定義控件的步驟。每個步驟里面又包括了很多細小的知識點。我們可以記住這五個點,并且了解每個點里包含的小知識點。再加上一些自定義控件的練習。不斷的將這些知識熟練于心,相信我們每個人都能夠定義出優秀的自定義控件。接下來我們開始對上面列出的5個要點進行細化解說

自定義控件5個要點詳細說明

1、創建View

繼承View

對Android有一些了解的朋友都知道,android為我們提供的很多View都是繼承與View的。所以我們自定義的View當然也是繼承于View,當然如果你要自定義的View擁有某些android已經提供的控件的功能,你可以直接繼承于已經提供的控件。

我們在使用android提供的控件的時候,我們在.xml文件中編輯了一個控件,在運行的時候就能夠看到和獲得這個控件。我們自定義的控件當然也要支持配置和一些自定義屬性,所以下面的構造方法就必須有了。這個構造方法允許我們在.xml文件中創建和編輯我們自定義控件的實例。

上面說了那么多其實就是下面一段代碼。

class PieChart extends View {//繼承View 
      public PieChart(Context context, AttributeSet attrs) { 
           super(context, attrs); 
       }//為了支持.xml中進行創建和編輯
}

定義自定義屬性

大部分情況我們的自定義View需要有更多的靈活性,比如我們在xml中指定了顏色大小等屬性,在程序運行時候控件就能展示出相應的顏色和大小。所以我們需要自定義屬性

自定義屬性通常寫在在res/values/attrs.xml文件中 下面是自定義屬性的標準寫法

 <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>

這段代碼聲明了兩個自定義屬性,showText和labelPosition,它們都是屬于styleable PieChart的,為了方便,一般styleable的name和我們自定義控件的類名一樣。自定義控件定義好了之后就是使用了。

使用代碼示例

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:custom="http://schemas.android.com/apk/res/com.example.customviews">                    <com.example.customviews.charting.PieChart 
              custom:showText="true" 
              custom:labelPosition="left" />
</LinearLayout>

使用自定義屬性的時候需要指定命名空間,固定寫法就是http://schemas.android.com/apk/res/你的包名。如果你是在Android Studio,也可以用http://schemas.android.com/apk/res/res-auto

獲取自定義屬性在xml中設置了控件自定義屬性,我們就需要拿到屬性做一些事情。否則定義自定義屬性就沒有意義了。固定的獲取自定義屬性代碼如下

public PieChart(Context context, AttributeSet attrs) { super(context, attrs){
         TypedArray a = context.getTheme().obtainStyledAttributes( attrs, R.styleable.PieChart, 0, 0); 
          try { 
                   mShowText = a.getBoolean(R.styleable.PieChart_showText, false); 
                   mTextPos = a.getInteger(R.styleable.PieChart_labelPosition, 0); 
                 }finally {
                   a.recycle(); 
                 }
 }

當我們在 xml中創建了一個view時,所有在xml中聲明的屬性都會被傳入到view的構造方法中的AttributeSet類型的參數當中。 通過調用Context的obtainStyledAttributes()方法返回一個TypedArray對象。然后直接用TypedArray對象獲取自定義屬性的值。由于TypedArray對象是共享的資源,所以在獲取完值之后必須要調用recycle()方法來回收。

添加設置屬性事件

在xml中指定的自定義屬性只有在view被初始化的時候能夠獲取到,有時候我們可能在運行時做一些操作,這種情況就需要我們為自定義屬性設置getter和setter方法,以下代碼展示了自定義控件暴露的set 和get方法

public boolean isShowText() { 
    return mShowText;
}

public void setShowText(boolean showText) { 
    mShowText = showText; 
    invalidate(); 
    requestLayout();
}

重點看setShowText方法,在為mShowText賦值之后,調用了invalidate()和requestLayout()方法,我們自定義控件的屬性發生改變之后,控件的樣子也可能發生改變,在這種情況下就需要調用invalidate()方法讓系統去調用view的onDraw()重新繪制。同樣的,控件屬性的改變可能導致控件所占的大小和形狀發生改變,所以我們需要調用requestLayout()來請求測量獲取一個新的布局位置。

2、處理View的布局.

測量

一個View是在展示時總是有它的寬和高,測量View就是為了能夠讓自定義的控件能夠根據各種不同的情況以合適的寬高去展示。提到測量就必須要提到onMeasure方法了。onMeasure方法是一個view確定它的寬高的地方。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    }

onMeasure方法里有兩個重要的參數, widthMeasureSpec, heightMeasureSpec。
在這里你只需要記住它們包含了兩個信息:mode和size

我們可以通過以下代碼拿到mode和size

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

那么獲取到的mode和size又代表了什么呢?

mode代表了我們當前控件的父控件告訴我們控件,你應該按怎樣的方式來布局。
mode有三個可選值:EXACTLY, AT_MOST, UNSPECIFIED。

它們的含義是:

EXACTLY:父控件告訴我們子控件了一個確定的大小,你就按這個大小來布局。比如我們指定了確定的dp值和macth_parent的情況。

AT_MOST:當前控件不能超過一個固定的最大值,一般是wrap_content的情況。

UNSPECIFIED:當前控件沒有限制,要多大就有多大,這種情況很少出現。size其實就是父布局傳遞過來的一個大小,父布局希望當前布局的大小。

下面是一個重寫onMeasure的固定偽代碼寫法:


if mode is EXACTLY{ 
         父布局已經告訴了我們當前布局應該是多大的寬高, 所以我們直接返回從measureSpec中獲取到的size 
}else{ 
        計算出希望的desiredSize 
         if mode is AT_MOST
                返回desireSize和specSize當中的最小值 
         else: 
                返回計算出的desireSize}

上面的代碼雖然基本都是固定的,但是需要寫的步驟還是有點多,如果你不想自己寫,你也可以用android為我們提供的工具方法:resolveSizeAndState,
該方法需要傳入兩個參數:我們測量的大小和父布局希望的大小,它會返回根據各種情況返回正確的大小。這樣我們就可以不需要實現上面的模版,只需要計算出想要的大小然后調用resolveSizeAndState。之后在做自定義View的時候我會展示用這個方法來確定view的大小。
計算出height和width之后在onMeasure中別忘記調用setMeasuredDimension()方法。否則會出現運行時異常。

計算一些自定義控件需要的值 onSizeChange()

onSizeChange() 方法在view第一次被指定了大小值、或者view的大小發生改變時會被調用。所以一般用來計算一些位置和與view的size有關的值。

3、繪制View(Draw)

一旦自定義控件被創建并且測量代碼寫好之后,接下來你就可以實現onDraw()來繪制View了,onDraw方法包含了一個Canvas叫做畫布的參數,onDraw()簡單來說就兩點:
Canvas決定要去畫什么
Paint決定怎么畫

比如,Canvas提供了畫線方法,Paint就來決定線的顏色。Canvas提供了畫矩形,Paint又可以決定讓矩形是空心還是實心。

在onDraw方法中開始繪制之前,你應該讓畫筆Paint對象的信息初始化完畢。這是因為View的重新繪制是比較頻繁的,這就可能多次調用onDraw,所以初始化的代碼不應該放在onDraw方法里。

Canvas和Paint提供的很多方法在本文中就不一一列舉了。大家可以自己去查看api,之后的文章中我們也會用到,現在你只需要理解定義的大體步驟,然后再慢慢鍛煉加深理解。

4、與用戶進行交互

也許某些情況你的自定義控件不僅僅只是展示一個漂亮的內容,還需要支持用戶點擊,拖動等等操作,這時候我們的自定義控件就需要做用戶交互這一步驟了。

在android系統中最常見的事件就是觸摸事件了,它會調用view的onTouchEvent(android.view.MotionEvent).重寫這個方法去處理我們的事件邏輯

  @Override 
  public boolean onTouchEvent(MotionEvent event) {
       return super.onTouchEvent(event);
  }

對與onTouchEvent方法相信大家都有一定了解,如果不了解的話,你就先記住這是處理Touch的地方。

現在的觸控有了更多的手勢,比如輕點,快速滑動等等,所以在支持特殊用戶交互的時候你需要用到android提供的GestureDetector.你只需要實現GestureDetector中相對應的接口,并且處理相應的回調方法。

除了手勢之外,如果有移動之類的情況我們還需要讓滑動的動畫顯示得比較平滑。動畫應該是平滑的開始和結束,而不是突然消失突然開始。在這種情況下,我們需要用到屬性動畫 property animation framework

由于與用戶進行交互中涉及到的知識舉例子會比較多,所以我在之后的自定義控件文章中再講解。

5、優化你的自定義View

在上面的步驟結束之后,其實一個完善的自定義控件已經出來了。接下來你要做的只是確保自定義控件運行得流暢,官方的說法是:為了避免你的控件看得來遲緩,確保動畫始終保持每秒60幀.

下面是官網給出的優化建議:

1、避免不必要的代碼
2、在onDraw()方法中不應該有會導致垃圾回收的代碼。
3、盡可能少讓onDraw()方法調用,大多數onDraw()方法調用都是手動調用了invalidate()的結果,所以如果不是必須,不要調用invalidate()方法。

總結

到這里基本上自定義控件的大致步驟和可能涉及到的知識點都說完了。看一張圖。

圖片基本描述了自定義控件的大致流程,右邊是相對應的流程所涉及到的一些知識點。可以看到自定義控件包括了很多android知識。所以學習android自定義控件是很有必要的。當你能夠輕松的定義出一個完美的自定義控件的時候,相信你已經是大牛了。

本篇文章只對自定義控件的步驟進行了大體的介紹,如果你對自定義的流程還不清楚,請你記住上面所說的步驟,至少也要有個大致的印象。在之后的文章中,我在按照這個步驟去講解一些自定義控件,用這里列出的步驟去一步步實現我們想要的自定義控件。

喜歡我的文章,歡迎關注我!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容