博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Android从零撸美团三】Android多标签tab滑动切换 - 自定义View快速实现高度定制封装
阅读量:4122 次
发布时间:2019-05-25

本文共 10098 字,大约阅读时间需要 33 分钟。

这是【从零撸美团】系列文章第三篇

【从零撸美团】是一个高仿美团的开源项目,旨在巩固 Android 相关知识的同时,帮助到有需要的小伙伴。
GitHub 源码地址:


每个项目基本都会有多个 Tab ,以期在有限的屏幕空间展现更多的功能。

有需求就会有市场,如今也出现了很多优秀的 tab 切换框架,使用者众多。

但是深入思考之后还是决定自己造轮子~

因为框架虽好,可不要贪杯哦~

使用第三方框架最大的问题在于并不能完全满足实际需求,有的是 icon 图片 跟文字间距无法调整,有的后期会出现各种各样问题,不利于维护。
最重要的是自己写一个也不是很复杂,有研究框架填坑的时间也就写出来了。

先看怎么用:一句代码搞定

tabWidget.init(getSupportFragmentManager(), fragmentList);

再上效果图:

在这里插入图片描述

你没看错,长得跟美团一模一样,毕竟这个项目就叫 ㄟ( ▔, ▔ )ㄏ

一、思路

底部 tab 布局有很多实现方式,比如 RadioButton、FragmentTabHost、自定义组合View等。这里采用的是自定义组合View方式,因为可定制度更高。

滑动切换基本都是采用 ViewPager + Fragment ,集成简单,方案较成熟。这里同样采用这种方式。

二、准备

开始之前需要准备两样东西:

  1. 五个 tab 的选中和未选中状态的 icon 图片共计10张
  2. 五个 Fragment

这是最基本的素材,有了素材之后就开始干活吧~

由于要实现点击选中图片和文字都变色成选中状态,没有选中就变成灰色,所以要对每组 icon 建立一个 selector xml文件实现状态切换。

这里用了 android:state_activated 作为状态标记,因为最常用的 pressedfocused 都达不到长久保持状态的要求,都是松开手指之后就恢复了。在代码中手动设置 activated 值就好。

注意:此处设置的是 icon 图片,所以用 android:drawable,与下面文字使用的 android:color 有区别。

设置完图片资源后,该设置文字颜色的 selector 了,因为文字的颜色也要跟着变。

注意图片用 android:drawable,文字用 android:color

三、实现

准备工作做完之后,就开始正式的自定义View啦。

1. 写布局

首先是布局文件:

widget_custom_bottom_tab.xml

最外层用竖向排列的 LinearLayout 包裹,它有两个子节点,上面是用于滑动和装载 FragmentViewPager,下面是五个 Tab 的布局。

为了方便管理把几个 ImageViewTextView 的共有属性抽取到 styles.xml 里了:

有了布局文件之后,就开始真正的自定义 View 吧。

2. 写 Java 代码自定义View

新建 java 文件 CustomBottomTabWidget 继承自 LinearLayout。为什么继承 LinearLayout 呢?因为我们的布局文件根节点就是 LinearLayout 呀,根节点是什么就继承什么。

先上代码吧:

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
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); //设置默认的选中项 selectTab(MenuTab.HOME); } /** * 外部调用初始化,传入必要的参数 * * @param fm */ public void init(FragmentManager fm, List
fragmentList) { mFragmentManager = fm; mFragmentList = fragmentList; initViewPager(); } /** * 初始化 ViewPager */ private void initViewPager() { mAdapter = new TabPagerAdapter(mFragmentManager, mFragmentList); viewPager.setAdapter(mAdapter); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { //将ViewPager与下面的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.id.ll_menu_home_page, R.id.ll_menu_nearby, R.id.ll_menu_discover, R.id.ll_menu_order, R.id.ll_menu_mine}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.ll_menu_home_page: selectTab(MenuTab.HOME); //使ViewPager跟随tab点击事件滑动 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; } } /** * 设置 Tab 的选中状态 * * @param tab 要选中的标签 */ public void selectTab(MenuTab tab) { //先将所有tab取消选中,再单独设置要选中的tab unCheckedAll(); 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); } } //让所有tab都取消选中 private void unCheckedAll() { llMenuHome.setActivated(false); llMenuNearby.setActivated(false); llMenuDiscover.setActivated(false); llMenuOrder.setActivated(false); llMenuMine.setActivated(false); } /** * tab的枚举类型 */ public enum MenuTab { HOME, NEARBY, DISCOVER, ORDER, MINE }}

注释应该写的很清楚了,这里再强调几个点:

  1. 实现了三个构造方法,这三个构造方法分别对应于不同的创建方式。如果不确定怎么创建它就都实现吧,不会出错。
    既然不确定到底走哪个方法,那把初始化方法写到哪个里面呢?这儿有个小技巧,就是把一个参数的 super(context),和两个参数的 super(context, attrs) 分别改成:this(context, null, 0)this(context, attrs, 0)。这样无论走的哪个构造函数,最终都会走到三个参数的构造函数里,我们只要把初始化操作放在这个函数里就行了。
  2. 构造函数里的这行代码:
    View view = View.inflate(context, R.layout.widget_custom_bottom_tab, this);
    widget_custom_bottom_tab.xml 文件与 java 代码绑定了起来,注意最后 一个参数是 this 而不是 null
  3. 本项目用到了 ButterKnifefindViewById() 解脱出来。
  4. 切换选中未选中状态的原理是每次点击的时候,先调用 unCheckedAll () 将所有 tab 都置为未选中状态,再单独设置要选中的 tab 为选中状态 llMenuHome.setActivated(true);
  5. 实现 tab 的点击事件与 ViewPager 的滑动绑定需要在两个地方写逻辑:
    1)tab 的点击回调里执行下面两行代码,分别使 tab 变为选中状态和让 ViewPager 滑动到相应位置。
    selectTab(MenuTab.HOME);//使ViewPager跟随tab点击事件滑动viewPager.setCurrentItem(0);
    2)在 ViewPager 的监听方法 onPageSelected() 中,每滑动到一个页面,就调用 selectTab(MenuTab.HOME) 方法将对应的 tab 设置为选中状态。
  6. 记得在构造方法里设置默认的选中项:
    //设置默认的选中项 selectTab(MenuTab.HOME);

好啦,到这自定义 View 已经完成了。下面看看怎么使用。

四、使用

在主页的布局文件里直接引用:

然后在 Activity 里一句话调用:

tabWidget.init(getSupportFragmentManager(), fragmentList);

就是这么简单!

是不是很爽很清新?

贴出 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
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); //初始化 init(); } private void init() { //构造Fragment的集合 fragmentList = new ArrayList<>(); fragmentList.add(new HomeFragment()); fragmentList.add(new NearbyFragment()); fragmentList.add(new DiscoverFragment()); fragmentList.add(new OrderFragment()); fragmentList.add(new MineFragment()); //初始化CustomBottomTabWidget tabWidget.init(getSupportFragmentManager(), fragmentList); }}

整个代码很简单,只需要构造出 Fragment 的列表传给 CustomBottomTabWidget 就好啦。

总结:自己造轮子可能前期封装花些时间,但自己写的代码自己最清楚,几个月后再改需求改代码能快速的定位到要改的地方,便于维护。

并且最后封装完用起来也很简单啊,不用在 Activity 里写那么多配置代码,整体逻辑更清晰,耦合度更低。


以上就是用自定义 View 的方式实现高度定制化的多 tab 标签滑动切换实例。

源码地址:
欢迎下载,欢迎 star,欢迎点赞~

转载地址:http://mvvpi.baihongyu.com/

你可能感兴趣的文章
链睿和家乐福合作推出下一代零售业隐私保护技术
查看>>
Unifrax宣布新建SiFAB™生产线
查看>>
艾默生纪念谷轮™在空调和制冷领域的百年创新成就
查看>>
NEXO代币持有者获得20,428,359.89美元股息
查看>>
Piper Sandler为EverArc收购Perimeter Solutions提供咨询服务
查看>>
RMRK筹集600万美元,用于在Polkadot上建立先进的NFT系统标准
查看>>
JavaSE_day12 集合
查看>>
JavaSE_day14 集合中的Map集合_键值映射关系
查看>>
Day_15JavaSE 异常
查看>>
异常 Java学习Day_15
查看>>
JavaSE_day_03 方法
查看>>
day-03JavaSE_循环
查看>>
Mysql初始化的命令
查看>>
day_21_0817_Mysql
查看>>
day-22 mysql_SQL 结构化查询语言
查看>>
MySQL关键字的些许问题
查看>>
浅谈HTML
查看>>
css基础
查看>>
HTML&CSS进阶
查看>>
Servlet进阶和JSP基础
查看>>