Android规划优化之ViewStub、include、merge运用与源码剖析-Android-优质IT资源分享社区

admin
管理员
管理员
  • UID1
  • 粉丝29
  • 关注4
  • 发帖数581
  • 社区居民
  • 忠实会员
  • 原创写手
阅读:185回复:0

  Android规划优化之ViewStub、include、merge运用与源码剖析

楼主#
更多 发布于:2016-05-21 22:53


在开发中UI规划是咱们都会遇到的疑问,跟着UI越来越多,规划的重复性、复杂度也会随之增加。Android官方给了几个优化的办法,可是网络上的材料基本上都是对官方材料的翻译,这些材料都格外的简略,常常会出现疑问而不知其所以然。这篇文章即是对这些疑问的更具体的阐明,也欢迎咱们多留言沟通。
一、include
首要用得最多的应该是include,依照官方的意思,include即是为了处理重复界说相同规划的疑问。例如你有五个界面,这五个界面的顶部都有规划如出一辙的一个回来按钮和一个文本控件,在不运用include的状况下你在每个界面都需求从头在xml里边写相同的回来按钮和文本控件的顶部栏,这么的重复工作会相当的厌恶。运用include标签,咱们只需求把这个会被多次运用的顶部栏独立成一个xml文件,然后在需求运用的当地经过include标签引进即可。本来就相当于C言语、C++中的include头文件相同,咱们把一些常用的、底层的API封装起来,然后复用,需求的时分引进它即可,而不用每次都自个写一遍。示例如下
:
my_title_layout.xml

include规划文件:

这么咱们就能够运用my_title_layout了。
留意事项
运用include最常见的疑问即是findViewById查找不到方针控件,其准确的运用形式如下:
View titleView = findViewById(R.id.my_title_ly) ;
 TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;
 titleTextView.setText("new Title");
首要找到include的id,
例如这儿include设置的id为“my_title_ly”,然后再对获取到的titleView.findViewById来查找方针规划中的子控件,例如title_tv即是my_title_layout.xml中界说的子控件。因而咱们假如需求查找控件的话,能够设置include标签的id,经过这个id获取include对应的view今后,再经过对这个view进行findViewById才干准确查找。假如你设置了include标签的id,然后经过被include的规划的root
view的id来查找子元素的话,则会报错,如下 :
View titleView =
findViewById(R.id.my_title_parent_id) ;  TextView titleTextView =
(TextView)titleView.findViewById(R.id.title_tv) ;  titleTextView.setText("new
Title");
这么会报空指针反常,由于titleView没有找到,会报空指针。那么这是怎么回事呢?
咱们来剖析它的源码看看吧。对于规划文件的解析,终究都会调用到LayoutInflater的inflate办法,该办法终究又会调用rInflate办法,咱们看看这个办法。
/**      * Recursive method used to descend down
the xml hierarchy and instantiate      * views, instantiate their children, and
then call onFinishInflate().      */      void rInflate(XmlPullParser parser,
View parent, final AttributeSet attrs,              boolean finishInflate)
throws XmlPullParserException, IOException {          final int depth =
parser.getDepth();          int type;           // 迭代xml中的一切元素,挨个解析        
 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)) {//
假如xml中的节点是include节点,则调用parseInclude办法                  if (parser.getDepth() ==
0) {                      throw new InflateException("cannot be the
root element");                  }                  parseInclude(parser, parent,
attrs);              } else if (TAG_MERGE.equals(name)) {                  throw
new InflateException("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();      }
这个办法本来即是遍历xml中的一切元素,然后挨个进行解析。例如解析到一个标签,那么就依据用户设置的一些layout_width、layout_height、id等特点来构造一个TextView方针,然后添加到父控件(ViewGroup类型)中。标签也是相同的,咱们看到遇到include标签时,会调用parseInclude函数,这即是对标签的解析,咱们看看吧。
private void parseInclude(XmlPullParser parser,
View parent, AttributeSet attrs)             throws XmlPullParserException,
IOException {         int type;         if (parent instanceof ViewGroup) {      
      final int layout = attrs.getAttributeResourceValue(null, "layout", 0);    
        if (layout == 0) {// include标签中没有设置layout特点,会抛出反常                 final
String value = attrs.getAttributeValue(null, "layout");                 if
(value == null) {                     throw new InflateException("You must
specifiy a layout in the"                             + " include tag:");                 } else {                    
throw new InflateException("You must specifiy a valid layout "                  
          + "reference. The layout ID " + value + " is not valid.");            
    }             } else {                 final XmlResourceParser childParser =
                        getContext().getResources().getLayout(layout);          
      try {// 获取特点集,即在include标签中设置的特点                     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!");                     }                     //
1、解析include中的第一个元素                     final String childName =
childParser.getName();                     // 假如第一个元素是merge标签,那么调用rInflate函数解析  
                  if (TAG_MERGE.equals(childName)) {                         //
Inflate all children.                         rInflate(childParser, parent,
childAttrs, false);                     } else {//
2、咱们比如中的状况会走到这一步,首要依据include的特点集创立被include进来的xml规划的根view                        
// 这儿的根view对应为my_title_layout.xml中的RelativeLayout                         final
View view = createViewFromTag(parent, childName, childAttrs);                  
      final ViewGroup group = (ViewGroup) parent;// include标签的parent view      
                  ViewGroup.LayoutParams params = null;                        
try {// 获3、取规划特点                             params =
group.generateLayoutParams(attrs);                         } catch
(RuntimeException e) {                             params =
group.generateLayoutParams(childAttrs);                         } finally {    
                        if (params != null) {// 被inlcude进来的根view设置规划参数          
                      view.setLayoutParams(params);                            
}                         }                         // 4、Inflate all children.
解析一切子控件                         rInflate(childParser, view, childAttrs, true);  
                      // Attempt to override the included layout's android:id
with the                         // one set on thetag 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();                          //
5、将include中设置的id设置给根view,因而实际上my_title_layout.xml中的RelativeLayout的id会成为include标签中的id,include不设置id,那么也能够经过relative的找到.
                        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;          
              }                         // 6、将根view添加到父控件中                      
  group.addView(view);                     }                 } finally {        
            childParser.close();                 }             }         } else
{             throw new InflateException("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         }    
}
全部进程即是依据不相同的标签解析不相同的元素,首要会解析include元素,然后再解析被include进来的规划的root
view元素。在咱们的比如中对应的root view即是id为my_title_parent_id的RelativeLayout,然后再解析root
view下面的一切元素,这个进程是从上面注释的2~4的进程,然后是设置规划参数。咱们留意看注释5处,这儿就解说了为何include标签和被引进的规划的根元素都设置了id的状况下,经过被引进的根元素的id来查找子控件会找不到的状况。咱们看到,注释5处的会判别include标签的id假如不是View.NO_ID的话会把该id设置给被引进的规划根元素的id,即此刻在咱们的比如中被引进的id为my_title_parent_id的根元素RelativeLayout的id被设置成了include标签中的id,即RelativeLayout的id被动态修正成了”my_title_ly”。因而此刻咱们再经过“my_title_parent_id”这个id来查找根元素就会找不到了!
所以定论即是:
假如include中设置了id,那么就经过include的id来查找被include规划根元素的View;假如include中没有设置Id,
而被include的规划的根元素设置了id,那么经过该根元素的id来查找该view即可。拿到根元素后查找其子控件都是相同的。
二、ViewStub
咱们先看看官方的阐明:
ViewStub is a lightweight view with no dimension
and doesn’t draw anything or participate in the layout. As such, it’s cheap to
inflate and cheap to leave in a view hierarchy. Each ViewStub simply needs to
include the android:layout attribute to specify the layout to inflate.
本来ViewStub即是一个宽高都为0的一个View,它默许是不行见的,只要经过调用setVisibility函数或许Inflate函数才会将其要装载的方针规划给加载出来,然后达到延迟加载的作用,这个要被加载的规划经过android:layout特点来设置。例如咱们经过一个ViewStub来慵懒加载一个音讯流的谈论列表,由于一个帖子也许并没有谈论,此刻我能够不加载这个谈论的ListView,只要当有谈论时我才把它加载出来,这么就去除了加载ListView带来的资源耗费以及延时,示例如下
:
<ViewStub      android:id="@+id/stub_import"  
   android:inflatedId="@+id/stub_comm_lv"    
 android:layout="@layout/my_comment_layout"    
 android:layout_width="fill_parent"      android:layout_height="wrap_content"  
   android:layout_gravity="bottom" /
my_comment_layout.xml如下:

在运行时,咱们只需求操控id为stub_import的ViewStub的可见性或许调用inflate()函数来操控是不是加载这个谈论列表即可。示例如下
:
public class MainActivity extends Activity {    
 public void onCreate(Bundle b){          // main.xml中包括上面的ViewStub        
 setContentView(R.layout.main);          // 办法1,获取ViewStub,          ViewStub
listStub = (ViewStub) findViewById(R.id.stub_import);          // 加载谈论列表规划      
   listStub.setVisibility(View.VISIBLE);          //
获取到谈论ListView,留意这儿是经过ViewStub的inflatedId来获取              ListView commLv =
findViewById(R.id.stub_comm_lv);                  if ( listStub.getVisibility()
== View.VISIBLE ) {                         // 现已加载, 否则还没有加载                  }
             }         }
经过setVisibility(View.VISIBILITY)来加载谈论列表,此刻你要获取到谈论ListView方针的话,则需求经过findViewById来查找,而这个id并不是即是ViewStub的id。
这是为何呢 ?
咱们先看ViewStub的有些代码吧:
@SuppressWarnings({"UnusedDeclaration"})    
 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);        
 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;          setVisibility(GONE);// 设置不行教案        
 setWillNotDraw(true);// 设置不制作      }      @Override      protected void
onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        
 setMeasuredDimension(0, 0);// 宽高都为0      }      @Override      public void
setVisibility(int visibility) {          if (mInflatedViewRef != null) {//
假如现已加载过则只设置Visibility特点              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();//
调用inflate来加载方针规划              }          }      }      /**      * Inflates the
layout resource identified by {@link #getLayoutResource()}      * and replaces
this StubbedView in its parent by the inflated layout resource.      *      *
@return The inflated layout resource.      *      */      public View inflate()
{          final ViewParent viewParent = getParent();          if (viewParent !=
null && viewParent instanceof ViewGroup) {              if
(mLayoutResource != 0) {                  final ViewGroup parent = (ViewGroup)
viewParent;// 获取ViewStub的parent view,也是方针规划根元素的parent view                
 final LayoutInflater factory = LayoutInflater.from(mContext);                
 final View view = factory.inflate(mLayoutResource, parent,                    
     false);// 1、加载方针规划                //
2、假如ViewStub的inflatedId不是NO_ID则把inflatedId设置为方针规划根元素的id,即谈论ListView的id          
       if (mInflatedId != NO_ID) {                      view.setId(mInflatedId);
                 }                  final int index = parent.indexOfChild(this);
                 parent.removeViewInLayout(this);// 3、将ViewStub本身从parent中移除    
             final ViewGroup.LayoutParams layoutParams = getLayoutParams();    
             if (layoutParams != null) {                    
 parent.addView(view, index, layoutParams);// 4、将方针规划的根元素添加到parent中,有参数        
         } else {                      parent.addView(view, index);//
4、将方针规划的根元素添加到parent中                  }                  mInflatedViewRef = new
WeakReference(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");          }      }
能够看到,本来终究加载方针规划的仍是inflate()函数,在该函数中将加载方针规划,获取到根元素后,假如mInflatedId不为NO_ID则把mInflatedId设置为根元素的id,这也是为何咱们在获取谈论ListView时会运用findViewById(R.id.stub_comm_lv)来获取,其间的stub_comm_lv即是ViewStub的inflatedId。当然假如你没有设置inflatedId的话仍是能够经过谈论列表的id来获取的,例如findViewById(R.id.my_comm_lv)。然后即是ViewStub从parent中移除、把方针规划的根元素添加到parent中。最终会把方针规划的根元素回来,因而咱们在调用inflate()函数时能够直接获得根元素,省掉了findViewById的进程。
还有一种办法加载方针规划的即是直接调用ViewStub的inflate()办法,示例如下 :
public class MainActivity extends Activity {    
 // 把commLv2设置为类的成员变量      ListView commLv2 = null;      //      public void
onCreate(Bundle b){          // main.xml中包括上面的ViewStub        
 setContentView(R.layout.main);          // 办法二          ViewStub listStub2 =
(ViewStub) findViewById(R.id.stub_import) ;          // 成员变量commLv2为空则代表未加载    
     if ( commLv2 == null ) {          // 加载谈论列表规划,
而且获取谈论ListView,inflate函数直接回来ListView方针            commLv2 =
(ListView)listStub2.inflate();          } else {          // ViewStub现已加载      
   }      }  }
留意事项
判别是不是现已加载过,
假如经过setVisibility来加载,那么经过判别可见性即可;假如经过inflate()来加载是不能够经过判别可见性来处理的,而需求运用办法2来进行判别。
findViewById的疑问,留意ViewStub中是不是设置了inflatedId,假如设置了则需求经过inflatedId来查找方针规划的根元素。
三、Merge
首要咱们看官方的阐明:
The tag helps eliminate redundant view groups in
your view hierarchy when including one layout within another. For example, if
your main layout is a vertical LinearLayout in which two consecutive views can
be re-used in multiple layouts, then the re-usable layout in which you place the
two views requires its own root view. However, using another LinearLayout as the
root for the re-usable layout would result in a vertical LinearLayout inside a
vertical LinearLayout. The nested LinearLayout serves no real purpose other than
to slow down your UI performance.
本来即是削减在include规划文件时的层级。标签是这几个标签中最让我隐晦的,咱们也许想不到,标签竟然会是一个Activity,里边有一个LinearLayout方针。
/**  * Exercisetag in XML files.  */
 public class Merge extends Activity {      private LinearLayout mLayout;    
 @Override      protected void onCreate(Bundle icicle) {        
 super.onCreate(icicle);          mLayout = new LinearLayout(this);        
 mLayout.setOrientation(LinearLayout.VERTICAL);        
 LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout);        
 setContentView(mLayout);      }      public ViewGroup getLayout() {        
 return mLayout;      }  }
运用merge来安排子元素能够削减规划的层级。例如咱们在复用一个富含多个子控件的规划时,必定需求一个ViewGroup来办理,例如这么
:

将该规划经过include引进时就会多引进了一个FrameLayout层级,此刻构造如下 :

运用merge标签就会消除上图中蓝色的FrameLayout层级。示例如下 :

作用图如下 :

那么它是怎么完成的呢,咱们仍是看源码吧。有关的源码也是在LayoutInflater的inflate()函数中。
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;             View result = root;             try
{                 // Look for the root node.                 int type;          
      while ((type = parser.next()) != XmlPullParser.START_TAG &&      
                  type != XmlPullParser.END_DOCUMENT) {                     //
Empty                 }                 if (type != XmlPullParser.START_TAG) {  
                  throw new InflateException(parser.getPositionDescription()    
                        + ": No start tag found!");                 }          
      final String name = parser.getName();                 //
m假如是erge标签,那么调用rInflate进行解析                 if (TAG_MERGE.equals(name)) {      
              if (root == null || !attachToRoot) {                         throw
new InflateException("can be used only with a valid "                    
            + "ViewGroup root and attachToRoot=true");                     }    
                // 解析merge标签                     rInflate(parser, root, attrs,
false);                 } else {                    // 代码省掉                 }  
          } catch (XmlPullParserException e) {                 // 代码省掉          
  }              return result;         }     }        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)) {                  //
代码省掉                parseInclude(parser, parent, attrs);             } else if
(TAG_MERGE.equals(name)) {                 throw new
InflateException("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);                 // 获取merge标签的parent    
            final ViewGroup viewGroup = (ViewGroup) parent;                 //
获取规划参数                 final ViewGroup.LayoutParams params =
viewGroup.generateLayoutParams(attrs);                 // 递归解析每个子元素            
    rInflate(parser, view, attrs, true);                 //
将子元素直接添加到merge标签的parent view中                 viewGroup.addView(view, params);  
          }         }         if (finishInflate) parent.onFinishInflate();    
}
本来即是假如是merge标签,那么直接将其间的子元素添加到merge标签parent中,这么就确保了不会引进额外的层级。
在开发进程中,咱们一定要尽量去深究一些常用技能点的实质,这么才干避免出了疑问不知怎么处理的困境。刨根究底才干知道为何是这么,也是自我成长的必经之路。




优质IT资源分享社区[font=Tahoma  ]为你提供此文。 [font=Tahoma  ]
[font=Tahoma  ]本站有大量优质android教程视频,资料等资源,包含android基础教程,高级进阶教程等等,教程视频资源涵盖传智播客,极客学院,达内,北大青鸟,猎豹网校等等IT职业培训机构的培训教学视频,价值巨大。欢迎点击下方链接查看。[font=Tahoma  ]

android教程视频
优质IT资源分享社区(www.itziyuan.top)
一个免费,自由,开放,共享,平等,互助的优质IT资源分享网站。
专注免费分享各大IT培训机构最新培训教学视频,为你的IT学习助力!

!!!回帖受限制请看点击这里!!!
!!!资源失效请在此版块发帖说明!!!

[PS:按 CTRL+D收藏本站网址~]

——“优质IT资源分享社区”管理员专用签名~

本版相似帖子

游客