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