背景介绍
在不沉浸状态栏时,当PreferenceFragmentCompat
中有EditTextPreference
时,点击该条目呼出的输入对话框将跟随键盘上移,但一旦在styles.xml
中设置
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
也就是沉浸状态栏或导航栏后,再呼出对话框时则无法上移。
历史回顾
这是一个2013年的陈年老bug1,从谷歌引入沉浸状态栏这个特性至今,该bug从未被修复,并且在stackoverflow也有不少帖子2,大致是通过手动上移控件的方式解决问题。
我的问题
这些帖子大多是针对自定义View上的EditText,所以自定义比较轻松,集大成者莫过于这个GitHub的回答,几乎可以适应一切布局,但在系统包装好的PreferenceFragmentCompat
下,这个代码仅能检测到键盘呼出后的高度改变,却无法自动上移输入框。
解决办法
在综合上面的资料之后,结合PreferenceFragmentCompat
的特性,我写出了最终解法,可能不是完美的办法,但是工作正常。
package xxx.yyy.zzz
import android.animation.ObjectAnimator
import android.graphics.Rect
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.Window
import androidx.annotation.Keep
import androidx.preference.EditTextPreference
import androidx.preference.EditTextPreferenceDialogFragmentCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import xxx.yyy.zzz.R
import java.lang.Thread.sleep
class SettingsFragment: PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_setting, rootKey)
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference is EditTextPreference) {
Log.d("MySF", "preference is EditTextPreference")
val f = EditTextPreferenceDialogFragmentCompat.newInstance(preference.key)
f.setTargetFragment(this, 0)
f.show(parentFragmentManager, null)
Thread {
var diff = 0
var cnt = 0
while (diff == 0 && cnt++ < 20) {
sleep(50)
if (f.dialog == null) continue
val v = view?:return@Thread
// https://github.com/mikepenz/MaterialDrawer/blob/aa9136fb4f5b3a80460fe5f47213985026d20c88/library/src/main/java/com/mikepenz/materialdrawer/util/KeyboardUtil.java
val r = Rect()
//r will be populated with the coordinates of your view that area still visible.
v.getWindowVisibleDisplayFrame(r)
//get screen height and calculate the difference with the useable area from the r
val height = v.context.resources.displayMetrics.heightPixels
diff = height - r.bottom
Log.d("MySF", "diff: $diff")
}
Log.d("MySF", "diff out while: $diff")
if (diff <= 0) return@Thread
Log.d("MySF", "f.dialog is ${
f.dialog}")
f.activity?.runOnUiThread {
f.dialog?.window?.apply {
val attr = attributes
Log.d("MySF", "animate from ${
attr.y} to ${
attr.y-diff/2}")
ObjectAnimator.ofInt(WindowAttributeSetter(this), "y", attr.y, attr.y-diff/2).setDuration(233).start()
}
}
}.start()
return
}
super.onDisplayPreferenceDialog(preference)
}
inner class WindowAttributeSetter(private val window: Window) {
@Keep
fun setY(y: Int) {
val attr = window.attributes
attr.y = y
Log.d("MySF", "set y to $y")
window.attributes = attr
}
}
}