从零开始玩Android 实战项目 7 (底部导航栏重构优化+滑动切换页面)

616 阅读5分钟

Hello 小伙伴们大家好,我是Krain

先简单介绍一下自己的情况:毕业没多久的社畜小牛犊,目前已工作一年的初级Java(可能还算不上),凭借自己对编程开发的热爱踏上这个卷不死就往死里卷的行业,接下来我将以自己CV战士(新手小白)的身份记录成长历程、督促自己学习、遇见错误记录、加深编码记忆、技术相互交流。

特此声明:本人萌新,文章如提及技术分析均为自己理解,有错还请大佬纠正

上篇我们提到使用BottomNavigationView+FrameLayout来制作app的底部导航栏,有大佬提出BottomNavigationView加载fragment会出现销毁不全面的情况,并建议我们使用FlycoTabLayout,以下是大佬上篇评论中提及的问题贴、以及FlycoTabLayout所在的github地址。

www.sunofbeach.net

github.com/H07000223/F…

当我去了解这FlycoTabLayout时,看见那堆成山的代码,顿时虎躯一震,直接看GitHub对初出茅庐的小白极度不友好(至少我是这么认为的),但凭借自己强烈的求知欲,查遍了百度,翻遍了贴吧,看光了小破站,终于!我悟了!我研究出来了哈哈!先请各位官爷来看看我做的效果图,之后我们再做分析。

image.png

我们先以小白视角去简单理解一下FlycoTabLayout,大家就把它想象成一个书包(第三方库),可以在任何人手里(开源),书包中有3样做导航栏的工具(TabLayout),分别叫作

  • SlidingTabLayout 依赖ViewPager,内部实现了fragmentPagerAdapter
  • CommonTabLayout 不依赖ViewPager,可与其他控件自由搭配使用,适合做底部导航栏
  • SegmentTabLayout 适合做顶部导航栏,类似qq顶部的首页消息切换

不提源码,大概就是这样吧,如果有大佬能说一下这三者的本质区别的话还请评论区补充。

想要使用FlycoTabLayout这个库,先导入以下依赖

implementation 'io.github.h07000223:flycoTabLayout:3.0.0'

我采用xml布局特别简单,一个CommonTabLayout,一个ViewPager足矣,这边有坑:ViewPager需要放在CommonTabLayout上面,不然后面会出现点击导航栏无法切换的情况,因为导航栏被ViewPager覆盖掉了

<androidx.viewpager.widget.ViewPager
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

<com.flyco.tablayout.CommonTabLayout
    android:id="@+id/bottomTag"
    tl:tl_indicator_color="#2C97DE"
    tl:tl_textSelectColor="#2C97DE"
    tl:tl_textUnselectColor="#66000000"
    tl:tl_underline_color="#DDDDDD"
    tl:tl_underline_height="1dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#EDEBEB"
    android:paddingTop="5dp"
    android:paddingBottom="5dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent" />

导航栏中的图标文字在Activity中加入,我的图标是在阿里的矢量图库中找的,网址在上篇中有提到。Activity中的操作想必大家经过前几篇已经并不陌生了,初始化嘛,只不过这次东西有点多

private ArrayList<Fragment> mFragments=new ArrayList<>(); //Fragment集合
private ArrayList<Fragment> mFragments2 = new ArrayList<>();
private ArrayList<CustomTabEntity> mTabEntities=new ArrayList<>();  //CustomTabEntity集合
private String[] mTitles = {"首页","视频","消息","我的"};  //标题
private int[] mIconUnselectIds = {            //选中时的图标
        R.drawable.home, R.drawable.video,
        R.drawable.comment, R.drawable.user};
private int[] mIconSelectIds = {              //未选中时的图标
        R.drawable.home_selected, R.drawable.video_selected,
        R.drawable.comment_selected, R.drawable.user_selected};
private ViewPager viewPager;               //xml中的viewPager
private CommonTabLayout commonTabLayout;   //xml中的CommonTabLayout

有小伙伴可能不知道CustomTabEntity是什么,大家可以点进源码看一下,首先Entity说明它是个实体类,它里面封装了三个方法,一个是得到标题,第二个是得到选中的图标,第三个是得到未选中的图标,这里可以知道我们定义的mTabEntities集合装的就是所有东西的汇总。

到这里,我们就知道逻辑是什么样子了,初始化控件和准备好要加载的数据 -> 处理图标的文字图片按顺序放入集合 -> 加入适配器绑定页面实现页面跟随按钮跳转 -> 分别加入页面、按钮的监听器双向绑定实现滑动切换页面及图标。那么代码不就来了嘛。

public class MainActivity extends AppCompatActivity {

    private ArrayList<Fragment> mFragments=new ArrayList<>();
    private ArrayList<Fragment> mFragments2 = new ArrayList<>();
    private ArrayList<CustomTabEntity> mTabEntities=new ArrayList<>();
    private String[] mTitles = {"首页","视频","消息","我的"};
    private int[] mIconUnselectIds = {                   //未选中时的图标数组
            R.drawable.home, R.drawable.video,
            R.drawable.comment, R.drawable.user};
    private int[] mIconSelectIds = {                     //选中时的图标数组
            R.drawable.home_selected, R.drawable.video_selected,
            R.drawable.comment_selected, R.drawable.user_selected};
    private ViewPager viewPager;
    private CommonTabLayout commonTabLayout;

--------------------------------------------以上是初始化------------------------------------------
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (String title:mTitles){  //这里我也不太清楚传这个title有什么用 HomeFragment的代码在下一段
            mFragments.add(HomeFragment.getInstance("Switch ViewPager"+title));
            mFragments2.add(HomeFragment.getInstance("Switch Fragment"+title));
        }

        for (int i=0;i<mTitles.length;i++){    //将准备好的选中图标、未选中图标、标题加入集合
            mTabEntities.add(new TabEntity(mTitles[i],mIconSelectIds[i],mIconUnselectIds[i]));//TabEntity实体类代码在下下段
        }  
        viewPager=findViewById(R.id.view_pager);      //获取xml控件
        commonTabLayout=findViewById(R.id.bottomTag); //获取xml控件
        
        viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager())); //创建对象绑定页面,不知道MyPagerAdapter是什么拉到本段代码最后
        
-----------------------------------------以下是滑动页面双向监听------------------------------------
        
        commonTabLayout.setOnTabSelectListener(new OnTabSelectListener() {  //监听切换按钮
            @Override
            public void onTabSelect(int position) {
                viewPager.setCurrentItem(position);
            }

            @Override
            public void onTabReselect(int position) {

            }
        });
        
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {  //监听切换页面
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                commonTabLayout.setCurrentTab(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
        
      ---------------------------------------------------------------------------------------------
        
        commonTabLayout.setTabData(mTabEntities);  //将集合中的图标使用setTabData为CommonTabLayout赋值

    }
    
    
    //以下是绑定页面的内部类 是不是都不知道内部类是干啥的了 来帮大家回忆一下
    
    //内部类的访问特点:
       //内部类可以直接访问外部类的成员,包括私有
       //外部类要访问内部类的成员,必须创建对象

    private class MyPagerAdapter extends FragmentPagerAdapter {
        public MyPagerAdapter(FragmentManager fm) {
            super(fm);
        }

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

        @Override
        public CharSequence getPageTitle(int position) {
            return mTitles[position];
        }

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

下面是HomeFragment的代码,其实这个类我也不清楚getInstance()具体起到什么作用,我只是把他当作页面对应的Activity,被MyPagerAdapter适配器绑定,如果有大佬知道还请评论区解释一下。

public class HomeFragment extends Fragment {

    private String mTitle;

    public static HomeFragment getInstance(String title) {
        HomeFragment sf = new HomeFragment();
        sf.mTitle = title;
        return sf;
    }

    @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_home, container, false);
    }
}

下面是实体类代码

public class TabEntity implements CustomTabEntity {
    public String title;
    public int selectedIcon;
    public int unSelectedIcon;

    public TabEntity(String title, int selectedIcon, int unSelectedIcon) {
        this.title = title;
        this.selectedIcon = selectedIcon;
        this.unSelectedIcon = unSelectedIcon;
    }

    @Override
    public String getTabTitle() {
        return title;
    }

    @Override
    public int getTabSelectedIcon() {
        return selectedIcon;
    }

    @Override
    public int getTabUnselectedIcon() {
        return unSelectedIcon;
    }
}

代码到这里就能实现我们上面所展示的效果了,可能一开始看有点难理解,接下来是我在做这个底部导航栏时产生的问题,对自己的问题做一个整理

  • 什么是FragmentManager,他的作用是什么
  • HomeFragment中重写的onCreateView是用来做什么的,

以下是我从网上学习加上自己理解的回答,欢迎补充

  • 首先FragmentManager字面意思 Fragment的管理者,那Fragment是什么呢?他是安卓3.0后的一个api,应用场景是为了适应大屏幕的设备,可以理解为小型的Activity,嵌套在Activity中,有生命周期,,并直接受到Activity的影响,简单点说就是Fragment是寄生虫,Activity是被寄生的宿主,我感觉有点像JAVA中的Bean。那么FragmentManager就是统一管理这些Fragment,可以对 Fragment 进行添加,移除,替换等操作,通过getFragmentManager()或getSupportFragmentManager()获得

  • onCreateView是用来加载Fragment的视图,就和onCreate去加载xml布局一个道理,里面有三个参数,LayoutInflater inflater:和findViewById一样找控件的。ViewGroup container:一个View容器。Bundle savedInstanceState:是否保存当前状态,上面一个回答提到Fragment直接受到Activity影响,也就是当Activity进程被嘎了,Fragment也会被嘎,那么这个参数就是:当Activity被嘎了,你是否要Fragment也被嘎掉