BottomSheet 中的 ListView
ListView in BottomSheet
我 运行 遇到一个问题,我在 BottomSheet
中有一个简单的 ListView
并且 ListView
有足够的项目来填满屏幕并滚动更多。
当我向下滚动时,一切似乎都正常,但是当我尝试向上滚动时,它正在滚动 BottomSheet
本身并关闭视图,而不是仅仅滚动 ListView
。
一段时间后我找到了解决方案,因为我在这里找不到它,所以我想我会 post 在这里。
解决方案是像这样扩展 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();
这将提供与 ListView
滚动完全兼容的 BottomSheet
。
public class BottomSheetListView extends ListView {
public BottomSheetListView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
View view = (View) getChildAt(getChildCount() - 1);
int diffBottom = (view.getBottom() - (getHeight() + getScrollY()));
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (diffBottom == 0) {
return false;
}
}
/*//Need more improvement on this logic. Do not uncomment
int diffTop = (view.getTop() - (getHeight() + getScrollY()));
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (diffTop < 0) {
return true;
}
}*/
return super.onInterceptTouchEvent(motionEvent);
}
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (canScrollVertically(this)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return super.onTouchEvent(motionEvent);
}
public boolean canScrollVertically(AbsListView absListView) {
boolean canScroll = false;
if (absListView != null && absListView.getChildCount() > 0) {
boolean isOnTop = absListView.getFirstVisiblePosition() != 0 || absListView.getChildAt(0).getTop() != 0;
boolean isAllItemsVisible = isOnTop && getLastVisiblePosition() == absListView.getChildCount();
if (isOnTop || isAllItemsVisible)
canScroll = true;
}
return canScroll;
}
}
如果你不想扩展 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;
}
});
我知道这有点乱七八糟,但它对我有用,甚至列表视图都是可选的。
正如我们所知,当 bottom sheet 改变它的状态时,它可以被它的监听器捕获,所以如果 listview 没有触及顶部,不要让它改变状态,此后 touch 事件将被传递给 listview 并将作为确实如此。
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);
}
这是带有触摸事件处理的正确列表视图自定义 class
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;
}
}
从 版本 22.1.0 开始,您可能想尝试 setNestedScrollingEnabled=true
If this property is set to true the view will be permitted to initiate nested scrolling operations with a compatible parent view in the current hierarchy. If this view does not implement nested scrolling this will have no effect. Disabling nested scrolling while a nested scroll is in progress has the effect of stopping the nested scroll.
我已经尝试过此处描述的嵌套 ListView,但它们不允许在触摸 ListView
时展开/折叠底部 sheet。下面是我的解决方案,当你向上滚动而ListView
无法向上滚动时,底部sheet将展开,当你向下滚动且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();
}
}
只需将 android:nestedScrollingEnabled
设置为 true
布局为 ListView
属性或 listView.setNestedScrollingEnabled(true)
在 java.
我 运行 遇到一个问题,我在 BottomSheet
中有一个简单的 ListView
并且 ListView
有足够的项目来填满屏幕并滚动更多。
当我向下滚动时,一切似乎都正常,但是当我尝试向上滚动时,它正在滚动 BottomSheet
本身并关闭视图,而不是仅仅滚动 ListView
。
一段时间后我找到了解决方案,因为我在这里找不到它,所以我想我会 post 在这里。
解决方案是像这样扩展 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();
这将提供与 ListView
滚动完全兼容的 BottomSheet
。
public class BottomSheetListView extends ListView {
public BottomSheetListView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
View view = (View) getChildAt(getChildCount() - 1);
int diffBottom = (view.getBottom() - (getHeight() + getScrollY()));
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (diffBottom == 0) {
return false;
}
}
/*//Need more improvement on this logic. Do not uncomment
int diffTop = (view.getTop() - (getHeight() + getScrollY()));
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
if (diffTop < 0) {
return true;
}
}*/
return super.onInterceptTouchEvent(motionEvent);
}
@Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (canScrollVertically(this)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return super.onTouchEvent(motionEvent);
}
public boolean canScrollVertically(AbsListView absListView) {
boolean canScroll = false;
if (absListView != null && absListView.getChildCount() > 0) {
boolean isOnTop = absListView.getFirstVisiblePosition() != 0 || absListView.getChildAt(0).getTop() != 0;
boolean isAllItemsVisible = isOnTop && getLastVisiblePosition() == absListView.getChildCount();
if (isOnTop || isAllItemsVisible)
canScroll = true;
}
return canScroll;
}
}
如果你不想扩展 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;
}
});
我知道这有点乱七八糟,但它对我有用,甚至列表视图都是可选的。 正如我们所知,当 bottom sheet 改变它的状态时,它可以被它的监听器捕获,所以如果 listview 没有触及顶部,不要让它改变状态,此后 touch 事件将被传递给 listview 并将作为确实如此。
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);
}
这是带有触摸事件处理的正确列表视图自定义 class
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;
}
}
从 版本 22.1.0 开始,您可能想尝试 setNestedScrollingEnabled=true
If this property is set to true the view will be permitted to initiate nested scrolling operations with a compatible parent view in the current hierarchy. If this view does not implement nested scrolling this will have no effect. Disabling nested scrolling while a nested scroll is in progress has the effect of stopping the nested scroll.
我已经尝试过此处描述的嵌套 ListView,但它们不允许在触摸 ListView
时展开/折叠底部 sheet。下面是我的解决方案,当你向上滚动而ListView
无法向上滚动时,底部sheet将展开,当你向下滚动且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();
}
}
只需将 android:nestedScrollingEnabled
设置为 true
布局为 ListView
属性或 listView.setNestedScrollingEnabled(true)
在 java.