為啥不一樣
最近做一個小項目馍资,項目中需要一個具備圓角背景的居中的提示筒主。這讓我想到了優(yōu)雅的Snackbar。但是Google了許多自定義Snackbar的帖子都不甚滿意鸟蟹,主要是在修改時不能很好的確定你想要的布局乌妙,大部分的文章都是通過獲取View,在代碼中修改例如背景建钥,文字顏色等藤韵,無法通過布局文件設置顯示的樣式。于是我設計了一個不一樣的實現(xiàn)熊经,實現(xiàn)并不復雜泽艘,只希望對看到此文的人有所幫助。
分析源碼
-
Snackbar的接口中沒有指定布局的接口镐依,我們只能從源碼分析是否有“漏洞“可尋匹涮。我們首先看一下make的源碼:
SnackBar#make源碼
如圖所示,我們看到了一個布局文件 “R.layout.design_layout_snackbar_include”槐壳,他對應著Snackbar的content然低。看到這里我們基本可以確定,這就是控制顯示樣式的布局文件雳攘。
-
布局文件“ R.layout.design_layout_snackbar_include”:
"內(nèi)容布局文件"
這個布局文件中一個SnackbarContentLayout包含一個Textview和一個Button带兜,這正是Snackbar 顯示時提示文字和按鈕。既然如此我們只要替換這個布局吨灭,我們就可以自定義布局了刚照。
修改布局代碼設計分析
- make 方法是一個靜態(tài)方法,通重寫是不可行的
- 然后我想到是否可以將舊的布局移除添加新的布局沃于?
- 這種方式可以實現(xiàn)布局的修改涩咖,SnackBar中的內(nèi)容布局已經(jīng)變了;
- 然后我們要考慮make時的文字內(nèi)容及button是否能應用到新的布局中去繁莹。我們在上一張圖中看到內(nèi)容布局是一個SnackbarContentLayout檩互。內(nèi)部有一個ID為snackbar_text的TextView和ID為snackbar_action的Button;我們的自定義布局中沒有這個ID咨演;而我們又想通過布局文件可視化的自定義布局我們該怎么辦呢闸昨?
- 使用布局文件自定義布局,并使得SnackbarContentLayout使用我們自定義的Msg view和Action button:
-
我們無法再xml中指定id為support庫中的ID snackbar_text 和 snackbar_action薄风。那么我們退一步饵较,初始時我們用我們的ID,在Inflate布局后修改這個ID為相應的ID遭赂。這樣修改后我們在show時會出現(xiàn)問題:snackbar_text和snackbar_action對應的View是空的循诉。我們在看一下源碼:
”onFinishInflate“
源碼中看出,這兩個view在inflate完成后就不再find了撇他,不管是requesLayout還是invalid都不行茄猫。而且這個onFinishInflate是protected修飾的,無法重寫困肩。我們只能找其他方式划纽,于是我想到了反射,用反射調(diào)用onFinishInflate锌畸。至此我們的設計已經(jīng)完成
具體編碼實現(xiàn):
- JSnackBar
class JSnackBar{
private lateinit var snackbar: Snackbar
private lateinit var msgView:TextView
private lateinit var actBtn:Button
fun make(view: View, msg:String, duration: Int, layout:Int):Snackbar{
snackbar = Snackbar.make(view, msg, duration)
val parent = snackbar.view as Snackbar.SnackbarLayout
//Layout depend on your custon layout file
parent.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
parent.setBackgroundColor(Color.TRANSPARENT)
parent.setPadding(0, 0,0, 0)
val contentView = parent.getChildAt(0) as SnackbarContentLayout
val newContentView = LayoutInflater.from(view.context).inflate(layout, contentView, false) as SnackbarContentLayout
//Reset Id
newContentView.id = contentView.id
msgView = newContentView.findViewById(R.id.jepack_snack_bar_msg)
actBtn = newContentView.findViewById(R.id.jepack_snack_bar_action)
msgView.id = android.support.design.R.id.snackbar_text
actBtn.id = android.support.design.R.id.snackbar_action
//Reflect find views勇劣。
try {
val method = newContentView::class.java.getDeclaredMethod("onFinishInflate")
method.isAccessible = true
method.invoke(newContentView)
//Reset msg text
msgView.text = msg
val index = parent.indexOfChild(contentView)
//替換布局
parent.removeViewAt(index)
parent.addView(newContentView, index)
}catch (e:Exception){
LogUtil.e("Failed to change snackbar layout! ", e.message)
}
return snackbar
}
}
- 布局文件(這只是我的一個簡單實現(xiàn),可根據(jù)實際情況自定義)
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.internal.SnackbarContentLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="bottom"
android:padding="<padding>">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@id/jepack_snack_bar_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
android:textColor="@android:color/white"
android:layout_gravity="center_horizontal"
android:background="@drawable/shape_tip_bg"
android:paddingStart="@dimen/snack_padding"
android:paddingEnd="@dimen/snack_padding"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<Button
android:id="@id/jepack_snack_bar_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:button="@null"
android:visibility="gone"
/>
</android.support.constraint.ConstraintLayout>
</android.support.design.internal.SnackbarContentLayout>
這里面的圖片潭枣,dimen等需要你根據(jù)需求修改比默;
- ids設置,方便擴展
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="jepack_snack_bar_msg" />
<item type="id" name="jepack_snack_bar_action" />
</resources>
到此我們就可以輕松實現(xiàn)了我們需要的效果卸耘。
此代碼將增加到Github倉庫中, 歡迎指正退敦!