如何防止 HorizontalScrollView 的 scroll/fling 上的焦点发生变化?
How can I prevent focus changes on scroll/fling for a HorizontalScrollView?
当 EditText 或其他可聚焦视图存在于 HorizontalScrollView
内部时,它会在滑动时获得聚焦。
深入研究源码,你会明白为什么:
/**
* Fling the scroll view
*
* @param velocityX The initial velocity in the X direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the left.
*/
public void fling(int velocityX) {
if (getChildCount() > 0) {
int width = getWidth() - mPaddingRight - mPaddingLeft;
int right = getChildAt(0).getWidth();
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
Math.max(0, right - width), 0, 0, width/2, 0);
final boolean movingRight = velocityX > 0;
View currentFocused = findFocus();
View newFocused = findFocusableViewInMyBounds(movingRight,
mScroller.getFinalX(), currentFocused);
if (newFocused == null) {
newFocused = this;
}
if (newFocused != currentFocused) {
newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
}
postInvalidateOnAnimation();
}
}
为此建议的一些解决方法涉及使用以下属性:
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true"
这适用于一些简单的情况,但如果你的 HorizontalScrollView
嵌套在另一个 ScrollView
中,它可能会导致奇怪的行为(例如,外部滚动视图将跳转到现在被聚焦的容器).
同样根据尝试此解决方案的经验,可能需要将其添加到 每个 包含可聚焦视图(例如 EditText
)的父容器中。如果你有任何复杂的焦点逻辑在进行,这一切都会失控。
还有其他解决方法吗?
另一种解决方案是继承 HorizontalScrollView
并进行以下修改:
/**
* Class which modifies default focus logic so that children aren't focused
* when scrolling/flinging.
*/
class NoFocusHorizontalScrollView(context: Context, attrs: AttributeSet) : HorizontalScrollView(context, attrs) {
private var flingCallInProgress: Boolean = false
override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean {
return true
}
override fun fling(velocityX: Int) {
// Mark that the fling is in progress before calling the fling logic.
// This should be fairly safe given we're on the UI thread.
flingCallInProgress = true
super.fling(velocityX)
flingCallInProgress = false
}
override fun getFocusables(direction: Int): ArrayList<View> {
// During a fling HSV will try to focus on a child. This helps prevents that behavior.
return if (flingCallInProgress) {
ArrayList(0)
} else {
super.getFocusables(direction)
}
}
}
当 EditText 或其他可聚焦视图存在于 HorizontalScrollView
内部时,它会在滑动时获得聚焦。
深入研究源码,你会明白为什么:
/**
* Fling the scroll view
*
* @param velocityX The initial velocity in the X direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the left.
*/
public void fling(int velocityX) {
if (getChildCount() > 0) {
int width = getWidth() - mPaddingRight - mPaddingLeft;
int right = getChildAt(0).getWidth();
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
Math.max(0, right - width), 0, 0, width/2, 0);
final boolean movingRight = velocityX > 0;
View currentFocused = findFocus();
View newFocused = findFocusableViewInMyBounds(movingRight,
mScroller.getFinalX(), currentFocused);
if (newFocused == null) {
newFocused = this;
}
if (newFocused != currentFocused) {
newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
}
postInvalidateOnAnimation();
}
}
为此建议的一些解决方法涉及使用以下属性:
android:descendantFocusability="beforeDescendants"
android:focusable="true"
android:focusableInTouchMode="true"
这适用于一些简单的情况,但如果你的 HorizontalScrollView
嵌套在另一个 ScrollView
中,它可能会导致奇怪的行为(例如,外部滚动视图将跳转到现在被聚焦的容器).
同样根据尝试此解决方案的经验,可能需要将其添加到 每个 包含可聚焦视图(例如 EditText
)的父容器中。如果你有任何复杂的焦点逻辑在进行,这一切都会失控。
还有其他解决方法吗?
另一种解决方案是继承 HorizontalScrollView
并进行以下修改:
/**
* Class which modifies default focus logic so that children aren't focused
* when scrolling/flinging.
*/
class NoFocusHorizontalScrollView(context: Context, attrs: AttributeSet) : HorizontalScrollView(context, attrs) {
private var flingCallInProgress: Boolean = false
override fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean {
return true
}
override fun fling(velocityX: Int) {
// Mark that the fling is in progress before calling the fling logic.
// This should be fairly safe given we're on the UI thread.
flingCallInProgress = true
super.fling(velocityX)
flingCallInProgress = false
}
override fun getFocusables(direction: Int): ArrayList<View> {
// During a fling HSV will try to focus on a child. This helps prevents that behavior.
return if (flingCallInProgress) {
ArrayList(0)
} else {
super.getFocusables(direction)
}
}
}