探究Android中通過繼承ViewGroup自定義控件的原理


原文地址:http://www.cnblogs.com/kross/p/3378395.html

今天斷斷續續的折騰了一下午到現在20:38,終於有點明白了。o(╯□╰)o

在Android開發中,我們往往對系統提供的控件並不是很滿意,比如現在市面上很多應用的Tab都是一張圖加上文本控件的形式。加入我們的頁面上有5個tab,那么就有五個ImageView和五個TextView,看上去就有點惡心,從軟件工程的重用性上來說也是不符合要求的。

前段時間,我看了一本《Android UI 基礎教程》by:Jason Ostrander,里面提到一種<include>標簽的形式可以有效的介紹xml中的重復代碼,提高復用性。

但我使用<include>之后,xml中的代碼確實減少了很多,但是在Java代碼中,重復代碼還是很多,仍然需要findViewById,然后給每個控件設置屬性。

因為以上的情況,我認識到應該自己定義一個復合控件,設置屬性的方法也直接封裝起來,使用的時候就方便多了。人類果然是在進步啊O(∩_∩)O哈!

如何自定義控件呢?

本文講述的是通過繼承ViewGroup來自定義控件的,通過覆寫onDraw方法來自定義控件的高端方式在下還沒有接觸……

光說如何實現的話,是非常簡單的,就和你百度“Android 繼承LinearLayout”后看到的大多數文章一樣。

假設我們要自定義一個Tab控件,上面是一個ImageView,下面是一個TextView。

首先我們要寫一個XML文件。tab.xml(/res/layout/tab.xml)代碼如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <ImageView
        android:id="@+id/imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="default"/>
</LinearLayout>

然后你需要自己定義一個類,繼承LinearLayout,當然你繼承其它的ViewGroup也行。代碼如下Tab.java(/src/view/Tab.java):

public class Tab extends LinearLayout {

    public Tab(Context context, AttributeSet attrs) {
        super(context, attrs);

        LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Service.LAYOUT_INFLATER_SERVICE);
        
        View v = layoutInflater.inflate(R.layout.tab, this, true);
    }
}

最后一步,是需要能直接使用我們寫好的復合控件嘛,那么在需要的xml里加上<view.Tab/>標簽就好了~(注意標簽的名字是和自己定義的控件類的完整類名是一樣的

比如這樣:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >
    <View
      class="view.Tab"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
    <view.Tab
        android:id="@+id/tab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

上面兩種方式都是可以的。

使用起來就是比較簡單的了。但是,當我學會如何使用的時候就一直有一個疑惑,我自定義一個類繼承LinearLayout,覆寫一下構造函數,為什么它就可以把剛剛那個xml文件中的兩個控件用上呢?它到底是什么樣的原理。

這個不像Fragment那么好理解的樣子,在學Fragment的時候,Fragment如果有視圖的話,只需要覆寫onCreateView方法返回一個View就好了,這個View就是Fragment的視圖,非常的好理解。

探究的過程我就不細說了,我是通過給每個控件設置一個id,然后獲取子控件,獲取子控件的數量,把這些信息輸出到Logcat來研究它的原理的。這里就說下我研究的一些結果與規律吧。

通過繼承ViewGroup來自定義控件也有兩種方式,一種是通過XML來加載控件,這種方式比較好。另一種則是通過純Java代碼來添加控件,這種方式就和Java的GUI沒什么區別了。但兩者是有一定的區別的。

通過純Java代碼來加載控件是比較好理解的。看下面的代碼。

public class Tab extends LinearLayout {

    public Tab(Context context, AttributeSet attrs) {
        super(context, attrs);
        
        int count1 = getChildCount();//count1 -> 0
        
        TextView tv = new TextView(context);
        tv.setText("aaaaa");
        addView(tv);
        
        int count2 = getChildCount();//count2 -> 1
    }
}

通過Java代碼來操作的話比較好理解,上面這個類是繼承與LinearLayout的,LinearLayout是一個ViewGroup,然后往里面添加了一個TextView對象。這樣當你在xml中寫上<view.Tab/>的時候,編譯器會先把XML轉成二進制的形式變成對象,那么它自然會去new我們自定義的這個Tab類,並且執行它里面的構造函數,理所應當的給里面添加了一個TextView,這個和如下的XML代碼是木有區別的。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/box"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="aaaaa"/>
</LinearLayout>

我們用一個圖來表示剛剛實現的Tab類,它的結構層次。

那么通過加載XML文件的形式來自定義控件是怎么個實現方式呢。

它最關鍵的就在於調用inflate這個方法的時候。

LayoutInflater li = (LayoutInflater)context.getSystemService(Service.LAYOUT_INFLATER_SERVICE);

li.inflate(R.layout.tab, this, true);

第二行代碼的意思是:擴充tab.xml布局,並將它附着於本類上

也就是這行代碼,相當於給Tab類綁定了一個XML的視圖。當我了解到這行的作用后,我的困惑基本就解決了。

這行代碼執行完成后,Tab對象就已經具有自己的視圖的,當系統發現<view.Tab/>標簽的時候,便會去找到Tab類,去實例化它,並調用它的構造函數,當執行完這句話之后,tab.xml的布局便會附着於Tab類自身(也就是附着於LinearLayout)。假設我們有如下的tab.xml。(留意給控件加的id

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/box"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <ImageView
        android:id="@+id/tab_icon"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_launcher"/>
    <TextView
        android:id="@+id/tab_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="aaaaa"/>
</LinearLayout>

ok,如果我們使用<view.Tab/>后,所形成的視圖層次結構應該是這樣的。

很顯然,通過擴充XML的方式自定義的控件,比通過java代碼操作來自定義控件的方式多了一層ViewGroup。

還有一個需要注意的地方就是,inflate(R.layout.tab, this, true)第三個參數為true的時候,這個函數返回的是root,如果是false,則返回的xml的root。

就是說,當是true的時候,返回的是外層的LinarLayout,也就是Tab類本身,如果需要找到TextView,需要向下找兩層

當是false的時候,返回的是id為box的LinearLayout

OK,到這里為止,我對於通過繼承ViewGroup的方式來自定義控件的方式理解的比較好了。希望能幫到和我有相同困惑的少年。

轉載請注明出處:http://www.cnblogs.com/kross/p/3378395.html
新浪微博:http://weibo.com/KrossFord


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM