背景

Android应用几乎都会用到底部菜单栏,在Material Design还没有出来之前,TabHost等技术一直占主流,现在Google新sdk中提供了TabLayout类可以便捷的做出底部菜单栏效果。

本节我们实现两种主要的Tab效果:

  1. 仿微信底部菜单
  2. 仿今日头条顶部导航条

效果预览:

底部菜单

Tab一般与Activity或Fragment配合使用,以达到多页面切换效果,这里使用Fragment来开发子界面。

微信形式的底部菜单可以理解为一个外层Activity,套用几个Fragment。页面布局层次为:

  • MainActivity 主框架

    • MsgFragment 微信
    • ContactFragment 通讯录
    • FindFragment 发现
    • MeFragment 我

做出来的效果:

1、activity_main.xml布局

ViewPager+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=".MainActivity"><android.support.v4.view.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="0dip"android:layout_weight="1" /><Viewandroid:layout_width="match_parent"android:layout_height="0.5dip"android:background="@color/line_gray" /><android.support.design.widget.TabLayoutandroid:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="54dip"app:tabIndicatorHeight="0dip" /></LinearLayout>

2、MainActivity源码

package com.dommy.tab;import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;import com.dommy.tab.adapter.MainFragmentAdapter;import butterknife.BindView;
import butterknife.ButterKnife;/*** 主框架*/
public class MainActivity extends AppCompatActivity {/*** 菜单标题*/private final int[] TAB_TITLES = new int[]{R.string.menu_msg, R.string.menu_contact, R.string.menu_find, R.string.menu_me};/*** 菜单图标*/private final int[] TAB_IMGS = new int[]{R.drawable.tab_main_msg_selector, R.drawable.tab_main_contact_selector, R.drawable.tab_main_find_selector, R.drawable.tab_main_me_selector};@BindView(R.id.view_pager)ViewPager viewPager;@BindView(R.id.tab_layout)TabLayout tabLayout;/*** 页卡适配器*/private PagerAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);// 初始化页卡initPager();setTabs(tabLayout, getLayoutInflater(), TAB_TITLES, TAB_IMGS);}/*** 设置页卡显示效果* @param tabLayout* @param inflater* @param tabTitlees* @param tabImgs*/private void setTabs(TabLayout tabLayout, LayoutInflater inflater, int[] tabTitlees, int[] tabImgs) {for (int i = 0; i < tabImgs.length; i++) {TabLayout.Tab tab = tabLayout.newTab();View view = inflater.inflate(R.layout.item_main_menu, null);// 使用自定义视图,目的是为了便于修改,也可使用自带的视图tab.setCustomView(view);TextView tvTitle = (TextView) view.findViewById(R.id.txt_tab);tvTitle.setText(tabTitlees[i]);ImageView imgTab = (ImageView) view.findViewById(R.id.img_tab);imgTab.setImageResource(tabImgs[i]);tabLayout.addTab(tab);}}private void initPager() {adapter = new MainFragmentAdapter(getSupportFragmentManager());viewPager.setAdapter(adapter);// 关联切换viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {@Overridepublic void onTabSelected(TabLayout.Tab tab) {// 取消平滑切换viewPager.setCurrentItem(tab.getPosition(), false);}@Overridepublic void onTabUnselected(TabLayout.Tab tab) {}@Overridepublic void onTabReselected(TabLayout.Tab tab) {}});}}

源码说明:

  • 绑定视图对象使用到了ButterKnife框架;
  • viewPager和tabLayout添加了事件互相绑定,这样viewPager的滑动和tab的切换都能相互影响;
  • setTabs方法设置tabLayout内部的具体内容,为界面中的TabLayout添加了四个子Tab视图。
  • Tab的内容使用到了自定义视图,比较灵活一点,也可以使用Tab自带的布局结构。

3、Tab自定义视图item_main_menu.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:gravity="center_horizontal"android:orientation="vertical"><ImageViewandroid:id="@+id/img_tab"android:layout_width="24dip"android:layout_height="24dip"android:src="@drawable/menu_msg_default" /><TextViewandroid:id="@+id/txt_tab"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="2dip"android:text="首页"android:textColor="@drawable/txt_main_menu_selector"android:textSize="11sp" /></LinearLayout>

4、Tab图标selector
以第一个Tab“微信”使用的图标为例,tab_main_msg_selector.xml内容:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@drawable/menu_msg_selected" android:state_pressed="true" /><item android:drawable="@drawable/menu_msg_selected" android:state_selected="true" /><item android:drawable="@drawable/menu_msg_default" />
</selector>

使用到了两张图片:menu_msg_default、menu_msg_selected,一张默认图样式,一张选中图样式,对比如下:

5、Tab文字selector
因为Tab选中时需要做区分,所以文字颜色与图标一起变动会更好看,文字颜色也需要写selector。txt_main_menu_selector.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/menu_green" android:state_selected="true"></item><item android:color="@color/menu_gray"></item>
</selector>

menu_gray是默认状态的灰色,menu_green是选中时呈现的绿色。

6、页面切换Adapter

页面切换内容由viewPager的adapter对象完成,使用Fragment作为子页面时,adapter需要是FragmentPagerAdapter的实例,所以上述代码中的MainFragmentAdapter源码为:

package com.dommy.tab.adapter;import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;import com.dommy.tab.fragment.ContactFragment;
import com.dommy.tab.fragment.FindFragment;
import com.dommy.tab.fragment.MeFragment;
import com.dommy.tab.fragment.MsgFragment;/*** 主界面底部菜单适配器*/
public class MainFragmentAdapter extends FragmentPagerAdapter {public MainFragmentAdapter(FragmentManager fm) {super(fm);}@Overridepublic Fragment getItem(int i) {Fragment fragment = null;switch (i) {case 0:fragment = new MsgFragment();break;case 1:fragment = new ContactFragment();break;case 2:fragment = new FindFragment();break;case 3:fragment = new MeFragment();break;default:break;}return fragment;}@Overridepublic int getCount() {return 4;}}

说明:

  • getItem返回具体位置的viewPager切换到i位置时对应的fragment,因为主框架的视图是固定的,所以在这里根据i的值返回对应的fragment对象即可;
  • getItem中返回的fragment也可以携带一些参数,如果需要的话;
  • getCount返回视图的总数量,这里是固定值4。

7、子页面示例

本例中的子页面只是呈现一个简单的文字,实际开发中根据需要写入相应布局和功能替换即可。这里以MeFragment作为示例:

MeFragment.java:

package com.dommy.tab.fragment;import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;import com.dommy.tab.R;/*** 我*/
public class MeFragment extends Fragment {public MeFragment() {}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_me, container, false);}}

fragment_me.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"tools:context=".fragment.MeFragment"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="我" /></RelativeLayout>

呈现效果:

8、小结

MainActivity作为主框架,使用ViewPager实现4个子页面Fragment的切换,使用TabLayout绑定ViewPager来切换视图,实现了Tab卡切换、ViewPager滑动页面的效果,基本实现了微信主框架的效果。

顶部导航条

TabLayout放在顶部的时候,加上一些属性配置,就可以完美实现顶部导航的效果。根据导航条样式,这里分为三类:

  1. 自适应非固定条数形式;
  2. 居中固定条数形式;
  3. 平铺固定条数形式。

现就三种效果分别展开,读者可以根据需要选用相应的方法。

自适应非固定条数

这种就是和今日头条类似的形式,适用于顶部菜单数量不固定,而且比较多的情况。

从左至右依次排放,每个菜单的内容均完全显示,长度根据内容自动伸缩,超长后的菜单需要滚动显示。

看下效果:

为了方便,源码写在了MsgFragment中,也就是第一个子页面“微信”中。
1、MsgFragment.java
布局结构与MainActivity有类似之处。

package com.dommy.tab.fragment;import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;import com.dommy.tab.R;
import com.dommy.tab.adapter.MsgContentFragmentAdapter;import java.util.ArrayList;
import java.util.List;import butterknife.BindView;
import butterknife.ButterKnife;/*** 消息* <p>在这个界面中实现类似今日头条的头部tab</p>*/
public class MsgFragment extends Fragment {@BindView(R.id.tab_layout)TabLayout tabLayout;@BindView(R.id.view_pager)ViewPager viewPager;private MsgContentFragmentAdapter adapter;private List<String> names;@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);initData();}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_msg, container, false);ButterKnife.bind(this, view);adapter = new MsgContentFragmentAdapter(getChildFragmentManager());viewPager.setAdapter(adapter);tabLayout.setupWithViewPager(viewPager);// 更新适配器数据adapter.setList(names);return view;}private void initData() {names = new ArrayList<>();names.add("关注");names.add("推荐");names.add("热点");names.add("视频");names.add("小说");names.add("娱乐");names.add("问答");names.add("图片");names.add("科技");names.add("懂车帝");names.add("体育");names.add("财经");names.add("军事");names.add("国际");names.add("健康");}
}

2、fragment_msg.xml
布局有主界面比较相似,不过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=".fragment.MsgFragment"><android.support.design.widget.TabLayoutandroid:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="34dip"app:tabBackground="@color/white"app:tabIndicatorColor="@color/menu_green"app:tabIndicatorHeight="1dip"app:tabMode="scrollable"app:tabMinWidth="40dip"app:tabPaddingStart="5dip"app:tabPaddingEnd="5dip"app:tabSelectedTextColor="@color/wx_head_selected"app:tabTextAppearance="@style/tab_head"app:tabTextColor="@color/wx_head_default" /><android.support.v4.view.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="10dip"android:layout_weight="1"android:background="@color/white" />
</LinearLayout>

说明:

app:tabMode=“scrollable”

tabMode的值设为scrollable,表示tab卡过多时自动滑动。

3、MsgContentFragmentAdapter
因为内容页大多近似,所以采用同一个Fragment布局即可,内容根据传参来修改。MsgContentFragmentAdapter.java:

package com.dommy.tab.adapter;import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;import com.dommy.tab.fragment.MsgContentFragment;import java.util.ArrayList;
import java.util.List;/*** 消息内容子页面适配器*/
public class MsgContentFragmentAdapter extends FragmentPagerAdapter {private List<String> names;public MsgContentFragmentAdapter(FragmentManager fm) {super(fm);this.names = new ArrayList<>();}/*** 数据列表** @param datas*/public void setList(List<String> datas) {this.names.clear();this.names.addAll(datas);notifyDataSetChanged();}@Overridepublic Fragment getItem(int position) {MsgContentFragment fragment = new MsgContentFragment();Bundle bundle = new Bundle();bundle.putString("name", names.get(position));fragment.setArguments(bundle);return fragment;}@Overridepublic int getCount() {return names.size();}@Overridepublic CharSequence getPageTitle(int position) {String plateName = names.get(position);if (plateName == null) {plateName = "";} else if (plateName.length() > 15) {plateName = plateName.substring(0, 15) + "...";}return plateName;}
}

4、MsgContentFragment

子页面只放了一个TextView用来显示参数,MsgContentFragment.java:

package com.dommy.tab.fragment;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;import com.dommy.tab.R;import butterknife.BindView;
import butterknife.ButterKnife;/*** 消息内容页*/
public class MsgContentFragment extends Fragment {@BindView(R.id.txt_content)TextView tvContent;private String name;@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Bundle bundle = getArguments();name = bundle.getString("name");if (name == null) {name = "参数非法";}}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_msg_content, container, false);ButterKnife.bind(this, view);tvContent.setText(name);return view;}}

页面布局fragment_msg_content.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"tools:context=".fragment.MsgContentFragment"><TextViewandroid:id="@+id/txt_content"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="@string/hello_blank_fragment"android:textSize="18sp" /></RelativeLayout>

居中固定条数

这种形式的导航栏位于水平居中位置,适用于顶部菜单数量较少的情况。

从左至右依次排放,菜单整体位于水平居中位置,因为数量较少,一般不会滚动显示。

看下效果:

由于界面构建原理与上述内容一致,在此仅说明不同之处:

<?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=".fragment.ContactFragment"><android.support.design.widget.TabLayoutandroid:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="34dip"android:background="@color/white"app:tabBackground="@color/white"app:tabGravity="center"app:tabIndicatorHeight="0dip"app:tabMinWidth="40dip"app:tabMode="fixed"app:tabPaddingEnd="5dip"app:tabPaddingStart="5dip"app:tabSelectedTextColor="@color/wx_head_selected"app:tabTextAppearance="@style/tab_head"app:tabTextColor="@color/wx_head_default" /><android.support.v4.view.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="10dip"android:layout_weight="1"android:background="@color/white" />
</LinearLayout>

TabLayout有两个属性与“自适应非固定条数”形式不一样:

app:tabMode=“fixed”

tabMode的值设置为fixed(默认值,也可以不加该属性),表示TabLayout的内容最大长度不会超过自身长度,也就是说不会出现滚动条,添加这个属性时,如果Tab过多,则会比较挤,出现Tab内部内容换行的情况。

app:tabGravity=“center”

tabGravity定义Tab内部的对齐方式,当该属性值为center时,表示居中对齐,不进行拉伸,根据Tab内容自适应宽度。

平铺固定条数

Tab平均分配宽度,适用于顶部菜单数量固定,且需要撑满页面的情况。

从左至右依次排放,菜单内容填满整个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=".fragment.FindFragment"><android.support.design.widget.TabLayoutandroid:id="@+id/tab_layout"android:layout_width="match_parent"android:layout_height="34dip"android:background="@color/white"app:tabBackground="@color/white"app:tabGravity="fill"app:tabIndicatorHeight="0dip"app:tabMinWidth="40dip"app:tabMode="fixed"app:tabPaddingEnd="5dip"app:tabPaddingStart="5dip"app:tabSelectedTextColor="@color/wx_head_selected"app:tabTextAppearance="@style/tab_head"app:tabTextColor="@color/wx_head_default" /><android.support.v4.view.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="10dip"android:layout_weight="1"android:background="@color/white" />
</LinearLayout>

TabLayout有一个属性与“居中固定条数”形式不一样:

app:tabGravity=“fill”

tabGravity定义Tab内部的对齐方式,当该属性值为fill时,表示填充宽度,会进行拉伸,根据Tab数量平均分配每个Tab的宽度。

总结

TabLayout的出现基本解决了以前Android开发遇到的Tab页卡效果不好、不流畅的问题,而且TabLayout还添加了Indicator,能够随手指滑动,修改起来也比较方便。

除了没有直接解决滑动过程中颜色渐变、过渡的问题,普通场景使用TabLayout这个控件已经可以满足需求了。

源码

https://github.com/ahuyangdong/TabCustom

CSDN下载:
https://download.csdn.net/download/ahuyangdong/10722882

Android仿微信底部菜单栏+今日头条顶部导航栏相关推荐

  1. Android仿微信底部菜单栏+顶部菜单栏(附源码)

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 本文要实现仿微信微信底部菜单栏+顶部菜单栏,采用ViewPage来做,每一个page对应一个X ...

  2. android 底部tab效果,Android 仿微信底部渐变Tab效果

    先来看一下效果图 除了第三个的发现Tab有所差别外,其他的基本还原了微信的底部Tab渐变效果 每个Tab都是一个自定义View,根据ImageView的tint属性来实现颜色渐变效果,tint属性的使 ...

  3. 微信小程序实现自定义顶部导航栏(选项卡),固定导航栏和可滑动导航栏

    微信小程序实现自定义顶部导航栏(选项卡),固定导航栏和可滑动导航栏 固定导航栏 可滑动导航栏 顶部导航栏的需求实现: 顶部导航栏由列表渲染,通过tabclick点击事件改变activeIndex变量的 ...

  4. 安卓微信 返回显示未读条数_Android仿微信底部菜单栏功能显示未读消息数量

    底部菜单栏很重要,我看了一下很多应用软件都是用了底部菜单栏,这里使用了tabhost做了一种通用的(就是可以像微信那样显示未读消息数量的,虽然之前也做过但是layout下的xml写的太臃肿,这里去掉了 ...

  5. Android开发笔记(二十)顶部导航栏ActionBar

    标题栏ActionBar ActionBar是在Android3.0之后引入的,所以Android2.x之前的版本不能直接使用ActionBar.现在ActionBar广泛用做APP的顶部导航栏,它在 ...

  6. vue实现仿DJI大疆官网顶部导航栏组件

    页面进入时,导航栏背景色为透明: 鼠标移入时,导航栏背景变白,鼠标停留的标签字体颜色变换,出现底边框,若有二级下拉菜单,则平滑向下弹出菜单: gitee仓库地址:https://gitee.com/Y ...

  7. 微信小程序自定义navigationBar顶部导航栏,兼容安卓ios

    1.设置导航栏自定义"navigationStyle":"custom" "pages": [ //pages数组中第一项表示应用启动页,参 ...

  8. android底部tab页动画,Android仿微信底部实现Tab选项卡切换效果

    在网上看了比较多的关于Tab的教程,发现都很杂乱.比较多的用法是用TitlePagerTabStrip和ViewPaper.不过TitlePagerTabStrip有个很大的缺陷,Tab里面的内容刚进 ...

  9. 微信小程序 解决自定义顶部导航栏被键盘挤压的问题

    解决思路:         先把键盘的adjust-position置为false,防止键盘自动挤压.然后计算键盘弹起时,input实际被遮挡的高度,通过代码来上滑相应的距离 wxml代码: < ...

最新文章

  1. 为何 NLP 领域难以出现“独角兽”?
  2. Unity UGUI - Canvas / Camera
  3. 【会议记录】第二次例会(10.06)记录
  4. Controller接口控制器(4)
  5. FastDFS服务器搭建
  6. 自定义_Excel中的自定义函数(自定义函数的基础内容)
  7. 手把手教你玩转CSS3 3D技术
  8. Firefox和Chrome的选择
  9. mac 下 docker 镜像加速器
  10. 移动端开发框架mui介绍
  11. libcurl官方手册
  12. Windows Server 2019系统Windows defender误删文件的解决办法
  13. Android安卓原生接支付宝SDK支付客户端
  14. android仿tim主界面,简单仿腾讯TIM界面
  15. 使用Python画小猪佩奇(turtle库)
  16. 初识ElasticSearch(2) -文档查询之match查询 | 分词器
  17. 排列组合常见解题方法
  18. 使用物联网卡发送短信
  19. 【错误汇总】PYTHON开发
  20. 这四款PC软件能够帮你在工作中轻松脱颖而出

热门文章

  1. arcgis设置蒙版掩膜遮盖标注
  2. 柠檬妈妈的推荐,会讲故事的手机app
  3. 太变态!还有这样的 Hello World 鬼畜代码
  4. python提取word内容并写入excle
  5. 在Visual Studio中直接编译Fluent的UDF
  6. SQL LIKE操作符
  7. 移动端机器学习框架SNPE的使用
  8. 如何理解线程与进程(含有通俗解释)
  9. 嵌入式Linux串口终端的定制,基于PC/104平台嵌入式Linux系统定制方法
  10. C# 窗口背景 短消息提示