此示例取决于支持库23.4.0。+。
BottomSheetBehavior的特点是:
两个带有动画的工具栏,它们响应底部工作表的移动。
一个FAB,当它靠近“模式工具栏”(向上滑动时会出现)时隐藏。
底页后面的背景图像具有某种视差效果。
到达底部时出现在工具栏中的标题(TextView)。
通知状态栏可以将其背景变为透明或全彩色。
具有“锚定”状态的自定义底纸行为。
现在让我们一一检查它们:
工具栏
当您在Google Maps中打开该视图时,您会看到一个可以在其中搜索的工具栏,这是我唯一不完全像Google Maps那样的工具栏,因为我想使其更加通用。无论如何,它ToolBar都在内部,AppBarLayout并且在您开始拖动BottomSheet时它被隐藏了,并且在BottomSheet到达COLLAPSED状态时再次出现。
要实现它,您需要:
创建一个Behavior并从中扩展它AppBarLayout.ScrollingViewBehavior
覆盖layoutDependsOn和onDependentViewChanged方法。这样做,您将聆听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(); //往上走 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(); }
这是完整的文件,如果您需要
第二个工具栏或“模态”工具栏:
您必须重写相同的方法,但是在此方法中,您必须注意更多的行为:
显示/隐藏带有动画的工具栏
更改状态栏的颜色/背景
在工具栏中显示/隐藏BottomSheet标题
关闭bottomSheet或将其发送到折叠状态
这个的代码有点广泛,所以我让链接
FAB
这也是一种自定义行为,但从扩展FloatingActionButton.Behavior。在其中,onDependentViewChanged您必须查看它何时到达“ offSet”或指向要隐藏它的位置。就我而言,我想在靠近第二个工具栏时将其隐藏,因此我深入研究FAB父级(一个CoordinatorLayout),寻找包含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; }
完整的自定义FAB行为链接
具有视差效果的BottomSheet后面的图像:
与其他图像一样,这是一种自定义行为,此算法中唯一的“复杂”事情是使图像固定在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(); //往上走 if (dVerticalScroll <= 0 && child.getY() <= 0) { child.setY(0); return true; } //下降 if (dVerticalScroll >= 0 && dependency.getY() <= mImageHeight) return false; child.setY( (int)(child.getY() + (dVerticalScroll * mYmultiplier) ) ); return true; }
具有视差效果的背景图像的完整文件
现在结束:自定义BottomSheet行为
要完成3个步骤,首先您需要了解默认的BottomSheetBehavior具有5个状态:STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN对于Google Maps行为,您需要在折叠和展开之间添加一个中间状态: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);
}
新增状态
公共静态最终诠释STATE_ANCHOR_POINT = X;
修改下一个方法:onLayoutChild,onStopNestedScroll,和(可选)BottomSheetBehavior<V> from(V view)setState
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) { // 首先让父母布置 if (mState != STATE_DRAGGING && mState != STATE_SETTLING) { if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) { ViewCompat.setFitsSystemWindows(child, true); } parent.onLayoutChild(child, layoutDirection); } // 偏移底页 mParentHeight = parent.getHeight(); mMinOffset = Math.max(0, mParentHeight - child.getHeight()); mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset); //如果(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) { //顶部= 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 { //顶部= 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) { //视图尚未布局;修改mState并让onLayoutChild稍后处理 /** * 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("非法状态参数: " + 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.LayoutParamsparams = view.getLayoutParams(); if (!(params instanceof CoordinatorLayout.LayoutParams)) { throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); } CoordinatorLayout.Behaviorbehavior = ((CoordinatorLayout.LayoutParams) params) .getBehavior(); if (!(behavior instanceof BottomSheetBehaviorGoogleMapsLike)) { throw new IllegalArgumentException( "The view is not associated with BottomSheetBehaviorGoogleMapsLike"); } return (BottomSheetBehaviorGoogleMapsLike<V>) behavior; }
链接到整个项目,您可以在其中查看所有自定义行为
这是它的样子:
[ ]