Collapsing Toolbar layout with logo, title, subtitle in toolbar - android

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.

Related

Reverse HideBottomViewOnScrollBehavior and initialize to hide state android

I have a CoordinatorLayout with a RecyclerView and a Fab button.
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/swipeChatRoomRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/chatRoomRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="true"
android:scrollbars="vertical" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/bottomScrollFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:src="#drawable/ic_arrow_down"
app:backgroundTint="#color/colorPrimaryLight"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:layout_anchor="#id/swipeChatRoomRecycler"
app:layout_anchorGravity="bottom|right|end"
app:fabSize="mini" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The recyclerView is set as below
viewManager = LinearLayoutManager(activity).apply {
orientation = LinearLayoutManager.VERTICAL
stackFromEnd = false
reverseLayout = true
}
What i want to achieve is
At the launch of the activity i want the bottomScrollFab to be invisible since the recycler is scrolled in the end as by default
I want to achieve the exact reverse behaviour, meaning that i want the Fab to be shown when recycler is not onScroll and to be hidden when recycler is onScroll
How can i do those two things?
Well up to now i have created a custom Behaviour which changes the alpha when the recycler is scrolled. It works pretty fine
class ReverseHideViewOnScrollBehaviour(context: Context, attributeSet: AttributeSet): CoordinatorLayout.Behavior<View>(context, attributeSet) {
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
return dependency is RecyclerView
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
val yPercentage = getViewOffsetFromRecycler(parent, child)
val diff = 100.0f - yPercentage
child.alpha = 1 - diff
return true
}
private fun getViewOffsetFromRecycler(parent: CoordinatorLayout, view: View): Float{
var percentage = 100f
val dependencies = parent.getDependencies(view)
dependencies.forEach { dependency ->
if (dependency is SwipeRefreshLayout) {
val recycler = (dependency.findViewById<RecyclerView>(R.id.chatRoomRecycler))
val offset: Int = recycler.computeVerticalScrollOffset()
val extent: Int = recycler.computeVerticalScrollExtent()
val range: Int = recycler.computeVerticalScrollRange()
percentage = if (range - extent == 0) 100f else 100.0f * offset / (range - extent).toFloat()
}
}
return percentage
}
}
So the FloatinActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/bottomScrollFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="12dp"
android:src="#drawable/ic_arrow_down"
app:layout_behavior=".ui.views.ReverseHideViewOnScrollBehaviour"
app:layout_anchor="#id/swipeChatRoomRecycler"
app:layout_anchorGravity="bottom|right|end"
app:fabSize="mini" />

How to bring a view to front when doing animation?

Right now I am implementing a feature where I need to animate any ImageView in the app to full screen when it is clicked. And when back it hit, a reverse animation will be applied to restored the ImageView to original position. The ImageView can be anywhere in the view tree. To simplify the description, consider this layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="#+id/image1"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:src="#mipmap/ic_launcher"/>
<ImageView
android:id="#+id/image2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:layout_marginTop="100dp"
android:src="#mipmap/ic_launcher"/>
<FrameLayout
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="100dp"
android:layout_gravity="center">
<ImageView
android:id="#+id/image3"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:src="#mipmap/ic_launcher"/>
</FrameLayout>
</LinearLayout>
And the corresponding kotlin file:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.image2).setOnClickListener {
it.animate().scaleX(5.0f).scaleY(5.0f).setDuration(5000L).start()
}
findViewById<View>(R.id.image3).setOnClickListener {
it.animate().scaleX(5.0f).scaleY(5.0f).setDuration(5000L).start()
}
}
}
Here when we click image2 or image3 we want it to scale up. However the result is not what we want. For image2 it will be covered by image3 after scaling up. For image3 it will simply be clipped:
image2 covered by image3
image3 clipped
What's the right way to temporarily bring the ImageView to front?
Try this it would help you
class BottomNavigationExample : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bottom_navigation_example)
findViewById<View>(R.id.image2).setOnClickListener {
it.animate().scaleX(5.0f).scaleY(5.0f).setDuration(5000L).start()
setElevation(it)
}
findViewById<View>(R.id.image3).setOnClickListener {
it.animate().scaleX(5.0f).scaleY(5.0f).setDuration(5000L).start()
setElevation(it)
}
}
fun setElevation(view: View) {
ViewCompat.setElevation(image1, 0f);
ViewCompat.setElevation(image2, 0f);
ViewCompat.setElevation(image3, 0f);
ViewCompat.setElevation(view, 5f);
}
}
You can use something like this..
class MainActivity : FragmentActivity() {
private var currentAnimator: Animator? = null
private var shortAnimationDuration: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val thumbView: View = findViewById(R.id.thumb_button_1)
val thumbView2: View = findViewById(R.id.thumb_button_2)
thumbView.setOnClickListener { zoomImageFromThumb(thumb_button_1, R.drawable.image1) }
thumbView2.setOnClickListener { zoomImageFromThumb(thumb_button_2, R.drawable.girls2) }
shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime)
}
private fun zoomImageFromThumb(thumbView: View, imageResId: Int) {
currentAnimator?.cancel()
val expandedImageView: ImageView = findViewById(R.id.expanded_image)
expandedImageView.setImageResource(imageResId)
val startBoundsInt = Rect()
val finalBoundsInt = Rect()
val globalOffset = Point()
thumbView.getGlobalVisibleRect(startBoundsInt)
findViewById<View>(R.id.container)
.getGlobalVisibleRect(finalBoundsInt, globalOffset)
startBoundsInt.offset(-globalOffset.x, -globalOffset.y)
finalBoundsInt.offset(-globalOffset.x, -globalOffset.y)
val startBounds = RectF(startBoundsInt)
val finalBounds = RectF(finalBoundsInt)
val startScale: Float
if ((finalBounds.width() / finalBounds.height() > startBounds.width() / startBounds.height())) {
// Extend start bounds horizontally
startScale = startBounds.height() / finalBounds.height()
val startWidth: Float = startScale * finalBounds.width()
val deltaWidth: Float = (startWidth - startBounds.width()) / 2
startBounds.left -= deltaWidth.toInt()
startBounds.right += deltaWidth.toInt()
} else {
// Extend start bounds vertically
startScale = startBounds.width() / finalBounds.width()
val startHeight: Float = startScale * finalBounds.height()
val deltaHeight: Float = (startHeight - startBounds.height()) / 2f
startBounds.top -= deltaHeight.toInt()
startBounds.bottom += deltaHeight.toInt()
}
thumbView.alpha = 0f
expandedImageView.visibility = View.VISIBLE
expandedImageView.pivotX = 0f
expandedImageView.pivotY = 0f
currentAnimator = AnimatorSet().apply {
play(
ObjectAnimator.ofFloat(
expandedImageView,
View.X,
startBounds.left,
finalBounds.left
)
).apply {
with(
ObjectAnimator.ofFloat(
expandedImageView,
View.Y,
startBounds.top,
finalBounds.top
)
)
with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f))
with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f))
}
duration = shortAnimationDuration.toLong()
interpolator = DecelerateInterpolator()
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
currentAnimator = null
}
override fun onAnimationCancel(animation: Animator) {
currentAnimator = null
}
})
start()
}
expandedImageView.setOnClickListener {
currentAnimator?.cancel()
currentAnimator = AnimatorSet().apply {
play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left)).apply {
with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top))
with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale))
with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale))
}
duration = shortAnimationDuration.toLong()
interpolator = DecelerateInterpolator()
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
thumbView.alpha = 1f
expandedImageView.visibility = View.GONE
currentAnimator = null
}
override fun onAnimationCancel(animation: Animator) {
thumbView.alpha = 1f
expandedImageView.visibility = View.GONE
currentAnimator = null
}
})
start()
}
}
}
}
layout
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<ImageButton
android:id="#+id/thumb_button_1"
android:layout_width="100dp"
android:layout_height="75dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:background="#color/colorWhite"
android:contentDescription="#null"
android:scaleType="centerInside"
android:src="#drawable/image1" />
<ImageButton
android:id="#+id/thumb_button_2"
android:layout_width="100dp"
android:layout_height="75dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:background="#color/colorWhite"
android:contentDescription="#null"
android:scaleType="centerInside"
android:src="#drawable/girls2" />
</LinearLayout>
<ImageView
android:id="#+id/expanded_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="#null"
android:visibility="invisible" />

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

Expanding And Collapsing Toolbar In 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

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

Categories

Resources