国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android LayoutInflater加載布局詳解及實例代碼

2019-10-23 19:44:52
字體:
來源:轉載
供稿:網友

Android  LayoutInflater加載布局詳解

對于有一定Android開發經驗的同學來說,一定使用過LayoutInflater.inflater()來加載布局文件,但并不一定去深究過它的原理,比如

1.LayoutInflater為什么可以加載layout文件?
2.加載layout文件之后,又是怎么變成供我們使用的View的?
3.我們定義View的時候,如果需要在布局中使用,則必須實現帶AttributeSet參數的構造方法,這又是為什么呢?

既然在這篇文章提出來,那說明這三個問題都是跟LayoutInflater脫不了干系的。在我們的分析過程中,會對這些問題一一進行解答。

我們一步一步來,首先當我們需要從layout中加載View的時候,會調用這個方法

LayoutInflater.from(context).inflater(R.layout.main_activity,null);

1.如何創建LayoutInflater?

這有什么值得說的?如果你打開了LayoutInflater.Java你自然就明白了,LayoutInflater是一個抽象類,而抽象類是不能直接被實例化的,也就是說我們創建的對象肯定是LayoutInflater的某一個實現類。

我們進入LayoutInflater.from方法中可以看到

public static LayoutInflater from(Context context) {    LayoutInflater LayoutInflater =        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);    if (LayoutInflater == null) {      throw new AssertionError("LayoutInflater not found.");    }    return LayoutInflater;  }

好吧,是獲取的系統服務!是從context中獲取,沒吃過豬肉還沒見過豬跑么,一說到context對象十有八九是說得ContextImpl對象,于是我們直接去到ContextImpl.java中,找到getSystemService方法

@Override  public Object getSystemService(String name) {    return SystemServiceRegistry.getSystemService(this, name);  }

額。。。又要去SystemServiceRegistry.java文件中

  /**   * Gets a system service from a given context.   */  public static Object getSystemService(ContextImpl ctx, String name) {    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);    return fetcher != null ? fetcher.getService(ctx) : null;  }

由代碼可知,我們的Service是從SYSTEM_SERVICE_FETCHERS這個HashMap中獲得的,而稍微看一下代碼就會發現,這個HashMap是在static模塊中賦值的,這里注冊了很多的系統服務,什么ActivityService,什么AlarmService等等都是在這個HashMap中。從LayoutInflater.from方法中可以知道,我們找到是Context.LAYOUT_INFLATER_SERVICE對應的Service

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,        new CachedServiceFetcher<LayoutInflater>() {      @Override      public LayoutInflater createService(ContextImpl ctx) {        return new PhoneLayoutInflater(ctx.getOuterContext());      }});

好啦,主角終于登場了——PhoneLayoutInflater,我們獲取的LayoutInflater就是這個類的對象。

那么,這一部分的成果就是我們找到了PhoneLayoutInflater,具體有什么作用,后面再說。

2.inflater方法分析

這個才是最重要的方法,因為就是這個方法把我們的layout轉換成了View對象。這個方法直接就在LayoutInflater抽象類中定義

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {    return inflate(resource, root, root != null);  }

傳入的參數一個是layout的id,一個是是否指定ParentView,而真正的實現我們還得往下看

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {    final Resources res = getContext().getResources();    if (DEBUG) {      Log.d(TAG, "INFLATING from resource: /"" + res.getResourceName(resource) + "/" ("          + Integer.toHexString(resource) + ")");    }    final XmlResourceParser parser = res.getLayout(resource);    try {      return inflate(parser, root, attachToRoot);    } finally {      parser.close();    }  }

我們先從context中獲取了Resources對象,然后通過res.getLayout(resource)方法獲取一個xml文件解析器XmlResourceParser(關于在Android中的xml文件解析器這里就不詳細講了,免得扯得太遠,不了解的同學可以在網上查找相關資料閱讀),而這其實是把我們定義layout的xml文件給加載進來了。

然后,繼續調用了另一個inflate方法

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {    synchronized (mConstructorArgs) {      final Context inflaterContext = mContext;      //快看,View的構造函數中的attrs就是這個?。。?     final AttributeSet attrs = Xml.asAttributeSet(parser);      //這個數組很重要,從名字就可以看出來,這是構造函數要用到的參數      mConstructorArgs[0] = inflaterContext;      View result = root;      try {        // 找到根節點,找到第一個START_TAG就跳出while循環,        // 比如<TextView>是START_TAG,而</TextView>是END_TAG        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();        //判斷是否用了merge標簽        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, inflaterContext, attrs, false);        } else {          // 這里需要調用到PhoneLayoutInflater中的方法,獲取到根節點對應的View          final View temp = createViewFromTag(root, name, inflaterContext, attrs);          ViewGroup.LayoutParams params = null;          //如果指定了parentView(root),則生成layoutParams,          //并且在后面會將temp添加到root中          if (root != null) {            params = root.generateLayoutParams(attrs);            if (!attachToRoot) {              temp.setLayoutParams(params);            }          }          // 上面解析了根節點,這里解析根節點下面的子節點          rInflateChildren(parser, temp, attrs, true);          if (root != null && attachToRoot) {            root.addView(temp, params);          }          if (root == null || !attachToRoot) {            result = temp;          }        }      } catch (Exception e) {      } finally {        // Don't retain static reference on context.        mConstructorArgs[0] = lastContext;        mConstructorArgs[1] = null;      }      return result;    }  }

這個就稍微有點長了,我稍微去除了一些跟邏輯無關的代碼,并且添加了注釋,如果有耐心看的話應該是能看懂了。這里主要講兩個部分,首先是rInflateChildren這個方法,其實就是一層一層的把所有節點取出來,然后通過createViewFromTag方法將其轉換成View對象。所以重點是在如何轉換成View對象的。

3.createViewFromTag

我們一層層跟進代碼,最后會到這里

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,      boolean ignoreThemeAttr) {      ......      ......    try {      ......      if (view == null) {        final Object lastContext = mConstructorArgs[0];        mConstructorArgs[0] = context;        try {          //不含“.” 說明是系統自帶的控件          if (-1 == name.indexOf('.')) {            view = onCreateView(parent, name, attrs);          } else {            view = createView(name, null, attrs);          }        } finally {          mConstructorArgs[0] = lastContext;        }      }      return view;    } catch (InflateException e) {      throw e;      ......    }  }

為了方便理解,將無關的代碼去掉了,我們看到其實就是調用的createView方法來從xml節點轉換成View的。如果name中不包含'.' 就調用onCreateView方法,否則直接調用createView方法。

在上面的PhoneLayoutInflater中就復寫了onCreateView方法,而且不管是否重寫,該方法最后都會調用createView。唯一的區別應該是系統的View的完整類名由onCreateView來提供,而如果是自定義控件在布局文件中本來就是用的完整類名。

4. createView方法

public final View createView(String name, String prefix, AttributeSet attrs)      throws ClassNotFoundException, InflateException {    //1.通過傳入的類名,獲取該類的構造器    Constructor<? extends View> constructor = sConstructorMap.get(name);    Class<? extends View> clazz = null;    try {      if (constructor == null) {        clazz = mContext.getClassLoader().loadClass(            prefix != null ? (prefix + name) : name).asSubclass(View.class);        if (mFilter != null && clazz != null) {          boolean allowed = mFilter.onLoadClass(clazz);          if (!allowed) {            failNotAllowed(name, prefix, attrs);          }        }        constructor = clazz.getConstructor(mConstructorSignature);        constructor.setAccessible(true);        sConstructorMap.put(name, constructor);      } else {        if (mFilter != null) {          Boolean allowedState = mFilterMap.get(name);          if (allowedState == null) {                 clazz = mContext.getClassLoader().loadClass(                prefix != null ? (prefix + name) : name).asSubclass(View.class);                boolean allowed = clazz != null && mFilter.onLoadClass(clazz);            mFilterMap.put(name, allowed);            if (!allowed) {              failNotAllowed(name, prefix, attrs);            }          } else if (allowedState.equals(Boolean.FALSE)) {            failNotAllowed(name, prefix, attrs);          }        }      }      //2.通過獲得的構造器,創建View實例      Object[] args = mConstructorArgs;      args[1] = attrs;      final View view = constructor.newInstance(args);      if (view instanceof ViewStub) {        final ViewStub viewStub = (ViewStub) view;        viewStub.setLayoutInflater(cloneInContext((Context) args[0]));      }      return view;    } catch (NoSuchMethodException e) {     ......    }   }

這段代碼主要做了兩件事情

第一,根據ClassName將類加載到內存,然后獲取指定的構造器constructor。構造器是通過傳入參數類型和數量來指定,這里傳入的是mConstructorSignature

static final Class<?>[] mConstructorSignature = new Class[] {      Context.class, AttributeSet.class};

即傳入參數是Context和AttributeSet,是不是猛然醒悟了!??!這就是為什么我們在自定義View的時候,必須要重寫View(Context context, AttributeSet attrs)則個構造方法,才能在layout中使用我們的View。

第二,使用獲得的構造器constructor來創建一個View實例。

5.回答問題

還記得上面我們提到的三個問題嗎?現在我們來一一解答:

1.LayoutInflater為什么可以加載layout文件?

因為LayoutInflater其實是通過xml解析器來加載xml文件,而layout文件的格式就是xml,所以可以讀取。

2.加載layout文件之后,又是怎么變成供我們使用的View的?

LayoutInflater加載到xml文件中內容之后,通過反射將每一個標簽的名字取出來,并生成對應的類名,然后通過反射獲得該類的構造器函數,參數為Context和AttributeSet。然后通過構造器創建View對象。

3.我們定義View的時候,如果需要在布局中使用,則必須實現帶AttributeSet參數的構造方法,這又是為什么呢?

因為LayoutInflater在解析xml文件的時候,會將xml中的內容轉換成一個AttributeSet對象,該對象中包含了在xml文件設定的屬性值。需要在構造函數中將這些屬性值取出來,賦給該實例的屬性。

感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 贡嘎县| 东平县| 亚东县| 秭归县| 溧水县| 城固县| 昌图县| 兰西县| 珲春市| 石屏县| 新源县| 宁波市| 内乡县| 油尖旺区| 隆化县| 南川市| 富川| 水城县| 宜城市| 出国| 柏乡县| 平南县| 莲花县| 吉木乃县| 九龙县| 岳西县| 内江市| 两当县| 宁安市| 阳信县| 郁南县| 个旧市| 尼玛县| 江达县| 惠水县| 峡江县| 景泰县| 县级市| 二连浩特市| 新津县| 甘孜县|