zoukankan      html  css  js  c++  java
  • 探究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

  • 相关阅读:
    final,finally和finalize三者的区别和联系
    Java程序开发中的简单内存分析
    Java堆、栈和常量池以及相关String的详细讲解(经典中的经典)
    java中的基本数据类型一定存储在栈中吗?
    CDN加速-内容分发网络
    大型Java web项目分布式架构演进-分布式部署
    阿里面试-2019
    jvm调优-从eclipse开始
    eclipse中导入maven项目:org.apache.maven.archiver.MavenArchiver.getManifest(org.apache.maven.project.Maven
    图解 CMS 垃圾回收机制原理,-阿里面试题
  • 原文地址:https://www.cnblogs.com/kross/p/3378395.html
Copyright © 2011-2022 走看看