ScrollViewの中でViewPagerとListViewを使う

この記事は [岩手県立大学 Advent Calendar 2015](http://qiita.com/advent-calendar/2015/ipu) の20日目の記事です．

最初はOpenFlowで書こうと思ってたんですがQiitaでハマってない感があったので無難にAndroidについて書きます．
Androidも書き尽くされている感がありますが，今回はScrollViewの中でViewPagerとListViewを使う方法について書きたいと思います．

# そもそも何が問題か
ScrollViewの中でいい感じにViewPagerを使うためにはHeightにwrapを指定したいです．
しかしViewPagerの特性上Heightはmatchになります．
無理やりwrapを指定しても望んだレイアウトにはなりません．
そういう仕様なんです．
また，ScrollViewの中で普通にListViewを使おうとするとListViewのitemが1行分しか表示されないなど問題があります．
これもそういう仕様なんです．
この上記問題を解決していきたいと思います．

# WrapContentHeightViewPager
まずはViewPagerの問題の解決法から．
[Doing it right: vertical ScrollView with ViewPager and ListView](https://pristalovpavel.wordpress.com/2014/12/26/doing-it-right-vertical-scrollview-with-viewpager-and-listview/) の下の方に載っているWrapContentHeightViewPagerを使います．
ソースはこんな感じです．

>```java:WrapContentHeightViewPager.java
>public class WrapContentHeightViewPager extends ViewPager {
>
>    public WrapContentHeightViewPager(Context context) {
>        super(context);
>    }
>
>    public WrapContentHeightViewPager(Context context, AttributeSet attrs) {
>        super(context, attrs);
>    }
>
>    @Override
>    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
>        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
>
>        int height = 0;
>        for(int i = 0; i < getChildCount(); i++) {
>            View child = getChildAt(i);
>            child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
>            
>            int h = child.getMeasuredHeight();
>            if(h > height) height = h;
>        }
>
>        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
>
>        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
>    }
>}
```

WrapContentHeightViewPagerが何をしているのかというとViewPagerの中のViewの高さを計算してセットしています．
これによってViewPagerの高さを中身のViewの高さで設定するので望んだレイアウトになってくれます．

# StretchedListView
次にLisViewの問題の解決法について．
[How can I put a ListView into a ScrollView without it collapsing?](http://stackoverflow.com/a/21878703) のStretchedListViewを使います．
ソースはイカの通り．

>```java:StretchedListView.java
>public class StretchedListView extends LinearLayout {
>
>    private final DataSetObserver dataSetObserver;
>    private ListAdapter adapter;
>    private AdapterView.OnItemClickListener onItemClickListener;
>
>    public StretchedListView(Context context, AttributeSet attrs) {
>        super(context, attrs);
>        setOrientation(LinearLayout.VERTICAL);
>        this.dataSetObserver = new DataSetObserver() {
>            @Override
>            public void onChanged() {
>                syncDataFromAdapter();
>                super.onChanged();
>            }
>
>            @Override
>            public void onInvalidated() {
>                syncDataFromAdapter();
>                super.onInvalidated();
>            }
>        };
>    }
>
>    public void setAdapter(ListAdapter adapter) {
>        ensureDataSetObserverIsUnregistered();
>
>        this.adapter = adapter;
>        if (this.adapter != null) {
>            this.adapter.registerDataSetObserver(dataSetObserver);
>        }
>        syncDataFromAdapter();
>    }
>
>    protected void ensureDataSetObserverIsUnregistered() {
>        if (this.adapter != null) {
>            this.adapter.unregisterDataSetObserver(dataSetObserver);
>        }
>    }
>
>    public Object getItemAtPosition(int position) {
>        return adapter != null ? adapter.getItem(position) : null;
>    }
>
>    public void setSelection(int i) {
>        getChildAt(i).setSelected(true);
>    }
>
>    public void setOnItemClickListener(AdapterView.OnItemClickListener onItemClickListener) {
>        this.onItemClickListener = onItemClickListener;
>    }
>
>    public ListAdapter getAdapter() {
>        return adapter;
>    }
>
>    public int getCount() {
>        return adapter != null ? adapter.getCount() : 0;
>    }
>
>    private void syncDataFromAdapter() {
>        removeAllViews();
>        if (adapter != null) {
>            int count = adapter.getCount();
>            for (int i = 0; i < count; i++) {
>                View view = adapter.getView(i, null, this);
>                boolean enabled = adapter.isEnabled(i);
>                if (enabled) {
>                    final int position = i;
>                    final long id = adapter.getItemId(position);
>                    view.setOnClickListener(new View.OnClickListener() {
>
>                        @Override
>                        public void onClick(View v) {
>                            if (onItemClickListener != null) {
>                                onItemClickListener.onItemClick(null, v, position, id);
>                            }
>                        }
>                    });
>                }
>                addView(view);
>
>            }
>        }
>    }
>}
```

LinearLayoutをextendsしてることから分かる通りLinearLayoutをListViewのように振る舞わせています．
ListViewのchildのViewの高さを取得してHeightに設定する方法もあるのですが，childの高さがバラバラの時はうまく高さが調節できないこともあるので私はStretchedListViewのほうが好きです．
ただListViewと違ってViewの再利用などはしていないのでitem数が多い時はパフォーマンスが落ちるかもしれません．

# 最後に
今回はScrollViewの中でViewPagerとListViewを使う方法について書きました．
解決方法がStack Over Flowなどに載っているものまとめみたいになってしまいましたが許してください（泣）
ありがとうございました．