Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kibotu/c1266c067543693c32d81eb5bcdcc5b2 to your computer and use it in GitHub Desktop.
Save kibotu/c1266c067543693c32d81eb5bcdcc5b2 to your computer and use it in GitHub Desktop.
Overscroll AppBarLayout Behavior—— AppBarLayout越界弹性效果
<?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>
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