同步ScrollView滚动位置 - Android

97

我在安卓布局中有2个ScrollView,如何同步它们的滚动位置?

4个回答

292

在ScrollView中有一个方法可以检查该视图是否已经滚动到底部,方法名为isAtBottom()。该方法返回一个布尔值,表示当前内容是否滚动到了底部。

protected void onScrollChanged(int x, int y, int oldx, int oldy)

很不幸,谷歌从未想到我们需要访问它,这就是为什么他们将其保护起来并且没有添加 "setOnScrollChangedListener" 钩子的原因。所以我们必须自己实现。

首先,我们需要一个接口。

package com.test;

public interface ScrollViewListener {

    void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);

}

我们需要重写 ScrollView 类,提供 ScrollViewListener 钩子。

package com.test;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

public class ObservableScrollView extends ScrollView {

    private ScrollViewListener scrollViewListener = null;

    public ObservableScrollView(Context context) {
        super(context);
    }

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ObservableScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setScrollViewListener(ScrollViewListener scrollViewListener) {
        this.scrollViewListener = scrollViewListener;
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);
        if(scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }
    }

}

我们应该在布局中指定这个新的ObservableScrollView类,而不是使用现有的ScrollView标签。

<com.test.ObservableScrollView
    android:id="@+id/scrollview1"
    ... >

    ...

</com.test.ObservableScrollView>

最后,我们将所有东西组合在 Layout 类中。

package com.test;

import android.app.Activity;
import android.os.Bundle;

public class Q3948934 extends Activity implements ScrollViewListener {

    private ObservableScrollView scrollView1 = null;
    private ObservableScrollView scrollView2 = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.q3948934);

        scrollView1 = (ObservableScrollView) findViewById(R.id.scrollview1);
        scrollView1.setScrollViewListener(this);
        scrollView2 = (ObservableScrollView) findViewById(R.id.scrollview2);
        scrollView2.setScrollViewListener(this);
    }

    public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
        if(scrollView == scrollView1) {
            scrollView2.scrollTo(x, y);
        } else if(scrollView == scrollView2) {
            scrollView1.scrollTo(x, y);
        }
    }

}

scrollTo()方法已经为我们处理了任何循环条件,因此我们不需要担心。唯一需要注意的是,这种解决方案不能保证在将来的Android版本中有效,因为我们正在覆盖受保护的方法。


嗨,安迪,感谢你的代码。它运行得很好。我想知道一件事情[如果你或任何人有答案]——当我快速滑动我的scrollView时,onscrollchanged方法没有在scrollView移动时注册所有的X和Y坐标。它只给我起始位置和最后位置。比如说,我从Y=10开始滑动,离开时在Y=30,然后快速滑动速度将其带到Y=50,然后停止。所以onscrollchanged只注册了——也许是10、11、12...30,然后是49、50。如何使它注册所有中间位置并获得从10到50的所有坐标? - alchemist
@Andy 我认为 protected 方法是用来重写的.. 否则就使用包私有或私有。 - dcow
@Andy,嗨,我已经实现了这个功能。它可以工作,但是出现了一个问题。当我在屏幕上滚动数据并按下此屏幕中的第二个按钮时,我永远不会到达第一个屏幕的最后位置。也就是说,我必须先按下按钮,然后才能得到解决方案。你有什么想法吗? - Google
@Innovator-z 你可以使用上面的代码自行提供ScrollViewListener类。 - Andy
@dcow 你说得对,但是我是在2010年10月发布了这个解决方案的,当时Google每六个月就会对Android API进行大幅更改。我应该澄清一下,我不是在谈论一般的受保护的方法。 - Andy
显示剩余5条评论

12

对Andy的解决方案进行改进: 在他的代码中,他使用了scrollTo方法,问题在于,如果您在一个方向上扔一个scrollview,然后在另一个方向上扔另一个scrollview,您会注意到第一个scrollview不会停止先前的扔动作。

这是因为scrollView使用computeScroll()执行它的flinging手势,并与scrollTo发生冲突。

为了防止这种情况,只需按照以下方式编写onScrollChanged即可:

    public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
    if(interceptScroll){
        interceptScroll=false;
        if(scrollView == scrollView1) {
            scrollView2.onOverScrolled(x,y,true,true);
        } else if(scrollView == scrollView2) {
            scrollView1.onOverScrolled(x,y,true,true);
        }
        interceptScroll=true;
    }
}

有一个名为interceptScroll的静态布尔变量,其初始值为true。(这有助于避免在滚动更改时出现无限循环)

我发现onOverScrolled是唯一可用于阻止scrollView抛掷的函数(但可能还有其他我错过的函数!)

为了访问此受保护的函数,您必须将此添加到ObservableScrollViewer中。

public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
    super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
}

2
孩子,你真是个好捕手! - FranciscoBouza

5
为什么不在你的活动中实现 OnTouchListener。然后重写 onTouch 方法,获取第一个 ScrollView 的滚动位置 ScrollViewOne.getScrollY() 并更新 ScrollViewTwo.scrollTo(0, ScrollViewOne.getScrollY());
只是另一个想法... :)

7
这可以实现,但是当你触摸并松开滚动视图时,它会继续滚动一段时间,这一点没有被捕捉到。 - richy
在Java中,我如何引用/获取scrollView ScrollViewOne的引用? - Bunny Rabbit
还有其他滚动方式(例如onTrackballEvent()) - surfealokesea
这是个很好的想法,但与其获取和设置滚动y位置,更容易的方法是将触摸事件分派到第二个滚动视图。这也将保持第二个滚动视图中的“投掷”操作。MotionEvent newEvent = MotionEvent.obtain(event); documentsScrollView.dispatchTouchEvent(newEvent); - Eduard Mossinkoff

3
在Android support-v4包中,Android提供了一个名为NestedScrollView的新类。我们可以在布局xml中使用标签替换标签,并在Java中实现它的NestedScrollView.OnScrollChangeListener来处理滚动事件。这样做可以让事情变得更加简单。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接