読者です 読者をやめる 読者になる 読者になる

woshidan's blog

そんなことよりコードにダイブ。

LayoutInfalter#inflateの引数について

Android LayoutInfalter

RecyclerView.AdapteronCreateViewHolder メソッドの中で、

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_in_list, parent, false));
    }

のように書くつもりのところを

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_in_list, parent));
    }

と、引数を一つ忘れてしまったところ、下記のようなエラーが発生しました。

java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
  at android.view.ViewGroup.addViewInner(ViewGroup.java:3378)
  at android.view.ViewGroup.addView(ViewGroup.java:3249)
  at android.view.ViewGroup.addView(ViewGroup.java:3194)
  // ...以下続く

ちょっと面白かったのでメモします。

忘れてしまっていたパラメータは、 attachToRootでした。

http://developer.android.com/intl/ja/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup)

上記によると、引数を一つなくした #inflate (int resource, ViewGroup root)メソッドの返り値は、

  • resourceで指定されたXMLから生成されたViewのrootにあたるView
  • 生成されたViewのRootにあたるViewは、rootがnullでなければ、rootに、rootがnullであればxmlに記述されているレイアウトのrootにあたるView

になります。なので、

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_in_list, parent));
    }

の場合、R.layout.item_in_listのrootであるLinearLayoutではなく、 parent(RecyclerView)が返ってきます。

ViewHolderで扱うView = RecyclerViewの子要素のViewとして RecyclerView を返していたので、The specified child already has a parent. You must call removeView() on the child's parent firstと怒られていたみたいです。

また、

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_in_list, parent, false));
    }

のように書いた場合も確認しておきます。

http://developer.android.com/intl/ja/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup, boolean)

attachToRootにfalseを指定した場合は、rootを返すのではなく、Xmlから生成されるViewのrootにあたるViewを返します。

ただし、rootに指定したViewのLayoutParamsは下記にある通り、xmlから生成されるViewのLayoutParamsに継承されるようです。

attachToRoot | boolean: Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view in the XML.

はっきりしなかったのですが、rootの指定の有無が子要素のレイアウトに影響するんだな、ということだけはわかるコード例を下記に置いておきます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/itemText"
        android:text="hello, world"
        android:background="#98fb98"
        android:layout_width="wrap_content"
        android:gravity="center"
        android:layout_height="32dp" />
</LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:padding="30dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

のレイアウトを用意して、

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 下記のparentをnullにしてみる
        return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_in_list, parent, false));
    }

Adapterのコードをコメントのように変更した場合としなかった場合のレイアウトを比べてみます。

(残りのコードの部分については

https://gist.github.com/woshidan/ea63ac52a9141eaa39de8c1cd26d9e45

をご参照ください.)

rootにparentを与えた場合

f:id:woshidan:20160408221151p:plain

rootにnullを与えた場合

f:id:woshidan:20160408221145p:plain

こちらも参考にさせていただきました。

http://y-anz-m.blogspot.jp/2012/04/android-viewgroup.html