Использование BottomSheetBehavior с внутренним CoordinatorLayout

Библиотека поддержки дизайна v. 23.2 представила BottomSheetBehavior, которая позволяет дочерним элементам координатора действовать как нижние листы (представления, которые можно перетаскивать из нижней части экрана).

Что я хотел бы сделать, так это иметь в качестве нижнего листа следующее представление (типичный координатор + сворачивающийся материал):

<CoordinatorLayout
    app:layout_behavior=“@string/bottom_sheet_behavior”>

   <AppBarLayout>
        <CollapsingToolbarLayout>
           <ImageView />
        </CollapsingToolbarLayout>
    </AppBarLayout>

    <NestedScrollView>
        <LinearLayout>
            < Content ... />
        </LinearLayout>
    </NestedScrollView>

</CoordinatorLayout>

К сожалению, представления нижнего листа должны реализовывать вложенную прокрутку, иначе они не будут получать события прокрутки. Если вы попробуете с основным действием, а затем загрузите это представление в качестве нижнего листа, вы увидите, что события прокрутки действуют только на «листе» бумаги с некоторым странным поведением, как вы можете увидеть, если продолжите чтение.

Я почти уверен, что с этим можно справиться путем создания подкласса CoordinatorLayout или, что еще лучше, путем создания подкласса BottomSheetBehavior. У вас есть намек?

Некоторые мысли

  • requestDisallowInterceptTouchEvent() следует использовать для кражи событий у родителя в некоторых условиях:

    • when the AppBarLayout offset is > 0
    • когда смещение AppBarLayout == 0, но мы прокручиваем вверх (подумайте об этом на секунду, и вы увидите)
  • первое условие можно получить, установив OnOffsetChanged на внутреннюю панель приложения;

  • второй требует некоторой обработки событий, например:

    switch (MotionEventCompat.getActionMasked(event)) {
        case MotionEvent.ACTION_DOWN:
            startY = event.getY();
            lastY = startY;
            userIsScrollingUp = false;
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            userIsScrollingUp = false;
            break;
        case MotionEvent.ACTION_MOVE:
            lastY = event.getY();
            float yDeltaTotal = startY - lastY;
            if (yDeltaTotal > touchSlop) { // Moving the finger up.
                userIsScrollingUp = true;
            }
            break;
    }
    

Проблемы

Излишне говорить, что я не могу сделать эту работу прямо сейчас. Я не умею ловить события при выполнении условий и не ловить их в других случаях. На изображении ниже вы можете увидеть, что происходит со стандартным CoordinatorLayout:

  • Лист закрывается, если вы прокручиваете вниз панель приложений, но не прокручиваете вниз вложенный контент. Кажется, что события вложенной прокрутки не распространяются на поведение координатора;

  • Также есть проблема с внутренней панелью приложений: содержимое вложенной прокрутки не следует за панелью приложений, когда она сворачивается.

введите здесь описание изображения

Я установил пример проекта на github, который показывает эти проблемы.

Просто чтобы быть ясным, желаемое поведение:

  • Корректное поведение панелей приложений/прокрутки внутри листа;

  • Когда лист развернут, он может свернуться при прокрутке вниз, но только если внутренняя панель приложения также полностью развернута. Прямо сейчас он рушится независимо от состояния панели приложений, и только если вы перетаскиваете панель приложений;

  • Когда лист свернут, жесты прокрутки вверх расширят его (без влияния на внутреннюю панель приложений).

Пример из приложения для контактов (которое, вероятно, не использует BottomSheetBehavior, но это то, что я хочу):

введите здесь описание изображения


comment
У вашего NestedScrollView есть этот атрибут? app:layout_behavior="@string/appbar_scrolling_view_behavior"   -  person sakiM    schedule 28.03.2016
comment
@saki_M да, см. пример проекта.   -  person natario    schedule 28.03.2016
comment
Я не думаю, что вложение CoordinatorLayout в настоящее время возможно. Может быть, взгляните на Plaid ElasticDragDismissFrameLayout.   -  person Markus Rubey    schedule 28.03.2016
comment
@Markus Я готов поместить внутреннего координатора во что-то еще, меня интересует только конечный результат. И я также хотел бы использовать BottomSheetBehavior.   -  person natario    schedule 28.03.2016
comment
Я знаю, что есть библиотеки, которые уже достигли этого (я использовал реализацию флипборда), но на этот раз я заинтересован в переключении на BottomSheetBehavior. Я посмотрю на то, что вы предложили, и посмотрю, если что-то придет в голову.   -  person natario    schedule 28.03.2016
comment
Думаю, вы можете сослаться на этот проект. Я пробовал, но по какой-то причине не использовал его в мой проект.   -  person sakiM    schedule 29.03.2016
comment
@Markus взглянул на это, но я думаю, что это работает только с API21+, поскольку оно основано на событиях вложенной прокрутки.   -  person natario    schedule 29.03.2016
comment
Я пытаюсь добиться чего-то подобного и застрял в этой же проблеме. Удалось ли вам найти какое-либо решение этой проблемы?   -  person nipun.birla    schedule 27.07.2016
comment
@nipun.birla да, я сделал это сам. Когда у меня будет время, я опубликую свое решение   -  person natario    schedule 27.07.2016
comment
Это будет полезно. Заранее спасибо!   -  person nipun.birla    schedule 27.07.2016
comment
Эй .. Можете ли вы опубликовать решение?   -  person nipun.birla    schedule 29.07.2016
comment
Можете ли вы опубликовать свой окончательный код?   -  person Anton Shkurenko    schedule 15.08.2016
comment
Извините, сейчас в командировке, вернусь к работе в середине сентября.   -  person natario    schedule 15.08.2016
comment
Можете ли вы опубликовать свой код сейчас?   -  person ahgpoug    schedule 10.10.2016
comment
решение было найдено?   -  person kassim    schedule 20.10.2016
comment
У кого-нибудь есть решение для этого???   -  person Polar    schedule 01.11.2016
comment
Мне кажется, я достаточно далеко продвинулся в этом вопросе в этом примере проекта, используя вложенный CoordinatorLayout с возможностью прокрутки для детей и слегка измененный BottomSheetBehavior. Взгляните на эту фиксацию, чтобы увидеть мои ключевые изменения. Вы найдете гифки, иллюстрирующие, что уже работает, а что нет.   -  person laenger    schedule 25.11.2016
comment
Я не знаю, кто проголосовал за мой ответ. Но, пожалуйста, просмотрите мой ответ один раз. Может, это поможет вам, ребята :-) #soreadytohelp   -  person Ravindra Shekhawat    schedule 24.03.2017


Ответы (6)


Наконец-то я выпустил свою реализацию. Найдите его на Github или напрямую из jcenter:

compile 'com.otaliastudios:bottomsheetcoordinatorlayout:1.0.0’

Все, что вам нужно сделать, это использовать BottomSheetCoordinatorLayout в качестве корневого представления для вашего нижнего листа. Он автоматически раздует рабочее поведение для себя, так что не беспокойтесь об этом.

Я использую это в течение долгого времени, и у него не должно быть проблем с прокруткой, поддержка перетаскивания ABL и т. д.

person natario    schedule 20.04.2017

Я просто следовал тому, как вы задали вопрос выше, и придумал решение, которое может потребовать дополнительных объяснений. Пожалуйста, следуйте вашему примеру кода и интегрируйте дополнительную часть в свой xml, чтобы он вел себя как поведение BottomSheeet.

<CoordinatorLayout>
   <AppBarLayout>
        <Toolbar
            app:layout_collapseMode="pin">
        </Toolbar>
    </AppBarLayout>
    <NestedScrollView
         app:layout_behavior=“@string/bottom_sheet_behavior” >
        <include layout="@layout/items" />
    </NestedScrollView>

    <!-- Bottom Sheet -->

     <BottomSheetCoordinatorLayout>
        <AppBarLayout
            <CollapsingToolbarLayout">
             <ImageView />
                <Toolbar />
            </CollapsingToolbarLayout>
        </AppBarLayout>
        <NestedScrollView">
            <include layout="@layout/items" />
        </NestedScrollView>
    </BottomSheetCoordinatorLayout>
</CoordinatorLayout>

Примечание. Решение, которое сработало для меня, уже объяснено в последнем комментарии к вашему вопросу.

Лучшее объяснение: https://github.com/laenger/BottomSheetCoordinatorLayout

person Ravindra Shekhawat    schedule 11.01.2017

Если первый дочерний элемент nestedscroll, возникнут другие проблемы. Это решение устранило мою проблему, надеюсь, исправит вашу.

<CoordinatorLayout
    app:layout_behavior=“@string/bottom_sheet_behavior”>

   <AppBarLayout>
        <CollapsingToolbarLayout>
           <ImageView />
        </CollapsingToolbarLayout>
    </AppBarLayout>
</LinearLayout>
    <NestedScrollView>
        <LinearLayout>
            < Content ... />
        </LinearLayout>
    </NestedScrollView>
</LinearLayout>
</CoordinatorLayout>
person Eren Utku    schedule 02.12.2016

Старайтесь не использовать NestedScrollView с LinearLayout, это тоже вызывает проблемы в моем приложении. Просто используйте вместо этого только LinearLayout, у меня отлично работает.

Попробуйте следующее:

<CoordinatorLayout
app:layout_behavior=“@string/bottom_sheet_behavior”>

<AppBarLayout>
    <CollapsingToolbarLayout>
       <ImageView />
    </CollapsingToolbarLayout>
</AppBarLayout>

<LinearLayout>
     <!--don't forget to addd this line-->
     app:layout_behavior="@string/appbar_scrolling_view_behavior">

        < Content ... />
</LinearLayout>

person Rahul    schedule 01.01.2017

Я следил за первоначальным тестовым проектом Laenger на github по этой проблеме, и я рад поделиться с вами решением некоторых из его проблем, поскольку мне нужно было такое поведение и в моем приложении.

это решение его проблемы: ❌ панель инструментов иногда рушится слишком рано

чтобы предотвратить это, вам нужно создать свой собственный AppBarLayout.Behavior, так как именно когда вы прокручиваете вверх, продолжая перетаскивать, AppBarLayout.behavior получает движение прокрутки. Нам нужно определить, находится ли он в состоянии STATE_DRAGGING, и просто вернуться, чтобы избежать преждевременного скрытия/сворачивания панели инструментов.

public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {

    private CoordinatorLayoutBottomSheetBehavior behavior;

    public CustomAppBarLayoutBehavior() {
    }

    public CustomAppBarLayoutBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
        behavior = CoordinatorLayoutBottomSheetBehavior.from(parent);
        return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        if(behavior.getState() == CoordinatorLayoutBottomSheetBehavior.STATE_DRAGGING){
            return;
        }else {
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    }

    @Override
    public void setDragCallback(@Nullable DragCallback callback) {
        super.setDragCallback(callback);
    }
}

это может быть хорошим началом того, как мы решаем другие проблемы:

❌ панель инструментов нельзя свернуть перетаскиванием

❌ макет основного координатора требует некоторой прокрутки

На самом деле я не очень хорошо разбираюсь в пользовательском интерфейсе/анимации, но тяжелая работа иногда окупается пониманием кода, поиском правильной функции обратного вызова/переопределения для реализации.

установите это как поведение для appbarlayout

<android.support.design.widget.AppBarLayout
    android:id="@+id/bottom_sheet_appbar"
    style="@style/BottomSheetAppBarStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="your.package.CustomAppBarLayoutBehavior">
person user3115201    schedule 14.03.2017

Макет для полноэкранного макета панели приложений выглядит следующим образом:

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/detail_backdrop_height"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsing_toolbar"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        android:fitsSystemWindows="true"
        app:contentScrim="?attr/colorPrimary"
        app:expandedTitleMarginStart="48dp"
        app:expandedTitleMarginEnd="64dp">

        <ImageView
            android:id="@+id/backdrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:fitsSystemWindows="true"
            app:layout_collapseMode="parallax" />

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:layout_collapseMode="pin" />

    </android.support.design.widget.CollapsingToolbarLayout>

</android.support.design.widget.AppBarLayout>

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingTop="24dp">

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/card_margin">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Info"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/card_margin"
            android:layout_marginLeft="@dimen/card_margin"
            android:layout_marginRight="@dimen/card_margin">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Friends"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

        <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/card_margin"
            android:layout_marginLeft="@dimen/card_margin"
            android:layout_marginRight="@dimen/card_margin">

            <LinearLayout
                style="@style/Widget.CardContent"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="Related"
                    android:textAppearance="@style/TextAppearance.AppCompat.Title" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/cheese_ipsum" />

            </LinearLayout>

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

    </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/appbar"
    app:layout_anchorGravity="bottom|right|end"
    android:src="@drawable/ic_discuss"
    android:layout_margin="@dimen/fab_margin"
    android:clickable="true"/>

и после этого вы должны реализовать AppBarLayout.OnOffsetChangedListener в своем классе и установить смещение экрана.

person Prathamesh Bandekar    schedule 02.04.2016