使用官方支持库 23.x.+ bottomSheet 像谷歌地图一样向上滑动图像 [英] Sliding up image with Official Support Library 23.x.+ bottomSheet like google maps
问题描述
更新
我想实现与谷歌地图相同的行为支持库 23.x.+ 而没有任何第三个库
注意:这不是一个重复的问题,因为:
NOTE: this is not a duplicated question because:
- 我想使用行为、支持库而不使用任何 3rd 方库(我在问题标题和上面的描述中添加了它)
- 我想要你在下一个 gif 中看到的所有行为,其他问题是要求一两个行为并无论如何使用来实现它.
- I want to use Behaviors, Support Library and without ANY 3rd party library (I added it in question title and above description)
- I wanted ALL behaviors that you see in the next gif, the other questions are asking for one or two behaviors and using anyway to achieve it.
我已经使用了 Official bottomSheet(甚至在选项卡和视图寻呼机中).
I have already the Official bottomSheet working (even inside a tab and view pager).
是什么让我发疯是如何实现使用官方bottomSheet向上滑动时从BottomSheet出现的图像行为?.
我曾尝试使用像 FAB 这样的锚但没有成功.
我读了一些关于使用滚动侦听器的内容,但 ppl 说它不像谷歌地图那样流畅和快速.
我的 XML(我认为它不会有帮助,但无论如何):
What is making me going crazy is how to achieve the image behavior that comes up from the BottomSheet when sliding up using the official bottomSheet?.
I have tried using anchor like FAB with no success.
I read something about using a scroll listener but ppl said it's not smooth and faster like google maps.
My XML (I don't think it's going to help but anyway):
<?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>
推荐答案
如果你想使用 Support Library 23.4.0.+ 来实现它,我会告诉你我是如何得到它以及它是如何工作的.
If you want to achieve it using Support Library 23.4.0.+ I will tell you how I got it and how its works.
据我所知,活动/片段具有以下行为:
As far I can see that activity/fragment has the followings behaviors:
- 2 个带有动画的工具栏,可响应底部工作表的移动.
- 靠近模态工具栏"时隐藏的 FAB;(向上滑动时出现的那个).
- 底片后面的背景图像,具有某种视差效果.
- 工具栏中的标题 (TextView),在底部工作表到达时出现.
- 通知状态栏可以将其背景变为透明或全色.
- 带有锚点"的自定义底部工作表行为状态.
note2:这个答案讨论了 6 件事,而不是像其他问题一样关于 1 或 2,你现在能看出区别吗?
note2: This answer talk about 6 things not about 1 or 2 like other question, can you see the difference now?
好的,现在让我们来看看:
Ok, now let's check one bye one:
工具栏
当您在谷歌地图中打开该视图时,您可以看到一个可以搜索的工具栏,这是唯一一个我没有像谷歌地图那样做的,因为我想让它更通用.无论如何,ToolBar
位于 AppBarLayout
内,当您开始拖动 BottomSheet 时它会隐藏起来,当 BottomSheet 到达 COLLAPSED
状态时它会再次出现.
要实现它,您需要:
ToolBars
When you open that view in google maps u can see a toolbar where you can search, it's the only one that I'm not doing equals like google maps because I wanted to do it more generic. Anyway, that ToolBar
is inside an AppBarLayout
and it got hidden when you start dragging the BottomSheet and it appears again when the BottomSheet reaches the COLLAPSED
state.
To achieve it you need:
- 创建一个
Behavior
并从AppBarLayout.ScrollingViewBehavior
扩展它 - 覆盖
layoutDependsOn
和onDependentViewChanged
方法.这样做你会听到bottomSheet的运动. - 创建一些方法来使用动画隐藏和取消隐藏 AppBarLayout/ToolBar.
- create a
Behavior
and extend it fromAppBarLayout.ScrollingViewBehavior
- override
layoutDependsOn
andonDependentViewChanged
methods. Doing it you will listen for bottomSheet movements. - create some methods to hide and unhide the AppBarLayout/ToolBar with animations.
这就是我为第一个工具栏或 ActionBar 所做的:
This is how I did it for the first toolbar or 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 second Toolbar or "Modal" toolbar:
You have to override some methods but in this one, you have to take care of more behaviors:
- 显示/隐藏带有动画的工具栏
- 更改状态栏颜色/背景
- 在工具栏中显示/隐藏BottomSheet标题
- 关闭bottomSheet或将其发送到折叠状态
这个代码有点多,所以我会让 链接
The code for this one is a little extensive so I will let the link
FAB
这也是一个自定义行为,但从 FloatingActionButton.Behavior
扩展而来.在 onDependentViewChanged
中,您必须查看它何时到达offSet"或指向您想要隐藏它的位置.在我的情况下,我想在它靠近第二个工具栏时隐藏它,所以我深入 FAB 父级(一个 CoordiantorLayout)寻找包含 ToolBar 的 AppBarLayout,然后我使用 ToolBar 位置,如 OffSet
:
The FAB
This is a Custom Behavior too but extends from FloatingActionButton.Behavior
. In onDependentViewChanged
you have to look when it reaches the "offSet" or point in where you want to hide it. In my case I want to hide it when it's near to the second toolbar, so I dig into FAB parent (a CoordiantorLayout) looking for the AppBarLayout that contains the ToolBar, then I use the ToolBar position like 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;
}
具有视差效果的BottomSheet后面的图像:
像其他人一样,它是一种自定义行为,唯一的复杂"是这其中的一点是保持图像锚定到BottomSheet并避免图像像默认视差效果那样折叠的小算法:
The Image behind the BottomSheet with parallax effect:
Like the others its a custom behavior, the only "complicated" thing in this one is the little algorithm that keeps the Image anchored to the BottomSheet and avoids the image collapse like the default parallax effect:
@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
,对于 Google 地图行为,您需要在折叠和折叠之间添加一个中间状态扩展:STATE_ANCHOR_POINT
.
我尝试扩展默认的 bottomSheetBehavior 没有成功,所以我只是复制粘贴所有代码并修改了我需要的内容.
要实现我所说的内容,请执行以下步骤:
[complete file for backdrop Image with parallax effect][4]
Now for the end: The Custom BottomSheet Behavior
To achieve the 3 steps first you need to understand that default BottomSheetBehavior has 5 states: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN
, and for the Google Maps behavior you need to add a middle state between collapsed and expanded: STATE_ANCHOR_POINT
.
I tried extends the default bottomSheetBehavior with no success, so I just copy-paste all code and modified what I need.
To achieve what I'm talking about following the next steps:
创建一个Java类并从
CoordinatorLayout.Behavior
将粘贴代码从默认的 BottomSheetBehavior
文件复制到您的新文件中.
Copy paste code from the default BottomSheetBehavior
file to your new one.
使用以下代码修改方法 clampViewPositionVertical
:
Modify the method clampViewPositionVertical
with the following code:
@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);
}
添加新状态
Add a new state
public static final int STATE_ANCHOR_POINT = X;
public static final int STATE_ANCHOR_POINT = X;
修改接下来的方法:onLayoutChild
、onStopNestedScroll
、BottomSheetBehavior
和 setState
(可选)
Modify the next methods: onLayoutChild
, onStopNestedScroll
, BottomSheetBehavior<V> from(V view)
and setState
(optional)
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 in where you can see all Custom Behaviors
注意3:下次添加评论以礼貌的方式要求更改答案或询问为什么此答案的某些内容与我关于同一主题的其他答案相同,然后再关闭它或标记为重复.
note3: next time add a comment asking in a polite way for a change of the answer or ask why this answer has SOME equals stuff than others answer of mine about the same topic BEFORE close it or mark like duplicated.
这是它的样子
[]
这篇关于使用官方支持库 23.x.+ bottomSheet 像谷歌地图一样向上滑动图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!