这周完成了一个需求,涉及到EditText和软键盘,其中一个要求是:键盘出现时,文本输入框和键盘的间距大于16dp。
现在回头看,没想到EditText能折腾出新鲜东西。
整个页面的布局走常规路线,我画了一张草图,其中ScrollView和EditText用红色标注了一下:
一、键盘出现时,遮盖住了部分EditText
这个问题,写项目的时候我是没有遇到的,为了这篇文章写Demo时出现了,真是amazing。
然后这个问题的解法,在搜索引擎上查找到,但是结果并不理想,看到的方案太复杂,比如:使用ViewTreeObserver.OnGlobalLayoutListener监听整个页面布局变化,在回调里滑动ScrollView
于是我对比了一下项目和Demo关于使用EditText的代码。发现区别在于:layout_height的值。
- 当layout_height="xxdp"时,会出现软键盘遮盖部分EditText的问题
- 下面这种写法则不会导致软键盘遮盖的问题
<EditText ... android:paddingTop="18dp" android:paddingBottom="18dp" android:layout_height="wrap_content"/>
所以,这个问题我们可以规避掉。至于这两种写法,为何会造成键盘表现不同,有兴趣的朋友可以深拔拔看,又知道的也请告知一下,感谢~
图【EditText 被键盘遮盖部分底部】:
二、如何控制文本输入框和键盘的间距
首先,ScrollView会自动滚动合适的距离将带焦点的View滑动到屏幕的可见区域,这个知识在做这次的需求之前我已知道。后面所有的思路,就是如何利用ScrollView的这一特点。
解法一:
设置EditText的bottom padding,结合InsetDrawable,在视觉上可以以假乱真。
最后没有采取这种方案,这个方案会导致EditText和它底部临近的控件间距变大(如果没底部没有其他控件这个方案就很适合)。
效果大概是这样的:
解法二:
巧用ScrollView自动滚动带焦点View的特点。当带焦点的View不在屏幕可见的范围时,ScrollView内部会根据这个View的bounds,计算一个合适的偏移量,然后将该View滚动到可见的合适范围。
我们看一下ScrollView#onSizeChanged()方法的源码:
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
View currentFocused = findFocus();
if (null == currentFocused || this == currentFocused)
return;
// If the currently-focused view was visible on the screen when the
// screen was at the old height, then scroll the screen to make that
// view visible with the new screen height.
if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
//获取View的位置宽高信息等
currentFocused.getDrawingRect(mTempRect);
offsetDescendantRectToMyCoords(currentFocused, mTempRect);
//根据此时获取到焦点的View计算滑动距离,
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);
doScrollY(scrollDelta);
}
}
computeScrollDeltaToGetChildRectOnScreen方法名好长,打开这个方法的文档,我们能看到:
Compute the amount to scroll in the Y direction in order to get a rectangle completely on the screen (or, if taller than the screen, at least the first screen size chunk of it).
大意是:计算rectangle表示的View完全可见需要的的滑动距离,这个是我们需要的方法。该方法的修饰符是protected,意味着我们通过可以继承的方式重写该方法,通过修改该方法的返回值,达到我们的目的,整个修改的开发量巨小,如下:
class FITBScrollView(
context: Context?,
attrs: AttributeSet?
) : ScrollView(context, attrs) {
var extraMargin = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
16f,
resources.displayMetrics
).roundToInt()
/**
* EditText获取焦点后,ScrollView会将它滑动到屏幕显示的范围内,这个时候这个方法会被触发,用来计算Y轴滚动距离
*/
override fun computeScrollDeltaToGetChildRectOnScreen(rect: Rect?): Int {
//Child非完全可见时
if (rect?.bottom ?: 0 > scrollY + height - extraMargin) {
return super.computeScrollDeltaToGetChildRectOnScreen(rect) + extraMargin
}
return super.computeScrollDeltaToGetChildRectOnScreen(rect)
}
}
效果大概是这样的:
希望对有类似问题的开发朋友们有帮助。尤其是EditText被软键盘遮挡的问题,不希望大家用太复杂的方案,因为复杂的方案常常意味着更多的风险。