android - 使用官方支持库 23.x.+bottomSheet 像谷歌地图一样向上滑动图像

标签 android google-maps parallax bottom-sheet

更新
我想完成与谷歌地图相同的行为 带支持库 23.x.+ 且不带任何第三个库
注意:这不是一个重复的问题,因为:

  • 我想使用行为、支持库而不使用任何 3rd 方库(我在问题标题和上面的描述中添加了它)
  • 我要 所有行为 您在下一个 gif 中看到的其他问题是要求一两个行为并无论如何使用来实现它。

    like you can see in this gif

  • 我已经使用了 Official bottomSheet(甚至在选项卡和 View 寻呼机中)。
    是什么让我发疯是如何实现使用官方bottomSheet向上滑动时从BottomSheet出现的图像行为? .
    我曾尝试使用像 FAB 这样的 anchor 但没有成功。
    我读了一些关于使用滚动监听器的内容,但 ppl 说它不像谷歌地图那样流畅和快速。

    我的 XML(我认为它不会有帮助,但无论如何):
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.design.widget.CoordinatorLayout
        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="match_parent"
        tools:context=".ui.MasterActivity">
    
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:layout_scrollFlags="scroll|enterAlways|snap">
    
                <Button
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    style="?android:attr/borderlessButtonStyle"
                    android:text="Departure"
                    android:layout_gravity="center"
                    android:id="@+id/buttonToolBar"
                    />
    
    
            </android.support.v7.widget.Toolbar>
    
            <android.support.design.widget.TabLayout
                android:id="@+id/tabs"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabBackground="@android:color/white"
                app:tabTextColor="@color/colorAccent"
                app:tabSelectedTextColor="@color/colorAccent"/>
    
        </android.support.design.widget.AppBarLayout>
    
        <android.support.v4.view.ViewPager
            android:id="@+id/viewpager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />
        
        
        <android.support.v4.widget.NestedScrollView
            android:id="@+id/asdf"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:behavior_peekHeight="100dp"
            android:fitsSystemWindows="true"
                app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
    
            <LinearLayout
                android:id="@+id/qwert"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:paddingBottom="16dp"
                android:background="@android:color/white"
                android:padding="15dp">
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="BOOTOMSHEET TITLE"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title" />
    
                <Button
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Button1"/>
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="text 2"
                    android:layout_margin="10dp"/>
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="text 3"
                    android:layout_margin="10dp"/>
    
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="text 4"
                    android:layout_margin="10dp"/>
    
    
                <FrameLayout
                    android:layout_width="match_parent"
                    android:layout_height="320dp"
                    android:background="@color/colorAccent">
    
                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:text="Your remaining content here"
                        android:textColor="@android:color/white" />
    
                </FrameLayout>
            </LinearLayout>
        </android.support.v4.widget.NestedScrollView>
    
    
        <android.support.design.widget.FloatingActionButton
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            app:layout_anchor="@id/asdf"
            app:layout_anchorGravity="top|right|end"
            android:src="@drawable/abc_ic_search_api_mtrl_alpha_copy"
            android:layout_margin="@dimen/fab_margin"
            android:clickable="true"/>
    
    </android.support.design.widget.CoordinatorLayout>
    

    最佳答案

    如果你想使用支持库 23.4.0.+ 来实现它,我会告诉你我是如何得到它以及它是如何工作的。
    据我所知, Activity/fragment 具有以下行为:

  • 2 个带有动画的工具栏,可响应 Bottom Sheet 的移动。
  • 靠近“模态工具栏”(向上滑动时出现的工具栏)时隐藏的 FAB。
  • Bottom Sheet 后面的背景图像,具有某种视差效果。
  • 工具栏中的标题 (TextView) 在 Bottom Sheet 到达时出现。
  • 通知状态栏可以将其背景变为透明或全色。
  • 具有“ anchor 定”状态的自定义 Bottom Sheet 行为。

  • 注意2:这个答案讨论了 6 件事,而不是像其他问题一样关于 1 或 2,你现在能看出区别了吗?
    好的,现在让我们逐一检查:
    工具栏
    当您在谷歌地图中打开该 View 时,您可以看到一个可以搜索的工具栏,这是唯一一个我没有像谷歌地图那样做的,因为我想让它更通用。无论如何,那个ToolBarAppBarLayout 里面当您开始拖动BottomSheet 时它被隐藏,当BottomSheet 到达COLLAPSED 时它再次出现状态。
    要实现它,您需要:
  • 创建 Behavior并从 AppBarLayout.ScrollingViewBehavior 扩展它
  • 覆盖 layoutDependsOnonDependentViewChanged方法。这样做你会听到bottomSheet运动。
  • 创建一些方法来使用动画隐藏和取消隐藏 AppBarLayout/ToolBar。

  • 这是我为第一个工具栏或 ActionBar 所做的:
    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof NestedScrollView;
    }
    
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                                          View dependency) {
    
        if (mChild == null) {
            initValues(child, dependency);
            return false;
        }
    
        float dVerticalScroll = dependency.getY() - mPreviousY;
        mPreviousY = dependency.getY();
    
        //going up
        if (dVerticalScroll <= 0 && !hidden) {
            dismissAppBar(child);
            return true;
        }
    
        return false;
    }
    
    private void initValues(final View child, View dependency) {
    
        mChild = child;
        mInitialY = child.getY();
    
        BottomSheetBehaviorGoogleMapsLike bottomSheetBehavior = BottomSheetBehaviorGoogleMapsLike.from(dependency);
        bottomSheetBehavior.addBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, @BottomSheetBehaviorGoogleMapsLike.State int newState) {
                if (newState == BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED ||
                        newState == BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN)
                    showAppBar(child);
            }
    
            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
    
            }
        });
    }
    
    private void dismissAppBar(View child){
        hidden = true;
        AppBarLayout appBarLayout = (AppBarLayout)child;
        mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_shortAnimTime));
        mToolbarAnimation.y(-(mChild.getHeight()+25)).start();
    }
    
    private void showAppBar(View child) {
        hidden = false;
        AppBarLayout appBarLayout = (AppBarLayout)child;
        mToolbarAnimation = appBarLayout.animate().setDuration(mContext.getResources().getInteger(android.R.integer.config_mediumAnimTime));
        mToolbarAnimation.y(mInitialY).start();
    }
    
    the complete file if you need it

    第二个工具栏或“模态”工具栏:
    您必须覆盖一些方法,但在此方法中,您必须处理更多行为:
  • 显示/隐藏带有动画的工具栏
  • 更改状态栏颜色/背景
  • 在工具栏中显示/隐藏BottomSheet 标题
  • 关闭bottomSheet或将其发送到折叠状态

  • 这个代码有点多,所以我会让 the link

    FAB
    这也是一个自定义行为,但扩展自 FloatingActionButton.Behavior .在 onDependentViewChanged您必须查看它何时到达“offSet”或指向要隐藏它的位置。在我的情况下,我想在它靠近第二个工具栏时隐藏它,所以我深入 FAB 父级(一个 CoordiantorLayout)寻找包含 ToolBar 的 AppBarLayout,然后我使用 ToolBar 位置,如 OffSet :
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
    
        if (offset == 0)
            setOffsetValue(parent);
    
        if (dependency.getY() <=0)
            return false;
    
        if (child.getY() <= (offset + child.getHeight()) && child.getVisibility() == View.VISIBLE)
            child.hide();
        else if (child.getY() > offset && child.getVisibility() != View.VISIBLE)
            child.show();
    
        return false;
    }
    
    Complete Custom FAB Behavior link
    具有视差效果的BottomSheet 后面的图像 :
    像其他人一样,它是一种自定义行为,这个唯一“复杂”的事情是保持图像 anchor 定到BottomSheet并避免图像像默认视差效果一样折叠的小算法:
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                                          View dependency) {
    
        if (mYmultiplier == 0) {
            initValues(child, dependency);
            return true;
        }
    
        float dVerticalScroll = dependency.getY() - mPreviousY;
        mPreviousY = dependency.getY();
    
        //going up
        if (dVerticalScroll <= 0 && child.getY() <= 0) {
            child.setY(0);
            return true;
        }
    
        //going down
        if (dVerticalScroll >= 0 && dependency.getY() <= mImageHeight)
            return false;
    
        child.setY( (int)(child.getY() + (dVerticalScroll * mYmultiplier) ) );
    
        return true;
    }
    

    [具有视差效果的背景图像的完整文件][4]

    现在结束:自定义BottomSheet行为
    要首先实现 3 个步骤,您需要了解默认的 BottomSheetBehavior 有 5 个状态:STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN ,对于谷歌地图行为,你需要在折叠和展开之间添加一个中间状态:STATE_ANCHOR_POINT .
    我尝试扩展默认的 bottomSheetBehavior 没有成功,所以我只是复制粘贴所有代码并修改了我需要的内容。
    要实现我所说的,请执行以下步骤:
  • 创建一个 Java 类并从 CoordinatorLayout.Behavior<V> 扩展它
  • 从默认 BottomSheetBehavior 复制粘贴代码文件到你的新文件。
  • 修改方法clampViewPositionVertical使用以下代码:
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
        return constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset);
    }
    int constrain(int amount, int low, int high) {
        return amount < low ? low : (amount > high ? high : amount);
    }
    
  • 添加新状态
    public static final int STATE_ANCHOR_POINT = X;
  • 修改下一个方法:onLayoutChild , onStopNestedScroll , BottomSheetBehavior<V> from(V view)setState (可选的)
  • public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
        // First let the parent lay it out
        if (mState != STATE_DRAGGING && mState != STATE_SETTLING) {
            if (ViewCompat.getFitsSystemWindows(parent) &&
                    !ViewCompat.getFitsSystemWindows(child)) {
                ViewCompat.setFitsSystemWindows(child, true);
            }
            parent.onLayoutChild(child, layoutDirection);
        }
        // Offset the bottom sheet
        mParentHeight = parent.getHeight();
        mMinOffset = Math.max(0, mParentHeight - child.getHeight());
        mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);
    
        //if (mState == STATE_EXPANDED) {
        //    ViewCompat.offsetTopAndBottom(child, mMinOffset);
        //} else if (mHideable && mState == STATE_HIDDEN...
        if (mState == STATE_ANCHOR_POINT) {
            ViewCompat.offsetTopAndBottom(child, mAnchorPoint);
        } else if (mState == STATE_EXPANDED) {
            ViewCompat.offsetTopAndBottom(child, mMinOffset);
        } else if (mHideable && mState == STATE_HIDDEN) {
            ViewCompat.offsetTopAndBottom(child, mParentHeight);
        } else if (mState == STATE_COLLAPSED) {
            ViewCompat.offsetTopAndBottom(child, mMaxOffset);
        }
        if (mViewDragHelper == null) {
            mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);
        }
        mViewRef = new WeakReference<>(child);
        mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
        return true;
    }
    
    
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
        if (child.getTop() == mMinOffset) {
            setStateInternal(STATE_EXPANDED);
            return;
        }
        if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) {
            return;
        }
        int top;
        int targetState;
        if (mLastNestedScrollDy > 0) {
            //top = mMinOffset;
            //targetState = STATE_EXPANDED;
            int currentTop = child.getTop();
            if (currentTop > mAnchorPoint) {
                top = mAnchorPoint;
                targetState = STATE_ANCHOR_POINT;
            }
            else {
                top = mMinOffset;
                targetState = STATE_EXPANDED;
            }
        } else if (mHideable && shouldHide(child, getYVelocity())) {
            top = mParentHeight;
            targetState = STATE_HIDDEN;
        } else if (mLastNestedScrollDy == 0) {
            int currentTop = child.getTop();
            if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
                top = mMinOffset;
                targetState = STATE_EXPANDED;
            } else {
                top = mMaxOffset;
                targetState = STATE_COLLAPSED;
            }
        } else {
            //top = mMaxOffset;
            //targetState = STATE_COLLAPSED;
            int currentTop = child.getTop();
            if (currentTop > mAnchorPoint) {
                top = mMaxOffset;
                targetState = STATE_COLLAPSED;
            }
            else {
                top = mAnchorPoint;
                targetState = STATE_ANCHOR_POINT;
            }
        }
        if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
            setStateInternal(STATE_SETTLING);
            ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
        } else {
            setStateInternal(targetState);
        }
        mNestedScrolled = false;
    }
    
    public final void setState(@State int state) {
        if (state == mState) {
            return;
        }
        if (mViewRef == null) {
            // The view is not laid out yet; modify mState and let onLayoutChild handle it later
            /**
             * New behavior (added: state == STATE_ANCHOR_POINT ||)
             */
            if (state == STATE_COLLAPSED || state == STATE_EXPANDED ||
                    state == STATE_ANCHOR_POINT ||
                    (mHideable && state == STATE_HIDDEN)) {
                mState = state;
            }
            return;
        }
        V child = mViewRef.get();
        if (child == null) {
            return;
        }
        int top;
        if (state == STATE_COLLAPSED) {
            top = mMaxOffset;
        } else if (state == STATE_ANCHOR_POINT) {
            top = mAnchorPoint;
        } else if (state == STATE_EXPANDED) {
            top = mMinOffset;
        } else if (mHideable && state == STATE_HIDDEN) {
            top = mParentHeight;
        } else {
            throw new IllegalArgumentException("Illegal state argument: " + state);
        }
        setStateInternal(STATE_SETTLING);
        if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
            ViewCompat.postOnAnimation(child, new SettleRunnable(child, state));
        }
    }
    
    
    public static <V extends View> BottomSheetBehaviorGoogleMapsLike<V> from(V view) {
        ViewGroup.LayoutParams params = view.getLayoutParams();
        if (!(params instanceof CoordinatorLayout.LayoutParams)) {
            throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
        }
        CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params)
                .getBehavior();
        if (!(behavior instanceof BottomSheetBehaviorGoogleMapsLike)) {
            throw new IllegalArgumentException(
                    "The view is not associated with BottomSheetBehaviorGoogleMapsLike");
        }
        return (BottomSheetBehaviorGoogleMapsLike<V>) behavior;
    }
    

    link to the hole project您可以在其中看到所有自定义行为
    注意3:下次添加评论以礼貌的方式要求更改答案或询问为什么在关闭它或标记为重复之前,该答案的某些内容与我关于同一主题的其他答案相同。
    这是它的样子
    [ CustomBottomSheetBehavior ]

    关于android - 使用官方支持库 23.x.+bottomSheet 像谷歌地图一样向上滑动图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37335366/

    相关文章:

    javascript - 使用输入类型 ="range"更改 Circle google map 的半径

    android - 非常电池友好的应用程序/小部件(Android)

    google-maps - 2008 年 TIGER/Line® Shapefiles from Census.gov -> Google Maps

    android - Facebook SDK Android,java.lang.RuntimeException : Unable to get provider com. facebook.internal.FacebookInitProvider

    google-maps - 带有邮政信箱的 Google Places 自动填写地址表?

    android - CollapsingToolbarLayout 缩放图像

    android - 视差和引脚折叠模式在折叠工具栏布局中不起作用

    jquery - IE8 和 9 中的 CSS3 视差错误

    Java 游戏创建确定性瓦片 map

    android - 如何从 Dailymotion 获取 RTSP 视频链接?