首頁 > 軟體

Android用於載入xml的LayoutInflater原始碼超詳細分析

2022-08-26 18:05:35

1.在view的載入和繪製流程中:文章連結

我們知道,定義在layout.xml佈局中的view是通過LayoutInflate載入並解析成Java中對應的View物件的。那麼具體的解析過程是哪樣的。

先看onCreate方法,如果我們的Activity是繼承自AppCompactActivity。android是通過getDelegate返回的物件setContentView,這個mDelegate 是AppCompatDelegateImpl的範例。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 }
 //getDelegate 返回的是AppCompatDelegateImpl的範例
  public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
  }
  public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);
    }

在AppDelegateImpl中

  public void setContentView(int resId) {
        this.ensureSubDecor();
        //contentParent 是 系統佈局檔案 id 為content的view
        ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(android.R.id.content));
        contentParent.removeAllViews();
        LayoutInflater.from(this.mContext).inflate(resId, contentParent);
        this.mOriginalWindowCallback.onContentChanged();
  }

resource 就是傳遞過來的layout資源id,系統通過XmlPullParser來解析xml。Root是上面得到的contentView。

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
         final Resources res = getContext().getResources();
         final XmlResourceParser parser = res.getLayout(resource);
         try {
             return inflate(parser, root, attachToRoot);
         } finally {
             parser.close();
         }
  }

name 就是定義在佈局檔案中的控制元件名字,LinearLayout,TextView等,包括自定義的控制元件

attrs定義在控制元件下所有屬性,包括寬高顏色背景等。

先通過createViewFromTag拿到佈局檔案中的root view。

再通過rInflateChildren遍歷子View。

最後root.addView(temp, params);將佈局檔案的root view 新增到contentView中,成為它的一個子View。

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
   final AttributeSet attrs = Xml.asAttributeSet(parser);
     final String name = parser.getName();
   //在layout.xml中找到的root view
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
   // Create layout params that match root, if supplied
      params = root.generateLayoutParams(attrs);
    // Inflate all children under temp against its context.
    //遍歷佈局檔案中定義的子view,將定義在xml的view轉換成對應的java物件。
      rInflateChildren(parser, temp, attrs, true);
    if (root != null && attachToRoot) {
        //將layout中定義的root view 加到contentView中
         root.addView(temp, params);
    }
 }

createViewFromTag方法,通過name和attrs建立View物件。

再呼叫rInflateChildren 載入子View,通過迴圈遍歷,把整個layout樹轉換成Java的View物件。

  final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
//開始遍歷子view
    void rInflate(XmlPullParser parser, View parent, Context context,
               AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
            .......
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            rInflateChildren(parser, view, attrs, true);
            viewGroup.addView(view, params);
         }
    }

createViewFromTag是建立View物件的關鍵方法。

有兩種方式,一種是繼承自AppCompactActivity,會通過factory的onCreateView建立view。

另外一種是繼承自Activity,沒有設定factory,或者通過factory建立view失敗,則呼叫onCreateView方法進行建立。

 //將定義在xml的標籤通過反射成對應的java物件。
     View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            // 當Activity繼承自AppCompactActivity時,會在AppCompactActivity,onCreate時呼叫
            // delegate.installViewFactory()設定factory,然後呼叫factory的方法建立view
           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);
            }
            //當Activity繼承自Activity時,沒有設定factory時,執行下面的建立過程
			//或者通過上面的方式沒有載入到View,也會呼叫下面的方法建立view物件。
            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;
      }

先看第一種方法:呼叫factory的onCreateView方法,是通過呼叫mAppCompatViewInflater.createView建立的,根據name和attrs,直接呼叫View的建構函式建立的物件。建立的都是一些系統內建的view物件。

final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext.....){
  View view = null;
        // We need to 'inject' our tint aware Views in place of the standard versions
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            .............
         return view;
}

再看第二種方式:通過反射進行建立。通過反射的方式,可以建立自定義的view物件。

public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs){
       Class<? extends View> clazz = null;
          clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
          constructor = clazz.getConstructor(mConstructorSignature);
           constructor.setAccessible(true);
           //將得到的建構函式儲存的map中
           sConstructorMap.put(name, constructor);
     final View view = constructor.newInstance(args);
     return view;
}

通過以上兩種方式,就可以完成整個layout 的Java 物件轉換。

然後就可以呼叫view的繪製的方法,執行view繪製流程。onlayout,onMeasure,ondraw。

app換膚的的框架可以通過設定自定義的Factory來實現。這塊有機會再寫文章探討。

到此這篇關於Android用於載入xml的LayoutInflater原始碼超詳細分析的文章就介紹到這了,更多相關Android LayoutInflater 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com