本文(争取做到)Android 最全的底部导航栏实现方法.

现在写了4个主要方法.

官方方法. 官方的 BottomNavigationActivity

使用Android studio 新建一个工程,可以选择到这个BottomNavigationActivity。

或者在工程里新建BottomNavigationActivity

会创建出这么几个文件,分别介绍一下。

navigation/mobile_navigation.xml 如下

<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/mobile_navigation"app:startDestination="@+id/navigation_home"><fragmentandroid:id="@+id/navigation_home"android:name="com.yao.tab.ui.home.HomeFragment"android:label="@string/title_home"tools:layout="@layout/fragment_home" /><fragmentandroid:id="@+id/navigation_dashboard"android:name="com.yao.tab.ui.dashboard.DashboardFragment"android:label="@string/title_dashboard"tools:layout="@layout/fragment_dashboard" /><fragmentandroid:id="@+id/navigation_notifications"android:name="com.yao.tab.ui.notifications.NotificationsFragment"android:label="@string/title_notifications"tools:layout="@layout/fragment_notifications" />
</navigation>

这里是使用 xml 配置声明3个 Fragment 的地方。name 属性表示对应这个 Fragment 的包名+类名,label 属性表示标题栏显示什么文字,tools:layout 属性表示这个 fragment 使用哪个布局(tools属性只是为了 Android studio 的界面预览,实际在每个Fragment之间会inflate这个布局)。

menu/bottom_nav_menu.xml 如下

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"><itemandroid:id="@+id/navigation_home"android:icon="@drawable/ic_home_black_24dp"android:title="@string/title_home" /><itemandroid:id="@+id/navigation_dashboard"android:icon="@drawable/ic_dashboard_black_24dp"android:title="@string/title_dashboard" /><itemandroid:id="@+id/navigation_notifications"android:icon="@drawable/ic_notifications_black_24dp"android:title="@string/title_notifications" /></menu>

声明每个 Tab 的 icon 图片,title 文字,还有 id 表示应该对应哪个fragment,这里的 id 应该跟 navigation/mobile_navigation.xml  声明的 fragment id 一样。

layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingTop="?attr/actionBarSize"><com.google.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/nav_view"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_marginStart="0dp"android:layout_marginEnd="0dp"android:background="?android:attr/windowBackground"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:menu="@menu/bottom_nav_menu" /><fragmentandroid:id="@+id/nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"android:layout_height="match_parent"app:defaultNavHost="true"app:layout_constraintBottom_toTopOf="@id/nav_view"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:navGraph="@navigation/mobile_navigation" /></androidx.constraintlayout.widget.ConstraintLayout>

使用 BottomNavigationView 控件配合 fragment 使用。

BottomNavigationView 需要标明 app:menu 属性 app:menu="@menu/bottom_nav_menu"

fragment 需要标明 app:navGraph 属性 app:navGraph="@navigation/mobile_navigation"

MainActivity.java 如下

package com.yao.tab;import android.os.Bundle;import com.google.android.material.bottomnavigation.BottomNavigationView;import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);BottomNavigationView navView = findViewById(R.id.nav_view);// Passing each menu ID as a set of Ids because each// menu should be considered as top level destinations.AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications).build();NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);NavigationUI.setupWithNavController(navView, navController);}}

findViewById 找到布局里面的 BottomNavigationView。

初始化并设置好 NavController,代表 各个fragment 与其切换的控制逻辑。

使用 NavigationUI 把 BottomNavigationView 和 NavController 关联。

Bottom Navigation是5.0(API level 21)推出的一种符合MD规范的导航栏规范。

详细规范在这篇文章可以有讲。

原文: https://material.google.com/components/bottom-navigation.html

译文:https://modao.cc/posts/3068

除了官方推出的这个 BottomNavigationView,还有一些民间的开源库也挺不错。

这些开源库的第一优势是有动画效果,第二优势是配合 CoordinatorLayout 和一些相应的控件,可以实现滑动伸缩的效果。

GitHub - aurelhubert/ahbottomnavigation

Github - Ashok-Varma/BottomNavigation

我有个小项目就用到了ahbottomnavigation。

方法一. RadioButton + ViewPager + FragmentPagerAdapter

它的xml如下

<?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"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><androidx.viewpager.widget.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><androidx.cardview.widget.CardViewandroid:layout_width="match_parent"android:layout_height="55dp"app:cardElevation="12dp"><RadioGroupandroid:layout_width="match_parent"android:layout_height="55dp"android:background="?android:attr/windowBackground"android:baselineAligned="false"android:orientation="horizontal"><RadioButtonandroid:id="@+id/rb_home"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:button="@null"android:checked="true"android:drawableTop="@drawable/ic_home"android:gravity="center_horizontal"android:paddingTop="8dp"android:text="@string/title_home"android:textColor="@drawable/tab_text_view_color" /><RadioButtonandroid:id="@+id/rb_dashboard"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:button="@null"android:drawableTop="@drawable/ic_dashboard"android:gravity="center_horizontal"android:paddingTop="8dp"android:text="@string/title_dashboard"android:textColor="@drawable/tab_text_view_color" /><RadioButtonandroid:id="@+id/rb_notifications"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:button="@null"android:drawableTop="@drawable/ic_notifications"android:gravity="center_horizontal"android:paddingTop="8dp"android:text="@string/title_notifications"android:textColor="@drawable/tab_text_view_color" /></RadioGroup></androidx.cardview.widget.CardView></LinearLayout>

Activity.java 如下

package com.yao.tab;import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.View;
import android.view.Window;
import android.widget.RadioButton;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;import com.yao.tab.ui.dashboard.DashboardFragment;
import com.yao.tab.ui.home.HomeFragment;
import com.yao.tab.ui.notifications.NotificationsFragment;
import com.yao.tab.util.ResUtil;public class Tab1Activity extends AppCompatActivity implements View.OnClickListener {private static final int FRAGMENT_COUNT = 3;private ViewPager mViewPager;private RadioButton mRbHome;private RadioButton mRbDashboard;private RadioButton mRbNotifications;private SparseArray<Fragment> fragmentList = new SparseArray<>(3);@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_tab_1);mViewPager = findViewById(R.id.view_pager);mRbHome = findViewById(R.id.rb_home);mRbDashboard = findViewById(R.id.rb_dashboard);mRbNotifications = findViewById(R.id.rb_notifications);//设置 RadioButton 图标的大小Drawable drawableHome = ResUtil.getDrawable(R.drawable.ic_home);drawableHome.setBounds(0, 0, ResUtil.getDimen(R.dimen.tab_icon_size), ResUtil.getDimen(R.dimen.tab_icon_size));mRbHome.setCompoundDrawables(null, drawableHome, null, null);Drawable drawableDashboard = ResUtil.getDrawable(R.drawable.ic_dashboard);drawableDashboard.setBounds(0, 0, ResUtil.getDimen(R.dimen.tab_icon_size), ResUtil.getDimen(R.dimen.tab_icon_size));mRbDashboard.setCompoundDrawables(null, drawableDashboard, null, null);Drawable drawableNotifications = ResUtil.getDrawable(R.drawable.ic_notifications);drawableNotifications.setBounds(0, 0, ResUtil.getDimen(R.dimen.tab_icon_size), ResUtil.getDimen(R.dimen.tab_icon_size));mRbNotifications.setCompoundDrawables(null, drawableNotifications, null, null);mRbHome.setOnClickListener(this);mRbDashboard.setOnClickListener(this);mRbNotifications.setOnClickListener(this);//设置左边和右边各缓存多少个页面。//设置成2后,可以保证3个Tab滑动到哪个Tab下,其他Tab都不会被回收。mViewPager.setOffscreenPageLimit(2);mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageSelected(int position) {if (position == 0) {onClick(mRbHome);} else if (position == 1) {onClick(mRbDashboard);} else if (position == 2) {onClick(mRbNotifications);}}@Overridepublic void onPageScrolled(int arg0, float arg1, int arg2) {}@Overridepublic void onPageScrollStateChanged(int arg0) {}});FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {@Overridepublic int getCount() {return FRAGMENT_COUNT;}@NonNull@Overridepublic Fragment getItem(int position) {//https://mp.weixin.qq.com/s/MOWdbI5IREjQP1Px-WJY1Qif (position == 0) {return new HomeFragment();} else if (position == 1) {return new DashboardFragment();} else if (position == 2) {return new NotificationsFragment();} else {throw new IllegalStateException("FragmentPagerAdapter getItem position is illegal");}}};mViewPager.setAdapter(adapter);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.rb_home:mViewPager.setCurrentItem(0);mRbHome.setChecked(true);break;case R.id.rb_dashboard:mViewPager.setCurrentItem(1);mRbDashboard.setChecked(true);break;case R.id.rb_notifications:mViewPager.setCurrentItem(2);mRbNotifications.setChecked(true);break;default:break;}}
}

1.RadioButton 最恶心的地方在于,不能在xml中设置它的图片展示大小。所以要使用 Drawable.setBounds 的方式在代码中设置其大小。

2.mViewPager.setOffscreenPageLimit(2); 可以设置左边和右边各缓存多少个页面,这样3个Tab就不会被回收了。

3.ViewPager 有滑动事件,需要在 addOnPageChangeListener 的 onPageSelected 回调里,切换对应的 Fragment。

方法二. TabLayout + ViewPager + FragmentPagerAdapter

它的xml如下,为了实现分界线效果,加了一层CardView进行包裹。

<?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"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><androidx.viewpager.widget.ViewPagerandroid:id="@+id/view_pager"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><androidx.cardview.widget.CardViewandroid:layout_width="match_parent"android:layout_height="wrap_content"app:cardElevation="12dp"><com.google.android.material.tabs.TabLayoutandroid:id="@+id/tab_layout"style="@style/MyTabLayoutStyle"app:tabTextAppearance="@style/MyTabLayoutTextAppearance"android:layout_width="match_parent"android:layout_height="55dp"android:background="?android:attr/windowBackground" /></androidx.cardview.widget.CardView></LinearLayout>

Activity.java 如下

package com.yao.tab;import android.os.Bundle;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;import com.google.android.material.tabs.TabLayout;
import com.yao.tab.ui.dashboard.DashboardFragment;
import com.yao.tab.ui.home.HomeFragment;
import com.yao.tab.ui.notifications.NotificationsFragment;import java.util.Objects;public class Tab2Activity extends AppCompatActivity {private static final int FRAGMENT_COUNT = 3;private ViewPager mViewPager;private TabLayout mTabLayout;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_tab_2);mViewPager = findViewById(R.id.view_pager);mTabLayout = findViewById(R.id.tab_layout);mTabLayout.addTab(mTabLayout.newTab());mTabLayout.addTab(mTabLayout.newTab());mTabLayout.addTab(mTabLayout.newTab());//设置左边和右边各缓存多少个页面。//设置成2后,可以保证3个Tab滑动到哪个Tab下,其他Tab都不会被回收。mViewPager.setOffscreenPageLimit(2);mTabLayout.setupWithViewPager(mViewPager, false);FragmentPagerAdapter adapter = new FragmentPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {@Overridepublic int getCount() {return FRAGMENT_COUNT;}@NonNull@Overridepublic Fragment getItem(int position) {//https://mp.weixin.qq.com/s/MOWdbI5IREjQP1Px-WJY1Qif (position == 0) {return new HomeFragment();} else if (position == 1) {return new DashboardFragment();} else if (position == 2) {return new NotificationsFragment();} else {throw new IllegalStateException("FragmentPagerAdapter getItem position is illegal");}}};mViewPager.setAdapter(adapter);//需要放在 mTabLayout.setupWithViewPager 后面,否则标题会被替换掉。Objects.requireNonNull(mTabLayout.getTabAt(0)).setText(R.string.title_home).setIcon(R.drawable.ic_home);Objects.requireNonNull(mTabLayout.getTabAt(1)).setText(R.string.title_dashboard).setIcon(R.drawable.ic_dashboard);Objects.requireNonNull(mTabLayout.getTabAt(2)).setText(R.string.title_notifications).setIcon(R.drawable.ic_notifications);}
}

TabLayout.addTab 添加3个 Tab。执行 TabLayout.setupWithViewPager 后,再设置每个 Tab 的图片和文字。ViewPager 和 FragmentPagerAdapter 配合使用作为页面。

TabLayout 还有 setCustomView(int) 和 setCustomView(View) 方法,可以自定义 Tab 布局。

以前还有个 FragmentTabHost 可以使用的,现在已经声明被废弃了,被这个 TabLayout 取代。

ps:根据「https://mp.weixin.qq.com/s/MOWdbI5IREjQP1Px-WJY1Q」,请注意正确使用使用FragmentPagerAdapter写法。

方法三. 使用 FragmentTransaction.hide(Fragment) 和 show(Fragment)

它的xml如下,底部是4个 LinearLayout 作为 Tab 使用,LinearLayout 里面是一个 ImageView 和一个 TextView。写成这种基础布局的好处是可以很方便地调节文字和图片的各种属性,还可以很方便在每个 Tab 里添加内容,比如小红点提醒或者数字提醒。同样为了实现分界线效果,加了一层CardView进行包裹。

<?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"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><FrameLayoutandroid:id="@+id/layout_container"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><androidx.cardview.widget.CardViewandroid:layout_width="match_parent"android:layout_height="55dp"app:cardElevation="12dp"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="55dp"android:background="?android:attr/windowBackground"android:baselineAligned="false"android:orientation="horizontal"><LinearLayoutandroid:id="@+id/layout_home"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_home"android:layout_width="@dimen/tab_icon_size"android:layout_height="@dimen/tab_icon_size"android:background="#00000000"android:contentDescription="@string/app_name"android:src="@drawable/ic_home" /><TextViewandroid:id="@+id/tv_home"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/title_home"android:textColor="@drawable/tab_text_view_color" /></LinearLayout><LinearLayoutandroid:id="@+id/layout_dashboard"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_dashboard"android:layout_width="@dimen/tab_icon_size"android:layout_height="@dimen/tab_icon_size"android:background="#00000000"android:contentDescription="@string/app_name"android:src="@drawable/ic_dashboard" /><TextViewandroid:id="@+id/tv_dashboard"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/title_dashboard"android:textColor="@drawable/tab_text_view_color" /></LinearLayout><LinearLayoutandroid:id="@+id/layout_notifications"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1"android:gravity="center"android:orientation="vertical"><ImageViewandroid:id="@+id/iv_notifications"android:layout_width="@dimen/tab_icon_size"android:layout_height="@dimen/tab_icon_size"android:background="#00000000"android:contentDescription="@string/app_name"android:src="@drawable/ic_notifications" /><TextViewandroid:id="@+id/tv_notifications"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/title_notifications"android:textColor="@drawable/tab_text_view_color" /></LinearLayout></LinearLayout></androidx.cardview.widget.CardView></LinearLayout>

需要注意的是,ImageView 的 src 和 TextView 的 textColor 都需要用 selector ,可以实现选中后变色的效果。

Activity.java 如下,使用了自己封装好的 FragmentSwitchTool,里面就是使用 FragmentTransaction.hide(Fragment) 和 show(Fragment)实现的 Fragment 切换。

package com.yao.tab;import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;import com.yao.tab.ui.dashboard.DashboardFragment;
import com.yao.tab.ui.home.HomeFragment;
import com.yao.tab.ui.notifications.NotificationsFragment;
import com.yao.tab.util.FragmentSwitchTool;import java.util.ArrayList;
import java.util.List;import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;public class Tab3Activity extends AppCompatActivity {private static final String TAG = "Tab3Activity";private List<Fragment> mFragments = new ArrayList<>();private LinearLayout mLayoutHome;private LinearLayout mLayoutDashboard;private LinearLayout mLayoutNotifications;//当前被选中的Layout,包括图片和文字。图片需要是个Selector才能实现选中状态。private LinearLayout mLayoutCurrent;private FragmentSwitchTool mFragmentSwitchTool;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);requestWindowFeature(Window.FEATURE_NO_TITLE);setContentView(R.layout.activity_tab_3);mLayoutHome = findViewById(R.id.layout_home);mLayoutDashboard = findViewById(R.id.layout_dashboard);mLayoutNotifications = findViewById(R.id.layout_notifications);mFragmentSwitchTool = new FragmentSwitchTool.Builder().fragmentManager(getSupportFragmentManager()).containerId(R.id.layout_container).clickableViews(mLayoutHome, mLayoutDashboard, mLayoutNotifications).fragments(new HomeFragment(), new DashboardFragment(), new NotificationsFragment()).showAnimator(true).onClickListener(new FragmentSwitchTool.OnClickListener() {@Overridepublic void onClick(View view) {Log.e(TAG, "Tab3Activity.java - onClick() ----- view:" + getResources().getResourceName(view.getId()));}}).onDoubleClickListener(new FragmentSwitchTool.OnDoubleClickListener() {@Overridepublic void onDoubleClick(View view) {String viewName = getResources().getResourceName(view.getId());Log.e(TAG, "Tab3Activity.java - onDoubleClick() ----- view:" + viewName);Log.e(TAG, viewName + " 被双击,可以执行列表页面的回到顶部操作");}}).build();mFragmentSwitchTool.changeTag(mLayoutHome);}}

封装好的 FragmentSwitchTool 如下:

package com.yao.tab.util;import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;import com.yao.tab.R;import java.util.Arrays;
import java.util.List;import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;public class FragmentSwitchTool implements OnClickListener {public interface OnClickListener {void onClick(View view);}public interface OnDoubleClickListener {void onDoubleClick(View view);}private FragmentManager mFragmentManager;private int mContainerId;private boolean mShowAnimator;private List<View> mClickableViews; //传入用于被点击的view,比如是一个LinearLayoutprivate Fragment[] mFragments;private OnClickListener mOnClickListener;private OnDoubleClickListener mOnDoubleOnClickListener;private Fragment mCurrentFragment;private View mCurrentSelectedView;public void setClickableViews(View... clickableViews) {this.mClickableViews = Arrays.asList(clickableViews);for (View view : clickableViews) {view.setOnClickListener(this);}}public void setClickableViewsAndListener(OnClickListener onClickListener, View... clickableViews) {mOnClickListener = onClickListener;}public void setFragments(Fragment... fragments) {this.mFragments = fragments;}public void changeTag(View targetView) {if (targetView == mCurrentSelectedView) {return;}FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();Fragment targetFragment = mFragmentManager.findFragmentByTag(String.valueOf(targetView.getId()));int targetPosition = mClickableViews.indexOf(targetView);int currentPosition = mClickableViews.indexOf(mCurrentSelectedView);if (mShowAnimator) {if (targetPosition > currentPosition) {fragmentTransaction.setCustomAnimations(R.animator.slide_right_in, R.animator.slide_left_out);} else if (targetPosition < currentPosition) {fragmentTransaction.setCustomAnimations(R.animator.slide_left_in, R.animator.slide_right_out);}}if (targetFragment == null) {if (mCurrentFragment != null) {fragmentTransaction.hide(mCurrentFragment);mCurrentSelectedView.setSelected(false);}targetFragment = mFragments[targetPosition];fragmentTransaction.add(mContainerId, targetFragment, String.valueOf(targetView.getId()));} else {fragmentTransaction.hide(mCurrentFragment);mCurrentSelectedView.setSelected(false);fragmentTransaction.show(targetFragment);}fragmentTransaction.commit();mCurrentFragment = targetFragment;mCurrentSelectedView = targetView;mCurrentSelectedView.setSelected(true);}@Overridepublic void onClick(View v) {changeTag(v);}public static final class Builder {FragmentManager mFragmentManager;int mContainerId;boolean mShowAnimator;List<View> mClickableViews; //传入用于被点击的view,比如是一个LinearLayoutFragment[] mFragments;OnClickListener mOnClickListener;OnDoubleClickListener mOnDoubleClickListener;/*** 必要参数 Fragment管理类* @param fragmentManager fragmentManager* @return Builder*/public Builder fragmentManager(FragmentManager fragmentManager) {this.mFragmentManager = fragmentManager;return this;}/*** 必要参数 fragment容器的布局id* @param containerId containerId* @return Builder*/public Builder containerId(int containerId) {this.mContainerId = containerId;return this;}/*** 必要参数 Tab里的可点击控件* @param clickableViews clickableViews* @return Builder*/public Builder clickableViews(View... clickableViews) {this.mClickableViews = Arrays.asList(clickableViews);return this;}/*** 必要参数 Tab对应的Fragment* @param fragments fragments* @return Builder*/public Builder fragments(Fragment... fragments) {this.mFragments = fragments;return this;}/*** 可选参数 是否显示动画* @param showAnimator showAnimator* @return Builder*/public Builder showAnimator(boolean showAnimator) {this.mShowAnimator = showAnimator;return this;}/*** 可选参数 Tab的单击事件* @param onClickListener onClickListener* @return Builder*/public Builder onClickListener(OnClickListener onClickListener) {this.mOnClickListener = onClickListener;return this;}/*** 可选参数 Tab的双击事件* @param onDoubleClickListener onDoubleClickListener* @return Builder*/public Builder onDoubleClickListener(OnDoubleClickListener onDoubleClickListener) {this.mOnDoubleClickListener = onDoubleClickListener;return this;}public FragmentSwitchTool build() {final FragmentSwitchTool fragmentSwitchTool = new FragmentSwitchTool();fragmentSwitchTool.mFragmentManager = mFragmentManager;fragmentSwitchTool.mContainerId = mContainerId;fragmentSwitchTool.mShowAnimator = mShowAnimator;fragmentSwitchTool.mClickableViews = mClickableViews;fragmentSwitchTool.mFragments = mFragments;fragmentSwitchTool.mOnClickListener = mOnClickListener;fragmentSwitchTool.mOnDoubleOnClickListener = mOnDoubleClickListener;if (mOnDoubleClickListener == null) {for (View view : mClickableViews) {view.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {fragmentSwitchTool.changeTag(view);if (mOnClickListener != null) {mOnClickListener.onClick(view);}}});}} else {for (final View view : mClickableViews) {final GestureDetector gestureDetector = new GestureDetector(mClickableViews.get(0).getContext(), new GestureDetector.SimpleOnGestureListener(){@Overridepublic boolean onSingleTapUp(MotionEvent e) {Log.e("YAO", "onSingleTapUp:" + e.getAction());fragmentSwitchTool.changeTag(view);if (fragmentSwitchTool.mOnClickListener != null) {fragmentSwitchTool.mOnClickListener.onClick(view);}return true;}@Overridepublic boolean onDown(MotionEvent e) {return true;}@Overridepublic boolean onDoubleTap(MotionEvent e) {fragmentSwitchTool.mOnDoubleOnClickListener.onDoubleClick(view);return true;}});view.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {return gestureDetector.onTouchEvent(event);}});}}return fragmentSwitchTool;}}
}

使用 Builder 模式传入参数,4个必要参数包括 FragmentManager、fragment容器的布局 containerId、Tab里的可点击控件 clickableViews 和 Tab对应的Fragment。一些可选的参数比如是否有左右切换的动画,单击事件和双击事件回调。双击事件回调用在 RecyclerView 或者 ScrollView 的回到顶部尤其实用。

核心部分在于 changeTag(View) 方法,含有以下逻辑:

如果目前被点击的 View 是选中的 View,则不做处理。

尝试通过 FragmentManager.findFragmentByTag 获取目标 Fragment。

如果没获取到,则使用 fragmentTransaction.add 方法把目标 Fragment 添加进去。同时隐藏当前 Fragment。

如果获取到了,则执行让被点击 Fragment 显示,让当前 Fragment 隐藏。

中间有设置 View 的 selector 状态,还有可展示也可以不展示的动画逻辑。

总结:

官网方法当前不错,跟着用就完事了。

虽然老大哥 微信 还保留着能左右滑动切换4个页面,但是市面上大部分的App都没有这种滑动切换的功能了,考虑不使用ViewPager了。

如果需要考虑双击事件,小红点提醒,或者更多的自定义效果。使用方法三的这种灵活的布局,配合 FragmentSwitchTool 使用当然是不错的。

对MD设计的这种底部栏伸缩很看重,可以使用这两个第三方库或者参考其实现。

Github代码地址:

https://github.com/yaodiwei/Tab

(2019年10月更新) Android 最全的底部导航栏实现方法相关推荐

  1. 转载:Android (争取做到)最全的底部导航栏实现方法

    原文出处 标题:Android (争取做到)最全的底部导航栏实现方法 作者:野狼谷 原文链接:Android (争取做到)最全的底部导航栏实现方法 - 野狼谷 - 博客园 前言 本文(争取做到)And ...

  2. 中国地区表-mysql-包含钓鱼岛-2019年10月更新-[2]

    链接:中国地区表-mysql-包含钓鱼岛-2019年10月更新-[1] 中国地区表-mysql-包含钓鱼岛-2019年10月更新-[2] INSERT INTO `a_area` VALUES (15 ...

  3. android导航栏自动弹出,解决android 显示内容被底部导航栏遮挡的问题

    描述: 由于产品需求,要求含有EditText的界面全屏显示,最好的解决方式是使用AndroidBug5497Workaround.assistActivity(this) 的方式来解决,但是华为和魅 ...

  4. android获取刘海屏状态栏高度,Android刘海屏全面屏底部导航栏的适配

    关于Android状态栏和虚拟导航栏的适配,文章:https://blog.csdn.net/leogentleman/article/details/54566319 讲的很不错. 状态栏的适配: ...

  5. Android最好用的底部导航栏

    转载自这个项目的github地址:https://github.com/xubinhong/BottomBar 这个底部导航栏的特点: 1.告别xml中的item布局,一切icon.title统统绘制 ...

  6. Android ------ Android X 的BottomNavigationView底部导航栏

    有小伙伴问到我BottomNavigationView底部导航的问题,分享一下,底部导航栏的使用比较常见,目前常用的APP几乎都是使用底部导航栏将内容分类. 这个Android 的底部导航栏 Bott ...

  7. Android 设置应用的底部导航栏(虚拟按键)背景颜色

    对于有些Android手机的底部虚拟键,进行设置颜色,其实很简单,利用系统提供的Api一步代码就可以搞定,只支持Android5.0以上的系统 //设置底部导航栏颜色if (Build.VERSION ...

  8. 在Ubuntu 18.04 LTS安装ROS Melodic版机器人操作系统(2019年10月更新MoveIt! 1.0 ROS 2.0 Dashing)

    ROS Melodic版本在2018年5月23日推出正式版,这是ROS第三款长期支持版本,前2版LTS分别为:indigo(14.04):kinetic(16.04).此版本有windows版已经推出 ...

  9. android 虚拟键背景,Android 设置应用的底部导航栏(虚拟按键)背景颜色

    Android手机机型种类繁多,但是虚拟按键也就是底部的导航栏,不外乎两种设计方式,一种是作为虚拟按键设计到屏幕内部,一种是作为系统按键设计到屏幕外面. 对于按键在屏幕内部的机型,因为虚拟按键也是屏幕 ...

最新文章

  1. PHP异常处理类(文件上传提示)
  2. C 语言编程 — 编程实践
  3. 用python画四叶草代码-python—字符串拼接三种方法
  4. JS实现html国际化二
  5. 寻找丢失的数字(二)
  6. velocity学习(2)
  7. 【Maven实用技巧】03. Maven 编译打包时如何忽略测试用例
  8. Visual C++中 #include stdafx.h 头文件的用法
  9. NLP太难学了!?吃透NLP的方法来拿走
  10. LeetCode 69 x 的平方根
  11. matlab立体坐标定位_【光电视界】视觉导航定位系统工作原理及过程
  12. 借助 OpenGL* ES 2.0 实现动态分辨率渲染
  13. 使用yarn运行react项目指令_Jenkins | 使用yarn构建前端项目
  14. js打开新页面的两种方式
  15. 2019年通信工程考研初试经验帖(366分)
  16. 光栅图形学-中点画线法
  17. TabLayout自定义Indicator
  18. 《2021政府工作报告》词云图一览
  19. c语言捕鱼达人源码,用捕鱼达人去理解C中的多线程.doc
  20. 做一个自己的LaTeX幻灯片模板

热门文章

  1. (附源码)ssm驾考预约管理系统设计与实现 毕业设计250910
  2. 人机交互技术在车管所的应用探索
  3. html5合并单元格边框线,table边框表头单元格空间合并等设置
  4. Halcon例程分析6:圆弧测量工具
  5. windows10强制删除文件_二合一平板电脑的痛点要被解决:Windows 10开始改善触摸操作体验!...
  6. SQL 如何在已分组统计的数据中统计某一字段特定值的总数
  7. AccessController.doPrivileged
  8. 抽象类与接口与模板方法设计模式
  9. Kubernatesv1.21.2集群搭建手册
  10. 使用VBA在Office中输入特殊字符(3/3)