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

posted @ 2013-10-19 21:43  krosshuang  阅读(2963)  评论(3编辑  收藏  举报