[JAVA] Customize tabs with animation

Introduction

I implemented TabLayout that animates like this.

tab_anim.gif

!Caution! --The author of this article is a beginner in both Android and Java. Includes typographical errors, omissions, and mistakes. Please note. ――Since it was necessary to study Android-related matters, this is a summary and memorandum article. See others for technical details.

TabLayout + ViewPager

Create a layout file for the main screen with TabLayout + ViewPager. For details on how to use TabLayout and ViewPager, refer to other articles and officials.

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

Here, simply place TabLayout and ViewPager and control them with MainActivity. As a bonus, I installed the title bar at the top.

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>

Make an Adapter and link it.

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);

Adapter settings

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; }
}

Since the tab title is set by Activity, getPageTitle () should return null. Set getItem () and getCount () respectively.

Create a View of the contents of TabLayout

Next, set the text and icon in the contents of TabLayout.

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

Or

// 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);
                             :
                             :

It's easiest to set it in tabLayout like this I have to make a lot of layout files, If you setText () and setIcon (), you will not be able to handle it from the layout file, so ...

This time, inflate the layout file and bind the image and text with DataBinding. After that, we will set the View bound to the array of TabLayout.Tab.

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);
                               :
                               :

It's a hassle, but I feel that it has some great expandability. You can prepare a layout file for each tab,

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);
                               :
                               :

Get an instance of the child View for each tab You can set the animation for each part inside the tab. You can do it whether you use it or not.

tab_anim2.gif

I tried to summarize it like this.

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)));
}

Set tabLength = 4 and create about 4 tabs. It is easy to see if you make the DataBinding part a method and take the resource file you want to bind to the argument.

Next, we need a tab layout file, a model of DataBinding, so add it. See others for Data Binding Recommended

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;
    }
}

Don't forget to add it to build.gradle

build.gradle


dataBinding {
    enabled = true
}

At this point, tab generation is complete.

Set Animation in Listener

Next, I would like to set the animation. And before that

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);
}

Get the View you want to animate from the id of the child View for each tab as an array. Declare tabLayout and viewGroup as member variables so that they can be accessed from within the activity.

Then, from the listener settings

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) {
    }
});

It seems best to use addOnTabSelectedListener. * Version 26.1.0 or later You can now extend TabLayout.OnTabSelectedListener. Now let's add an animation to each callback.

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) {
    }
});

I think it would be nice to start the animation for the View acquired when each tab is selected and stop it when the selected state ends. First, let's start with the layout file. Create an anim folder under the res folder and create it in it. Implement a simple one using View Animation. I made it as a sample like this. See others for animation Recommended

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>

If you want to handle from the activity

// 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);

You can set multiple animations to make it look a little complicated by writing. (Property Animation is better ...)

It would be nice if I could do this, Finally, try to increase the scale of the entire selected tab. I think you should add the following to onTabSelected and onTabUnselected respectively.

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

After that, if you make it into a method and arrange it in a switch statement, it is almost complete as you wanted to do. Thank you for your hard work.

Finally

... I don't want to build with Gradle anymore and I want to use Flutter

Source https://github.com/udzuv/AnimatingTabLayout

end

Recommended Posts

Customize tabs with animation
Customize View with View Modifier in Swift UI