如何模仿谷歌地图的底页 3 阶段行为? [英] How to mimic Google Maps' bottom-sheet 3 phases behavior?

本文介绍了如何模仿谷歌地图的底页 3 阶段行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

我被指派制作一个 UI,其行为类似于 Google 地图显示已找到结果的底部表格的方式.

它分为三个不同的阶段:

  1. 底部内容.上部区域仍可触摸,底部不会滚动任何内容
  2. 全屏内容,而上部区域有一个大标题.
  3. 全屏内容,而上部区域只有工具栏.

这是我在 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

我希望它没有任何错误.


遗憾的是,该解决方案存在错误,因此我不会将此答案标记为正确答案:

  1. 它仅适用于 Android 6 及更高版本.其他人有一种奇怪的行为,每次显示底部工作表时,它都会显示一小部分时间.
  2. 方向更改根本不会保存滚动状态,因此我已将其禁用.
  3. 在底部工作表仍处于折叠状态(底部)时能够在底部工作表的内容内滚动的罕见问题
  4. 如果之前显示了键盘,则在尝试查看时底部工作表可能会全屏显示.

如果有人可以帮忙,请帮忙.


对于问题 #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:

  1. Bottom content. The upper area is still touchable and won't scroll anything at the bottom
  2. Full-screen content, while the upper area has a large header.
  3. 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:

  1. 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.
  2. Orientation changes do not save the state of the scrolling at all, so I've disabled it.
  3. Rare issue of being able to scroll inside the bottom sheet's content while it's still collapsed (at the bottom)
  4. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆