Android实现底部导航栏切换并解决Fragment切换时多次加载网络等问题

问题描述

问题1 如何实现一个Activity多个Fragment
问题2 底部导航栏fragment切换时会反复加载网络数据,最终造成网络阻塞甚至崩溃
底部导航栏切换时如果使用remove和add方法,这样每次切换都会导致创建多个Fragment实例,所以造成网络阻塞甚至程序崩溃。这里采用hide和show方法来切换fragment
问题3 去掉BottomNavigationMenuView的默认动画效果
为了方便布局采用了BottomNavigationMenuView进行底部导航栏布局,但当底部按钮数量较多时,会有个自带动画效果,这显然不是我们想要的,可以通过反射去掉这个效果。

代码实现

编写Fragment,这里创了5个简易的Fragment,代码都是类似的
Android实现底部导航栏切换并解决Fragment切换时多次加载网络等问题

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import example.com.myapplication.R;

public class FiveFragment extends Fragment {

    public FiveFragment() {
        // Required empty public constructor
    }


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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_five, container, false);
    }

}

对Fragment布局,这里只是简单地加了个背景
Android实现底部导航栏切换并解决Fragment切换时多次加载网络等问题

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/md_light_green_A700"/>

</LinearLayout>

添加design包,这里版本号根据实际情况自行更改
Android实现底部导航栏切换并解决Fragment切换时多次加载网络等问题

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

activity_main.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:orientation="vertical"
        android:layout_height="match_parent">
        <FrameLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:orientation="vertical"/>
        <android.support.design.widget.BottomNavigationView
            android:id="@+id/bottomNav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="?android:attr/windowBackground"
            app:itemBackground="@null"
            app:itemIconTint="@drawable/bottom_navigation_selector"
            app:itemTextColor="@drawable/bottom_navigation_selector"
            app:menu="@menu/tab" />
</LinearLayout>

添加tab.xml 用于按钮文字及图片配置

Android实现底部导航栏切换并解决Fragment切换时多次加载网络等问题

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/home"
        android:title="首页" />
    <item
        android:id="@+id/navigation_two"
        android:icon="@drawable/home"
        android:title="分类"
        />
    <item
        android:id="@+id/navigation_three"
        android:icon="@drawable/home"
        android:title="圈子"
        />
    <item
        android:id="@+id/navigation_four"
        android:icon="@drawable/home"
        android:title="发现" />
    <item
        android:id="@+id/navigation_five"
        android:icon="@drawable/home"
        android:title="记录" />
</menu>

bottom_navigation_selector.xml用于选中和未选中的颜色的配置
Android实现底部导航栏切换并解决Fragment切换时多次加载网络等问题

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/md_red_400" android:state_checked="true" />
    <item android:color="@color/md_grey_600" android:state_checked="false" />
</selector>

最后,MainActivity 用isFragment指向当前正在使用的Fragment,同时使用hide和show方法,防止切换时重新创建实例。在去掉底部导航栏动画效果上,采用了反射的方法。

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.internal.BottomNavigationItemView;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;

import java.lang.reflect.Field;

import example.com.myapplication.Fragment.FiveFragment;
import example.com.myapplication.Fragment.FourFragment;
import example.com.myapplication.Fragment.HomeFragment;
import example.com.myapplication.Fragment.SecondFragment;
import example.com.myapplication.Fragment.ThirdFragment;

import static example.com.myapplication.R.id.bottomNav;

public class MainActivity extends AppCompatActivity {
    private BottomNavigationView bottomNavigationView;
    //定义Fragment
    private HomeFragment homeFragment;
    private SecondFragment secondFragment;
    private ThirdFragment thirdFragment;
    private FourFragment fourFragment;
    private FiveFragment fiveFragment;
    //记录当前正在使用的fragment
    private Fragment isFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化Fragment及底部导航栏
        initFragment(savedInstanceState);
        bottomNavigationView = (BottomNavigationView) findViewById(bottomNav);
        //关闭底部导航栏默认动画效果并添加监听器
        disableShiftMode(bottomNavigationView);
        bottomNavigationView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    }

    public void initFragment(Bundle savedInstanceState) {
        //判断activity是否重建,如果不是,则不需要重新建立fragment.
        if (savedInstanceState == null) {
            FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            if (homeFragment == null) {
                homeFragment = new HomeFragment();
            }
            isFragment = homeFragment;
            ft.replace(R.id.container, homeFragment).commit();
        }
    }

    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener = new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                    if (homeFragment == null) {
                        homeFragment = new HomeFragment();
                    }
                    switchContent(isFragment, homeFragment);
                    return true;
                case R.id.navigation_two:
                    if (secondFragment == null) {
                        secondFragment = new SecondFragment();
                    }
                    switchContent(isFragment, secondFragment);
                    return true;
                case R.id.navigation_three:
                    if (thirdFragment == null) {
                        thirdFragment = new ThirdFragment();
                    }
                    switchContent(isFragment, thirdFragment);
                    return true;
                case R.id.navigation_four:
                    if (fourFragment == null) {
                        fourFragment = new FourFragment();
                    }
                    switchContent(isFragment, fourFragment);
                    return true;
                case R.id.navigation_five:
                    if (fiveFragment == null) {
                        fiveFragment = new FiveFragment();
                    }
                    switchContent(isFragment, fiveFragment);
                    return true;
            }
            return false;
        }

    };


    public void switchContent(Fragment from, Fragment to) {
        if (isFragment != to) {
            isFragment = to;
            FragmentManager fm = getSupportFragmentManager();
            //添加渐隐渐现的动画
            FragmentTransaction ft = fm.beginTransaction();
            if (!to.isAdded()) {    // 先判断是否被add过
                ft.hide(from).add(R.id.container, to).commit(); // 隐藏当前的fragment,add下一个到Activity中
            } else {
                ft.hide(from).show(to).commit(); // 隐藏当前的fragment,显示下一个
            }
        }
    }

    //利用反射关闭底部导航栏默认动画效果,使多个按钮平分界面
    public void disableShiftMode(BottomNavigationView view) {
        BottomNavigationMenuView menuView = (BottomNavigationMenuView) view.getChildAt(0);
        try {
            Field shiftingMode = menuView.getClass().getDeclaredField("mShiftingMode");
            shiftingMode.setAccessible(true);
            shiftingMode.setBoolean(menuView, false);
            shiftingMode.setAccessible(false);
            for (int i = 0; i < menuView.getChildCount(); i++) {
                BottomNavigationItemView item = (BottomNavigationItemView) menuView.getChildAt(i);
                //noinspection RestrictedApi
                item.setShiftingMode(false);
                // set once again checked value, so view will be updated
                //noinspection RestrictedApi
                item.setChecked(item.getItemData().isChecked());
            }
        } catch (NoSuchFieldException e) {
            Log.e("BNVHelper", "Unable to get shift mode field", e);
        } catch (IllegalAccessException e) {
            Log.e("BNVHelper", "Unable to change value of shift mode", e);
        }
    }
}

在本例中,用到了BottomNavigationMenuView作为底部导航栏的布局,实际上不用也可以的,只需要将它替换为线性布局并适当修改代码即可。
整个框架搭建好后,就可以在Fragment中加入网络线程了。