我有一个很长的带有滚动视图的活动。它是一个包含用户必须填写的各种字段的表单。我在表单的中间有一个复选框,当用户选中它时,我想滚动到视图的特定部分。是否有办法以编程方式滚动到EditText对象(或任何其他视图对象)?
此外,我知道这是可能的使用X和Y坐标,但我想避免这样做,因为形式可能会从用户到用户的变化。
我有一个很长的带有滚动视图的活动。它是一个包含用户必须填写的各种字段的表单。我在表单的中间有一个复选框,当用户选中它时,我想滚动到视图的特定部分。是否有办法以编程方式滚动到EditText对象(或任何其他视图对象)?
此外,我知道这是可能的使用X和Y坐标,但我想避免这样做,因为形式可能会从用户到用户的变化。
当前回答
我认为我已经找到了更优雅、更不容易出错的解决方案
ScrollView.requestChildRectangleOnScreen
它不涉及数学运算,与其他提出的解决方案相反,它将正确地处理上下滚动。
/**
* Will scroll the {@code scrollView} to make {@code viewToScroll} visible
*
* @param scrollView parent of {@code scrollableContent}
* @param scrollableContent a child of {@code scrollView} whitch holds the scrollable content (fills the viewport).
* @param viewToScroll a child of {@code scrollableContent} to whitch will scroll the the {@code scrollView}
*/
void scrollToView(ScrollView scrollView, ViewGroup scrollableContent, View viewToScroll) {
Rect viewToScrollRect = new Rect(); //coordinates to scroll to
viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout)
scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
}
如果ScrollView正在被更改,那么将它封装到postDelayed中以使其更加可靠是个好主意
/**
* Will scroll the {@code scrollView} to make {@code viewToScroll} visible
*
* @param scrollView parent of {@code scrollableContent}
* @param scrollableContent a child of {@code scrollView} whitch holds the scrollable content (fills the viewport).
* @param viewToScroll a child of {@code scrollableContent} to whitch will scroll the the {@code scrollView}
*/
private void scrollToView(final ScrollView scrollView, final ViewGroup scrollableContent, final View viewToScroll) {
long delay = 100; //delay to let finish with possible modifications to ScrollView
scrollView.postDelayed(new Runnable() {
public void run() {
Rect viewToScrollRect = new Rect(); //coordinates to scroll to
viewToScroll.getHitRect(viewToScrollRect); //fills viewToScrollRect with coordinates of viewToScroll relative to its parent (LinearLayout)
scrollView.requestChildRectangleOnScreen(scrollableContent, viewToScrollRect, false); //ScrollView will make sure, the given viewToScrollRect is visible
}
}, delay);
}
其他回答
我的EditText在ScrollView中嵌套了几个层,而ScrollView本身并不是布局的根视图。因为getTop()和getBottom()似乎在报告它所包含的视图中的坐标,所以我让它通过迭代EditText的父节点来计算从ScrollView顶部到EditText顶部的距离。
// Scroll the view so that the touched editText is near the top of the scroll view
new Thread(new Runnable()
{
@Override
public
void run ()
{
// Make it feel like a two step process
Utils.sleep(333);
// Determine where to set the scroll-to to by measuring the distance from the top of the scroll view
// to the control to focus on by summing the "top" position of each view in the hierarchy.
int yDistanceToControlsView = 0;
View parentView = (View) m_editTextControl.getParent();
while (true)
{
if (parentView.equals(scrollView))
{
break;
}
yDistanceToControlsView += parentView.getTop();
parentView = (View) parentView.getParent();
}
// Compute the final position value for the top and bottom of the control in the scroll view.
final int topInScrollView = yDistanceToControlsView + m_editTextControl.getTop();
final int bottomInScrollView = yDistanceToControlsView + m_editTextControl.getBottom();
// Post the scroll action to happen on the scrollView with the UI thread.
scrollView.post(new Runnable()
{
@Override
public void run()
{
int height =m_editTextControl.getHeight();
scrollView.smoothScrollTo(0, ((topInScrollView + bottomInScrollView) / 2) - height);
m_editTextControl.requestFocus();
}
});
}
}).start();
检查Android源代码,你会发现ScrollView已经有一个成员函数scrollToChild(View),它做的正是所请求的。不幸的是,这个函数由于某种模糊的原因被标记为private。在这个函数的基础上,我写了下面的函数,它可以找到指定为参数的视图上方的第一个ScrollView,并滚动它,使它在ScrollView中可见:
private void make_visible(View view)
{
int vt = view.getTop();
int vb = view.getBottom();
View v = view;
for(;;)
{
ViewParent vp = v.getParent();
if(vp == null || !(vp instanceof ViewGroup))
break;
ViewGroup parent = (ViewGroup)vp;
if(parent instanceof ScrollView)
{
ScrollView sv = (ScrollView)parent;
// Code based on ScrollView.computeScrollDeltaToGetChildRectOnScreen(Rect rect) (Android v5.1.1):
int height = sv.getHeight();
int screenTop = sv.getScrollY();
int screenBottom = screenTop + height;
int fadingEdge = sv.getVerticalFadingEdgeLength();
// leave room for top fading edge as long as rect isn't at very top
if(vt > 0)
screenTop += fadingEdge;
// leave room for bottom fading edge as long as rect isn't at very bottom
if(vb < sv.getChildAt(0).getHeight())
screenBottom -= fadingEdge;
int scrollYDelta = 0;
if(vb > screenBottom && vt > screenTop)
{
// need to move down to get it in view: move down just enough so
// that the entire rectangle is in view (or at least the first
// screen size chunk).
if(vb-vt > height) // just enough to get screen size chunk on
scrollYDelta += (vt - screenTop);
else // get entire rect at bottom of screen
scrollYDelta += (vb - screenBottom);
// make sure we aren't scrolling beyond the end of our content
int bottom = sv.getChildAt(0).getBottom();
int distanceToBottom = bottom - screenBottom;
scrollYDelta = Math.min(scrollYDelta, distanceToBottom);
}
else if(vt < screenTop && vb < screenBottom)
{
// need to move up to get it in view: move up just enough so that
// entire rectangle is in view (or at least the first screen
// size chunk of it).
if(vb-vt > height) // screen size chunk
scrollYDelta -= (screenBottom - vb);
else // entire rect at top
scrollYDelta -= (screenTop - vt);
// make sure we aren't scrolling any further than the top our content
scrollYDelta = Math.max(scrollYDelta, -sv.getScrollY());
}
sv.smoothScrollBy(0, scrollYDelta);
break;
}
// Transform coordinates to parent:
int dy = parent.getTop()-parent.getScrollY();
vt += dy;
vb += dy;
v = parent;
}
}
scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.smoothScrollTo(0, myTextView.getTop());
}
});
回答来自我的实际项目。
这对我来说很管用:
targetView.getParent().requestChildFocus(targetView,targetView);
RequestChildFocus(查看子视图,查看焦点)
child -这个ViewParent的想要聚焦的子对象。该视图将包含聚焦视图。它并不一定是真正有焦点的观点。
focused -实际上具有焦点的child的后代视图
如果有人正在寻找Kotlin版本,您可以使用扩展函数来实现这一点
fun ScrollView.scrollToChild(view: View, onScrolled: (() -> Unit)? = null) {
view.requestFocus()
val scrollBounds = Rect()
getHitRect(scrollBounds)
if (!view.getLocalVisibleRect(scrollBounds)) {
findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.Main) {
smoothScrollTo(0, view.bottom - 40)
onScrolled?.invoke()
}
}
}
有一个小回调让你在滚动之后做一些事情。