Отключение перетаскивания пользователя на BottomSheet
Я пытаюсь отключить перетаскивание пользователя BottomSheet
. Почему я хочу отключить это две вещи. 1. Это предотвращает ListView
прокрутить вниз, 2. Я не хочу, чтобы пользователи увольняли с помощью перетаскивания, но с помощью кнопки на BottomSheetView
. Вот что я сделал!--6-->
bottomSheetBehavior = BottomSheetBehavior.from(bottomAnc);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
//Log.e("BottomSheet", "Expanded");
} else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
//Log.e("BottomSheet", "Collapsed");
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
// React to dragging events
bottomSheet.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
switch (action) {
case MotionEvent.ACTION_DOWN:
return false;
default:
return true;
}
}
});
}
});
bottomSheetLayout
<?xml version="1.0" encoding="utf-8"?><FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior"
android:id="@+id/bottomSheet">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:elevation="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/text1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Order Items"
android:layout_margin="16dp"
android:textAppearance="@android:style/TextAppearance.Large"/>
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginRight="5dp"
android:background="@drawable/bg_accept"/>
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:background="@drawable/bg_cancel"/>
</LinearLayout>
<ListView
android:id="@+id/item_edit"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:divider="@color/md_divider_black"
android:dividerHeight="1dp"/>
</LinearLayout>
</android.support.v7.widget.CardView>
16 ответов
Это может быть уже не актуально, но я оставлю его здесь:
import android.content.Context;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by vitaliiobideiko on 10/5/16.
*/
public class UserLockBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
public UserLockBottomSheetBehavior() {
super();
}
public UserLockBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
return false;
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
return false;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
return false;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {}
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
return false;
}
}
он отключает все действия пользователей, его можно использовать, когда вы хотите управлять BottomSheet только программно. Вы можете отредактировать его и создать логический флаг для вызова super
методы вместо return false
.
проверить состояние в onStateChanged
метод setBottomSheetCallback
если государство BottomSheetBehavior.STATE_DRAGGING
затем измените его на BottomSheetBehavior.STATE_EXPANDED
таким образом, вы можете остановить STATE_DRAGGING
пользователем. как ниже
final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
открыть закрыть нижний лист, как показано ниже
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (behavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} else {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
});
не используйте setPeekHeight
или app:behavior_peekHeight
выше способом вы можете достичь своей цели
Я закончил писать обходной путь для решения этого случая использования динамического отключения перетаскивания пользователя, в результате чего BottomSheetBehavior подкласс для переопределения onInterceptTouchEvent и игнорировать его, когда пользовательский флаг( в данном случае mAllowUserDragging) установлен в false:
import android.content.Context;
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 WABottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
private boolean mAllowUserDragging = true;
/**
* Default constructor for instantiating BottomSheetBehaviors.
*/
public WABottomSheetBehavior() {
super();
}
/**
* Default constructor for inflating BottomSheetBehaviors from layout.
*
* @param context The {@link Context}.
* @param attrs The {@link AttributeSet}.
*/
public WABottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setAllowUserDragging(boolean allowUserDragging) {
mAllowUserDragging = allowUserDragging;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
if (!mAllowUserDragging) {
return false;
}
return super.onInterceptTouchEvent(parent, child, event);
}
}
и в вашем макете xml:
<FrameLayout
android:id="@+id/bottom_sheet_frag_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:behavior_hideable="true"
app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
app:elevation="@dimen/bottom_sheet_elevation"
app:layout_behavior="com.example.ray.WABottomSheetBehavior" />
до сих пор это наиболее последовательно действующее решение для отключения перетаскивания пользователя на нижнем листе по требованию.
все остальные решения, которые полагались на запуск другого вызова setState в обратном вызове onStateChanged, привели к тому, что BottomSheet попал в плохое состояние или вызывает значительные проблемы UX (в случае публикации вызова setState в Runnable).
надеюсь, это поможет кому-то:)
Рэй
хорошо, поэтому принятый ответ не сработал для меня. Однако,Виталий Обидейко это!--5--> вдохновил меня на окончательное решение.
во-первых, я создал следующий пользовательский BottomSheetBehavior. Он переопределяет все методы, включающие touch, и возвращает false (или ничего не сделал), если он заблокирован. В противном случае он действует как обычный BottomSheetBehavior. Это отключает возможность перетаскивания пользователя и не влияет на изменение состояния в код.
LockableBottomSheetBehavior.java
public class LockableBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
private boolean mLocked = false;
public LockableBottomSheetBehavior() {}
public LockableBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setLocked(boolean locked) {
mLocked = locked;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
boolean handled = false;
if (!mLocked) {
handled = super.onInterceptTouchEvent(parent, child, event);
}
return handled;
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
boolean handled = false;
if (!mLocked) {
handled = super.onTouchEvent(parent, child, event);
}
return handled;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
boolean handled = false;
if (!mLocked) {
handled = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
return handled;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
if (!mLocked) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
if (!mLocked) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
}
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
boolean handled = false;
if (!mLocked) {
handled = super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
return handled;
}
}
вот пример того, как его использовать. В моем случае мне нужно было, чтобы нижний лист был заблокирован при расширении.
activity_home.в 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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|snap"
app:titleEnabled="false"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</android.support.design.widget.AppBarLayout>
<!-- Use layout_behavior to set your Behavior-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layout_behavior="com.myapppackage.LockableBottomSheetBehavior"/>
</android.support.design.widget.CoordinatorLayout>
HomeActivity.java
public class HomeActivity extends AppCompatActivity {
BottomSheetBehavior mBottomSheetBehavior;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setAdapter(new SomeAdapter());
mBottomSheetBehavior = BottomSheetBehavior.from(recyclerView);
mBottomSheetBehavior.setBottomSheetCallback(new MyBottomSheetCallback());
}
class MyBottomSheetCallback extends BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_EXPANDED) {
if (mBottomSheetBehavior instanceof LockableBottomSheetBehavior) {
((LockableBottomSheetBehavior) mBottomSheetBehavior).setLocked(true);
}
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
});
}
надеюсь, это поможет прояснить много путаницы!
принятый ответ не работает на первом тестовом устройстве, которое я использую. И отскок назад не гладкий. Кажется, лучше установить состояние STATE_EXPANDED только после того, как пользователь отпустит перетаскивание. Ниже приводится моя версия:
final BottomSheetBehavior behavior = BottomSheetBehavior.from(findViewById(R.id.bottomSheet));
behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState > BottomSheetBehavior.STATE_DRAGGING)
bottomSheet.post(new Runnable() {
@Override public void run() {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
});
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
чтобы заблокировать нижний лист и избежать пользователя, чтобы провести его, это то, что я сделал
public void showBottomSheet() {
bsb.setHideable(false);
bsb.setState(BottomSheetBehavior.STATE_EXPANDED);
}
public void hideBottomSheet() {
bsb.setHideable(true);
bsb.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
это работает довольно хорошо для меня.
вам не нужно блокировать все события, когда нижний лист отключен. Можно заблокировать только событие ACTION_MOVE. Вот почему используйте пользовательское поведение нижнего листа, как это
public class BottomSheetBehaviorWithDisabledState<V extends View> extends BottomSheetBehavior<V> {
private boolean enable = true;
/**
* Default constructor for instantiating BottomSheetBehaviors.
*/
public BottomSheetBehaviorWithDisabledState() {
super();
}
/**
* Default constructor for inflating BottomSheetBehaviors from layout.
*
* @param context The {@link Context}.
* @param attrs The {@link AttributeSet}.
*/
public BottomSheetBehaviorWithDisabledState(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setEnable(boolean enable){
this.enable = enable;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
if (!enable && event.getAction() == MotionEvent.ACTION_MOVE){
return false;
}
return super.onInterceptTouchEvent(parent, child, event);
}
}
простой способ блокировки перетаскивания-setPeekHeight такой же, как высота просмотра. Например:
private LinearLayout bottomSheet;
private BottomSheetBehavior bottomBehavior;
@Override
public void onResume() {
super.onResume();
bottomBehavior = BottomSheetBehavior.from((bottomSheet);
bottomBehavior.setPeekHeight(bottomSheet.getHeight());
bottomBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
Я нашел потрясающее решение. Первоначальная проблема заключалась в том, что bottomSheet переходил в скрытое состояние, а затем не появлялся в bottomSheetDialog.шоу.)( Но я хотел, чтобы диалог был виден в методе show (), а также хотел, чтобы пользователь мог провести его вниз, чтобы он чувствовал себя как нижний лист. Вот что я сделал..
BottomSheetDialog itemTypeDialog = new BottomSheetDialog(this);
View bottomSheetView = getLayoutInflater().inflate(R.layout.dialog_bottomsheet, null);
itemTypeDialog.setContentView(bottomSheetView);
BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from((View) bottomSheetView.getParent());
bottomSheetBehavior.setBottomSheetCallback(bottomSheetCallback); // You can also attach the listener here itself.
BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
Log.d(TAG, "BottomSheetCallback: " + newState);
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
itemTypeDialog.dismiss();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
};
поздний ответ, но это то, что сработало для меня, что немного отличается от того, что предложили другие.
вы можете попробовать установить cancelable
свойство false, т. е.
setCancelable(false);
а затем вручную обрабатывать события, где вы хотели бы закрыть диалоговое окно в setupDialog
метод.
@Override
public void setupDialog(final Dialog dialog, final int style) {
// handle back button
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(final DialogInterface dialog, final int keyCode, final KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
dialog.dismiss();
}
return true;
}
});
// handle touching outside of the dialog
final View touchOutsideView = getDialog().getWindow().getDecorView().findViewById(android.support.design.R.id.touch_outside);
touchOutsideView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View view) {
dialog.dismiss();
}
});
}
это работает с ListView внутри фрагмента диалога, где я немного застрял с другими решениями.
добавить этот код BottomSheetBehavior "объект". Перетаскивание будет отключено. Мне подходит.
final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) view.getParent());
behavior.setHideable(false);
behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
- скопировать
BottomSheetDialog
к вашему проекту и переименуйте вMyBottomSheetDialog
- добавить
getBottomSheetBehavior
доMyBottomSheetDialog
- использовать
MyBottomSheetDialog
вместоBottomSheetDialog
- bottomSheetBehavior.setBottomSheetCallback
код
public class MyBottomSheetDialog extends AppCompatDialog {
// some code
public BottomSheetBehavior<FrameLayout> getBottomSheetBehavior() {
return mBehavior;
}
// more code
код
final BottomSheetBehavior<FrameLayout> bottomSheetBehavior = myBottomSheetDialog.getBottomSheetBehavior();
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_DRAGGING) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
это в основном версия Котлина правильного ответа вверху:
class UserLockBottomSheetBehavior<V : View> : BottomSheetBehavior<V> {
companion object {
fun <V : View> from(view: V): UserLockBottomSheetBehavior<V> {
val params = view.layoutParams as? CoordinatorLayout.LayoutParams
?: throw IllegalArgumentException("The view is not a child of CoordinatorLayout")
val behavior = params.behavior as? UserLockBottomSheetBehavior<*>
?: throw IllegalArgumentException(
"The view is not associated with BottomSheetBehavior")
return behavior as UserLockBottomSheetBehavior<V>
}
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
return false
}
override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
return false
}
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
return false
}
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {}
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {}
override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: V, target: View, velocityX: Float, velocityY: Float): Boolean {
return false
}
}
вот рабочая версия верхнего решения в Котлине:
import android.support.design.widget.BottomSheetBehavior
import android.support.design.widget.CoordinatorLayout
import android.view.MotionEvent
import android.view.View
class CustomBottomSheetBehavior<V : View> : BottomSheetBehavior<V>() {
@Suppress("UNCHECKED_CAST")
companion object {
fun <V : View> from(view: V): CustomBottomSheetBehavior<V> {
val params = view.layoutParams as? CoordinatorLayout.LayoutParams ?:
throw IllegalArgumentException("The view is not a child of CoordinatorLayout")
params.behavior as? BottomSheetBehavior<V> ?:
throw IllegalArgumentException("The view is not associated with BottomSheetBehavior")
params.behavior = CustomBottomSheetBehavior<V>()
return params.behavior as CustomBottomSheetBehavior<V>
}
}
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
return false
}
override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
return false
}
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
return false
}
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {}
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {}
override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: V, target: View, velocityX: Float, velocityY: Float): Boolean {
return false
}
}
тогда, когда вы хотите использовать:
val bottomSheetBehavior by lazy {
CustomBottomSheetBehavior.from(bottom_sheet_main)
}
на bottom_sheet_main
фактическое представление с помощью Котлин Android Расширения.
установите bottomSheet onClickListener в значение null.
bottomSheet.setOnClickListener(null);
эта строка отключает все действия только для bottomSheet и не влияет на внутренний вид.
настройка peakHeight
стоимость работал для меня.
Я устанавливаю peakheight как высоту нижнего листа, если он расширен.
private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_EXPANDED)
bottomSheetBehavior.peekHeight = bottomSheet.height
}
}