Android animation bottom navigation bar

final effect:

Description of Requirement:

As shown in the figure above, the bottom is an unfixed number of tabs, and the arrangement is that the remaining space is evenly divided. When you click to switch tabs, the selected tab will follow the animation and display the content of the corresponding tab

Demand split:

1. Regarding animation, the implementation method can be to load gif files; you can also use lottie to load json files. The advantage of lottie is that the pixel loading is clear enough. If lottie encounters loading problems, it is recommended to upgrade the lottie library to the latest version.

2. Regarding uniform distribution, you can use the chainStyle property of ConstraintLayout to implement spread; you can also use LinearLayout to add an empty space control between each tab, the weight is 1, and the remaining space is equally divided.

3. There are many ways to implement tab switching. This article uses a custom view, which is better for customization. Others can also use Materail Design’s BottomNavigationView or TabLayout, but the customization requirements are more limited, such as some animation effects and font styles. Wait

4. Regarding the display of the content above the tab, it is usually achieved by switching different fragments to achieve tab switching to load different content

Demand realization:

Import the required libraries:

implementation 'com.airbnb.android:lottie:3.4.0'
implementation 'com.github.bumptech.glide:glide:3.8.0'

1. Custom tabItem items

Before customizing the View, define the data structure required to load the View. The elements required to display include the unique identifier of a tab, the dynamic image resource, the static image resource, and the tab name. You can define an interface to implement it.

public interface TabItem {

    String getName();

    @DrawableRes
    int getStaticRes();

    @DrawableRes
    int getAnimateRes();

    String getTabType();
}

Implementation of custom View:

public class TabItemView extends FrameLayout {
    private Context mContext;
    private TextView mTabNameView;
    private LottieAnimationView mTabLottieView;
    private String mTabName;
    private int mTabStaticRes;
    private int mTabAnimateRes;
    private boolean isSelected;
    private int mSelectedTextColor;
    private int mUnSelectedTextColor;
    private int mTextSize;
    private int mIconSize;

    public TabItemView(@NonNull Context context) {
        this(context, null);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public TabItemView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.TabItemView);
        mTextSize = a.getDimensionPixelSize(R.styleable.TabItemView_textSize, 10);
        mIconSize = a.getDimensionPixelSize(R.styleable.TabItemView_iconSize, 40);
        mSelectedTextColor = a.getColor(R.styleable.TabItemView_selectedTextColor, getResources().getColor(R.color.tab_selected));
        mUnSelectedTextColor = a.getColor(R.styleable.TabItemView_unSelectedTextColor, getResources().getColor(R.color.tab_unselected));
        mTabStaticRes = a.getResourceId(R.styleable.TabItemView_mTabStaticRes, 0);
        mTabAnimateRes = a.getResourceId(R.styleable.TabItemView_mTabAnimateRes, 0);
        mTabName = a.getString(R.styleable.TabItemView_tabName);
        a.recycle();
        init();
    }

    private void init() {
        View view = LayoutInflater.from(mContext).inflate(R.layout.bottom_tab_view_item, this, false);
        mTabNameView = view.findViewById(R.id.tab_name);
        mTabLottieView = view.findViewById(R.id.tab_animation_view);
        addView(view);
    }

    private void setSelectedUI() {
        if (mTabAnimateRes == 0) {
            throw new NullPointerException("animation resource must be not empty");
        } else {
            mTabNameView.setTextColor(mSelectedTextColor);
            mTabLottieView.setAnimation(mTabAnimateRes);
            mTabLottieView.playAnimation();
        }
    }

    private void setUnselectedUI() {
        mTabNameView.setTextColor(mUnSelectedTextColor);
        mTabLottieView.clearAnimation();
        // 没有找到静态图,就选择加载gif图的第一帧,这里可以选择mipmap下的静图
        Glide.with(mContext).load(mTabStaticRes).asBitmap().into(mTabLottieView);
    }

    public void updateUI() {
        if (isSelected) {
            setSelectedUI();
        } else {
            setUnselectedUI();
        }
    }

    public TabItemView setIconSize(int iconSize) {
        if (iconSize > 0) {
            mIconSize = iconSize;
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mTabLottieView.getLayoutParams();
            lp.width = mIconSize;
            lp.height = mIconSize;
            mTabLottieView.setLayoutParams(lp);
        }
        return this;
    }

    public TabItemView setStaticIcon(int staticRes) {
        mTabStaticRes = staticRes;
        return this;
    }

    public TabItemView setAnimateIcon(int animateRes) {
        mTabAnimateRes = animateRes;
        return this;
    }

    public TabItemView setTabName(String tabName) {
        mTabName = tabName;
        mTabNameView.setText(mTabName);
        return this;
    }

    public TabItemView setTextColor(int selectColor, int unSelectColor) {
        this.mSelectedTextColor = selectColor;
        this.mUnSelectedTextColor = unSelectColor;
        return this;
    }

    public TabItemView setTabSelected(boolean isSelected) {
        this.isSelected = isSelected;
        updateUI();
        return this;
    }

    public TabItemView setTabTextSize(int textSize) {
        this.mTextSize = textSize;
        mTabNameView.setTextSize(mTextSize);
        return this;
    }
}

2. Encapsulate the loading of item items

public class BottomTabNavigation extends LinearLayout implements View.OnClickListener {
    public static final String TAB_NONE = "none";
    public static final String TAB_HOME = "home";
    public static final String TAB_CLOUD = "cloud";
    public static final String TAB_EXCHANGE = "exchange";
    public static final String TAB_USER_CENTER = "user_center";
    private Context mContext;
    private List<TabItem> tabList = new ArrayList<>();
    private TabSelectedListener tabSelectedListener;
    private String selectedTabType = TAB_NONE;

    @StringDef({TAB_HOME, TAB_CLOUD, TAB_EXCHANGE, TAB_USER_CENTER, TAB_NONE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface BottomTabType {
    }

    public BottomTabNavigation(@NonNull Context context) {
        this(context, null);
    }

    public BottomTabNavigation(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BottomTabNavigation(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public BottomTabNavigation(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
        setOrientation(LinearLayout.HORIZONTAL);
    }

    public void setTabItems(List<TabItem> tabList) {
        removeAllViews();
        this.tabList = tabList;
        if (null == tabList || tabList.size() == 0)
            return;
        addView(getSpaceView());
        for (int i = 0; i < tabList.size(); i++) {
            TabItem tab = tabList.get(i);
            TabItemView tabItemView = new TabItemView(mContext)
                    .setTabName(tab.getName())
                    .setStaticIcon(tab.getStaticRes())
                    .setAnimateIcon(tab.getAnimateRes());
            tabItemView.setOnClickListener(this);
            tabItemView.setTag(tab.getTabType());
            addView(tabItemView);
            addView(getSpaceView());
        }

        if (selectedTabType.equals(TAB_NONE)) {
            getChildAt(1).performClick();
        }
    }

    private Space getSpaceView() {
        Space space = new Space(mContext);
        space.setLayoutParams(new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1));
        return space;
    }

    @Override
    public void onClick(View v) {
        if (null == v.getTag())
            return;
        String tabType = (String) v.getTag();
        if (tabType.equals(selectedTabType))
            return;
        if (null != tabSelectedListener) {
            this.selectedTabType = tabType;
            for (int i = 0; i < tabList.size(); i++) {
                TabItem tab = tabList.get(i);
                if (tabType.equals(tab.getTabType())) {
                    TabItemView tabView = (TabItemView) v;
                    tabView.setTabSelected(true);
                    tabSelectedListener.tabSelected(tabView);
                } else {
                    TabItemView tabView = (TabItemView) getChildAt(i * 2 + 1);
                    tabSelectedListener.tabUnselected(tabView);
                    tabView.setTabSelected(false);
                }
            }
        }
    }

    public void addOnTabSelectedListener(TabSelectedListener selectedListener){
        this.tabSelectedListener = selectedListener;
    }

    public interface TabSelectedListener {
        void tabSelected(TabItemView itemView);

        void tabUnselected(TabItemView itemView);
    }
}

3. Use

public class MainActivity extends AppCompatActivity {
    private HomeFragment homeFragment;
    private UserCenterFragment userCenterFragment;
    private ExchangeFragment exchangeFragment;
    private CloudFragment cloudFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initFragment();
        BottomTabNavigation navigation = findViewById(R.id.bottom_navigation);
        navigation.addOnTabSelectedListener(new BottomTabNavigation.TabSelectedListener() {
            @Override
            public void tabSelected(TabItemView itemView) {
                displayFragment((String) itemView.getTag());
            }

            @Override
            public void tabUnselected(TabItemView itemView) {

            }
        });
        navigation.setTabItems(getTabList());
    }

    private void initFragment() {
        homeFragment = new HomeFragment();
        cloudFragment = new CloudFragment();
        exchangeFragment = new ExchangeFragment();
        userCenterFragment = new UserCenterFragment();
        FragmentManager manager = getSupportFragmentManager();
        manager.beginTransaction()
                .add(R.id.fl_container, homeFragment)
                .add(R.id.fl_container, exchangeFragment)
                .add(R.id.fl_container, cloudFragment)
                .add(R.id.fl_container, userCenterFragment)
                .commitAllowingStateLoss();
    }

    private List<TabItem> getTabList() {
        List<TabItem> tabs = new ArrayList<>();
        tabs.add(new BottomTab("首页", BottomTabNavigation.TAB_HOME));
        tabs.add(new BottomTab("云存储", BottomTabNavigation.TAB_CLOUD));
        tabs.add(new BottomTab("交易", BottomTabNavigation.TAB_EXCHANGE));
        tabs.add(new BottomTab("我的", BottomTabNavigation.TAB_USER_CENTER));
        return tabs;
    }

    private void displayFragment(String selectTabType) {
        switch (selectTabType) {
            case BottomTabNavigation.TAB_HOME:
            default:
                getSupportFragmentManager().beginTransaction()
                        .show(homeFragment)
                        .hide(exchangeFragment)
                        .hide(cloudFragment)
                        .hide(userCenterFragment)
                        .commitAllowingStateLoss();
                break;
            case BottomTabNavigation.TAB_CLOUD:
                getSupportFragmentManager().beginTransaction()
                        .show(cloudFragment)
                        .hide(exchangeFragment)
                        .hide(homeFragment)
                        .hide(userCenterFragment)
                        .commitAllowingStateLoss();
                break;
            case BottomTabNavigation.TAB_EXCHANGE:
                getSupportFragmentManager().beginTransaction()
                        .show(exchangeFragment)
                        .hide(homeFragment)
                        .hide(cloudFragment)
                        .hide(userCenterFragment)
                        .commitAllowingStateLoss();
                break;
            case BottomTabNavigation.TAB_USER_CENTER:
                getSupportFragmentManager().beginTransaction()
                        .show(userCenterFragment)
                        .hide(exchangeFragment)
                        .hide(cloudFragment)
                        .hide(homeFragment)
                        .commitAllowingStateLoss();
                break;
        }
    }
}

other problems:

1. This article is based on the effect achieved by referring to https://blog.csdn.net/yiranhaiziqi/article/details/88965548 , thank you for this

2. About the animation resources used in this article come from the animation resources provided by lottie, the specific address is https://lottiefiles.com/user/475858 , you can also download other resources by yourself , you can download gif or json files, thanks for the animation Resources provided by the author; 

3. The static image in the non-selected state cannot be found, and the demo is implemented by loading only the first frame of the gif image

4. Encountered a crash caused by lottie animation [java.lang.IllegalStateException: Missing values ​​for keyframe]

Please upgrade the lottie animation library to above 3.0, see https://www.freesion.com/article/1000623784/ for details

github address:

https://github.com/Jane205/BottomNavigation

Guess you like

Origin blog.csdn.net/qingwangwang/article/details/108698037