当前位置: 移动技术网 > IT编程>移动开发>Android > 深入解析Android App的LayoutInflate布局

深入解析Android App的LayoutInflate布局

2019年07月24日  | 移动技术网IT编程  | 我要评论

9c8844,神探夏洛克 迅雷下载,睡前瑜伽减肥视频

1、 题外话 
 相信大家对layoutinflate都不陌生,特别在listview的adapter的getview方法中基本都会出现,使用inflate方法去加载一个布局,用于listview的每个item的布局。inflate有三个参数,我在初学android的时候这么理解的:
(1)对于inflate的三个参数(int resource, viewgroup root, boolean attachtoroot);
(2)如果inflate(layoutid, null )则layoutid的最外层的控件的宽高是没有效果的;
(3)如果inflate(layoutid, root, false ) 则认为和上面效果是一样的;
(4)如果inflate(layoutid, root, true ) 则认为这样的话layoutid的最外层控件的宽高才能正常显示;
如果你也这么认为,那么你有就必要好好阅读这篇文章,因为这篇文章首先会验证上面的理解是错误的,然后从源码角度去解释,最后会从viewgroup与view的角度去解释。
2、 实践是验证真理的唯一标准
下面我写一个特别常见的例子来验证上面的理解是错误的,一个特别简单的listview,每个item中放一个按钮:
activity的布局文件:

<listview xmlns:android="http://schemas.android.com/apk/res/android" 
xmlns:tools="http://schemas.android.com/tools" 
android:id="@+id/id_listview" 
android:layout_width="fill_parent" 
android:layout_height="wrap_content" > 
</listview> 

listview的item的布局文件:

<button xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/id_btn" 
  android:layout_width="120dp" 
  android:layout_height="120dp" > 
 
</button> 

listview的适配器:

package com.example.zhy_layoutinflater; 
 
import java.util.list; 
 
import android.content.context; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.viewgroup; 
import android.widget.baseadapter; 
import android.widget.button; 
 
public class myadapter extends baseadapter 
{ 
 
  private layoutinflater minflater; 
  private list<string> mdatas; 
 
  public myadapter(context context, list<string> datas) 
  { 
    minflater = layoutinflater.from(context); 
    mdatas = datas; 
  } 
 
  @override 
  public int getcount() 
  { 
    return mdatas.size(); 
  } 
 
  @override 
  public object getitem(int position) 
  { 
    return mdatas.get(position); 
  } 
 
  @override 
  public long getitemid(int position) 
  { 
    return position; 
  } 
 
  @override 
  public view getview(int position, view convertview, viewgroup parent) 
  { 
 
    viewholder holder = null; 
    if (convertview == null) 
    { 
      holder = new viewholder(); 
      convertview = minflater.inflate(r.layout.item, null); 
//     convertview = minflater.inflate(r.layout.item, parent ,false); 
//     convertview = minflater.inflate(r.layout.item, parent ,true); 
      holder.mbtn = (button) convertview.findviewbyid(r.id.id_btn); 
      convertview.settag(holder); 
    } else 
    { 
      holder = (viewholder) convertview.gettag(); 
    } 
 
    holder.mbtn.settext(mdatas.get(position)); 
 
    return convertview; 
  } 
 
  private final class viewholder 
  { 
    button mbtn; 
  } 
} 

 

主activity:

package com.example.zhy_layoutinflater; 
 
import java.util.arrays; 
import java.util.list; 
 
import android.app.activity; 
import android.os.bundle; 
import android.widget.listview; 
 
public class mainactivity extends activity 
{ 
 
  private listview mlistview; 
  private myadapter madapter; 
  private list<string> mdatas = arrays.aslist("hello", "java", "android"); 
 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
 
    mlistview = (listview) findviewbyid(r.id.id_listview); 
    madapter = new myadapter(this, mdatas); 
    mlistview.setadapter(madapter); 
 
 
  } 
 
} 

好了,相信大家对这个例子都再熟悉不过了,没啥好说的,我们主要关注getview里面的inflate那行代码:下面我依次把getview里的写成:
(1)convertview = minflater.inflate(r.layout.item, null);
(2)convertview = minflater.inflate(r.layout.item, parent ,false);
(3)convertview = minflater.inflate(r.layout.item, parent ,true);
分别看效果图:

图1:

2016418161657231.jpg (362×301)

图2:

2016418161728832.jpg (366×510)

图3:

fatal exception: main 
java.lang.unsupportedoperationexception:  
addview(view, layoutparams) is not supported in adapterview 

嗯,没错没有图3,第三种写法会报错。
由上面三行代码的变化,产生3个不同的结果,可以看到
inflater(resid, null )的确不能正确处理宽高的值,但是inflater(resid,parent,false)并非和inflater(resid, null )效果一致,它可以看出完美的显示了宽和高。
而inflater(resid,parent,true)报错了(错误的原因在解析源码的时候说)。
由此可见:文章开始提出的理解是绝对错误的。
3、源码解析
下面我通过源码来解释,这三种写法真正的差异
这三个方法,最终都会执行下面的代码:

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(); 
         
        if (debug) { 
          system.out.println("**************************"); 
          system.out.println("creating root view: " 
              + name); 
          system.out.println("**************************"); 
        } 
 
        if (tag_merge.equals(name)) { 
          if (root == null || !attachtoroot) { 
            throw new inflateexception("<merge /> can be used only with a valid " 
                + "viewgroup root and attachtoroot=true"); 
          } 
 
          rinflate(parser, root, attrs, false); 
        } else { 
          // temp is the root view that was found in the xml 
          view temp; 
          if (tag_1995.equals(name)) { 
            temp = new blinklayout(mcontext, attrs); 
          } else { 
            temp = createviewfromtag(root, name, attrs); 
          } 
 
          viewgroup.layoutparams params = null; 
 
          if (root != null) { 
            if (debug) { 
              system.out.println("creating params from root: " + 
                  root); 
            } 
            // 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); 
            } 
          } 
 
          if (debug) { 
            system.out.println("-----> start inflating children"); 
          } 
          // inflate all children under temp 
          rinflate(parser, temp, attrs, true); 
          if (debug) { 
            system.out.println("-----> done inflating children"); 
          } 
 
          // we are supposed to attach all the views we found (int temp) 
          // to root. do that now. 
          if (root != null && attachtoroot) { 
            root.addview(temp, params); 
          } 
 
          // decide whether to return the root that was passed in or the 
          // top view found in xml. 
          if (root == null || !attachtoroot) { 
            result = temp; 
          } 
        } 
 
      } catch (xmlpullparserexception e) { 
        inflateexception ex = new inflateexception(e.getmessage()); 
        ex.initcause(e); 
        throw ex; 
      } catch (ioexception e) { 
        inflateexception ex = new inflateexception( 
            parser.getpositiondescription() 
            + ": " + e.getmessage()); 
        ex.initcause(e); 
        throw ex; 
      } finally { 
        // don't retain static reference on context. 
        mconstructorargs[0] = lastcontext; 
        mconstructorargs[1] = null; 
      } 
 
      return result; 
    } 
  } 

第6行:首先声明了view result = root ;//最终返回值为result
第43行执行了:temp = createviewfromtag(root, name, attrs);创建了view
然后直接看48-59:

if(root!=null) 
{ 
 params = root.generatelayoutparams(attrs); 
    if (!attachtoroot) 
 { 
  temp.setlayoutparams(params); 
 } 
} 

可以看到,当root不为null,attachtoroot为false时,为temp设置了layoutparams.
继续往下,看73-75行:

if (root != null && attachtoroot) 
{ 
root.addview(temp, params); 
} 

当root不为null,attachtoroot为true时,将tmp按照params添加到root中。
然后78-81行:

if (root == null || !attachtoroot) {  
result = temp;  
}  

如果root为null,或者attachtoroot为false则,将temp赋值给result。
最后返回result。

从上面的分析已经可以看出:
(1)inflate(resid , null ) 只创建temp ,返回temp
(2)inflate(resid , parent, false )创建temp,然后执行temp.setlayoutparams(params);返回temp
(3)inflate(resid , parent, true ) 创建temp,然后执行root.addview(temp, params);最后返回root
由上面已经能够解释:
(1)inflate(resid , null )不能正确处理宽和高是因为:layout_width,layout_height是相对了父级设置的,必须与父级的layoutparams一致。而此temp的getlayoutparams为null
inflate(resid , parent,false ) 可以正确处理,因为temp.setlayoutparams(params);这个params正是root.generatelayoutparams(attrs);得到的。
(2)inflate(resid , parent,true )不仅能够正确的处理,而且已经把resid这个view加入到了parent,并且返回的是parent,和以上两者返回值有绝对的区别,还记得文章前面的例子上,myadapter里面的getview报的错误:
[html] view plain copy 在code上查看代码片派生到我的代码片

java.lang.unsupportedoperationexception:  
addview(view, layoutparams) is not supported in adapterview 

这是因为源码中调用了root.addview(temp, params);而此时的root是我们的listview,listview为adapterview的子类:
直接看adapterview的源码:

@override 
 public void addview(view child) { 
    throw new unsupportedoperationexception("addview(view) is not supported in adapterview"); 
 } 

可以看到这个错误为啥产生了。
4、 进一步的解析
上面我根据源码得出的结论可能大家还是有一丝的迷惑,我再写个例子论证我们上面得出的结论:
主布局文件:

<button xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/id_btn" 
  android:layout_width="120dp" 
  android:layout_height="120dp" 
  android:text="button" > 
</button> 

主activity:

package com.example.zhy_layoutinflater; 
 
import android.app.listactivity; 
import android.os.bundle; 
import android.util.log; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.viewgroup; 
 
public class mainactivity extends listactivity 
{ 
 
 
  private layoutinflater minflater; 
 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
 
    minflater = layoutinflater.from(this); 
 
    view view1 = minflater.inflate(r.layout.activity_main, null); 
    view view2 = minflater.inflate(r.layout.activity_main, 
        (viewgroup)findviewbyid(android.r.id.content), false); 
    view view3 = minflater.inflate(r.layout.activity_main, 
        (viewgroup)findviewbyid(android.r.id.content), true); 
 
    log.e("tag", "view1 = " + view1 +" , view1.layoutparams = " + view1.getlayoutparams()); 
    log.e("tag", "view2 = " + view2 +" , view2.layoutparams = " + view2.getlayoutparams()); 
    log.e("tag", "view3 = " + view3 ); 
 
  } 
 
} 

可以看到我们的主activity并没有执行setcontentview,仅仅执行了layoutinflater的3个方法。
注:parent我们用的是activity的内容区域:即android.r.id.content,是一个framelayout,我们在setcontentview(resid)时,其实系统会自动为了包上一层framelayout(id=content)。
按照我们上面的说法:
(1)view1的layoutparams 应该为null
(2)view2的layoutparams 应该不为null,且为framelayout.layoutparams
(3)view3为framelayout,且将这个button添加到activity的内容区域了(因为r.id.content代表actvity内容区域)
下面看一下输出结果,和activity的展示:

07-27 14:17:36.703: e/tag(2911): view1 = android.widget.button@429d1660 , view1.layoutparams = null 
07-27 14:17:36.703: e/tag(2911): view2 = android.widget.button@42a0e120 , view2.layoutparams = android.widget.framelayout$layoutparams@42a0e9a0 
07-27 14:17:36.703: e/tag(2911): view3 = android.widget.framelayout@42a0a240 

效果图:

2016418162206798.jpg (364×356)

可见,虽然我们没有执行setcontentview,但是依然可以看到绘制的控件,是因为

复制代码 代码如下:

view view3 = minflater.inflate(r.layout.activity_main,(viewgroup)findviewbyid(android.r.id.content), true);

这个方法内部已经执行了root.addview(temp , params); 上面已经解析过了。

也可以看出:和我们的推测完全一致,到此已经完全说明了inflate3个重载的方法的区别。相信大家以后在使用时也能选择出最好的方式。不过下面准备从viewgroup和view的角度来说一下,为啥layoutparams为null,就不能这确的处理。

5、从viewgroup和view的角度来解析
如果大家对自定义viewgroup和自定义view有一定的掌握,肯定不会对onmeasure方法陌生:
viewgroup的onmeasure方法所做的是:
为childview设置测量模式和测量出来的值。
如何设置呢?就是根据layoutparams。
(1)如果childview的宽为:layoutparams. match_parent,则设置模式为measurespec.exactly,且为childview计算宽度。
(2)如果childview的宽为:固定值(即大于0),则设置模式为measurespec.exactly,且将lp.width直接作为childview的宽度。
(3)如果childview的宽为:layoutparams. wrap_content,则设置模式为:measurespec.at_most
高度与宽度类似。
view的onmeasure方法:
主要做的就是根据viewgroup传入的测量模式和测量值,计算自己应该的宽和高:
一般是这样的流程:
(1)如果宽的模式是at_most:则自己计算宽的值。
(2)如果宽的模式是exactly:则直接使用measurespec.getsize(widthmeasurespec);
(3)对于最后一块,如果不清楚,不要紧,以后我会在自定义viewgroup和自定义view时详细讲解的。
大概就是这样的流程,真正的绘制过程肯定比这个要复杂,就是为了说明如果view的宽和高如果设置为准确值,则一定依赖于layoutparams,所以我们的inflate(resid,null)才没能正确处理宽和高。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网