本文主要是想表達一下在Android UI開發(fā)中我對
View
的看法,個人經驗有限,有什么問題歡迎一塊討論病曾。
如果說Activity
是Android提供的頁面容器的話互妓,那View
就是最基礎的UI組件(有點是廢話)寺滚。什么意思呢?我認為絕大部分UI開發(fā)工作都可以使用View來完成。下文就結合我工作中的一些實際case來談一下View
的使用。(當然不是講怎么自定義View
)
Fragment與View
Google
推薦使用Fragment
來在Activity
中搭建碎片化UI亲桦,但我感覺完全可以使用View
來代替Fragment
完成這個功能,并且代碼簡單易懂可維護浊仆、bug也少客峭。
為什么不推薦使用Fragment
呢?可以看一下這篇文章:
Fragment
都有哪些坑呢抡柿?下面這兩篇文章了解一下:
Android實戰(zhàn)技巧:Fragment的那些坑
當然我也是踩過Fragment
很多坑的,比如在使用ViewPager + Fragment + LifeCycle
這種架構時,ViewPager
切換Fragment
時舔琅,LifeCycle
根本沒做通知。
View相較于Fragment的優(yōu)勢
當然都是一些個人觀點
-
View
復用性更強,不像Fragment
那樣需要依賴于FrameLayout
洲劣。其實Fragment
的UI顯示邏輯也是交給View的呀(有點是廢話)备蚓。 -
View
使用起來更靈活,你可以對他進行各種操作课蔬,比如remove/add、嵌套在任何地方等等郊尝,而且回調寫起來更扁平二跋。 - 使用
View
不需要理會復雜的生命周期,其實你大部分情況下View的生命周期
已經足夠你使用了流昏,大不了寫個方法讓Activity
來回調就可以了扎即。 - 都是用來顯示UI,
View
相較于Fragment
更直接更純粹,更輕量級,當然bug更少况凉。
我們只需要使用View
創(chuàng)建響應式UI铺遂,實現回退棧以及屏幕事件的處理,不用Fragment
也能滿足實際開發(fā)的需求茎刚。《出自Square:從今天開始拋棄Fragment吧!》
View使用實戰(zhàn)
下面從幾個不同的case來講一下在實際場景中View
的使用襟锐。
使用View來代替Fragment
很簡單,只需要自定義一個ViewGroup
就Ok了膛锭。不過對于一些邏輯復雜的頁面我們會引入MVP
粮坞,那么如何讓Presenter
來感知生命周期事件呢?在使用Fragment
時初狰,我們可以直接感受生命周期莫杈,對于View
的話我們可以引入LifeCycle
,即View
感知Activity
的生命周期奢入,其實Fragment
的生命周期也是跟著Activity
走的呀筝闹。
View的Presenter對生命周期的感知
假設項目引入了LifeCycle
, 那么可以這樣設計:
Presenter實現LifecycleObserver
class DemoPresenter(demoPage:DemoPageProtocol) : LifecycleObserver {
private val model...
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
model.clearDisposable() //釋放model中的網絡資源
}
}
View把Presenter注冊到LifeCycle中
class DemoPage(context: Context) : LinearLayout(context), DemoPageProtocol{
private val presenter: DemoPresenter by lazy { DemoPresenter(this) }
init {
LayoutInflater.from(context).inflate(R.layout.demo_page, this) //這里可以使用merge來消除冗余的父節(jié)點
(context as AppCompatActivity).lifecycle.addObserver(presenter)
}
}
這里的強轉其實是沒有問題的腥光,我們使用的Activity
基本都繼承自AppCompatActivity
武福。(當然你要知道你在寫什么)
RecyclerView中的View
RecyclerView
是使用頻率非常高的一個控件,我個人比較推薦的一種寫法是:直接寫View
,View
到ViewHolder
的映射交給Adapter
來完成平痰。具體封裝方式可以參考下面這篇文章:
View中可以做一些簡單的網絡請求
RecyclerView
中的View
有時是會含有一些簡單的網絡事件的比如點贊宗雇、關注等等莹规。我一般是直接寫在View
中,因為我感覺這樣寫起來更直觀腻扇。但是網絡請求在什么時候釋放呢砾嫉?我感覺可以在View onDetachedFromWindow
時把這些網絡事件釋放掉:
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
disposableList.forEach { //釋放 disposable, 防止內存泄漏問題
it.dispose()
}
}
PopupWindow與View
為什么說這個呢焕刮? 其實是對應到DialogFragment
配并。對于這個我想說還是別用。它的內部實現是: Fragment->(Dialog ->(PhoneWindow))溉旋。這3個東西加在一起就夠頭疼的了观腊。
所以對于一些側滑彈窗、上下操作彈窗可以使用PopupWindow+View
來實現梧油,不過PopupWindow
在這種場景下也有一些問題,但相較于DialogFragment
少一些:
PopupWindow不顯示的問題:其實這篇文章也沒有完全解決褪子,在某些手機上你一定要定死寬高骗村,PopupWindow
才可以顯示出來。
PopupWindow的彈出位置:PopupWindow彈出位置的計算渔扎。其實我目前使用的都是基于參照物Anchor(一般我都是取Activity.window.decorView
)的相對位置來展示的。
在具體使用時最好采用組合的方式,比如:
class SimplePopupWindow(val context: Context, val mContentView: View) {
private var mWindow = PopupWindow()
init {
mWindow.apply {
contentView = mContentView
height = UIUtil.getScreenHeight()
width = UIUtil.getScreenWidth()
...
}
}
fun show(anchor: View) {
mWindow.update()
mWindow.showAsDropDown(anchor, 0, 0, Gravity.TOP)
}
.....
}
View的生命周期
想較于Fragment
的生命周期來說财忽,View
的生命周期就很弱了即彪,View
的生命周期相關方法可以參考下面這篇文章:
這里重點提一下:對于View.onAttatchToWindow
方法你應該知道它是在ViewRootImpl.performTraversal()
中開始回調的隶校,具體回調時機是measure
前深胳。
但是當使用View
來搭建頁面級UI時,像onAttatchToWindow
轻庆、onDetachedFromWindow
這種方法可能就不是很適用了敛劝。我的一般操作是寫個方法直接讓上層(Activity
)來調用:
DemoPage
class DemoPage(context: Context) : LinearLayout(context),DemoPageProtocol{
//View被展示時,Activtiy回調這個方法
fun show() {
//load data
}
//對應Activtiy的onResume
fun onResume(){
}
}
對于ViewPager+View
的架構來說夸盟,完成View
的懶加載并不是什么難事蛾方。
Dialog與View
我曾經遇到過這樣一個需求:
項目中的一個全局loading
是使用Dialog
來實現的上陕,這就造成在loading出現時界面是鎖死的,在這種情況下如果網絡比較慢的話转捕,很容易就讓用戶以為我們的app死掉了唆垃。所以需要實現這樣一個全局loading:在它出現的時候不能鎖死界面五芝,并且用戶點擊返回鍵可以關掉它。
我是怎么實現的呢枢步?其實我的實現方法比較取巧,好不好先不說,說一下思路吧:
- 自定義一個
ViewGroup
, 它出現時會展示loading動畫渐尿。 - 其內部含有一個看不見的
EditText
醉途,用于監(jiān)聽用戶是否點擊了返回鍵,點擊了返回鍵的話就關掉loading砖茸。 - 可以自動attach到
Activity
的DecorView
上隘擎,當然也可以當做一個普通的View
由其他組件來使用。
我個人感覺使用View
來實現這個loading凉夯,相較于Dialog
來說,還是很靈活的劲够,有興趣的同學可以看一下代碼: