前言

这篇文章是这个系列的第三篇文章了,前两篇文章分别是玩安卓从 0 到 1 之总体概览和玩安卓从 0 到 1 之项目首页。

一开始想的是一篇文章搞定,从项目的搭建到完成把所有的知识点写一遍,努力不做一篇水文;但后来开始写第一篇文章的时候,就感觉这不是一件简单的事,很麻烦,特别是想的很多但写的时候无从下手,这种感觉太恶心了;所以在这之前已经写了两篇文章来介绍这个项目,今天是第三篇,准备介绍一下项目的首页框架的搭建。行了,废话也不多说了,开始正文吧!

正文

按照惯例,还是放一下 Github 地址和 apk 下载地址吧。

apk 下载地址:www.pgyer.com/llj2

Github地址:github.com/zhujiang521…

接下来看一下实现好的样式吧!

这就是首页的框架展示的内容,这篇文章并不是介绍这几个页面如何编写,而是介绍整个首页的搭建。

Bottom 导航栏

首先问大家一个问题,如果让你实现类似上图的底部导航栏你会怎样实现呢?

大概会有以下几种方案:

1、BottomNavigationView,这个就不多描述了,这是 Google 给我们提供的一个专门用于底部导航的 View,你只需要在新建 Activity 的时候选择 “Bottom Navigation Activity”,IDE 就会自动使用 BottomNavigationView 帮你生成好相应的代码了。

2、RadioGroup + ViewPager, 这是一种比较常见的方法,下面几个 tab 的导航按钮,可以切换不同的页面,页面切换使用了 ViewPager + Fragment 的组合,实现了滑动的页面效果,也可以不使用 ViewPager。

3、LinearLayout + TextView,这种也比较常见,通过是否 selected 来判断控件应该显示的图片和字体颜色,页面切换同样使用 ViewPager + Fragment 的组合。

。。。。。。

应该还有很多骚操作,就不一一列举了。**注意!这里的实现方式没有好坏之分,只有适合不适合的区别,没有哪一种实现比另外几种酒高大上。**下面我来说下我的实现方式吧。

我个人其实比较倾向于上面描述第三种的实现。第一种官方的用法也使用过,但是觉得可扩展性不强。第二种和第三种就看使用熟练度来选择了。

我看很多人使用的时候都喜欢在 Activity 的布局中直接添加进行使用,然后这些切换的操作自然都放在了 Activity 中,并不是说这种方法不好,只是感觉这样的话 Activity 有点臃肿,个人认为能放在 View 中完成的操作绝对不放在 Activity 或者 Fragment 中。(这种好像有个专用名词——控件化)

布局

先放下布局文件吧:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><FrameLayoutandroid:id="@+id/flHomeFragment"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:orientation="vertical"></FrameLayout><Viewandroid:layout_width="match_parent"android:layout_height="@dimen/dp_0_1"android:background="@color/text_color" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="@dimen/dp_55"android:orientation="horizontal"><TextViewandroid:id="@+id/llHomeATHome"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:drawableTop="@drawable/selector_one"android:drawablePadding="@dimen/dp_3"android:gravity="center"android:text="首页"android:textColor="@color/color_bottom_tv"android:textSize="@dimen/sp_11" /><TextViewandroid:id="@+id/llHomeATCalendar"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:drawableTop="@drawable/selector_two"android:drawablePadding="@dimen/dp_3"android:gravity="center"android:text="项目"android:textColor="@color/color_bottom_tv"android:textSize="@dimen/sp_11" /><TextViewandroid:id="@+id/llHomeATObject"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:drawableTop="@drawable/selector_three"android:drawablePadding="@dimen/dp_3"android:gravity="center"android:text="公众号"android:textColor="@color/color_bottom_tv"android:textSize="@dimen/sp_11" /><TextViewandroid:id="@+id/llHomeATMy"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_weight="1"android:drawableTop="@drawable/selector_four"android:drawablePadding="@dimen/dp_3"android:gravity="center"android:text="我的"android:textColor="@color/color_bottom_tv"android:textSize="@dimen/sp_11" /></LinearLayout></LinearLayout>

布局很简单,最外面一层 LinearLayout ,里面一个 FrameLayout 用来放 Fragment ,下面一个 LinearLayout 包裹着 TextViewTextView 用作 Tab 标签。

这里的 Tab 我并没有使用 ImageView + TextView ,而是直接使用了 TextViewdrawableTop,能省一个控件是一个嘛!能少写一个是一个!还有一点需要注意的是 textColordrawableTop 都使用了 selector,用来切换不同状态下应该显示的图片和文字颜色,接下来分别看一下吧!

首先是 textColor 的:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:color="@color/text_green" android:state_selected="true" /><item android:color="@color/black_33" android:state_selected="false" /><!-- 按压时 --><item android:color="@color/black_33" android:state_pressed="true" />
</selector>

然后是 drawableTop 的:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><!-- 当前窗口失去焦点时 --><item android:drawable="@drawable/ic_nav_discover_normal" android:state_window_focused="false" /><!-- 不可用时 --><item android:drawable="@drawable/ic_nav_discover_normal" android:state_enabled="false" /><!-- 被选中时 --><item android:drawable="@drawable/ic_nav_discover_actived" android:state_selected="true" /><!-- 被激活时 --><item android:drawable="@drawable/ic_nav_discover_actived" android:state_activated="true" /><!-- 默认时 --><item android:drawable="@drawable/ic_nav_discover_normal" />
</selector>

textColor 的都一样,drawableTop 的只展示一个的吧,其余的都基本一样,只是图片不同而已。

代码

布局很简单咱们写完了,接下来就是代码了。想一想代码应该怎么写?首先来想一下这个控件咱们想要实现什么功能吧!点击肯定要能切换 Fragment ,而且 TextView 的字体和图片的状态也要进行相应的变化,差不多了,这个控件目前就有这么多要求,当然之后你想要加上角标也是可以的,添加一个对外的接口即可。

TextView 的字体和图片的状态改变这一点很好实现,咱们在布局文件中已经写好了 selector ,只需要改变 TextView 的 selected 即可:

for (j in textViews!!.indices) {textViews!![j].isSelected = position == j
}

很简单吧!textViews 就是 TextView 的集合,在初始化的时候放入即可:

textViews = arrayListOf(view.findViewById(R.id.llHomeATHome),view.findViewById(R.id.llHomeATCalendar),view.findViewById(R.id.llHomeATObject),view.findViewById(R.id.llHomeATMy)
)

状态可以改变之后就只剩 Fragment 的切换了:

    /*** fragment的切换 实现底部导航栏的切换** @param position 序号*/protected open fun fragmentManger(position: Int) {mViewModel.setPage(position)val fragmentTransaction =mFragmentManager!!.beginTransaction()val targetFg: Fragment = mFragments!![position]val lastFg: Fragment = mFragments!![mLastFgIndex]mLastFgIndex = positionif (lastFg.isAdded)fragmentTransaction.hide(lastFg)if (!targetFg.isAdded) {mFragmentManager!!.beginTransaction().remove(targetFg).commitAllowingStateLoss()fragmentTransaction.add(R.id.flHomeFragment, targetFg)}fragmentTransaction.show(targetFg)fragmentTransaction.commitAllowingStateLoss()}

上面代码中的 mFragments 即为 Fragment 的集合,也是初始化的时候加入即可:

if (mFragments == null) {mFragments = arrayListOf()mFragments?.add(getCurrentFragment(0)!!)mFragments?.add(getCurrentFragment(1)!!)mFragments?.add(getCurrentFragment(2)!!)mFragments?.add(getCurrentFragment(3)!!)
}

最后加上 TextView 的点击事件即大功告成:

    /*** 实现按钮的点击事件*/override fun onClick(v: View) {when (v.id) {R.id.llHomeATHome -> fragmentManger(0)R.id.llHomeATCalendar -> fragmentManger(1)R.id.llHomeATObject -> fragmentManger(2)R.id.llHomeATMy -> fragmentManger(3)}}

使用 Bottom 导航栏

底部导航栏已经编写完成,使用很简单,和正常使用控件一样放入布局即可:

<?xml version="1.0" encoding="utf-8"?>
<com.zj.play.view.main.HomeBottomTabWidget xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/homeView"android:layout_width="match_parent"android:layout_height="match_parent" />

然后直接在 Activity 中进行初始化即可,剩下的操作都在控件中了:

homeView.init(supportFragmentManager, viewModel)

现在咱们来看一下整个 Activity 的代码:

class MainActivity : BaseActivity() {private val viewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }override fun initView() {homeView.init(supportFragmentManager, viewModel)}override fun getLayoutId(): Int {return R.layout.activity_main}companion object {fun actionStart(context: Context) {val intent = Intent(context, MainActivity::class.java)context.startActivity(intent)}}}

加上空行和括号也就二十行左右,是不是比直接在 Activity 中要简洁的多!

横屏适配

竖屏完美实现,但是横屏就凉凉了!四个大按钮平均堆在最下面,如下图所示:

丑的一批!

虽然大部分程序员的审美都不咋地,当然我更差,连我这样的审美都觉得丑了,那就证明是真的看不下去了,所以必须要进行优化。

于是就有了现在的横屏样式:

点击右下角的按钮即会展开,按钮中间的图片表示当前的页面。

这样我觉得比刚才好看太多了!下面就来说下横屏的实现吧!

这里我用到了一个三方控件:

implementation 'com.cpacm:floatingmusicmenu:1.0.0'

同上面一样,把横屏的也抽成一个控件吧!

布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:ignore="MissingDefaultResource"><FrameLayoutandroid:id="@+id/flHomeFragment"android:layout_width="match_parent"android:layout_height="match_parent" /><com.cpacm.FloatingMusicMenuandroid:id="@+id/fabMenu"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom|right"android:layout_marginEnd="@dimen/dp_16"android:layout_marginBottom="@dimen/dp_16"app:fmm_backgroundTint="@color/floatBackground"app:fmm_button_interval="2dp"app:fmm_cover="@drawable/ic_nav_news_actived"app:fmm_floating_direction="left"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fabHome"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="@dimen/dp_16"android:layout_marginBottom="@dimen/dp_16"android:src="@drawable/selector_one"app:backgroundTint="@color/floatBackground"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent" /><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fabRepo"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="@dimen/dp_16"android:layout_marginBottom="@dimen/dp_16"android:src="@drawable/selector_two"app:backgroundTint="@color/floatBackground"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent" /><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fabProject"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="@dimen/dp_16"android:layout_marginBottom="@dimen/dp_16"android:src="@drawable/selector_three"app:backgroundTint="@color/floatBackground"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent" /><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fabProfile"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginEnd="@dimen/dp_16"android:layout_marginBottom="@dimen/dp_16"android:src="@drawable/selector_four"app:backgroundTint="@color/floatBackground"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent" /></com.cpacm.FloatingMusicMenu></FrameLayout>

因为 Fragment 要全屏展示,所以使用了 FrameLayout 来布局,把 FloatingMusicMenu 放在右下角的位置。剩下的这个布局没什么可说的了,大家可以下载 apk 进行体验。

代码

这里的需求和竖屏时候其实一样,都需要状态的切换和 Fragment 的切换,Fragment 的切换和上面一样就不赘述了,来看一下状态的切换吧:

fabMenu?.setMusicCover(BitmapFactory.decodeResource(context.resources,when (position) {0 -> R.drawable.ic_nav_news_actived1 -> R.drawable.ic_nav_tweet_actived2 -> R.drawable.ic_nav_discover_actived3 -> R.drawable.ic_nav_my_pressedelse -> R.drawable.ic_nav_news_actived})
)
if (fabMenu != null && fabMenu!!.isExpanded)fabMenu!!.toggle()

这就 OK 了。

但是会发现横屏和竖屏的功能都差不多,所以就可以抽取一个父类了:

abstract class BaseHomeBottomTabWidget @JvmOverloads constructor(context: Context?,attrs: AttributeSet? = null,defStyleAttr: Int = 0,layoutId: Int
) : LinearLayout(context, attrs, defStyleAttr), View.OnClickListener {private var mFragmentManager: FragmentManager? = nullprivate var mFragments: java.util.ArrayList<Fragment>? = nullprivate var mLastFgIndex = 0private lateinit var mViewModel: MainViewModel/*** 外部调用初始化,传入必要的参数** @param fm*/fun init(fm: FragmentManager?, viewModel: MainViewModel) {mFragmentManager = fmmViewModel = viewModelif (mFragments == null) {mFragments = arrayListOf()mFragments?.add(getCurrentFragment(0)!!)mFragments?.add(getCurrentFragment(1)!!)mFragments?.add(getCurrentFragment(2)!!)mFragments?.add(getCurrentFragment(3)!!)}fragmentManger(viewModel.getPage() ?: 0)}/*** 初始化 设置点击事件。** @param view /*/@Suppress("LeakingThis")abstract fun initView(view: View)/*** fragment的切换 实现底部导航栏的切换** @param position 序号*/protected open fun fragmentManger(position: Int) {mViewModel.setPage(position)val fragmentTransaction =mFragmentManager!!.beginTransaction()val targetFg: Fragment = mFragments!![position]val lastFg: Fragment = mFragments!![mLastFgIndex]mLastFgIndex = positionif (lastFg.isAdded)fragmentTransaction.hide(lastFg)if (!targetFg.isAdded) {mFragmentManager!!.beginTransaction().remove(targetFg).commitAllowingStateLoss()fragmentTransaction.add(R.id.flHomeFragment, targetFg)}fragmentTransaction.show(targetFg)fragmentTransaction.commitAllowingStateLoss()}init {initView(View.inflate(context, layoutId, this))}}
使用

使用和竖屏是一样的,不过需要在 layout-land 文件夹下建立一个同名的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.zj.play.view.main.HomeBottomLandTabWidget xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/homeLandView"android:layout_width="match_parent"android:layout_height="match_parent" />

然后在 Activity 使用时要对横竖屏做一下判断:

when (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {true -> homeView.init(supportFragmentManager, viewModel)false -> homeLandView.init(supportFragmentManager, viewModel)
}

好了,这就可以了!

结尾

就先写到这里吧,其实代码并不多,大家可以直接去github.com/zhujiang521…进行查看代码,希望能够帮到大家,如果大家有什么建议欢迎评论区告诉我。

看完别忘了点赞

玩安卓从 0 到 1 之首页框架搭建相关推荐

  1. 玩安卓从 0 到 1 之架构思考

    前言 这篇文章是这个系列的第四篇文章了,下面是前三篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项目首页 3.玩安卓从 0 到 1 之首页框架搭建. 按照惯例,放一下 G ...

  2. 玩安卓从 0 到 1 之项目总结

    玩安卓从 0 到 1 之项目总结 前言 这篇文章是这个系列的第七篇文章了,也是我准备写的这个系列的最后一篇文章.下面是前六篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项 ...

  3. 玩安卓从 0 到 1 之列表一键置顶

    前言 系列文章 这篇文章是这个系列的第六篇文章了,下面是前五篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项目首页 3.玩安卓从 0 到 1 之首页框架搭建. 4.玩安卓 ...

  4. 玩安卓从 0 到 1 之总体概览

    玩安卓从 0 到 1 之总体概览 前言 其实写MVVM?瞎搞一波?和MVVM?继续搞一波这两篇文章的时候没觉得水,但是后来自己看了一遍,感觉除了截了几张图之外并没说什么关于技术的东西,这就很扯淡了,技 ...

  5. android10xposed,VirtualXposed v0.18.0,支持安卓10.0,无需root使用xp框架

    VirtualXposed适用于手机没有root,但是想用xp框架的同学!virtualxposed更新了0.18.0版本全面支持安卓10,vxp支持免root使用框架 安装模块 打开 Virtual ...

  6. creo JAVA_Creo 4.0二次开发工具框架搭建

    一.新建MFC DLL工程 二.配置项目属性 附加依赖项中输入:netapi32.lib;psapi.lib;mpr.lib;wsock32.lib;protk_dll_NU.lib;protk_dl ...

  7. android6.0分屏插件,xposed分屏模块安卓6.0下载

    安卓6.0系统分屏软件(xposed分屏插件)是一款支持分屏多任务软件,具有多窗口/双窗口功能,在众多智能分屏app中算是比较好用的啦,推荐给有需要的用户下载使用! 安卓6.0多窗口分屏软件简介 XH ...

  8. 安卓6.0运行时权限处理

    由于一直以来公司做的项目一直使用的目标版本是API22[安卓5.0],但是在API22上面会有很多的功能不能使用,例如软件管理权限,同时目前推荐的目标版本是API25也就是Android7.0,所以在 ...

  9. 《云阅2.0》一款同时看玩安卓和干货集中营资讯的App

    一.云阅2.0 <云阅>一个仿网易云音乐UI,使用Gank.Io及豆瓣Api开发的开源项目 在云阅发布第一版之后,大约经过了近两年的时间,不断的更新迭代,现在已经完成了2.0,相比第一版它 ...

最新文章

  1. 【Java源码分析】HashTable源码分析
  2. Java自带的性能监测工具之jinfo
  3. 第15章 进程间通行 15.6 XSI IPC 15.7 消息队列
  4. 腾讯天衍实验室主任郑冶枫
  5. mysql 没有mysql库_MySQL安装之后没有MySQL数据库的原因
  6. 算法 -克鲁斯卡尔算法
  7. asm字节码操作 方法的动态修改增加
  8. Linux实训vim编辑器的应用,Linux实训例题(vim编辑器)
  9. 调用系统相册,相机功能,遇到闪退的情况
  10. 三次给你讲清楚Redis之Redis是个啥
  11. 罗森伯格荣获2015年度中国数据中心优秀供应商与中国十大布线品牌两项大奖
  12. [HAOI2010]软件安装(树形背包,tarjan缩点)
  13. [sqlite] android create db in SD card
  14. ats反向代理和重定向
  15. rtx2060什么水平_不出预料+不负众望,NVIDIA RTX2060显卡获得2019我最喜欢称号
  16. SLAM--各种开源项目
  17. 车牌识别easypr的详细介绍
  18. Windows系统批量创建文件夹的技巧
  19. 穿孔发光字/外露发光字制作流程步骤
  20. 2023东北大学计算机考研信息汇总

热门文章

  1. android 浏览器支持java,Android浏览器访问java web的方法
  2. 任务管理器 中mysqld.exe 所占内存过高
  3. ubuntu自动同步时间
  4. 笔记本电脑之间建立串口通信
  5. noip模拟赛 街灯
  6. 程序员是吃青春饭的吗?
  7. PT项目-SAP库存账龄分析报表
  8. Ubuntu18.04 下虚拟机vm16pro 无法连接WIFI问题解决
  9. 【ArcGIS风暴】CASS建立标准分幅图框并在ArcGIS中DOM批量分幅案例教程
  10. 关于我的专业(niit软件工程方向)