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

我已经在 scrollview 中添加了一个 scrollview 和子子元素。在某些时候,我需要滚动到一个特定的视图。

<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>

上面的布局是动态创建的。所以我不能把 xml 文件放在这里。布局创建是完全动态的。甚至线性布局中的子视图的数量也可能有所不同。

所以当我点击按钮时,我需要滚动到一个特定的视图,这里说,当我点击按钮时,我需要滚动到线性布局4。 我试过 scrollTo 方法,但它滚动到 scrollview 的顶部。

请提供一些建议。

65717 次浏览

既然您可以知道需要滚动到的子 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());

更多关于 滚动滑动卷轴的信息

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

这应该会奏效:

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

Public void RequestChildFocus (View child,View focus)

Child -此 ViewParent 的需要焦点的子级。此视图将包含焦点视图。真正有焦点的不一定是观点。

焦点 -实际上有焦点的孩子的后代的观点

试试这个:

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()不能在 API 级别11以下工作,对于 API 级别11以下的,可以用 insideView.getTop()代替 insideView.getY()

如果我们需要滚动到的孩子不是一个直接的孩子,那么上述解决方案不起作用

我已经在我的项目中使用了下面的解决方案,分享它可能对其他人有帮助

/**
* 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);
}

我认为我已经找到了更优雅和更容易出错的解决方案

ScrollView.requestChildRectangleOnScreen

没有数学参与,并与其他提出的解决方案相反,它将处理正确的方式向上和向下滚动。

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
}

这是一个好主意,包装它到 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);
}

只是不错,不是吗?

如果目标视图不是 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
}

像这样使用它:

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

此外,如果你想集中精力,可以这样做:

targetView.requestFocus ();

如果你想让键盘显示出来,这样做:

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);
}
});
}

用途:

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();
}
});

正如 Swas _ 99的 上面的回答所建议的那样,我使用 requestChildFocus ()得到了期望的结果。

但是由于它不能平滑地滚动,我查看了调用 ScrollToChild请求儿童焦点的源代码,ScrollToChild调用 OffsetDescendantRectToMyCoords。从那里我得到了使用下面的 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;
}
}

基本思想是找到目标视图中包含的 ScrollView。然后得到目标视图的绘制矩形,调整它的坐标,使它们被 ScrollView 理解,然后要求 ScrollView 滚动以平滑滚动到该位置。最终的效果是,您想要滚动到的视图总是在屏幕顶部附近完全可见(您可以使用 ViewRect 进行操作)。如果你想把视图放在屏幕的中央,请放在顶部)。

在这里,rootView 是您的父视图,child View 是您想要滚动视图的视图

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

您可以使用 SmoothScrollTo 方法滚动到滚动视图中的特定视图。 - 例子:

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

祝你好运!

我把它当做分机

fun ScrollView.focusOnView(toView: View){


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


}

用法:

myScrollView.focusOnView(myView)

给 Kotlin 用户的。 如果上面的代码不起作用。 试试看。

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

科特林: 试试这个,它对我的作用就像魅力一样!

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


})

对我来说,实现这一点的最佳方法是使用 requestRectangleOnScreen()函数。 您想要滚动的 View可以提供。 您不需要访问 ScrollView,也不需要计算子偏移量。 它没有改变焦点,看起来也不像是代码中的黑客行为。(我永远不会猜到 requestChildFocus()是用来滚动的)

同样在 kotlin 中,你可以添加一些小的扩展函数来简化它的使用:

  1. 为 View 添加两个扩展函数:
fun View.requestOnScreen() = post {
requestRectangleOnScreen(getDrawingRect())
}
    

fun View.getDrawingRect() = Rect().also(::getDrawingRect)
  1. 如果需要,可以使用该函数:

view.requestOnScreen()

仅此而已。

经过这么多的研究,并尝试了许多方法,以下工作的奇迹为我。

考虑到您的主要父级是 ScrollView 和子视图是/是 LinearLayout 或 RelativeLayout,其中可能有更多的视图,可以有两种方法去-

案例1: 您希望将焦点集中在儿童视图的顶部

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);
}

案例2: 关注于你的儿童视图的底部。使用以下代码-

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);
}

要使用这些函数,只需像这样调用它们-

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

希望能帮到你,编程愉快!

从 API 29开始,在 ScrollView上有一个可用的方法 可以替换其他自定义解决方案的 scrollToDescendant(View child)

Android 文档: Https://developer.android.com/reference/android/widget/scrollview#scrolltodescendant (android.View. View)