3

I wrote a small app displaying 2 scrollViews next to each other. I need the scroll position of the 2 scrollViews to be synchronised. For that, I extended ScrollView and I overrode onScrollChanged to be notified when a scroll occurs, and then to sync the 2 scrollViews.

My two scroll views display a bunch of blue Views. The left scrollView has a red background, and the right has a green background.

Here is what happens with a scroll on the left scrollView:
scroll on left scrollView
=> The synchronisation is ok

And here is what happens with a scroll on the right scrollView:
scroll on right scrollView
=> The synchronisation is not good, there's a gap

(both screenshots were taken during the scrollviews' fling)

How to have a good synchronisation in both case?

The code of my Activity, my scrollView and my scrollView container is here.

MartinMoizard
  • 6,600
  • 12
  • 45
  • 75

2 Answers2

1

It seems like you hit a system limitation here - the system draws the views in order they are declared in your XML.

Therefore, when you fling the first declared ScrollView, the second one gets updated but the first one is not updated again. When you fling the second one, however, the first one gets updated, then the second one gets updated, the change is reflected to the first one and it gets updated again.

I'm not sure that the above description is 100% accurate, but it is something along these lines.

I created a test case to check my hypothesis substituting the following for main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/main_layout">


    <View android:id="@+id/separator_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_centerHorizontal="true"
        android:visibility="invisible"/>


    <com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_alignParentEnd="true"
        android:layout_toEndOf="@id/separator_view"
        android:background="@android:color/holo_green_light"
        android:id="@+id/left_scrollview">
        <com.example.www.syncscrollviewtesting_stackoverflow.Container
            android:layout_width="match_parent"
            android:layout_height="100000dp"
            android:minHeight="100000dp"
            android:id="@+id/left_container"/>

    </com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView>


    <com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:layout_toStartOf="@id/separator_view"
        android:background="@android:color/holo_red_dark"
        android:id="@+id/right_scrollview">
        <com.example.www.syncscrollviewtesting_stackoverflow.Container
            android:layout_width="match_parent"
            android:layout_height="100000dp"
            android:minHeight="100000dp"
            android:id="@+id/right_container"/>
    </com.example.www.syncscrollviewtesting_stackoverflow.ObservableScrollView>

</RelativeLayout>

The above XML allows you to position both ScrollViews on either sides of the separator_view. I found out that no matter how you position them, a fling of the ScrollView having red background (declared second) always causes the "lag", while a fling of the ScrollView having green background works fine.

I also tried to prevent unnecessary updates of the ScrollViews by adding this to their code:

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

   public void setDisabled(boolean isDisabled) {
      mIsDisabled = isDisabled;
   }

   @Override
   public boolean onTouchEvent(MotionEvent ev) {
      if (mIsDisabled)
         return false; // Ignore touch event when disabled
      else
         return super.onTouchEvent(ev);
   }

... and this to Activity's:

   @Override
   public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
      if (scrollView == mRightScrollView) {
         mLeftScrollView.setDisabled(true);
         mLeftScrollView.setScrollY(y);
         mLeftScrollView.setDisabled(false);
      } else {
         mRightScrollView.setDisabled(true);
         mRightScrollView.setScrollY(y);
         mRightScrollView.setDisabled(false);
      }

   }

but it didn't help either...

So, I guess, you better find another approach which does not involve redrawing a whole lot of Views, or just accept the "lag".

Solution: This solution was provided by the OP himself, based on my analysis of the situation: touch events can be forwarded from the right ScrollView (declared second in XML) to the left ScrollView. This way, given that flings on the left ScrollView do not cause lags, all touch events are treated as being initiated by the first declared ScrollView and the lag is avoided.

Vasiliy
  • 16,221
  • 11
  • 71
  • 127
  • The child position is indeed linked to the scrollView having the sync issue. As a workaround, when a touch occurs on the right scrollView, I forward it to the left scrollView. It's a hack but that's the only solution I found to keep the 2 scrollViews all the time synced. – MartinMoizard Jun 10 '15 at 07:55
  • @MartinMoizard, I didn't even think about this simple solution. It is ingenious in its simplicity! Thanks for sharing back - I'll add this to the answer if you don't mind. – Vasiliy Jun 10 '15 at 18:41
0

Apologies for my previous answer hadn't properly studied your question.

As I've said, I don't think you can sync 2 ScrollViews properly without getting any latency at all. However you can still use a single view.

This gets a little trickier because you also want to split your split into equal parts with weight attribute.

The solution can be found here. Building up on that I've come up with the following code:

<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <RelativeLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <View
            android:id="@+id/spacer"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_centerHorizontal="true"/>
        <LinearLayout
            android:id="@+id/left"
            android:layout_alignRight="@id/spacer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            ...
        </LinearLayout>
        <LinearLayout
            android:id="@+id/right"
            android:layout_alignLeft="@id/spacer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            ...
        </LinearLayout>
    </RelativeLayout>
</ScrollView>

Then when you want to toggle the left and right view overlapping call this:

leftParams.addRule(RelativeLayout.ALIGN_RIGHT, 0);
rightParams.addRule(RelativeLayout.ALIGN_LEFT, 0);

and

leftParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.spacer);
rightParams.addRule(RelativeLayout.ALIGN_LEFT, R.id.spacer);

after setting the params you need to redraw the view with:

left.requestLayout();
right.requestLayout();

A working example can be found on this GitHub page.

Community
  • 1
  • 1
Simas
  • 43,548
  • 10
  • 88
  • 116