こんにちは、新米Androidエンジニアの鶴田です。今回はAndroidでの「角丸のタブ」の実装方法について紹介したいと思います。デフォルト以外の形状でタブを作成したい方も是非参考にしてください。
※開発言語はKotlinです

作成するタブ

こんな感じのタブを作ります。説明しやすいように中身のタブ1個1個(タブ1やタブ2)を「子タブ」と呼ぶことにします。
Screenshot_1517295380

画面レイアウト

ActivityのレイアウトファイルではTabLayoutのみ定義しておきます。

※説明に必要ないコードは省いています

activity_main.xml

<android.support.design.widget.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="250dp"
    android:layout_height="35dp"
    android:background="@drawable/tab_background_shape"
    app:tabBackground="@null"
    app:tabIndicatorColor="@null"
    app:tabTextColor="@color/tab_item_text"
    app:tabSelectedTextColor="@color/tab_item_text_selected"" />
tablayout_shape.xml

<shape
  xmlns:android="http://schemas.android.com/apk/res/android">
  <solid 
  	android:color="@android:color/white" />
  <corners
    android:topRightRadius="20dp"
    android:bottomRightRadius="20dp"
    android:bottomLeftRadius="20dp"
    android:topLeftRadius="20dp" />
</shape>

android:background="@drawable/tablayout_shape" でタブ全体の形状を角丸にしています。

app:tabBackground は子タブの背景色・形状を指定できる属性ですが、タブ1とタブ2で形状が異なるのでここではnullにします。

MainActivity

プログラム上でタブのレイアウトを定義していきます。

MainActivity.kt

class MainActivity : FragmentActivity() {

  lateinit var tabLayout: TabLayout

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    tabLayout = findViewById(R.id.tab_layout)

    tabLayout.addTab(tabLayout.newTab().setText("タブ1"))
    tabLayout.addTab(tabLayout.newTab().setText("タブ2"))

    val firstTabView = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
    firstTabView.background = getDrawable(R.drawable.shape_first_tab_background_selected)
    val secondTabView = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(1)
    secondTabView.background = getDrawable(R.drawable.shape_second_tab_background)

    tabLayout.addOnTabSelectedListener(TabSelectedListener())
    
  }

  inner class TabSelectedListener : TabLayout.OnTabSelectedListener {
    override fun onTabSelected(tab: TabLayout.Tab?) {
      val selectedTabPosition = tab?.position
      val firstTab = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(0)
      val secondTab = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(1)

      when(selectedTabPosition) {
        0 -> {
          firstTab.background = getDrawable(R.drawable.shape_first_tab_background_selected)
          secondTab.background = getDrawable(R.drawable.shape_second_tab_background)
        }
        1 -> {
          firstTab.background = getDrawable( R.drawable.shape_first_tab_background)
          secondTab.background = getDrawable(R.drawable.shape_second_tab_background_selected)
        }
      }
    }
    override fun onTabReselected(tab: TabLayout.Tab?) {
    }

    override fun onTabUnselected(tab: TabLayout.Tab?) {
    }
  }
}

tabLayout.addTab(tabLayout.newTab()) で空の子タブをTabLayoutに追加します。今回は子タブが2個必要なので2回実行しています。
次に追加した子タブのレイアウトを定義していきます。TabLayoutから子タブのViewGroupをとりだすときは(tabLayout.getChildAt(0) as ViewGroup).getChildAt()を実行します。TabLayoutは複数の子タブを直接保持しておらず、複数の子タブを包括する一つのViewGroupを子として持っています。イメージとしてはこんな感じです。
TabLayout.001

shape_first_tab_background_selected.xml

<shape 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:shape="rectangle">
  <corners
    android:topLeftRadius="20dp"
    android:bottomLeftRadius="20dp" />
  <solid
    android:color="@android:color/holo_orange_light" />
</shape>

firstTab.background=getDrawable(R.drawable.shape_first_tab_background_selected)
で子タブの形状を定義します。角丸の度合いは親のTabLayoutの形状と合わせてください。

[追記] 2018/05/25 ConstraintLayout1.1.0よりTabItem#setCustomViewが上手く機能しなくなったのでコードを修正しました。修正後はバージョン1.0.2でも動作します。

おわりに

タブのカスタム方法について理解いただけたでしょうか?backgroundをnullにしている所にあえて色をつけてみるとどのような構造になっているか分かりやすいと思います。オリジナルタブの作成に役立ていただければ嬉しいです!

© 2018. SuperSoftware Co., Ltd. All Rights Reserved.