简体   繁体   English

在滚动视图中滚动到特定视图

[英]Scroll to a specific view in scroll view

I have added a scrollview and the subchilds inside the scrollview.我在滚动视图中添加了一个滚动视图和子子项。 At some point i need to scroll to a specific view.在某些时候,我需要滚动到特定视图。

<scrollview>

1. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

2. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

3. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

4. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

5. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

6. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

7. <linearlayout>

    <textview></textview>
    <textview></textview>

   </linearlayout>

   <button>
   </button>

 </scrollview>

The above layout was created dynamically.上面的布局是动态创建的。 so i can't have the xml file posted here.所以我不能在这里发布 xml 文件。 Layout creation is completely dynamic.布局创建是完全动态的。 Even the number of child view inside the linearlayout may also vary.甚至线性布局内的子视图数量也可能会有所不同。

So when i click on the button i need to get scrolled to a particular view say here when i click on button i need to scroll to the linear layout 4. I tried with the scrollTo method but it scrolls to the top of the scrollview.所以当我点击按钮时,我需要滚动到一个特定的视图,当我点击按钮时,我需要滚动到线性布局 4。我尝试使用 scrollTo 方法,但它滚动到滚动视图的顶部。

Please provide some suggestions.请提供一些建议。

If child that we need to scroll to is not a direct child then above solutions don't work如果我们需要滚动到的孩子不是直接孩子,那么上述解决方案不起作用

I have used below solution in my project, sharing it may be helpful to others我在我的项目中使用了以下解决方案,分享它可能对其他人有帮助

/**
 * Used to scroll to the given view.
 *
 * @param scrollViewParent Parent ScrollView
 * @param view View to which we need to scroll.
 */
private void scrollToView(final ScrollView scrollViewParent, final View view) {
    // Get deepChild Offset
    Point childOffset = new Point();
    getDeepChildOffset(scrollViewParent, view.getParent(), view, childOffset);
    // Scroll to child.
    scrollViewParent.smoothScrollTo(0, childOffset.y);
}

/**
 * Used to get deep child offset.
 * <p/>
 * 1. We need to scroll to child in scrollview, but the child may not the direct child to scrollview.
 * 2. So to get correct child position to scroll, we need to iterate through all of its parent views till the main parent.
 *
 * @param mainParent        Main Top parent.
 * @param parent            Parent.
 * @param child             Child.
 * @param accumulatedOffset Accumulated Offset.
 */
private void getDeepChildOffset(final ViewGroup mainParent, final ViewParent parent, final View child, final Point accumulatedOffset) {
    ViewGroup parentGroup = (ViewGroup) parent;
    accumulatedOffset.x += child.getLeft();
    accumulatedOffset.y += child.getTop();
    if (parentGroup.equals(mainParent)) {
        return;
    }
    getDeepChildOffset(mainParent, parentGroup.getParent(), parentGroup, accumulatedOffset);
}

This should do the trick:这应该可以解决问题:

View targetView = findViewById(R.id.DESIRED_VIEW_ID);  
targetView.getParent().requestChildFocus(targetView,targetView);

public void RequestChildFocus (View child, View focused) public void RequestChildFocus(查看子项,查看焦点)

child - The child of this ViewParent that wants focus. child - 想要获得焦点的这个 ViewParent 的孩子。 This view will contain the focused view.该视图将包含聚焦视图。 It is not necessarily the view that actually has focus.实际具有焦点的不一定是视图。

focused - The view that is a descendant of child that actually has focus focused - 视图是实际上具有焦点的 child 的后代

try this:尝试这个:

ScrollView sv = // your scrollview
View insideView = // find the View that you need to scroll to which is inside this ScrollView
sv.scrollTo(0, (int)insideView.getY());

insideView.getY() will not work below API Level 11, for API Level below 11 you can replace insideView.getY() with insideView.getTop() insideView.getY()将无法在 API 级别 11 以下工作,对于低于 11 的 API 级别,您可以将insideView.getY()替换为insideView.getTop()

Since you can know the child LinearLayout that you need to scroll to, how about trying this.既然你可以知道你需要滚动到的子LinearLayout ,那么试试这个怎么样。

ScrollView sv = // your scrollview
View highlightedItem = // find the LinearLayout or View that you need to scroll to which is inside this ScrollView
sv.scrollTo(0, highlightedItem.getY());

More information on scrollTo as well as smoothScrollTo有关scrollTosmoothScrollTo 的更多信息

I think I have found more elegant and error prone solution using我想我找到了更优雅且容易出错的解决方案

ScrollView.requestChildRectangleOnScreen ScrollView.requestChildRectangleOnScreen

No math involved, and contrary to other proposed solutions, it will handle correctly scrolling both ways up and down.不涉及数学,与其他建议的解决方案相反,它将正确处理上下滚动。

void scrollToRow(ScrollView scrollView, LinearLayout linearLayout, TextView textViewToShow) {
    Rect textRect = new Rect(); //coordinates to scroll to
    textViewToShow.getHitRect(textRect); //fills textRect with coordinates of TextView relative to its parent (LinearLayout) 
    scrollView.requestChildRectangleOnScreen(linearLayout, textRect, false); //ScrollView will make sure, the given textRect is visible
}

It is a good idea to wrap it into postDelayed to make it more reliable, in case the ScrollView is being changed at the moment最好将其包装到postDelayed以使其更可靠,以防此时ScrollView正在更改

private void scrollToRow(final ScrollView scrollView, final LinearLayout linearLayout, final TextView textViewToShow) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            Rect textRect = new Rect(); //coordinates to scroll to
            textViewToShow.getHitRect(textRect); //fills textRect with coordinates of TextView relative to its parent (LinearLayout) 
            scrollView.requestChildRectangleOnScreen(linearLayout, textRect, false); //ScrollView will make sure, the given textRect is visible
        }
    }, delay);
}

Just nice isn`t?很好不是吗?

Kotlin: Try this, it works for me like charm ! Kotlin:试试这个,它对我很有用!

parentScrollView.post(object : Runnable {
        override fun run() {
            parentScrollView.smoothScrollTo(0, targetView.bottom)
        }

    })

After so much researching into this and trying many ways, the following worked WONDERS for me.在对此进行了大量研究并尝试了多种方法之后,以下内容对我来说是奇迹。

Considering your main parent is ScrollView and child view(s) are/is LinearLayout or RelativeLayout which might have further views in them, there can be 2 ways to go-考虑到您的主要父视图是 ScrollView,而子视图是 LinearLayout 或 RelativeLayout,它们可能有更多视图,可以有两种方法-

Case 1: You want to focus on top of childView.案例 1:您想专注于 childView 之上。 Use the following code-使用以下代码-

public void scrollToViewTop(ScrollView scrollView, View childView) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            scrollView.smoothScrollTo(0, childView.getTop());
        }
    }, delay);
}

Case 2: Focus on bottom of your childView.案例 2:专注于 childView 的底部。 Use the following code-使用以下代码-

public void scrollToViewBottom(ScrollView scrollView, View childView) {
    long delay = 100; //delay to let finish with possible modifications to ScrollView
    scrollView.postDelayed(new Runnable() {
        public void run() {
            scrollView.smoothScrollTo(0, childView.getBottom());
        }
    }, delay);
}

To use these functions, simply call them like this-要使用这些功能,只需像这样调用它们-

scrollToViewTop(scrollView, childView);
scrollToViewBottom(scrollView, childView);

Hope it saves your day.希望它能拯救你的一天。 Happy coding!快乐编码!

Here's a solution that works if the target view is not a direct child of your ScrollView:如果目标视图不是 ScrollView 的直接子视图,则这是一个有效的解决方案:

public int findYPositionInView (View rootView, View targetView)
{
  return findYPositionInView (rootView, targetView, 0);
}


// returns -1 if targetView not found
private int findYPositionInView (View rootView, View targetView, int yCumulative)
{
  if (rootView == targetView)
    return yCumulative;

  if (rootView instanceof ViewGroup)
  {
    ViewGroup parentView = (ViewGroup)rootView;
    for (int i = 0;  i < parentView.getChildCount ();  i++)
    {
      View child = parentView.getChildAt (i);
      int yChild = yCumulative + (int)child.getY ();

      int yNested = findYPositionInView (child, targetView, yChild);
      if (yNested != -1)
        return yNested;
    }
  }

  return -1; // not found
}

Use it like this:像这样使用它:

int yScroll = findYPositionInView (scrollView, targetView);
scrollView.scrollTo (0, yScroll);

Further, if you wish to set focus, do this:此外,如果您想设置焦点,请执行以下操作:

targetView.requestFocus ();

And, if you want the keyboard to show, do this:而且,如果您希望键盘显示,请执行以下操作:

if (targetView instanceof EditText)
{
  targetView.post (new Runnable ()
  {
    @Override public void run ()
    {
      InputMethodManager imm = (InputMethodManager)context.getSystemService (Context.INPUT_METHOD_SERVICE);
      imm.showSoftInput (targetView, InputMethodManager.SHOW_FORCED);
    }
  });
}

Use :用 :

final  ScrollView scrollView= (ScrollView) 
int speed=1000;
findViewById(R.id.main_scrollView);
final View view = findViewById(R.id.your_view); 
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this);   
                ObjectAnimator.ofInt(scrollView, "scrollY",  (int) view.getY()).setDuration(speed).start();
            }
        }); 

I got the desired result using requestChildFocus() as suggested by Swas_99's answer above .我使用 requestChildFocus() 得到了想要的结果,正如上面Swas_99 的回答所建议的那样。

However since it doesn't do smooth scrolling, I looked into the source code for requestChildFocus which calls scrollToChild which calls offsetDescendantRectToMyCoords .然而,由于它不能平滑滚动,我查看了requestChildFocus的源代码,它调用了scrollToChild ,它调用了offsetDescendantRectToMyCoords From there I got the idea to use the following C#/Xamarin code which gives a similar effect to using requestChildFocus() but with smooth scrolling:从那里我得到了使用以下 C#/Xamarin 代码的想法,该代码与使用 requestChildFocus() 具有类似的效果,但具有平滑滚动:

private static void ScrollToView(View ArgTarget) {
    var CurParent = ArgTarget.Parent;

    while (CurParent != null) {
      var ScrollArea = CurParent as ScrollView;

      if (ScrollArea != null) {
        var ViewRect = new Rect();
        ArgTarget.GetDrawingRect(ViewRect);

        ScrollArea.OffsetDescendantRectToMyCoords(ArgTarget, ViewRect);
        ScrollArea.SmoothScrollTo(ViewRect.Left, ViewRect.Top);

        break;
      }

      CurParent = CurParent.Parent;
   }
}

The basic idea is to find the target view's containing ScrollView.基本思想是找到包含 ScrollView 的目标视图。 Then get the target view's drawing rectangle, adjust its coordinates so they are understood by the ScrollView then ask the ScrollView to scroll to smooth-scroll to that position.然后获取目标视图的绘图矩形,调整其坐标以便 ScrollView 理解它们,然后要求 ScrollView 滚动以平滑滚动到该位置。 The end effect is that the view you want to scroll to always ends up being fully visible near the top of the screen (you can play around with ViewRect.Top if you want to center the view on the screen instead).最终效果是您想要滚动到的视图始终在屏幕顶部附近完全可见(如果您想将视图置于屏幕中心,则可以使用 ViewRect.Top)。

Here rootView is your parent view and childView is a view where you want to scroll your view这里 rootView 是您的父视图,而 childView 是您想​​要滚动视图的视图

public static int getRelativeTop(View rootView, View childView) {
    if(childView.getParent() == rootView) return childView.getTop();
    else return childView.getTop() + getRelativeTop(rootView, (View) childView.getParent());
}

You can use method smoothScrollTo to scroll to specific view in scroll view.您可以使用方法 smoothScrollTo 在滚动视图中滚动到特定视图。 - Example: - 例子:

my_scroll_View.smoothScrollTo(0, editText.getBottom());

Good luck!祝你好运!

I use this like a extension我把它当作扩展来使用

fun ScrollView.focusOnView(toView: View){

    Handler().post(Runnable {
        this.smoothScrollTo(0, toView.top)
    })

}

Usage:用法:

myScrollView.focusOnView(myView)

For Kotlin user.对于 Kotlin 用户。 If above code are not worked.如果上面的代码不起作用。 Try it.尝试一下。

val your_scrollview = findView...
val your_view_inside_SV = findView...
your_scrollview.post( { your_scrollview.scrollTo(0, your_view_inside_SV.getY().toInt()) })

For me, the best way to achieve this is to use the requestRectangleOnScreen() function.对我来说,实现这一点的最佳方法是使用requestRectangleOnScreen()函数。 It is available from View that you want to scroll.它可以从您想要滚动的View中获得。 You don't need access to the ScrollView and you don't need to calculate the childOffset.您不需要访问 ScrollView,也不需要计算 childOffset。 It does not change the focus and does not seems to be a hack in code.它不会改变焦点,并且似乎不是代码中的黑客。 (I would never guess that requestChildFocus() is used for scrolling) (我永远不会猜到requestChildFocus()用于滚动)

Also in kotlin you can add some small extension functions to make it's usage a way simpler:同样在 kotlin 中,您可以添加一些小的扩展函数,使其使用更简单:

  1. Add two extension functions for View:为View添加两个扩展函数:
fun View.requestOnScreen() = post {
    requestRectangleOnScreen(getDrawingRect())
}
    
fun View.getDrawingRect() = Rect().also(::getDrawingRect)
  1. Use that functions whenether you need:在您需要时使用该功能:

view.requestOnScreen()

And that's all.就这样。

From API 29 there is an available method on ScrollView scrollToDescendant(View child) that can replace other custom solutions.从 API 29 开始,在ScrollView scrollToDescendant(View child)上有一个可用的方法可以替换其他自定义解决方案。

Android docs: https://developer.android.com/reference/android/widget/ScrollView#scrollToDescendant(android.view.View) Android 文档: https : //developer.android.com/reference/android/widget/ScrollView#scrollToDescendant(android.view.View)

尝试在要滚动到的视图上调用view.requestFocus()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM