From af50cfea0b3ffae6c6b188fad457b080e98eaf23 Mon Sep 17 00:00:00 2001 From: Takao Sumitomo Date: Wed, 28 Jun 2017 11:54:51 +0900 Subject: [PATCH 1/3] Impl OverScrollFrameLayout --- ...va => ClassicNestedCoordinatorLayout.java} | 12 +- .../view/NestedCoordinatorLayout.java | 97 ++++++++++++++ .../snippets/view/OverScrollFrameLayout.java | 120 ++++++++++++++++++ 3 files changed, 224 insertions(+), 5 deletions(-) rename snippets/src/main/java/net/cattaka/android/snippets/view/{NestedScrollingCoordinatorLayout.java => ClassicNestedCoordinatorLayout.java} (88%) create mode 100644 snippets/src/main/java/net/cattaka/android/snippets/view/NestedCoordinatorLayout.java create mode 100644 snippets/src/main/java/net/cattaka/android/snippets/view/OverScrollFrameLayout.java diff --git a/snippets/src/main/java/net/cattaka/android/snippets/view/NestedScrollingCoordinatorLayout.java b/snippets/src/main/java/net/cattaka/android/snippets/view/ClassicNestedCoordinatorLayout.java similarity index 88% rename from snippets/src/main/java/net/cattaka/android/snippets/view/NestedScrollingCoordinatorLayout.java rename to snippets/src/main/java/net/cattaka/android/snippets/view/ClassicNestedCoordinatorLayout.java index 16fbb57..c31f29e 100644 --- a/snippets/src/main/java/net/cattaka/android/snippets/view/NestedScrollingCoordinatorLayout.java +++ b/snippets/src/main/java/net/cattaka/android/snippets/view/ClassicNestedCoordinatorLayout.java @@ -7,25 +7,27 @@ import android.view.View; /** - * Created by cattaka on 2016/04/27. + * Created by cattaka on 2017/06/28. + *

+ * Only for support-library below 26.0.0 */ -public class NestedScrollingCoordinatorLayout extends CoordinatorLayout { +public class ClassicNestedCoordinatorLayout extends CoordinatorLayout { private final NestedScrollingChildHelper mScrollingChildHelper; private final int[] mParentOffsetInWindow = new int[2]; - public NestedScrollingCoordinatorLayout(Context context) { + public ClassicNestedCoordinatorLayout(Context context) { super(context); mScrollingChildHelper = new NestedScrollingChildHelper(this); mScrollingChildHelper.setNestedScrollingEnabled(true); } - public NestedScrollingCoordinatorLayout(Context context, AttributeSet attrs) { + public ClassicNestedCoordinatorLayout(Context context, AttributeSet attrs) { super(context, attrs); mScrollingChildHelper = new NestedScrollingChildHelper(this); mScrollingChildHelper.setNestedScrollingEnabled(true); } - public NestedScrollingCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) { + public ClassicNestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mScrollingChildHelper = new NestedScrollingChildHelper(this); mScrollingChildHelper.setNestedScrollingEnabled(true); diff --git a/snippets/src/main/java/net/cattaka/android/snippets/view/NestedCoordinatorLayout.java b/snippets/src/main/java/net/cattaka/android/snippets/view/NestedCoordinatorLayout.java new file mode 100644 index 0000000..b7359f5 --- /dev/null +++ b/snippets/src/main/java/net/cattaka/android/snippets/view/NestedCoordinatorLayout.java @@ -0,0 +1,97 @@ +package net.cattaka.android.snippets.view; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.CoordinatorLayout; +import android.support.v4.view.NestedScrollingChildHelper; +import android.util.AttributeSet; +import android.view.View; + +/** + * Created by cattaka on 2017/06/28. + *

+ * Only for support-library over 26.0.0 + */ +public class NestedCoordinatorLayout extends CoordinatorLayout { + private final NestedScrollingChildHelper mScrollingChildHelper; + private final int[] mParentOffsetInWindow = new int[2]; + + public NestedCoordinatorLayout(Context context) { + super(context); + mScrollingChildHelper = new NestedScrollingChildHelper(this); + mScrollingChildHelper.setNestedScrollingEnabled(true); + } + + public NestedCoordinatorLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mScrollingChildHelper = new NestedScrollingChildHelper(this); + mScrollingChildHelper.setNestedScrollingEnabled(true); + } + + public NestedCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mScrollingChildHelper = new NestedScrollingChildHelper(this); + mScrollingChildHelper.setNestedScrollingEnabled(true); + } + + // NestedScrollingParent + @Override + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, int type) { + if (!mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, mParentOffsetInWindow, type)) { + super.onNestedPreScroll(target, dx, dy, consumed, type); + } + } + + // NestedScrollingParent + @Override + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes, int type) { + boolean handled = super.onStartNestedScroll(child, target, axes, type); + handled |= mScrollingChildHelper.startNestedScroll(axes, type); + return handled; + } + + @Override + public void onStopNestedScroll(@NonNull View target, int type) { + super.onStopNestedScroll(target, type); + mScrollingChildHelper.stopNestedScroll(); + } + + @Override + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) { + if (!mScrollingChildHelper.startNestedScroll(axes, type)) { + super.onNestedScrollAccepted(child, target, axes, type); + } + } + + @Override + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { + if (!mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mParentOffsetInWindow, type)) { + super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type); + } + } + + @Override + public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { + if (!mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, mParentOffsetInWindow)) { + super.onNestedPreScroll(target, dx, dy, consumed); + } + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed) + || super.onNestedFling(target, velocityX, velocityY, consumed); + } + + @Override + public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY) + || super.onNestedPreFling(target, velocityX, velocityY); + } + + @Override + public int getNestedScrollAxes() { + return super.getNestedScrollAxes(); + } +} diff --git a/snippets/src/main/java/net/cattaka/android/snippets/view/OverScrollFrameLayout.java b/snippets/src/main/java/net/cattaka/android/snippets/view/OverScrollFrameLayout.java new file mode 100644 index 0000000..7be88d2 --- /dev/null +++ b/snippets/src/main/java/net/cattaka/android/snippets/view/OverScrollFrameLayout.java @@ -0,0 +1,120 @@ +package net.cattaka.android.snippets.view; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.annotation.AttrRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StyleRes; +import android.support.v4.view.NestedScrollingParent; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.widget.FrameLayout; + +/** + * Created by cattaka on 2017/06/28. + */ + +public class OverScrollFrameLayout extends FrameLayout implements NestedScrollingParent { + public static final int DURATION_RETURN = 100; + + private IOnReleaseOverScrollListener mOnReleaseOverScrollListener; + + public OverScrollFrameLayout(@NonNull Context context) { + super(context); + initialize(); + } + + public OverScrollFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initialize(); + } + + public OverScrollFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public OverScrollFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + private void initialize() { + ViewCompat.setNestedScrollingEnabled(this, true); + } + + public void setOnReleaseOverScrollListener(IOnReleaseOverScrollListener onReleaseOverScrollListener) { + mOnReleaseOverScrollListener = onReleaseOverScrollListener; + } + + public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + return true; + } + + public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int nestedScrollAxes) { + super.onNestedScrollAccepted(child, target, nestedScrollAxes); + } + + public void onStopNestedScroll(@NonNull View target) { + super.onStopNestedScroll(target); + final float dx = getTranslationX(); + final float dy = getTranslationY(); + boolean consumed = false; + if (mOnReleaseOverScrollListener != null) { + consumed = mOnReleaseOverScrollListener.onReleaseOverScroll(this, dx, dy); + } + if (!consumed) { + ViewPropertyAnimator animator = animate().translationX(0).translationY(0).setDuration(DURATION_RETURN); + animator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + if (mOnReleaseOverScrollListener != null) { + mOnReleaseOverScrollListener.onReleaseAnimationEnd(OverScrollFrameLayout.this, dx, dy); + } + } + }); + animator.start(); + } + } + + public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, + int dxUnconsumed, int dyUnconsumed) { + super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); + if (dxUnconsumed != 0) { + setTranslationX(getTranslationX() - (float) dxUnconsumed / 2f); + } + if (dyUnconsumed != 0) { + setTranslationY(getTranslationY() - (float) dyUnconsumed / 2f); + } + } + + public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) { + super.onNestedPreScroll(target, dx, dy, consumed); + } + + public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) { + return super.onNestedFling(target, velocityX, velocityY, consumed); + } + + public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) { + return super.onNestedPreFling(target, velocityX, velocityY); + } + + public int getNestedScrollAxes() { + return super.getNestedScrollAxes(); + } + + public interface IOnReleaseOverScrollListener { + boolean onReleaseOverScroll(@NonNull OverScrollFrameLayout view, float dx, float dy); + + void onReleaseAnimationEnd(@NonNull OverScrollFrameLayout view, float dx, float dy); + } +} From 080c8d8d420d0f7757027677585ba93f7d256c61 Mon Sep 17 00:00:00 2001 From: Takao Sumitomo Date: Wed, 28 Jun 2017 12:17:53 +0900 Subject: [PATCH 2/3] Add OverScrollRecyclerViewActivity --- example/src/main/AndroidManifest.xml | 3 ++ .../snippets/example/MainActivity.java | 3 ++ .../OverScrollRecyclerViewActivity.java | 45 +++++++++++++++++++ .../activity_over_scroll_recycler_view.xml | 19 ++++++++ example/src/main/res/values/styles.xml | 6 +++ 5 files changed, 76 insertions(+) create mode 100644 example/src/main/java/net/cattaka/android/snippets/example/OverScrollRecyclerViewActivity.java create mode 100644 example/src/main/res/layout/activity_over_scroll_recycler_view.xml diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml index 3028cf9..d4cb014 100644 --- a/example/src/main/AndroidManifest.xml +++ b/example/src/main/AndroidManifest.xml @@ -40,6 +40,9 @@ android:theme="@style/NoActionBarAppTheme" /> + \ No newline at end of file diff --git a/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java b/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java index 73366ec..dc669dc 100644 --- a/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java +++ b/example/src/main/java/net/cattaka/android/snippets/example/MainActivity.java @@ -18,6 +18,9 @@ public class MainActivity extends AppCompatActivity { private static final List ACTIVITY_ENTRIES = Arrays.asList( + new ActivityEntry("Over Scroll", null, + new ActivityEntry("With RecyclerView", OverScrollRecyclerViewActivity.class) + ), new ActivityEntry("Workaround of issues", null, new ActivityEntry("AOSP Issue 212316", Issue212316ParrierExampleActvity.class) ), diff --git a/example/src/main/java/net/cattaka/android/snippets/example/OverScrollRecyclerViewActivity.java b/example/src/main/java/net/cattaka/android/snippets/example/OverScrollRecyclerViewActivity.java new file mode 100644 index 0000000..097decf --- /dev/null +++ b/example/src/main/java/net/cattaka/android/snippets/example/OverScrollRecyclerViewActivity.java @@ -0,0 +1,45 @@ +package net.cattaka.android.snippets.example; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; + +import net.cattaka.android.adaptertoolbox.adapter.ScrambleAdapter; +import net.cattaka.android.snippets.example.adapter.factory.SimpleStringViewHolderFactory; + +import java.util.ArrayList; + +/** + * Created by cattaka on 16/12/03. + */ + +public class OverScrollRecyclerViewActivity extends AppCompatActivity { + RecyclerView mRecyclerView; + + ScrambleAdapter mAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_over_scroll_recycler_view); + + // Find views + mRecyclerView = (RecyclerView) findViewById(R.id.view_recycler); + + { // Setup mRecyclerView + mAdapter = new ScrambleAdapter<>(this, new ArrayList(), null, new SimpleStringViewHolderFactory()); + mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + mRecyclerView.setAdapter(mAdapter); + for (int i = 0; i < 100; i++) { + mAdapter.getItems().add("Item : " + mAdapter.getItems().size()); + } + } + } + + @Override + protected void onStart() { + super.onStart(); + } +} diff --git a/example/src/main/res/layout/activity_over_scroll_recycler_view.xml b/example/src/main/res/layout/activity_over_scroll_recycler_view.xml new file mode 100644 index 0000000..fd3cfda --- /dev/null +++ b/example/src/main/res/layout/activity_over_scroll_recycler_view.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/example/src/main/res/values/styles.xml b/example/src/main/res/values/styles.xml index 975ec8f..586fcaf 100644 --- a/example/src/main/res/values/styles.xml +++ b/example/src/main/res/values/styles.xml @@ -8,6 +8,12 @@ @color/colorAccent + +