Android LayoutInflater原理分析

前言

經(jīng)歷就是財富

發(fā)現(xiàn)問題

有意無意之間,Android的開發(fā)不可避免涉及不到列表頁的,而列表頁大多也逃不開ListView,當然還有RecyclerView。在開發(fā)的時候總是有莫名的問題出現(xiàn),而這些莫名的問題可能會浪費你一大半時間,索性現(xiàn)在網(wǎng)上的資源也很豐富,盡可能的縮短了這個時間,但與此同時還需要一雙發(fā)現(xiàn)問題本質(zhì)的眼睛,閑話不再多說,還是直奔主題的好。

情況是這樣的:也就是一般的給ListView的Item設(shè)置高度,代碼表象如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="@dimen/dimen_120"
                android:background="@color/white"
                android:paddingEnd="@dimen/dimen_30"
                android:paddingLeft="@dimen/dimen_30"
                android:paddingRight="@dimen/dimen_30"
                android:paddingStart="@dimen/dimen_30">
    <View
        android:id="@+id/view_divide"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/color_dddddd"
        tools:background="@color/color_ff5736"/>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/color_333333"
            android:textSize="@dimen/text_size_28"
            tools:text="每日簽到"/>
        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/dimen_16"
            android:textColor="@color/color_999999"
            android:textSize="@dimen/text_size_24"
            tools:text="2017-05-13 15:24"/>
    </LinearLayout>

    <TextView
        android:id="@+id/tv_point_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:textSize="@dimen/text_size_34"
        tools:text="+2"
        tools:textColor="@color/color_ff5736"/>
</RelativeLayout>

在getView方法中:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // 省略代碼......

    convertView = LayoutInflater.from(getContext()).inflate(item.getItemLayout(), null, false);

    // 省略代碼......
}

導致運行在手機上的表象就是無論把這個高度設(shè)置多大,其表現(xiàn)出來的都是剛好自適應(yīng)其高度,讓人無可奈何,各種打斷點瞎改都無濟于事,時間緊迫,瞬間上頭了,哪根神經(jīng)搭錯了,想到去網(wǎng)上尋找答案,起初認為這個高度不起作用,肯定是哪塊代碼有問題了,屬于自己的疏忽操作,怎么可能是普遍存在呢?結(jié)果還真找到這樣的詞條了,搜索到結(jié)果后很快的解決了問題,兩種解決方案,特附上鏈接:

解決ListView中Item布局設(shè)置的layout_height無效
Android中設(shè)置ListView的item高度無效--解決方案

然而問題就這樣解決了嗎? 這樣的話,怎么能滿足一顆程序員求知的心?其歸根結(jié)底還是LayoutInflater在從中作梗,且聽我慢慢道來。

解決問題

熟悉LayoutInflater的同學都知道,LayoutInflater是用來動態(tài)加載布局的一大利器,獲取LayoutInflater有三種方式:

1. LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT\_INFLATER\_SERVICE);

2. LayoutInflater inflater = LayoutInflater.from(context); 

4. LayoutInflater inflater = getLayoutInflater();  //在Activity中調(diào)用

跟蹤源碼,上面的三種方式,最終都是通過第一種方式獲取的,在日常編碼過程中,通常是使用第二種或第三種方式間接獲取LayoutInflater,跟蹤源碼便不在此贅述,相信聰明的你肯定可以自行查看。

說到這里不得不說到動態(tài)創(chuàng)建View的方式:

1. View.inflate(Context context, int resource, ViewGroup root);

2. LayoutInflater.from(context).inflate(int resource, ViewGroup root, boolean attachToRoot);

跟蹤源碼,通過View.inflate()的方式其實還是調(diào)用了LayoutInflater.from(context).inflate(), 我們一起來看一下:

//View.inflate方法調(diào)用LayoutInflater的inflate方法
public static View inflate(Context context, int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
//LayoutInflater的inflate重載方法
public View inflate(int resource, ViewGroup root) {
    return inflate(resource, root, root != null);
}
//LayoutInflater的inflate重載方法
public View inflate(int resource, 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 {
            //最終的調(diào)用重載方法
            return inflate(parser, root, attachToRoot);
    } finally {
            parser.close();
    }
}

最終的最終調(diào)用該方法:


public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        mConstructorArgs[0] = mContext;
        View result = root;
        try {
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
            }
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            final String name = parser.getName();
            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);
            } else {
                View temp = createViewFromTag(name, attrs);
                ViewGroup.LayoutParams params = null;
                if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }
                rInflate(parser, temp, attrs);
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                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;
        }
        return result;
    }
}

上面的代碼就是使用Android提供的pull解析方式來解析布局文件的,核心方法是createViewFromTag(),這個方法是用于根據(jù)節(jié)點來創(chuàng)建View對象。

重點代碼摘要:

View temp = createViewFromTag(name, attrs);
ViewGroup.LayoutParams params = null;
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
    temp.setLayoutParams(params);
}
rInflate(parser, temp, attrs);

if (root != null && attachToRoot) {
    root.addView(temp, params);
}

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

歸納總結(jié):對于inflate(int resource, ViewGroup root, boolean attachToRoot)

  • 如果root為null,attachToRoot將失去作用,設(shè)置任何值都沒有意義
  • 如果root不為null,attachToRoot設(shè)為true,則會給加載的布局文件的指定一個父布局,即root
  • 如果root不為null,attachToRoot設(shè)為false,則會將布局文件最外層的所有l(wèi)ayout屬性進行設(shè)置,當該view被添加到父view當中時,這些layout屬性會自動生效
  • 在不設(shè)置attachToRoot參數(shù)的情況下,如果root不為null,attachToRoot參數(shù)默認為true

總結(jié)

綜上所述,給ListView的Item設(shè)置高度時所出現(xiàn)的問題,便迎刃而解。有時候我們不應(yīng)該僅僅停留在問題的表象上面,而應(yīng)該發(fā)現(xiàn)問題的本質(zhì),當你解決了這個問題,收獲的就不僅僅是這個問題了。

參考:
Android LayoutInflater原理分析,帶你一步步深入了解View(一)
LayoutInflater源碼分析與應(yīng)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容