I am implementing expanding and collapsing toolbar with the help of collapsing toolbar but I am stuck when my toolbar is collapsed I want to show different toolbar. I have s
Here's another approach that doesn't use a custom CoordinatorLayoutBehavior
.
It uses an OnOffsetChangedListener
which comes from AppBarLayout
.
Here's a snippet:
class OnOffsetChangedListener implements AppBarLayout.OnOffsetChangedListener {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
final int scrollRange = appBarLayout.getTotalScrollRange();
float offsetFactor = (float) (-verticalOffset) / (float) scrollRange;
...
This shows you how to find the total scroll range and then find the ratio between the total scroll range and the current scroll position. This is what you need to figure out how to scale and position your toolbar views.
For a custom layout (like I did), you can override onAttachedToWindow
and add the listener there:
// Add an OnOffsetChangedListener if possible
final ViewParent parent = getParent();
if (parent instanceof AppBarLayout) {
if (mOnOffsetChangedListener == null) {
mOnOffsetChangedListener = new OnOffsetChangedListener();
}
((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
}
I found this approach to be a little simpler than creating a custom behavior.
I created an example project on GitHub. The app looks like this:
You can see the whole project at https://github.com/klarson2/Collapsing-Image
you should add Line #33
<?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_layout"
android:layout_width="match_parent"
android:layout_height="192dp"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|snap|exitUntilCollapsed"
app:title="Collapsing"
app:toolbarId="@+id/toolbar">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/nana"
app:layout_collapseMode="parallax" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ax" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:baselineAlignBottom="false"
android:clickable="true"
android:src="@drawable/possetive"
app:fabSize="normal"
app:layout_anchor="@id/appbar_layout"
app:layout_anchorGravity="bottom|right"
app:rippleColor="#E4D6D6" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
I have preperead two amaizing avatar collapsing demo samples with approach that doesn’t use a custom CoordinatorLayoutBehavior
!
To view my samples native code: "Collapsing Avatar Toolbar Sample"
To read my "Animation Collapsing Toolbar Android" post on Medium.
demo 1 demo 2
Instead of use use a custom CoordinatorLayoutBehavior
i use an OnOffsetChangedListener
which comes from AppBarLayout
.
private lateinit var appBarLayout: AppBarLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_demo_1)
...
appBarLayout = findViewById(R.id.app_bar_layout)
/**/
appBarLayout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { appBarLayout, i ->
...
/**/
updateViews(Math.abs(i / appBarLayout.totalScrollRange.toFloat()))
})
}
in updateViews
method avatar changes the size and changes avatar’s X, Y position translation in first demo.
private fun updateViews(offset: Float) {
...
/* Collapse avatar img*/
ivUserAvatar.apply {
when {
offset > avatarAnimateStartPointY -> {
val avatarCollapseAnimateOffset = (offset - avatarAnimateStartPointY) * avatarCollapseAnimationChangeWeight
val avatarSize = EXPAND_AVATAR_SIZE - (EXPAND_AVATAR_SIZE - COLLAPSE_IMAGE_SIZE) * avatarCollapseAnimateOffset
this.layoutParams.also {
it.height = Math.round(avatarSize)
it.width = Math.round(avatarSize)
}
invisibleTextViewWorkAround.setTextSize(TypedValue.COMPLEX_UNIT_PX, offset)
this.translationX = ((appBarLayout.width - horizontalToolbarAvatarMargin - avatarSize) / 2) * avatarCollapseAnimateOffset
this.translationY = ((toolbar.height - verticalToolbarAvatarMargin - avatarSize ) / 2) * avatarCollapseAnimateOffset
}
else -> this.layoutParams.also {
if (it.height != EXPAND_AVATAR_SIZE.toInt()) {
it.height = EXPAND_AVATAR_SIZE.toInt()
it.width = EXPAND_AVATAR_SIZE.toInt()
this.layoutParams = it
}
translationX = 0f
}
}
}
}
to find avatarAnimateStartPointY
and avatarCollapseAnimationChangeWeight
(for convert general offset to avatar animate offset):
private var avatarAnimateStartPointY: Float = 0F
private var avatarCollapseAnimationChangeWeight: Float = 0F
private var isCalculated = false
private var verticalToolbarAvatarMargin =0F
...
if (isCalculated.not()) {
avatarAnimateStartPointY =
Math.abs((appBarLayout.height - (EXPAND_AVATAR_SIZE + horizontalToolbarAvatarMargin)) / appBarLayout.totalScrollRange)
avatarCollapseAnimationChangeWeight = 1 / (1 - avatarAnimateStartPointY)
verticalToolbarAvatarMargin = (toolbar.height - COLLAPSE_IMAGE_SIZE) * 2
isCalculated = true
}
avatar change his size and than animate move to right at one moment with top toolbar text became to show and moving to left.
You need to track states: TO_EXPANDED_STATE
changing, TO_COLLAPSED_STATE
changing, WAIT_FOR_SWITCH
.
/*Collapsed/expended sizes for views*/
val result: Pair<Int, Int> = when {
percentOffset < ABROAD -> {
Pair(TO_EXPANDED_STATE, cashCollapseState?.second ?: WAIT_FOR_SWITCH)
}
else -> {
Pair(TO_COLLAPSED_STATE, cashCollapseState?.second ?: WAIT_FOR_SWITCH)
}
}
Create animation for avatar on state switch change:
result.apply {
var translationY = 0f
var headContainerHeight = 0f
val translationX: Float
var currentImageSize = 0
when {
cashCollapseState != null && cashCollapseState != this -> {
when (first) {
TO_EXPANDED_STATE -> {
translationY = toolbar.height.toFloat()
headContainerHeight = appBarLayout.totalScrollRange.toFloat()
currentImageSize = EXPAND_AVATAR_SIZE.toInt()
/**/
titleToolbarText.visibility = View.VISIBLE
titleToolbarTextSingle.visibility = View.INVISIBLE
background.setBackgroundColor(ContextCompat.getColor(this@Demo2Activity, R.color.color_transparent))
/**/
ivAvatar.translationX = 0f
}
TO_COLLAPSED_STATE -> {
background.setBackgroundColor(ContextCompat.getColor(this@Demo2Activity, R.color.colorPrimary))
currentImageSize = COLLAPSE_IMAGE_SIZE.toInt()
translationY = appBarLayout.totalScrollRange.toFloat() - (toolbar.height - COLLAPSE_IMAGE_SIZE) / 2
headContainerHeight = toolbar.height.toFloat()
translationX = appBarLayout.width / 2f - COLLAPSE_IMAGE_SIZE / 2 - margin * 2
/**/
ValueAnimator.ofFloat(ivAvatar.translationX, translationX).apply {
addUpdateListener {
if (cashCollapseState!!.first == TO_COLLAPSED_STATE) {
ivAvatar.translationX = it.animatedValue as Float
}
}
interpolator = AnticipateOvershootInterpolator()
startDelay = 69
duration = 350
start()
}
...
}
}
ivAvatar.apply {
layoutParams.height = currentImageSize
layoutParams.width = currentImageSize
}
collapsingAvatarContainer.apply {
layoutParams.height = headContainerHeight.toInt()
this.translationY = translationY
requestLayout()
}
/**/
cashCollapseState = Pair(first, SWITCHED)
}
To view my samples native code: "Collapsing Avatar Toolbar Sample"
To achieve this, we must have to create custom behavior using CoordinatorLayout.Behavior
Take into account two core elements: child and dependency:
The child is the view that enhances behavior, dependency who will serve as a trigger to interact with the child element. In your requirement the child is the ImageView and the dependency is the Toolbar, in that way, if the Toolbar moves, the ImageView will move too.
Please check some below links for custome behaviour toolbar demos