inflater.inflate使用詳解


原文地址:https://possiblemobile.com/2013/05/layout-inflation-as-intended/


Layout inflation is the term usedwithin the context of Android to indicate when an XML layout resource is parsedand converted into a hierarchy of View objects.

We’re hiring! Android Engineers apply here.

It’s commonpractice in the Android SDK, but you may be surprised to find that there is awrong way to use LayoutInflater, and yourapplication might be one of the offenders. If you’ve ever written somethinglike the following code using LayoutInflater in your Androidapplication:

1

inflater.inflate(R.layout.my_layout, null);

PLEASE read on,because you’re doing it wrong and I want to explain to you why.

Get to KnowLayoutInflater

Let’s first takea look at how LayoutInflater works. There aretwo usable versions of the inflate() method for astandard application:

1

inflate(int resource, ViewGroup root)

1

inflate(int resource, ViewGroup root, boolean attachToRoot)

The firstparameter points to the layout resource you want to inflate. The secondparameter is the root view of the hierarchy you are inflating the resource toattach to. When the third parameter is present, it governs whether or not theinflated view is attached to the supplied root after inflation.

It is these lasttwo parameters that can cause a bit of confusion. With the two parameterversion of this method, LayoutInflater willautomatically attempt to attach the inflated view to the supplied root.However, the framework has a check in place that if you pass null for the root itbypasses this attempt to avoid an application crash.

Many developerstake this behavior to mean that the proper way to disable attachment oninflation is by passing null as root; in manycases not even realizing that the three parameter version of inflate() exists. By doingthings this way, we also disable another very important function the root viewhas…but I’m getting ahead of myself.

Examples from theFramework

Let’s examinesome situations in Android where the framework expects you as a developer tointeractively inflate portions of the view.

Adapters are the mostcommon case for using LayoutInflater is custom ListView adapters overriding getView(), which has thefollowing method signature:

1

getView(int position, View convertView, ViewGroup parent)

Fragments also useinflation often when creating views via onCreateView(); notice itsmethod signature:

1

onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

Have you noticedthat every time the framework wants you to inflate a layout, they also pass youthe parent ViewGroup it willeventually be attached to? Notice also that in most cases (including the abovetwo examples), it will throw an Exception later on if LayoutInflater is allowed toautomatically attach the inflated view to the root.

So why do yousuppose we are given this ViewGroup if we are notsupposed to attach to it? It turns out the parent view is a very important partof the inflation process because it is necessary in order to evaluate the LayoutParams declared in theroot element of the XML being inflated. Passing nothing here is akin to tellingthe framework “I don’t know what parent this view will be attached to, sorry.”

The problem withthis is android:layout_xxx attributes arealways be evaluated in the context of the parent view. As aresult, without any known parent, all LayoutParams youdeclared on the root element of your XML tree will just get thrown away, and thenyou’ll be left asking “why is the framework ignoring the layout customizationsI defined? I’d better check SO and then file a bug.”

WithoutLayoutParams, the ViewGroup that eventuallyhosts the inflated layout is left to generate a default set for you. If you arelucky (and in many cases you are) these default parameters are the same as whatyou had in XML…masking the fact that something is amiss.

Application Example

So you claimyou’ve never seen this happen in an application? Take the following simplelayout that we want to inflate for a ListView row:

R.layout.item_row

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="?android:attr/listPreferredItemHeight"

    android:gravity="center_vertical"

    android:orientation="horizontal">

    <TextView

        android:id="@+id/text1"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:paddingRight="15dp"

        android:text="Text1" />

    <TextView

        android:id="@+id/text2"

        android:layout_width="0dp"

        android:layout_height="wrap_content"

        android:layout_weight="1"

        android:text="Text2" />

</LinearLayout>

We want to setthe height of our row to be a fixed height, in this case the preferred itemheight for the current theme…seems reasonable.

However, when weinflate this layout the wrong way

1

2

3

4

5

6

7

public View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null) {

        convertView = inflate(R.layout.item_row, null);

    }

 

    return convertView;

}

we end up with aresult that looks like this


What happened tothe fixed height we set?? This is usually where you end up setting the fixedheight on all your child views, switching the root elements height to wrap_content, and move onwithout really knowing why it broke (you may have even cursed at Google in theprocess).

If we insteadinflate the same layout this way

1

2

3

4

5

6

7

public View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null) {

        convertView = inflate(R.layout.item_row, parent, false);

    }

 

    return convertView;

}

we end up withwhat we expected in the first place.


Hooray!

Every Rule Has AnException

There are ofcourse instances where you can truly justify a null parent duringinflation, but they are few. One such instance occurs when you are inflating acustom layout to be attached to an AlertDialog. Consider thefollowing example where we want to use our same XML layout but set it as thedialog view:

1

2

3

4

5

6

7

AlertDialog.Builder builder = new AlertDialog.Builder(context);

View content = LayoutInflater.from(context).inflate(R.layout.item_row, null);

 

builder.setTitle("My Dialog");

builder.setView(content);

builder.setPositiveButton("OK", null);

builder.show();

The issue hereis that AlertDialog.Builder supports acustom view, but does not provide an implementation of setView() that takes alayout resource; so you must inflate the XML manually. However, because theresult will go into the dialog, which does not expose its root view (in fact,it doesn’t exist yet), we do not have access to the eventual parent of thelayout, so we cannot use it for inflation. It turns out, this is irrelevant,because AlertDialog will erase any LayoutParams on the layoutanyway and replace them with match_parent.

So the next timeyour fingers are tempted to just type null into inflate(), you shouldstop and ask yourself “do I really not know where this view will end up?”

Bottom line, youshould think of the two parameter version of inflate() as a convenienceshortcut to omit true as the third paramter. You should not think of passing null as a convenienceshortcut to omit false.

For futureinsights, please subscribe to our email list at the bottom of the page (belowthe comments)!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章