中文字幕日韩精品一区二区免费_精品一区二区三区国产精品无卡在_国精品无码专区一区二区三区_国产αv三级中文在线

Android應(yīng)用中如何使用LayoutInflater加載布局

Android應(yīng)用中如何使用LayoutInflater加載布局?針對這個問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

站在用戶的角度思考問題,與客戶深入溝通,找到五臺網(wǎng)站設(shè)計(jì)與五臺網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名申請、虛擬空間、企業(yè)郵箱。業(yè)務(wù)覆蓋五臺地區(qū)。

需要從layout中加載View的時(shí)候,會調(diào)用這個方法

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

1.如何創(chuàng)建LayoutInflater?

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

我們進(jìn)入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;
  }

好吧,是獲取的系統(tǒng)服務(wù)!是從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<&#63;> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null &#63; fetcher.getService(ctx) : null;
  }

由代碼可知,我們的Service是從SYSTEM_SERVICE_FETCHERS這個HashMap中獲得的,而稍微看一下代碼就會發(fā)現(xiàn),這個HashMap是在static模塊中賦值的,這里注冊了很多的系統(tǒng)服務(wù),什么ActivityService,什么AlarmService等等都是在這個HashMap中。從LayoutInflater.from方法中可以知道,我們找到是Context.LAYOUT_INFLATER_SERVICE對應(yīng)的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方法分析

這個才是最重要的方法,因?yàn)榫褪沁@個方法把我們的layout轉(zhuǎn)換成了View對象。這個方法直接就在LayoutInflater抽象類中定義

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

傳入的參數(shù)一個是layout的id,一個是是否指定ParentView,而真正的實(shí)現(xiàn)我們還得往下看

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(關(guān)于在Android中的xml文件解析器這里就不詳細(xì)講了,免得扯得太遠(yuǎn),不了解的同學(xué)可以在網(wǎng)上查找相關(guān)資料閱讀),而這其實(shí)是把我們定義layout的xml文件給加載進(jìn)來了。

然后,繼續(xù)調(diào)用了另一個inflate方法

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
      final Context inflaterContext = mContext;
      //快看,View的構(gòu)造函數(shù)中的attrs就是這個?。?!
      final AttributeSet attrs = Xml.asAttributeSet(parser);

      //這個數(shù)組很重要,從名字就可以看出來,這是構(gòu)造函數(shù)要用到的參數(shù)
      mConstructorArgs[0] = inflaterContext;
      View result = root;

      try {
        // 找到根節(jié)點(diǎn),找到第一個START_TAG就跳出while循環(huán),
        // 比如<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!");
        }
        //獲取根節(jié)點(diǎn)的名稱
        final String name = parser.getName();

        //判斷是否用了merge標(biāo)簽
        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 {
          // 這里需要調(diào)用到PhoneLayoutInflater中的方法,獲取到根節(jié)點(diǎn)對應(yīng)的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);
            }
          }

          // 上面解析了根節(jié)點(diǎn),這里解析根節(jié)點(diǎn)下面的子節(jié)點(diǎn)
          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;
    }
  }

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

3.createViewFromTag

我們一層層跟進(jìn)代碼,最后會到這里

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 {
          //不含“.” 說明是系統(tǒng)自帶的控件
          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;
      ......
    }
  }

為了方便理解,將無關(guān)的代碼去掉了,我們看到其實(shí)就是調(diào)用的createView方法來從xml節(jié)點(diǎn)轉(zhuǎn)換成View的。如果name中不包含'.' 就調(diào)用onCreateView方法,否則直接調(diào)用createView方法。

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

4. createView方法

public final View createView(String name, String prefix, AttributeSet attrs)
      throws ClassNotFoundException, InflateException {
    //1.通過傳入的類名,獲取該類的構(gòu)造器

    Constructor<&#63; extends View> constructor = sConstructorMap.get(name);
    Class<&#63; extends View> clazz = null;

    try {
      if (constructor == null) {

        clazz = mContext.getClassLoader().loadClass(
            prefix != null &#63; (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 &#63; (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.通過獲得的構(gòu)造器,創(chuàng)建View實(shí)例
      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) {
     ......
    } 
  }

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

第一,根據(jù)ClassName將類加載到內(nèi)存,然后獲取指定的構(gòu)造器constructor。構(gòu)造器是通過傳入?yún)?shù)類型和數(shù)量來指定,這里傳入的是mConstructorSignature

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

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

第二,使用獲得的構(gòu)造器constructor來創(chuàng)建一個View實(shí)例。

5.回答問題

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

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

因?yàn)長ayoutInflater其實(shí)是通過xml解析器來加載xml文件,而layout文件的格式就是xml,所以可以讀取。

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

LayoutInflater加載到xml文件中內(nèi)容之后,通過反射將每一個標(biāo)簽的名字取出來,并生成對應(yīng)的類名,然后通過反射獲得該類的構(gòu)造器函數(shù),參數(shù)為Context和AttributeSet。然后通過構(gòu)造器創(chuàng)建View對象。

3.我們定義View的時(shí)候,如果需要在布局中使用,則必須實(shí)現(xiàn)帶AttributeSet參數(shù)的構(gòu)造方法,這又是為什么呢?

因?yàn)長ayoutInflater在解析xml文件的時(shí)候,會將xml中的內(nèi)容轉(zhuǎn)換成一個AttributeSet對象,該對象中包含了在xml文件設(shè)定的屬性值。需要在構(gòu)造函數(shù)中將這些屬性值取出來,賦給該實(shí)例的屬性。

關(guān)于Android應(yīng)用中如何使用LayoutInflater加載布局問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道了解更多相關(guān)知識。

網(wǎng)站名稱:Android應(yīng)用中如何使用LayoutInflater加載布局
網(wǎng)頁鏈接:http://www.rwnh.cn/article46/jjspeg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、網(wǎng)站策劃、品牌網(wǎng)站建設(shè)網(wǎng)站設(shè)計(jì)公司、定制網(wǎng)站微信小程序

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司
新宁县| 宜宾县| 邹平县| 高清| 乌苏市| 凭祥市| 凭祥市| 鹿邑县| 佛坪县| 锦屏县| 罗定市| 海城市| 诸城市| 江永县| 石狮市| 竹溪县| 花莲市| 宝鸡市| 靖宇县| 灌南县| 城口县| 丰县| 阿坝| 自贡市| 瑞丽市| 彩票| 周口市| 铁岭县| 满洲里市| 清涧县| 武安市| 泸州市| 乐至县| 大竹县| 自贡市| 张家港市| 菏泽市| 岑巩县| 皋兰县| 合作市| 留坝县|