这是我们 Material Design 系列的第四篇分享了,对 Material Design 还不是太清楚的可以先看前几篇博文大概的了解一下,今天我们主要通过实现顶部和底部导航栏的功能来了解又一个 Material Design 的控件 TabLayout 的使用,在移动应用开发中,由于屏膜大小的限制,使得顶部、底部 Tab 导航栏的使用非常的广泛,相信大家在实际开发过程中也是经常遇到,以前可以通过 RadioGroup,FragmentTabHost 以及后来的 Bottom Navigation 等方法来实现,现在我们可以使用 Design Support library 库的 TabLayout 来实现了


一、底部导航栏


我们先上效果图吧


android 设置dialog显示时顶部状态栏图标显示颜色 android顶部tab导航栏_Material Design

android 设置dialog显示时顶部状态栏图标显示颜色 android顶部tab导航栏_TabLayout_02


      上面就是我们的效果图,接下来我们来通过代码一步步实现上面的效果,这里我使用了一点 DataBinding 的知识,如果对 DataBinding 还没有一点了解的可以去看我以前的博文简单的了解一下(这里强烈的建议大家一定去了解一下 DataBinding 相关的知识),好了开始代码演绎吧

Android DataBinding 详解

1.首先我们先来创建所需要的 Fragement 以及相对应的布局文件,这里我们拿首页 HomeFragment 来做示例:

首先创建 fragment_home.xml 布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="首页"
            android:textSize="40sp" />

    </LinearLayout>
</layout>

       这里非常简单,一个线性布局里面放一个 TextView,想必大家一看就明白,只是要注意最外层布局是 <layout> 标签,这就是刚才提到的 DataBinding 需要的,和我们平时的布局不太一样的一点

接下来创建 HomeFragment 类:

package com.example.qiudengjiao.tablayout.Module.home;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.qiudengjiao.tablayout.Base.BaseFragment;
import com.example.qiudengjiao.tablayout.R;
import com.example.qiudengjiao.tablayout.databinding.FragmentHomeBinding;

/**
 * 主界面 - 首页Fragment
 * Created by qiudengjiao on 2017/5/6.
 */

public class HomeFragment extends BaseFragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        FragmentHomeBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_home, container, false);
        
        return binding.getRoot();
    }
}



         我们来简单看一下上面的这段代码,和我们平时写的其实差不太多,使用 DataBindingUtil 调用 inflate() 方法来加载我们刚写好的布局,返回一个 FramentHomeBinding 对象,这个对象是自动生成的,这个对象的生成和布局文件的名字是有关系的,生成规则就是布局文件的名字加 Binding,更详细的大家可以去看我关于 DataBinding 的博文,这里不再展开详细描述,再就是  return binding.getRoot(),这句代码就是返回上面绑定的布局文件,到这里我们的首页 HomeFragment 就创建完成了,剩下了热卖、购物车、分类、我的依次类推,我们一一创建完成,这里不再描述,如下:


android 设置dialog显示时顶部状态栏图标显示颜色 android顶部tab导航栏_底部导航栏_03


       这样我们所需要的所有 Fragment 就全部创建完成,需要多少个 Tab 就创建多少个相对应的 Fragment,这个大家根据自己的项目来定

接下来我们来创建主布局文件 activity_main.xml 代码如下:


这里需要注意的是需要先在 app/build.gradle 中添加如下依赖,如果你的项目中还没有添加的话:


compile 'com.android.support:design:25.1.0'



<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.qiudengjiao.tablayout.MainActivity">

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

        </android.support.v4.view.ViewPager>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#d6d2d2" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            app:tabIndicatorHeight="0dp"
            app:tabIndicatorColor="#33e0ff">


        </android.support.design.widget.TabLayout>
    </LinearLayout>
</layout>



       和上面一样,最外层是 layout 布局,接着里面嵌套了一个线性布局,线性布局里面主要放了一个 ViewPager,一个 TabLayout,也比较简单,接下来就是定义适配器 Adapter 类,这里我们暂且命名为 MainTabAdapter,代码如下:


/**
 * 适配器
 * Created by qiudengjiao on 2017/5/6.
 */

public class MainTabAdapter extends FragmentPagerAdapter {

    private MainActivity mContext;

    private HomeFragment homeFragment;
    private HotFragment hotFragment;
    private CategoryFragment categoryFragment;
    private CartFragment cartFragment;
    private MineFragment mineFragment;

    public MainTabAdapter(MainActivity mainActivity) {
        super(mainActivity.getSupportFragmentManager());
        this.mContext = mainActivity;

        //初始化Fragment
        homeFragment = new HomeFragment();
        hotFragment = new HotFragment();
        categoryFragment = new CategoryFragment();
        cartFragment = new CartFragment();
        mineFragment = new MineFragment();
    }

    @Override
    public Fragment getItem(int position) {
        if (position == 0) {
            return homeFragment;
        } else if (position == 1) {
            return hotFragment;
        } else if (position == 2) {
            return categoryFragment;
        } else if (position == 3) {
            return cartFragment;
        } else if (position == 4) {
            return mineFragment;
        }
        return null;
    }

    @Override
    public int getCount() {
        return 5;
    }
}



        这里我们继承 FragmentPagerAdapter,并且在构造函数里初始化了我们刚才准备好的 Fragment,还实现了它的 getItem() 和 getCount() 方法,在 getItem() 方法中返回相应的 Fragment,getCount() 方法中返回相对应的个数


接下来我们开始来定义 MainAcitivity,代码如下:


package com.example.qiudengjiao.tablayout;

import android.databinding.DataBindingUtil;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.qiudengjiao.tablayout.Adapter.MainTabAdapter;
import com.example.qiudengjiao.tablayout.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    private FragmentPagerAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        //初始化适配器
        mAdapter = new MainTabAdapter(this);

        binding.viewPager.setAdapter(mAdapter);


        //将TabLayout和ViewPager关联起来
        binding.tabLayout.setupWithViewPager(binding.viewPager);

        binding.viewPager.setOffscreenPageLimit(5);


        initTab();
    }

    /**
     * 设置添加Tab
     */
    private void initTab() {

        binding.tabLayout.getTabAt(0).setCustomView(R.layout.tab_home);
        binding.tabLayout.getTabAt(1).setCustomView(R.layout.tab_hot);
        binding.tabLayout.getTabAt(2).setCustomView(R.layout.tab_category);
        binding.tabLayout.getTabAt(3).setCustomView(R.layout.tab_cart);
        binding.tabLayout.getTabAt(4).setCustomView(R.layout.tab_mine);

        //默认选中的Tab
        binding.tabLayout.getTabAt(0).getCustomView().setSelected(true);

    }

}



       这里的代码其实也挺干净简单,首先初始化适配器,并通过 setupWithViewPager() 方法将 TabLayout 和 ViewPager 关联起来,然后初始化 Tab,并通过 getTabAt() 方法返回指定的 Tab 选项,然后通过 setCustomView() 把自定义的布局设置到此 Tab 选项卡上,注意这里的布局是我们自己定义的,下面会讲解,然后通过 setSelected() 方法来设置默认选中的 Tab 选项卡,接下来我们来看一下我们自定义的布局,还是拿一个 tab_home.xml 来做演示,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:src="@drawable/selector_tab_home" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="首页"
        android:textColor="@color/selector_tab_text"
        android:textSize="14sp" />

</LinearLayout>



       可以看到在线性布局里面包含了一个 ImageView 和一个 TextView 这个 ImageView 就是我们地步导航栏 Tab 选项卡中的图标,TextView 就是相对应的文字,这里需要注意的就是需要用选择器 selector 的形式来设置图片和文字,代码如下:

图片选择器:selector_tab_home.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<item android:drawable="@mipmap/icon_home" android:state_selected="false"/>
<item android:drawable="@mipmap/icon_home_press" android:state_selected="true"/>

</selector>



文字选择器:selector_tab_text.xml,需要注意的是这里文字选择器只需要一个就可以了,因为不管那个 Tab 选项卡的文字颜色变化都是一样的

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="#999999" android:state_selected="false"/>
    <item android:color="#e22b2b" android:state_selected="true"/>
</selector>



剩下的图片选择器请大家自行编写,写完如下:


android 设置dialog显示时顶部状态栏图标显示颜色 android顶部tab导航栏_TabLayout_04


       到这里我们的底部导航栏就完成了,实现效果就是我们文章刚开始贴出的效果,到这里还有一个问题,那就是我们的底部导航栏是可以滑动的,但往往在实际项目中是不能滑动的,这个也好解决,就是我们需要去自定义一个 ViewPager,把滑动功能禁止掉就可以了,下面贴上自定义的 ViewPager,代码如下:


/**
 * 不可滑动的ViewPager
 * Created by qiudengjiao on 2017/1/20.
 */

public class NoScrollViewPager extends ViewPager {


    public boolean canScroll = false;

    public NoScrollViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NoScrollViewPager(Context context) {
        super(context);
    }
    

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        return canScroll && super.onTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return canScroll && super.onInterceptTouchEvent(ev);
    }

    @Override
    public void setCurrentItem(int item) {
        setCurrentItem(item, false);
    }
}



        这里我们自定义了自己的 NoScrollViewPager,继承 ViewPager 并从写了 onTouchEvent() 和 onInterceptTouchEvent() 方法,这两个方法的返回值都是 boolean 类型的,只需要改为 false,ViewPager 就不会消耗掉手指的滑动事件了,也就是说就不会再传递给上层View 去处理该滑动事件了,如果大家想让 ViewPager 从新滑动,只需要把 pulic boolean canSoroll = false 改为 ture 即可,如果大家想彻底的搞明白,那么就需要去搞清楚 View 的事件分发,这里就不详细介绍了

接下来就是在 activity_main.xml 布局文件中去引用我们自己定义的 ViewPager,代码如下:


<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.qiudengjiao.tablayout.MainActivity">

        <com.example.qiudengjiao.tablayout.view.NoScrollViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">

        </com.example.qiudengjiao.tablayout.view.NoScrollViewPager>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#d6d2d2" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            app:tabIndicatorHeight="0dp"
            app:tabIndicatorColor="#33e0ff">


        </android.support.design.widget.TabLayout>
    </LinearLayout>
</layout>



到这里我们就把 ViewPager 左右滑动禁止了,底部导航栏也就算是彻底完成了


二、顶部导航栏


到这里大家想必顶部导航栏心里也就有数了,这里我们还是简单的来看一下,效果图如下:


android 设置dialog显示时顶部状态栏图标显示颜色 android顶部tab导航栏_Material Design_05


接下来我们就上代码,这里我们首先创建顶部 Tab 对应的 Fragment,代码就不写了,和上面的是一样的,接下来我们来看 activity_top_layout.xml 布局文件:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <android.support.design.widget.TabLayout
            android:id="@+id/top_tabLayout"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            app:tabSelectedTextColor="#a61849da"
            app:tabTextColor="#767474"
            app:tabIndicatorColor="#6cefe2"
            app:tabIndicatorHeight="2dp">

        </android.support.design.widget.TabLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#d6d2d2" />

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


        </android.support.v4.view.ViewPager>

    </LinearLayout>

</layout>



是不是和我们上面的基本一样,这是上下调换了一下顺序,一样的内容就不重复来讲解了,不过这里简单说下 TabLayout 中的几个属性用法:

  • 导航栏背景颜色:android:background
  • 指示器颜色:app:tabIndicatorColor
  • 普通状态下文字颜色:app:tabTextColor
  • 选中时文字的颜色:app:tabSelectedTextColor
  • 是否可滑动( fixed:固定  scrollable:可滑动 ):app:tabMode

等等,属性就简单介绍这些,更多的项目中用到的大家可以自己尝试,这里就不一一全部列举了


接下来我们就来写适配器了,这里我们命名为 TopAdapter,相关代码如下:

/**
 * 顶部导航栏 Adapter
 * Created by qiudengjiao on 2017/5/8.
 */

public class TopAdapter extends FragmentPagerAdapter {

    //Fragment列表
    private List<Fragment> mFragment;

    //Tab名列表
    private List<String> mTitle;

    public TopAdapter(FragmentManager fm, List<Fragment> fragments, List<String> titles) {
        super(fm);

        this.mFragment = fragments;
        this.mTitle = titles;
    }

    @Override
    public Fragment getItem(int position) {
        return mFragment.get(position);
    }

    @Override
    public int getCount() {
        return mTitle.size();
    }

    //此方法用来显示Tab上的名字
    @Override
    public CharSequence getPageTitle(int position) {
        return mTitle.get(position);
    }
}



布局文件和 Adapter 都准备好了,我们该写 Activity了,来看 Activity 中的代码:

package com.example.qiudengjiao.tablayout.top;

import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import com.example.qiudengjiao.tablayout.Adapter.TopAdapter;
import com.example.qiudengjiao.tablayout.Base.BaseActivity;
import com.example.qiudengjiao.tablayout.R;
import com.example.qiudengjiao.tablayout.databinding.ActivityTopLayoutBinding;
import java.util.ArrayList;
import java.util.List;

/**
 * 顶部导航栏
 * Created by qiudengjiao on 2017/5/8.
 */

public class TopLayoutActivity extends BaseActivity {

    private FragmentPagerAdapter mAdapter;
    private List<Fragment> mFragment;
    private List<String> mTitle;

    private ActivityTopLayoutBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        //隐藏掉整个ActionBar
        getSupportActionBar().hide();
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_top_layout);

        initTabFragment();
    }

    private void initTabFragment() {

        //初始化Fragment
        Fragment1 fragment1 = new Fragment1();
        Fragment2 fragment2 = new Fragment2();
        Fragment3 fragment3 = new Fragment3();

        //将Fragment装进列表中
        mFragment = new ArrayList<>();
        mFragment.add(fragment1);
        mFragment.add(fragment2);
        mFragment.add(fragment3);

        //将名称添加daoTab列表
        mTitle = new ArrayList<>();
        mTitle.add("Tab1");
        mTitle.add("Tab2");
        mTitle.add("Tab3");

        //为TabLayout添加Tab名称
        binding.topTabLayout.addTab(binding.topTabLayout.newTab().setText(mTitle.get(0)));
        binding.topTabLayout.addTab(binding.topTabLayout.newTab().setText(mTitle.get(1)));
        binding.topTabLayout.addTab(binding.topTabLayout.newTab().setText(mTitle.get(2)));


        mAdapter = new TopAdapter(this.getSupportFragmentManager(), mFragment, mTitle);

        //ViewPager加载Adapter
        binding.topViewPager.setAdapter(mAdapter);

        //TabLayout加载ViewPager
        binding.topTabLayout.setupWithViewPager(binding.topViewPager);
    }
}



       由于这里我们在代码里都加了相关的注释,还是非常清楚的,就不在详细的去介绍代码,这样顶部导航栏也就实现了,具体到项目中有用到不同的样式也是完变不离其宗,大家自行处理就好了

到这里我们项目中常用的的底部和顶部导航栏就都实现了,是不是觉得比以前的实现方法简单了许多

源码Demo:点击打开链接

谢谢阅读,如有问题请指出