0
点赞
收藏
分享

微信扫一扫

Android Study Material Design 九 之 Hello TabLayout


LZ-Says: 我把我后背留给你,你却对他人说,瞧,那个傻子。。。


前言

随着年龄的增长,日子仿佛一天天的加快了步伐,总是回头看,不知不觉过了这么久。

回头看,17年,又所剩无几了。

在这17年最后几个月里,好好努力,奋进~

今天学习了有关TabLayout相关知识,分享给大家~

演示效果


Android Study Material Design 九 之 Hello TabLayout_ide_02

TabLayout简述

简单介绍下TabLayout:

1) TabLayout来自于design包内,是一种具有Material Design效果的选项卡。

那么它是怎么用的呢?

1) 一般的话,配合Fragment使用,同样也可以TabLayout+ViewPager+Fragment一起使用。

这里为大家简单介绍其中关键Api,这些同样是比较常用的。

  • app:tabBackground: 设置Tab选项卡背景色;
  • app:tabIndicatorHeight: 设置指示器高度;
  • app:tabGravity: 设置Tab显示方式:

1) center:居中显示;

2) fill:填充整个宽度

  • app:tabIndicatorColor: 设置Tab指示器颜色;
  • app:tabTextColor: 设置Tab文字颜色;
  • app:tabSelectedTextColor: 设置Tab选中颜色;
  • app:tabMode: 设置Tab模式:

1) fixed:不能滑动,子控件平分父容器,类似LinearLayout权重;

2) scrollable:可滑动

ok,基于以上关键Api,我们开始着手实现撸码~

着手撸码

第一步:搭建布局

<?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"
android:orientation="vertical"
tools:context="com.materialdesignstudy.tablayout.TabLayoutActivity">

<android.support.design.widget.TabLayout
android:id="@+id/id_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabBackground="@color/blue"
app:tabGravity="center"
app:tabIndicatorColor="@color/colorPrimary_pink"
app:tabIndicatorHeight="10dp"
app:tabMode="scrollable"
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"
app:tabTextColor="@color/colorPrimary_pink" />

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

</LinearLayout>

第二步:定义测试Fragment

package com.materialdesignstudy.tablayout;

import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
* Created by HLQ on 2017/11/16
* 测试使用Fragment
*/
public class MyFragment extends Fragment {

@Nullable
@Override
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.setText(title);
tv.setBackgroundColor(Color.rgb((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)));
return tv;
}
}

第三步:初始化以及设置监听

private TabLayout mTabLayout;
private ViewPager mViewPager;
private MyPagerAdapter mAdapter;

private String[] titles = {
"今日头条", "热点新闻", "娱乐",
"房地产", "体育", "科技",
"互联网","CSDN Blog", "体育",
"APK Bus", "健康医疗", "民生"
};

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

private void initView() {
// 实例化
mTabLayout = (TabLayout) findViewById(R.id.id_tab_layout);
mViewPager = (ViewPager) findViewById(R.id.id_view_pager);

// 设置分割线
LinearLayout linearLayout= (LinearLayout) mTabLayout.getChildAt(0);
linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,R.drawable.shape_tablayout_divider));

mAdapter = new MyPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);

// 关联tabLayout与ViewPager
mTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {

@Override
public void onTabSelected(TabLayout.Tab tab) {
// 当前被选中
mViewPager.setCurrentItem(tab.getPosition(), true);
}

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

}

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

}
});

// viewPager滑动关联TabLayout
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
// 实现方式一
// 设置tabLayout标签来自于pagerAdapter
mTabLayout.setTabsFromPagerAdapter(mAdapter);

}

class MyPagerAdapter extends FragmentPagerAdapter {

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

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

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

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

一起来看下目前实现的效果:


Android Study Material Design 九 之 Hello TabLayout_material-design_03

还记得当初写这个,死费劲儿,一个别憋屈了好久,才实现了部分功能,当初肿么没想到看看api呢!

下面我们反手实现一个特殊的需求(TabLayout怎么就能在下方且无指示器效果呢?),并且为大家介绍第二种玩法,细心的小伙伴一定发现了,代码中写的方式一~

首先要做的第一步,布局变更:

<?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"
android:orientation="vertical"
tools:context="com.materialdesignstudy.tablayout.TabLayoutActivity">

<android.support.v4.view.ViewPager
android:id="@+id/id_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />

<android.support.design.widget.TabLayout
android:id="@+id/id_tab_layout"
android:layout_width="match_parent"
android:layout_height="80dp"
app:tabBackground="@color/blue"
app:tabGravity="fill"
app:tabIndicatorColor="@color/colorPrimary_pink"
app:tabIndicatorHeight="0dp"
app:tabMode="fixed"
app:tabSelectedTextColor="@color/colorPrimary_pinkDark"
app:tabTextColor="@color/colorPrimary_pink" />

</LinearLayout>

第二步:初始化以及设置方式二

private TabLayout mTabLayout;
private ViewPager mViewPager;
private MyPagerAdapter mAdapter;

private String[] titles = {
"今日头条", "热点新闻", "娱乐"
};

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

private void initView() {
// 实例化
mTabLayout = (TabLayout) findViewById(R.id.id_tab_layout);
mViewPager = (ViewPager) findViewById(R.id.id_view_pager);

mAdapter = new MyPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);

// 方式二:
mTabLayout.setupWithViewPager(mViewPager);

for (int i = 0; i < mTabLayout.getTabCount(); i++) {
TabLayout.Tab tab = mTabLayout.getTabAt(i);
View view = View.inflate(this, R.layout.item_bottom_tab_layout, null);
TextView tv = view.findViewById(R.id.tv_item_content);
tv.setText(titles[i]);
tab.setCustomView(view);
}

}

class MyPagerAdapter extends FragmentPagerAdapter {

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

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

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

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

没错,就是这么6。看看效果~


Android Study Material Design 九 之 Hello TabLayout_android_04

以上内容便是有关TabLayout使用,而下面,我们开始简要分析TabLayout源码,一起来瞅瞅~

TabLayout源码简要分析

首先,点击进入源码,我们得知此神器依旧继承HorizontalScrollView,不信?源码附上~

public class TabLayout extends HorizontalScrollView

又是似曾相识的感觉哈~

// 检测是否使用Theme或者引入design库
ThemeUtils.checkAppCompatTheme(context);

// 关闭HorizontalScrollBar
setHorizontalScrollBarEnabled(false);

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

通过查看构造,不知道大家有没有注意到最下面俩行代码,实例化一个SlidingTabStrip,并添加到View中。

看到这里,我们有些疑问,SlidingTabStrip是什么东西呢?一起来看。

继续深入:

private class SlidingTabStrip extends LinearLayout

SlidingTabStrip是存在于TabLayout的内部类,此类继承了LinearLayout,也就是说,我们的TabLayout父布局为线性布局,这里也就很好的诠释了我们添加分割线为什么直接getCharAt(0)拿到第一个,从而设置其分割线显示以及样式。

private ValueAnimatorCompat mIndicatorAnimator;

SlidingTabStrip(Context context) {
super(context);
setWillNotDraw(false);
mSelectedIndicatorPaint = new Paint();
}

void setSelectedIndicatorColor(int color) {
if (mSelectedIndicatorPaint.getColor() != color) {
mSelectedIndicatorPaint.setColor(color);
ViewCompat.postInvalidateOnAnimation(this);
}
}

void setSelectedIndicatorHeight(int height) {
if (mSelectedIndicatorHeight != height) {
mSelectedIndicatorHeight = height;
ViewCompat.postInvalidateOnAnimation(this);
}
}

上面截取部分,我们发现其内部采用属性动画,通过画笔进行绘制内容,包含指示器颜色、高度。

boolean childrenNeedLayout() {
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
if (child.getWidth() <= 0) {
return true;
}
}
return false;
}

而上面则是通过循环去判断所包含子控件是否有效,也就是getWidth()<=0的时候。

而下面,便是将Tab的位置设置到指示器的位置上。

void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
mIndicatorAnimator.cancel();
}

mSelectedPosition = position;
mSelectionOffset = positionOffset;
updateIndicatorPosition();
}

接下来我们来看看绘制方法中进行了什么操作。

// 校验当前模式是否有效
if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {
// 获取子控件个数
final int count = getChildCount();

// First we'll find the widest tab 首先查找最大宽度
int largestTabWidth = 0;
for (int i = 0, z = count; i < z; i++) {
View child = getChildAt(i); // 拿到子控件
if (child.getVisibility() == VISIBLE) {
largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());
}
}

if (largestTabWidth <= 0) {
// If we don't have a largest child yet, skip until the next measure pass
return;
}

// 获取dp转px数值
final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);
boolean remeasure = false;

if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {
// 如果tab匹配我们宽度,我们将为所有的tabs设置相同宽度
// If the tabs fit within our width minus gutters, we will set all tabs to have
// the same width
for (int i = 0; i < count; i++) {
final LinearLayout.LayoutParams lp =
(LayoutParams) getChildAt(i).getLayoutParams();
if (lp.width != largestTabWidth || lp.weight != 0) {
lp.width = largestTabWidth;
lp.weight = 0;
remeasure = true;
}
}
} else {
// 如果大于,则需要选择填充
// If the tabs will wrap to be larger than the width minus gutters, we need
// to switch to GRAVITY_FILL
mTabGravity = GRAVITY_FILL;
updateTabViews(false);
remeasure = true;
}

// 绘制
if (remeasure) {
// Now re-measure after our changes
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}

其实主要就是根据宽度做比对去计算,绘制。

继续往下看。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);

if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
// If we're currently running an animation, lets cancel it and start a
// new animation with the remaining duration
mIndicatorAnimator.cancel();
final long duration = mIndicatorAnimator.getDuration();
// 下面通过大量的计算 去放置子控件
animateIndicatorToPosition(mSelectedPosition,
Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration));
} else {
// If we've been layed out, update the indicator position
updateIndicatorPosition();
}
}

其中,animateIndicatorToPosition方法则通过计算重而绘制移动时指示器的动画效果。很nice。

这里为大家截取有关动画相关代码:

if (startLeft != targetLeft || startRight != targetRight) {
ValueAnimatorCompat animator = mIndicatorAnimator = ViewUtils.createAnimator();
animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
animator.setDuration(duration);
animator.setFloatValues(0, 1);
animator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimatorCompat animator) {
final float fraction = animator.getAnimatedFraction();
setIndicatorPosition(
AnimationUtils.lerp(startLeft, targetLeft, fraction),
AnimationUtils.lerp(startRight, targetRight, fraction));
}
});
animator.addListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(ValueAnimatorCompat animator) {
mSelectedPosition = position;
mSelectionOffset = 0f;
}
});
animator.start();
}

而最后,则开始绘制。

@Override
public void draw(Canvas canvas) {
super.draw(canvas);

// Thick colored underline below the current selection
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
}

好了 到此结束 以后 好好研究下关于自定义View相关知识点

而关于我们在文章开头方式一与方式二之间的区别 或者说是某种联系 具体可自行查看源码。相信你一看就秒懂~

GitHub查看地址

​​https://github.com/HLQ-Struggle/MaterialDesignStudy/tree/master/app/src/main/java/com/materialdesignstudy/tablayout​​

举报

相关推荐

0 条评论