Android UI进阶之旅8--Material Design之TabLayout

TabLayout的基本使用

以前我们是用TabHost来实现Tab切换的效果。现在谷歌推荐使用TabLayout。

下面以TabLayout+ViewPager+Fragment为例,讲述TabLayout的基本使用。

布局文件如下面所示:



    

    


其中,需要关注的属性有:

app:tabIndicatorColor="@color/colorPrimary_pink"//指示器的颜色
app:tabTextColor="@color/colorPrimary_pink"//tab的文字颜色
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"//选中的tab的文字颜色
app:tabMode="fixed"//scrollable:可滑动;fixed:不能滑动,平分tabLayout宽度
app:tabGravity="center"// fill:tab平均填充整个宽度;center:tab居中显示

需要切换的Fragment,为了方便,我们重用一个Fragment:

public class NewsDetailFragment extends Fragment {
    
    @Override
    @Nullable
    public View onCreateView(LayoutInflater inflater,
            @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        TextView tv = new TextView(getContext());
        Bundle bundle = getArguments();
        String title = bundle.getString("title");
        tv.setBackgroundColor(Color.rgb((int)(Math.random()*255), (int)(Math.random()*255), (int)(Math.random()*255)));
        tv.setText(title);
        return tv;
    }

}

Activity的代码:

public class TabLayoutActivity extends AppCompatActivity {

    private TabLayout tabLayout;
    private String[] title = {
            "头条",
            "新闻",
            "娱乐",
            "体育",
            "科技",
            "美女",
            "财经",
            "汽车",
            "房子",
            "头条"
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab_layout);

        final ViewPager viewPager = (ViewPager) findViewById(R.id.vp);
        tabLayout = (TabLayout) findViewById(R.id.tablayout);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());


        //1.TabLayout和Viewpager关联
//        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
//
//            @Override
//            public void onTabUnselected(TabLayout.Tab arg0) {
//
//            }
//
//            @Override
//            public void onTabSelected(TabLayout.Tab tab) {
//                // 被选中的时候回调
//                viewPager.setCurrentItem(tab.getPosition(), true);
//            }
//
//            @Override
//            public void onTabReselected(TabLayout.Tab tab) {
//
//            }
//        });
        //2.ViewPager滑动关联tabLayout
//        viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        //设置tabLayout的标签来自于PagerAdapter
//        tabLayout.setTabsFromPagerAdapter(adapter);


        //设置tabLayout的标签来自于PagerAdapter
        tabLayout.setupWithViewPager(viewPager);

        viewPager.setAdapter(adapter);

        //设置Indicator的左右间距(Indicator的宽度)
        setIndicator(this, tabLayout, 15, 15);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public CharSequence getPageTitle(int position) {
            return title[position];
        }

        @Override
        public Fragment getItem(int position) {
            Fragment f = new NewsDetailFragment();
            Bundle bundle = new Bundle();
            bundle.putString("title", title[position]);
            f.setArguments(bundle);
            return f;
        }

        @Override
        public int getCount() {
            return title.length;
        }

    }

    //下面三个方法是设置Indicator

    public static void setIndicator(Context context, TabLayout tabs, int leftDip, int rightDip) {
        Class tabLayout = tabs.getClass();
        Field tabStrip = null;
        try {
            tabStrip = tabLayout.getDeclaredField("mTabStrip");
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

        tabStrip.setAccessible(true);
        LinearLayout ll_tab = null;
        try {
            ll_tab = (LinearLayout) tabStrip.get(tabs);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        int left = (int) (getDisplayMetrics(context).density * leftDip);
        int right = (int) (getDisplayMetrics(context).density * rightDip);

        for (int i = 0; i < ll_tab.getChildCount(); i++) {
            View child = ll_tab.getChildAt(i);
            child.setPadding(0, 0, 0, 0);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
            params.leftMargin = left;
            params.rightMargin = right;
            child.setLayoutParams(params);
            child.invalidate();
        }
    }

    public static DisplayMetrics getDisplayMetrics(Context context) {
        DisplayMetrics metric = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
        return metric;
    }

    public static float getPXfromDP(float value, Context context) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value,
                context.getResources().getDisplayMetrics());
    }
}

代码比较简单,需要注意的是,新提供的tabLayout.setupWithViewPager(viewPager);方法代替了注释中的3个方法了,其实内部做的事都是一样的。TabLayout默认没有提供修改Indicator宽度的函数,需要我们通过反射的方式去设置。

用TabLayout实现底部导航(相对于传统的TabHost,它是可滑动的)

只需要三个步骤:

  1. 在布局中就把TabLayout放在布局底部
  2. 去掉底部的indicator,app:tabIndicatorHeight="0dp"
  3. 实现自己的效果,自定义的标签布局

代码如下:

for (int i = 0; i < tabLayout.getTabCount(); i++) {
    TabLayout.Tab tab = tabLayout.getTabAt(i);
    tab.setCustomView(view);
}

TabLayout源码分析

TabLayout是可以水平滑动的,因此继承了HorizontalScrollView

public class TabLayout extends HorizontalScrollView {

}

TabLayout里面有那么多小tab,tab有分为是否填充,是否可滑动,那么测量的时候就会很复杂:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //推荐的高度     
    final int idealHeight = dpToPx(getDefaultHeight()) + getPaddingTop() + getPaddingBottom();
    switch (MeasureSpec.getMode(heightMeasureSpec)) {
        case MeasureSpec.AT_MOST:
            //最大值模式下,最终的高度是推荐的高度与所给的高度的最小值
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                    Math.min(idealHeight, MeasureSpec.getSize(heightMeasureSpec)),
                    MeasureSpec.EXACTLY);
            break;
        case MeasureSpec.UNSPECIFIED:
            //没有指定的模式下,最终的高度就是推荐的高度
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(idealHeight, MeasureSpec.EXACTLY);
            break;
    }

    final int specWidth = MeasureSpec.getSize(widthMeasureSpec);
    if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) {
        // If we don't have an unspecified width spec, use the given size to calculate
        // the max tab width
        mTabMaxWidth = mRequestedTabMaxWidth > 0
                ? mRequestedTabMaxWidth
                : specWidth - dpToPx(TAB_MIN_WIDTH_MARGIN);
    }

    // 父容器(TabLayout)测量自身
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    if (getChildCount() == 1) {
        // so we don't scroll
        final View child = getChildAt(0);
        boolean remeasure = false;

        switch (mMode) {
            case MODE_SCROLLABLE:
                // 如果Tab有多个,那么需要重新测量
                remeasure = child.getMeasuredWidth() < getMeasuredWidth();
                break;
            case MODE_FIXED:
                // 固定模式下,每一个Tab都是不可滑动的,只有一个的情况下需要重新测量
                remeasure = child.getMeasuredWidth() != getMeasuredWidth();
                break;
        }

        if (remeasure) {
            // 重新测量每一个子View(Tab)
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop()
                    + getPaddingBottom(), child.getLayoutParams().height);
            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                    getMeasuredWidth(), MeasureSpec.EXACTLY);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

Tab是如何添加进来:

public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
    if (tab.mParent != this) {
        throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
    }
    configureTab(tab, position);
    addTabView(tab);

    if (setSelected) {
        tab.select();
    }
}

private void addTabView(Tab tab) {
    final TabView tabView = tab.mView;
    mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
}

可以看见最终是通过addView添加进来的,其中,每一个TabView又是一个LinearLayout:

class TabView extends LinearLayout implements OnLongClickListener {

    private Tab mTab;
    private TextView mTextView;
    private ImageView mIconView;

    private View mCustomView;
    private TextView mCustomTextView;
    private ImageView mCustomIconView;

    //...
}

细心的你会发现TabView本来就支持Icon的设置,并且提供我们添加自定义Tab的View的接口。

mTabStrip也是一个LinearLayout:

private class SlidingTabStrip extends LinearLayout {
}

mTabStrip是在TabLayout构造的时候创建的,作为根布局:

public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    ThemeUtils.checkAppCompatTheme(context);

    // Disable the Scroll Bar
    setHorizontalScrollBarEnabled(false);

    // Add the TabStrip
    mTabStrip = new SlidingTabStrip(context);
    super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));

    // ...
}

如果觉得我的文字对你有所帮助的话,欢迎关注我的公众号:

公众号:Android开发进阶

我的群欢迎大家进来探讨各种技术与非技术的话题,有兴趣的朋友们加我私人微信huannan88,我拉你进群交(♂)流(♀)

你可能感兴趣的:(Android UI进阶之旅8--Material Design之TabLayout)