Android自定义view教程05--自定义view的基础知识之LayoutInflater详解

 

前面的教程我们讲了一下android的动画 的使用,尤其是着重讲解了属性动画的使用。那么这章开始我们将会讲一下android 自定义view的一些基础知识。帮助大家理解。

首先我们来关注一下LayoutInflater这个类,经常使用开源控件的人对这个类应该很熟悉了,但是很少有人能把这个类讲明白 用清楚,今天我们就来深挖一下这个类。

首先我们定义一个button.xml 和button2.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <!--
 3 这个地方你无论怎么更改 layout_width layout_height 对显示出来的效果都是没有影响的
 4 原因是view 必须存在于一个布局之中 这2个属性才有用 不然这2个属性设置是无效的
 5 可以看看button2.xml的布局 在那个里面修改这2个属性 就能起作用 而在这里更改是无效的
 6 
 7 -->
 8 <Button xmlns:android="http://schemas.android.com/apk/res/android"
 9     android:layout_width="match_parent"
10     android:layout_height="match_parent"
11     android:text="button" >
12 
13 </Button>

 

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <Button
 8         android:layout_width="wrap_content"
 9         android:layout_height="wrap_content"
10         android:text="button" >
11     </Button>
12 
13 </LinearLayout>

然后定义一下MainActivity

 1 package com.example.viewtest1;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.widget.LinearLayout;
 8 
 9 public class MainActivity extends Activity {
10 
11     private LinearLayout layout;
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.activity_main);
17         layout = (LinearLayout) this.findViewById(R.id.layout);
18         LayoutInflater inflater = LayoutInflater.from(this);
19         // 这个地方可以试一下 加载button2的布局文件 然后也更改一下layout
20         // _width 以及height属性 看看有什么不一样的结果
21         // 同时要注意inflate这个方法与findviewByid的区别 前者是加载一个布局文件
22         // 而后者则是从布局文件里面查找一个控件
23         // 获取inflater有三种方法 另外两种是LayoutInflater inflater= getLayoutInflater()
24         // 和LayoutInflater
25         // inflater=(LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);
26         View buttonLayout = inflater.inflate(R.layout.button, null);
27         layout.addView(buttonLayout);
28     }
29 
30 }

最后看一下activity_main,

1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2     xmlns:tools="http://schemas.android.com/tools"
3     android:id="@+id/layout"
4     android:layout_width="match_parent"
5     android:layout_height="match_parent"
6     android:orientation="vertical" >
7 
8 </LinearLayout>

跑一下这个demo  稍微修改一下 button和button2的xml 就知道注释里面写的是什么意思了,到这里 肯定很多人要看inflate函数的分析,我们跟踪源码 会发现最终其实调用的 都是

 1 /**
 2      * Inflate a new view hierarchy from the specified XML node. Throws
 3      * {@link InflateException} if there is an error.
 4      * <p>
 5      * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
 6      * reasons, view inflation relies heavily on pre-processing of XML files
 7      * that is done at build time. Therefore, it is not currently possible to
 8      * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
 9      * 
10      * @param parser XML dom node containing the description of the view
11      *        hierarchy.
12      * @param root Optional view to be the parent of the generated hierarchy (if
13      *        <em>attachToRoot</em> is true), or else simply an object that
14      *        provides a set of LayoutParams values for root of the returned
15      *        hierarchy (if <em>attachToRoot</em> is false.)
16      * @param attachToRoot Whether the inflated hierarchy should be attached to
17      *        the root parameter? If false, root is only used to create the
18      *        correct subclass of LayoutParams for the root view in the XML.
19      * @return The root View of the inflated hierarchy. If root was supplied and
20      *         attachToRoot is true, this is root; otherwise it is the root of
21      *         the inflated XML file.
22      */
23     public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {

这个函数,我们可以再写一个demo 来看看这个函数到底有什么用 三个参数分别有什么意义。搞明白这一点我们再去看源码。

 

 定义一个activity

  1 package com.example.viewtest2;
  2 
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 import java.util.Random;
  6 
  7 import android.app.Activity;
  8 import android.content.Context;
  9 import android.os.Bundle;
 10 import android.view.LayoutInflater;
 11 import android.view.View;
 12 import android.view.ViewGroup;
 13 import android.widget.BaseAdapter;
 14 import android.widget.Button;
 15 import android.widget.ListView;
 16 
 17 public class MainActivity extends Activity {
 18 
 19     private ListView lv;
 20 
 21     private BaseAdapter adapter;
 22 
 23     private List<String> data = new ArrayList<String>();
 24 
 25     @Override
 26     protected void onCreate(Bundle savedInstanceState) {
 27         super.onCreate(savedInstanceState);
 28         setContentView(R.layout.activity_main);
 29         lv = (ListView) this.findViewById(R.id.lv);
 30         for (int i = 0; i < 10; i++) {
 31             data.add(getRandomString(5));
 32         }
 33         adapter = new ModifyAdapter(this, data);
 34         lv.setAdapter(adapter);
 35 
 36     }
 37 
 38     class ModifyAdapter extends BaseAdapter {
 39 
 40         private LayoutInflater mLayoutInflater;
 41         private List<String> data;
 42 
 43         public ModifyAdapter(Context context, List<String> datas) {
 44             mLayoutInflater = LayoutInflater.from(context);
 45             data = datas;
 46         }
 47 
 48         @Override
 49         public int getCount() {
 50             // TODO Auto-generated method stub
 51             return data.size();
 52         }
 53 
 54         @Override
 55         public Object getItem(int position) {
 56             // TODO Auto-generated method stub
 57             return data.get(position);
 58         }
 59 
 60         @Override
 61         public long getItemId(int position) {
 62             // TODO Auto-generated method stub
 63             return position;
 64         }
 65 
 66         @Override
 67         public View getView(int position, View convertView, ViewGroup parent) {
 68             ViewHolder holder = null;
 69             if (convertView == null) {
 70                 holder = new ViewHolder();
 71                 // 可以执行下面三种写法 看看代码实际效果
 72                 // 下面这个效果 会发现布局文件里设置的高度和宽度是无效的
 73                 convertView = mLayoutInflater.inflate(R.layout.item, null);
 74                 // 下面这个效果 就可以发现高度和宽度的设置是有效的了
 75                 // convertView = mLayoutInflater.inflate(R.layout.item, parent,
 76                 // false);
 77                 // 下面这个是会报异常的:java.lang.UnsupportedOperationException:
 78                 // addView(View, LayoutParams) is not supported in AdapterView
 79 
 80                 // convertView = mLayoutInflater.inflate(R.layout.item, parent,
 81                 // true);
 82                 holder.bt = (Button) convertView.findViewById(R.id.bt);
 83                 convertView.setTag(holder);
 84             } else {
 85                 holder = (ViewHolder) convertView.getTag();
 86             }
 87 
 88             holder.bt.setText(data.get(position));
 89 
 90             return convertView;
 91         }
 92 
 93         class ViewHolder {
 94             Button bt;
 95         }
 96 
 97     }
 98 
 99     public static String getRandomString(int length) { // length表示生成字符串的长度
100         String base = "abcdefghijklmnopqrstuvwxyz0123456789";
101         Random random = new Random();
102         StringBuffer sb = new StringBuffer();
103         for (int i = 0; i < length; i++) {
104             int number = random.nextInt(base.length());
105             sb.append(base.charAt(number));
106         }
107         return sb.toString();
108     }
109 
110 }

 

 把item的布局文件代码上一下

1 <Button xmlns:android="http://schemas.android.com/apk/res/android"
2     android:id="@+id/bt"
3     android:layout_width="100dp"
4     android:layout_height="100dp" >
5 
6 </Button>

 

 大家可以跑一下这个demo 看看getview里的注释的效果。

到这里 我们似乎就能发现 一个规律

inflate(res,null) 设置的长宽不启作用----但是在这种情况下 如果我们修改一下item的布局文件 最外层随便包一个viewgroup就是可以启作用的 大家可以想一下是为什么?可以看看我们第一个demo就很容易明白了

inflate(res,parent,true)会报错

inflate(res,parent,false) 就能正确的显示长宽高。

此时 我们就可以来分析一下 源代码 为什么这三种调用方法 会是这样的结果

 

  1 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  2         synchronized (mConstructorArgs) {
  3             final AttributeSet attrs = Xml.asAttributeSet(parser);
  4             Context lastContext = (Context)mConstructorArgs[0];
  5             mConstructorArgs[0] = mContext;
  6             View result = root;
  7 
  8             try {
  9                 // Look for the root node.
 10                 int type;
 11                 while ((type = parser.next()) != XmlPullParser.START_TAG &&
 12                         type != XmlPullParser.END_DOCUMENT) {
 13                     // Empty
 14                 }
 15 
 16                 if (type != XmlPullParser.START_TAG) {
 17                     throw new InflateException(parser.getPositionDescription()
 18                             + ": No start tag found!");
 19                 }
 20 
 21                 final String name = parser.getName();
 22                 
 23                 if (DEBUG) {
 24                     System.out.println("**************************");
 25                     System.out.println("Creating root view: "
 26                             + name);
 27                     System.out.println("**************************");
 28                 }
 29 
 30                 if (TAG_MERGE.equals(name)) {
 31                     if (root == null || !attachToRoot) {
 32                         throw new InflateException("<merge /> can be used only with a valid "
 33                                 + "ViewGroup root and attachToRoot=true");
 34                     }
 35 
 36                     rInflate(parser, root, attrs, false);
 37                 } else {
 38                     // Temp is the root view that was found in the xml
 39                     View temp;
 40                     if (TAG_1995.equals(name)) {
 41                         temp = new BlinkLayout(mContext, attrs);
 42                     } else {
 43                         temp = createViewFromTag(root, name, attrs);
 44                     }
 45 
 46                     ViewGroup.LayoutParams params = null;
 47 
 48                     if (root != null) {
 49                         if (DEBUG) {
 50                             System.out.println("Creating params from root: " +
 51                                     root);
 52                         }
 53                         // Create layout params that match root, if supplied
 54                         params = root.generateLayoutParams(attrs);
 55                         if (!attachToRoot) {
 56                             // Set the layout params for temp if we are not
 57                             // attaching. (If we are, we use addView, below)
 58                             temp.setLayoutParams(params);
 59                         }
 60                     }
 61 
 62                     if (DEBUG) {
 63                         System.out.println("-----> start inflating children");
 64                     }
 65                     // Inflate all children under temp
 66                     rInflate(parser, temp, attrs, true);
 67                     if (DEBUG) {
 68                         System.out.println("-----> done inflating children");
 69                     }
 70 
 71                     // We are supposed to attach all the views we found (int temp)
 72                     // to root. Do that now.
 73                     if (root != null && attachToRoot) {
 74                         root.addView(temp, params);
 75                     }
 76 
 77                     // Decide whether to return the root that was passed in or the
 78                     // top view found in xml.
 79                     if (root == null || !attachToRoot) {
 80                         result = temp;
 81                     }
 82                 }
 83 
 84             } catch (XmlPullParserException e) {
 85                 InflateException ex = new InflateException(e.getMessage());
 86                 ex.initCause(e);
 87                 throw ex;
 88             } catch (IOException e) {
 89                 InflateException ex = new InflateException(
 90                         parser.getPositionDescription()
 91                         + ": " + e.getMessage());
 92                 ex.initCause(e);
 93                 throw ex;
 94             } finally {
 95                 // Don‘t retain static reference on context.
 96                 mConstructorArgs[0] = lastContext;
 97                 mConstructorArgs[1] = null;
 98             }
 99 
100             return result;
101         }
102     }

 

首先看一下第六行 定义了一个view 名称为result  这个result的初始值 就是我们传进去的parent ,而这个函数值的返回也恰好就是这个result

然后看看77-81行。 这段代码的意思就是说 当传进去的parent 为空 或者第三个参数值为false的时候 result的值 就被设置成了temp的值。

除此之外 result的值就是我们传进去的parent的值。

我们再来看看temp是什么。

 

42-44行 就是告诉你 创建了一个名为temp的view

 

48-60行  告诉我们 当我们传进去的parent的值 不为空 并且第三个参数值为false的时候 我们就会设置这个temp的的layoutparams了。66行就是遍历所有子元素

然后看下 71-74行  如果传进去的parent的值不为空 并且第三个参数值为true的时候 就会调用parent的addview 方法

而在我们这个例子里,我们的parent 是listview  要知道 listview 是继承自 abstract class AdapterView<T extends Adapter> extends ViewGroup 这个类

我们去看看这个类的addview方法的源码

 

1 @Override
2     public void addView(View child) {
3         throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
4     }

 

到这 我们就算印证了demo里的结果了,如果有人到这里还是不明白 可以回头再看一遍源码 我来总结一下

inflate(res,null) 最终返回的是temp的值 大家都知道layout_weight width这2个属性是相对父级 也就是viewgroup来设置的、

你传进去的parent为null的话 ,58行代码是执行不了的,当然你设置的属性就没用了

inflate(resId , parent, false ) 最终返回的也是temp的值,但是这种情况再返回之前,他会执行temp.setLayoutParams(params) 这个params

看下54行代码就知道了就是取的父控件的值。 所以这种情况下 我们设置的长宽就是有效的。

第三种报错的情况 刚才已经分析过了,当第三个参数为true的时候 就会调用addview了

 

最后我们再来看一个例子 来加深一下 对这个函数的理解 

 1 package com.example.viewtest2;
 2 
 3 import android.app.Activity;
 4 import android.os.Bundle;
 5 import android.util.Log;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 
10 public class MainActivity extends Activity {
11 
12     private LayoutInflater inflater;
13 
14     @Override
15     protected void onCreate(Bundle savedInstanceState) {
16         super.onCreate(savedInstanceState);
17 
18         // 注意看一下我们的代码 这里可是没有setcontentview的
19         inflater = LayoutInflater.from(this);
20 
21         // 这个地方日志打出来就会发现view的layoutparams 就是空 可以和源码互相验证一下
22 
23         View view1 = inflater.inflate(R.layout.activity_main, null);
24 
25         // 这个地方打印view2 就能发现了 打印的是activity的内容区域 farmelayout的
26         // layoutparams
27         View view2 = inflater.inflate(R.layout.activity_main,
28                 (ViewGroup) findViewById(android.R.id.content), false);
29 
30         // 如果你把view3 這邊的代碼注释掉的话 你就会发现 执行这个代码 界面上是没有显示的
31         // 因为只有第三种情况 也就是parent不为空 并且第三个参数为true的时候 才会去调用
32         // 父控件的addview方法 这里我们的父控件就是activity的内容区域 那个大名鼎鼎的
33         // framelayout了
34         View view3 = inflater.inflate(R.layout.activity_main,
35                 (ViewGroup) findViewById(android.R.id.content), true);
36 
37         Log.v("ccc",
38                 "view1 = " + view1 + " , view1.layoutParams = "
39                         + view1.getLayoutParams());
40         Log.v("ccc",
41                 "view2 = " + view2 + " , view2.layoutParams = "
42                         + view2.getLayoutParams());
43         Log.v("ccc", "view3 = " + view3);
44 
45     }
46 
47 }

把布局文件上一下

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:paddingBottom="@dimen/activity_vertical_margin"
 6     android:paddingLeft="@dimen/activity_horizontal_margin"
 7     android:paddingRight="@dimen/activity_horizontal_margin"
 8     android:paddingTop="@dimen/activity_vertical_margin"
 9     tools:context="com.example.viewtest2.MainActivity" >
10 
11     <Button
12         android:id="@+id/lv"
13         android:layout_width="100dp"
14         android:layout_height="100dp"
15         android:text="test button" >
16     </Button>
17 
18 </RelativeLayout>

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。