同步两个ScrollView

时间:2010-08-19 23:35:43

标签: android android-widget

情况: 我在TableRow的两个Horizo​​ntalScrollView中分别有两个ScrollView
目标: 当我触摸拖动其中一个ScrollView时,另一个ScrollView必须滚动尽可能多。例如,如果我在左侧ScrollView上有一个名称列表,并且右侧ScrollView中有相应的电话号码,则滚动一个ScrollView不应该破坏名称和电话号码之间的原始界限。

可以通过onTouchEvent实现吗?如果是这样,我应该如何实现它(在两个或一个ScrollView上)?

请帮我解决Android Gurus!

2 个答案:

答案 0 :(得分:16)

我有一个适合我的简单解决方案:

  • 将两个ScrollView子类化,并在滚动更改时覆盖其onScrollChanged事件以更新ScrollManager

    public interface ScrollNotifier {   
        public void setScrollListener(ScrollListener scrollListener);
    
        public ScrollListener getScrollListener();
    }
    
    public class SyncedScrollView extends ScrollView implements ScrollNotifier {
    
        //...
    
        private ScrollListener scrollListener = null;
    
        @Override
        protected void onScrollChanged(int l, int t, int oldl, int oldt) {
            super.onScrollChanged(l, t, oldl, oldt);
            if (scrollListener != null)
                scrollListener.onScrollChanged(this, l, t, oldl, oldt);
        }
        @Override
        public void setScrollListener(ScrollListener scrollListener) {
            this.scrollListener = scrollListener;
        }
        @Override
        public ScrollListener getScrollListener() {
            return scrollListener;
        }
    }
    
  • 创建一个ScrollManager类,用于协调多个参与者的滚动

    public interface ScrollListener {
        void onScrollChanged(View syncedScrollView, int l, int t, int oldl,
            int oldt);
    }
    
    public class ScrollManager implements ScrollListener {
        private static final int SCROLL_HORIZONTAL = 1;
        private static final int SCROLL_VERTICAL = 2;
    
        private ArrayList<ScrollNotifier> clients = new ArrayList<ScrollNotifier>(4);
    
        private volatile boolean isSyncing = false;
        private int scrollType = SCROLL_HORIZONTAL;
    
        public void addScrollClient(ScrollNotifier client) {
            clients.add(client);
            client.setScrollListener(this);
        }
    
        // TODO fix dependency on all views being of equal horizontal/ vertical
        // dimensions
        @Override
        public void onScrollChanged(View sender, int l, int t, int oldl, int oldt) {
            // avoid notifications while scroll bars are being synchronized
            if (isSyncing) {
                return;
            }
    
            isSyncing = true;
    
            // remember scroll type
            if (l != oldl) {
                scrollType = SCROLL_HORIZONTAL;
            } else if (t != oldt) {
                scrollType = SCROLL_VERTICAL;
            } else {
                // not sure why this should happen
                isSyncing = false;
                return;
            }
    
            // update clients
            for (ScrollNotifier client : clients) {
                View view = (View) client;
                // don't update sender
                if (view == sender) {
                    continue;
                }
    
                // scroll relevant views only
                // TODO Add support for horizontal ListViews - currently weird things happen when ListView is being scrolled horizontally
                if ((scrollType == SCROLL_HORIZONTAL && view instanceof HorizontalScrollView)
                        || (scrollType == SCROLL_VERTICAL && view instanceof ScrollView)
                        || (scrollType == SCROLL_VERTICAL && view instanceof ListView)) {
                    view.scrollTo(l, t);
                }
            }
    
            isSyncing = false;
        }
    }
    
  • 创建自定义ScrollView并在{/ 1>上设置ScrollManager通知

    private void setupScrolling() {
        ScrollNotifier view;
        ScrollManager scrollManager = new ScrollManager();
    
        // timeline horizontal scroller
        view = (ScrollNotifier) findViewById(R.id.epgtimeline_container);
        scrollManager.addScrollClient(view);
    
        // services vertical scroller
        view = (ScrollNotifier) findViewById(R.id.epgservices_container);
        scrollManager.addScrollClient(view);
    
        // content area scrollers
        view = (ScrollNotifier) findViewById(R.id.epgevents_container_inner);
        scrollManager.addScrollClient(view);
        view = (ScrollNotifier) findViewById(R.id.epgevents_container_outer);
        scrollManager.addScrollClient(view);
    }
    

答案 1 :(得分:2)

感谢andig这么棒的synching scrollview solution

但是在投掷时两个滚动视图之间存在一点延迟。

所以我在这里写扩展解决方案,以消除两个scrollview之间的延迟。

我刚刚使用过OverScroller类并在SyncedScrollView中手动处理了fling事件。

您必须使用以下代码替换SyncedScrollView。 使用andig's solution

中的其他类
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.OverScroller;
import android.widget.ScrollView;

/**
 * Created by mitul.varmora on 11/7/2016.
 * SyncedScrollView
 * https://stackoverflow.com/questions/3527119/sync-two-scrollview
 */
public class SyncedScrollView extends ScrollView implements ScrollNotifier {

    private ScrollListener scrollListener = null;

    private OverScroller scroller;
    private Runnable scrollerTaskRunnable;

    public SyncedScrollView(Context context) {
        super(context);
        init();
    }

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

    public SyncedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {

        scroller = new OverScroller(getContext());
        scrollerTaskRunnable = new Runnable() {
            @Override
            public void run() {
                scroller.computeScrollOffset();

                smoothScrollTo(0, scroller.getCurrY());

                if (!scroller.isFinished()) {
                    SyncedScrollView.this.post(this);
                } else {
                    //deceleration ends here, do your code
                    ViewCompat.postInvalidateOnAnimation(SyncedScrollView.this);
                }
            }
        };
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (scrollListener != null)
            scrollListener.onScrollChanged(this, l, t, oldl, oldt);
    }

    @Override
    public ScrollListener getScrollListener() {
        return scrollListener;
    }

    @Override
    public void setScrollListener(ScrollListener scrollListener) {
        this.scrollListener = scrollListener;
    }

    @Override
    public void fling(int velocityY) {

        scroller.forceFinished(true);
        scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, getChildAt(0).getHeight());
        post(scrollerTaskRunnable);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
//        return super.onTouchEvent(ev);
        boolean eventConsumed = super.onTouchEvent(ev);
        if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) {
            if (scroller.isFinished()) {
                //do your code
            }
        }
        return eventConsumed;
    }
}