2014年12月9日火曜日

Android Material Design な TabHost + ViewPager に移行する

前回のエントリ「Android Material Design のタブの仕様まとめ」で紹介した Material Design の Fixed Tabs full-screen width を TabHost で実装してみます。

Activity 生成ウィザードの Tabbed Activity で Swipe Views(ViewPager)を選択して作成される Activity に付け足していきます。

*以下の例は minSdkVersion = 21 としています。


0. テーマに色をセット

メインカラーとアクセントカラーをセットしておきます

res/values-v21/styles.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">  
  4.         <item name="android:colorPrimaryDark">#00695C</item>  
  5.         <item name="android:colorPrimary">#00897B</item>  
  6.         <item name="android:colorAccent">#FFD54F</item>  
  7.     </style>  
  8. </resources>  



1. 普通に TabHost を実装する
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     tools:context=".MainActivity">  
  8.   
  9.     <TabHost  
  10.         android:id="@android:id/tabhost"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="wrap_content"  
  13.         android:background="?android:attr/colorPrimary">  
  14.   
  15.         <TabWidget  
  16.             android:id="@android:id/tabs"  
  17.             android:layout_width="match_parent"  
  18.             android:layout_height="wrap_content" />  
  19.   
  20.         <FrameLayout  
  21.             android:id="@android:id/tabcontent"  
  22.             android:layout_width="0dp"  
  23.             android:layout_height="0dp" />  
  24.   
  25.     </TabHost>  
  26.   
  27.     <android.support.v4.view.ViewPager  
  28.         android:id="@+id/pager"  
  29.         android:layout_width="match_parent"  
  30.         android:layout_height="match_parent" />  
  31. </LinearLayout>  
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     super.onCreate(savedInstanceState);  
  4.     setContentView(R.layout.activity_main);  
  5.   
  6.     // Create the adapter that will return a fragment for each of the three  
  7.     // primary sections of the activity.  
  8.     mSectionsPagerAdapter = new SectionsPagerAdapter(getFragmentManager());  
  9.   
  10.     // Set up the ViewPager with the sections adapter.  
  11.     mViewPager = (ViewPager) findViewById(R.id.pager);  
  12.     mViewPager.setAdapter(mSectionsPagerAdapter);  
  13.   
  14.     TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost);  
  15.     tabHost.setup();  
  16.   
  17.     for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {  
  18.         tabHost.addTab(tabHost  
  19.                 .newTabSpec(String.valueOf(i))  
  20.                 .setIndicator(mSectionsPagerAdapter.getPageTitle(i))  
  21.                 .setContent(android.R.id.tabcontent));  
  22.     }  
  23.   
  24.     tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {  
  25.         @Override  
  26.         public void onTabChanged(String tabId) {  
  27.             mViewPager.setCurrentItem(Integer.valueOf(tabId));  
  28.         }  
  29.     });  
  30.   
  31.     mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener(){  
  32.         @Override  
  33.         public void onPageSelected(int position) {  
  34.             super.onPageSelected(position);  
  35.             tabHost.setCurrentTab(position);  
  36.         }  
  37.     });  
  38. }  


基本的なタブ機能が実装できましたが、Material Design に沿ったタブにするにはいくつか修正が必要です。
  • タブ間の区切り線を消す
  • Action Bar と タブの間の影を消す
  • タブの下に影を出す
  • タブの文字を仕様に合わせる
  • ViewPager のスクロールに応じてインディケータもスクロールさせる


2. タブ間の区切り線を消す
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     ...  
  4.   
  5.     TabWidget tabWidget = (TabWidget) findViewById(android.R.id.tabs);  
  6.     // タブ間の区切り線を消す  
  7.     tabWidget.setStripEnabled(false);  
  8.     tabWidget.setShowDividers(LinearLayout.SHOW_DIVIDER_NONE);  
  9. }  



3. Action Bar と タブの間の影を消す
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     ...  
  4.   
  5.     // Action Bar と タブの間の影を消す  
  6.     getActionBar().setElevation(0);  
  7. }  



4. タブの下に影を出す
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     ...  
  4.   
  5.     // タブの下に影を出す  
  6.     // App bar の elevation は 4dp  
  7.     // http://www.google.com/design/spec/what-is-material/objects-in-3d-space.html#objects-in-3d-space-elevation  
  8.     float elevation = 4 * getResources().getDisplayMetrics().density;  
  9.     tabHost.setElevation(elevation);  
  10. }  



5. タブの文字を仕様に合わせる

res/color/tab_widget_text.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">  
  3.     <item android:color="#ffffffff" android:state_selected="true" />  
  4.     <item android:color="#99ffffff" />  
  5. </selector>  
res/layout/tab_widget.xml
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <TextView xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="0dp"  
  5.     android:layout_height="match_parent"  
  6.     android:layout_weight="1"  
  7.     android:gravity="bottom|center_horizontal"  
  8.     android:paddingBottom="16dp"  
  9.     android:textColor="@color/tab_widget_text"  
  10.     android:textSize="14sp"  
  11.     tools:layout_width="match_parent"  
  12.     tools:text="group 1 ITEM TWO" />  
  1. @Override  
  2. protected void onCreate(Bundle savedInstanceState) {  
  3.     ...  
  4.   
  5.     LayoutInflater inflater = LayoutInflater.from(this);  
  6.     for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {  
  7.         TextView tv = (TextView) inflater.inflate(R.layout.tab_widget, tabWidget, false);  
  8.         tv.setText(mSectionsPagerAdapter.getPageTitle(i));  
  9.   
  10.         tabHost.addTab(tabHost  
  11.                 .newTabSpec(String.valueOf(i))  
  12.                 .setIndicator(tv)  
  13.                 .setContent(android.R.id.tabcontent));  
  14.     }  
  15.   
  16.     ...  
  17. }  



6. ViewPager のスクロールに応じてインディケータもスクロールさせる
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical"  
  7.     tools:context=".MainActivity">  
  8.   
  9.     <TabHost  
  10.         android:id="@android:id/tabhost"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="wrap_content"  
  13.         android:background="?android:attr/colorPrimary">  
  14.   
  15.         ...  
  16.   
  17.         <View  
  18.             android:id="@+id/indicator"  
  19.             android:layout_width="match_parent"  
  20.             android:layout_height="2dp"  
  21.             android:layout_gravity="bottom"  
  22.             android:background="?android:attr/colorControlActivated" />  
  23.   
  24.     </TabHost>  
  25.   
  26.     ...  
  27. </LinearLayout>  
  1. View indicator;  
  2. TabWidget tabWidget;  
  3.   
  4. @Override  
  5. protected void onCreate(Bundle savedInstanceState) {  
  6.     ...  
  7.   
  8.     tabWidget = (TabWidget) findViewById(android.R.id.tabs);  
  9.     ...  
  10.   
  11.     indicator = findViewById(R.id.indicator);  
  12.     mViewPager.setOnPageChangeListener(new PageChangeListener());  
  13. }  
  14.   
  15. private class PageChangeListener implements ViewPager.OnPageChangeListener {  
  16.     private int scrollingState = ViewPager.SCROLL_STATE_IDLE;  
  17.   
  18.     @Override  
  19.     public void onPageSelected(int position) {  
  20.         // スクロール中はonPageScrolled()で描画するのでここではしない  
  21.         if (scrollingState == ViewPager.SCROLL_STATE_IDLE) {  
  22.             updateIndicatorPosition(position, 0);  
  23.         }  
  24.         tabWidget.setCurrentTab(position);  
  25.     }  
  26.   
  27.     @Override  
  28.     public void onPageScrollStateChanged(int state) {  
  29.         scrollingState = state;  
  30.     }  
  31.   
  32.     @Override  
  33.     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {  
  34.         updateIndicatorPosition(position, positionOffset);  
  35.     }  
  36.   
  37.     private void updateIndicatorPosition(int position, float positionOffset) {  
  38.         View tabView = tabWidget.getChildTabViewAt(position);  
  39.         int indicatorWidth = tabView.getWidth();  
  40.         int indicatorLeft = (int) ((position + positionOffset) * indicatorWidth);  
  41.   
  42.         final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) indicator.getLayoutParams();  
  43.         layoutParams.width = indicatorWidth;  
  44.         layoutParams.setMargins(indicatorLeft, 000);  
  45.         indicator.setLayoutParams(layoutParams);  
  46.     }  
  47. }  



2 件のコメント:

  1. このコメントは投稿者によって削除されました。

    返信削除
  2. SectionsPagerAdapterに、シンボルがありません。と出ます。どこで設定すればいいでしょうか?

    返信削除