LayoutInflater

LayoutInflater

LayoutInflatre能將一個xml文件解析成對應的View并構建對應的View的關系結構靴寂。使用這個類在需要的時候才解析一個布局文件匾荆,來避免一開始就加載xml布局文件造成資源浪費然走。

1. 使用方法

LayoutInflater inflater = LayoutInflater.from(this);
View inflatedLayout = inflater.inflate(R.layout.inflate_layout, mParent, false);

首先通過靜態(tài)方法傳入一個上下文對象獲取一個LayoutInflater實例,然后調用inflate方法來解析這個布局文件阱佛。這個方法傳入三個參數(shù):

  • resource:要解析的資源文件
  • root:解析出來的View的要添加到哪個容器中,相當于這個資源文件的上一層所表示的View
  • attachToRoot:是否將解析出來的View作為子View添加到上一個參數(shù)傳入的View容器中

2. 源碼分析

2.1

View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
    return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
    return inflate(parser, root, attachToRoot);
} finally {
    parser.close();
}

先判斷是否已經(jīng)解析過該文件余耽,如果已經(jīng)解析過直接返回解析出來的View,否則就將資源文件準換成一個XmlResourceParser對象辩蛋,然后調用inflate方法呻畸;

2.2

Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;

try {
    advanceToRootNode(parser);
    final String name = parser.getName();

    if (DEBUG) {
        System.out.println("**************************");
        System.out.println("Creating root view: "
                + name);
        System.out.println("**************************");
    }

    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 {
        // Temp is the root view that was found in the xml
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);

        ViewGroup.LayoutParams params = null;

        if (root != null) {
            if (DEBUG) {
                System.out.println("Creating params from root: " +
                        root);
            }
            // Create layout params that match root, if supplied
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                // Set the layout params for temp if we are not
                // attaching. (If we are, we use addView, below)
                temp.setLayoutParams(params);
            }
        }

        if (DEBUG) {
            System.out.println("-----> start inflating children");
        }

        // Inflate all children under temp against its context.
        rInflateChildren(parser, temp, attrs, true);

        if (DEBUG) {
            System.out.println("-----> done inflating children");
        }

        // We are supposed to attach all the views we found (int temp)
        // to root. Do that now.
        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }

        // Decide whether to return the root that was passed in or the
        // top view found in xml.
        if (root == null || !attachToRoot) {
            result = temp;
        }
    }
  

這個方法用于對不同的標簽類型來執(zhí)行不同的解析策略,最終把返回一個解析出來的View.在解析過程中主要由以下幾個步驟:

  • 把解析出來的View添加到父View中,并設置LayoutParam

  • 解析完成后調用onFinishInflate方法悼院,通知已經(jīng)解析完成

  • 返回最終解析出來的最頂層的父View,如果inflate參數(shù)傳入的root不為空且attachToRoottrue伤为,返回的View就是傳入的root,否則就是解析出來的頂層的view

  • 如果inflate方法傳入的root不為空attachToRoottrue或不傳入,就會把解析出來的View作為root下的一個子View.

    總結

    從上面的流程中看出在inflate階段就會創(chuàng)建布局文件中的所有View實例,并按照層級關系組織好View之間的布局關系据途。最后返回一個這個布局文件的根View,通過這個View就可以遍歷所有的View绞愚。

    3. findViewById

    從上面的分析中可以看出在inflate階段就會實例化布局文件中的所有View, 那通過findViewById是怎么拿到對應的View的。

    public final <T extends View> T findViewById(@IdRes int id) {
        if (id == NO_ID) {
            return null;
        }
        return findViewTraversal(id);
    }
    

    這個方法是在View.java這個類中定義的final方法颖医,這個方法會調用另外一個findViewTraversal方法

    protected <T extends View> T findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return (T) this;
        }
        return null;
    }
    

    這個方法是一個可以被重寫的方法位衩,在ViewGourp中重寫了這個方法,在調用的時候如果當前的View不是一個ViewGroup熔萧,就會執(zhí)行上面的邏輯糖驴,判斷傳入的id是不是等于自己的id僚祷,如果是就返回本View否則就返回null

    如果是一個ViewGroup就執(zhí)行下面的邏輯

    /**
     * {@hide}
     */
    @Override
    protected <T extends View> T findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return (T) this;
        }
    
        final View[] where = mChildren;
        final int len = mChildrenCount;
    
        for (int i = 0; i < len; i++) {
            View v = where[i];
    
            if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                v = v.findViewById(id);
    
                if (v != null) {
                    return (T) v;
                }
            }
        }
    
        return null;
    }
    

    ViewGroup中檢查Id是否和自己相等贮缕,如果不等會遍歷自己的所有子View/ViewGroup辙谜,然后依次調用他們的findViewById方法,如果子View是一個View就會判斷id是否相等感昼,如果相等就返回這個View,如果是ViewGroup就遞歸的調用這個方法來找子View中是否存在相同id的View装哆,如果遍歷完后也沒有就說明這個根節(jié)點的View下是沒有這個id的View。從這里可以看出通過findViewById這個方法查找某個View定嗓,這個View必須要是自己的子View,否則就會找不到烂琴,并不是在任意的View上調用findViewById方法來查找任意的View都能找到。

    3. 與inflate相關的幾個問題

    3.1 ResyclerView蜕乡、ListView中的子項設置寬高屬性失效

    在使用inflate來加載ItemView子項的布局文件時,如果在inlfate參數(shù)中參數(shù)的rootnull奸绷,那布局文件中設置的寬高可能就會失效。因為當傳入的root參數(shù)為null時层玲,就不會給這個布局文件的中的rootView設置layoutParams号醉。

    代碼:

    // Temp is the root view that was found in the xml
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    ViewGroup.LayoutParams params = null;
    
    if (root != null) {
        if (DEBUG) {
            System.out.println("Creating params from root: " +
                    root);
        }
        // Create layout params that match root, if supplied
        params = root.generateLayoutParams(attrs);
        if (!attachToRoot) {
            // Set the layout params for temp if we are not
            // attaching. (If we are, we use addView, below)
            temp.setLayoutParams(params);
        }
    }
    

    看到這有當rootView不為null,并且attachToRootfalse時才會設置布局文件的layoutParam辛块。否則layoutParam就位null畔派。inflate完成后當RecyclerView創(chuàng)建ViewHolder時發(fā)現(xiàn)如果當前ViewLayoutParam為空時就是設置一個默認的LayoutParam

    代碼:

    //從ViewAdapter中創(chuàng)建一個ViewHolder
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
    //設置LayoutParam
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
                final LayoutParams rvLayoutParams;
                if (lp == null) {
                    rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                    holder.itemView.setLayoutParams(rvLayoutParams);
                } else if (!checkLayoutParams(lp)) {
                    rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                    holder.itemView.setLayoutParams(rvLayoutParams);
                } else {
                    rvLayoutParams = (LayoutParams) lp;
                }
    
    

    獲取創(chuàng)建的ViewHolderLayoutParam润绵,如果為空就生成一個默認的LayoutManager线椰,實現(xiàn)的生成的方法在LayoutManager中定義,在LinearLayoutManager的實現(xiàn)為

    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
        } else {
            return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    }
    

    可以看到LineaLayoutManager中會生成wrap_content的參數(shù)值尘盼,也就是所有的ViewHolder的布局寬高都是wrap_content的憨愉。

    3.2 RecyclerView inflate第三個參數(shù)傳true報錯

    報錯的信息:

    throw new IllegalStateException("ViewHolder views must not be attached when"
            + " created. Ensure that you are not passing 'true' to the attachToRoot"
            + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
    

    inflate源碼中可以看出,但傳入的不為空且attachToRoottrue時卿捎,inflate完成返回的為傳入的 Root 配紫,holder = mAdapter.createViewHolder(RecyclerView.this, type)在創(chuàng)建一個ViewHodler時傳入的root就是當前的ResyclerView對象,所以午阵,如果inflate時傳入true躺孝,那么返回的還是RecyclerView對象,這樣在LayoutManager在調用下面的方法獲取一個View時拿到還是當前的RecyclerView底桂。-

    ---疑問:拿到RecyclerView也是可以獲取到里面的ViewHolder的植袍,為什么不可以呢。是不是無法確定哪個ViewHolder是新增的

    @NonNull
    public View getViewForPosition(int position) {
        return getViewForPosition(position, false);
    }
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末籽懦,一起剝皮案震驚了整個濱河市于个,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猫十,老刑警劉巖览濒,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呆盖,死亡現(xiàn)場離奇詭異,居然都是意外死亡贷笛,警方通過查閱死者的電腦和手機应又,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乏苦,“玉大人株扛,你說我怎么就攤上這事』慵觯” “怎么了洞就?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掀淘。 經(jīng)常有香客問我旬蟋,道長,這世上最難降的妖魔是什么革娄? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任倾贰,我火速辦了婚禮,結果婚禮上拦惋,老公的妹妹穿的比我還像新娘匆浙。我一直安慰自己,他們只是感情好厕妖,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布首尼。 她就那樣靜靜地躺著,像睡著了一般言秸。 火紅的嫁衣襯著肌膚如雪软能。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天井仰,我揣著相機與錄音埋嵌,去河邊找鬼。 笑死俱恶,一個胖子當著我的面吹牛,可吹牛的內容都是我干的范舀。 我是一名探鬼主播合是,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锭环!你這毒婦竟也來了聪全?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辅辩,失蹤者是張志新(化名)和其女友劉穎难礼,沒想到半個月后娃圆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蛾茉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年讼呢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谦炬。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡悦屏,死狀恐怖,靈堂內的尸體忽然破棺而出键思,到底是詐尸還是另有隱情础爬,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布吼鳞,位于F島的核電站看蚜,受9級特大地震影響,放射性物質發(fā)生泄漏赔桌。R本人自食惡果不足惜失乾,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纬乍。 院中可真熱鬧碱茁,春花似錦、人聲如沸仿贬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茧泪。三九已至蜓氨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間队伟,已是汗流浹背穴吹。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗜侮,地道東北人港令。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像锈颗,于是被迫代替她去往敵國和親顷霹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354