我有一个很长的滚动视图活动。这是一个包含用户必须填写的各种字段的表单。我在表单的中间有一个复选框,当用户检查它时,我想滚动到视图的特定部分。有没有办法以编程方式滚动到 EditText 对象(或任何其他视图对象)?
另外,我知道这可以使用 X 和 Y 坐标,但我想避免这样做,因为表单可能会因用户而异。
我有一个很长的滚动视图活动。这是一个包含用户必须填写的各种字段的表单。我在表单的中间有一个复选框,当用户检查它时,我想滚动到视图的特定部分。有没有办法以编程方式滚动到 EditText 对象(或任何其他视图对象)?
另外,我知道这可以使用 X 和 Y 坐标,但我想避免这样做,因为表单可能会因用户而异。
private final void focusOnView(){
your_scrollview.post(new Runnable() {
@Override
public void run() {
your_scrollview.scrollTo(0, your_EditBox.getBottom());
}
});
}
如果您想将视图滚动到滚动视图的中心, Sherif elKhatib 的答案可以大大提高。这种可重用的方法将视图平滑滚动到 HorizontalScrollView 的可见中心。
private final void focusOnView(final HorizontalScrollView scroll, final View view) {
new Handler().post(new Runnable() {
@Override
public void run() {
int vLeft = view.getLeft();
int vRight = view.getRight();
int sWidth = scroll.getWidth();
scroll.smoothScrollTo(((vLeft + vRight - sWidth) / 2), 0);
}
});
}
垂直ScrollView
使用
...
int vTop = view.getTop();
int vBottom = view.getBottom();
int sHeight = scroll.getBottom();
scroll.smoothScrollTo(((vTop + vBottom - sHeight) / 2), 0);
...
这对我很有效:
targetView.getParent().requestChildFocus(targetView,targetView);
public void RequestChildFocus(查看孩子,查看重点)
child - 此 ViewParent 需要焦点的孩子。此视图将包含焦点视图。实际关注的不一定是视图。
聚焦- 实际具有焦点的孩子的后代视图
在我看来,滚动到给定矩形的最佳方式是通过View.requestRectangleOnScreen(Rect, Boolean)
. 您应该在要滚动到的位置上调用它View
并传递要在屏幕上可见的本地矩形。第二个参数应该false
用于平滑滚动和true
立即滚动。
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, false);
我根据 WarrenFaith 的 Answer 制作了一个小型实用方法,此代码还考虑了该视图是否已经在滚动视图中可见,无需滚动。
public static void scrollToView(final ScrollView scrollView, final View view) {
// View needs a focus
view.requestFocus();
// Determine if scroll needs to happen
final Rect scrollBounds = new Rect();
scrollView.getHitRect(scrollBounds);
if (!view.getLocalVisibleRect(scrollBounds)) {
new Handler().post(new Runnable() {
@Override
public void run() {
scrollView.smoothScrollTo(0, view.getBottom());
}
});
}
}
您应该将您的TextView
请求重点放在:
mTextView.requestFocus();
我的 EditText 在我的 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();
另一种变化是:
scrollView.postDelayed(new Runnable()
{
@Override
public void run()
{
scrollView.smoothScrollTo(0, img_transparent.getTop());
}
}, 200);
或者您可以使用该post()
方法。
如果 ScrollView 是 ChildView 的直接父级,则上述答案将正常工作。如果您的 ChildView 被包裹在 ScrollView 中的另一个 ViewGroup 中,则会导致意外行为,因为 View.getTop() 获取相对于其父级的位置。在这种情况下,您需要实现:
public static void scrollToInvalidInputView(ScrollView scrollView, View view) {
int vTop = view.getTop();
while (!(view.getParent() instanceof ScrollView)) {
view = (View) view.getParent();
vTop += view.getTop();
}
final int scrollPosition = vTop;
new Handler().post(() -> scrollView.smoothScrollTo(0, scrollPosition));
}
我知道这对于更好的答案可能为时已晚,但理想的完美解决方案必须是像定位器这样的系统。我的意思是,当系统为编辑器字段进行定位时,它会将字段放在键盘上,因此按照 UI/UX 规则,它是完美的。
下面的代码使Android方式定位顺利。首先,我们将当前滚动点作为参考点。第二件事是为编辑器找到最佳定位滚动点,为此我们滚动到顶部,然后请求编辑器字段使 ScrollView 组件进行最佳定位。嘎查!我们已经学会了最好的位置。现在,我们要做的是从上一个点平滑滚动到我们新找到的点。如果您愿意,您可以通过使用scrollTo而不是仅使用smoothScrollTo来省略平滑滚动。
注意:主容器 ScrollView 是一个名为 scrollViewSignup 的成员字段,因为我的示例是一个注册屏幕,您可能会想很多。
view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(final View view, boolean b) {
if (b) {
scrollViewSignup.post(new Runnable() {
@Override
public void run() {
int scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
int new_scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, scrollY);
scrollViewSignup.smoothScrollTo(0, new_scrollY);
}
});
}
}
});
如果您想将此块用于所有EditText实例,并快速将其与您的屏幕代码集成。您可以简单地制作如下所示的遍历器。为此,我将主 OnFocusChangeListener 设为名为focusChangeListenerToScrollEditor的成员字段,并在 onCreate 期间调用它,如下所示。
traverseEditTextChildren(scrollViewSignup, focusChangeListenerToScrollEditor);
方法实现如下。
private void traverseEditTextChildren(ViewGroup viewGroup, View.OnFocusChangeListener focusChangeListenerToScrollEditor) {
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = viewGroup.getChildAt(i);
if (view instanceof EditText)
{
((EditText) view).setOnFocusChangeListener(focusChangeListenerToScrollEditor);
}
else if (view instanceof ViewGroup)
{
traverseEditTextChildren((ViewGroup) view, focusChangeListenerToScrollEditor);
}
}
}
因此,我们在这里所做的就是让所有 EditText 实例子级调用焦点的侦听器。
为了达到这个解决方案,我在这里检查了所有的解决方案,并生成了一个新的解决方案以获得更好的 UI/UX 结果。
非常感谢所有其他激励我的答案。
yourScrollView.smoothScrollTo(0, yourEditText.getTop());
去做就对了 ;)
我想我找到了更优雅、更不容易出错的解决方案
不涉及数学,并且与其他建议的解决方案相反,它将正确处理上下滚动。
/**
* 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
}
将其包装起来postDelayed
以使其更可靠是个好主意,以防ScrollView
目前正在更改
/**
* 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);
}
参考:https ://stackoverflow.com/a/6438240/2624806
以下效果要好得多。
mObservableScrollView.post(new Runnable() {
public void run() {
mObservableScrollView.fullScroll([View_FOCUS][1]);
}
});
我的解决方案是:
int[] spinnerLocation = {0,0};
spinner.getLocationOnScreen(spinnerLocation);
int[] scrollLocation = {0, 0};
scrollView.getLocationInWindow(scrollLocation);
int y = scrollView.getScrollY();
scrollView.smoothScrollTo(0, y + spinnerLocation[1] - scrollLocation[1]);
垂直滚动,适用于表单。答案基于 Ahmadalibaloch 水平滚动。
private final void focusOnView(final HorizontalScrollView scroll, final View view) {
new Handler().post(new Runnable() {
@Override
public void run() {
int top = view.getTop();
int bottom = view.getBottom();
int sHeight = scroll.getHeight();
scroll.smoothScrollTo(0, ((top + bottom - sHeight) / 2));
}
});
}
查看 Android 源代码,您会发现已经有一个成员函数ScrollView
– scrollToChild(View)
– 完全按照要求执行。不幸的是,这个功能被标记为一些晦涩的原因private
。基于该函数,我编写了以下函数,该函数ScrollView
在指定的参数上方找到第View
一个并滚动它以使其在 中可见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;
}
}
你可以ObjectAnimator
这样使用:
ObjectAnimator.ofInt(yourScrollView, "scrollY", yourView.getTop()).setDuration(1500).start();
根据 Sherif 的回答,以下内容最适合我的用例。值得注意的变化是getTop()
代替getBottom()
和smoothScrollTo()
代替scrollTo()
。
private void scrollToView(final View view){
final ScrollView scrollView = findViewById(R.id.bookmarksScrollView);
if(scrollView == null) return;
scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.smoothScrollTo(0, view.getTop());
}
});
}
问:有没有办法以编程方式将滚动视图滚动到特定的编辑文本?
Ans:recyclerview 最后位置的嵌套滚动视图添加了记录数据。
adapter.notifyDataSetChanged();
nested_scroll.setScrollY(more Detail Recycler.getBottom());
以下是我正在使用的:
int amountToScroll = viewToShow.getBottom() - scrollView.getHeight() + ((LinearLayout.LayoutParams) viewToShow.getLayoutParams()).bottomMargin;
// Check to see if scrolling is necessary to show the view
if (amountToScroll > 0){
scrollView.smoothScrollTo(0, amountToScroll);
}
这将获得显示视图底部所需的滚动量,包括该视图底部的任何边距。
就我而言,那不是EditText
,那是googleMap
。它像这样成功地工作。
private final void focusCenterOnView(final ScrollView scroll, final View view) {
new Handler().post(new Runnable() {
@Override
public void run() {
int centreX=(int) (view.getX() + view.getWidth() / 2);
int centreY= (int) (view.getY() + view.getHeight() / 2);
scrollView.smoothScrollBy(centreX, centreY);
}
});
}
如果您想在打开软键盘时滚动到视图,那么它可能会有点棘手。到目前为止,我得到的最佳解决方案是结合使用插入回调和requestRectangleOnScreen
方法。
首先,您需要设置插入回调:
fun View.doOnApplyWindowInsetsInRoot(block: (View, WindowInsetsCompat, Rect) -> Unit) {
val initialPadding = recordInitialPaddingForView(this)
val root = getRootForView(this)
ViewCompat.setOnApplyWindowInsetsListener(root) { v, insets ->
block(v, insets, initialPadding)
insets
}
requestApplyInsetsWhenAttached()
}
fun View.requestApplyInsetsWhenAttached() {
if (isAttachedToWindow) {
requestApplyInsets()
} else {
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
v.removeOnAttachStateChangeListener(this)
v.requestApplyInsets()
}
override fun onViewDetachedFromWindow(v: View) = Unit
})
}
}
我们正在根视图上设置回调以确保我们被调用。Insets 可以在我们有问题的视图收到它们之前被消耗掉,所以我们必须在这里做额外的工作。
现在几乎很容易:
doOnApplyWindowInsetsInRoot { _, _, _ ->
post {
if (viewInQuestion.hasFocus()) {
requestRectangleOnScreen(Rect(0, 0, width, height))
}
}
}
您可以摆脱焦点检查。它可以限制调用次数requestRectangleOnScreen
。我使用post
在可滚动父计划滚动到焦点视图后运行操作。
如果有人正在寻找 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()
}
}
}
有一个小回调可以让你在滚动后做一些事情。
如果 scrlMain 是您的 NestedScrollView,则使用以下内容,
scrlMain.post(new Runnable() {
@Override
public void run() {
scrlMain.fullScroll(View.FOCUS_UP);
}
});