This is the third article in the series of “from Zero meituan”. [from zero Meituan] is a high imitation of Meituan open source project, aimed at consolidating the knowledge of Android at the same time, to help partners in need. GitHub source address: github.com/cachecats/L…

Android extracts Gradle dependencies from zero to a single file

Android from zero meituan (2) – Imitation Meituan drop-down refresh custom animation

Banner+ custom View+SmartRefreshLayout pull down refresh up load more


Each project will basically have multiple tabs to provide more functionality in the limited screen space. Where there’s a demand, there’s a market, and there are a lot of great tab-switching frameworks out there that are widely used.

But after more thought, he decided to build his own wheel

The biggest problem with using a third-party framework is that it can’t fully meet the actual needs. Some icon pictures and text spacing can’t be adjusted, and some later will have various problems, which is not conducive to maintenance. The most important thing is that it is not very complicated to write one by yourself, and there is time for the research framework to fill in the holes.

Here’s how to use it: One line of code

tabWidget.init(getSupportFragmentManager(), fragmentList);
Copy the code

Then the effect picture:

Well, the name of the program is (▔, ▔)ㄏ

A, thinking

The bottom TAB layout can be implemented in many ways, such as RadioButton, FragmentTabHost, custom combined View, and so on. The custom combined View is used here, because the determinability system is higher. Basically, the sliding switch adopts ViewPager + Fragment, with simple integration and relatively mature scheme. The same is true here.

2, preparation,

Two things are needed to get started:

  1. There are a total of 10 icon pictures with five TAB selected and unselected states
  2. Five fragments

This is the most basic material, once you have the material, start to work. Because you want to achieve the selected picture and text will change to the selected state, not selected will become gray, so you need to create a selector XML file for each group of ICONS to achieve the state switch.

<? xml version="1.0" encoding="utf-8"? > <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_vector_home_pressed" android:state_activated="true" />
    <item android:drawable="@drawable/ic_vector_home_normal" android:state_activated="false" />
</selector>
Copy the code

Android :state_activated is used as the status flag because the most commonly used pressed and Focused are not pressed and restored after releasing the finger. Manually set the value of Activated in the code. Note: The icon image is set here, so android:drawable is used, which is different from android:color used for the following text.

So once you’ve set the image resource, it’s time to set the selector for the text color, because the text color has to change.

<? xml version="1.0" encoding="utf-8"? > <selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/meituanGreen" android:state_activated="true" />
    <item android:color="@color/gray666" android:state_activated="false" />
</selector>
Copy the code

Note that android:drawable is used for images and Android :color is used for text.

Three, implementation,

With all the preparation done, we can start to customize the View.

1. Write a layout

The first is the layout file:

widget_custom_bottom_tab.xml

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_tab_widget"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/ > <! > <LinearLayout Android: Layout_width ="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="3dp"
        android:paddingTop="3dp"
        >

        <LinearLayout
            android:id="@+id/ll_menu_home_page"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_menu_home"
                style="@style/menuIconStyle"
                android:src="@drawable/selector_icon_menu_home" />

            <TextView
                android:id="@+id/tv_menu_home"
                style="@style/menuTextStyle"
                android:text="Home page" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_menu_nearby"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_menu_nearby"
                style="@style/menuIconStyle"
                android:src="@drawable/selector_icon_menu_nearby" />

            <TextView
                android:id="@+id/tv_menu_nearby"
                style="@style/menuTextStyle"
                android:text="Near" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_menu_discover"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_menu_discover"
                style="@style/menuIconStyle"
                android:src="@drawable/selector_icon_menu_discover" />

            <TextView
                android:id="@+id/tv_menu_discover"
                style="@style/menuTextStyle"
                android:text="Discovered" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_menu_order"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_menu_order"
                style="@style/menuIconStyle"
                android:src="@drawable/selector_icon_menu_order" />

            <TextView
                android:id="@+id/tv_menu_order"
                style="@style/menuTextStyle"
                android:text="Order" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_menu_mine"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_menu_mine"
                style="@style/menuIconStyle"
                android:src="@drawable/selector_icon_menu_mine" />

            <TextView
                android:id="@+id/tv_menu_mine"
                style="@style/menuTextStyle"
                android:text="I" />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>
Copy the code

The outermost layer is wrapped with a vertical LinearLayout, which has two child nodes, above which is the ViewPager for sliding and loading the Fragment, and below which is the layout of the five tabs. To facilitate management, several common attributes of ImageView and TextView have been extracted into styles. XML:

<! < span style = "box-sizing: border-box! Important; word-wrap: break-word! Important"menuIconStyle" >
        <item name="android:layout_width">25dp</item>
        <item name="android:layout_height">25dp</item> </style> <! < span style = "box-sizing: border-box! Important; word-wrap: break-word! Important"menuTextStyle">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textColor">@drawable/selector_menu_text_color</item>
        <item name="android:textSize">12sp</item>
        <item name="android:layout_marginTop">3dp</item>
    </style>
Copy the code

Now that you have the layout file, it’s time to actually customize the View.

2. Write Java code to customize the View

Create a new Java file, CustomBottomTabWidget, that inherits from The LinearLayout. Why inherit LinearLayout? Because the root node of our layout file is the LinearLayout, which inherits whatever the root node is.

Let’s start with the code:

package com.cachecats.meituan.widget.bottomtab; import android.content.Context; import android.support.annotation.Nullable; import android.support.v4.app.FragmentManager; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import com.cachecats.meituan.R; import com.cachecats.meituan.base.BaseFragment; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; public class CustomBottomTabWidget extends LinearLayout { @BindView(R.id.ll_menu_home_page) LinearLayout llMenuHome; @BindView(R.id.ll_menu_nearby) LinearLayout llMenuNearby; @BindView(R.id.ll_menu_discover) LinearLayout llMenuDiscover; @BindView(R.id.ll_menu_order) LinearLayout llMenuOrder; @BindView(R.id.ll_menu_mine) LinearLayout llMenuMine; @BindView(R.id.vp_tab_widget) ViewPager viewPager; private FragmentManager mFragmentManager; private List<BaseFragment> mFragmentList; private TabPagerAdapter mAdapter; public CustomBottomTabWidget(Context context) { this(context, null, 0); } public CustomBottomTabWidget(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public CustomBottomTabWidget(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); View view = View.inflate(context, R.layout.widget_custom_bottom_tab, this); ButterKnife.bind(view); // Set the default option selectTab(menutab.home); } /** * External call initialization, Pass the necessary arguments * * @param FM */ public void init(FragmentManager FM, List<BaseFragment> fragmentList) {mFragmentManager = FM; mFragmentList = fragmentList; initViewPager(); } /** * initialize ViewPager */ private voidinitViewPager() {
        mAdapter = new TabPagerAdapter(mFragmentManager, mFragmentList);
        viewPager.setAdapter(mAdapter);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, floatpositionOffset, Int positionOffsetPixels) {} @override public void onPageSelected(int position) {// Connect ViewPager to TAB switch (position) {case 0:
                        selectTab(MenuTab.HOME);
                        break;
                    case 1:
                        selectTab(MenuTab.NEARBY);
                        break;
                    case 2:
                        selectTab(MenuTab.DISCOVER);
                        break;
                    case 3:
                        selectTab(MenuTab.ORDER);
                        break;
                    case 4:
                        selectTab(MenuTab.MINE);
                        break;
                    default:
                        selectTab(MenuTab.HOME);
                        break; } } @Override public void onPageScrollStateChanged(int state) { } }); } @onclick ({r.d.l_menu_home_page, R.D.l_menu_nearby, R.D.l_menu_Discover, R.D.l_menu_order, R.id.ll_menu_mine}) public void onViewClicked(View view) { switch (view.getId()) {caseR.id.ll_menu_home_page: selectTab(MenuTab.HOME); // Make ViewPager follow TAB click event slide viewpager. setCurrentItem(0);break;
            case R.id.ll_menu_nearby:
                selectTab(MenuTab.NEARBY);
                viewPager.setCurrentItem(1);
                break;
            case R.id.ll_menu_discover:
                selectTab(MenuTab.DISCOVER);
                viewPager.setCurrentItem(2);
                break;
            case R.id.ll_menu_order:
                selectTab(MenuTab.ORDER);
                viewPager.setCurrentItem(3);
                break;
            case R.id.ll_menu_mine:
                selectTab(MenuTab.MINE);
                viewPager.setCurrentItem(4);
                break; }} public void selectTab(MenuTab Tab) {public void selectTab(MenuTab Tab) { Separately set TAB unCheckedAll() to be selected; switch (tab) {case HOME:
                llMenuHome.setActivated(true);
                break;
            case NEARBY:
                llMenuNearby.setActivated(true);
                break;
            case DISCOVER:
                llMenuDiscover.setActivated(true);
                break;
            case ORDER:
                llMenuOrder.setActivated(true);
                break;
            case MINE:
                llMenuMine.setActivated(true); }} // Uncheck private void for all tabsunCheckedAll() {
        llMenuHome.setActivated(false);
        llMenuNearby.setActivated(false);
        llMenuDiscover.setActivated(false);
        llMenuOrder.setActivated(false);
        llMenuMine.setActivated(false); Public enum MenuTab {HOME, NEARBY, DISCOVER, ORDER, MINE}}Copy the code

The comments should be clear, but here are a few more points:

  1. Three constructors are implemented, each corresponding to a different creation style. If you’re not sure how to create it, just implement it. You can’t go wrong. Since I’m not sure which method to go, where to write the initialization method? Here’s a little trick, which is to take a parametersuper(context), and of two parameterssuper(context, attrs)Change into:this(context, null, 0)this(context, attrs, 0). So whatever constructor we go, we end up in a three-parameter constructor, and we just put the initialization in that constructor.
  2. This line in the constructor:
    View view = View.inflate(context, R.layout.widget_custom_bottom_tab, this);
    Copy the code

    willwidget_custom_bottom_tab.xmlThe file is bound to Java code, and notice that the last parameter isthisRather thannull.

  3. This project uses itButterKnifefindViewById()Get out.
  4. The principle of toggling the unchecked state is that each click is called firstunCheckedAll ()Set all tabs to unselected, and set the selected TAB to selectedllMenuHome.setActivated(true);
  5. Implement TAB click events withViewPager1) Execute the following two lines of code in the TAB click callback to make the TAB selected and let, respectivelyViewPagerSlide to position.
    selectTab(MenuTab.HOME); // Make ViewPager follow TAB click event slide viewpager. setCurrentItem(0);Copy the code

    2) inViewPagerListening method ofonPageSelected()Each time you swipe to a page, theselectTab(MenuTab.HOME)Method to set the corresponding TAB to the selected state.

  6. Remember to set the default selection in the constructor:
    // Set the default option selectTab(menutab.home);Copy the code

Ok, so we’re done customizing the View. Here’s how to use it.

Four, the use of

Quote directly from the home page layout file:

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.cachecats.meituan.app.MainActivity">

    <com.cachecats.meituan.widget.bottomtab.CustomBottomTabWidget
        android:id="@+id/tabWidget"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
Copy the code

Then call the Activity in one sentence:

tabWidget.init(getSupportFragmentManager(), fragmentList);
Copy the code

It’s that simple! Isn’t it nice and fresh?

Post the complete code for MainActivity:

package com.cachecats.meituan.app;

import android.os.Bundle;

import com.cachecats.meituan.MyApplication;
import com.cachecats.meituan.R;
import com.cachecats.meituan.app.discover.DiscoverFragment;
import com.cachecats.meituan.app.home.HomeFragment;
import com.cachecats.meituan.app.mine.MineFragment;
import com.cachecats.meituan.app.nearby.NearbyFragment;
import com.cachecats.meituan.app.order.OrderFragment;
import com.cachecats.meituan.base.BaseActivity;
import com.cachecats.meituan.base.BaseFragment;
import com.cachecats.meituan.di.DIHelper;
import com.cachecats.meituan.di.components.DaggerActivityComponent;
import com.cachecats.meituan.di.modules.ActivityModule;
import com.cachecats.meituan.widget.bottomtab.CustomBottomTabWidget;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends BaseActivity {

    @BindView(R.id.tabWidget)
    CustomBottomTabWidget tabWidget;
    private List<BaseFragment> fragmentList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); ButterKnife.bind(this); DaggerActivityComponent.builder() .applicationComponent(MyApplication.getApplicationComponent()) .activityModule(new ActivityModule(this)) .build().inject(this); // initialize init(); } private voidinit() {// Construct a collection of fragments fragmentList = new ArrayList<>(); fragmentList.add(new HomeFragment()); fragmentList.add(new NearbyFragment()); fragmentList.add(new DiscoverFragment()); fragmentList.add(new OrderFragment()); fragmentList.add(new MineFragment()); / / initialize CustomBottomTabWidget tabWidget. Init (getSupportFragmentManager (), fragmentList); }}Copy the code

The entire code is simple, just construct the Fragment list and pass it to the CustomBottomTabWidget.

Conclusion: It may take some time to build the wheel in the early stage, but the code written by yourself is the most clear. After a few months, the code can be quickly located to the place to be changed, which is easy to maintain. And the final package is also very simple to use ah, do not have to write so much configuration code in the Activity, the overall logic is clearer, lower coupling degree.


The above is to use custom View way to achieve highly customized multi-tab TAB sliding switch example. Source code address: github.com/cachecats/L… Welcome to download, welcome to star, welcome to like ~