From d98ac37c4e085a5e6a32c6b4eecd914a016b8011 Mon Sep 17 00:00:00 2001 From: Valkriaine Date: Mon, 9 May 2022 05:57:47 -0700 Subject: [PATCH] resolve issue #8 and issue #12 --- .../factor/bouncy/BouncyNestedScrollView.kt | 43 ++++++++++++++++--- .../com/factor/bouncy/BouncyRecyclerView.kt | 23 ++++++++++ build.gradle | 2 +- example/build.gradle | 9 +++- example/src/main/AndroidManifest.xml | 10 +++-- .../java/com/factor/example/MainActivity.kt | 6 ++- example/src/main/res/layout/activity_main.xml | 33 ++++++++++---- example/src/main/res/values/strings.xml | 9 ++++ example/src/main/res/values/themes.xml | 8 ++++ 9 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 example/src/main/res/values/themes.xml diff --git a/bouncy/src/main/java/com/factor/bouncy/BouncyNestedScrollView.kt b/bouncy/src/main/java/com/factor/bouncy/BouncyNestedScrollView.kt index e8b919c..c95878d 100644 --- a/bouncy/src/main/java/com/factor/bouncy/BouncyNestedScrollView.kt +++ b/bouncy/src/main/java/com/factor/bouncy/BouncyNestedScrollView.kt @@ -56,6 +56,9 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: private var mEdgeGlowTop: BouncyEdgeEffect? = null private var mEdgeGlowBottom: BouncyEdgeEffect? = null + + private var touched = false + /** * Position of the last motion event. */ @@ -624,6 +627,7 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: val vMotionEvent = MotionEvent.obtain(ev) vMotionEvent.offsetLocation(0f, mNestedYOffset.toFloat()) + touched = true when (actionMasked) { MotionEvent.ACTION_DOWN -> @@ -711,7 +715,7 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: { EdgeEffectCompat.onPull(mEdgeGlowBottom!!, deltaY.toFloat() / height, ev.getX(activePointerIndex) / width) - if (!mEdgeGlowBottom!!.isFinished) + if (!mEdgeGlowBottom!!.isFinished && !touched) mEdgeGlowBottom!!.onRelease() } @@ -719,7 +723,7 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: { EdgeEffectCompat.onPull(mEdgeGlowBottom!!, deltaY.toFloat() / height, 1f - ev.getX(activePointerIndex) / width) - if (!mEdgeGlowTop!!.isFinished) + if (!mEdgeGlowTop!!.isFinished && !touched) mEdgeGlowTop!!.onRelease() } @@ -732,6 +736,7 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: } MotionEvent.ACTION_UP -> { + touched = false val velocityTracker = mVelocityTracker velocityTracker!!.computeCurrentVelocity(1000, mMaximumVelocity.toFloat()) val initialVelocity = velocityTracker.getYVelocity(mActivePointerId).toInt() @@ -751,6 +756,7 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: } MotionEvent.ACTION_CANCEL -> { + touched = false if (mIsBeingDragged && childCount > 0 && mScroller!!.springBack(scrollX, scrollY, 0, 0, 0, scrollRange)) ViewCompat.postInvalidateOnAnimation(this) @@ -1652,15 +1658,42 @@ class BouncyNestedScrollView @JvmOverloads constructor(context: Context, attrs: } } + + /** + * Option to bind overscroll effect to parent instead of self. + * + * Default is false, set this to true will bind SpringAnimation to parent view. + */ + var bindSpringToParent = false + set(value) + { + field = value + ensureGlows() + } + private fun ensureGlows() { if (overScrollMode != OVER_SCROLL_NEVER) { if (mEdgeGlowTop == null) { - val context = context - mEdgeGlowTop = BouncyEdgeEffect(context, spring, this, EdgeEffectFactory.DIRECTION_TOP, flingAnimationSize, overscrollAnimationSize) - mEdgeGlowBottom = BouncyEdgeEffect(context, spring, this, EdgeEffectFactory.DIRECTION_BOTTOM, flingAnimationSize, overscrollAnimationSize) + + val viewToAnimate : View = if (!bindSpringToParent) + this + else + this.parent as View + + val spring = SpringAnimation(viewToAnimate, SpringAnimation.TRANSLATION_Y) + .setSpring( + SpringForce() + .setFinalPosition(0f) + .setDampingRatio(dampingRatio) + .setStiffness(stiffness) + ) + mEdgeGlowTop = BouncyEdgeEffect(context, spring, viewToAnimate, EdgeEffectFactory.DIRECTION_TOP, flingAnimationSize, overscrollAnimationSize) + mEdgeGlowBottom = BouncyEdgeEffect(context, spring, viewToAnimate, EdgeEffectFactory.DIRECTION_BOTTOM, flingAnimationSize, overscrollAnimationSize) + + } } else diff --git a/bouncy/src/main/java/com/factor/bouncy/BouncyRecyclerView.kt b/bouncy/src/main/java/com/factor/bouncy/BouncyRecyclerView.kt index 914c659..9c5a347 100644 --- a/bouncy/src/main/java/com/factor/bouncy/BouncyRecyclerView.kt +++ b/bouncy/src/main/java/com/factor/bouncy/BouncyRecyclerView.kt @@ -1,8 +1,10 @@ package com.factor.bouncy +import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas import android.util.AttributeSet +import android.view.MotionEvent import android.widget.EdgeEffect import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce @@ -77,6 +79,22 @@ class BouncyRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView( ) + var touched: Boolean = false + + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(e: MotionEvent?): Boolean + { + + touched = when (e?.actionMasked) + { + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> false + else -> true + } + return super.onTouchEvent(e) + } + + override fun setAdapter(adapter: RecyclerView.Adapter<*>?) { super.setAdapter(adapter) @@ -215,6 +233,11 @@ class BouncyRecyclerView(context: Context, attrs: AttributeSet?) : RecyclerView( override fun onRelease() { super.onRelease() + + if (touched) + return + + onOverPullListener?.onRelease() spring.start() diff --git a/build.gradle b/build.gradle index 5d1091d..19b1936 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.0.4' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/example/build.gradle b/example/build.gradle index 0d946cf..c3bc58a 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -6,7 +6,7 @@ android { buildToolsVersion "30.0.3" defaultConfig { - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion 31 versionCode 1 versionName "1.0" @@ -25,6 +25,12 @@ android { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } + buildFeatures { + viewBinding true + } + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { @@ -36,6 +42,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.android.material:material:1.6.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml index dba3f29..fd3d6a8 100644 --- a/example/src/main/AndroidManifest.xml +++ b/example/src/main/AndroidManifest.xml @@ -3,15 +3,17 @@ package="com.factor.example"> + android:allowBackup="false" + android:label="@string/app_name"> + + android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"> + diff --git a/example/src/main/java/com/factor/example/MainActivity.kt b/example/src/main/java/com/factor/example/MainActivity.kt index 12a1fb4..176fb7a 100644 --- a/example/src/main/java/com/factor/example/MainActivity.kt +++ b/example/src/main/java/com/factor/example/MainActivity.kt @@ -3,6 +3,7 @@ package com.factor.example import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.recyclerview.widget.GridLayoutManager +import com.factor.bouncy.BouncyNestedScrollView import com.factor.bouncy.BouncyRecyclerView @@ -13,8 +14,9 @@ class MainActivity : AppCompatActivity() super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + findViewById(R.id.scrollView).bindSpringToParent = true - findViewById(R.id.rc).adapter = MyAdapter(30) - findViewById(R.id.rc).layoutManager = GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false) + //findViewById(R.id.rc).adapter = MyAdapter(30) + //findViewById(R.id.rc).layoutManager = GridLayoutManager(this, 2, GridLayoutManager.VERTICAL, false) } } \ No newline at end of file diff --git a/example/src/main/res/layout/activity_main.xml b/example/src/main/res/layout/activity_main.xml index 75ab604..baf6dd2 100644 --- a/example/src/main/res/layout/activity_main.xml +++ b/example/src/main/res/layout/activity_main.xml @@ -7,17 +7,32 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + + + - - + + + @@ -29,7 +44,7 @@ @@ -157,6 +172,6 @@ - --> + \ No newline at end of file diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index 5984ab1..9e8cdc6 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -1,3 +1,12 @@ Factor Example + MainActivity2 + + First Fragment + Second Fragment + Next + Previous + + Hello first fragment + Hello second fragment. Arg: %1$s \ No newline at end of file diff --git a/example/src/main/res/values/themes.xml b/example/src/main/res/values/themes.xml new file mode 100644 index 0000000..2abd767 --- /dev/null +++ b/example/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + +