I am trying to create a vertically and horizontally scrolling calendar, where when you scroll vertically it is by hours of the day, and when you scroll horizontally it is by the days of the month. This link contains an almost working gif of what I need:
If you notice near the later half of the gif, after scrolling vertically, the horizontal scrolling of the body and the day header has dsynced, but once you move the list back to the beginning or end, they sync up again.
Basically what I have here are 2 horizontally scrolling recycler views that scroll with each other as mentioned here:
Sync scrolling of multiple RecyclerViews
Actual implementation:
public class CalendarFragment extends BaseFragment {
@BindView(R.id.dayHeaderView)
RecyclerView dayHeaderView;
@BindView(R.id.dayBodyView)
RecyclerView dayBodyView;
private static final String TAG = CalendarFragment.class.getSimpleName();
private DayHeaderAdapter dayHeaderAdapter = new DayHeaderAdapter();
private DayBodyAdapter dayBodyAdapter = new DayBodyAdapter();
private final RecyclerView.OnScrollListener dayHeaderOSL = new SelfRemovingOnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
dayBodyView.scrollBy(dx, dy);
Log.e(TAG, "onScrolled: dayHeader x : " + dx + " y : " + dy );
}
};
private final RecyclerView.OnScrollListener dayBodyOSL = new SelfRemovingOnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
dayHeaderView.scrollBy(dx, dy);
Log.e(TAG, "onScrolled: function x : " + dx + " y : " + dy );
Log.e(TAG, "onScrolled: dayHeader x : " + dayHeaderView.getX() + " y : " + dayHeaderView.getY() );
Log.e(TAG, "onScrolled: dayBody x : " + dayBodyView.getX() + " y : " + dayBodyView.getY() );
}
};
public CalendarFragment() {}
public static CalendarFragment create(boolean showScheduler) {
return new CalendarFragment();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_calendar, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getComponent().inject(this);
ButterKnife.bind(this, view);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
llm.setOrientation(LinearLayoutManager.HORIZONTAL);
LinearLayoutManager llm2 = new LinearLayoutManager(getContext());
llm2.setOrientation(LinearLayoutManager.HORIZONTAL);
dayHeaderView.setLayoutManager(llm2);
dayHeaderView.setAdapter(dayHeaderAdapter);
dayHeaderAdapter.displayHeader();
dayHeaderView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
private int mLastX;
@Override
public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final
MotionEvent e) {
Log.d("debug", "Day Header: onInterceptTouchEvent");
final Boolean ret = rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (!ret) {
onTouchEvent(rv, e);
}
return Boolean.FALSE;
}
@Override
public void onTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) {
Log.d("debug", "Day Header: onTouchEvent");
final int action;
if ((action = e.getAction()) == MotionEvent.ACTION_DOWN && dayBodyView
.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
mLastX = rv.getScrollX();
dayBodyView.scrollBy(rv.getScrollX(), rv.getScrollY());
rv.addOnScrollListener(dayHeaderOSL);
}
else {
if (action == MotionEvent.ACTION_UP && rv.getScrollX() == mLastX) {
rv.removeOnScrollListener(dayHeaderOSL);
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(final boolean disallowIntercept) {
Log.d("debug", "Day Header: onRequestDisallowInterceptTouchEvent");
}
});
CalenderLayoutManager clm = new CalenderLayoutManager();
clm.setTotalColumnCount(10);
dayBodyView.setLayoutManager(llm);
dayBodyView.setAdapter(dayBodyAdapter);
dayBodyAdapter.displayBody();
dayBodyView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
private int mLastX;
@Override
public boolean onInterceptTouchEvent(@NonNull final RecyclerView rv, @NonNull final
MotionEvent e) {
Log.d("debug", "Day Body: onInterceptTouchEvent");
final Boolean ret = rv.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
if (!ret) {
onTouchEvent(rv, e);
}
return Boolean.FALSE;
}
@Override
public void onTouchEvent(@NonNull final RecyclerView rv, @NonNull final MotionEvent e) {
Log.d("debug", "Day Body: onTouchEvent");
final int action;
if ((action = e.getAction()) == MotionEvent.ACTION_DOWN && dayHeaderView
.getScrollState
() == RecyclerView.SCROLL_STATE_IDLE) {
mLastX = rv.getScrollX();
dayHeaderView.scrollBy(rv.getScrollX(), rv.getScrollY());
rv.addOnScrollListener(dayBodyOSL);
}
else {
if (action == MotionEvent.ACTION_UP && rv.getScrollX() == mLastX) {
rv.removeOnScrollListener(dayBodyOSL);
}
}
}
@Override
public void onRequestDisallowInterceptTouchEvent(final boolean disallowIntercept) {
Log.d("debug", "Day Body: onRequestDisallowInterceptTouchEvent");
}
});
}
public class SelfRemovingOnScrollListener extends RecyclerView.OnScrollListener {
@Override
public final void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
recyclerView.removeOnScrollListener(this);
}
}
}
}
The vertical scrolling is accomplished with a column row, and the body of the 2nd horizontal recycler view being contained inside of a scrollview. Shown here:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/silver"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="@+id/fillable_area_01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:splitMotionEvents="false">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<View
android:layout_width="30dp"
android:layout_height="40dp"
android:background="@color/black"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/dayHeaderView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:splitMotionEvents="false">
<LinearLayout
android:id="@+id/fillable_area_02"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:splitMotionEvents="false">
<edu.calendar.ui.HourView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="@+id/dayBodyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
</LinearLayout>
Does anyone have any ideas on why the dsync occurs after using the scrollview, and what I can do to fix it? Failing or in addition to that, is there a better method to implement this setup? The behavior of the headers where sometimes they are fixed, and sometimes they are scrollable had been a tough nut for me to wrap my head around, and any advice would be appreciated!
Thanks so much for your time.