Android - 如何在底部工作表覆盖另一个视图时添加视差效果

9
使用来自Google设计库的BottomSheetBehavior,默认行为是底部工作表在扩展时“覆盖”同一CoordinatorLayout中的其他视图。我可以将类似FAB(或具有适当定义的CoordinatorLayout.Behavior的其他视图)锚定到表单顶部,并在表单扩展时将其向上滑动,这很好,但我想要的是在底部表单扩展时使视图“折叠”,显示视差效果。
Google Maps中的此效果与我要寻找的相似;它以视差效果开始,然后在达到某个滚动位置后切换回仅将底部表单“覆盖”地图:

enter image description here

我尝试过的一件事(虽然我从一开始就怀疑它不会起作用),是在BottomSheetBehavior.BottomSheetCallbackonSlide调用中以编程方式设置上部视图的高度。 这有些成功,但移动不像在Google Maps中那样平滑。

如果有人知道如何实现此效果,我将非常感激!


一些可以帮助的 - Lalit Fauzdar
2个回答

14

经过更多的尝试/研究,我从这篇帖子中意识到 如何为google MapView创建具有视差滚动效果的自定义CoordinatorLayout.Behavior?问题的一个重要部分是不理解视差效果,它会使视图移动而不是缩小它们。一旦我意识到这一点,创建一个自定义行为应用视差到我的主视图时底部表单扩展就变得微不足道了:

public class CollapseBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V>{


  public CollapseBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  @Override
  public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
    if (isBottomSheet(dependency)) {
        BottomSheetBehavior behavior = ((BottomSheetBehavior) ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior());

        int peekHeight = behavior.getPeekHeight();
        // The default peek height is -1, which 
        // gets resolved to a 16:9 ratio with the parent
        final int actualPeek = peekHeight >= 0 ? peekHeight : (int) (((parent.getHeight() * 1.0) / (16.0)) * 9.0);
        if (dependency.getTop() >= actualPeek) {
            // Only perform translations when the 
            // view is between "hidden" and "collapsed" states
            final int dy = dependency.getTop() - parent.getHeight();
            ViewCompat.setTranslationY(child, dy/2);
            return true;
        }
    }

    return false;
  }

  private static boolean isBottomSheet(@NonNull View view) {
    final ViewGroup.LayoutParams lp = view.getLayoutParams();
    if (lp instanceof CoordinatorLayout.LayoutParams) {
        return ((CoordinatorLayout.LayoutParams) lp)
                .getBehavior() instanceof BottomSheetBehavior;
    }
    return false;
  }


}

然后在我的布局XML中,我将主视图的 app:layout_behavior 设为 com.mypackage.CollapseBehavior,将 app:layout_anchor 设为底部表视图,这样就会触发 onDependentViewChanged 回调。这种效果比尝试调整视图大小要平滑得多。我猜返回使用 BottomSheetBehavior.BottomSheetCallback 的初始策略也会与此解决方案类似。

编辑:根据请求,相关的XML如下。我在运行时添加了一个 MapFragment@+id/map_container 中,不过这应该也适用于任何你放入该容器中的东西,比如静态图像。 LocationListFragment 可以被替换为任何视图或片段,只要它仍具有 BottomSheetBehavior

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/fragment_coordinator">
        <FrameLayout
            android:id="@+id/map_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            app:layout_anchor="@+id/list_container"
            app:layout_behavior="com.mypackage.behavior.CollapseBehavior"/>

        <fragment
            android:name="com.mypackage.fragment.LocationListFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/list_container"
            app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>


    </android.support.design.widget.CoordinatorLayout>

我知道这是一段时间以前的事了,但您介意也为这个问题发布您的XML吗? - Pythogen
2
没问题!我已经添加了它。 - Patrick Grayson
谢谢您的帖子,它很有帮助。但是我遇到了一个问题,当我展开底部表单时,Google的标志会被隐藏。我们能解决这个问题吗? - Rashpal Singh
@RashpalSingh 我不是很确定,但如果您使用的是地图sdk v2或更高版本,则可以使用 GoogleMap.setPadding() 来实现此目标,该方法将移动标志和地图视图的其他部分,而不会移动地图本身(请参见 https://dev59.com/5mw05IYBdhLWcg3w9mZW)。这将需要对我上面的代码进行大量更改,因为您需要找到一种方法从 onDependentViewChanged 触发填充变化,但在底部表单扩展时增加填充应该可以保持标志可见。 - Patrick Grayson

4
帕特里克·格雷森(Patrick Grayson)的帖子非常有用。在我的情况下,我确实需要能够调整地图大小的东西。我采用了上面的解决方案来进行调整而不是转换。也许其他人也在寻找类似的解决方案。
public class CollapseBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V> {

private int pixels = NO_RESIZE_BUFFER; // default value, in case getting a value from resources, bites the dust.

private static final int NO_RESIZE_BUFFER = 200; //The amount of dp to not have the bottom sheet ever push away.

public CollapseBehavior(Context context, AttributeSet attrs)
{
    super(context, attrs);
    pixels = (int)convertDpToPixel(NO_RESIZE_BUFFER,context);
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
    // child is the map
    // dependency is the bottomSheet
    if(isBottomSheet(dependency))
    {
        BottomSheetBehavior behavior = ((BottomSheetBehavior) ((CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior());

        int peekHeight;
        if (behavior != null) {
            peekHeight = behavior.getPeekHeight();
        }
        else
            return true;

        if(peekHeight > 0) { // Dodge the case where the sheet is hidden.

            if (dependency.getTop() >= peekHeight) { // Otherwise we'd completely overlap the map

                if(dependency.getTop() >= pixels) { // On resize when we have more than our NO_RESIZE_BUFFER of dp left.
                    if(dependency.getTop() > 0) { // Don't want to map to be gone completely.
                        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
                        params.height = dependency.getTop();
                        child.setLayoutParams(params);
                    }
                    return true;
                }
            }
        }
    }
    return false;
}

private static float convertDpToPixel(float dp, Context context)
{
    float densityDpi = context.getResources().getDisplayMetrics().densityDpi;
    return dp * (densityDpi / DisplayMetrics.DENSITY_DEFAULT);
}

private static boolean isBottomSheet(@NonNull View view)
{
    final ViewGroup.LayoutParams lp = view.getLayoutParams();
    if(lp instanceof CoordinatorLayout.LayoutParams)
    {
        return ((CoordinatorLayout.LayoutParams) lp).getBehavior() instanceof BottomSheetBehavior;
    }
    return false;
}
}

And the layout...

<FrameLayout
    android:id="@+id/flMap"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="top"
    android:layout_margin="0dp"
    app:layout_anchor="@+id/persistentBottomSheet"
    app:layout_behavior="com.yoursite.yourapp.CollapseBehavior">

    <fragment
        android:id="@+id/mapDirectionSummary"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.yoursite.yourapp.YourActivity" />

</FrameLayout>

<android.support.constraint.ConstraintLayout
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="wrap_content"
android:id="@+id/persistentBottomSheet"
app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
app:behavior_hideable="false"
    app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
    tools:context="com.yoursite.yourapp.YourActivity">

<!-- Whatever you want on the bottom sheet. -->

</android.support.constraint.ConstraintLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        app:cardElevation="8dp"
        app:cardBackgroundColor="#324">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
            app:popupTheme="@style/Theme.AppCompat.Light">

            <EditText
                android:id="@+id/txtSearch"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:ems="10"
                android:inputType="text"
                android:maxLines="1" />


        </android.support.v7.widget.Toolbar>
    </android.support.v7.widget.CardView>


</LinearLayout>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接