Viewの階層を入れ替える

以下の二つのView構造が以下のような構造だったとしよう。

  • scrollpane.xml

content.xmlはActivity生成時にsetContentViewメソッドでインフレートしており、その後何らかのタイミングで、適宜、View構造のルートより下全て(LinearLayout2以下全て)を、scrollpane.xmlから同様にインフレートした階層中のScrollView下にぶら下げ直したい。

結果としてViewの階層は以下のような構造になることを期待している。

目的としてはJFC/SwingのScrollPaneのように、LinearLayout2以下をScrollViewでスクロール可能な領域に設定したい訳だ。

これをコードで行う場合、以下のようになる。

  • ScrollableActivity.java
//content.xml側のルートビューを取得
ViewGroup linearLayout1 = (ViewGroup)this.findViewById(R.id.LinearLayout1);

LayoutInflater factory = LayoutInflater.from(this);

//scrollpane.xmlのルートを取得
LinearLayout linearLayout3 = (LinearLayout)factory.inflate(R.layout.scrollpane, null);

//その下のScrollViewを取得
ScrollView scrollView = (ScrollView)linearLayout3.findViewById(R.id.ScrollView);

//scrollView 下に元々のView階層を追加
scrollView.addView(linearLayout1.getChildAt(0));

//コンテンツViewを差し替え
this.setContentView(scrollView);

これで良いと思ったのだが、実行すると色を付けた行で以下の例外が発生する。

Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. 

例外のトレースから推するに、AndroidのViewGroupとその継承クラスは一度設定されたparent(そのViewの親に当たるView。addViewメソッドが実行された際に内部で設定される)を勝手に変更できないらしい。

removeViewを先に呼びだせとも言われているが、任意のView(ViewGroup)を他のView下に挿げ替えるには、

1. 自らの親のViewを取得する
2. 親のViewのremoveViewメソッドを呼びだして、自身との関連を除去する
3. 改めて他のViewに追加する(親Viewが設定し直される)

という手順を踏む必要があるようだ。

  • 修正後のコード
//content.xml側のルートビューを取得
ViewGroup linearLayout1 = (ViewGroup)this.findViewById(R.id.LinearLayout1);

LayoutInflater factory = LayoutInflater.from(this);

//scrollpane.xmlのルートを取得
LinearLayout linearLayout3 = (LinearLayout)factory.inflate(R.layout.scrollpane, null);

//その下のScrollViewを取得
ScrollView scrollView = (ScrollView)linearLayout3.findViewById(R.id.ScrollView);

//親Viewを取得してremoveView実行
ViewGroup parent = (ViewGroup)linearLayout1.getParent(); 
if ( parent != null ) {
    parent.removeView(linearLayout1);
}

//ScrollView下に元々のView階層を追加
scrollView.addView(linearLayout1.getChildAt(0));

//コンテンツViewを差し替え
this.setContentView(scrollView);

これで取り敢えず期待通りになったのだが、getParent()メソッドで戻るViewParentインタフェースがよく分からない。Viewの親たる振る舞いを規定しているインタフェースなのだが、ならばなぜViewParentに配下のViewを除去するためのremoveViewメソッドが無いのだろう。