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

うさがにっき

読書感想文とプログラムのこと書いてきます

HorizonalScrollViewを使った横スクロールできるタブメニュー

概要

Gunossyみたいな、横スクロールできるタブビューを実装する
f:id:tiro105:20140421161529p:plain

詳細

レイアウト

main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <!-- tab部分 -->
    <include layout="@layout/tab_track" />

    <!-- contents部分 -->
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
タブ部分のレイアウト

tab_track.xml

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/track_scroller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/tab_bg"
    android:fadingEdgeLength="0dp"
    android:scrollbars="none" >

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <LinearLayout
            android:id="@+id/track"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/tab_height"
            android:divider="?android:attr/dividerVertical"
            android:dividerPadding="@dimen/tab_divider_padding"
            android:orientation="horizontal"
            android:showDividers="middle" />

        <View
            android:id="@+id/indicator"
            android:layout_width="@dimen/indicator_width"
            android:layout_height="@dimen/indicator_height"
            android:layout_gravity="bottom"
            android:background="#aa33b5e5" />
    </FrameLayout>

</HorizontalScrollView>
タブ一項目ずつのレイアウト

tab_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:gravity="center"
    android:paddingLeft="@dimen/tab_padding"
    android:paddingRight="@dimen/tab_padding" />
Activityの初期設定など
  • viewpagerのviewを取得、adapter、listener設定
  • tabタップ時のイベント設定
  • indicatorの移動処理はviewPagerのlistenerで行う
  • adapterは以前のtabの記事参照(SectionsPagerAdapterとか)

ActionBarを使ったタブレイアウトの作成 - うさがにっき

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // INDICATOR_OFFSET・・・ タブのindicatorの幅
    final float density = getResources().getDisplayMetrics().density;
    mIndicatorOffset = (int) (INDICATOR_OFFSET * density);

    // Viewを取得
    mTrackScroller = (HorizontalScrollView) findViewById(R.id.track_scroller);
    mTrack = (ViewGroup) findViewById(R.id.track);
    mIndicator = findViewById(R.id.indicator);

    // ViewPagerのセットアップ
    // adapterは自由に作成
    PagerAdapter adapter = new ViewPagerAdapter(getFragmentManager());
    final ViewPager pager = (ViewPager) findViewById(R.id.pager);
    pager.setAdapter(adapter);
    pager.setOnPageChangeListener(new PageChangeListener());

    // タブをコンテナに追加
    LayoutInflater inflater = LayoutInflater.from(this);
    for (int i = 0; i < adapter.getCount(); i++) {
        final int position = i;
        TextView tv = (TextView) inflater.inflate(R.layout.tab_item, mTrack, false);

        tv.setText(adapter.getPageTitle(position));
        // タブタップ時対応したviewPagerを表示
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                pager.setCurrentItem(position);
            }
        });
        mTrack.addView(tv);
    }
}
Pagerのlistener
  • viewpagerのスクロール時に、タブ移動の画面描画を行う
  • 現在のタブと、次のタブからindicatorを描画
  • viewPagerスワイプ時、tab領域をスクロール
private class PageChangeListener implements ViewPager.OnPageChangeListener {
    private int mScrollingState = ViewPager.SCROLL_STATE_IDLE;

    @Override
    public void onPageSelected(int position) {
        // スクロール中はonPageScrolled()で描画するのでここではしない
        if (mScrollingState == ViewPager.SCROLL_STATE_IDLE) {
            updateIndicatorPosition(position, 0);
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        mScrollingState = state;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset,
            int positionOffsetPixels) {
        updateIndicatorPosition(position, positionOffset);
    }

    private void updateIndicatorPosition(int position, float positionOffset) {
        // 現在の位置のタブのView
        final View view = mTrack.getChildAt(position);
        // 現在の位置の次のタブのView、現在の位置が最後のタブのときはnull
        final View view2 = position == (mTrack.getChildCount() - 1) ? null
                : mTrack.getChildAt(position + 1);

         // 現在の位置のタブの左端座標取得
        int left = view.getLeft();

        // 現在の位置のタブの横幅
        int width = view.getWidth();
        // 現在の位置の次のタブの横幅
        int width2 = view2 == null ? width : view2.getWidth();

        // インディケータの幅
        // width2 × スライドした割合 + (width × スライドした割合 - 1)
        int indicatorWidth = (int) (width2 * positionOffset + width
                * (1 - positionOffset));
        // インディケータの左端の位置
        // 今選択中のタブの左端 + width * スライドした割合
        int indicatorLeft = (int) (left + positionOffset * width);

        // インディケータの幅と左端の位置をセット
        final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mIndicator
                .getLayoutParams();
        layoutParams.width = indicatorWidth;
        layoutParams.setMargins(indicatorLeft, 0, 0, 0);
        mIndicator.setLayoutParams(layoutParams);

        // インディケータが画面に入るように、タブの領域をスクロール
        mTrackScroller.scrollTo(indicatorLeft - mIndicatorOffset, 0);
    }
}

参考

Android Pattern Cookbook マーケットで埋もれないための差別化戦略

Android Pattern Cookbook マーケットで埋もれないための差別化戦略