Synchronise Scrollview Scroll Positions - Android

Synchronise ScrollView scroll positions - android

There is a method in ScrollView...

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

Unfortunately Google never thought that we would need to access it, which is why they made it protected and didn't add a "setOnScrollChangedListener" hook. So we will have to do that for ourselves.

First we need an interface.

package com.test;

public interface ScrollViewListener {

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

}

Then we need to override the ScrollView class, to provide the ScrollViewListener hook.

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

}

And we should specify this new ObservableScrollView class in the layout, instead of the existing ScrollView tags.

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

...

</com.test.ObservableScrollView>

Finally, we put it all together in the Layout class.

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

}

The scrollTo() code takes care of any loop conditions for us, so we don't need to worry about that. The only caveat is that this solution is not guaranteed to work in future versions of Android, because we are overriding a protected method.

How do I synchronize scrollview positions of dynamic horizontal scroll views inside a container?

You can achieve the synchronized scrolling with LiveData from Android Architecture Components. Declare an interface

interface LobbyReservationHSVListener{
void updateScrollPosition(int scrollX);
}

Declare a MutableLiveData variable and implement the LobbyReservationHSVListener in your activity like below:

public class HorizontalActivity extends AppCompatActivity implements
LobbyReservationHSVListener {

MutableLiveData<Integer> hsvPosition = new MutableLiveData<Integer>();

@Override
public void updateScrollPosition(int scrollX) {
hsvPosition.setValue(scrollX);
}
}

In onCreate in your activity start observing the MutableLiveData you declared. Whenever its value changes, loop through all children views of container and update their scroll positions.

hsvPosition.observe(this,new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
for(int i=0; i<container.getChildCount();i++){
HorizontalScrollView hsv = container.getChildAt(i).findViewById(R.id.horizontal_timebar_view);
hsv.smoothScrollTo(integer,0);
}
}
});

In your LobbyReservationRowView create a function to set the LobbyReservationHSVListener object and call updateScrollPosition whenever scroll changes.

public class LobbyReservationRowView extends FrameLayout implements
OnClickListener, OnItemClickListener, HorizontalScrollView.OnScrollChangeListener {

private LobbyReservationHSVListener hsvUpdateListener;

@BindView(R.id.horizontal_timebar_view)
HorizontalScrollView mHorizontalTimeBarView;

public void setHSVUpdateListener(LobbyReservationHSVListener hsvUpdateListener){
this.hsvUpdateListener = hsvUpdateListener;
}

@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {

hsvUpdateListener.updateScrollPosition(scrollX);
}

}

Don't forget to call setHSVUpdateListener(this) on your LobbyReservationRowView object whenever adding it to your container

You should get something like this:

Sample Image

Synchronizing two Horizontal Scroll Views in Android

The error points to a null pointer in a constructor in your ObservableScrollView. Could you please post the constructor, as well as line 12 on its own?

One possible issue I am seeing with the way your XML and custom scroll view interact is that your custom scroll view is an inner class. See this page for how to declare an inner class custom component in XML, or move it to an external class. I usually put it in an external class, but if you wanted to keep it as an inner class it would be something like

<view
class="com.glen.apps.TeacherAidePro$ObservableScrollView"
.../>

I don't think this explains your null pointer exception though, so please post your ObservableScrollView class.

EDIT:

If you insist on doing everything in Java, here is a working example:

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

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

LinearLayout parent = new LinearLayout(this);
parent.setOrientation(LinearLayout.HORIZONTAL);
parent.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
parent.setWeightSum(2.0f);

scrollView1 = new ObservableScrollView(this);
scrollView1.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, 1.0f));
scrollView2 = new ObservableScrollView(this);
scrollView2.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT, 1.0f));

scrollView1.setScrollViewListener(new ScrollViewListener() {
public void onScrollChanged(ObservableScrollView scrollView, int x,
int y, int oldx, int oldy) {
scrollView2.scrollTo(x, y);
}
});
scrollView2.setScrollViewListener(new ScrollViewListener() {
public void onScrollChanged(ObservableScrollView scrollView, int x,
int y, int oldx, int oldy) {
scrollView1.scrollTo(x, y);
}
});

TextView tv1 = new TextView(this);
tv1.setText("TEXT1TEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXT");
tv1.setTextSize(36.0f);
scrollView1.addView(tv1);

TextView tv2 = new TextView(this);
tv2.setText("TEXT2TEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXTTEXT");
tv2.setTextSize(36.0f);
scrollView2.addView(tv2);

parent.addView(scrollView1);
parent.addView(scrollView2);
parent.invalidate();

setContentView(parent);
}

Basically, this creates two scroll views side by side, with weights of 1.0 each, and puts them in a LinearLayout with total weight on the layout of 2.0, so they each get half the width.

However, I highly recommend getting used to XML as it is much, much easier (in my opinion) to create layouts. It is also easier to spot mistakes, and the nested form of XML makes it easier to read. Anyway, hope this clears things up.

Android: Synchronized scrolling of two different views

First of all, I think you should be aware of this: ScrollView Inside ScrollView

In short: using scrollviews inside scrollviews is a bad thing that breaks many optimizations.

Now, onto your question.

I've had a similar need to what you described. I ended up implementing a custom view and its onDraw method. This was in part because I was drawing something not trivial and you may not have to do it.

Anyway, I believe that your best option is:

  1. Implement a custom view that extends relative layout
  2. create the layout of this view with the top, left and "main" views that will be the scrollable components
  3. add a OnGestureListener to this view and pass touch events in your activity into the custom view
  4. when your gesture listener detects a fling or a scroll, invoke scrollBy in each of the scrolling views. When you do this, if you want the top view to scroll horizontally only, pass 0 as the vertical scroll distance.
  5. In order to implement smooth fling movements, you need to create a scroller (in your custom view). When the gesture listener detects a fling event, set the scroller up. Then, override your custom view's computeScroll() method and update the scroll in each of child views. Check this example to know how to implement it. I apologize, I will try to post a better example when possible. Check my code below... it's simpler :)

Update: sample code

@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
if (!scrolledLastFrame) {
lastX = scroller.getStartX();
lastY = scroller.getStartY();
}

int dx = scroller.getCurrX() - lastX;
int dy = scroller.getCurrY() - lastY;

lastX = scroller.getCurrX();
lastY = scroller.getCurrY();

doScroll(dx, dy);
scrolledLastFrame = true;
} else {
scrolledLastFrame = false;
}

}

how to synchronize scroll views simultaneously

I guess you can do something like

    if(scrollView == scrollView1) {
scrollView2.scrollTo(x, y);
scrollView3.scrollTo(x, y);
scrollView4.scrollTo(x, y);
} else if(scrollView == scrollView2) {
scrollView1.scrollTo(x, y);
scrollView3.scrollTo(x, y);
scrollView4.scrollTo(x, y);
} else if(scrollView == scrollView3) {
scrollView1.scrollTo(x, y);
scrollView2.scrollTo(x, y);
scrollView4.scrollTo(x, y);
} else if(scrollView == scrollView4) {
scrollView1.scrollTo(x, y);
scrollView2.scrollTo(x, y);
scrollView3.scrollTo(x, y);
}

synchronize two horizontal scroll view android

What you could do is on the onTouch of the first Horizontal Scroll view, record the X position that it started with for an action of Down. Then when you have an action of Move, record the change in the X position. Then you can call the second horizontal scroll view's scrollBy (deltaX, 0). On an action of Up or Cancel, make sure to reset your state variables.

I've done this with a List View scrolling a vertical scroll, just using Y positions instead of X. Here is my code to accomplish this. The concurrentScroller is my vertical view.

if(concurrentScroller != null) {
int deltaY = (int) (startTouchConcurrentY - ev.getY());
startTouchConcurrentY = ev.getY();
concurrentScroller.scrollBy(0, deltaY);
}


Related Topics



Leave a reply



Submit