Ответ 1
Если вы хотите достичь этого с помощью Support Library 23.4.0. + Я расскажу вам, как я получил его и как его работы.
Примечание: извиниться за мой английский, я попытался дать ответ на программирование (просто короткий и смешанный с полезным кодом), но похоже, что это было недостаточно хорошо...
Насколько я могу видеть, что активность/фрагмент имеет следующие поведения:
- 2 панели инструментов с анимациями, которые реагируют на движение нижнего листа.
- FAB, который скрывается, когда он находится рядом с "модальной панелью инструментов" (тот, который появляется, когда вы приближаетесь).
- Изображение заднего плана за нижним листом с каким-то эффектом параллакса.
- Заголовок (TextView) на панели инструментов, который появляется, когда нижний лист достигает его.
- Панель состояния уведомлений может превратить свой фон в прозрачный или полный цвет.
- Пользовательское поведение нижнего листа с состоянием "привязки".
note2: В этом ответе поговорите о 6 вещах не о 1 или 2, как о другом вопросе, вы можете увидеть разницу сейчас?
Хорошо, теперь можно проверить один за другим:
Панели инструментов
Когда вы открываете это представление в картах google, вы можете видеть панель инструментов, в которой вы можете искать, это единственная, что я не делаю, как карты Google, потому что я хотел сделать это более общим. В любом случае ToolBar
находится внутри AppBarLayout
, и он скрыт, когда вы начинаете перетаскивать нижний лист и снова появляется, когда BottomSheet достигает состояния COLLAPSED
.
Для этого вам нужно:
- создать
Behavior
и расширить его отAppBarLayout.ScrollingViewBehavior
- переопределить методы
layoutDependsOn
иonDependentViewChanged
. Сделав это, вы будете слушать движения нижнего листа. - создайте некоторые методы, чтобы скрыть и отобразить 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();
}
полный файл, если вам это нужно
Вторая панель инструментов или "Модальная" панель инструментов:
Вы должны переопределить те же методы, но в этом вам нужно позаботиться о более поведении:
- показать/скрыть панель инструментов с анимацией
- изменить цвет статуя/цвет фона
- показать/скрыть заголовок BottomSheet в ToolBar
- закрыть нижний лист или отправить его в сложенное состояние.
Код для этого немного обширен, поэтому я дам ссылку
FAB
Это также пользовательское поведение, но оно простирается от FloatingActionButton.Behavior
. В onDependentViewChanged
вам нужно посмотреть, когда он достигнет "offSet" или указать, где вы хотите скрыть его. В моем случае я хочу скрыть его, когда он находится рядом со второй панелью инструментов, поэтому я копаюсь в родительском блоке FAB (файл CoordiantorLayout), который ищет AppBarLayout, который содержит ToolBar, затем я использую позицию 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 и избегает обрушения изображения, как эффект параллакса по умолчанию:
@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;
}
полный файл для фона Изображение с эффектом параллакса
Теперь для завершения: Пользовательское поведение нижнего листа
Для достижения 3-х шагов сначала вам нужно понять, что Default BottomSheetBehavior по умолчанию имеет 5 состояний: STATE_DRAGGING, STATE_SETTLING, STATE_EXPANDED, STATE_COLLAPSED, STATE_HIDDEN
и для поведения в Google Картах вам нужно добавить среднее состояние между свернутым и расширенным: 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;
}
ссылка на проект дыр, где вы можете увидеть все пользовательские поведения
note3: в следующий раз добавьте комментарий, просящий вежливым способом для изменения ответа или спросите, почему этот ответ имеет НЕКОТОРЫЕ равные вещи, чем другие мои ответы по той же теме. ДО ТОГО, как закрыть его или пометить как дублирующийся.
И вот как это выглядит:
[]