Skip to content

Commit

Permalink
fix(android): implement missing scroll events for ListView and TableView
Browse files Browse the repository at this point in the history
  • Loading branch information
garymathews authored and sgtcoolguy committed Jan 19, 2021
1 parent 69ac871 commit 2cde1bc
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import ti.modules.titanium.ui.widget.TiSwipeRefreshLayout;
import ti.modules.titanium.ui.widget.searchbar.TiUISearchBar.OnSearchChangeListener;
Expand Down Expand Up @@ -66,6 +67,49 @@ public TiListView(ListViewProxy proxy)
this.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
this.recyclerView.setFocusableInTouchMode(false);

// Add listener to fire scroll events.
this.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState)
{
super.onScrollStateChanged(recyclerView, newState);

if (newState == RecyclerView.SCROLL_STATE_IDLE) {
proxy.fireSyncEvent(TiC.EVENT_SCROLLEND, generateScrollPayload());
}
}

@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy)
{
super.onScrolled(recyclerView, dx, dy);

proxy.fireSyncEvent(TiC.EVENT_SCROLLSTART, generateScrollPayload());
}
});
this.recyclerView.setOnFlingListener(new RecyclerView.OnFlingListener()
{
@Override
public boolean onFling(int velocityX, int velocityY)
{
final KrollDict payload = new KrollDict();

// Determine scroll direction.
if (velocityY > 0) {
payload.put(TiC.PROPERTY_DIRECTION, "down");
} else if (velocityY < 0) {
payload.put(TiC.PROPERTY_DIRECTION, "up");
}

// Set scroll velocity.
payload.put(TiC.EVENT_PROPERTY_VELOCITY, velocityY);

proxy.fireSyncEvent(TiC.EVENT_SCROLLING, payload);
return true;
}
});

// Disable list animations.
this.recyclerView.setItemAnimator(null);

Expand Down Expand Up @@ -192,6 +236,41 @@ public void filterBy(String query)
this.isFiltered = true;
}

/**
* Generate payload for `scrollstart` and `scrollend` events.
*
* @return KrollDict
*/
public KrollDict generateScrollPayload()
{
final KrollDict payload = new KrollDict();
final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

// Obtain first visible list item view.
final View firstVisibleView =
layoutManager.findViewByPosition(layoutManager.findFirstVisibleItemPosition());
final ListViewHolder firstVisibleHolder =
(ListViewHolder) recyclerView.getChildViewHolder(firstVisibleView);

// Obtain first visible list item proxy.
final ListItemProxy firstVisibleProxy = (ListItemProxy) firstVisibleHolder.getProxy();
payload.put(TiC.PROPERTY_FIRST_VISIBLE_ITEM, firstVisibleProxy);

// Obtain first visible list item index in section.
final int firstVisibleItemIndex = firstVisibleProxy.getIndexInSection();
payload.put(TiC.PROPERTY_FIRST_VISIBLE_ITEM_INDEX, firstVisibleItemIndex);

// Obtain first visible section proxy.
final ListSectionProxy firstVisibleSection = (ListSectionProxy) firstVisibleProxy.getParent();
payload.put(TiC.PROPERTY_FIRST_VISIBLE_SECTION, firstVisibleSection);

// Obtain first visible section index.
final int firstVisibleSectionIndex = proxy.getIndexOfSection(firstVisibleSection);
payload.put(TiC.PROPERTY_FIRST_VISIBLE_SECTION_INDEX, firstVisibleSectionIndex);

return payload;
}

/**
* Get list adapter.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public class TiNestedRecyclerView extends RecyclerView implements NestedScrollin

private boolean isScrollEnabled = true;

private float lastTouchX;
private float lastTouchY;

public TiNestedRecyclerView(@NonNull Context context)
{
super(new ContextThemeWrapper(context, R.style.RecyclerView));
Expand All @@ -46,6 +49,9 @@ public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEv
switch (action) {

case MotionEvent.ACTION_DOWN: {
lastTouchX = e.getX();
lastTouchY = e.getY();

if (scrollState == RecyclerView.SCROLL_STATE_SETTLING) {
rv.stopScroll();
}
Expand Down Expand Up @@ -86,6 +92,16 @@ public void setScrollEnabled(boolean enabled)
this.isScrollEnabled = enabled;
}

public float getLastTouchX()
{
return this.lastTouchX;
}

public float getLastTouchY()
{
return this.lastTouchY;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.titanium.TiApplication;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.TiDimension;
import org.appcelerator.titanium.util.TiUIHelper;

import android.app.Activity;
Expand All @@ -33,6 +34,7 @@
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import ti.modules.titanium.ui.TableViewProxy;
import ti.modules.titanium.ui.TableViewRowProxy;
Expand All @@ -57,6 +59,8 @@ public class TiTableView extends TiSwipeRefreshLayout implements OnSearchChangeL
private final SelectionTracker tracker;

private boolean isFiltered = false;
private int scrollOffsetX = 0;
private int scrollOffsetY = 0;

public TiTableView(TableViewProxy proxy)
{
Expand All @@ -70,6 +74,45 @@ public TiTableView(TableViewProxy proxy)
this.recyclerView.setBackgroundColor(Color.TRANSPARENT);
this.recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

// Add listener to fire scroll events.
this.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState)
{
super.onScrollStateChanged(recyclerView, newState);

if (newState == RecyclerView.SCROLL_STATE_IDLE) {
final KrollDict payload = generateScrollPayload();
final TiNestedRecyclerView nestedRecyclerView = getRecyclerView();

// Obtain last touch position for `scrollend` event.
final TiDimension xDimension =
new TiDimension(nestedRecyclerView.getLastTouchX(), TiDimension.TYPE_WIDTH);
final TiDimension yDimension =
new TiDimension(nestedRecyclerView.getLastTouchY(), TiDimension.TYPE_HEIGHT);
payload.put(TiC.EVENT_PROPERTY_X, xDimension.getAsDefault(nestedRecyclerView));
payload.put(TiC.EVENT_PROPERTY_Y, yDimension.getAsDefault(nestedRecyclerView));

proxy.fireSyncEvent(TiC.EVENT_SCROLLEND, payload);
}
}

@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy)
{
super.onScrolled(recyclerView, dx, dy);

// Update scroll offsets.
scrollOffsetX += dx;
scrollOffsetY += dy;

final KrollDict payload = generateScrollPayload();

proxy.fireSyncEvent(TiC.EVENT_SCROLL, payload);
}
});

// Disable table animations.
this.recyclerView.setItemAnimator(null);

Expand Down Expand Up @@ -193,6 +236,59 @@ public void filterBy(String query)
this.isFiltered = true;
}

/**
* Generate payload for `scroll` and `scrollend` events.
*
* @return KrollDict
*/
public KrollDict generateScrollPayload()
{
final KrollDict payload = new KrollDict();
final LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();

// Obtain index for first visible row.
final View firstVisibleView =
layoutManager.findViewByPosition(layoutManager.findFirstVisibleItemPosition());
final TableViewHolder firstVisibleHolder =
(TableViewHolder) recyclerView.getChildViewHolder(firstVisibleView);
final TableViewRowProxy firstVisibleProxy = (TableViewRowProxy) firstVisibleHolder.getProxy();
final int firstVisibleIndex = firstVisibleProxy.getIndexInSection();
payload.put(TiC.PROPERTY_FIRST_VISIBLE_ITEM, firstVisibleIndex);

// Obtain scroll offset for content.
final KrollDict contentOffset = new KrollDict();
final TiDimension scrollOffsetXDimension = new TiDimension(scrollOffsetX, TiDimension.TYPE_WIDTH);
final TiDimension scrollOffsetYDimension = new TiDimension(scrollOffsetY, TiDimension.TYPE_HEIGHT);
contentOffset.put(TiC.EVENT_PROPERTY_X, scrollOffsetXDimension.getAsDefault(recyclerView));
contentOffset.put(TiC.EVENT_PROPERTY_Y, scrollOffsetYDimension.getAsDefault(recyclerView));
payload.put(TiC.PROPERTY_CONTENT_OFFSET, contentOffset);

// Approximate content size.
// NOTE: Due to recycling of views, we cannot calculate the true
// content size without loading all rows. The best we can do is an
// approximation based on first visible row.
final KrollDict contentSize = new KrollDict();
final TiDimension contentWidthDimension =
new TiDimension(firstVisibleView.getMeasuredWidth(), TiDimension.TYPE_WIDTH);
final TiDimension contentHeightDimension =
new TiDimension(firstVisibleView.getMeasuredHeight() * rows.size(), TiDimension.TYPE_HEIGHT);
contentSize.put(TiC.PROPERTY_WIDTH, contentWidthDimension.getAsDefault(recyclerView));
contentSize.put(TiC.PROPERTY_HEIGHT, contentHeightDimension.getAsDefault(recyclerView));
payload.put(TiC.PROPERTY_CONTENT_SIZE, contentSize);

// Obtain view size.
final KrollDict size = new KrollDict();
final TiDimension widthDimension =
new TiDimension(recyclerView.getMeasuredWidth(), TiDimension.TYPE_WIDTH);
final TiDimension heightDimension =
new TiDimension(recyclerView.getMeasuredHeight(), TiDimension.TYPE_HEIGHT);
size.put(TiC.PROPERTY_WIDTH, widthDimension.getAsDefault(recyclerView));
size.put(TiC.PROPERTY_HEIGHT, heightDimension.getAsDefault(recyclerView));
payload.put(TiC.PROPERTY_SIZE, size);

return payload;
}

/**
* Get table adapter.
*
Expand Down
20 changes: 20 additions & 0 deletions android/titanium/src/java/org/appcelerator/titanium/TiC.java
Original file line number Diff line number Diff line change
Expand Up @@ -1742,6 +1742,26 @@ public class TiC
*/
public static final String PROPERTY_FIRSTNAME = "firstName";

/**
* @module.api
*/
public static final String PROPERTY_FIRST_VISIBLE_ITEM = "firstVisibleItem";

/**
* @module.api
*/
public static final String PROPERTY_FIRST_VISIBLE_ITEM_INDEX = "firstVisibleItemIndex";

/**
* @module.api
*/
public static final String PROPERTY_FIRST_VISIBLE_SECTION = "firstVisibleSection";

/**
* @module.api
*/
public static final String PROPERTY_FIRST_VISIBLE_SECTION_INDEX = "firstVisibleSectionIndex";

/**
* @module.api
*/
Expand Down

0 comments on commit 2cde1bc

Please sign in to comment.