关于软键盘的一些思考

前言

  公司的app大部分的界面都涉及到表单相关内容,所以要考虑软键盘和输入界面之间的协调性,于是在经过一系列的方法实践之后,总结了两种还算是比较和谐的方式。阅读本文前,请你先弄清楚windowSoftInputMode这个属性的作用,这个属性在Window与WindowManager一文中有讲解。

从布局入手

  这种方式需要设置activity的windowSoftInputMode为adjustResize。然后通过对activity根布局的变化来知晓软键盘的显示和隐藏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class KeyBoardInfo(
private var rootView: View,
var listener: OnKeyBoardHiddenChangeListener?
) : ViewTreeObserver.OnGlobalLayoutListener {
constructor(rootView: View, keyboardHiddenChanged: (Boolean) -> Unit) : this(rootView, object : OnKeyBoardHiddenChangeListener {
override fun onKeyBoardHiddenChanged(hidden: Boolean) {
keyboardHiddenChanged(hidden)
}
})

private val rootViewRect: Rect = Rect()
private var rootViewVisibleHeight: Int = 0

init {
rootView.viewTreeObserver.addOnGlobalLayoutListener(this)
}

override fun onGlobalLayout() {
rootView.getWindowVisibleDisplayFrame(rootViewRect)
val visibleHeight = rootViewRect.height()
when {
rootViewVisibleHeight == 0 -> rootViewVisibleHeight = visibleHeight
rootViewVisibleHeight - visibleHeight > 200 -> {
rootViewVisibleHeight = visibleHeight
listener?.onKeyBoardHiddenChanged(false)
}
visibleHeight - rootViewVisibleHeight > 200 -> {
rootViewVisibleHeight = visibleHeight
listener?.onKeyBoardHiddenChanged(true)
}
}
}

fun release() {
listener = null
}

interface OnKeyBoardHiddenChangeListener {
fun onKeyBoardHiddenChanged(hidden: Boolean)
}
}

  思路很简单,通过保存最近一次根布局的可视高度,然后和变化后的可是高度进行比较,达到某个差值就认为软键盘的状态发生了变化。

从事件分发入手

  这种方式在BaseActivity中重写dispatchTouchEvent(),在ACTION_DOWN的时候查看当前聚焦控件是否需要隐藏软键盘,若需要,则直接隐藏掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
if (shouldHideKeyboard(currentFocus, ev)) {
hideKeyboard(this)
whenKeyboardHidden()
}
}
}
return super.dispatchTouchEvent(ev)
}

open fun whenKeyboardHidden() {
//todo 软键盘隐藏的时候需要做的时候
}

fun hideKeyboard(context: Activity) {
context.currentFocus?.windowToken?.apply {
val service: InputMethodManager? = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager?
service?.hideSoftInputFromWindow(this, InputMethodManager.HIDE_NOT_ALWAYS)
}
}

fun shouldHideKeyboard(currentFocus: View?, event: MotionEvent): Boolean {
if (currentFocus == null || currentFocus !is EditText) return false

val l = intArrayOf(0, 0)
currentFocus.getLocationInWindow(l)
val left = l[0]
val top = l[1]
val bottom = top + currentFocus.height
val right = left + currentFocus.width
return !(event.x > left && event.x < right && event.y > top && event.y < bottom)
}

  使用这种方式,请设置windowSoftInputMode为adjustPan。

推荐方式

  我们来对比下两者的优缺点。
  1. 对于第一种方式,windowSoftInputMode设置为adjustResize的时候,如果布局底部有按钮,那么得根据软键盘的显示和隐藏动态的设置按钮的可视性,这种方式会让用户在视觉上有不协调地方;你可能会说,一样可以设置windowSoftInputMode为adjustPan,但是这样会导致一些表单界面最下面必须将软键盘收起后才能看到,而且需要给最外层控件设置点击事件,在点击之后隐藏软键盘,这样就太麻烦了;其次,现在app一般都会沉浸式状态栏,一般情况下我们会让布局占据状态栏的部分,然后动态设置顶部控件的边距达到这种效果,但是在这种方式下,adjustResize是无效的,也就是说,全屏模式和adjustResize两者是冲突的。但是这种方式也有优点,就是我们可以很方便的知道软键盘何时显示何时隐藏。
  2. 对于第二种方式,优点就是在手势触碰的时候,就会隐藏掉软键盘,然后再进行其他的响应。当然它也有缺点,一般软键盘的右上角都有一个收起软件盘的按钮,如果我们通过这个方式去隐藏软键盘,whenKeyboardHidden()方法是不会回调的,这显然就不是我们想要的那种效果。

  分析完这两者的利弊之后,我决定将两者结合使用,于是乎就得到了下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.guoshujinfu.mobile.gscloud.ui

import android.content.res.Resources
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.support.annotation.CallSuper
import android.support.v7.app.AppCompatActivity
import android.view.MotionEvent
import android.view.WindowManager
import com.guoshujinfu.mobile.gscloud.extension.hideKeyboard
import com.guoshujinfu.mobile.gscloud.extension.shouldHideKeyboard
import com.guoshujinfu.mobile.gscloud.ui.login.LoginActivity
import com.guoshujinfu.mobile.gscloud.utils.ActivityController
import com.guoshujinfu.mobile.gscloud.views.dialog.IphoneDialog
import com.guoshujinfu.mobile.gscloud.views.dialog.LoadingDialog
import com.gyf.barlibrary.ImmersionBar

/**
* @author fxYan
*/
abstract class BaseActivity : AppCompatActivity() {
private var latestHeight: Int = 0
private var displayRect: Rect = Rect()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutRes())
initComponents(savedInstanceState)
}

abstract fun getLayoutRes(): Int

@CallSuper
open fun initComponents(savedInstanceState: Bundle?) {
window.decorView.viewTreeObserver.addOnGlobalLayoutListener {
window.decorView.getWindowVisibleDisplayFrame(displayRect)
val visibleHeight = displayRect.height()
when {
latestHeight == 0 -> latestHeight = visibleHeight
latestHeight - visibleHeight > 200 -> {
latestHeight = visibleHeight
whenKeyboardShow()
}
visibleHeight - latestHeight > 200 -> {
latestHeight = visibleHeight
whenKeyboardHidden()
}
}
}
}

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
if (shouldHideKeyboard(currentFocus, ev)) {
hideKeyboard(this)
}
}
}
return super.dispatchTouchEvent(ev)
}

open fun whenKeyboardShow() {}

open fun whenKeyboardHidden() {}
}

  软键盘的显示和隐藏还是通过监听布局高度的变化来完成,然后再事件分发的时候如果需要隐藏软键盘就先将软键盘给隐藏掉,在进行其他的操作。

本文标题:关于软键盘的一些思考

文章作者:严方雄

发布时间:2018-06-11

最后更新:2018-09-13

原始链接:http://yanfangxiong.com/2018/06/11/关于软键盘的一些思考/

0%