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