如何模仿谷歌地图的底页 3 阶段行为? [英] How to mimic Google Maps' bottom-sheet 3 phases behavior?
问题描述
背景
我被指派制作一个 UI,其行为类似于 Google 地图显示已找到结果的底部表格的方式.
它分为三个不同的阶段:
- 底部内容.上部区域仍可触摸,底部不会滚动任何内容
- 全屏内容,而上部区域有一个大标题.
- 全屏内容,而上部区域只有工具栏.
这是我在 Google 地图上谈论的内容:
问题
事实是,底部工作表还不是设计库的一部分(尽管有人要求,
问题
代码有什么问题?我该怎么做才能实现所需的行为?
注意:阅读底部的编辑
好的,我找到了一种方法,但我不得不更改多个类的代码,以便底部工作表知道 appBarLayout 的状态(展开与否),并忽略向上滚动如果没有展开:
BottomSheetLayout.java
添加的字段:
private AppBarLayout mAppBarLayout;私人 OnOffsetChangedListener mOnOffsetChangedListener;私有 int mAppBarLayoutOffset;
init() - 添加了这个:
mOnOffsetChangedListener = new OnOffsetChangedListener() {@覆盖public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {mAppBarLayoutOffset = verticalOffset;}};
添加了设置appBarLayout的函数:
public void setAppBarLayout(final AppBarLayout appBarLayout) {如果(mAppBarLayout == appBarLayout)返回;如果(mAppBarLayout != null)mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);mAppBarLayout = appBarLayout;mAppBarLayout.addOnOffsetChangedListener(mOnOffsetChangedListener);}
onDetachedFromWindow() - 添加了这个:
if (mAppBarLayout != null)mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);
onTouchEvent() - 添加了这个:
<代码> ...如果(bottomSheetOwnsTouch){if (state == State.EXPANDED && scrollingDown && mAppBarLayout != null && mAppBarLayoutOffset != 0) {event.offsetLocation(0, sheetTranslation - getHeight());getSheetView().dispatchTouchEvent(event);返回真;}...
这些是主要的变化.现在是什么设置它们:
MyFragment.java
onCreateView() - 添加了这个:
mBottomSheetLayout.setAppBarLayout((AppBarLayout) view.findViewById(R.id.appbar));
我也添加了这个功能:
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
我希望它没有任何错误.
遗憾的是,该解决方案存在错误,因此我不会将此答案标记为正确答案:
- 它仅适用于 Android 6 及更高版本.其他人有一种奇怪的行为,每次显示底部工作表时,它都会显示一小部分时间.
- 方向更改根本不会保存滚动状态,因此我已将其禁用.
- 在底部工作表仍处于折叠状态(底部)时能够在底部工作表的内容内滚动的罕见问题
- 如果之前显示了键盘,则在尝试查看时底部工作表可能会全屏显示.
如果有人可以帮忙,请帮忙.
对于问题 #1,我尝试通过将可见性设置为 INVISIBLE(当底部工作表尚未看到时)来添加修复程序,但它并不总是有效,尤其是在显示键盘的情况下.
对于问题 #1,我已经找到了解决方法,只需将 CoordinatorLayout 包装(在fragment_my.xml"中)您希望使用的任何视图(我使用的是 FrameLayout),并且还放置了一个完整的- 大小的视图(我只是放了视图"),如下所示:
<!--这个全尺寸视图与上面的 FrameLayout 一起用于处理 Android-6 之前的一些奇怪的 UI 问题--><查看android:layout_width=match_parent"android:layout_height=match_parent"/><...折叠工具栏布局...
当我将 CoordinatorLayout 作为它的视图时,它可能混淆了 bottomSheet.我已经更新了项目,但如果有任何更好的解决方案,我想知道.
最近几个月,Google 发布了自己的bottomSheet 类,但我发现它有很多问题,所以我什至无法尝试.
Background
I'm assigned to make a UI that behaves similarly to how Google Maps shows a bottom-sheet for a found result.
It has three different phases:
- Bottom content. The upper area is still touchable and won't scroll anything at the bottom
- Full-screen content, while the upper area has a large header.
- Full-screen content, while the upper area has just the toolbar.
Here's what I'm talking about on Google Maps:
The problem
Thing is, the bottom sheet isn't a part of the design library yet (though it was requested, here).
Not only that, but the UI seems quite complex and needs handling of the toolbar on multiple phases.
What I've tried
I've found a good (enough) library for the bottom sheet (here), and added content to its fragment sample, to have about the same views as shown on material design samples (like here), to have a CollapsingToolbarLayout that will take care of phases 2+3.
In the app I'm making, I also have to move an icon as you scroll, but I think that if I succeed with the rest, this should be easy. Here's the code:
###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);
}
});
}
}
It almost works well. The only problem is the transition from #3 back to #2:
The question
What is wrong with the code? What can I do in order to achieve the required behavior?
Note: Read the edits at the bottom
OK, I've found a way to do it, but I had to change the code of multiple classes, so that the bottom sheet would know of the state of the appBarLayout (expanded or not), and ignore scroll-up in case it's not expanded:
BottomSheetLayout.java
Added fields:
private AppBarLayout mAppBarLayout;
private OnOffsetChangedListener mOnOffsetChangedListener;
private int mAppBarLayoutOffset;
init() - added this:
mOnOffsetChangedListener = new OnOffsetChangedListener() {
@Override
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) {
mAppBarLayoutOffset = verticalOffset;
}
};
Added function to set the appBarLayout:
public void setAppBarLayout(final AppBarLayout appBarLayout) {
if (mAppBarLayout == appBarLayout)
return;
if (mAppBarLayout != null)
mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);
mAppBarLayout = appBarLayout;
mAppBarLayout.addOnOffsetChangedListener(mOnOffsetChangedListener);
}
onDetachedFromWindow() - added this:
if (mAppBarLayout != null)
mAppBarLayout.removeOnOffsetChangedListener(mOnOffsetChangedListener);
onTouchEvent() - added this:
...
if (bottomSheetOwnsTouch) {
if (state == State.EXPANDED && scrollingDown && mAppBarLayout != null && mAppBarLayoutOffset != 0) {
event.offsetLocation(0, sheetTranslation - getHeight());
getSheetView().dispatchTouchEvent(event);
return true;
}
...
Those were the main changes. Now for what sets them:
MyFragment.java
onCreateView() - added this:
mBottomSheetLayout.setAppBarLayout((AppBarLayout) view.findViewById(R.id.appbar));
I also added this function:
public void setBottomSheetLayout(final BottomSheetLayout bottomSheetLayout) {
mBottomSheetLayout = bottomSheetLayout;
}
Now this is how the activity tells the fragment about the appBarLayout:
final MyFragment myFragment = new MyFragment();
myFragment.setBottomSheetLayout(bottomSheetLayout);
myFragment.show(getSupportFragmentManager(), R.id.bottomsheet);
The project is now available on GitHub:
https://github.com/AndroidDeveloperLB/ThreePhasesBottomSheet
I hope it doesn't have any bugs.
The solution has bugs, sadly, so I won't mark this answer as the correct one:
- It only works well on Android 6 and above. Others have a weird behavior of showing the bottom sheet expanded for a tiny fraction of a time, each time when showing it.
- Orientation changes do not save the state of the scrolling at all, so I've disabled it.
- Rare issue of being able to scroll inside the bottom sheet's content while it's still collapsed (at the bottom)
- If a keyboard was shown before, the bottom sheet might get to be full screen when trying to peek.
If anyone can help with it, please do.
For issue #1, I've tried adding a fix by setting the visibility to INVISIBLE when the bottom sheet doesn't peek yet, but it doesn't always work, especially if a keyboard is shown.
For issue #1, I've found how to fix it, by just wrapping (in "fragment_my.xml") the CoordinatorLayout with any view that you wish to use (I used FrameLayout), and also put a full-sized view in it (I just put "View") , as such:
<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
...
It probably confused the bottomSheet when I had the CoordinatorLayout being its view. I've updated the project, but still, if there is any way to have a nicer solution, I'd like to know about it.
In recent months, Google has published its own bottomSheet class, but as I've found it has a lot of issues, so I can't even try it out.
这篇关于如何模仿谷歌地图的底页 3 阶段行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!