I got a linear layout that I want to move up when a Snackbar appears.
I saw many examples how to do this with FloatingButton, but what about a regular view?
I got a linear layout that I want to move up when a Snackbar appears.
I saw many examples how to do this with FloatingButton, but what about a regular view?
I'm going to elaborate on the approved answer because I think there's a slightly simpler implementation than that article provides.
I wasn't able to find a built-in behavior that handles a generic moving of views, but this one is a good general purpose option (from http://alisonhuang-blog.logdown.com/posts/290009-design-support-library-coordinator-layout-and-behavior linked in another comment):
import android.content.Context;
import android.support.annotation.Keep;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
@Keep
public class MoveUpwardBehavior extends CoordinatorLayout.Behavior<View> {
public MoveUpwardBehavior() {
super();
}
public MoveUpwardBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, ViewCompat.getTranslationY(dependency) - dependency.getHeight());
ViewCompat.setTranslationY(child, translationY);
return true;
}
//you need this when you swipe the snackbar(thanx to ubuntudroid's comment)
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
ViewCompat.animate(child).translationY(0).start();
}
}
then, in your layout file add a layout_behavior as below:
<LinearLayout
android:id="@+id/main_content"
android:orientation="vertical"
app:layout_behavior="com.example.MoveUpwardBehavior"/>
where the layout_behavior is the full path to your custom behavior. There's no need to subclass LinearLayout unless you have a specific need to have a default behavior, which seems uncommon.
Based on @Travis Castillo answer. Fixed problems such as:
So here is fixed code for MoveUpwardBehavior
Class :
import android.content.Context;
import android.support.annotation.Keep;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
@Keep
public class MoveUpwardBehavior extends CoordinatorLayout.Behavior<View> {
public MoveUpwardBehavior() {
super();
}
public MoveUpwardBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, ViewCompat.getTranslationY(dependency) - dependency.getHeight());
//Dismiss last SnackBar immediately to prevent from conflict when showing SnackBars immediately after eachother
ViewCompat.animate(child).cancel();
//Move entire child layout up that causes objects on top disappear
ViewCompat.setTranslationY(child, translationY);
//Set top padding to child layout to reappear missing objects
//If you had set padding to child in xml, then you have to set them here by <child.getPaddingLeft(), ...>
child.setPadding(0, -Math.round(translationY), 0, 0);
return true;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
//Reset paddings and translationY to its default
child.setPadding(0, 0, 0, 0);
ViewCompat.animate(child).translationY(0).start();
}
}
This codes pushes up what user sees on screen and besides user have access to all objects in your layout while SnackBar is showing.
If you want the SnackBar cover the objects instead of pushing and besides user do have access to all objects, then you need to change method onDependentViewChanged
:
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, ViewCompat.getTranslationY(dependency) - dependency.getHeight());
//Dismiss last SnackBar immediately to prevent from conflict when showing SnackBars immediately after eachother
ViewCompat.animate(child).cancel();
//Padding from bottom instead pushing top and padding from top.
//If you had set padding to child in xml, then you have to set them here by <child.getPaddingLeft(), ...>
child.setPadding(0, 0, 0, -Math.round(translationY));
return true;
}
and method onDependentViewRemoved
:
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
//Reset paddings and translationY to its default
child.setPadding(0, 0, 0, 0);
}
Unfortunately you will lose animation when user swipe to remove SnackBar
. And you have to use ValueAnimator
class to make animation for padding changes that makes some conflict here and you have to debug them.
https://developer.android.com/reference/android/animation/ValueAnimator.html
Any comment about animation for swipe to remove SnackBar
appreciated.
If you can skip that animation then you can use it.
Anyhow, i recommend first type.
You need to add a behaviour to your LinearLayout and embed it in a CoordinatorLayout. You might want to read this: http://alisonhuang-blog.logdown.com/posts/290009-design-support-library-coordinator-layout-and-behavior
I implemented this and found when the snackbar disappeared the view remained up with white space in the snackbars place, apparently this is known if animations have been disable on the device.
To fix this I changed the onDependentViewChanged method to store the initial Y position of the view this behaviour is attached to. Then on removal of the snackbar reset the position of that view to the stored Y position
private static float initialPositionY;
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
initialPositionY = child.getY();
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
super.onDependentViewRemoved(parent, child, dependency);
child.setTranslationY(initialPositionY);
}
A further improvement Travis Castillo's answer.
This fixes the janky animation + converts to Kotlin.
There is no need to manually do a Y translation since it's handled automatically.
Manually doing the Y translation actually makes the animation look janky.
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.annotation.Keep
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.ViewCompat
import com.google.android.material.snackbar.Snackbar.SnackbarLayout
import kotlin.math.min
import kotlin.math.roundToInt
/**
* To use this, the parent container must be a CoordinatorLayout.
* This can be applied to a child ViewGroup with app:layout_behavior="com.example.MoveUpwardBehavior"
*/
@Keep
class MoveUpwardBehavior(context: Context?, attrs: AttributeSet?) : CoordinatorLayout.Behavior<View>(context, attrs) {
override fun layoutDependsOn(parent: CoordinatorLayout, targetView: View, snackBar: View): Boolean {
return snackBar is SnackbarLayout
}
/**
* @param parent - the parent container
* @param targetView - the view that applies the layout_behavior
* @param snackBar
*/
override fun onDependentViewChanged(parent: CoordinatorLayout, targetView: View, snackBar: View): Boolean {
val bottomPadding = min(0f, snackBar.translationY - snackBar.height).roundToInt()
//Dismiss last SnackBar immediately to prevent from conflict when showing SnackBars immediately after each other
ViewCompat.animate(targetView).cancel()
//Set bottom padding so the target ViewGroup is not hidden
targetView.setPadding(targetView.paddingLeft, targetView.paddingTop, targetView.paddingRight, -bottomPadding)
return true
}
override fun onDependentViewRemoved(parent: CoordinatorLayout, targetView: View, snackBar: View) {
//Reset padding to default value
targetView.setPadding(targetView.paddingLeft, targetView.paddingTop, targetView.paddingRight, 0)
}
}
In addition to Travis Castillo's answer:
To allow triggering consecutive SnackBars, within onDependentViewChanged()
, you have to cancel any possibly ongoing animation started by onDependentViewRemoved()
:
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, ViewCompat.getTranslationY(dependency) - dependency.getHeight());
ViewCompat.animate(child).cancel(); //cancel potential animation started in onDependentViewRemoved()
ViewCompat.setTranslationY(child, translationY);
return true;
}
Without cancelling, the LinearLayout will jump down below the 2nd SnackBar when a SnackBar is replaced by another SnackBar.
I've written a library which additional views can be added to animate with the SnackProgressBar. It also includes progressBar and other stuff. Try it out https://github.com/tingyik90/snackprogressbar
Suppose you have the following views to animate.
View[] views = {view1, view2, view3};
Create an instance of SnackProgressBarManager in your activity and includes the view to animate.
SnackProgressBarManager snackProgressBarManager = new SnackProgressBarManager(rootView)
.setViewsToMove(views)
When a SnackProgressBar is shown or dismissed, these views will be animated accordingly.
@Markymark propose great solution, but on the first frame of snackbar there is translationY==0, on second translationY==full height and start decreasing correctly, so dependent layout janks on the first frame. This can be fixed by skipping first frame + restoring original padding as good side effect.
class MoveUpwardBehavior(context: Context?, attrs: AttributeSet?) : CoordinatorLayout.Behavior<View>(context, attrs), Parcelable {
var originalPadding = -1
constructor(parcel: Parcel) : this(
TODO("context"),
TODO("attrs")
) {
}
override fun layoutDependsOn(parent: CoordinatorLayout, targetView: View, snackBar: View): Boolean {
return snackBar is Snackbar.SnackbarLayout
}
/**
* @param parent - the parent container
* @param targetView - the view that applies the layout_behavior
* @param snackBar
*/
override fun onDependentViewChanged(parent: CoordinatorLayout, targetView: View, snackBar: View): Boolean {
if (originalPadding==-1) {
originalPadding = targetView.paddingBottom
return true
}
val bottomPadding = min(0f, snackBar.translationY - snackBar.height).roundToInt()
// println("bottomPadding: ${snackBar.translationY} ${snackBar.height}")
//Dismiss last SnackBar immediately to prevent from conflict when showing SnackBars immediately after each other
ViewCompat.animate(targetView).cancel()
//Set bottom padding so the target ViewGroup is not hidden
targetView.setPadding(targetView.paddingLeft, targetView.paddingTop, targetView.paddingRight, -(bottomPadding-originalPadding))
return true
}
override fun onDependentViewRemoved(parent: CoordinatorLayout, targetView: View, snackBar: View) {
//Reset padding to default value
targetView.setPadding(targetView.paddingLeft, targetView.paddingTop, targetView.paddingRight, originalPadding)
originalPadding = -1
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<MoveUpwardBehavior> {
override fun createFromParcel(parcel: Parcel): MoveUpwardBehavior {
return MoveUpwardBehavior(parcel)
}
override fun newArray(size: Int): Array<MoveUpwardBehavior?> {
return arrayOfNulls(size)
}
}
}
NO need of Co-ordinator layout using a regular view align the snack bar to the bottom of the view, and place the button on top of it, on click of button of whatever your logic is show snackbar or linear layout.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
/*snackbar code
<LinearLayout
android:id="@+id/linear_snack bar"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_45"
android:layout_alignParentBottom="true"
android:layout_marginTop="@dimen/margin_0"
android:background="@color/dark_grey">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/orange"
android:gravity="center"
android:textColor="@color/white"
android:textSize="@dimen/text_size_h7" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textSize="@dimen/text_size_h7" />
</LinearLayout>
<View android:layout_above="@+id/linear_snack bar"
</RelativeLayout>