【问题标题】:How to move a view above Snackbar just like FloatingButton如何像浮动按钮一样在 Snackbar 上方移动视图
【发布时间】:2016-01-17 23:12:17
【问题描述】:

当出现 Snackbar 时,我想要向上移动一个线性布局。

我看到了很多如何使用 FloatingButton 执行此操作的示例,但是常规视图呢?

【问题讨论】:

  • @Mygod 你应该添加这个作为答案
  • @もっくん 我做到了。 Stackoverflow 自动将其转换为评论,因为它非常简短和优雅。 :)
  • 我应该在哪里添加这个@Mygod

标签: android android-snackbar


【解决方案1】:

我将详细说明批准的答案,因为我认为有一个比那篇文章提供的更简单的实现。

我无法找到处理通用视图移动的内置行为,但这是一个很好的通用选项(来自http://alisonhuang-blog.logdown.com/posts/290009-design-support-library-coordinator-layout-and-behavior 在另一条评论中链接):

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();
    }
}

然后,在你的布局文件中添加一个 layout_behavior 如下:

<LinearLayout
    android:id="@+id/main_content"
    android:orientation="vertical"
    app:layout_behavior="com.example.MoveUpwardBehavior"/>

其中 layout_behavior 是自定义行为的完整路径。除非您特别需要默认行为,否则无需继承 LinearLayout,这似乎并不常见。

【讨论】:

  • 这个解决方案不需要子类化目标视图,我觉得这太过分了。此外,内联解释答案通常是最佳做法,因为链接往往会在一段时间后消失。
  • 很好的解决方案。要同时支持快速关闭小吃栏,请添加以下代码:@Override public void onDependentViewRemoved ( CoordinatorLayout parent, View child, View dependency ) { super.onDependentViewRemoved(parent, child, dependency); child.setTranslationY(0); }
  • 如果您想要依赖视图的实际动画,请使用child.animate().translationY(0) 而不是child.setTranslation(0)
  • Snackbar.make(..) 传递给View 也很重要,即CoordinatorLayoutCoordinatorLayout 的子代。出于某种原因,我通过了 getRootView(),这使得 layoutDependsOn(..) 永远不会返回 true。
  • ViewCompat.getTranslationY(dependency) 现在已弃用,请改用dependency.getTranslationY();同样使用child.setTranslationY(translationY)
【解决方案2】:

基于@Travis Castillo 的回答。修复了以下问题:

  • 向上移动整个布局并导致视图顶部的对象消失。
  • 当 SnackBars 紧随其后显示时,不会向上推布局。

所以这里是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();

    }
}

此代码会提升用户在屏幕上看到的内容,而且用户可以在 SnackBar 显示时访问您布局中的所有对象。

如果您希望 SnackBar 覆盖对象而不是推送,并且用户确实可以访问所有对象,那么您需要更改方法 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;
}

和方法onDependentViewRemoved:

@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {

    //Reset paddings and translationY to its default
    child.setPadding(0, 0, 0, 0);

}

不幸的是,当用户滑动删除SnackBar 时,您将失去动画。而且您必须使用ValueAnimator 类为填充更改制作动画,这会在此处产生一些冲突,并且您必须对其进行调试。

https://developer.android.com/reference/android/animation/ValueAnimator.html

任何关于滑动删除SnackBar动画的评论表示赞赏。

如果您可以跳过该动画,那么您可以使用它。

无论如何,我推荐第一种。

【讨论】:

  • 这个答案非常好。我看到一种情况,它的表现与我的预期不同。如果我嵌套&lt;CoordinatorLayout&gt;&lt;FrameLayout app:layout_behavior="com.example.MoveUpwardBehavior"&gt;&lt;ScrollView&gt;&lt;LinearLayout&gt;,当 SnackBar 向上推包含 FrameLayout 时,ScrollView 会笨拙地滚动。有了 Travis Castello 的回答,它就被推高了。
  • +1 对此解决方案。我试图在 BottomSheetDialogFragment 中显示一个 Snackbar,但其他解决方案均无效。这个工作完美。谢谢!!!
【解决方案3】:

您需要向您的 LinearLayout 添加一个行为并将其嵌入到 CoordinatorLayout 中。您可能想阅读以下内容:http://alisonhuang-blog.logdown.com/posts/290009-design-support-library-coordinator-layout-and-behavior

【讨论】:

    【解决方案4】:

    我实现了这一点,发现当小吃店消失时,小吃店位置的视图仍然是空白,显然如果设备上的动画已被禁用,这就是已知的。

    为了解决这个问题,我更改了 onDependentViewChanged 方法以存储此行为所附加到的视图的初始 Y 位置。然后在删除快餐栏时将该视图的位置重置为存储的 Y 位置

    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);
    }
    

    【讨论】:

    • Travis Castillo 的答案在显示小吃店时出现故障,这个答案非常流畅。
    【解决方案5】:

    除了特拉维斯卡斯蒂略的回答: 要允许触发连续的 SnackBars,在onDependentViewChanged() 内,您必须取消由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;
    }
    

    如果不取消,当一个 SnackBar 被另一个 SnackBar 替换时,LinearLayout 将跳到第二个 SnackBar 下方。

    【讨论】:

      【解决方案6】:

      我编写了一个库,可以添加其他视图以使用 SnackProgressBar 进行动画处理。它还包括progressBar 和其他东西。试试看https://github.com/tingyik90/snackprogressbar

      假设您有以下要制作动画的视图。

      View[] views = {view1, view2, view3};
      

      在您的 Activity 中创建 SnackProgressBarManager 的实例,并包含要制作动画的视图。

      SnackProgressBarManager snackProgressBarManager = new SnackProgressBarManager(rootView)
              .setViewsToMove(views)
      

      当 SnackProgressBar 显示或关闭时,这些视图将相应地进行动画处理。

      【讨论】:

        【解决方案7】:

        Travis Castillo 的回答进一步改进。
        这修复了 janky 动画 + 转换为 Kotlin。

        无需手动进行 Y 平移,因为它是自动处理的。
        手动进行 Y 平移实际上会使动画看起来很卡。

        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)
            }
        }
        

        【讨论】:

          【解决方案8】:

          @Markymark 提出了很好的解决方案,但是在快餐栏的第一帧有 translateY==0,在第二个 translationY==full height 并开始正确减少,因此依赖布局在第一帧上出现卡顿。这可以通过跳过第一帧 + 恢复原始填充作为良好的副作用来解决。

          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)
                  }
              }
          }
          

          【讨论】:

            【解决方案9】:

            不需要 Co-ordinator 布局 使用常规视图将小吃栏对齐到视图的底部,然后将按钮放在其顶部,点击任何您的逻辑按钮显示小吃吧或线性布局。

            <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>
            

            【讨论】:

            • 我不知道你以前是否使用过snackbar,但这不是一个常见的实现。通常它是在运行时添加的动态警报。如果这是一种对您有用的技术,请尝试更具体,而不是在“snackbar code”中进行评论,这可以说是布局中更重要的部分之一。
            猜你喜欢
            • 1970-01-01
            • 2015-10-23
            • 2023-04-07
            • 1970-01-01
            • 2019-11-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多