Forked from zly394/AppBarLayoutOverScrollViewBehavior.java
Last active
November 15, 2023 07:59
-
-
Save kibotu/c1266c067543693c32d81eb5bcdcc5b2 to your computer and use it in GitHub Desktop.
Overscroll AppBarLayout Behavior—— AppBarLayout越界弹性效果
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<androidx.coordinatorlayout.widget.CoordinatorLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:fitsSystemWindows="true"> | |
<com.google.android.material.appbar.AppBarLayout | |
android:id="@+id/appbar" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:fitsSystemWindows="true" | |
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |
android:transitionName="picture" | |
app:layout_behavior="com.zly.exifviewer.widget.behavior.AppBarLayoutOverScrollViewBehavior" | |
tools:targetApi="lollipop"> | |
<com.google.android.material.appbar.CollapsingToolbarLayout | |
android:id="@+id/collapsingToolbarLayout" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
app:contentScrim="@color/colorPrimary" | |
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed" | |
app:statusBarScrim="@color/colorPrimaryDark"> | |
<ImageView | |
android:id="@+id/siv_picture" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:fitsSystemWindows="true" | |
android:scaleType="centerCrop" | |
android:tag="overScroll" | |
app:layout_collapseMode="parallax" | |
tools:src="@android:drawable/sym_def_app_icon" /> | |
<androidx.appcompat.widget.Toolbar | |
android:id="@+id/toolbar" | |
android:layout_width="match_parent" | |
android:layout_height="?attr/actionBarSize" | |
app:contentInsetEnd="64dp" | |
app:layout_collapseMode="pin" | |
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" /> | |
</com.google.android.material.appbar.CollapsingToolbarLayout> | |
</com.google.android.material.appbar.AppBarLayout> | |
<androidx.core.widget.NestedScrollView | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
app:layout_behavior="@string/appbar_scrolling_view_behavior"> | |
<LinearLayout | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:orientation="vertical"> | |
<ImageView | |
android:layout_width="match_parent" | |
android:layout_height="200dp" | |
android:src="@drawable/ic_launcher" /> | |
<ImageView | |
android:layout_width="match_parent" | |
android:layout_height="200dp" | |
android:src="@drawable/ic_launcher" /> | |
<ImageView | |
android:layout_width="match_parent" | |
android:layout_height="200dp" | |
android:src="@drawable/ic_launcher" /> | |
<ImageView | |
android:layout_width="match_parent" | |
android:layout_height="200dp" | |
android:src="@drawable/ic_launcher" /> | |
<ImageView | |
android:layout_width="match_parent" | |
android:layout_height="200dp" | |
android:src="@drawable/ic_launcher" /> | |
</LinearLayout> | |
</androidx.core.widget.NestedScrollView> | |
<com.google.android.material.floatingactionbutton.FloatingActionButton | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_gravity="bottom|end" | |
android:layout_marginEnd="16dp" | |
android:layout_marginBottom="16dp" | |
android:clickable="true" /> | |
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.zly.widget.behavior; | |
/** | |
* Created by zhuleiyue on 2017/3/7. | |
*/ | |
class AppBarLayoutOverScrollViewBehavior(context: Context?, attrs: AttributeSet?) : AppBarLayout.Behavior(context, attrs) { | |
private var targetHeight = 500f | |
private var targetView: View? = null | |
private var parentHeight = 0 | |
private var targetViewHeight = 0 | |
private var totalDy = 0f | |
private var lastScale = 0f | |
private var lastBottom = 0 | |
private var isAnimate = false | |
init { | |
attrs?.let { | |
// todo get overscroll view by id | |
// todo get height multiplier | |
} | |
} | |
override fun onLayoutChild(parent: CoordinatorLayout, abl: AppBarLayout, layoutDirection: Int): Boolean { | |
val handled = super.onLayoutChild(parent, abl, layoutDirection) | |
// 需要在调用过super.onLayoutChild()方法之后获取 | |
if (targetView == null) { | |
targetView = parent.findViewWithTag(TAG) | |
if (targetView != null) { | |
initial(abl) | |
} | |
} | |
if (targetView == null) { | |
throw NullPointerException("No target view defined, please set tag to 'overscroll'") | |
} | |
targetHeight = targetView!!.height.toFloat() * 1.1f | |
return handled | |
} | |
override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, target: View, nestedScrollAxes: Int, type: Int): Boolean { | |
isAnimate = true | |
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type) | |
} | |
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { | |
if (targetView != null && (dy < 0 && child.bottom >= parentHeight || dy > 0 && child.bottom > parentHeight)) { | |
scale(child, target, dy) | |
} else { | |
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type) | |
} | |
} | |
override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, target: View, velocityX: Float, velocityY: Float): Boolean { | |
if (velocityY > 100) { | |
isAnimate = false | |
} | |
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY) | |
} | |
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, abl: AppBarLayout, target: View, type: Int) { | |
recovery(abl) | |
super.onStopNestedScroll(coordinatorLayout, abl, target, type) | |
} | |
private fun initial(abl: AppBarLayout) { | |
abl.clipChildren = false | |
parentHeight = abl.height | |
targetViewHeight = targetView!!.height | |
} | |
private fun scale(abl: AppBarLayout, target: View, dy: Int) { | |
totalDy += (-dy).toFloat() | |
totalDy = min(totalDy, targetHeight) | |
lastScale = max(1f, 1f + totalDy / targetHeight) | |
targetView!!.scaleX = lastScale | |
targetView!!.scaleY = lastScale | |
lastBottom = parentHeight + (targetViewHeight / 2 * (lastScale - 1)).toInt() | |
abl.bottom = lastBottom | |
target.scrollY = 0 | |
} | |
private fun recovery(abl: AppBarLayout) { | |
if (totalDy > 0) { | |
totalDy = 0f | |
if (isAnimate) { | |
val anim = ValueAnimator.ofFloat(lastScale, 1f).setDuration(200) | |
anim.addUpdateListener { animation -> | |
val value = animation.animatedValue as Float | |
targetView!!.scaleX = value | |
targetView!!.scaleY = value | |
abl.bottom = (lastBottom - (lastBottom - parentHeight) * animation.animatedFraction).toInt() | |
} | |
anim.start() | |
} else { | |
targetView!!.scaleX = 1f | |
targetView!!.scaleY = 1f | |
abl.bottom = parentHeight | |
} | |
} | |
} | |
companion object { | |
private const val TAG = "overScroll" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment