Android BottomSheet, как свернуть при нажатии снаружи?

Я реализовал поведение нижнего листа с помощью NestedScrollView. И было интересно, можно ли скрыть вид нижнего листа при прикосновении снаружи.


person Pradeep Kumar Kushwaha    schedule 04.07.2016    source источник
comment
Это вопрос с самостоятельным ответом? Потому что вы добавили вопрос и ответ одновременно, поэтому я и спрашивал.   -  person Pankaj Kumar    schedule 04.07.2016
comment
Да, чтобы другие не чувствовали разочарования, если у них такая же проблема.   -  person Pradeep Kumar Kushwaha    schedule 04.07.2016
comment
Для BottomSheetDialogFragment см. stackoverflow.com/ вопросы/40616833/.   -  person CoolMind    schedule 17.04.2019


Ответы (9)


Наконец-то я смог это сделать,

Использовал следующие строки кода:

@Override public boolean dispatchTouchEvent(MotionEvent event){
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (mBottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED) {

            Rect outRect = new Rect();
            bottomSheet.getGlobalVisibleRect(outRect);

            if(!outRect.contains((int)event.getRawX(), (int)event.getRawY()))
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

    return super.dispatchTouchEvent(event);
}

Надеюсь, это спасет чей-то целый день!

person Pradeep Kumar Kushwaha    schedule 04.07.2016
comment
Работает как шарм! - person galvan; 14.11.2016
comment
а во Фрагменте? - person Rajesh Nasit; 19.06.2017
comment
@RajeshNasit Реализуйте метод DispatchTouchEvent в своей деятельности и используйте FragmentManager для вызова метода в своем фрагменте. - person Luca Ziegler; 13.01.2018
comment
Я сделал это, но проблема в том, что событие касания вызывается при нажатии или пролистывании каждого представления. Я хочу, чтобы это было только для определенных просмотров - person Rajesh Nasit; 13.01.2018
comment
как вы инициализировали mBottomSheetBehavior? - person Fady Emad; 14.07.2018
comment
Это работает, но хорошее ли это решение? Поскольку каждый раз, когда мы касаемся экрана или прокручиваем его, этот метод dispatchTouchEvent называется серверным временем, что может отрицательно сказаться на использовании батареи и производительности приложений. - person ankalagba; 05.06.2021

Спасибо ОП за вопрос/ответ. Я использовал его код, но улучшил его чистоту и хотел поделиться. Вместо расширения представления и добавления интерфейса вы можете закодировать это непосредственно в BottomSheetBehavior. Как это:

AutoCloseBottomSheetBehavior.java

import android.content.Context;
import android.graphics.Rect;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AutoCloseBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {

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

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN &&
            getState() == BottomSheetBehavior.STATE_EXPANDED) {

            Rect outRect = new Rect();
            child.getGlobalVisibleRect(outRect);

            if (!outRect.contains((int) event.getRawX(), (int) event.getRawY())) {
                setState(BottomSheetBehavior.STATE_COLLAPSED);
            }
        }
        return super.onInterceptTouchEvent(parent, child, event);
    }
}

а затем вы просто добавляете его в свой XML-макет:

<?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">

   ... your normal content here ...
   <SomeLayout... />

    ... the bottom sheet with the behavior
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_behavior="<com.package.name.of.the.class>.AutoCloseBottomSheetBehavior">

        ... the bottom sheet views

    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>
person Budius    schedule 30.08.2017
comment
Это отлично работает! Небольшой совет... добавление «return true» после вызова «setState» остановит прохождение кликов. В моем случае, если щелкнуть за пределами диалогового окна, я просто хотел, чтобы он рухнул, не передавая событие щелчка для просмотра позади него :) - person Psest328; 10.04.2019
comment
@ Psest328 добавление return true привело к тому, что это перестало работать для меня иногда. Если бы у меня было return true, лист закрывался бы только изредка. - person Emil S.; 20.11.2020
comment
не работает для меня. - person Nouman Ch; 26.01.2021
comment
Отлично работает для меня. Это должен быть принятый ответ. - person Milan Zelenka; 08.06.2021

Для активности:

@Override 
public boolean dispatchTouchEvent(MotionEvent event){
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (mBottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED) {

            Rect outRect = new Rect();
            bottomSheet.getGlobalVisibleRect(outRect);

            if(!outRect.contains((int)event.getRawX(), (int)event.getRawY()))
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

    return super.dispatchTouchEvent(event);
}

Для фрагмента: используйте тот же метод в Activity, например,

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        if (fragment != null && fragment instanceof HomeFragment) {
            ((HomeFragment) fragment).hideBottomSheetFromOutSide(event);
        }
    }
    return super.dispatchTouchEvent(event);
}

и создайте метод во фрагменте, например:

   /**
     * Calling from Dashboard Activity
     *
     * @param event Motion Event
     */
    public void hideBottomSheetFromOutSide(MotionEvent event) {
        if (isBottomSheetMenuExpanded()) {
            Rect outRect = new Rect();
            mBinding.homeBottomSheetLayout.getGlobalVisibleRect(outRect);
            if (!outRect.contains((int) event.getRawX(), (int) event.getRawY()))
                mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }

Надеюсь, это поможет вам.

Спасибо.

person Pratik Butani    schedule 03.10.2018
comment
привет, это работает с. нижний лист диалогового окна? - person famfamfam; 13.03.2021
comment
Я не проверял. Вы можете попробовать это. - person Pratik Butani; 13.03.2021
comment
проверил сейчас, не работает, точка останова не срабатывает - person famfamfam; 13.03.2021
comment
Это работает, но хорошее ли это решение? Поскольку каждый раз, когда мы касаемся экрана или прокручиваем его, этот метод dispatchTouchEvent называется серверным временем, что может отрицательно сказаться на использовании батареи и производительности приложений. - person ankalagba; 05.06.2021
comment
Да, это так. Дайте мне знать, если вы найдете какое-либо другое хорошее решение. - person Pratik Butani; 07.06.2021

Установите прослушиватель кликов для вашего основного макета (в данном случае координатный макет)

@OnClick(R.id.coordinateLayout)
public void onClickView(View view) {
    if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
        sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    }
}

Примечание. Butterknife используется для щелчка, в противном случае используйте приведенный ниже код в onCreate действия.

CoordinateLayout layout = (CoordinateLayout) findViewById(R.id. coordinateLayout);
layout.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
            sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }
    }
});
person Abiranjan    schedule 18.01.2018

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

BottomSheetDialog dialog = new BottomSheetDialog(context);
dialog.setContentView(R.layout.bottom_sheet);
dialog.setCanceledOnTouchOutside(true);
dialog.show(); 
person kapsid    schedule 04.12.2019

someViewToClickOn.setOnClickListener(v -> 
    behavior.setState(BottomSheetBehavior.STATE_HIDDEN));

Это тоже работает! Сначала я использовал BottomSheetBehavior.STATE_COLLAPSED, который не работает

person Boy    schedule 27.09.2018

Многие люди находят способ реализовать dispatchTouchEvent на фрагменте. Итак, вот как они могут это сделать:

создайте пользовательский макет, как определено:

public class DispatchTouchEvent extends LinearLayout {

    public interface onDispatchEvent
    {
        void dispatchEvent(MotionEvent e);
    }

    private onDispatchEvent dispatchEvent;

    public DispatchTouchEvent(Context context) {
        super(context);
    }

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

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

    public void setDispatchEvent(onDispatchEvent dispatchEvent)
    {
        this.dispatchEvent=dispatchEvent;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(dispatchEvent!=null)
        {
            dispatchEvent.dispatchEvent(ev);
        }
        return super.dispatchTouchEvent(ev);
    }

}

Теперь используйте этот макет в качестве основы макета вашего фрагмента. Внутри фрагмента инициализируйте этот макет как:

public class ABC extends fragment implements DispatchTouchEvent.onDispatchEvent
{

DispatchTouchEvent dispatchTouchEvent;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
....
    dispatchTouchEvent = (DispatchTouchEvent)rootView.findViewById(R.id.dispatch_event);
    dispatchTouchEvent.setDispatchEvent(this);
....
}

@Override
public void dispatchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
    if (mBottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED) 
    {

        Rect outRect = new Rect();
        bottomSheet.getGlobalVisibleRect(outRect);

        if(!outRect.contains((int)event.getRawX(), (int)event.getRawY()))   
         mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    }
    }

    }

}
person Pradeep Kumar Kushwaha    schedule 27.07.2017

Я обнаружил проблемы с UX при использовании ответа OP или версии AutoCloseBottomSheetBehavior.java (от Budius). Я изменил код AutoCloseBottomSheetBehavior таким образом, который кажется мне более чистым и не вызывает никаких проблем с UX (код находится в Kotlin):

class AutoCloseBottomSheetBehavior<V : View>(
        context: Context,
        attrs: AttributeSet
) : BottomSheetBehavior<V>(context, attrs) {

    private val gestureDetector: GestureDetectorCompat =
            GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() {

                override fun onDown(event: MotionEvent?): Boolean {
                    return true
                }

                override fun onSingleTapUp(e: MotionEvent?): Boolean =
                        when {
                            state == STATE_EXPANDED -> {
                                state = STATE_COLLAPSED
                                true
                            }
                            state == STATE_COLLAPSED && isHideable -> {
                                state = STATE_HIDDEN
                                true
                            }
                            else -> false
                        }
            })

    @SuppressLint("ClickableViewAccessibility")
    override fun onLayoutChild(parent: CoordinatorLayout, child: V, layoutDirection: Int): Boolean {
        parent.setOnTouchListener { _, event ->
            gestureDetector.onTouchEvent(event)
        }
        return super.onLayoutChild(parent, child, layoutDirection)
    }
}

Это сворачивает/скрывает нижний лист всякий раз, когда пользователь выполняет одно нажатие на родительском CoordinatorLayout, а также обрабатывает случай, когда нижний лист можно скрыть (и мы хотим скрыть его, если он находится в состоянии COLLAPSED).

person Bozhidar Stoyanov    schedule 21.08.2020

для меня это был простой setCancelable(true); т.е.

@Override
public void setupDialog(Dialog dialog, int style) {
    super.setupDialog(dialog, style);

    View contentView = View.inflate(getContext(), R.layout.layout_additional_prices, null);
    unbinder = ButterKnife.bind(this, contentView);
    dialog.setContentView(contentView);
    dialog.setOnKeyListener(new BottomSheetBackDismissListener());
    //makeBottomSheetFullScreen(getActivity(), mBottomSheetBehaviorCallback, contentView);
    setCancelable(true);
}
person The Billionaire Guy    schedule 14.03.2021