原文地址: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