Android 底部导航栏的两种实现(附源码)(通过FragmentTabHost + Fragment实现)

        Android底部导航栏是非常常见的功能,UI设计大致分为两种:第一种纯小图+文字组合;第二种除了小图+文字组合外,还将中间设置大图。两种方式都有很多APP使用,并无优劣,根据需求选用即可。在这里我用我最熟悉的FragmentTabHost+Fragment实现上述功能。

        注:文章末尾附项目源码下载链接。

      效果展示

        主要功能包括:FragmentTabHost的使用、图片选择器、文字选择器、沉浸式状态栏、带图片的底部导航栏等。

                

      第一种纯小图+文字组合实现

        页面布局

        以下是Activity的布局文件,主要通过FragmentTabHost+FrameLayout实现底部导航栏,用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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/fl_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0.4dp"
        android:background="#99cccccc"
        android:visibility="visible" />

    <android.support.v4.app.FragmentTabHost
        android:id="@+id/tabHost"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="2dp"
        android:paddingTop="2dp"
        android:visibility="visible" />
</LinearLayout>

        创建多个图片选择器

        在res的drawable文件夹下,创建与模块数量相同的图片选择器,使用户可以通过底部导航栏来区分,模块的选中和未选中状态。

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

        创建一个文字选择器

        在res的color文件夹(自行创建)下,创建一个文字选择器,用于底部导航栏的字体变色,一般创建一个即可。

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

        创建底部导航栏Tab的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:gravity="center">

    <TextView
        android:id="@+id/tv_indicator"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:drawablePadding="4dp"
        android:drawableTop="@drawable/selector_tab_home"
        android:gravity="center"
        android:text="首页"
        android:textColor="@color/selector_tab_text"
        android:textSize="10sp" />
</RelativeLayout>

        创建多个Fragment(继承v4包下的Fragment)

        创建与模块数量相同的的Fragment,首页的UI、功能等都将在这些Fragment中编写,并且由于Activity实现了沉浸式状态栏(4.4以上),因此布局需要做适配。

package com.wy.fragmenttabhosttwoimplementions.fragment;

import android.content.Context;
import android.os.Build;
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 com.wy.fragmenttabhosttwoimplementions.R;

/**
 * 首页
 */

public class HomeTabFragment extends Fragment {

    private Context context;
    private View view;

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

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        context = getActivity();
        if (view == null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                view = View.inflate(context, R.layout.fragment_tab_home_v19, null);
            } else {
                view = View.inflate(context, R.layout.fragment_tab_home, null);
            }
        }
        return view;
    }
}

        创建FragmentTabHost的枚举Tab

        用于定义底部导航栏和Fragment的对应关系,并将创建的图片选择器与Fragment传进去。同时提供了构造函数、get、set方法供后续使用。

package com.wy.fragmenttabhosttwoimplementions.menu;

import com.wy.fragmenttabhosttwoimplementions.R;
import com.wy.fragmenttabhosttwoimplementions.fragment.HomeTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.OrderTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.StatisticsTabFragment;

public enum MainTabs {
    Home(0,"首页", R.drawable.selector_tab_home, HomeTabFragment.class),
    Classify(1,"订单", R.drawable.selector_tab_home, OrderTabFragment.class),
    ShoppingCar(2,"统计", R.drawable.selector_tab_home, StatisticsTabFragment.class);

    private int i;
    private String name;
    private int icon;
    private Class<?> cla;

     MainTabs(int i, String name, int icon, Class<?> cla) {
        this.i = i;
        this.name = name;
        this.icon = icon;
        this.cla = cla;
    }

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public Class<?> getCla() {
        return cla;
    }

    public void setCla(Class<?> cla) {
        this.cla = cla;
    }
}

        初始化FragmentTabHost

        前边创建了那么多文件,终于到了整合使用的时候了,这也是实现底部导航栏的最后一步。首先将FragmentTabHost和FrameLayout关联起来,并去掉分割线(也可以保留,看需求),然后通过枚举类MainTabs添加Tab及其对应的Fragment,最后将Tab添加到FragmentTabHost中。大功告成,可以看看效果了。

/**
     * 初始化FragmentTabHost
     */
    private void initFragmentTabHost() {
        //初始化tabHost
        FragmentTabHost tabHost = (FragmentTabHost) findViewById(R.id.tabHost);
        //将tabHost和FragmentLayout关联
        tabHost.setup(getApplicationContext(), getSupportFragmentManager(), R.id.fl_content);

        //去掉分割线
        if (Build.VERSION.SDK_INT > 10) {
            tabHost.getTabWidget().setShowDividers(0);
        }
        //添加tab和其对应的fragment
        MainTabs[] tabs = MainTabs.values();
        for (int i = 0; i < tabs.length; i++) {
            MainTabs mainTabs = tabs[i];
            TabHost.TabSpec tabSpec = tabHost.newTabSpec(mainTabs.getName());

            View indicator = View.inflate(getApplicationContext(), R.layout.tab_indicator, null);
            TextView tv_indicator = (TextView) indicator.findViewById(R.id.tv_indicator);
            Drawable drawable = getApplicationContext().getResources().getDrawable(mainTabs.getIcon());

            tv_indicator.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);
            tv_indicator.setText(mainTabs.getName());
            tabSpec.setIndicator(indicator);
            tabHost.addTab(tabSpec, mainTabs.getCla(), null);
        }
    }

      第二种带图的底部导航栏实现

        页面布局

        以下是Activity的布局文件,主要通过FragmentTabHost+FrameLayout实现底部导航栏,用View当做分割线,区分主体和底部导航栏。ImageView为中间的大图。

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

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

        <FrameLayout
            android:id="@+id/fl_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />

        <View
            android:layout_width="match_parent"
            android:layout_height="0.4dp"
            android:background="#99cccccc"
            android:visibility="visible" />

        <android.support.v4.app.FragmentTabHost
            android:id="@+id/tabhost"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <ImageView
        android:id="@+id/listen"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:contentDescription="@null"
        android:paddingBottom="5dp"
        android:src="@drawable/tab_center_img" />
</RelativeLayout>

        图片选择器、文字选择器、底部导航栏Tab的布局文件、Fragment文件均与第一种相同,参照上边的即可。

        创建FragmentTabHost的枚举Tab

        与第一种不同的是中间的Classify的内容修改,这样看着更加清晰一下。也可以不修改,反正初始化FragmentTabHost的时候也不用。(推荐修改,更清晰)

package com.wy.fragmenttabhosttwoimplementions.menu;

import com.wy.fragmenttabhosttwoimplementions.R;
import com.wy.fragmenttabhosttwoimplementions.fragment.HomeTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.OrderTabFragment;
import com.wy.fragmenttabhosttwoimplementions.fragment.StatisticsTabFragment;

public enum MainTabsSecond {
    Home(0,"首页", R.drawable.selector_tab_home, HomeTabFragment.class),
    Classify(1,"", R.mipmap.ic_launcher, null),
    ShoppingCar(2,"统计", R.drawable.selector_tab_home, StatisticsTabFragment.class);

    private int i;
    private String name;
    private int icon;
    private Class<?> cla;

     MainTabsSecond(int i, String name, int icon, Class<?> cla) {
        this.i = i;
        this.name = name;
        this.icon = icon;
        this.cla = cla;
    }

    public int getI() {
        return i;
    }

    public void setI(int i) {
        this.i = i;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public Class<?> getCla() {
        return cla;
    }

    public void setCla(Class<?> cla) {
        this.cla = cla;
    }
}

         初始化FragmentTabHost

        与第一种不同的是需要判断,当tab替换成ImageView时,隐藏tab并保持占位,其它的跟第一种相同。最后再为中间的ImageView设置点击事件监听即可。

/**
     * 初始化FragmentTabHost
     */
    private void initFragmentTabHost() {
        //初始化tabHost
        FragmentTabHost tabHost = (FragmentTabHost) findViewById(R.id.tabhost);
        ImageView listen = (ImageView) findViewById(R.id.listen);
        //将tabHost和FragmentLayout关联
        tabHost.setup(getApplicationContext(), getSupportFragmentManager(), R.id.fl_content);

        //去掉分割线
        if (Build.VERSION.SDK_INT > 10) {
            tabHost.getTabWidget().setShowDividers(0);
        }
        //添加tab和其对应的fragment
        MainTabsSecond[] tabs = MainTabsSecond.values();
        for (int i = 0; i < tabs.length; i++) {
            MainTabsSecond mainTabs = tabs[i];
            TabHost.TabSpec tabSpec = tabHost.newTabSpec(mainTabs.getName());

            View indicator = View.inflate(getApplicationContext(), R.layout.tab_indicator, null);
            TextView tv_indicator = (TextView) indicator.findViewById(R.id.tv_indicator);
            Drawable drawable = getApplicationContext().getResources().getDrawable(mainTabs.getIcon());

            //当tab替换成ImageView时,隐藏tab并保持占位
            if (i == 1) {
                tv_indicator.setVisibility(View.INVISIBLE);
            } else {
                tv_indicator.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null);
                tv_indicator.setText(mainTabs.getName());
            }
            tabSpec.setIndicator(indicator);
            tabHost.addTab(tabSpec, mainTabs.getCla(), null);

            //图片的点击事件
            listen.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(getApplicationContext(), "点击了图片", Toast.LENGTH_SHORT).show();
                    //替换Fragment
                    //不推荐这么干,因为将tab替换成了ImageView
//                    getSupportFragmentManager().beginTransaction()
//                            .add(R.id.fl_content, new OrderTabFragment()).commit();
                }
            });
        }
    }

      小结

        经过以上的种种努力,终于实现了底部导航栏的两种效果,试一下效果感觉还是挺好的。当然这只是简单的Demo,主体UI、功能就等待我们的后续开发了。底部导航栏当然还有其它的实现方式,有更好的idea可以相互交流,不过适合自己的才是最好的,坚持使用一种并不断的研究完善,相信会更好。 

      附:

        项目源码下载地址(https://download.csdn.net/download/qq941263013/10554431)

        GitHub项目地址(https://github.com/wangyang0313/FragmentTabHostTwoImplementions)

猜你喜欢

转载自blog.csdn.net/qq941263013/article/details/81136064