Expanding And Collapsing Toolbar In Android - android

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 seen so piece of code but cannot be able to find my solution.
I have also seen the solution of one of the amazing developer https://github.com/saulmm/CoordinatorLayoutExample but cannot be able to find out my solution properly
This is my piece of code which i have implemented
activity_collapsing_toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="176dp"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="#color/base_color_theme_new"
android:gravity="center_horizontal"
app:layout_collapseMode="parallax">
<RelativeLayout
android:id="#+id/rl_class_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="26dp"
android:gravity="center">
<LinearLayout
android:id="#+id/ll_class"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#drawable/rounded_white_circle"
android:gravity="center">
<ImageView
android:id="#+id/iv_class_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_gravity="center"
android:padding="8dp"
android:src="#drawable/class_4" />
</LinearLayout>
</RelativeLayout>
<TextView
android:id="#+id/tv_class_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/rl_class_image"
android:layout_marginTop="15dp"
android:gravity="center"
android:text="MATHEMATICS"
android:textSize="17sp" />
<TextView
android:id="#+id/tv_videos_test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#id/tv_class_name"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="20 VIDEOS | 5 TESTS"
android:textSize="10sp" />
</RelativeLayout>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#drawable/rounded_corners_for_list"
android:fillViewport="true"
app:behavior_overlapTop="10dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<!--<include layout="#layout/activity_chapters" />-->
<com.chalklit.widget.NonScrollListView
android:id="#+id/lv_modules_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
android:divider="#null"
android:scrollbars="none"></com.chalklit.widget.NonScrollListView>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
CollapsingToolbarActivity.java
private CollapsingToolbarLayout collapsingToolbarLayout = null;
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_collapsing_toolbar);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.inflateMenu(R.menu.menu_main);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
collapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbarLayout.setTitle(" ");
collapsingToolbarLayout.setContentScrimColor(getResources().getColor(R.color.base_color_theme_new));
collapsingToolbarLayout.setStatusBarScrimColor(getResources().getColor(R.color.base_color_theme_new));
}

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()))
})
}
Demo 1
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
}
Demo 2
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"

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>

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
http://www.devexchanges.info/2016/03/android-tip-custom-coordinatorlayout.html
https://medium.com/google-developers/intercepting-everything-with-coordinatorlayout-behaviors-8c6adc140c26#.tfsd7ftkl

Related

Scrim color change callback on CollapsingToolbarLayout?

Problem
Given the following view hierarchy
<?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:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="96dp"
android:backgroundTint="?android:colorBackground">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:collapsedTitleGravity="start"
app:collapsedTitleTextAppearance="?textAppearanceHeadline6"
app:contentScrim="?colorSurface"
app:expandedTitleGravity="start|bottom"
app:expandedTitleMarginBottom="16dp"
app:expandedTitleMarginEnd="16dp"
app:expandedTitleMarginStart="16dp"
app:expandedTitleTextAppearance="?textAppearanceHeadline5"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:statusBarScrim="?android:colorBackground"
app:toolbarId="#+id/toolbar">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#null"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/menu_section_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
When a user scrolls the list
Then status bar background color should change as per the top navigation bar's background at-least in expanded and collapsed state.
Current Solution
Implement a custom OnOffsetChangedListener
internal abstract class AppBarLayoutOffsetStateChangeListener : AppBarLayout.OnOffsetChangedListener {
enum class State {
COLLAPSED, EXPANDED, SCROLLING
}
private var postJob: Runnable? = null
abstract fun onStateChange(state: State)
override fun onOffsetChanged(appBarLayout: AppBarLayout, verticalOffset: Int) {
if (abs(verticalOffset) - appBarLayout.totalScrollRange == 0) {
postJob = Runnable {
onStateChange(State.COLLAPSED)
}
appBarLayout.postDelayed(postJob, DELAY_COLLAPSED_STATE)
} else if (verticalOffset == 0) {
onStateChange(State.EXPANDED)
} else {
onStateChange(State.SCROLLING)
postJob?.let {
appBarLayout.removeCallbacks(it)
postJob = null
}
}
}
private companion object {
const val DELAY_COLLAPSED_STATE = 1000L // in ms.
}
}
Set an instance of AppBarLayoutOffsetStateChangeListener to AppBarLayout
appBarLayout.addOnOffsetChangedListener(object : AppBarLayoutOffsetStateChangeListener() {
override fun onStateChange(state: State) {
when (state) {
State.EXPANDED -> {
// set status bar color to `AppBarLayout` background tint.
}
State.COLLAPSED -> {
// set status bar color to `CollapsingToolbarLayout` content scrim color
}
else -> Unit
}
}
})
Rough edges
CollapsingToolbarLayout color animate changes independently of AppBarLayout vertical offset, so had to add DELAY_COLLAPSED_STATE of 1 second ! just to synchronize the two animations.
Status bar colors in Expanded and Collapsed states are picked from two different views, after cross-checking using Layout Inspector.
Question to the community
Is there a better solution ?

How do I achieve on scroll behavior where both the toolbar and bottom navigation should hide?Only one of them is working

I am trying to achieve the coordinator layout behaviour where on scrolling the recycler view can hide both the toolbar and bottom navigation view. So far I have achieved one success i.e bottom navigation bottom bar does hide but with one caveat that it remains active even when the keyboard is on(how do I fix that too?)
My main concern here is how do I achieve the same feature of bottom navigation view of hiding into the toolbar?
I have included the custom toolbar in Appbar layout, but I have tried to add the Toolbar layout tag too in the AppBar nothing works, It just remains the same.
And for the bottomnavigation jumping up on top I don't know what to do? till now I have added snap scroll flags on the bottomnavigation view to stop this behaviour and also snap flag didn't work, I think so, coz it remains in halfway position while going up on search tap.
Got this BottomNavigationBehavior from the wonderful article.
reference
video showing behavior
image for snap behavior
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
>
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:elevation="0dp"
android:background="#android:color/transparent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
>
<include
app:layout_scrollFlags="scroll|enterAlways|snap"
layout="#layout/browser_search_tap_tb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/browser_tb"
/>
</com.google.android.material.appbar.AppBarLayout>
<!--Scrolling effect for the bottom nav menu-->
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/rv_test_items"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:id="#+id/rv_test"
/>
<!--Bottom navigation view for the Selection of the Tabs and Items in Menu-->
<com.google.android.material.bottomnavigation.BottomNavigationView
app:layout_scrollFlags="scroll|enterAlways|snap"
android:id="#+id/browser_bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#ffff"
app:layout_behavior="com.example.android.browserui.BottomNavigationBehavior"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_nav_menu"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
browser_search_tap_tb.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#android:color/transparent"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat"
app:contentInsetStart="8dp"
app:contentInsetEnd="8dp"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_width="0dp"
android:layout_height="match_parent"
android:hint="Search or type new address"
android:padding="8dp"
android:paddingEnd="12dp"
android:paddingStart="12dp"
android:drawableEnd="#drawable/ic_mic"
android:inputType="textWebEditText"
android:background="#drawable/rounded_et_search"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" android:id="#+id/et_search_bar_tap"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.appcompat.widget.Toolbar>
BottomNavigationBehavior.kt
class BottomNavigationBehavior<V : View>(context: Context, attrs: AttributeSet) :
CoordinatorLayout.Behavior<V>(context, attrs) {
private var lastStartedType: Int = 0
private var offsetAnimator: ValueAnimator? = null
var isSnappingEnabled = false
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
if (dependency is Snackbar.SnackbarLayout) {
updateSnackbar(child, dependency)
}
return super.layoutDependsOn(parent, child, dependency)
}
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int
): Boolean {
if (axes != ViewCompat.SCROLL_AXIS_VERTICAL)
return false
lastStartedType = type
offsetAnimator?.cancel()
return true
}
override fun onNestedPreScroll(
coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int
) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
child.translationY = max(0f, min(child.height.toFloat(), child.translationY + dy))
}
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
if (!isSnappingEnabled)
return
// add snap behaviour
// Logic here borrowed from AppBarLayout onStopNestedScroll code
if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
// find nearest seam
val currTranslation = child.translationY
val childHalfHeight = child.height * 0.5f
// translate down
if (currTranslation >= childHalfHeight) {
animateBarVisibility(child, isVisible = false)
}
// translate up
else {
animateBarVisibility(child, isVisible = true)
}
}
}
private fun animateBarVisibility(child: View, isVisible: Boolean) {
if (offsetAnimator == null) {
offsetAnimator = ValueAnimator().apply {
interpolator = DecelerateInterpolator()
duration = 150L
}
offsetAnimator?.addUpdateListener {
child.translationY = it.animatedValue as Float
}
} else {
offsetAnimator?.cancel()
}
val targetTranslation = if (isVisible) 0f else child.height.toFloat()
offsetAnimator?.setFloatValues(child.translationY, targetTranslation)
offsetAnimator?.start()
}
private fun updateSnackbar(child: View, snackbarLayout: Snackbar.SnackbarLayout) {
if (snackbarLayout.layoutParams is CoordinatorLayout.LayoutParams) {
val params = snackbarLayout.layoutParams as CoordinatorLayout.LayoutParams
params.anchorId = child.id
params.anchorGravity = Gravity.TOP
params.gravity = Gravity.TOP
snackbarLayout.layoutParams = params
}
}
}
So Finally Resolved My issue after getting my head around for 4 days here are some changes which I did and solved the issues :
activity_main.xml
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:elevation="0dp"
android:background="#android:color/transparent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"<!--Removed this line-->
>
<!--Bottom navigation view for the Selection of the Tabs and Items in Menu-->
<com.google.android.material.bottomnavigation.BottomNavigationView
app:layout_scrollFlags="scroll|enterAlways|snap"<!--Removed this line-->
android:id="#+id/browser_bottom_nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#ffff"
app:layout_behavior="com.example.android.browserui.BottomNavigationBehavior"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/bottom_nav_menu"
/>
Here in activity_main.xml, I removed the layout_behavior because the appbar itself is invoking the scroll behavior for other layout items, it acts as a parent.
app:layout_behavior="#string/appbar_scrolling_view_behavior"
Also removed scrollflags from the bottom navigation view, AS I was implementing this behavior from the Class BottomNavigationBehavior.kt, You find the implementation below
app:layout_scrollFlags="scroll|enterAlways|snap"
browser_search_tap_tb.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#android:color/transparent"
app:layout_scrollFlags="scroll|enterAlways" <!--Removed this line-->
app:popupTheme="#style/ThemeOverlay.AppCompat"
app:contentInsetStart="8dp"
app:contentInsetEnd="8dp"
>
Here in browser_search_tb, I removed the following line because it was overriding the Scrollflags in co-ordinator layout so removed it and it worked flawlessly
app:layout_scrollFlags="scroll|enterAlways"
BottomNavigationBehavior.kt
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {
if (!isSnappingEnabled)
return // removed this line
{
// add snap behaviour
// Logic here borrowed from AppBarLayout onStopNestedScroll code
if (lastStartedType == ViewCompat.TYPE_TOUCH || type == ViewCompat.TYPE_NON_TOUCH) {
// find nearest seam
val currTranslation = child.translationY
val childHalfHeight = child.height * 0.5f
// translate down
if (currTranslation >= childHalfHeight) {
animateBarVisibility(child, isVisible = false)
}
// translate up
else {
animateBarVisibility(child, isVisible = true)
}
}
}
}
Here I removed the
return
and added braces{} to the if statement and snap feature did work properly
Hope this answer will help you and will cut down your debugging time.
The above reference is one of the best article you can find for scrolling behavior on the internet it simple and smooth to grasp

AppBarLayout extend scroll range

I have a problem with scrolling, AppBarLayout not fully scrolls to out of screen.
How can i to continue scroll to statusbar height?
But if i set to root layout (CoordinatorLayout) fitsSystemWIndows=false, no problem with scroll, but status bar becomes without alpha channel.
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/search_appbar"
android:layout_width="match_parent"
android:layout_height="50dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
...
other layouts here
...
</LinearLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Add Scrolling behaviour to your Toolbar which is inside AppbarLayout.
app:layout_scrollFlags="scroll|enterAlways"
EDIT:- Replace scrolling behaviour of CollapsingToolbarLayout with below code.
app:layout_scrollFlags="scroll|exitUntilCollapsed"
I found solution.
Maybe someone it helps.
create package: com.google.android.material.appbar
create class which extend AppBarLayout.Behavior
Result in the image
package com.google.android.material.appbar
import ...
class OffsetBehavior(c: Context, a: AttributeSet) : AppBarLayout.Behavior(c, a)
{
private var mOffset = 0
fun setOffset(value: Int) {
mOffset = value
}
// completely copy the method onNestedPreScroll from AppBarLaout.Behavior
// and add some changes
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int)
{
if (dy != 0) {
val min: Int
val max: Int
// scroll down
if (dy < 0) {
min = -(child.totalScrollRange + mOffset)
max = min + child.downNestedPreScrollRange + mOffset
}
// scroll up
else {
min = -(child.upNestedPreScrollRange + mOffset)
max = 0
}
if (min != max) {
consumed[1] = scroll(coordinatorLayout, child, dy, min, max)
}
}
if (child.isLiftOnScroll) {
child.setLiftedState(child.shouldLift(target))
}
}
}
and then set it to AppBarLayout in XML
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/search_appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.OffsetBehavior">
...
</com.google.android.material.appbar.AppBarLayout>
in the Activity:
(1) add fun
// getting statusbar height
// my phone with Android9 = 96px
// my phone with Android7 = 72px
fun getStatusBarHeight(): Int {
val id = resources.getIdentifier("status_bar_height", "dimen", "android")
return if (id > 0) {
resources.getDimensionPixelSize(id)
} else {
0
}
}
(2) in onCreate()
val lp = search_appbar?.layoutParams as? CoordinatorLayout.LayoutParams
val bh = lp?.behavior as? com.google.android.material.appbar.OffsetBehavior
bh?.setOffset(getStatusBarHeight())
Result:
https://i.stack.imgur.com/pNeze.gif

AppBarLayout overlap my custom view with layout_behavior

I have a custom header with a custom behavior that interacting with a coordinator layout. This header depends on an appBarLayout that contains a collapsingToolbarLayout and a Toolbar. When the toolbar layout collapses, the custom header adjust its properties and position the way I want, but the second I reach the min height of the layout, the appBarLayout overlaps the custom header and I can't see it until I begin expanding it.
This is the code for the layout:
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="#dimen/mk_appbar_height">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/toolbar_collapse"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="#dimen/mt_toolbar_height"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:titleEnabled="false">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
style="#style/ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="#dimen/mt_toolbar_height"
app:layout_collapseMode="pin"
/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- This is a layout intended for containing MonkeyChatFragment and/or MonkeyConversationsFragment
RelativeLayout has an issue that doesn't render the RecyclerView with the whole size of its
container linear layout.
The only viable solution is FrameLayout -->
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
<FrameLayout
android:id="#+id/viewStatus"
android:layout_width="match_parent"
android:layout_height="#dimen/status_height"
android:layout_marginTop="#dimen/status_inverse_height"
android:background="#color/mk_status_connected"/>
<com.criptext.monkeykitui.toolbar.HeaderView
android:id="#+id/custom_toolbar"
layout="#layout/custom_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="56dp"
app:layout_behavior="com.criptext.monkeykitui.toolbar.HeaderViewBehavior"
app:behavior_overlapTop="64dp"
/></android.support.design.widget.CoordinatorLayout>
and this is the code of the layout_behavior:
class HeaderViewBehavior(context: Context, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<HeaderView>(context, attrs){
private val MIN_AVATAR_PERCENTAGE_SIZE = 0.3f
private val EXTRA_FINAL_AVATAR_PADDING = 80
private val TAG = "behavior"
private val mContext: Context = context
private val mCustomFinalHeight: Float = 0.toFloat()
private var mStartToolbarPosition: Float = 0.toFloat()
private var mStartYPosition: Int = 0
private var mFinalYPosition: Int = 0
private var mStartHeight: Int = 0
private var mfontSize: Float = 20.toFloat()
private var mChangeBehaviorPoint: Float = 0.toFloat()
override fun layoutDependsOn(parent: CoordinatorLayout?, child: HeaderView?, dependency: View?): Boolean {
var hello = (dependency is AppBarLayout)
return hello
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: HeaderView, dependency: View): Boolean {
maybeInitProperties(child, dependency)
val maxScrollDistance = - mContext.resources.getDimension(R.dimen.mk_header_scroll)
val expandedPercentageFactor = dependency.y / maxScrollDistance
Log.d("TEST", dependency.y.toString())
Log.d("TEST", maxScrollDistance.toString())
if (expandedPercentageFactor < mChangeBehaviorPoint) {
val heightFactor = (mChangeBehaviorPoint - expandedPercentageFactor) / mChangeBehaviorPoint
val distanceYToSubtract = (mStartYPosition - mFinalYPosition) * (1f - expandedPercentageFactor) + child.getHeight() / 2
child.setY(mStartYPosition - distanceYToSubtract)
val heightToSubtract = (mStartHeight - mCustomFinalHeight) * heightFactor
val lp = child.layoutParams as CoordinatorLayout.LayoutParams
child.layoutParams = lp
} else {
val distanceYToSubtract = (mStartYPosition - mFinalYPosition) * (1f - expandedPercentageFactor)
child.setY(mStartYPosition - distanceYToSubtract)
if(mStartYPosition - distanceYToSubtract < mStartYPosition){
child.setY(mStartYPosition.toFloat())
}else if(mStartYPosition - distanceYToSubtract > mFinalYPosition){
child.setY(mFinalYPosition.toFloat())
}
child.title.textSize = mfontSize - (mfontSize - 25) * (1f - expandedPercentageFactor)
child.subtitle.textSize = 15 - (15 - 20) * (1f - expandedPercentageFactor)
child.imageView.layoutParams.height = (126 - (126 - 226) * (1f - expandedPercentageFactor)).toInt()
child.imageView.layoutParams.width = (126 - (126 - 226) * (1f - expandedPercentageFactor)).toInt()
}
return true
}
private fun maybeInitProperties(child: HeaderView, dependency: View) {
if (mStartYPosition === 0)
mStartYPosition = 0
if (mFinalYPosition === 0)
mFinalYPosition = mContext.resources.getDimension(R.dimen.mk_header_scroll).toInt()
if (mStartHeight === 0)
mStartHeight = child.getHeight()
if (mStartToolbarPosition === 0.toFloat())
mStartToolbarPosition = dependency.y
if (mChangeBehaviorPoint === 0.toFloat())
mChangeBehaviorPoint = (child.height - mCustomFinalHeight) / (2f * (mStartYPosition - mFinalYPosition))
}
}
Just in case anyone else is looking for the same answer I can say I've found solution in another SO question after literally wasting 6 hours trying various solutions to no avail.
To make View visibly overlay AppBarLayout after the latter has been collapsed you need to add android:elevation="8dp" (or higher value) property to the overlaying View.
So, the part of the code in the question that needs to change is:
<com.criptext.monkeykitui.toolbar.HeaderView
android:id="#+id/custom_toolbar"
layout="#layout/custom_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="56dp"
app:layout_behavior="com.criptext.monkeykitui.toolbar.HeaderViewBehavior"
app:behavior_overlapTop="64dp"
/>
to:
<com.criptext.monkeykitui.toolbar.HeaderView
android:id="#+id/custom_toolbar"
layout="#layout/custom_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="56dp"
app:layout_behavior="com.criptext.monkeykitui.toolbar.HeaderViewBehavior"
app:behavior_overlapTop="64dp"
android:elevation="8dp"
/>
All credits go to #kris larson's answer here: CoordinatorLayout custom behavior with AppBarLayout

Collapsing Toolbar layout with logo, title, subtitle in toolbar

I want do this but with Collapsing toolbar layout or display the logo and title in toolbar after scroll.
<!-- Toolbars -->
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/detail_backdrop_height"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/background_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="#drawable/background_1"
app:layout_collapseMode="parallax"
android:fitsSystemWindows="true"/>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/avatar_image"
android:layout_width="#dimen/circular_image_avatar"
android:layout_height="#dimen/circular_image_avatar"
android:gravity="center"
android:scaleType="centerCrop"
android:src="#drawable/ic_placerholder"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:transitionName="image_toolbar"/>
<TextView
android:id="#+id/profile_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Name title"
android:textAlignment="center"
android:layout_marginTop="#dimen/item_padding_top_bottom"
android:gravity="center"
style="#style/titleText_toolbar"
android:layout_below="#+id/avatar_image"
android:transitionName="title_toolbar"/>
<TextView
android:id="#+id/profile_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Subtitle"
android:textAlignment="center"
android:gravity="center"
style="#style/captionText_toolbar"
android:layout_below="#+id/profile_title" />
</RelativeLayout>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin">
<!-- avatar image and title, subtitle -->
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
Please help me
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()))
})
}
Demo 1
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
}
Demo 2
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"
I think these type of animations can be achieved easily using MotionLayout. I have implemented sample collapsing layout using MotionLayout here. You can modify it for your use case. Simple change the start and end constraints.

Categories

Resources