内容简介:这是【从零撸美团】系列文章第三篇【从零撸美团】是一个高仿美团的开源项目,旨在巩固 Android 相关知识的同时,帮助到有需要的小伙伴。GitHub 源码地址:
这是【从零撸美团】系列文章第三篇
【从零撸美团】是一个高仿美团的开源项目,旨在巩固 Android 相关知识的同时,帮助到有需要的小伙伴。
GitHub 源码地址: github.com/cachecats/L…
Android从零撸美团(一) - 统一管理 Gradle 依赖 提取到单独文件中
Android从零撸美团(二) - 仿美团下拉刷新自定义动画
Android从零撸美团(四) - 美团首页布局解析及实现 - Banner+自定义View+SmartRefreshLayout下拉刷新上拉加载更多
每个项目基本都会有多个 Tab ,以期在有限的屏幕空间展现更多的功能。 有需求就会有市场,如今也出现了很多优秀的 tab 切换框架,使用者众多。
但是深入思考之后还是决定自己造轮子~
因为框架虽好,可不要贪杯哦~ 使用第三方框架最大的问题在于并不能完全满足实际需求,有的是 icon 图片 跟文字间距无法调整,有的后期会出现各种各样问题,不利于维护。 最重要的是自己写一个也不是很复杂,有研究框架填坑的时间也就写出来了。
先看怎么用:一句代码搞定
tabWidget.init(getSupportFragmentManager(), fragmentList); 复制代码
再上效果图:
你没看错,长得跟美团一模一样,毕竟这个项目就叫【从零撸美团】 ㄟ( ▔, ▔ )ㄏ
一、思路
底部 tab 布局有很多实现方式,比如 RadioButton、FragmentTabHost、自定义组合View等。这里采用的是自定义组合View方式,因为可定制度更高。 滑动切换基本都是采用 ViewPager + Fragment ,集成简单,方案较成熟。这里同样采用这种方式。
二、准备
开始之前需要准备两样东西:
- 五个 tab 的选中和未选中状态的 icon 图片共计10张
- 五个 Fragment
这是最基本的素材,有了素材之后就开始干活吧~ 由于要实现点击选中图片和文字都变色成选中状态,没有选中就变成灰色,所以要对每组 icon 建立一个 selector xml文件实现状态切换。
<?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>
复制代码
这里用了 android:state_activated 作为状态标记,因为最常用的 pressed 和 focused 都达不到长久保持状态的要求,都是松开手指之后就恢复了。在代码中手动设置 activated 值就好。 注意: 此处设置的是 icon 图片,所以用 android:drawable ,与下面文字使用的 android:color 有区别。
设置完图片资源后,该设置文字颜色的 selector 了,因为文字的颜色也要跟着变。
<?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>
复制代码
注意图片用 android:drawable ,文字用 android:color 。
三、实现
准备工作做完之后,就开始正式的自定义View啦。
1. 写布局
首先是布局文件:
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" />
<!--下面的tab标签布局-->
<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="首页" />
</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="附近" />
</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="发现" />
</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="订单" />
</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="我的" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
复制代码
最外层用竖向排列的 LinearLayout 包裹,它有两个子节点,上面是用于滑动和装载 Fragment 的 ViewPager ,下面是五个 Tab 的布局。 为了方便管理把几个 ImageView 和 TextView 的共有属性抽取到 styles.xml 里了:
<!--菜单栏的图标样式-->
<style name="menuIconStyle" >
<item name="android:layout_width">25dp</item>
<item name="android:layout_height">25dp</item>
</style>
<!--菜单栏的文字样式-->
<style name="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>
复制代码
有了布局文件之后,就开始真正的自定义 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<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);
//设置默认的选中项
selectTab(MenuTab.HOME);
}
/**
* 外部调用初始化,传入必要的参数
*
* @param fm
*/
public void init(FragmentManager fm, List<BaseFragment> 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
}
}
复制代码
注释应该写的很清楚了,这里再强调几个点:
- 实现了三个构造方法,这三个构造方法分别对应于不同的创建方式。如果不确定怎么创建它就都实现吧,不会出错。 既然不确定到底走哪个方法,那把初始化方法写到哪个里面呢?这儿有个小技巧,就是把一个参数的
super(context),和两个参数的super(context, attrs)分别改成:this(context, null, 0)和this(context, attrs, 0)。这样无论走的哪个构造函数,最终都会走到三个参数的构造函数里,我们只要把初始化操作放在这个函数里就行了。 - 构造函数里的这行代码:
View view = View.inflate(context, R.layout.widget_custom_bottom_tab, this); 复制代码
将widget_custom_bottom_tab.xml文件与 java 代码绑定了起来,注意最后 一个参数是this而不是null。 - 本项目用到了
ButterKnife从findViewById()解脱出来。 - 切换选中未选中状态的原理是每次点击的时候,先调用
unCheckedAll ()将所有 tab 都置为未选中状态,再单独设置要选中的 tab 为选中状态llMenuHome.setActivated(true); - 实现 tab 的点击事件与
ViewPager的滑动绑定需要在两个地方写逻辑: 1)tab 的点击回调里执行下面两行代码,分别使 tab 变为选中状态和让ViewPager滑动到相应位置。selectTab(MenuTab.HOME); //使ViewPager跟随tab点击事件滑动 viewPager.setCurrentItem(0); 复制代码
2)在ViewPager的监听方法onPageSelected()中,每滑动到一个页面,就调用selectTab(MenuTab.HOME)方法将对应的 tab 设置为选中状态。 - 记得在构造方法里设置默认的选中项:
//设置默认的选中项 selectTab(MenuTab.HOME); 复制代码
好啦,到这自定义 View 已经完成了。下面看看怎么使用。
四、使用
在主页的布局文件里直接引用:
<?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>
复制代码
然后在 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<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);
//初始化
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 标签滑动切换实例。
源码地址: github.com/cachecats/L…
欢迎下载,欢迎 star ,欢迎点赞~
以上所述就是小编给大家介绍的《Android从零撸美团(三) - Android多标签tab滑动切换 - 自定义View快速实现高度定制封装》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Numerical Recipes 3rd Edition
William H. Press、Saul A. Teukolsky、William T. Vetterling、Brian P. Flannery / Cambridge University Press / 2007-9-6 / GBP 64.99
Do you want easy access to the latest methods in scientific computing? This greatly expanded third edition of Numerical Recipes has it, with wider coverage than ever before, many new, expanded and upd......一起来看看 《Numerical Recipes 3rd Edition》 这本书的介绍吧!