【UI布局优化】Android布局优化的几种方式
来源:互联网 发布:吉尼佛摄影吧淘宝 编辑:程序博客网 时间:2024/05/10 21:37
在Android中,布局优化越来越受到重视,下面将介绍布局优化的几种方式,这几种方式一般可能都见过,因为现在用的还比较多,我们主要从两个方面来进行介绍,一方面是用法,另一方面是从源码来分析,为什么它能起到优化的效果。
一、几种方式的用法
1、布局重用<include />
这个标签的主要作用就是它能够重用布局文件,如果一些布局在许多布局文件中都需要被使用,我们就可以把它单独写在一个布局中,然后使用这个标签在需要使用它的地方把这个布局加进去,这样就达到了重用的目的,最典型的一个用法就是,如果我们自定义了一个TitleBar,这个TitleBar可能需要在每个Activity的布局文件中都使用到,这样我们就可以使用这个标签来实现,下面来举个例子。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width=”match_parent” android:layout_height=”match_parent” android:background="@color/app_bg" android:gravity="center_horizontal"> <include android:id="@+id/titlebar" layout="@layout/titlebar"/> <TextView android:layout_width=”match_parent” android:layout_height="wrap_content" android:text="@string/hello" android:padding="10dp" /> ... </LinearLayout>
上面就代表一个Activity的布局文件,我们自己写了一个titleBar布局,直接使用inclue标签的layout来指定就可以把这个titleBar的布局文件加入进去,这样在每个Activity中我们就可以使用include标签来重用这个titleBar布局了,不需要在每个里面都重复写一个titleBar的布局了,下面我们来看看这个titleBar的布局文件。
<?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="65dp" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="首页"/></LinearLayout>
上面只是我们简单的写了一个titleBar的布局文件,我们可以根据需要自己来写一个。
在代码中,如果我们希望得到这个titlebar的View,我们只需要跟其他控件一样,使用findViewById来得到这个titleBar布局的View并且可以对其进行相应的操作。
总结一点:这个标签主要是做到布局的重用,使用这个标签可以把公共布局嵌入到所需要嵌入的地方。
2、减少视图层级<merge />
这个标签的作用就是删减多余的层级,优化UI,具体什么意思呢?还是来是例子来说明,下面我们来看一个布局文件。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /></FrameLayout>
这个布局文件比较简单,就是一个FrameLayout里面放了一个ImageView和一个TextView。下面我们来使用HierarchyViewer来查看它的布局层次。
从这个布局层次,就可以看到我们的FrameLayout的父布局仍然是一个FrameLayout,其实它们是重复的,我们其实不需要使用一个FrameLayout,而是直接将我们的内容挂载上层的那个FrameLayout下面就可以,这样怎么做呢?使用merge标签就可以了,我们使用merge就代表merge里面的内容的父布局就是merge这个标签的父布局,这样就重用了父布局。
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="center" android:src="@drawable/golden_gate" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dip" android:layout_gravity="center_horizontal|bottom" android:padding="12dip" android:background="#AA000000" android:textColor="#ffffffff" android:text="Golden Gate" /></merge>
上面就是具体的代码,我们使用merge就表示我们的merge标签里面的ImageView和TextView的父布局就是merge的父布局FrameLayout,merge它不属于一个布局层次。下面我们再来看看整个布局层次。
从上图应该就一目了然了,总结一点:如果可以重用父布局,我们就可以使用merge,这样就减少了一个布局层次,这样可以加快UI的解析速度。
3、延迟加载<ViewStub />
<ViewStub />
标签最大的优点是当你需要时才会加载,使用他并不会影响UI初始化时的性能,它 是一个不可见的,大小为0的View,最佳用途就是实现View的延迟加载,避免资源浪费,在需要的时候才加载View。
<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"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="内容1"/> <ViewStub android:id="@+id/pic_stub" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:inflatedId="@+id/pic_view_id_after_inflate" android:layout="@layout/pic_view" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="内容2"/> <Button android:text="加载ViewStub" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="startService"/></LinearLayout>
最开始使用setContentView(R.layout.activity_main)的时候,ViewStub只是起到一个占位符的作用,它并不会占用空间,所以对其他的布局没有影响。
当我们点击Button的时候,我们就可以把ViewStub的layout属性指定的布局加载进来,用它来替换ViewStub,这样就把我们需要加载的内容加载进来了。具体的使用方式有两种:
1、通过findViewById找到ViewStub,然后直接调用setVisibility,这样它就会把layout里面指定的布局添加进来。
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
2、通过findViewById找到ViewStub,然后直接调用inflate函数,使用这样方式的好处就是它可以将加载的布局View返回去,这样我们就可以拿到这个View进行相应的操作了。
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
我们需要主要的是,在加载之前,我们通过pic_stub这个id来找到ViewStub,在加载之后,如果我们再希望获取到加载进来的这个布局的View,我们需要使用inflatedId这个属性指定的id来获取,因为在加载了布局之后,原来ViewStub的id会被inflatedId指定的这个id覆盖。
二、源码分析上面三种方式的过程
我们一般通过LayoutInflater来加载一个布局文件,这对这个不太明白的可以看看这篇文章Android获取到inflate服务的方式及inflate的解析过程.
我们知道它是通过Pull解析器来解析布局文件的,它在解析一个布局文件的时候,最终会执行rInflate函数,在Android获取到inflate服务的方式及inflate的解析过程这篇文章具体讲解它的过程,我们主要来分析分析这个函数。
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else if (TAG_1995.equals(name)) { final View view = new BlinkLayout(mContext, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); viewGroup.addView(view, params); } else { final View view = createViewFromTag(parent, name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) parent.onFinishInflate();}
在解析标签的时候,它会根据不同的标签进行不同的处理,我们来看看它的过程。
1、如果这个标签为include标签
if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs);}
它会执行parseInclude函数,我们来看看它的处理。
private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs) throws XmlPullParserException, IOException { int type; // 1、判断父布局是否为一个ViewGroup实例 if (parent instanceof ViewGroup) { // 2、得到include标签中layout属性的值,它就是重用布局 final int layout = attrs.getAttributeResourceValue(null, "layout", 0); if (layout == 0) { final String value = attrs.getAttributeValue(null, "layout"); if (value == null) { throw new InflateException("You must specifiy a layout in the" + " include tag: <include layout=\"@layout/layoutID\" />"); } else { throw new InflateException("You must specifiy a valid layout " + "reference. The layout ID " + value + " is not valid."); } } else { // 3、解析重用布局文件 final XmlResourceParser childParser = getContext().getResources().getLayout(layout); try { final AttributeSet childAttrs = Xml.asAttributeSet(childParser); while ((type = childParser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty. } if (type != XmlPullParser.START_TAG) { throw new InflateException(childParser.getPositionDescription() + ": No start tag found!"); } final String childName = childParser.getName(); if (TAG_MERGE.equals(childName)) { // Inflate all children. rInflate(childParser, parent, childAttrs, false); } else { final View view = createViewFromTag(parent, childName, childAttrs); final ViewGroup group = (ViewGroup) parent; // We try to load the layout params set in the <include /> tag. If // they don't exist, we will rely on the layout params set in the // included XML file. // During a layoutparams generation, a runtime exception is thrown // if either layout_width or layout_height is missing. We catch // this exception and set localParams accordingly: true means we // successfully loaded layout params from the <include /> tag, // false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; try { params = group.generateLayoutParams(attrs); } catch (RuntimeException e) { params = group.generateLayoutParams(childAttrs); } finally { if (params != null) { view.setLayoutParams(params); } } // Inflate all children. rInflate(childParser, view, childAttrs, true); // Attempt to override the included layout's android:id with the // one set on the <include /> tag itself. TypedArray a = mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, 0, 0); int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID); // While we're at it, let's try to override android:visibility. int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1); a.recycle(); if (id != View.NO_ID) { view.setId(id); } switch (visibility) { case 0: view.setVisibility(View.VISIBLE); break; case 1: view.setVisibility(View.INVISIBLE); break; case 2: view.setVisibility(View.GONE); break; } // 4、把解析处理的View加入到父布局中 group.addView(view); } } finally { childParser.close(); } } } else { throw new InflateException("<include /> can only be used inside of a ViewGroup"); } final int currentDepth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) { // Empty }}
上面展示它的整个过程,它就是将layout指定的这个布局文件进行解析,然后加入父布局中.
2、如果这个标签为merge标签
if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element");}
它里面抛出了一个异常,对应merge的使用,我们要具体的根据场合而定,具体要看父布局是否能够被重用,并且它要为根布局。在上面include的标签的解析中可以看到merge标签的处理过程。
if (TAG_MERGE.equals(childName)) { // Inflate all children. rInflate(childParser, parent, childAttrs, false);}
从这里可以可以看到,如果是merge标签,就直接解析它的所有子元素,也就是说merge的父布局就是它内部子元素的父布局。
3、对于ViewStub,它会跟其他控件一样,实例化一个ViewStub对象
下面我来重点看看ViewStub类的setVisibility和inflate函数
首先我们需要看的是ViewStub的构造函数:
public ViewStub(Context context, AttributeSet attrs, int defStyle) { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub, defStyle, 0); //得到属性inflatedId的值 mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID); //得到属性layout的值 mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0); a.recycle(); a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, 0); mID = a.getResourceId(R.styleable.View_id, NO_ID); a.recycle(); initialize(context);}private void initialize(Context context) { mContext = context; // 从这里可以看到最开始这个控件的可见性为GONE setVisibility(GONE); // 这里先对它不进行绘制 setWillNotDraw(true);}
上面的工作就是两点:
1、获取各个属性的值
2、设置ViewStub的可见性为GONE,也就是它不占位置,并且也不绘制,因为它不是真正要显示的View
下面看看ViewStub的inflate函数:
public View inflate() { final ViewParent viewParent = getParent(); if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { factory = LayoutInflater.from(mContext); } // 这里直接解析mLayoutResource这个布局,也就是上面得到的layout属性值 final View view = factory.inflate(mLayoutResource, parent, false); // 这里会对这个布局设置id,也就是inflatedId的属性值 if (mInflatedId != NO_ID) { view.setId(mInflatedId); } // 这里从父布局中找到这个viewstub的index final int index = parent.indexOfChild(this); //这里将viewstub这个占位view移除 parent.removeViewInLayout(this); // 这里会把这个view添加到父布局指定的index中去,也就实现了对viewstub的替换 final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { parent.addView(view, index, layoutParams); } else { parent.addView(view, index); } //这里会把这个view弱引用到mInflatedViewRef mInflatedViewRef = new WeakReference<View>(view); // 如果设置了回调,就会调用回调函数 if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } else { throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); }}
上面的工作可以总结为以下几点:
1、解析出layout属性赋值的布局,得到对应的View
2、为这个View指定id
3、找到ViewStub在父布局的索引,然后将ViewStub移除
4、将上面解析的View加入到父布局的指定索引处
上面的整个过程总结一点就是:使用给定的布局来替换ViewStub,达到动态加载的目的,ViewStub仅仅只是一个占位View.
下面看看setVisibility函数的源码。
public void setVisibility(int visibility) { if (mInflatedViewRef != null) { View view = mInflatedViewRef.get(); if (view != null) { view.setVisibility(visibility); } else { throw new IllegalStateException("setVisibility called on un-referenced view"); } } else { super.setVisibility(visibility); if (visibility == VISIBLE || visibility == INVISIBLE) { inflate(); } }}
它的处理可以看到,首先看mInflatedViewRef是否为空,上面在inflate中,我们看到它会把解析处理的view弱引用到mInflatedViewRef,如果不为空,就可以直接得到这个View,然后设置它为可见。如果为空,这样就会执行inflate方法。就是上面的那个方法。
参考文章:
Android Layout Trick #2: Include to Reuse
Android Layout Tricks #3: Optimize by merging
Android Layout Tricks #3: Optimize with stubs
Android抽象布局——include、merge 、ViewStub
- 【UI布局优化】Android布局优化的几种方式
- Android优化布局的几种方式
- Android UI布局优化
- Android布局优化的几种方法
- UI的布局优化
- android UI进阶之布局的优化
- android UI进阶之布局的优化
- android UI进阶之布局的优化
- android UI布局的性能优化小结
- android UI布局优化的常用工具
- 浅谈android布局UI优化
- 布局优化的几种方法
- Android布局的优化
- android的布局优化
- android布局的优化
- Android布局的优化
- Android优化--布局优化
- Android UI学习 - FrameLayou和布局优化
- hdu 2030 汉字统计
- hdu 2031 进制转换
- hdu 2032 杨辉三角
- hdu 2033 人见人爱A+B
- 基于数据库的C#用户登录程序
- 【UI布局优化】Android布局优化的几种方式
- android:hint 默认提示内容
- hdu 1509 Windows Message Queue (优先队列)
- hdu 1870 愚人节的礼物
- hdu 1873 看病要排队
- hdu 1702 ACboy needs your help again!
- system V信号量和Posix信号量
- websphere8 从安装到部署 测试集群应用程序 安装j2ee程序(非常详细)
- hdu 1518 Square(深搜+剪枝)