草庐IT

Android 底部导航栏(三、ViewPager+TabLayout+Fragment)简单易懂

&岁月不待人& 2023-05-01 原文

底部导航栏在Android应用中随处可见,今天使用ViewPager+TabLayout+Fragment这三个控件来实现此功能。

前面使用了另外两个方法来实现导航栏,不过我还是更喜欢Viewpager,代码也少,毕竟前两个不能左右滑动。

Android 底部导航栏(一、BottomNavigationView+Menu+Fragment)_&岁月不待人&的博客-CSDN博客_android 底部导航栏现在常用的App主页都会有一个底部导航栏,根据需求也使用过好几种方法进行实现,于是想着还是总结一下,今天还写一个简单的BottomNavigationView方法来实现这个功能它是android原生的一个底部导航框架,一般和Fragment一起使用。xml布局:最外层的layout不用管,那是databinding框架的根布局,主要的布局FrameLayout来装载fragment列表,BottomNavigationView实现底部导航栏,最后监听绑定实现点击切换其中BottomNavigationhttps://blog.csdn.net/LoveFHM/article/details/125498372?spm=1001.2014.3001.5501

Android 底部导航栏(二、自定义View+Fragment)_&岁月不待人&的博客-CSDN博客总结:我这上面只实现了简单的点击切换,用自定义View来实现底部导航栏,很自由,可以实现一些稀奇古怪的需求,但是很多方法的回调,切换时的动画,就需要自己去实现,可能会相对复杂一些。最后加上Fragment的切换,点击监听等等,坏处的话就是自己写的,可能没有封装好的那么完善,很多方法,状态需要自己去写。上一片文章用的是BottomNavigationView+Menu+Fragment,但是可能有时候需求不一样,menu的样式不太够,所以需要自定义View来实现。https://blog.csdn.net/LoveFHM/article/details/127651226?spm=1001.2014.3001.5501

一、基本介绍

ViewPager:Android 3.0后引入的一个UI控件——ViewPager(视图滑动切换工具),Viewpager使用起来就是我们通过创建adapter给它填充多个view,左右滑动时,切换不同的view。Google官方是建议我们使用Fragment来填充ViewPager的,这样 可以更加方便的生成每个Page,以及管理每个Page的生命周期。可以用来主页/模块切换,图片轮播,新手引导等等。

ViewPager 全面总结_淡然一笑、的博客-CSDN博客_viewpager

TabLayout:TabLayout提供了一个水平布局用于展示tabs,继承自HorizontalScrollView。TabLayout一般结合ViewPager+Fragment的使用实现滑动的标签选择器。

Android控件-TabLayout使用介绍_Android开发猿的博客-CSDN博客_tablayout

Fragment:Fragment可以说是轻量级的Activity,是Android3.0新增的概念。这个就很基础了,不给链接了。

二、实现原理

Fragment用于承载和展示内容,Viewpager用于界面的切换,TabLayout用于展示导航栏和点击事件通知ViewPager切换页面。

三、实现过程

第一步:先拉界面哦

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:orientation="vertical">

        <androidx.appcompat.widget.AppCompatTextView//只是个文案
            android:id="@+id/nav_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:gravity="center"
            android:padding="4dp"
            android:text="ViewPager+TabLayout+Fragment"
            android:textColor="@color/black"
            android:textSize="18sp"
            android:textStyle="bold" />

        <androidx.viewpager.widget.ViewPager //承载内容
            android:id="@+id/fragment_container_viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@id/nav_tablayout"
            android:layout_below="@id/nav_text" />

        <View
            android:layout_width="match_parent"//只是个分割线
            android:layout_height="0.3dp"
            android:layout_above="@id/nav_tablayout"
            android:background="@color/tab_color_false"/>

        <com.google.android.material.tabs.TabLayout
            android:id="@+id/nav_tablayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:background="@color/white"
            app:tabBackground="@color/white"
            app:tabIndicatorAnimationMode="elastic"
            app:tabIndicatorColor="@color/blue"
            app:tabIndicatorFullWidth="false"
            app:tabIndicatorGravity="bottom"
            app:tabMode="fixed"
            app:tabRippleColor="@android:color/transparent"
            app:tabSelectedTextColor="@color/black"
            app:tabTextColor="@color/bar_grey"
            tools:layout_height="50dp"
        />
    </RelativeLayout>
</layout>

第二步:业务代码,就是初始化这两个控件,tablayout初始化item,设置属性,使用tabLayout.setupWithViewPager(viewPager)绑定viewpager,然后往Viewpager里面加fragment列表,然后给viewpager设置fragment。(kotlin实现,如果你是java代码,拷进项目它会自动转码为java,布局绑定用的databinding,没用过看核心代码就行了)

class NavViewPagerTabActivity : BaseBindActivity<NavViewpagerTablayoutActivityLayoutBinding>() {

    override val layout: Int
        get() = R.layout.nav_viewpager_tablayout_activity_layout

    private lateinit var homeFragment: HomeFragment
    private lateinit var toolsFragment: ToolsFragment
    private lateinit var relaxFragment: RelaxFragment
    private lateinit var viewPager: ViewPager
    private lateinit var tabLayout: TabLayout
    private lateinit var meFragment: MineFragment
    private lateinit var playFragment: PlayFragment
    private lateinit var fragments: Array<BaseFragment>
    private val titles = arrayOf("首页", "视频", "用户")


    override fun initView() {
        initFragment()//初始化fragment列表
        viewPager = mBinding.fragmentContainerViewpager
        tabLayout = mBinding.navTablayout
        tabLayout.setupWithViewPager(viewPager)//绑定viewpager
//        for (element in titles) tabLayout.addTab(tabLayout.newTab().setText(element))
        viewPager.adapter = object :FragmentPagerAdapter(supportFragmentManager,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT){
            override fun getCount(): Int {
                return fragments.size
            }
            override fun getItem(position: Int): Fragment {
                return fragments[position]
            }
            override fun getPageTitle(position: Int): CharSequence {
                return titles[position]
            }
        }
    }
    private fun initFragment() {
        homeFragment = HomeFragment()
        toolsFragment = ToolsFragment()
        relaxFragment = RelaxFragment()
        meFragment = MineFragment()
        playFragment = PlayFragment()
//        fragments = arrayOf(homeFragment, toolsFragment, playFragment, relaxFragment, meFragment)
        fragments = arrayOf(homeFragment, toolsFragment, meFragment)
    }
}

eg:可以看到我把上面的这行代码注释,但是运行出来,tab是添加上了,为什么呢?

// for (element in titles) tabLayout.addTab(tabLayout.newTab())

原因很简单,是在tabLayout.setupWithViewPager的时候,TabLayout中先将所有tabs remove了,然后取的PagerAdapter中的getPageTitle返回值添加的tab。就是在给ViewPager设置Adapter的时候,一定要重写getPageTitle(int position)方法,不然TabLayout中的标签是看不到的,即使在addTab时newTab().setText(tabs[i])也没用。

这样就简单的实现了导航栏!可以单独为viewpager和tablayout添加监听事件,并且绑定也只需要一行代码,可以实现左右滑动切换界面,当然,如果想禁止左右滑的触摸事件也可以对viewpager事件作拦截。关于viewpager和tablayout,他们还有很多属性可以去配置,如果想详细了解的可以去看上面的两个链接去学习一下!

实现效果:

 

进阶知识点一:

为tablayout添加监听事件:

tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                TODO("Not yet implemented")
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
                TODO("Not yet implemented")
            }

            override fun onTabReselected(tab: TabLayout.Tab?) {
                TODO("Not yet implemented")
            }
        })

 为ViewPager添加监听事件:

viewPager?.setOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
            }

            override fun onPageSelected(position: Int) {
             
            }

            override fun onPageScrollStateChanged(state: Int) {
            }
        })

进阶知识点二:

为TabLayout设置自定义View,有时候底部导航栏可能不只是文字,他可能是图片和文字混合,可能点击后有图片,文字的大小变化,那么就需要为其单独配置view,并在监听事件里重新绘制界面。

这儿我展示一份项目里的代码:

 override fun onTabSelected(tab: TabLayout.Tab) {
                viewPager?.currentItem = tab.position
                val customView = tab.customView
                if (tab.position == 0) {//如果是第一个tab
                    if (customView == null) tab.setCustomView(R.layout.tab_text_layout)//设置布局
                    val textView = customView?.findViewById<TextView>(R.id.text1)
                    textView?.text = titles[tab.position].i18n()
                    textView?.setTextAppearance(requireContext(), R.style.TabLayoutTextSelected)//设置点击后的文本格式
                    val point = customView?.findViewById<TextView>(R.id.point_1)//这是tab右上角的红点
                    val layoutParams = RelativeLayout.LayoutParams(SizeUtils.dp2px(8f), SizeUtils.dp2px(8f))
                    layoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP)
                    layoutParams.addRule(RelativeLayout.END_OF, R.id.text1)
                    layoutParams.setMargins(5, 0, 0, 0)
                    point?.layoutParams = layoutParams
                } else {
                    if (customView == null) tab.setCustomView(R.layout.tab_text_layout2)
                    val textView = customView?.findViewById<TextView>(R.id.tab_text2)
                    textView?.text = titles[tab.position].i18n()
                    textView?.setTextAppearance(requireContext(), R.style.TabLayoutTextSelected)
                }
            }

有关Android 底部导航栏(三、ViewPager+TabLayout+Fragment)简单易懂的更多相关文章

  1. ruby - 简单获取法拉第超时 - 2

    有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url

  2. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  3. ruby-on-rails - 简单的 Ruby on Rails 问题——如何将评论附加到用户和文章? - 2

    我意识到这可能是一个非常基本的问题,但我现在已经花了几天时间回过头来解决这个问题,但出于某种原因,Google就是没有帮助我。(我认为部分问题在于我是一个初学者,我不知道该问什么......)我也看过O'Reilly的RubyCookbook和RailsAPI,但我仍然停留在这个问题上.我找到了一些关于多态关系的信息,但它似乎不是我需要的(尽管如果我错了请告诉我)。我正在尝试调整MichaelHartl'stutorial创建一个包含用户、文章和评论的博客应用程序(不使用脚手架)。我希望评论既属于用户又属于文章。我的主要问题是:我不知道如何将当前文章的ID放入评论Controller。

  4. ruby - 使用 Ruby 通过 Outlook 发送消息的最简单方法是什么? - 2

    我的工作要求我为某些测试自动生成电子邮件。我一直在四处寻找,但未能找到可以快速实现的合理解决方案。它需要在outlook而不是其他邮件服务器中,因为我们有一些奇怪的身份验证规则,我们需要保存草稿而不是仅仅发送邮件的选项。显然win32ole可以做到这一点,但我找不到任何相当简单的例子。 最佳答案 假设存储了Outlook凭据并且您设置为自动登录到Outlook,WIN32OLE可以很好地完成此操作:require'win32ole'outlook=WIN32OLE.new('Outlook.Application')message=

  5. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  6. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  7. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  8. ruby - 使用 Ruby,计算 n x m 数组的每一列中有多少个 true 的简单方法是什么? - 2

    给定一个nxmbool数组:[[true,true,false],[false,true,true],[false,true,true]]有什么简单的方法可以返回“该列中有多少个true?”结果应该是[1,3,2] 最佳答案 使用转置得到一个数组,其中每个子数组代表一列,然后将每一列映射到其中的true数:arr.transpose.map{|subarr|subarr.count(true)}这是一个带有inject的版本,应该在1.8.6上运行,没有任何依赖:arr.transpose.map{|subarr|subarr.in

  9. ruby - 有人可以解释一下在 Ruby 中注入(inject)的真实、通俗易懂的用法吗? - 2

    我正在学习Ruby,遇到了inject。我正处于理解它的风口浪尖,但当我是那种需要真实世界的例子来学习一些东西的人时。我遇到的最常见的例子是人们使用inject来添加一个(1..10)范围的总和,我不太关心这个。这是一个任意的例子。在实际程序中我会用它做什么?我正在学习,所以我可以继续使用Rails,但我不必有一个以Web为中心的示例。我只需要一些我可以全神贯注的目标。谢谢大家。 最佳答案 inject有时可以通过它的“其他”名称reduce更好地理解。它是一个对Enumerable进行操作(迭代一次)并返回单个值的函数。它有许多有

  10. ruby - 在 Ruby 中搜索大文件的更简单方法? - 2

    我正在编写一个简单的日志嗅探器,它将在日志中搜索表明我支持的软件存在问题的特定错误。它允许用户指定日志路径并指定他们想要搜索多少天前。如果用户关闭日志滚动,日志文件有时会变得非常大。目前我正在做以下事情(虽然还没有完成):File.open(@log_file,"r")do|file_handle|file_handle.eachdo|line|ifline.match(/\d+++-\d+-\d+/)etc...line.match显然会查找我们在日志中使用的日期格式,其余逻辑将在下面。但是,有没有更好的方法来搜索没有.each_line的文件?如果没有,我完全同意。我只是想确保我使

随机推荐