如何自定义SnackBar
首先我们得了解SnackBar的布局:
之前我看有一些方案是获取内部的contentLayout,然后做一些处理。但是现在已经行不通了:
@RestrictTo(LIBRARY_GROUP)
public static final class SnackbarLayout extends BaseTransientBottomBar.SnackbarBaseLayout
@RestrictTo(LIBRARY_GROUP)
public class SnackbarContentLayout extends LinearLayout implements ContentViewCallback
现在这些都添加的有@RestrictTo(LIBRARY_GROUP)
注解,只能内部使用。
那所以有一个比较挫一点的方案,就是全部移除,并添加:
val customView = layoutInflater.inflate(R.layout.layout_snack_info_sticker_tips, null).apply {
findViewById<TextView>(R.id.snack_tips).text = message
}
// 替换默认视图
(view as ViewGroup).apply {
removeAllViews()
addView(customView)
}
需要注意的一个点:
Snackbar.view
这里拿到的是最外层的SnackbarLayout- SnackbarLayout左右有一个默认的padding,查看代码是12dp
下滑取消
我们知道Snackbar的下滑取消是通过Behavior
实现的,它是BaseTransientBottomBar
的内部类:
public static class Behavior extends SwipeDismissBehavior<View>
那么有一定就需要注意,Snackbar挂载的父view就必须是ConstraintLayout
,否则就不会生效。
另外默认的滑动取消是从左向右。
那么如何实现下滑取消,也很简单:
- 把默认的滑动取消屏蔽掉
- 重新拦截时间自己处理
var initialX = 0.0f
var initialY = 0.0f
behavior = object : BaseTransientBottomBar.Behavior() {
// 禁用原有的向右滑动关闭
override fun canSwipeDismissView(child: View): Boolean = false
// 添加触摸事件处理
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: View, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
// 如果点击的是snackbar,再记录起始位置
if (parent.isPointInChildBounds(child, event.x.toInt(), event.y.toInt())){
initialX = event.x
initialY = event.y
} else { // 否则直接不处理
return false
}
}
MotionEvent.ACTION_UP -> {
// 计算滑动距离
val dx = abs(event.x - initialX)
val dy = abs(event.y - initialY)
// 当纵向滑动距离大于横向时拦截事件,且超过阈值
if (dy > dx && dy > 20) {
dismiss()
return true
}
}
}
return super.onInterceptTouchEvent(parent, child, event)
}
}