再懒也要逼自己每个星期输出至少一篇文章,哪怕没人看。这篇是Android爬坑日记第三篇,也是小鹅爬坑日记的第二篇,小鹅业务所是我开源的记载业务APP能够看看我之前的一篇文章。
什么是输入Dialog,输入Dialog一般呈现在谈论区、聊天框,我下面放一张动图,分别是微博轻享版,和微信(当然微信这个不是Dialog)
软键盘进场动画
不知道我们有没有发现,微博和的作用是相同的,也是市面上大部分产品的软键盘进场作用。虽然不影响运用,可是视觉上的作用会差一点。微信的软键盘进场动画是贴着输入框的,看起来非常丝滑流通。
动画交互非常影响用户对APP的第一印象。 —— 米奇律师
以下我将以【小鹅业务所】的输入框为案例来完成流通的软键盘DialogFragment输入框动画。
作用
如同差异也不是很大。
细节决定着一个APP的品质。 —— 米奇律师
先上代码:github.com/MReP1/Littl…
爬坑
运用Dialog来完成会比较好管理,因为这个输入框在底部,因而运用了BottomSheetDialogFragment
来完成。并且弹出Dialog的时分弹出软键盘,软键盘收起的一起关闭Dialog。
BottomSheetDialogFragment
BottomSheetDialogFragment
是Material Design里的一个组件。自带了缓入缓出的进场出场动画。如果需求自己到XML文件里写动画,比较难完成缓入缓出的作用,因而考虑到我比较懒这一点,直接运用BottomSheetDialogFragment
是正确之选。
弹出软键盘
坑点
弹出Dialog的时分一起弹出软键盘也是有坑的,如果说到网上去找东西类,大概率软键盘弹不出来。举个例子:
object KeyBoard {
fun show(focusView: View){
val inputManager = appContext.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.showSoftInput(focusView, InputMethodManager.SHOW_IMPLICIT)
}
}
如果在DialogFragment
中运用这个东西方法,你会发现软键盘弹不出来。
class InputTextDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.etInput.requestFocus()
KeyBoard.show(binding.etInput)
}
}
我猜想是因为Fragment的视图没有绑定到Dialog的Window上,或者Dialog还没有弹出来,导致没有办法弹出软键盘。
这个时分就或许会有人告知我们:这还不简单,我等它几百毫秒绑定好了再弹不就好了。
class InputTextDialogFragment : BottomSheetDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.etInput.postDelay(300) {
binding.etInput.requestFocus()
KeyBoard.show(binding.etInput)
}
}
}
这种方法还真能够,可是在我看来不高雅。
于是我看了米奇律师的沉溺式状态栏灵光一动写出了以下代码。
binding.etInput.doOnAttach {
binding.etInput.requestFocus()
KeyBoard.show(binding.etInput)
}
很遗憾这种方法也不能完成,View绑定到Root View了,可是或许Dialog还没有弹出来,因而软键盘也弹不出来,详细原因我没有深究,有不同了解的欢迎到谈论区沟通。
解决方法
其实想要启动DialogFragment
的时分弹出软键盘没那么复杂。只需求给Dialog地点的Window设置以下软键盘输入标志位就好了,只需Dialog一呈现,软键盘马上弹出来。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.etInput.requestFocus()
dialog?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
}
监听软键盘弹起
在Android API 达到30及以上,Android引入了WindowInsetsAnimation
API,此刻就能够监听WindowInsets的占用屏幕大小的变化了。因而就能够完成类似于图1动图中微信那样流通的动画了。那么详细怎样完成呢?我先放一下代码,在注释中解说一下。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ViewCompat.setWindowInsetsAnimationCallback(
binding.root,
object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
// 寄存根视图的的初始高度
private var startHeight = 0
private var lastDiffH = 0
override fun onPrepare(animation: WindowInsetsAnimationCompat) {
if (startHeight == 0) {
// 此处赋值根视图的初始高度,因为动画开始前,根视图现已绑定到Window上了
// 因而能够取得初始高度
startHeight = binding.root.height
}
}
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: MutableList<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
// 获取软键盘的Inset
val typesInset = insets.getInsets(WindowInsetsCompat.Type.ime())
// 获取体系状态栏、导航栏的Inset
val otherInset = insets.getInsets(WindowInsetsCompat.Type.systemBars())
// 获取它们的差值
val diff = Insets.subtract(typesInset, otherInset).let {
Insets.max(it, Insets.NONE)
}
// 获取需求调整的高度
val diffH = abs(diff.top - diff.bottom)
// 父布局为 wrap_content 适应高度,而所有子布局都是贴着父布局底部的
// 因而只需调整bottomMargin,子布局往上走的一起父布局高度自适应
binding.etInput.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = diffH
}
// 当察觉到软键盘高度越来越小(阐明在收起了),就能够dismiss该DialogFragment了
if (diffH < lastDiffH) {
dismiss()
ViewCompat.setWindowInsetsAnimationCallback(binding.root, null)
}
// 寄存每一次软键盘的高度
lastDiffH = diffH
return insets
}
}
)
}
我的布局是贴着父layout底部的,所以需求调整marginBottom。
记得给该Window设置软键盘的插入形式为WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
,这意味着该DialogFragment
不会被软键盘顶上去,也就是说软键盘背后的内容其实也是View的一部分,可是是空白的。
dialog?.window?.setSoftInputMode(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE or WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
} else {
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
}
)
这儿还需求分情况,在Android11以下的手机无法运用WindowInsetsAnimation
API,因而就没有办法用这个API享受到如此丝滑的动画啦。
其实在这个动图中,左图为Android10的作用,右图为Android11的作用,你会发现左图的界面没有贴着软键盘走,而右图的界面稳稳地贴着软键盘。
那么Android11以下如何监听软键盘收起dismiss掉DialogFragment呢?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android11 以上动画
} else {
binding.root.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
var lastBottom = 0
override fun onGlobalLayout() {
ViewCompat.getRootWindowInsets(binding.root)?.let { insets ->
val bottom = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
if (lastBottom != 0 && bottom == 0) {
// 收起键盘了,能够 dismiss 了
dismiss()
binding.root.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
lastBottom = bottom
}
}
})
}
lastBottom
寄存软键盘的高度,当它被赋值不为0时,阐明软键盘弹出来了,此刻如果将它赋值为0,也就也为这软键盘收起了,就能够dissmiss掉DialogFragment了。
总结
文章结束啦,再放一遍代码吧!
这个完成或许看起来也不算很高雅,或许不是最佳实践,是我界面交互优化的一个测验,有的时分UI给的规划稿是静态的,作为开发赶工期肯定是怎样便利怎样来,功能完成就行、UI规划师不提BUG就行。可是谷歌团队提供了这么多好用的API来给我们优化界面,在如今我们手机都性能过剩的布景下,把主线程腾出点空间用于更舒畅的用户交互,也未尝不是一种应用优化。
其实我个人比较喜爱上层界面层的开发,根据这篇文章我再开一个坑吧,关于交互、动画,有空渐渐填坑。
参阅
zhuanlan.zhihu.com/p/343022200