Android 底部导航栏(二、自定义View+Fragment)

简介: 上一片文章用的是BottomNavigationView+Menu+Fragment,但是可能有时候需求不一样,menu的样式不太够,所以需要自定义View来实现。

上一片文章用的是BottomNavigationView+Menu+Fragment,但是可能有时候需求不一样,menu的样式不太够,所以需要自定义View来实现。




自定义View:实现思路是自定义一个XMl布局,放五个切换的Item,根据需求的样式去绘制。最后加上Fragment的切换,点击监听等等,坏处的话就是自己写的,可能没有封装好的那么完善,很多方法,状态需要自己去写。


直接上代码吧!首先是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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/act_main_bottom_nav_ll"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="#fff"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:paddingTop="5dp"
    android:paddingBottom="5dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent">
    <LinearLayout
        android:id="@+id/bottom_bar_home"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/bottom_bar_tools"
        tools:ignore="UseCompoundDrawables">
        <ImageView
            android:id="@+id/bottom_bar_home_icon"
            android:layout_width="26dp"
            android:layout_height="26dp"
            android:layout_marginBottom="4dp"
            android:contentDescription="@null"
            android:src="@drawable/gongwenbao" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:text="@string/home"
            android:textColor="@color/black"
            android:textSize="8sp"
            tools:ignore="SmallSp" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/bottom_bar_tools"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintEnd_toStartOf="@id/bottom_bar_play"
        app:layout_constraintStart_toEndOf="@id/bottom_bar_home"
        tools:ignore="UseCompoundDrawables">
        <ImageView
            android:id="@+id/bottom_bar_tools_icon"
            android:layout_width="26dp"
            android:layout_height="26dp"
            android:layout_marginBottom="4dp"
            android:contentDescription="@null"
            android:src="@drawable/zixun" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:text="@string/tools"
            android:textColor="@color/black"
            android:textSize="8sp"
            tools:ignore="SmallSp" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/bottom_bar_play"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@id/bottom_bar_tools"
        app:layout_constraintEnd_toStartOf="@id/bottom_bar_relax"
        tools:ignore="UseCompoundDrawables">
        <ImageView
            android:id="@+id/bottom_bar_play_icon"
            android:layout_width="26dp"
            android:layout_height="26dp"
            android:layout_marginBottom="4dp"
            android:contentDescription="@null"
            android:src="@drawable/youxi" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:text="@string/play"
            android:textColor="@color/black"
            android:textSize="8sp"
            tools:ignore="SmallSp" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/bottom_bar_relax"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@id/bottom_bar_play"
        app:layout_constraintEnd_toStartOf="@id/bottom_bar_mine"
        tools:ignore="UseCompoundDrawables">
        <ImageView
            android:layout_width="26dp"
            android:layout_height="26dp"
            android:layout_marginBottom="4dp"
            android:contentDescription="@null"
            android:src="@drawable/yundong" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:text="@string/video"
            android:textColor="@color/black"
            android:textSize="8sp"
            tools:ignore="SmallSp" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/bottom_bar_mine"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@id/bottom_bar_relax"
        app:layout_constraintEnd_toEndOf="parent"
        tools:ignore="UseCompoundDrawables">
        <ImageView
            android:id="@+id/bottom_bar_mine_icon"
            android:layout_width="26dp"
            android:layout_height="26dp"
            android:layout_marginBottom="4dp"
            android:contentDescription="@null"
            android:src="@drawable/yonghu" />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:includeFontPadding="false"
            android:text="@string/mine"
            android:textColor="@color/black"
            android:textSize="8sp"
            tools:ignore="SmallSp" />
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
展示出来的效果是:
 就是写自定义View:
主要就是绑定布局,为每一个Item添加点击事件,这儿我只做了简单的点击回调
package com.example.lxview.base.widget
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import com.example.lxview.R
class MainBottomNavBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {
    private lateinit var videoContainer: ConstraintLayout
    private lateinit var navHome: LinearLayout
    private lateinit var navTools: LinearLayout
    private lateinit var navPlay: LinearLayout
    private lateinit var navMe: LinearLayout
    private lateinit var navRelax: LinearLayout
     var listener:ResultClick?=null
    init {
        initView()
    }
    private fun initView() {
        View.inflate(context, R.layout.main_act_bottom_nav_bar, this)
        videoContainer = findViewById(R.id.act_main_bottom_nav_ll)
        navHome = findViewById(R.id.bottom_bar_home)
        navTools = findViewById(R.id.bottom_bar_tools)
        navPlay = findViewById(R.id.bottom_bar_play)
        navMe = findViewById(R.id.bottom_bar_mine)
        navRelax = findViewById(R.id.bottom_bar_relax)
        navHome.setOnClickListener {
            listener?.click(0)
        }
        navTools.setOnClickListener {
            listener?.click(1)
        }
            navPlay.setOnClickListener {
            listener?.click(2)
        }
        navRelax.setOnClickListener {
            listener?.click(3)
        }
        navMe.setOnClickListener {
            listener?.click(4)
        }
    }
}
interface ResultClick{
    fun click(int: Int)
}
第三步,在展示的Activity 的 xml文件里引用此布局,上面的FrameLayout用来放置fragment,底部放导航栏。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_cl"
    android:background="#f7f7f7"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/navigation"
        />
    <com.example.lxview.base.widget.MainBottomNavBar
        android:id="@+id/navigation"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/fragment_container"
        app:layout_constraintStart_toStartOf="parent"
        />
</androidx.constraintlayout.widget.ConstraintLayout>
第4步:在Activity代码里加上逻辑,继承ResultClick接口,复写click方法,用replaceFragment实现每次点击item时切换fragment
package com.example.lxview
import android.view.MotionEvent
import android.widget.FrameLayout
import com.example.lxview.base.activity.BaseActivity
import com.example.lxview.base.fragment.BaseFragment
import com.example.lxview.base.widget.MainBottomNavBar
import com.example.lxview.home.fragment.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.example.lxview.base.widget.ResultClick
/**
 * author: 李 祥
 * date:   2022/3/31 1:57 下午
 * description:
 */
class MainActivity : BaseActivity(),ResultClick{
    override val contentId: Int
        get() = R.layout.activity_main
    private var indicatorView: MainBottomNavBar? = null
    private lateinit var fragments: Array<BaseFragment>
    private lateinit var curSelectId: String
    private lateinit var homeFragment: HomeFragment
    private lateinit var toolsFragment: ToolsFragment
    private lateinit var relaxFragment: RelaxFragment
    private lateinit var meFragment: MineFragment
    private lateinit var playFragment: PlayFragment
    private lateinit var container: FrameLayout
    override fun initView() {
        indicatorView = findViewById(R.id.navigation)
        indicatorView?.listener = this
        container = findViewById(R.id.fragment_container)
        homeFragment = HomeFragment()
        toolsFragment = ToolsFragment()
        relaxFragment = RelaxFragment()
        meFragment = MineFragment()
        playFragment = PlayFragment()
        curSelectId = homeFragment.id.toString()
        fragments = arrayOf(homeFragment, toolsFragment, playFragment, relaxFragment, meFragment)
        fragments.forEach {
            addFragment(it,it.tag.toString())
            hideFragment(it)
        }
        showFragment(fragments[0])
    }
    //添加Fragment到FragmentList中
    private fun addFragment(fragment: Fragment, tag: String) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.add(R.id.fragment_container, fragment, tag)
        transaction.commit()
    }
    // 清空fragmentList的所有Fragment,替换成新的Fragment,注意Fragment里面的坑
    private fun replaceFragment(fragment: Fragment, tag: String) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.replace(R.id.fragment_container, fragment, tag)
        transaction.commit()
    }
    //移除指定的Fragment
    private fun removeFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.remove(fragment)
        transaction.commit()
    }
    //把Fragment设置成显示状态,但是并没有添加到FragmentList中
    private fun showFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.show(fragment)
        transaction.commit()
    }
    //把Fragment设置成显示状态,但是并没有添加到FragmentList中
    private fun hideFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.hide(fragment)
        transaction.commit()
    }
    // 效果和show相近,创建视图,添加到containerid指定的Added列表,FragmentList依然保留,但是会引起生命周期的变化
    private fun attachFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.attach(fragment)
        transaction.commit()
    }
    // 效果和hide相近,清除视图,从containerid指定的Added列表移除,FragmentList依然保留,但是会引起生命周期的变化
    private fun detachFragment(fragment: Fragment) {
        val fragmentManager = supportFragmentManager
        val transaction: FragmentTransaction = fragmentManager.beginTransaction()
        transaction.detach(fragment)
        transaction.commit()
    }
    override fun click(int: Int) {//实现fragment切换
        replaceFragment(fragments[int],fragments[int].tag.toString())
    }
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        if (ev?.action ==MotionEvent.BUTTON_BACK){
            application.onTerminate()
        }
        return super.dispatchTouchEvent(ev)
    }
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event?.action ==MotionEvent.BUTTON_BACK){
            application.onTerminate()
        }
        return super.onTouchEvent(event)
    }
}


总结:我这上面只实现了简单的点击切换,用自定义View来实现底部导航栏,很自由,可以实现一些稀奇古怪的需求,但是很多方法的回调,切换时的动画,就需要自己去实现,可能会相对复杂一些。所以若是普通的导航栏,还是用Android自带的组件去实现比较好!


eg:使用replaceFragment,会清空掉当前的fragmnet列表,每次都是重新创建fragment,所以在一些界面的状态保存上就会有问题,所以也可以使用hide和show方法来进行点击切换,且不会销毁掉fragment


相关文章
|
1月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
27 1
|
1月前
|
XML 前端开发 Android开发
Android:UI:Drawable:View/ImageView与Drawable
通过本文的介绍,我们详细探讨了Android中Drawable、View和ImageView的使用方法及其相互关系。Drawable作为图像和图形的抽象表示,提供了丰富的子类和自定义能力,使得开发者能够灵活地实现各种UI效果。View和ImageView则通过使用Drawable实现了各种图像和图形的显示需求。希望本文能为您在Android开发中使用Drawable提供有价值的参考和指导。
40 2
|
1月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
1月前
|
Android开发 开发者 UED
安卓开发中自定义View的实现与性能优化
【10月更文挑战第28天】在安卓开发领域,自定义View是提升应用界面独特性和用户体验的重要手段。本文将深入探讨如何高效地创建和管理自定义View,以及如何通过代码和性能调优来确保流畅的交互体验。我们将一起学习自定义View的生命周期、绘图基础和事件处理,进而探索内存和布局优化技巧,最终实现既美观又高效的安卓界面。
41 5
|
2月前
|
缓存 数据处理 Android开发
在 Android 中使用 RxJava 更新 View
【10月更文挑战第20天】使用 RxJava 来更新 View 可以提供更优雅、更高效的解决方案。通过合理地运用操作符和订阅机制,我们能够轻松地处理异步数据并在主线程中进行 View 的更新。在实际应用中,需要根据具体情况进行灵活运用,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在 Android 中使用 RxJava 更新 View 的技巧和方法,为开发高质量的 Android 应用提供有力支持。
|
2月前
|
缓存 调度 Android开发
Android 在子线程更新 View
【10月更文挑战第21天】在 Android 开发中,虽然不能直接在子线程更新 View,但通过使用 Handler、AsyncTask 或 RxJava 等方法,可以实现子线程操作并在主线程更新 View 的目的。在实际应用中,需要根据具体情况选择合适的方法,并注意相关的注意事项和性能优化,以确保应用的稳定性和流畅性。可以通过不断的实践和探索,进一步掌握在子线程更新 View 的技巧和方法,为开发高质量的 Android 应用提供支持。
43 2
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
2月前
|
缓存 前端开发 Android开发
Android实战之如何截取Activity或者Fragment的内容?
本文首发于公众号“AntDream”,介绍了如何在Android中截取Activity或Fragment的屏幕内容并保存为图片。包括截取整个Activity、特定控件或区域的方法,以及处理包含RecyclerView的复杂情况。
29 3