前言
這篇文章是這個系列的第三篇文章了,前兩篇文章分別是玩安卓從 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">
<FrameLayout
android:id="@+id/flHomeFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
</FrameLayout>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/dp_0_1"
android:background="@color/text_color" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_55"
android:orientation="horizontal">
<TextView
android: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" />
<TextView
android: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" />
<TextView
android: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" />
<TextView
android: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 包裹著 TextView ,TextView 用作 Tab 標簽,
這里的 Tab 我并沒有使用 ImageView + TextView ,而是直接使用了 TextView 的 drawableTop,能省一個控制元件是一個嘛!能少寫一個是一個!還有一點需要注意的是 textColor 和 drawableTop 都使用了 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 = position
if (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">
<FrameLayout
android:id="@+id/flHomeFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.cpacm.FloatingMusicMenu
android: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.FloatingActionButton
android: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.FloatingActionButton
android: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.FloatingActionButton
android: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.FloatingActionButton
android: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_actived
1 -> R.drawable.ic_nav_tweet_actived
2 -> R.drawable.ic_nav_discover_actived
3 -> R.drawable.ic_nav_my_pressed
else -> 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? = null
private var mFragments: java.util.ArrayList<Fragment>? = null
private var mLastFgIndex = 0
private lateinit var mViewModel: MainViewModel
/**
* 外部呼叫初始化,傳入必要的引數
*
* @param fm
*/
fun init(fm: FragmentManager?, viewModel: MainViewModel) {
mFragmentManager = fm
mViewModel = viewModel
if (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 = position
if (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…進行查看代碼,希望能夠幫到大家,如果大家有什么建議歡迎評論區告訴我,
看完別忘了點贊👍啊!下回見!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/184743.html
標籤:其他
上一篇:web與Android和iOS的webview資料互動,兼容寫法
下一篇:手寫喜馬拉雅APP特效








