アニメーションでタブのカスタマイズ

## はじめに
こんなかんじのアニメーションするTabLayoutを実装してみました。

![tab_anim.gif](https://qiita-image-store.s3.amazonaws.com/0/266915/d2cb87b0-355f-683f-54a7-74be4a4cab12.gif)

**!注意!**
- この記事の著者はAndroid, Javaともに初心者です。誤字、脱字、間違いを含みます。予めご了承ください。
- 当人Android関係の勉強が必要だったため、そのまとめ、備忘録的な記事です。技術的なことは他を参照して下さい。

## TabLayout + ViewPager

TabLayout + ViewPager でメイン画面のレイアウトファイルを作ります。
TabLayout, ViewPagerの使い方について詳しくは、他の記事、公式を参照推奨です。

https://qiita.com/furu8ma/items/1602a4bbed4303fec5b1

ここではシンプルにTabLayout,ViewPagerを配置してMainActivityのほうで制御します。
おまけでタイトルバーを一番上に設置しておきました。

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.hoge.hogeapp.MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_below="@+id/titlebar"
            android:background="@color/color_white1">

            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="70dp"
                app:tabIndicatorColor="@color/color_green1"
                app:tabMode="scrollable"
                android:background="@android:color/white"
                android:elevation="4dp"
                tools:targetApi="lollipop" />

            <android.support.v4.view.ViewPager
                android:id="@+id/pager"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

        </LinearLayout>

        <include
            android:id="@+id/titlebar"
            layout="@layout/item_titlebar" />

    </RelativeLayout>
</LinearLayout>
```
<p>
Adapterを作って紐付けをします。

```MainActivity.java
MainFragmentPagerAdapter adapter = new MainFragmentPagerAdapter(getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.pager);
viewPager.setAdapter(adapter);

TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
```

<p>
Adapterの設定

```MainFragmentPagerAdapter.java
package com.hoge.hogeapp;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;


public class MainFragmentPagerAdapter extends FragmentPagerAdapter {

    public MainFragmentPagerAdapter(FragmentManager fm) { super(fm); }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return new FugaFragment();
            case 1:
                return new HogeFragment();
            default:
                return new PiyoFragment();
        }
    }

    @Override
    public CharSequence getPageTitle(int position) { return null; }

    @Override
    public int getCount() { return MainActivity.tabLength; }
}
```

タブのタイトルはActivityで設定していくので、getPageTitle()はnullを返すようにして
getItem(), getCount()をそれぞれ設定します。


## TabLayoutの中身のViewを作る

次にTabLayoutの中身にテキストとアイコンをセットしていきます。

```.java
// create TAB1
tabLayout.getTabAt(0).setCustomView(R.layout.item_tab1);
// create TAB2
tabLayout.getTabAt(1).setCustomView(R.layout.item_tab2);
                             :
                             :
```

もしくは

```.java
// create TAB1
tabLayout.getTabAt(0).setText(R.string.tab1);
tabLayout.getTabAt(0).setIcon(R.drawable.tab1Icon);
// create TAB2
tabLayout.getTabAt(1).setText(R.string.tab2);
tabLayout.getTabAt(1).setIcon(R.drawable.tab2Icon);
                             :
                             :
```

のようにtabLayoutにセットしていくのが一番楽なんですが
レイアウトファイルをたくさん作らないといけなかったり、
setText(), setIcon()するとレイアウトファイルから扱うことが出来なくなってしまうので...

今回はレイアウトファイルをinflateしてDataBindingで画像とテキストをバインドします。
その後、TabLayout.Tabの配列にバインドされたViewをセットしていきます。

```.java
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TabLayout.Tab[] tab = new TabLayout.Tab[tabLength];
// create TAB1
tab[0] = tabLayout.getTabAt(0);
View tabView = inflater.inflate(R.layout.item_tab, null);

ItemTabBinding binding = ItemTabBinding.bind(tabView);
Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.tab1Icon, null);
binding.setItemTabData(new ItemTabData(drawable, R.string.tab1));

tab[0].setCustomView(tabView);
                               :
                               :
```

面倒くさいですがなんかすごい拡張性があるような気がします。
タブごとにレイアウトファイルを用意できたり、

```.java
viewGroup = (ViewGroup) tabLayout.getChildAt(0);
View childView = viewGroup.getChildAt(tabPosition);
ImageView tabIconView1 = (ImageView) childView.findViewById(R.id.tab_icon);
TextView tabTitleView1 = (TextView) childView.findViewById(R.id.tab_title);
                               :
                               :
```

各タブ毎の子Viewのインスタンスを取得して
タブの中身のパーツごとにアニメーションを設定したりすることが出来ます。使うかどうかは別として、出来ます。

![tab_anim2.gif](https://qiita-image-store.s3.amazonaws.com/0/266915/3ed49a62-a5c4-ae9f-e05f-53cb82dd34df.gif)


こんな感じにまとめてみました。

```MainActivity.java
private View[] tabIconView;
private View tabView;
private LayoutInflater inflater;
                               :
                               :
// create tab
TabLayout.Tab[] tab = new TabLayout.Tab[tabLength];
inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
tabIconView = new View[tabLength];
for(int i = 0; i < tabLength; i++) {
    tab[i] = tabLayout.getTabAt(i);
    switch (i) {
        case 0:
            bindTabData(R.drawable.ic_cached, R.string.tab1);
            break;
        case 1:
            bindTabData(R.drawable.ic_alarm, R.string.tab2);
            break;
        case 2:
            bindTabData(R.drawable.ic_notifications, R.string.tab3);
            break;
        case 3:
            bindTabData(R.drawable.ic_android, R.string.tab4);
            break;
        default:
            break;
    }
    tab[i].setCustomView(tabView);
}
```

```MainActivity.java
// create tab dataBinding
private void bindTabData(int drawableRoot, int stringRoot) {
   tabView = inflater.inflate(R.layout.item_tab, null);

   ItemTabBinding binding = ItemTabBinding.bind(tabView);
   Drawable drawable = ResourcesCompat.getDrawable(getResources(), drawableRoot, null);
   binding.setItemTabData(new ItemTabData(drawable, getString(stringRoot)));
}
```

tabLength = 4 として、タブを４つほど作ります。
DataBindingの部分はメソッドにして、引数にバインドしたいリソースファイルを取るようにすると見易いです。

次に、タブのレイアウトファイル、DataBindingのモデルが必要なので追加します。
DataBindingについては他を参照推奨

https://qiita.com/Omoti/items/a83910a990e64f4dbdf1

```item_tab.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context="com.hoge.hogeapp.MainActivity">

    <data>
        <variable name="itemTabData" type="com.hoge.hogeapp.MainActivity.ItemTabData" />
    </data>

    <RelativeLayout
        android:id="@+id/item_tab"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="80dp"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:background="@drawable/tab_color_selector">

            <ImageView
                android:id="@+id/tab_icon"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:layout_marginTop="12dp"
                android:src="@{itemTabData.tabIcon}"
                android:layout_gravity="center" />

            <TextView
                android:id="@+id/tab_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:text="@{itemTabData.tabTitle}"
                android:layout_gravity="center"
                android:textColor="@color/color_green1"
                android:textSize="12sp" />

        </LinearLayout>
    </RelativeLayout>
</layout>
```

```MainActivity.java
public class ItemTabData {
    private Drawable tabIcon;
    private String tabTitle;

    private ItemTabData(Drawable tabIcon, String tabTitle) {
        this.tabIcon = tabIcon;
        this.tabTitle = tabTitle;
    }

    public Drawable getTabIcon() {
        return tabIcon;
    }
    public void setTabIcon(Drawable tabIcon) {
        this.tabIcon = tabIcon;
    }

    public String getTabTitle() {
        return tabTitle;
    }
    public void setTabTitle(String tabTitle) {
        this.tabTitle = tabTitle;
    }
}
```

build.gradleに追記するのを忘れずに

```build.gradle
dataBinding {
    enabled = true
}
```

ここまでで、タブの生成が完了しました。

## ListenerにAnimationの設定をする

次にアニメーションの設定を行っていきたいと思います。
とその前に、

```MainActivity.java
// get tabs instance
viewGroup = (ViewGroup) tabLayout.getChildAt(0);
for(int tabPosition = 0; tabPosition < tabLength; tabPosition++) {
    View childView = viewGroup.getChildAt(tabPosition);
    tabIconView[tabPosition] = (View) childView.findViewById(R.id.tab_icon);
}
```

各タブ毎の子ViewのidからアニメーションさせたいViewを配列で取得しておきます。
tabLayout, viewGroupはメンバ変数として宣言してアクティビティ内からアクセスできるようにしておいて下さい。

では、さっそくリスナーの設定から

```.java
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {
    }
});
```

addOnTabSelectedListenerを使用するのが一番いいみたいです。※version26.1.0以降
これで、TabLayout.OnTabSelectedListenerを拡張することが出来ます。
それでは、各コールバックにアニメーションを追加していきます。

```MainActivity.java
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        int position = tab.getPosition();
        switch (position) {
            case 0:
                Animation tabAnimation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.roll_anim);
                tabIconView[0].startAnimation(tabAnimation);
                break;
            case 1:
                Animation tabAnimation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.updown_anim);
                tabIconView[1].startAnimation(tabAnimation);
                break;
                               :
                               :
        }
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
        int position = tab.getPosition();
        switch (position) {
            case 0:
                tabIconView[0].setAnimation(null);
                break;
            case 1:
                tabIconView[1].setAnimation(null);
                break;
                               :
                               :
        }
    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {
    }
});
```

各タブが選択されたときに取得したViewに対してアニメーションを開始し、選択された状態が終了したときに停止させるといい感じなんじゃないかと思います。
まず、レイアウトファイルから扱っていきます。resフォルダの下にanimフォルダを作ってその中に作っていきます。
View Animationを使った簡単なものを実装します。サンプルとしてこんな感じで作ってみました。
アニメーションについては他を参照推奨

https://qiita.com/BingSyu/items/bd1278feab270501330b

```roll_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <rotate
        android:duration="800"
        android:fromDegrees="0"
        android:toDegrees="360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:startOffset="200"
        android:repeatMode="restart"
        android:repeatCount="-1" />
</set>
```

アクティビティから扱いたい場合は、

```.java
// composite animation
AlphaAnimation alphaAnimation = new AlphaAnimation(0.9f, 0.2f);
alphaAnimation.setRepeatCount(Animation.INFINITE);
alphaAnimation.setRepeatMode(Animation.REVERSE);

RotateAnimation rotateAnimation = new RotateAnimation(0, 360, 45, 45);
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setRepeatMode(Animation.RESTART);

AnimationSet animationSet = new AnimationSet(false);

animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(new ScaleAnimation(0.1f, 1, 0.1f, 1));
animationSet.addAnimation(new TranslateAnimation(50, 0, 150, 0));

animationSet.setDuration(3000);
iconView.startAnimation(animationSet);
```

のように、記述するとちょっと複雑な感じに複数アニメーションをセット出来ます。(Property Animationのほうが...)

ここまで出来たら大体いい感じなんですが、
最後に、選択されたタブ全体のスケールを大きくするようにしておきます。
onTabSelected, onTabUnselectedにそれぞれ次のように追記すればよいかと思います。

```.java
final View view = viewGroup.getChildAt(tabLayout.getSelectedTabPosition());
view.setScaleX(1.25F);
view.setScaleY(1.25F);
```

```.java
final View view = viewGroup.getChildAt(tabLayout.getSelectedTabPosition());
view.setScaleX(1.0F);
view.setScaleY(1.0F);
```

後はメソッド化してswitch文に並べれば、大体やりたかったこととしては完成です。お疲れ様でした。


## 最後に

<p>
...もうGradleでビルドしたくないしFlutterとか使ってみたいです

<p>
ソース
https://github.com/udzuv/AnimatingTabLayout

<p>
おわり