如何模仿Google地图的底层3阶段行为?

背景

我被分配到创build一个UI,其行为类似于Google地图显示search结果的底部表单。

它有三个不同的阶段:

  1. 底部内容。 上部区域仍然是可触摸的,不会在底部滚动任何东西
  2. 全屏幕的内容,而上面的区域有一个大头。
  3. 全屏幕的内容,而上面的区域只有工具栏。

以下是我在Google地图上讨论的内容:

在这里输入图像说明

问题

事情是,底部表不是devise图书馆的一部分(虽然这是要求, 在这里 )。

不仅如此,UI看起来相当复杂,需要在多个阶段处理工具栏。

我试过了

我已经find了一个好的(足够的)底部图纸库( 在这里 ),并添加了内容到它的片段样本,与材料devise样本(如这里 )上显示的大致相同的视图,有一个CollapsingToolbarLayout将小心阶段2 + 3。

在我制作的应用程序中,我也必须在滚动时移动一个图标,但是我认为如果我能够成功完成其他任务,这应该很简单。 代码如下:

fragment_my.xml

<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout android:id="@+id/main_content" 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/appbar" android:layout_width="match_parent" android:layout_height="@dimen/detail_backdrop_height" 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:expandedTitleMarginEnd="64dp" app:expandedTitleMarginStart="48dp" app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"> <ImageView android:id="@+id/backdrop" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </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" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingTop="24dp"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/card_margin"> <LinearLayout style="@style/Widget.CardContent" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Info" android:textAppearance="@style/TextAppearance.AppCompat.Title"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/cheese_ipsum"/> </LinearLayout> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/card_margin" android:layout_marginLeft="@dimen/card_margin" android:layout_marginRight="@dimen/card_margin"> <LinearLayout style="@style/Widget.CardContent" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Friends" android:textAppearance="@style/TextAppearance.AppCompat.Title"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/cheese_ipsum"/> </LinearLayout> </android.support.v7.widget.CardView> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/card_margin" android:layout_marginLeft="@dimen/card_margin" android:layout_marginRight="@dimen/card_margin"> <LinearLayout style="@style/Widget.CardContent" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Related" android:textAppearance="@style/TextAppearance.AppCompat.Title"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/cheese_ipsum"/> </LinearLayout> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="@dimen/fab_margin" android:clickable="true" android:src="@android:drawable/ic_menu_send" app:layout_anchor="@id/appbar" app:layout_anchorGravity="bottom|right|end"/> </android.support.design.widget.CoordinatorLayout> 

MyFragment.java

 public class MyFragment extends BottomSheetFragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_my, container, false); view.setMinimumHeight(getResources().getDisplayMetrics().heightPixels); CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) view.findViewById(R.id.collapsing_toolbar); collapsingToolbar.setTitle("AAA"); final Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar); final AppCompatActivity activity = (AppCompatActivity) getActivity(); activity.setSupportActionBar(toolbar); activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); //toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { NavUtils.navigateUpFromSameTask(getActivity()); } }); final ImageView imageView = (ImageView) view.findViewById(R.id.backdrop); Glide.with(this).load(R.drawable.cheese_1).centerCrop().into(imageView); return view; } } 

BottomSheetFragmentActivity.java

 public final class BottomSheetFragmentActivity extends AppCompatActivity { protected BottomSheetLayout bottomSheetLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bottom_sheet_fragment); bottomSheetLayout = (BottomSheetLayout) findViewById(R.id.bottomsheet); findViewById(R.id.bottomsheet_fragment_button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new MyFragment().show(getSupportFragmentManager(), R.id.bottomsheet); } }); bottomSheetLayout.setShouldDimContentView(false); bottomSheetLayout.setPeekOnDismiss(true); bottomSheetLayout.setPeekSheetTranslation(200); bottomSheetLayout.setInterceptContentTouch(false); bottomSheetLayout.setDefaultViewTransformer(new BaseViewTransformer() { @Override public void transformView(final float translation, final float maxTranslation, final float peekedTranslation, final BottomSheetLayout parent, final View view) { Log.d("AppLog", "translation:" + translation + " maxTranslation:" + maxTranslation + " peekedTranslation:" + peekedTranslation); } }); } } 

它几乎运作良好。 唯一的问题是从#3过渡到#2:

在这里输入图像说明

这个问题

代码有什么问题? 我能做些什么才能达到要求的行为?

注意 :请阅读底部的编辑


好的,我已经find了一个办法,但是我不得不改变多个类的代码,这样底部表单就会知道appBarLayout的状态(不pipe是否展开),并且忽略向上滚动不扩展:

BottomSheetLayout.java

添加字段:

 private AppBarLayout mAppBarLayout; private OnOffsetChangedListener mOnOffsetChangedListener; private int mAppBarLayoutOffset; 

init() – 添加了这个:

  mOnOffsetChangedListener = new OnOffsetChangedListener() { @Override public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) { mAppBarLayoutOffset = verticalOffset; } }; 

增加了设置appBarLayout的function:

 public void setAppBarLayout(final AppBarLayout appBarLayout) { if (mAppBarLayout == appBarLayout) return; if (mAppBarLayout != null) mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener); mAppBarLayout = appBarLayout; mAppBarLayout.addOnOffsetChangedListener(mOnOffsetChangedListener); } 

onDetachedFromWindow() – 增加了这个:

  if (mAppBarLayout != null) mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener); 

onTouchEvent() – 添加了这个:

  ... if (bottomSheetOwnsTouch) { if (state == State.EXPANDED && scrollingDown && mAppBarLayout != null && mAppBarLayoutOffset != 0) { event.offsetLocation(0, sheetTranslation - getHeight()); getSheetView().dispatchTouchEvent(event); return true; } ... 

这些是主要的变化。 现在为什么设置他们:

MyFragment.java

onCreateView() – 增加了这个:

  mBottomSheetLayout.setAppBarLayout((AppBarLayout) view.findViewById(R.id.appbar)); 

我还加了这个function:

  public void setBottomSheetLayout(final BottomSheetLayout bottomSheetLayout) { mBottomSheetLayout = bottomSheetLayout; } 

现在这就是活动如何告诉关于appBarLayout的片段:

  final MyFragment myFragment = new MyFragment(); myFragment.setBottomSheetLayout(bottomSheetLayout); myFragment.show(getSupportFragmentManager(), R.id.bottomsheet); 

项目现在在GitHub上可用:

https://github.com/AndroidDeveloperLB/ThreePhasesBottomSheet

我希望它没有任何错误。


解决scheme有一个错误,可悲的是,所以我不会把这个答案标记为正确的:

  1. 它只适用于Android 6及以上版本。 其他人有一个奇怪的行为,显示底部工作表扩展了很短的一段时间,每次显示它。
  2. 方向更改根本不保存滚动的状态,所以我禁用了它。
  3. 能够在底部表单的内容中滚动的罕见问题(底部)
  4. 如果以前显示过键盘,底部表格可能会在试图被窥视时变成全屏。

如果有人可以帮忙,请做。


对于问题#1,我试过在底部表单不被窥视的情况下将可见性设置为INVISIBLE来添加修补程序,但它并不总是有效,特别是在显示键盘的情况下。


对于问题1,我发现如何解决这个问题,只需要用CoordinateLayout(在“fragment_my.xml”)中包含任何你希望使用的视图(我使用了FrameLayout),并把一个全尺寸的视图放入它(我只是把“视图”),如:

 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!--This full sized view, together with the FrameLayout above, are used to handle some weird UI issues on pre-Android-6 --> <View android:layout_width="match_parent" android:layout_height="match_parent"/> <...CollapsingToolbarLayout ... 

当我把CoordinatorLayout作为它的视图时,可能会混淆bottomSheet。 我已经更新了这个项目,但是,如果有什么办法可以有更好的解决scheme,我想知道。


最近几个月,谷歌已经发布了自己的bottomSheet类,但是由于我发现它有很多问题,所以我甚至无法尝试。

大更新

因为有同样的话题有4或5个问题,但有不同的要求,我试图回答所有这些问题,但一个非礼貌的pipe理员删除/closures他们,使我为每一个创build一张票,并改变他们避免“复制粘贴”我会让你链接到完整的答案,在那里你可以find所有关于如何获得像谷歌地图的完整行为的解释。


回答你的问题

如何模仿Google地图的底层3阶段行为?

使用支持库23.x.x +你可以修改默认的BottomSheetBehavior ,通过以下步骤添加一个属性:

  1. 创build一个Java类并从CoordinatorLayout.Behavior<V>扩展它
  2. 将粘贴代码从默认的BottomSheetBehavior文件复制到新的。
  3. 使用以下代码修改方法clampViewPositionVertical
  4. 添加一个新的状态:

     public static final int STATE_ANCHOR_POINT = X; 
  5. 修改下面的方法: onLayoutChildonStopNestedScrollBottomSheetBehavior<V> from(V view)setState (可选)

我将添加这些修改的方法和一个链接到示例项目 。

下面是它的样子:
[ CustomBottomSheetBehavior ]

你试过这个吗? http://android-developers.blogspot.in/2016/02/android-support-library-232.html?m=1在这里它说,我们可以只指定底部图纸布局的行为。;

更新:

基本上链接状态 –

通过将BottomSheetBehavior附加到CoordinatorLayout的子视图(即添加app:layout_behavior =“android.support.design.widget.BottomSheetBehavior”),您将自动获得适当的触摸检测以在五种状态之间转换:

 STATE_COLLAPSED: this collapsed state is the default and shows just a portion of the layout along the bottom. The height can be controlled with the app:behavior_peekHeight attribute (defaults to 0) STATE_DRAGGING: the intermediate state while the user is directly dragging the bottom sheet up or down STATE_SETTLING: that brief time between when the View is released and settling into its final position STATE_EXPANDED: the fully expanded state of the bottom sheet, where either the whole bottom sheet is visible (if its height is less than the containing CoordinatorLayout) or the entire CoordinatorLayout is filled STATE_HIDDEN: disabled by default (and enabled with the app:behavior_hideable attribute), enabling this allows users to swipe down on the bottom sheet to completely hide the bottom sheet Keep in mind that scrolling containers in your bottom sheet must support nested scrolling (for example, NestedScrollView, RecyclerView, or ListView/ScrollView on API 21+). 

如果您希望收到状态更改的callback,可以添加一个BottomSheetCallback:

 // The View with the BottomSheetBehavior View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet); BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet); behavior.setBottomSheetCallback(new BottomSheetCallback() { @Override public void onStateChanged(@NonNull View bottomSheet, int newState) { // React to state change } @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { // React to dragging events } }); 

虽然BottomSheetBehavior捕获持久性底部工作表案例,但此版本还提供了BottomSheetDialog和BottomSheetDialogFragment来填充模式底部工作表用例。 只需将AppCompatDialog或AppCompatDialogFragmentreplace为它们的底部表单等效项即可将对话框样式化为底部表单。