diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java index 24c9701e1..a705211a4 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java @@ -13,6 +13,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; +import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AlertDialog; @@ -46,6 +47,7 @@ import org.fdroid.fdroid.installer.InstallerService; import org.fdroid.fdroid.net.Downloader; import org.fdroid.fdroid.net.DownloaderService; import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter; +import org.fdroid.fdroid.views.OverscrollLinearLayoutManager; import org.fdroid.fdroid.views.ShareChooserDialog; import org.fdroid.fdroid.views.apps.FeatureImage; @@ -59,6 +61,8 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog private FDroidApp fdroidApp; private App app; + private CoordinatorLayout coordinatorLayout; + private AppBarLayout appBarLayout; private RecyclerView recyclerView; private AppDetailsRecyclerViewAdapter adapter; private LocalBroadcastManager localBroadcastManager; @@ -91,10 +95,42 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog localBroadcastManager = LocalBroadcastManager.getInstance(this); + coordinatorLayout = (CoordinatorLayout) findViewById(R.id.rootCoordinator); + appBarLayout = (AppBarLayout) coordinatorLayout.findViewById(R.id.app_bar); recyclerView = (RecyclerView) findViewById(R.id.rvDetails); adapter = new AppDetailsRecyclerViewAdapter(this, app, this); - LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); + OverscrollLinearLayoutManager lm = new OverscrollLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); lm.setStackFromEnd(false); + + /** The recyclerView/AppBarLayout combo has a bug that prevents a "fling" from the bottom + * to continue all the way to the top by expanding the AppBarLayout. It will instead stop + * with the app bar in a collapsed state. See here: https://code.google.com/p/android/issues/detail?id=177729 + * Not sure this is the exact issue, but it is true that while in a fling the RecyclerView will + * consume the scroll events quietly, without calling the nested scrolling mechanism. + * We fix this behavior by using an OverscrollLinearLayoutManager that will give us information + * of overscroll, i.e. when we have not consumed all of a scroll event, and use this information + * to send the scroll to the app bar layout so that it will expand itself. + */ + lm.setOnOverscrollListener(new OverscrollLinearLayoutManager.OnOverscrollListener() { + @Override + public int onOverscrollX(int overscroll) { + return 0; + } + + @Override + public int onOverscrollY(int overscroll) { + int consumed = 0; + if (overscroll < 0) { + CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); + CoordinatorLayout.Behavior behavior = lp.getBehavior(); + if (behavior != null && behavior instanceof AppBarLayout.Behavior) { + ((AppBarLayout.Behavior) behavior).onNestedScroll(coordinatorLayout, appBarLayout, recyclerView, 0, 0, 0, overscroll); + consumed = overscroll; // Consume all of it! + } + } + return consumed; + } + }); recyclerView.setLayoutManager(lm); recyclerView.setAdapter(adapter); diff --git a/app/src/main/java/org/fdroid/fdroid/views/OverscrollLinearLayoutManager.java b/app/src/main/java/org/fdroid/fdroid/views/OverscrollLinearLayoutManager.java new file mode 100644 index 000000000..2af5ac1e6 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/views/OverscrollLinearLayoutManager.java @@ -0,0 +1,91 @@ +package org.fdroid.fdroid.views; + +import android.content.Context; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; + +/** + * This class is like a standard LinearLayoutManager but with an option to add an + * overscroll listener. This can be used to consume overscrolls, e.g. to draw custom + * "glows". + */ +public class OverscrollLinearLayoutManager extends LinearLayoutManager { + + /** + * A listener interface to get overscroll infromation. + */ + public interface OnOverscrollListener { + /** + * Notifies the listener that an overscroll has happened in the x direction. + * @param overscroll If negative, the recycler view has been scrolled to the "start" + * position. If positive to the "end" position. + * @return Return the amount of overscroll consumed. Returning 0 will let the + * recycler view handle this in the default way. Return "overscroll" to consume the + * whole event. + */ + int onOverscrollX(int overscroll); + + /** + * Notifies the listener that an overscroll has happened in the y direction. + * @param overscroll If negative, the recycler view has been scrolled to the "top" + * position. If positive to the "bottom" position. + * @return Return the amount of overscroll consumed. Returning 0 will let the + * recycler view handle this in the default way. Return "overscroll" to consume the + * whole event. + */ + + int onOverscrollY(int overscroll); + } + + private OnOverscrollListener overscrollListener = null; + + public OverscrollLinearLayoutManager(Context context) { + super(context); + } + + public OverscrollLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { + super(context, orientation, reverseLayout); + } + + public OverscrollLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * Set the {@link OverscrollLinearLayoutManager.OnOverscrollListener} to get information about + * when the parent recyclerview is overscrolled. + * + * @param listener Listener to add + * @see OverscrollLinearLayoutManager.OnOverscrollListener + */ + public void setOnOverscrollListener(OnOverscrollListener listener) { + overscrollListener = listener; + } + + @Override + public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { + int consumed = super.scrollHorizontallyBy(dx, recycler, state); + int overscrollX = dx - consumed; + if (overscrollX != 0) { + if (overscrollListener != null) { + int consumedByListener = overscrollListener.onOverscrollX(overscrollX); + consumed += consumedByListener; + } + } + return consumed; + } + + @Override + public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { + int consumed = super.scrollVerticallyBy(dy, recycler, state); + int overscrollY = dy - consumed; + if (overscrollY != 0) { + if (overscrollListener != null) { + int consumedByListener = overscrollListener.onOverscrollY(overscrollY); + consumed += consumedByListener; + } + } + return consumed; + } +}