亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Android XML中引用自定義內(nèi)部類(lèi)view的四個(gè)why

 更新時(shí)間:2016年12月05日 09:07:10   作者:willhua  
本篇文章主要介紹了詳解Android XML中引用自定義內(nèi)部類(lèi)view,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。

今天碰到了在XML中應(yīng)用以?xún)?nèi)部類(lèi)形式定義的自定義view,結(jié)果遇到了一些坑。雖然通過(guò)看了一些前輩寫(xiě)的文章解決了這個(gè)問(wèn)題,但是我看到的幾篇都沒(méi)有完整說(shuō)清楚why,于是決定做這個(gè)總結(jié)。

使用自定義內(nèi)部類(lèi)view的規(guī)則

本文主要是總結(jié)why,所以先把XML布局文件中引用內(nèi)部類(lèi)的自定義view的做法擺出來(lái),有四點(diǎn):

1.自定義的類(lèi)必須是靜態(tài)類(lèi);

2.使用view作為XML文件中的tag,注意,v是小寫(xiě)字母,小寫(xiě)字母v,小寫(xiě)字母v;

3.添加class屬性,注意,沒(méi)有帶android:命名空間的,表明該自定義view的完整路徑,且外部類(lèi)與內(nèi)部類(lèi)之間用美元“$”連接,而不是“.”,注意,要美元“$”,不要“.”;

4.自定義的view至少應(yīng)該含有帶有Context, AttributeSet這兩個(gè)參數(shù)的構(gòu)造函數(shù)

布局加載流程主要代碼  

首先,XML布局文件的加載都是使用LayoutInflater來(lái)實(shí)現(xiàn)的,通過(guò)這篇文章的分析,我們知道實(shí)際使用的LayoutInflater類(lèi)是其子類(lèi)PhoneLayoutInflater,然后通過(guò)這篇文章的分析,我們知道view的真正實(shí)例化的關(guān)鍵入口函數(shù)是createViewFromTag這個(gè)函數(shù),然后通過(guò)createView來(lái)真正實(shí)例化view,下面便是該流程用到的關(guān)鍵函數(shù)的主要代碼:

final Object[] mConstructorArgs = new Object[2];

  static final Class<?>[] mConstructorSignature = new Class[] {
      Context.class, AttributeSet.class};
      
  //...
  
  /**
   * Creates a view from a tag name using the supplied attribute set.
   * <p>
   * <strong>Note:</strong> Default visibility so the BridgeInflater can
   * override it.
   *
   * @param parent the parent view, used to inflate layout params
   * @param name the name of the XML tag used to define the view
   * @param context the inflation context for the view, typically the
   *        {@code parent} or base layout inflater context
   * @param attrs the attribute set for the XML tag used to define the view
   * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
   *            attribute (if set) for the view being inflated,
   *            {@code false} otherwise
   */
  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
      boolean ignoreThemeAttr) {
    //**關(guān)鍵1**//
    if (name.equals("view")) {
      name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
      final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
      final int themeResId = ta.getResourceId(0, 0);
      if (themeResId != 0) {
        context = new ContextThemeWrapper(context, themeResId);
      }
      ta.recycle();
    }

    if (name.equals(TAG_1995)) {
      // Let's party like it's 1995!
      return new BlinkLayout(context, attrs);
    }

    try {
      View view;
      if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
      } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
      } else {
        view = null;
      }

      if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
      }

      if (view == null) {
        final Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = context;
        try {
          if (-1 == name.indexOf('.')) {
            //**關(guān)鍵2**//
            view = onCreateView(parent, name, attrs);
          } else {
            //**關(guān)鍵3**//
            view = createView(name, null, attrs);
          }
        } finally {
          mConstructorArgs[0] = lastContext;
        }
      }

      return view;
    }
    //后面都是catch,省略
  }
  
  protected View onCreateView(View parent, String name, AttributeSet attrs)
      throws ClassNotFoundException {
    return onCreateView(name, attrs);
  }
  
  protected View onCreateView(String name, AttributeSet attrs)
      throws ClassNotFoundException {
    return createView(name, "android.view.", attrs);
  }
  
  /**
   * Low-level function for instantiating a view by name. This attempts to
   * instantiate a view class of the given <var>name</var> found in this
   * LayoutInflater's ClassLoader.
   * 
   * @param name The full name of the class to be instantiated.
   * @param attrs The XML attributes supplied for this instance.
   * 
   * @return View The newly instantiated view, or null.
   */
  public final View createView(String name, String prefix, AttributeSet attrs)
      throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {
      Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

      if (constructor == null) {
        //**關(guān)鍵4**//
        // Class not found in the cache, see if it's real, and try to add it
        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 we have a filter, apply it to cached constructor
        if (mFilter != null) {
          // Have we seen this name before?
          Boolean allowedState = mFilterMap.get(name);
          if (allowedState == null) {
            // New class -- remember whether it is allowed
            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);
          }
        }
      }

      Object[] args = mConstructorArgs;
      args[1] = attrs;
      //**關(guān)鍵5**//
      final View view = constructor.newInstance(args);
      if (view instanceof ViewStub) {
        // Use the same context when inflating ViewStub later.
        final ViewStub viewStub = (ViewStub) view;
        viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
      }
      return view;

    }
    //后面都是catch以及finally處理,省略
  }

PhoneLayoutInflater中用到的主要代碼:

public class PhoneLayoutInflater extends LayoutInflater { 
  private static final String[] sClassPrefixList = { 
    "android.widget.", 
    "android.webkit.", 
    "android.app." 
  }; 
  //......
  
  /** Override onCreateView to instantiate names that correspond to the 
    widgets known to the Widget factory. If we don't find a match, 
    call through to our super class. 
  */ 
  @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { 
    //**關(guān)鍵6**//
    for (String prefix : sClassPrefixList) { 
      try { 
        View view = createView(name, prefix, attrs); 
        if (view != null) { 
          return view; 
        } 
      } catch (ClassNotFoundException e) { 
        // In this case we want to let the base class take a crack 
        // at it. 
      } 
    } 
 
    return super.onCreateView(name, attrs); 
  } 

  //.........
}

WHY

對(duì)于任何一個(gè)XML中的元素,首先都是從“1”處開(kāi)始(“1”即表示代碼中標(biāo)注“關(guān)鍵1”的位置,后同),下面便跟著代碼的流程來(lái)一遍:

1.“1”處進(jìn)行名字是否為“view”的判斷,那么這里會(huì)有兩種情況,我們先假設(shè)使用“view”

2.由于在“1”滿(mǎn)足條件,所以name被賦值為class屬性的值,比如“com.willhua.view.MyView”或者“com.willhua.view.MyClass$MyView”。其實(shí)這里也是要使用“view”來(lái)定義,而不是其他名字來(lái)定義的原因。

3.根據(jù)name的值,name中含有‘.'符號(hào),于是代碼走到“3”處,調(diào)用createView,且prefix參數(shù)為空

4.來(lái)到“4”處,prefix為空,于是loadClass函數(shù)的參數(shù)即為name,即在a.2中說(shuō)的class屬性的值。我們知道,傳給loadClass的參數(shù)是想要加載的類(lèi)的類(lèi)名,而在Java中,內(nèi)部類(lèi)的類(lèi)名表示都是在外部類(lèi)類(lèi)名的后面用符號(hào)“$”連接內(nèi)部類(lèi)類(lèi)名而來(lái),于是文章開(kāi)頭提到的第3點(diǎn)的答案也就是在這里了。補(bǔ)充一下,為什么是來(lái)到“4”,而不是對(duì)應(yīng)的else塊中呢?類(lèi)第一次被加載的時(shí)候,構(gòu)造器肯定是還不存在的,也就是if條件肯定是成立的。然后等到后面再次實(shí)例化的時(shí)候,就來(lái)到了else塊中,而在else快中只是根據(jù)mFilter做一些是否可以加載該view的判斷而已,并沒(méi)有從本質(zhì)上影響view的加載流程。

5.在“4”處還有一個(gè)很重要的地方,那就是constructor = class.getConstructor(mConstructorSignature)這句。首先,在代碼開(kāi)頭已經(jīng)給出了mConstructorSignature的定義:

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

在Oracle的文檔上找到getConstructor函數(shù)的說(shuō)明:

Returns a Constructor object that reflects the specified public constructor of the class represented by this Class object. The parameterTypes parameter is an array of Class objects that identify the constructor's formal parameter types, in declared order. If this Class object represents an inner class declared in a non-static context, the formal parameter types include the explicit enclosing instance as the first parameter.

The constructor to reflect is the public constructor of the class represented by this Class object whose formal parameter types match those specified by parameterTypes.

于是,這里其實(shí)解答了我們兩個(gè)問(wèn)題:(1)getConstructor返回的構(gòu)造函數(shù)是其參數(shù)與傳getConstructor的參數(shù)完全匹配的那一個(gè),如果沒(méi)有就拋出NoSuchMethodException異常。于是我們就知道,必須要有一個(gè)與mConstructorSignature完全匹配,就需要Context和AttributeSet兩個(gè)參數(shù)的構(gòu)造函數(shù);(2)要是該類(lèi)表示的是非靜態(tài)的內(nèi)部類(lèi),那么應(yīng)該把一個(gè)外部類(lèi)實(shí)例作為第一個(gè)參數(shù)傳入。而我們的代碼中傳入的mConstructorSignature是不含有外部類(lèi)實(shí)例的,因此我們必須把我們自定義的內(nèi)部類(lèi)view聲明為靜態(tài)的才不會(huì)報(bào)錯(cuò)。有些同學(xué)的解釋只是說(shuō)到了非靜態(tài)內(nèi)部類(lèi)需要由一個(gè)外部類(lèi)實(shí)例來(lái)引用,但我想那萬(wàn)一系統(tǒng)在加載的時(shí)候它會(huì)自動(dòng)構(gòu)造一個(gè)外部類(lèi)實(shí)例呢?于是這里給了否定的答案。

6. 拿到了類(lèi)信息clazz之后,代碼來(lái)到了“5”,在這里注意下mConstructorArgs,在貼出的代碼最前面有給出它的定義,為Object[2],并且,通過(guò)源碼中發(fā)現(xiàn),mConstructorArgs[0]被賦值為創(chuàng)建LayoutInflater時(shí)傳入的context。于是我們就知道         在“5”這里,傳給newInstance的參數(shù)為Context和AttributeSet。然后在Oracl的文檔上關(guān)于newInstance的說(shuō)明中也有一句:If the constructor's declaring class is an inner class in a non-static context, the first argument to the constructor needs to be the enclosing instance; 我們這里也沒(méi)有傳入外部類(lèi)實(shí)例,也就是說(shuō)對(duì)于靜態(tài)內(nèi)部類(lèi)也會(huì)報(bào)錯(cuò)。這里同樣驗(yàn)證了自定義了的內(nèi)部類(lèi)view必須聲明為靜態(tài)類(lèi)這個(gè)問(wèn)題。

至此,我們已經(jīng)在假設(shè)使用“view”,再次強(qiáng)調(diào)是小寫(xiě)‘v',作為元素名稱(chēng)的情況,推出了要在XML中應(yīng)用自定義的內(nèi)部類(lèi)view必須滿(mǎn)足的三點(diǎn)條件,即在class屬性中使用“$”連接外部類(lèi)和內(nèi)部類(lèi),必須有一個(gè)接受Context和AttributeSet參數(shù)的構(gòu)造函數(shù),必須聲明為靜態(tài)的這三個(gè)要求。

而如果不使用“view”標(biāo)簽的形式來(lái)使用自定義內(nèi)部類(lèi)view,那么在寫(xiě)XML的時(shí)候我們發(fā)現(xiàn),只能使用比如<com.willhua.MyClass.MyView />的形式,而不能使用<com.willhua.MyClass$MyView />的形式,這樣AndroidStudio會(huì)報(bào)“Tag start is not close”的錯(cuò)。顯然,如果我們使用<com.willhua.MyClass.MyView />的形式,那么在“關(guān)鍵4”處將會(huì)調(diào)用loadClass("com.willhua.MyClass.MyView"),這樣在前面也已經(jīng)分析過(guò),是不符合Java中關(guān)于內(nèi)部類(lèi)的完整命名規(guī)則的,將會(huì)報(bào)錯(cuò)。有些人估計(jì)會(huì)粗心寫(xiě)成大寫(xiě)的V的形式,即<View class="com.willhua.MyClass$MyView" ... />的形式,這樣將會(huì)在運(yùn)行時(shí)報(bào)“wrong type”錯(cuò),因?yàn)檫@樣本質(zhì)上定義的是一個(gè)android.view.View,而你在代碼中卻以為是定義的com.willhua.MyClass$MyView。

在標(biāo)識(shí)的“2”處,該出的調(diào)用流程為onCreateView(parent, name, attrs)——>onCreateView(name, attrs)——>createView(name, "android.view.", attrs),在前面提到過(guò),我們真正使用的的LayoutInflater是PhoneLayoutInflater,而在PhoneLayoutInflater對(duì)這個(gè)onCreateView(name, attrs)函數(shù)是進(jìn)行了重寫(xiě)的,在PhoneLayoutInflater的onCreateView函數(shù)中,即“6”處,該函數(shù)通過(guò)在name前面嘗試分別使用三個(gè)前綴:"android.widget.","android.webkit.","android.app."來(lái)調(diào)用createView,若都沒(méi)有找到則調(diào)用父類(lèi)的onCreateView來(lái)嘗試添加"android.view."前綴來(lái)加載該view。所以,這也是為什么我們可以比如直接使用<Button />, <View />的原因,因?yàn)檫@些常用都是包含在這四個(gè)包名里的。

總結(jié)

至此,開(kāi)篇提到的四個(gè)要求我們都已經(jīng)找到其原因了。其實(shí),整個(gè)流程走下來(lái),我們發(fā)現(xiàn),要使定義的元素被正確的加載到相關(guān)的類(lèi),有三種途徑:

1.只使用簡(jiǎn)單類(lèi)名,即<ViewName />的形式。對(duì)于“android.widget”, "android.webkit", "android.app"以及"android.view"這四個(gè)包內(nèi)的類(lèi),可以直接使用這樣的形式,因?yàn)樵诖a中會(huì)自動(dòng)給加上相應(yīng)的包名來(lái)構(gòu)成完整的路徑,而對(duì)于的其他的類(lèi),則不行,因?yàn)閂iewName加上這四個(gè)包名構(gòu)成的完整類(lèi)名都無(wú)法找到該類(lèi)。

2.使用<"完整類(lèi)名" />的形式,比如<com.willhua.MyView />或者<android.widget.Button />的形式來(lái)使用,對(duì)于任何的非內(nèi)部類(lèi)view,這樣都是可以的。但是對(duì)于內(nèi)部類(lèi)不行,因?yàn)檫@樣的類(lèi)名不符合Java中關(guān)于內(nèi)部類(lèi)完整類(lèi)名的定義規(guī)則。如果<com.willhua.MyClass$MyView />的形式能夠通過(guò)編譯話,肯定是能正確Inflate該MyView的,可惜在編寫(xiě)的時(shí)候就會(huì)提示錯(cuò)。

3.使用<view class="完整類(lèi)名" />的形式。這個(gè)是最通用的,所有的類(lèi)都可以這么干,但是前提是“完整類(lèi)名”寫(xiě)對(duì),比如前面提到的內(nèi)部類(lèi)使用"$"符號(hào)連接。

為了搞清楚加載內(nèi)部類(lèi)view的問(wèn)題,結(jié)果對(duì)整個(gè)加載流程都有了一定了解,感覺(jué)能說(shuō)多若干個(gè)why了,還是收獲挺大的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Android圖片加載利器之Picasso基本用法

    Android圖片加載利器之Picasso基本用法

    這篇文章主要為大家詳細(xì)介紹了Android圖片加載利器之Picasso的基本用法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • listview 選中高亮顯示實(shí)現(xiàn)方法

    listview 選中高亮顯示實(shí)現(xiàn)方法

    當(dāng)點(diǎn)擊左側(cè)ListView后,選中的一行就會(huì)一直呈高亮狀態(tài)顯示,圖中選中行字的顏色顯示為藍(lán)色(注意:是選中行后一直高亮,而不是只是點(diǎn)擊時(shí)高亮),如果再次點(diǎn)擊另外的一行, 則新的那一行就高亮,下面就來(lái)實(shí)現(xiàn)這個(gè)高亮效果的顯示
    2012-11-11
  • Android 更新UI的方法匯總

    Android 更新UI的方法匯總

    這篇文章主要介紹了Android 更新UI的方法匯總的相關(guān)資料, 非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-06-06
  • 簡(jiǎn)單談?wù)凙ndroid中SP與DP的區(qū)別

    簡(jiǎn)單談?wù)凙ndroid中SP與DP的區(qū)別

    Android里面的sp和dp網(wǎng)上有很多文章都談過(guò)了,但是看后總有一種意猶未盡的感覺(jué)?,F(xiàn)在我也來(lái)談?wù)刣p和sp,和大家交流一下,不對(duì)之處歡迎拍磚。
    2016-09-09
  • 基于Android掃描sd卡與系統(tǒng)文件的介紹

    基于Android掃描sd卡與系統(tǒng)文件的介紹

    本篇文章是對(duì)Android掃描sd卡與系統(tǒng)文件進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • Android中的廣播、服務(wù)、數(shù)據(jù)庫(kù)、通知、包等術(shù)語(yǔ)的原理和介紹(圖解)

    Android中的廣播、服務(wù)、數(shù)據(jù)庫(kù)、通知、包等術(shù)語(yǔ)的原理和介紹(圖解)

    這篇文章主要介紹了Android中的廣播、服務(wù)、數(shù)據(jù)庫(kù)、通知、包等術(shù)語(yǔ)的原理和介紹(圖解),本文用圖文方式總結(jié)了Android中的一些開(kāi)發(fā)術(shù)語(yǔ),需要的朋友可以參考下
    2014-10-10
  • Recycleview實(shí)現(xiàn)無(wú)限自動(dòng)輪播

    Recycleview實(shí)現(xiàn)無(wú)限自動(dòng)輪播

    這篇文章主要為大家詳細(xì)介紹了Recycleview實(shí)現(xiàn)無(wú)限自動(dòng)輪播,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • Android5.0新特性詳解之全新的動(dòng)畫(huà)

    Android5.0新特性詳解之全新的動(dòng)畫(huà)

    這篇文章主要介紹了Android5.0新特性詳解之全新的動(dòng)畫(huà)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • 詳解Android中的MVP架構(gòu)分解和實(shí)現(xiàn)

    詳解Android中的MVP架構(gòu)分解和實(shí)現(xiàn)

    本篇文章主要介紹了詳解Android中的MVP架構(gòu)分解和實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • Android Studio多渠道打包的配置方法

    Android Studio多渠道打包的配置方法

    今天小編就為大家分享一篇關(guān)于Android Studio多渠道打包的配置方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12

最新評(píng)論