ListView в нижнем листе

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

Когда я прокручиваю вниз, кажется, что все работает, однако, когда я пытался прокрутить назад, он прокручивал сам BottomSheet и закрывал представление, а не просто прокручивал ListView.

Через некоторое время мне удалось найти решение, и, поскольку я нигде не мог найти его здесь, я решил опубликовать его здесь.


person ᴛʜᴇᴘᴀᴛᴇʟ    schedule 13.11.2016    source источник


Ответы (8)


Решение состоит в том, чтобы расширить ListView следующим образом:

public class BottomSheetListView extends ListView {
    public BottomSheetListView (Context context, AttributeSet p_attrs) {
        super (context, p_attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (canScrollVertically(this)) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.onTouchEvent(ev);
    }

    public boolean canScrollVertically (AbsListView view) {
        boolean canScroll = false;

        if (view !=null && view.getChildCount ()> 0) {
            boolean isOnTop = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0;
            boolean isAllItemsVisible = isOnTop && view.getLastVisiblePosition() == view.getChildCount();

            if (isOnTop || isAllItemsVisible) {
                canScroll = true;
            }
        }

        return  canScroll;
    }
}

Затем в вашем файле макета bottom_sheet_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.mypackage.name.BottomSheetListView
        android:id="@+id/listViewBtmSheet"
        android:divider="@color/colorPrimary"
        android:dividerHeight="1dp"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="0dp" />

</LinearLayout>

Затем, наконец, в вашем Activity/Fragment:

BottomSheetDialog dialog = new BottomSheetDialog(context);
dialog.setContentView(R.layout.bottom_sheet_view);

BottomSheetListView listView = (BottomSheetListView) dialog.findViewById(R.id.listViewBtmSheet);
// apply some adapter - add some data to listview

dialog.show();

Это обеспечит BottomSheet, полностью работающий с прокруткой ListView.

person ᴛʜᴇᴘᴀᴛᴇʟ    schedule 13.11.2016
comment
эй, приятель, хорошее решение, но элементы списка не кликабельны, какой-нибудь хак для этого? - person sUndeep; 04.01.2017
comment
Элементы @sUndeep кликабельны для меня. У вас есть ImageView или Button внутри макета элемента списка? Если это так, вам нужно добавить android:descendantFocusability="blocksDescendants" в корневой элемент макета элемента списка. - person ᴛʜᴇᴘᴀᴛᴇʟ; 04.01.2017
comment
Я сделал это без записи android:descendantFocusability=blocksDescendants. Но он нуждается в небольшом улучшении. Я разместил код в комментарии - person sUndeep; 05.01.2017
comment
элементы списка также не кликабельны для меня. любое решение? - person Zappy.Mans; 17.04.2017
comment
@th3pat3l Привет!! Кто-нибудь нашел решение? Даже я не могу щелкнуть элемент списка. У меня есть ImageView внутри списка. Пожалуйста, помогите, если это возможно. Спасибо. - person Harsh Patel; 17.10.2017
comment
Полезный! Любое решение для достижения этого без расширения списка? - person Pratik Saluja; 21.08.2018
comment
Большое спасибо! Протестируйте с помощью ExpandableListView и работайте. @PratikSaluja Если вы не хотите расширять класс, вы можете установить OnTouchListener в представлении списка, и он тоже будет работать правильно. Кстати, здесь: Управление сенсорными событиями в ViewGroup объясняется, что возврат false в onInterceptTouchEvent позволяет дочерние представления обрабатывают события. Может быть, это может быть решением для элементов, которые не кликабельны. - person marcRDZ; 22.07.2019
comment
привет, отличный ответ. Не могли бы вы подсказать мне, как сделать то же самое для recyclerview. Ваш ранний ответ будет очень признателен - person Android1005; 10.08.2019
comment
Большой! Спасибо очень много - person Hai Rom; 14.05.2020

Существует лучший подход, если вы не хотите расширять ListView:

//in onCreate

_listView.setOnTouchListener(new ListView.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                int action = event.getAction();
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        // Disallow NestedScrollView to intercept touch events.
                        v.getParent().requestDisallowInterceptTouchEvent(true);
                        break;

                    case MotionEvent.ACTION_UP:
                        // Allow NestedScrollView to intercept touch events.
                        v.getParent().requestDisallowInterceptTouchEvent(false);
                        break;
                }

                // Handle ListView touch events.
                v.onTouchEvent(event);
                return true;
            }
        });
person okkko    schedule 09.09.2017

Это правильный пользовательский класс просмотра списка с обработкой событий касания.

public class BottomSheetListView extends ListView
{
    public BottomSheetListView(Context context, AttributeSet p_attrs)
    {
        super(context, p_attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        if (canScrollVertically(this))
        {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        if (canScrollVertically(this))
        {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
        return super.onTouchEvent(ev);
    }

    public boolean canScrollVertically(AbsListView view)
    {
        boolean canScroll = false;

        if (view != null && view.getChildCount() > 0)
        {
            boolean isOnTop = view.getFirstVisiblePosition() != 0 || view.getChildAt(0).getTop() != 0;
            boolean isAllItemsVisible = isOnTop && view.getLastVisiblePosition() == view.getChildCount();

            if (isOnTop || isAllItemsVisible)
            {
                canScroll = true;
            }
        }

        return canScroll;
    }
}
person Nauman Afzaal    schedule 25.04.2018
comment
Идеальный ответ!! - person Nishant Chauhan; 03.07.2018

Начиная с версии 22.1.0, вы можете попробовать setNestedScrollingEnabled=true

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

Ссылка на Google API

person longi    schedule 24.09.2019

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

    mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback(){
@Override
public void onStateChanged(View bottomSheet, int newState) {
        if (newState == BottomSheetBehavior.STATE_DRAGGING && !listIsAtTop()){
            mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
}
@Override
public void onSlide(View bottomSheet, float slideOffset) {}});

public boolean listIsAtTop()   {
    if(tripListView.getChildCount() == 0) return true;
    return (tripListView.getChildAt(0).getTop() == 0 && tripListView.getFirstVisiblePosition() ==0);
}
person Prateek Gupta    schedule 25.11.2017

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

public class NestedListView extends ListView {

    private boolean mIsBeingDragged = false;
    private float mLastMotionY;

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

    public NestedListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: {
                if (canScrollDown() || canScrollUp()) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            }
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final float y = event.getRawY();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_MOVE: {
                if (!mIsBeingDragged) {
                    final float deltaY = mLastMotionY - y;
                    mIsBeingDragged = (deltaY > 0 && canScrollDown())
                        || (deltaY < 0 && canScrollUp());

                    if (mIsBeingDragged) {
                        final ViewParent parent = getParent();
                        if (parent != null) {
                            parent.requestDisallowInterceptTouchEvent(true);
                        }
                    } else {
                        final ViewParent parent = getParent();
                        if (parent != null) {
                            parent.requestDisallowInterceptTouchEvent(false);
                        }
                        return false;
                    }
                }
            }

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIsBeingDragged = false;
                break;
        }

        mLastMotionY = y;

        return super.onTouchEvent(event);
    }

    public boolean canScrollUp() {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return false;
        }

        final int firstPosition = getFirstVisiblePosition();
        final int firstTop = getChildAt(0).getTop();
        return firstPosition > 0 || firstTop < getListPaddingTop();
    }

    public boolean canScrollDown() {
        final int childCount = getChildCount();
        if (childCount == 0) {
            return false;
        }

        final int firstPosition = getFirstVisiblePosition();
        final int lastBottom = getChildAt(childCount - 1).getBottom();
        final int lastPosition = firstPosition + childCount;
        return lastPosition < getCount() || lastBottom > getHeight() - getListPaddingBottom();
    }
}
person Oleksandr Albul    schedule 22.11.2019

Просто установите android:nestedScrollingEnabled в true внутри макета как атрибут ListView или listView.setNestedScrollingEnabled(true) в java.

person Rohan Chauhan    schedule 09.08.2020

person    schedule
comment
@ th3pat3l, пожалуйста, проверьте мой комментарий в коде. если вы можете улучшить его, то это было бы здорово, иначе все в порядке, я справлюсь. я взял идею из stackoverflow.com/questions/32631547/ - person sUndeep; 05.01.2017