前言:上周末去看房,CAO,累的都快残废了,也没有满意的,害的博客也没写好……,不买了……
相关文章:
1、《 ListView滑动删除实现之一——merge标签与LayoutInflater.inflate()》
2、《ListView滑动删除实现之二——scrollTo、scrollBy详解》
3、《 ListView滑动删除实现之三——创建可滑动删除的ListView》
4、《ListView滑动删除实现之四——Scroller类与listview缓慢滑动》
从今天开始带着大家做一个滑动删除的listView控件,先拿效果来吸引下大家:
看着是不是挺好玩的,万丈高楼平地起,今天先讲讲有关merge与LayoutInflater.inflate()的用法
merge标签就是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。核心功能就是减少冗余的层次从而达到优化UI的目的!
看不懂?那看这里吧;merge标签,单从英文来看,意思为“合并”,那它用来合并什么呢?下面我们用例子来说明它可以合并什么。
将通过一个例子来了解这个标签实际所产生的作用,这样可以更直观的了解< merge/>的用法。
1、首先,我们新建一个工程,内容非常简单,界面如下:
当然它的布局代码如下:activity_main.xml
<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="com.example.blogmerge.MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher"/> </LinearLayout>代码非常简单,一个垂直布局下面有一个TextView控件和一个ImageView控件,效果图就是上面的那个
有关HierarchyViewer工具的使用,请参考:
1、《Android UI 优化——使用HierarchyViewer工具》
2、《Android UI 调试工具 Hierarchy Viewer》
请注意,HierarchyViewer在调试真机时,一般是会不成功的,因为只有在开发版的 Android 设备上才有这样的功能。
这时候的控件的层次关系是这样的:
最上层的FrameLayout和LinearLayout都是系统自带的布局,右下方的三个蓝色的控件布局是我们activity_main.xml里的控件布局。
从这里可以看到在我们的LinearLayout之上,系统已经给了一层FrameLayout布局,所以我们这里的LinearLayout控件是完全可以删除的,那要怎么删除呢,这里就是merge标签的作用了,我们尝试把我们布局中的LinearLayout标签换成merge标签,再看一下层次结构图;
2、先看看下面activity_main.xml中,根标签换成merge后的代码:
<merge 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" tools:context="com.example.blogmerge.MainActivity" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> </merge>运行出来之后,可以看出与上面的垂直布局已经不一样的,textView与ImageView叠加在了一起,效果是这样的:
然后再看看控件层次图:
从控件层次图中可以明显看出:TextView控件与ImageView控件直接连在了上层的FrameLayout控件上!所以这就少了一个层次,由于直接连在了FrameLayout控件上了,所以这两个控件的布局就是使用的FrameLayout帧布局,这也就是为什么效果图中TextView控件与ImageView控件叠加在一起的原因!
可见:merge标签能够将该标签中的所有控件直接连在上一级布局上面,从而减少布局层级
merge标签有下面两个使用限制:
至于第二点,为什么attachToRoot必须设为TRUE,看完下一部分就自然懂了。
源码在文章底部给出
LayoutInflater.inflate()函数的声明方式如下:
public View inflate(int resource, ViewGroup root, boolean attachToRoot)
<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" tools:context=".MainActivity" android:orientation="vertical" android:id="@+id/root"> <TextView android:text="@string/hello_world" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ff00ff"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="30dp" android:gravity="center" android:text="xxxxx附加的" /> </LinearLayout>
看下添加代码:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LayoutInflater layoutInflater = LayoutInflater.from(this); LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root); layoutInflater.inflate(R.layout.add_layout,linearLayout,true); } }在这段代码中,首先通过LayoutInflater.from(Context context);来获取LayoutInflater的实例。
LayoutInflater layoutInflater = LayoutInflater.from(this);然后,由于我们要把转换的视图添加到activity_main.xml的linearLayout布局下面,我将它的ID命为root,所以它就是LayoutInflater.inflate()所需要的根结点
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);最后,调用LayoutInflater.inflate()来转换布局,由于我们这里attachToRoot设为了True,所以,在add_layout.xml转换成View布局后,会调用root.addView()来将其添加到根结点视图的下面。所以在效果图显示时,我们是可以看得到add_layout.xml的效果的。
layoutInflater.inflate(R.layout.add_layout,linearLayout,true);效果图如下:
我们来看看如果把attachToRoot设为FALSE,效果图是怎样的,首先代码如下:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LayoutInflater layoutInflater = LayoutInflater.from(this); LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root); layoutInflater.inflate(R.layout.add_layout,linearLayout,false); } }大家可以看到,在代码上除了最后一句layoutInflater.inflate(R.layout.add_layout,linearLayout,false);中把true改成了false,其它没有任何变化
可以看到,我们的主布局没有任何变化,也就是说add_layout.xml的布局没有被添加到activity_mian.xml中;
我们开头就讲过,如果attachToRoot设为false,那转换后的布局是不会被添加到root中的,会作为结果返回。
其实attachToRoot设为TRUE的代码与下面的代码是等价的:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LayoutInflater layoutInflater = LayoutInflater.from(this); LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root); View view = layoutInflater.inflate(R.layout.add_layout,linearLayout,false); linearLayout.addView(view); } }即将 layoutInflater.inflate(R.layout.add_layout,linearLayout,false);返回的VIEW通过addView(view);手动添加到根结点!
我们前面也讲过,当attachToRoot设为TRUE时,其内部会调用root.addView(view)来将布局添加到root中;
源码在文章底部给出
在大家看到上面的现象以后,我们透过源码的角度来分析,attachToRoot这个参数都做了些什么,root又用来做了些什么。
先看LayoutInflate.inflate()的实现:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) { XmlResourceParser parser = getContext().getResources().getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }首先,通过我们传进去的布局,透过XML分析器获取parser:
XmlResourceParser parser = getContext().getResources().getLayout(resource);然后调用了inflate(parser, root, attachToRoot)函数,下面看看这个函数的实现:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { //第一步:初始化 final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; //注意这里,在初始化时,result表示要返回的视图,默认是返回root View result = root; ………… final String name = parser.getName(); //第二步:创建XML对应的空白VIEW:temp if (TAG_MERGE.equals(name)) { //如果是merge标签:抛出异常 ………… } else { View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); } //第三步:从根结点中,获取布局参数,设置到temp中 ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } //第四步:初始化temp中的子控件 rInflate(parser, temp, attrs, true); //第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中 if (root != null && attachToRoot) { root.addView(temp, params); } //第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回 if (root == null || !attachToRoot) { result = temp; } } return result; } }从上面注释大家应该也可以看出来功能所在,还是再带着大家重复一遍整个流程吧
final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; //注意这里,在初始化时,result表示要返回的视图,默认是返回root View result = root;通过XML文件获取布局中各个控件的属性集:AttributeSet,注意:这里通过XML只能知道布局里每个控件的布局参数。那整个LAYOUT的布局参数呢,虽然我们在XML的根结点的布局可能写的是layout_width:fill_parent,layout_height:fill_parent。但这只是很笼统的,系统要确实计算出它的宽高数来。这个宽高数的计算就是通过root中的属性来得出来的,下面代码中会提到。
View result = root;result表示最后返回的视图VIEW,所以这表示在默认情况下,返回root的视图!!注意这只是在默认情况下,下面会对result进行赋值的!
//第二步:创建XML对应的空白VIEW:temp if (TAG_MERGE.equals(name)) { //如果是merge标签,抛出异常 ………… } else { View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); }在这里首先判断XML的根结点是不是merge标签,大家知道我们的merge标签的主要作用是用来将merge标签下的所有控件合并到其上层布局上,也就是说merge标签下是没有根控件的!因为merge标签下的控件都是并列关系,所以如果merge标签使用的inflate函数,那我们根本没有办法给它返回根视图,所以肯定是要抛出异常的
//第三步:从根结点中,获取布局参数,设置到temp中 ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } }在这里看到,首先获取到了ROOT的布局参数赋值为params,然后当attachToRoot为FALSE时,将参数设置到temp中;
//第四步:初始化temp中的子控件 rInflate(parser, temp, attrs, true);rInflate()其实是一个递归函数,用来递归建立temp下的所有控件的视图
if (root != null && attachToRoot) { root.addView(temp, params); }在这里,就是当root不为空、attachToRoot为TRUE时,将构建好的temp视图添加到root中,注意这里的参数仍然从root中获取的布局参数params!!!所以,无论attachToRoot值为如何,temp的布局参数都是从ROOT里获取的!!!!
if (root == null || !attachToRoot) { result = temp; }从这里可以看到,如果root为空,获取attachToRoot为FALSE,就会将temp做为结果返回!
源码内容:
1、《BlogInflate》:对应第二部分:LayoutInflater.inflate()中的AttachToRoot参数
2、《BlogMerge》:对应第一部分:merge标签
如果本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/8618011
请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/45155965 谢谢