Поддержка Android BottomSheetBehavior дополнительное закрепленное состояние

Я использую в своем приложении библиотеку AndroidSlidingUpPanel. В версиях библиотеки поддержки дизайна Android, начиная с 23.1.1, это ломает некоторые вещи в моем макете. Поскольку в последних версиях представлен BottomSheetBehavior, I ' Я хочу заменить библиотеку AndroidSlidingUpPanel и использовать вместо нее BottomSheetBehavior. Однако BottomSheetBehavior имеет только 3 состояния: скрытое, свернутое и развернутое (а также 2 промежуточных состояния с перетаскиванием и установкой). AndroidSlidingUpPanel дополнительно имеет закрепленное состояние, то есть состояние, к которому панель привязывается между свернутым и развернутым. Как я могу использовать BottomSheetBehavior и получить это дополнительное закрепленное состояние?

Например, приложение Google Maps имеет такое поведение.

Скрыто:

Свернуто:

Перетаскивание (между свернутым и закрепленным):

Привязано:

Перетаскивание (между закрепленным и развернутым):

Развернутый:

Имеются некоторые эффекты параллакса с дополнительным изображением, скользящим вверх по карте в закрепленном состоянии, когда они есть в местах. И при полном раскрытии имя местоположения становится заголовком панели действий. В конце концов, я тоже буду заинтересован в достижении чего-то подобного.

Мой первый инстинкт состоит в том, что закрепленное состояние на самом деле является развернутым состоянием с пустым пространством над панелью, где карта все еще видна, являясь прозрачной частью представления. Тогда перетаскивание между закрепленным и развернутым состояниями - это просто прокрутка содержимого самого представления панели.

Это подтверждается тем фактом, что, находясь в закрепленном состоянии, вы можете продолжать прокручивать панель вверх, проводя видимую область карты над панелью. Эта невидимая часть представления должна расширяться в свою область (как это делают видимые дополнительные изображения) при смахивании вверх из свернутого состояния, поскольку невозможно сдвинуть панель вверх с карты в свернутом состоянии. Полагаю, я мог бы пойти по этому маршруту, но хотел посмотреть, есть ли какие-нибудь подходы получше.


person Jeff Lockhart    schedule 01.05.2016    source источник
comment
Вы нашли решение для изображения с эффектами параллакса, скользящими по карте?   -  person MiguelHincapieC    schedule 20.05.2016
comment
@MiguelHincapieC нет, у меня нет. Поскольку он у меня есть в настоящее время, он скользит только вверх до развернутого состояния, частично вверх по экрану, как в закрепленном положении выше. Все еще работаем над завершением представления, чтобы иметь больше контента для расширения на весь экран с эффектом параллакса, сливающимся с панелью действий.   -  person Jeff Lockhart    schedule 20.05.2016
comment
Я предполагаю, что эффекты параллакса, возникающие при скольжении изображения вверх, используют app:layout_collapseParallaxMultiplier с отрицательным значением   -  person MiguelHincapieC    schedule 22.05.2016
comment
вы заметили поведение в картах Google, когда вы пытаетесь скользить выше точки привязки? Я имею в виду, что он не перемещает карту, вместо этого он перемещает bottomSheet, как BottomSheetDialog делает o_O '   -  person MiguelHincapieC    schedule 23.05.2016
comment
Что касается моего последнего комментария, есть что-то странное в этом поведении, независимо от того, является ли это Dialog или нет, но вам действительно нужно позаботиться о minSdkVersion, если вы настроили для 14, это позволит вам работать как карты Google, если вы настроили для 17 например, он не позволит вам прокручивать, если вы не прикасаетесь к внутренней части нижнего листа.   -  person MiguelHincapieC    schedule 23.05.2016
comment
... и то же самое происходит с :23.2.0 и :23.4.0 , какого черта ....   -  person MiguelHincapieC    schedule 23.05.2016


Ответы (2)


БОЛЬШОЕ ОБНОВЛЕНИЕ, потому что было около 4 или 5 вопросов по одной и той же теме, НО с РАЗНЫМИ требованиями, и я попытался ответить на все из них. Невежливый администратор удалил / закрыл их, заставив меня создать тикет для каждого и изменить их, чтобы избежать копирования и вставки. Я дам вам ссылку на полный ответ, где вы можете найти все объяснения о том, как добиться полного поведения, такого как Google Карты.


Отвечая на ваши вопросы

Как я могу использовать BottomSheetBehavior и получить это дополнительное закрепленное состояние?

Вы можете сделать это, изменив значение по умолчанию BottomSheetBehavior, добавив еще один показатель, выполнив следующие действия:

  1. Создайте класс Java и расширьте его с CoordinatorLayout.Behavior<V>
  2. Скопируйте и вставьте код из BottomSheetBehavior файла по умолчанию в новый.
  3. Измените метод 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);
    }
  1. Добавить новое состояние

    public static final int STATE_ANCHOR_POINT = X;

  2. Измените следующие методы: 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;
}



Вы даже можете использовать обратные вызовы с behavior.setBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() {....

А вот как это выглядит
[CustomBottomSheetBehavior

person MiguelHincapieC    schedule 25.05.2016
comment
Спасибо, я займусь этим, когда перейду к реализации этого финального состояния. Было бы неплохо, если бы не пришлось копировать / вставлять код. Было бы лучше, если бы можно было каким-то образом создать подкласс, но, вероятно, нужен код, не видимый в подклассе. - person Jeff Lockhart; 25.05.2016
comment
Да, я пытался создать подкласс, но был проблемой в% $ #. Теперь я работаю с эффектом параллаксного изображения, например с картами Google. - person MiguelHincapieC; 25.05.2016
comment
@JeffLockhart Я понял! теперь он работает с эффектом параллакса изображения и анимацией панелей инструментов ... Мне не хватает только цвета, который принимает панель инструментов, когда вы продолжаете скользить вверх: D - person MiguelHincapieC; 26.05.2016
comment
Привет, мой ответ оказался для вас полезным или вам нужна дополнительная помощь? :) - person MiguelHincapieC; 11.06.2016
comment
да спасибо. Мы все еще работаем над доработкой дизайна этого поведения, после чего я смогу завершить его реализацию в коде. Эта информация полезна, спасибо. - person Jeff Lockhart; 11.06.2016
comment
@MiguelHincapieC Я клонировал ваше репо и работаю над решением аналогичной проблемы. Тем не менее, мне нужно, чтобы мое представление с нижним листом было LinearLayout с NestedScrollView внутри. Однако, когда я это делаю, onStopNestedScroll никогда не вызывается, поэтому состояние привязки никогда не достигается. У вас есть идеи по этому поводу? - person kjanderson2; 13.06.2017
comment
Привет @ kjanderson2, использование LinearLayout не должно быть проблемой. Проверяли ли вы XML при определении поведения? - person MiguelHincapieC; 14.06.2017
comment
@MiguelHincapieC Я сделал, и это не сработало. Однако с тех пор я решил эту проблему, переставив свои макеты. У меня возникла проблема, когда, когда ящик находится в позиции привязки, его все еще можно перетаскивать, перетаскивая из областей над листом. Таким образом, я больше не могу взаимодействовать с представлением под нижним листом, потому что область, которую лист в настоящее время не занимает (между привязкой и развернутым), все еще можно перетаскивать. Вы сталкивались с этим? У вас есть идеи, как исправить? - person kjanderson2; 14.06.2017
comment
@ kjanderson2 Я не очень хорошо понимаю проблему, вы можете поделиться с вами поведением в гифке? - person MiguelHincapieC; 15.06.2017
comment
@MiguelHincapieC Все в порядке. У меня все заработало. Спасибо! - person kjanderson2; 20.06.2017

есть простой способ изменить состояние изменения BottomSheetBehavior с помощью привязки FloatingActionButton

<android.support.design.widget.CoordinatorLayout 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:orientation="vertical">

  <android.support.v7.widget.CardView
    android:id="@+id/bottom_sheet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:behavior_hideable="false"
    app:behavior_peekHeight="44dp"
    app:cardCornerRadius="0dp"
    app:cardElevation="5dp"
    app:layout_behavior="@string/bottom_sheet_behavior">

</android.support.v7.widget.CardView>

<android.support.design.widget.FloatingActionButton
    android:id="@+id/request_show_fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="10dp"
    android:src="@drawable/fab_requests"
    app:layout_anchor="@id/bottom_sheet"
    app:layout_anchorGravity="top|end" />

now you can change state click on FloatingActionButton

bottomSheetBehavior = BottomSheetBehavior.from(bottom_sheet);

    request_show_fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            } else {
                bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);

            }
        }
    });
person Gautam Surani    schedule 11.06.2018