先上最终效果:
效果主要包括以下几方面的内容:
1)两项Tab的常规切换;
2)自定义两项Tab,仔细看下方的指示器里面的图片是可以动;
3)多项Tab,上方的共10个Tab项,涉及到基类的创建和继承;
4)最上面将Tab项放入CoordinatorLayout中,完成上下拉动时的联动;
TabLayout 是 Design Support 库中的一个全新控件。TabLayout 允许开发者将标签指示器(Tab)与其对应的页面(一般为ViewPager)进行关联,达到切换标签指示器的同时,与它关联的页面也会跟着一起滑动的效果。
列出经常设置的属性:
自定义Style,为Tablayout的字体改变大小TextSize。
1.Tablayout的常规用法
① 添加依赖;
ext {
// Sdk and tools
minSdkVersion = 17
targetSdkVersion = 29
compileSdkVersion = 29
buildToolsVersion = '29.0.0'
//Version
versionCode = 1
versionName = '1.0'
// App dependencies
supportLibraryVersion = '29.0.0'
butterknifeVersion = '10.1.0'
utilcodeVersion = '1.25.9'
gsonVersion = '2.6.2'
circleimageviewVersion = '2.1.0'
adapterHelperVersion = '2.9.40'
zhyadapterVersion = '3.0.3'
circularprogressbarVersion = '1.2.0'
magicIndictorVersion = '1.5.0'
}
implementation "com.android.support:appcompat-v7:$supportLibraryVersion"
implementation "com.android.support:design:$supportLibraryVersion"
② 制作Viewpager需要的Fragment;
基类制作,代码如下:
public abstract class BaseFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(getLayoutResId(), null);
ButterKnife.bind(this, root);
return root;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
}
protected void init(){}
protected abstract int getLayoutResId();
}
首页Fragment制作,继承父类,重写抽象方法,代码如下:
public class HomeFragment extends BaseFragment {
public static HomeFragment newInstance() {
return new HomeFragment();
}
@Override
protected int getLayoutResId() {
return R.layout.fragment_coor_home;
}
@Override
protected void init() {
loadData();
initView();
initMagicIndicator();
}
}
我的Fragment制作,继承基类,重写抽象方法,代码和上面类似。
③ 为Activity写入布局,放入TabLayout和ViewPager,放入CardView是为了实现上边缘的阴影效果;
④ Activity内代码如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//未选中的Tab图片
private int[] unSelectTabRes = new int[]{R.drawable.ic_launcher, R.drawable.ic_launcher_round};
//选中的Tab图片
private int[] selectTabRes = new int[]{R.drawable.ic_launcher_round, R.drawable.ic_launcher};
//Tab标题
private String[] title = new String[]{"首页", "我的"};
@BindView(R.id.tab_layout_view)
TabLayout mTabLayout;
@BindView(R.id.viewpager_content_view)
ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
initView();
initListener();
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setBackgroundDrawable(null);
}
private void initView() {
//使用适配器将ViewPager与Fragment绑定在一起
mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager()));
//将TabLayout与ViewPager绑定
mTabLayout.setupWithViewPager(mViewPager);
//获取底部的单个Tab
for (int i = 0; i < title.length; i++) {
if (i == 0) {
mTabLayout.getTabAt(0).setIcon(selectTabRes[0]);
} else {
mTabLayout.getTabAt(i).setIcon(unSelectTabRes[i]);
}
}
}
private void initListener() {
//TabLayout切换时导航栏图片处理
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
//选中图片操作
for (int i = 0; i < title.length; i++) {
if (tab == mTabLayout.getTabAt(i)) {
mTabLayout.getTabAt(i).setIcon(selectTabRes[i]);
mViewPager.setCurrentItem(i);
}
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
//未选中图片操作
for (int i = 0; i < title.length; i++) {
if (tab == mTabLayout.getTabAt(i)) {
mTabLayout.getTabAt(i).setIcon(unSelectTabRes[i]);
}
}
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
//自定义ViewPager适配器
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
if (position == 0) {
return HomeFragment.newInstance();//首页
} else if (position == 1) {
return MineFragment.newInstance();//我的
}
return HomeFragment.newInstance();
}
@Override
public int getCount() {
return title.length;
}
@Override
public CharSequence getPageTitle(int position) {
return title[position];
}
}
}
推荐文章《Android开发 Tablayout的学习》
2. TabLayout的自定义用法
以下用以实现Tab指示器不是添加一张简单的图片,而是加入一张可以动的图片,即Gif图。
1) 切换时动画效果实现:借鉴开源项目GifImageView的思路。原项目是加载网络上的Gif图,放入实际项目中,是使用本地图片,使用时,只需要直接调用setBytes()方法,传入的参数是字节。那么我们可以将使用的Gif图放在assets文件夹或者raw文件夹,然后读取到这张图片,然后将其转为字节,传入即可。
2) 将GifImageView放入到Tablayout中,就需要用到自定义TabLayout了。TabLayout的自定义,主要是通过setCustomView方法来添加自定义布局实现。
① 创建自定义布局,用以填充TabLayout,该布局中放入GifImageView;
② Activity布局不变,依旧放入TabLayout,而后代码中做修改;
private void initView() {
//使用适配器将ViewPager与Fragment绑定在一起
MyFragmentPagerAdapter fragmentPagerAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(fragmentPagerAdapter);
//将TabLayout与ViewPager绑定
mTabLayout.setupWithViewPager(mViewPager);
//设置自定义tab
for (int i = 0; i < title.length; i++) {
TabLayout.Tab tab = mTabLayout.getTabAt(i);
if (tab != null) {
tab.setCustomView(fragmentPagerAdapter.getTabView(i));
}
}
View view = mTabLayout.getTabAt(0).getCustomView();
TextView textView = view.findViewById(R.id.tv);
textView.setTextColor(getResources().getColor(R.color.choosed_text));
GifImageView gifImageView = view.findViewById(R.id.gif_img);
gifImageView.setBytes(AssetsUtils.getByte(MainActivity.this, selectTabRes[0]));
gifImageView.startAnimation();
}
private void initListener() {
//TabLayout切换时导航栏图片处理
mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
//选中图片操作
for (int i = 0; i < title.length; i++) {
if (tab == mTabLayout.getTabAt(i)) {
View view = tab.getCustomView();
if (view != null) {
TextView textView = view.findViewById(R.id.tv);
textView.setTextColor(getResources().getColor(R.color.choosed_text));
GifImageView gifImageView = view.findViewById(R.id.gif_img);
gifImageView.setBytes(AssetsUtils.getByte(MainActivity.this, selectTabRes[i]));
gifImageView.startAnimation();
mViewPager.setCurrentItem(i);
}
}
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
//未选中图片操作
for (int i = 0; i < title.length; i++) {
if (tab == mTabLayout.getTabAt(i)) {
View view = tab.getCustomView();
if(view != null) {
TextView textView = view.findViewById(R.id.tv);
textView.setTextColor(getResources().getColor(R.color.unchoosed_text));
GifImageView gifImageView = view.findViewById(R.id.gif_img);
gifImageView.setBytes(AssetsUtils.getByte(MainActivity.this, unSelectTabRes[i]));
gifImageView.startAnimation();
}
}
}
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
//自定义ViewPager适配器
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
if (position == 0) {
return HomeFragment.newInstance();//首页
} else if (position == 1) {
return MineFragment.newInstance();//我的
}
return HomeFragment.newInstance();
}
@Override
public int getCount() {
return title.length;
}
@Override
public CharSequence getPageTitle(int position) {
return title[position];
}
public View getTabView(int position) {
View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.custom_tablayout, null);
TextView textView = view.findViewById(R.id.tv);
GifImageView gifImageView = view.findViewById(R.id.gif_img);
textView.setText(title[position]);
if (position == 0) {
textView.setTextColor(getResources().getColor(R.color.choosed_text));
gifImageView.setBytes(AssetsUtils.getByte(MainActivity.this, selectTabRes[0]));
} else {
textView.setTextColor(getResources().getColor(R.color.unchoosed_text));
gifImageView.setBytes(AssetsUtils.getByte(MainActivity.this, unSelectTabRes[1]));
gifImageView.startAnimation();
}
return view;
}
}
}
1)修改设置自定义tab的方法;TabLayout.Tab tab = mTabLayout.getTabAt(i); 拿到每一个tab,然后调用setCustomView的方法,将每一个Tab都设置刚刚自定义好的布局。其中定义一个getTabView的方法,指定好初始状态时,各个Tab的状态。
2)Tab发生点击时,调用方法,tab == mTabLayout.getTabAt(i)和View view = tab.getCustomView(); 拿到每一个tab的view,然后再做各个状态对应的view切换,比如字体颜色的改变,GifImageView图片的改变。
其他代码如同1中的常规用法,最后就可以实现前面图中的,切换Tab,tab指示图片做动态变化。
推荐文章:《TabLayout自定义tab,实现多样导航栏》《TabLayout的自定义》
3. Tablayout的多项用法
所谓多项用法,就是当TabLayout填充多项时,该如何优雅处理 —— Fragment父类定义。
① 定义10个Fragment;
比如首页的所有布局都是类似的,10个Fragment,每个Fragment都是用列表填充,那么这里的实现也可以使用父类来简化实现。为每个列表Fragment创建父类,父类继承自此前BaseFragment,然后使用同一列表。注意这里使用了洋神的万能适配器。
implementation "com.zhy:base-rvadapter:$zhyadapterVersion"
列表Fragment使用的统一布局如下:
RecyclerView使用的适配器布局,使用CardView是为了快速实现圆角:
新建的列表Fragment基类如下:
public abstract class BaseItemFragment extends BaseFragment {
@BindView(R.id.rv)
ChooseCenterRecyclerview recyclerView;
public ArrayList datas = new ArrayList();
public CommonAdapter adapter;
public abstract void getDatas(ArrayList datas);
@Override
protected int getLayoutResId() {
return R.layout.fragment_base_item_tab;
}
@Override
protected void init() {
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
adapter = new CommonAdapter(getActivity(), R.layout.fragment_recy_item, datas) {
@Override
protected void convert(ViewHolder holder, Bean bean, int position) {
AppCompatImageView imageView = holder.getView(R.id.iv_effectmore_cover);
imageView.setImageResource(bean.getCover());
}
};
recyclerView.setAdapter(adapter);
adapter.setOnItemClickListener(new CommonAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, RecyclerView.ViewHolder holder, int position) {
onRecyItemClick.onClickRecyItem(position);
}
@Override
public boolean onItemLongClick(View view, RecyclerView.ViewHolder holder, int position) {
return false;
}
});
}
public interface OnRecyItemClick {
void onClickRecyItem(int pos);
}
public OnRecyItemClick onRecyItemClick;
public void setOnRecyItemClick(OnRecyItemClick onRecyItemClick) {
this.onRecyItemClick = onRecyItemClick;
}
}
写入10个子Fragment继承自刚刚自定义好的基类,这样得到的子类十分清爽,代码简洁。
public class PaintlyFragment1 extends BaseItemFragment {
public static PaintlyFragment1 newInstance(){
return new PaintlyFragment1();
}
@Override
public void getDatas(ArrayList list) {
datas = list;
}
}
然后正式项目中为了变得更加优雅,调用第三方库MagicIndicator,替换掉tabLayout,当然可以不替换,用法是一致的。
repositories {
...
maven {
url "https://jitpack.io"
}
}
implementation "com.github.hackware1993:MagicIndicator:$magicIndictorVersion"
② Activity布局如下:
③ Viewpager的适配器和列表填充数据使用的Bean如下:
public class Bean implements Serializable {
private String name;
private int cover;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCover() {
return cover;
}
public void setCover(int cover) {
this.cover = cover;
}
}
public class FmPagerAdapter extends FragmentPagerAdapter {
private List fragmentList;
public FmPagerAdapter(List fragmentList, FragmentManager fm) {
super(fm);
this.fragmentList = fragmentList;
}
@Override
public int getCount() {
return fragmentList != null && !fragmentList.isEmpty() ? fragmentList.size() : 0;
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
}
④ 主Fragment代码如下,主要是填充数据的操作:
public class HomeFragment extends BaseFragment {
private ArrayList datas_item_1 = new ArrayList<>();
... ...
private FmPagerAdapter pagerAdapter;
private ArrayList fragments = new ArrayList<>();
private String[] titles_12 = new String[]{"智能", "红润", "日系", "自然", "艺术黑白",
"甜美", "甜美", "甜美", "甜美", "甜美"};
private String[] tab_titles = new String[]{"智能", "红润", "日系", "自然", "艺术黑白",
"甜美", "甜美", "甜美", "甜美", "甜美"};
private List mDataList = Arrays.asList(tab_titles);
@BindView(R.id.home_fragment_viewpager)
ViewPager mViewPager;
@BindView(R.id.home_fragment_tab_magic)
MagicIndicator magicIndicator;
public static HomeFragment newInstance() {
return new HomeFragment();
}
@Override
protected int getLayoutResId() {
return R.layout.fragment_home;
}
@Override
protected void init() {
loadData();
initView();
initMagicIndicator();
}
private void initView() {
PaintlyFragment1 tabFragment1 = PaintlyFragment1.newInstance();
tabFragment1.getDatas(datas_item_1);
tabFragment1.setOnRecyItemClick(pos -> {
Bean bean = datas_item_1.get(pos);
int res = bean.getCover();
});
... ...
fragments.add(tabFragment1);
fragments.add(tabFragment2);
fragments.add(tabFragment3);
fragments.add(tabFragment4);
fragments.add(tabFragment5);
fragments.add(tabFragment6);
fragments.add(tabFragment7);
fragments.add(tabFragment8);
fragments.add(tabFragment9);
fragments.add(tabFragment10);
pagerAdapter = new FmPagerAdapter(fragments, getActivity().getSupportFragmentManager(), mDataList);
mViewPager.setAdapter(pagerAdapter);
}
private void initMagicIndicator() {
magicIndicator.setBackgroundColor(Color.WHITE);
CommonNavigator commonNavigator = new CommonNavigator(getActivity());
commonNavigator.setScrollPivotX(0.35f);
commonNavigator.setAdapter(new CommonNavigatorAdapter() {
@Override
public int getCount() {
return mDataList == null ? 0 : mDataList.size();
}
@Override
public IPagerTitleView getTitleView(Context context, final int index) {
SimplePagerTitleView simplePagerTitleView = new SimplePagerTitleView(context);
simplePagerTitleView.setText(mDataList.get(index));
simplePagerTitleView.setNormalColor(Color.parseColor("#A0A0A0")); // 不被选中时文字颜色
simplePagerTitleView.setSelectedColor(Color.parseColor("#ffffff")); // 选中时文字颜色
simplePagerTitleView.setOnClickListener(v -> mViewPager.setCurrentItem(index));
return simplePagerTitleView;
}
@Override
public IPagerIndicator getIndicator(Context context) {
WrapPagerIndicator indicator = new WrapPagerIndicator(context);
indicator.setFillColor(Color.parseColor("#FE8D9A")); // 椭圆色块背景色
return indicator;
}
});
magicIndicator.setNavigator(commonNavigator);
ViewPagerHelper.bind(magicIndicator, mViewPager);
}
private void loadData() {
for (int i = 0; i < titles_12.length; i++) {
Bean bean_item_1 = new Bean();
bean_item_1.setCover(pics_item_1[i]);
bean_item_1.setName(titles_12[i]);
datas_item_1.add(bean_item_1);
}
... ...
}
private static int[] pics_item_1 = new int[]{
R.drawable.ic_a0110001_change, R.drawable.ic_a0110001_change,
R.drawable.ic_a0110001_change, R.drawable.ic_a0110001_change,
R.drawable.ic_a0110001_change, R.drawable.ic_a0110001_change,
R.drawable.ic_a0110001_change, R.drawable.ic_a0110001_change,
R.drawable.ic_a0110001_change, R.drawable.ic_a0110001_change,
R.drawable.ic_a0110001_change, R.drawable.ic_a0110001_change};
... ...
}
4. 将Tablayout 放入Coordinatorlayout中实现列表上下拉联动
列表部分不变,AppCompatImageView图片控件放入CoordinatorLayout,然后列表所在的父容器LinearLayout加入layout_behavior实现联动。最后实现的效果如上面那幅图的上下拉效果。
推荐文章:《Android Material Design:常用控件学习笔记》
最后提一下此项目的总架构为:单Activity + 多Fragment。结构如下图: