我有一个SlidingUpPanelLayout,它将图片保存为顶视图,还有一个需要滑动的视图寻呼机。
viewpager
有3个片段,其中两个是列表视图。所以我希望能够在拉起时扩展视图寻呼机,一旦视图寻呼机启动,我希望能够在片段内滚动scrollviews
。但是,如果没有更多要滚动的情况下拉scrollview
,我想开始折叠viewpager
。
因此,如果没有更多要滚动的内容,请建议如何在拉动滚动视图时使SlidingUpPanelLayout
崩溃?
这里我发布一些代码:
我尝试捕获触摸事件并以下列方式覆盖SlidingUpPanel onInterceptTouchEvent
函数:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (isHandled) {
Log.i("interceptToch", "HEREEE");
return onTouchEvent(ev);
}
return false;
}
因此,当SlidingUpPanelLayout展开时,我设置了isHandled = false
。因此,当slidingUpPanelLayout展开时,所有触摸事件都将传递给其子视图。
我还将onTouchEvent
放入scrollView
,以便取消阻止SlidingUpPanelLayout.onInterceptTouchEvent
:
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
scroll = 0;
y = event.getY();
} else if (action == MotionEvent.ACTION_MOVE) {
if (scroll_view_summary.getScrollY() == 0 && event.getY() > y) {
scroll = scroll + 1;
if (scroll > 2) {
// the user has pulled the list and the slidingUpPanelLauout
// should be able to handle the toch events again
SlidingUpPanelLayoutCustom las =
((SaleDetailsActivity) getActivity()).getLayout();
las.setHandle(true);
scroll = 0;
return false;
}
}
}
return false;
}
但这不起作用。问题是,scrollview.onTouch
MotionEvent.ACTION_MOVE
事件在SlidingUpPanelLayout.onInterceptTouchEvent
SlidingUpPanelLayout.onInterceptTouchEvent
内未被调用。在MotionEvent.ACTION_CANCEL
之后调用{{1}}。这意味着事件无法传递到SlidingUpPanelLayout,面板也无法崩溃。
答案 0 :(得分:15)
不幸的是,出于上述原因,您无法依赖SlidingUpPanelLayout的onInterceptTouchEvent
方法。一旦子视图的onTouchEvent
方法返回true
,就不再调用onInterceptTouchEvent
。
我的解决方案有点令人费解,但它可以让您完全达到(我认为)您正在寻找的目标。单个触摸/拖动事件会将面板拖动到位,一旦就位,继续滚动子视图。同样,当向下拖动时,单个触摸/拖动事件可以滚动子视图,并且一旦完全滚动,将开始向下拖动面板。
更新2015-04-12 已更新至SlidingUpPanelLayout代码的3.0.0版。还要考虑ListViews而不仅仅是ScrollViews。
<强> 1)强>
在SlidingUpPanel的库项目的res/
文件夹中,打开attrs.xml
并添加
<attr name="scrollView" format="reference" />
您将使用此功能识别单个子视图,该视图会在面板拖动到位后篡改触摸事件。在布局xml文件中,您可以添加
sothree:scrollView="@+id/myScrollView"
或者您的scrollView的ID是什么。另外,请确保您没有声明sothree:dragView
ID,因此整个视图都可以拖动。
其余步骤均在SlidingUpPanelLayout.java
...
<强> 2)强> 声明以下变量:
View mScrollView;
int mScrollViewResId = -1;
boolean isChildHandlingTouch = false;
float mPrevMotionX;
float mPrevMotionY;
3)在构造函数中,在设置mDragViewResId
之后,添加以下行:
mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1);
<强> 4)强>
在onFinishInflate
中,添加以下代码:
if (mScrollViewResId != -1) {
mScrollView = findViewById(mScrollViewResId);
}
<强> 5)强> 添加以下方法:
private boolean isScrollViewUnder(int x, int y) {
if (mScrollView == null)
return false;
int[] viewLocation = new int[2];
mScrollView.getLocationOnScreen(viewLocation);
int[] parentLocation = new int[2];
this.getLocationOnScreen(parentLocation);
int screenX = parentLocation[0] + x;
int screenY = parentLocation[1] + y;
return screenX >= viewLocation[0] &&
screenX < viewLocation[0] + mScrollView.getWidth() &&
screenY >= viewLocation[1] &&
screenY < viewLocation[1] + mScrollView.getHeight();
}
<强> 6)强>
删除onInterceptTouchEvent
。
<强> 7)强>
将onTouchEvent
修改为以下内容:
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled() || !isTouchEnabled()) {
return super.onTouchEvent(ev);
}
try {
mDragHelper.processTouchEvent(ev);
final int action = ev.getAction();
boolean wantTouchEvents = false;
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_UP: {
final float x = ev.getX();
final float y = ev.getY();
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
View dragView = mDragView != null ? mDragView : mSlideableView;
if (dx * dx + dy * dy < slop * slop &&
isDragViewUnder((int) x, (int) y) &&
!isScrollViewUnder((int) x, (int) y)) {
dragView.playSoundEffect(SoundEffectConstants.CLICK);
if ((PanelState.EXPANDED != mSlideState) && (PanelState.ANCHORED != mSlideState)) {
setPanelState(PanelState.ANCHORED);
} else {
setPanelState(PanelState.COLLAPSED);
}
break;
}
break;
}
}
return wantTouchEvents;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
<强> 8)强> 添加以下方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Identify if we want to handle the touch event in this class.
// We do this here because we want to be able to handle the case
// where a child begins handling a touch event, but then the
// parent takes over. If we rely on onInterceptTouchEvent, we
// lose control of the touch as soon as the child handles the event.
if (mScrollView == null)
return super.dispatchTouchEvent(ev);
final int action = MotionEventCompat.getActionMasked(ev);
final float x = ev.getX();
final float y = ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
// Go ahead and have the drag helper attempt to intercept
// the touch event. If it won't be dragging, we'll cancel it later.
mDragHelper.shouldInterceptTouchEvent(ev);
mInitialMotionX = mPrevMotionX = x;
mInitialMotionY = mPrevMotionY = y;
isChildHandlingTouch = false;
} else if (action == MotionEvent.ACTION_MOVE) {
float dx = x - mPrevMotionX;
float dy = y - mPrevMotionY;
mPrevMotionX = x;
mPrevMotionY = y;
// If the scroll view isn't under the touch, pass the
// event along to the dragView.
if (!isScrollViewUnder((int) x, (int) y))
return this.onTouchEvent(ev);
// Which direction (up or down) is the drag moving?
if (dy > 0) { // DOWN
// Is the child less than fully scrolled?
// Then let the child handle it.
if (isScrollViewScrolling()) {
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
// Was the child handling the touch previously?
// Then we need to rejigger things so that the
// drag panel gets a proper down event.
if (isChildHandlingTouch) {
// Send an 'UP' event to the child.
MotionEvent up = MotionEvent.obtain(ev);
up.setAction(MotionEvent.ACTION_UP);
super.dispatchTouchEvent(up);
up.recycle();
// Send a 'DOWN' event to the panel. (We'll cheat
// and hijack this one)
ev.setAction(MotionEvent.ACTION_DOWN);
}
isChildHandlingTouch = false;
return this.onTouchEvent(ev);
} else if (dy < 0) { // UP
// Is the panel less than fully expanded?
// Then we'll handle the drag here.
if (mSlideOffset < 1.0f) {
isChildHandlingTouch = false;
return this.onTouchEvent(ev);
}
// Was the panel handling the touch previously?
// Then we need to rejigger things so that the
// child gets a proper down event.
if (!isChildHandlingTouch) {
mDragHelper.cancel();
ev.setAction(MotionEvent.ACTION_DOWN);
}
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
} else if ((action == MotionEvent.ACTION_CANCEL) ||
(action == MotionEvent.ACTION_UP)) {
if (!isChildHandlingTouch) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if ((mIsUsingDragViewTouchEvents) && (dx * dx + dy * dy < slop * slop))
return super.dispatchTouchEvent(ev);
return this.onTouchEvent(ev);
}
}
// In all other cases, just let the default behavior take over.
return super.dispatchTouchEvent(ev);
}
9)添加以下方法以确定scrollView是否仍在滚动。处理ScrollView和ListView的案例:
/**
* Computes the scroll position of the the scrollView, if set.
* @return
*/
private boolean isScrollViewScrolling() {
if (mScrollView == null)
return false;
// ScrollViews are scrolling when getScrollY() is a value greater than 0.
if (mScrollView instanceof ScrollView) {
return (mScrollView.getScrollY() > 0);
}
// ListViews are scrolling if the first child is not displayed, or if the first child has an offset > 0
else if (mScrollView instanceof ListView) {
ListView lv = (ListView) mScrollView;
if (lv.getFirstVisiblePosition() > 0)
return true;
View v = lv.getChildAt(0);
int top = (v == null) ? (0) : (-v.getTop() + lv.getFirstVisiblePosition() * lv.getHeight());
return top > 0;
}
return false;
}
10)(可选)添加以下方法以允许您在运行时设置scrollView(即您希望在面板中放置一个片段,并且片段的子节点具有要滚动的ScrollView / ListView:
public void setScrollView(View scrollView) {
mScrollView = scrollView;
}
我们现在完全管理此课程中触摸事件的处理。如果我们向上拖动面板并将其完全滑动到位,我们会取消拖动,然后在mScrollView
子项中欺骗新的触摸。如果我们滚动孩子并到达顶部,我们就会欺骗孩子们#34; up&#34;在孩子身上发生的事件和欺骗新的触摸。这也允许在其他子窗口小部件上点击事件。
已知问题 &#34; up&#34; /&#34; down&#34;我们欺骗的事件可能会无意中触发scrollView的子元素上的click事件。
答案 1 :(得分:6)
我有同样的问题,但在我的应用程序中有ListView而不是ScrollView。我无法应用themarshal的答案来解决我的问题。但我找到了基于themarshal,Chris的答案和Maria Sakharova的评论的解决方案
首先我找不到变量mCanSlide和mIsSlidingEnabled以及方法expandPane(mAnchorPoint)和collapsePane()所以我使用下一个代码:
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!isEnabled() || !isTouchEnabled()) {
return super.onTouchEvent(ev);
}
try {
mDragHelper.processTouchEvent(ev);
final int action = ev.getAction();
boolean wantTouchEvents = false;
switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_UP: {
final float x = ev.getX();
final float y = ev.getY();
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
View dragView = mDragView != null ? mDragView : mSlideableView;
if (dx * dx + dy * dy < slop * slop &&
isDragViewUnder((int) x, (int) y) &&
!isScrollViewUnder((int) x, (int) y)) {
dragView.playSoundEffect(SoundEffectConstants.CLICK);
if (!isExpanded() && !isAnchored()) {
//expandPane(mAnchorPoint);
setPanelState(PanelState.ANCHORED);
} else {
//collapsePane();
setPanelState(PanelState.COLLAPSED);
}
break;
}
break;
}
}
return wantTouchEvents;
} catch (Exception ex){
ex.printStackTrace();
return false;
}
}
需要尝试/捕捉,因为施加两根手指会引发异常。
克里斯的第二个答案是必须履行的。
然后由于ListView的方法,getScrollY()总是返回零,我在方法dispatchTouchEvent(MotionEvent ev)中略微改变代码:
这样:
if (mScrollView.getScrollY() > 0) {
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
为:
if (((ListView)mScrollView).getFirstVisiblePosition() > 0 || getFirstChildTopOffset((ListView) mScrollView) > 0){
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
//at some other place in class SlidingUpPanelLayout
public int getFirstChildTopOffset(ListView list){
View v = list.getChildAt(0);
int top = (v == null) ? 0 : (list.getPaddingTop() - v.getTop());
return top;
}
此外,我的应用程序将谷歌地图作为主要内容,它也必须得到MotionEvent,因为玛丽亚萨哈罗娃说我们必须返回这个.onTouchEvent(ev)|| super.dispatchTouchEvent(ev)而不是this.onTouchEvent(ev)在两个地方。 我们必须更改此代码:
if (!isScrollViewUnder((int) x, (int) y))
return this.onTouchEvent(ev);
为:
if (!isScrollViewUnder((int) x, (int) y))
return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
在这种情况下,如果主要内容必须获取MotionEvent,则需要super.dispatchTouchEvent(ev)。
第二个代码:
} else if ((action == MotionEvent.ACTION_CANCEL) ||
(action == MotionEvent.ACTION_UP)) {
if (!isChildHandlingTouch) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if ((mIsUsingDragViewTouchEvents) &&
(dx * dx + dy * dy < slop * slop))
return super.dispatchTouchEvent(ev);
return this.onTouchEvent(ev);
}
}
到:
} else if ((action == MotionEvent.ACTION_CANCEL) ||
(action == MotionEvent.ACTION_UP)) {
if (!isChildHandlingTouch) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if ((mIsUsingDragViewTouchEvents) &&
(dx * dx + dy * dy < slop * slop))
return super.dispatchTouchEvent(ev);
return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
}
}
在这种情况下,需要super.dispatchTouchEvent(ev)来扩展面板。
总结方法dispatchTouchEvent(MotionEvent ev)将是下一个:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// Identify if we want to handle the touch event in this class.
// We do this here because we want to be able to handle the case
// where a child begins handling a touch event, but then the
// parent takes over. If we rely on onInterceptTouchEvent, we
// lose control of the touch as soon as the child handles the event.
if (mScrollView == null)
return super.dispatchTouchEvent(ev);
final int action = MotionEventCompat.getActionMasked(ev);
final float x = ev.getX();
final float y = ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
// Go ahead and have the drag helper attempt to intercept
// the touch event. If it won't be dragging, we'll cancel it later.
mDragHelper.shouldInterceptTouchEvent(ev);
mInitialMotionX = mPrevMotionX = x;
mInitialMotionY = mPrevMotionY = y;
isChildHandlingTouch = false;
} else if (action == MotionEvent.ACTION_MOVE) {
float dx = x - mPrevMotionX;
float dy = y - mPrevMotionY;
mPrevMotionX = x;
mPrevMotionY = y;
// If the scroll view isn't under the touch, pass the
// event along to the dragView.
if (!isScrollViewUnder((int) x, (int) y))
//return this.onTouchEvent(ev);
return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
// Which direction (up or down) is the drag moving?
if (dy > 0) { // DOWN
// Is the child less than fully scrolled?
// Then let the child handle it.
//if (mScrollView.getScrollY() > 0) {
if (((ListView)mScrollView).getFirstVisiblePosition() > 0 || getFirstChildTopOffset((ListView) mScrollView) > 0){
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
// Was the child handling the touch previously?
// Then we need to rejigger things so that the
// drag panel gets a proper down event.
if (isChildHandlingTouch) {
// Send an 'UP' event to the child.
MotionEvent up = MotionEvent.obtain(ev);
up.setAction(MotionEvent.ACTION_UP);
super.dispatchTouchEvent(up);
up.recycle();
// Send a 'DOWN' event to the panel. (We'll cheat
// and hijack this one)
ev.setAction(MotionEvent.ACTION_DOWN);
}
isChildHandlingTouch = false;
return this.onTouchEvent(ev);
} else if (dy < 0) { // UP
// Is the panel less than fully expanded?
// Then we'll handle the drag here.
//if (mSlideOffset > 0.0f) {
if (mSlideOffset < 1.0f) {
isChildHandlingTouch = false;
return this.onTouchEvent(ev);
//return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
}
// Was the panel handling the touch previously?
// Then we need to rejigger things so that the
// child gets a proper down event.
if (!isChildHandlingTouch) {
mDragHelper.cancel();
ev.setAction(MotionEvent.ACTION_DOWN);
}
isChildHandlingTouch = true;
return super.dispatchTouchEvent(ev);
}
} else if ((action == MotionEvent.ACTION_CANCEL) ||
(action == MotionEvent.ACTION_UP)) {
if (!isChildHandlingTouch) {
final float dx = x - mInitialMotionX;
final float dy = y - mInitialMotionY;
final int slop = mDragHelper.getTouchSlop();
if ((mIsUsingDragViewTouchEvents) &&
(dx * dx + dy * dy < slop * slop))
return super.dispatchTouchEvent(ev);
//return this.onTouchEvent(ev);
return this.onTouchEvent(ev) || super.dispatchTouchEvent(ev);
}
}
// In all other cases, just let the default behavior take over.
return super.dispatchTouchEvent(ev);
}
答案 2 :(得分:3)
从3.1.0开始,Umano SlidingUpPanelLayout支持使用ScrollView,ListView和RecyclerView开箱即用的嵌套滚动。
在大多数情况下,只需在XML布局文件中添加sothree:umanoScrollableView
属性,如下所示:
<com.sothree.slidinguppanel.SlidingUpPanelLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sothree="http://schemas.android.com/apk/res-auto"
android:id="@+id/sliding_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
sothree:umanoScrollableView="@+id/my_scrollable_view"
android:gravity="bottom"
sothree:umanoAnchorPoint="0.3"
sothree:umanoPanelHeight="@dimen/bottom_playlist_height"
sothree:umanoShadowHeight="4dp"
android:paddingTop="?attr/actionBarSize">
有关详细信息,请查看以下链接:https://github.com/umano/AndroidSlidingUpPanel#scrollable-sliding-views
答案 3 :(得分:0)
为了让JJD的答案得以实现,您需要添加另一个步骤
8)添加此方法mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1);
在SlidingPanelLayout
public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
...
if (attrs != null) {
...
if (ta != null) {
...
mScrollViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_scrollView, -1);
...
}
ta.recycle();
}
}
答案 4 :(得分:0)
只需使用setScrollableView
!
示例:
public View onCreateView(
@NonNull LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
((SlidingUpPanelLayout)findViewById(R.id.view))
.setScrollableView(findViewById(R.id.scrollable));
}
可滚动视图可以是RecyclerView
,ListView
,ScrollView
等。
答案 5 :(得分:0)
哇! 4年过去了!但问题仍然存在。至少对于我来说。我在代码的一小部分找到了一个解决方案而没有修改库。它适用于ScrollView。
public class MySlidingUpPanelLayout extends SlidingUpPanelLayout {
public void setScrollViewInside(final ScrollViewInsideSlidingUpPanelLayout scroll){
this.addPanelSlideListener(new PanelSlideListener() {
@Override public void onPanelSlide(View panel, float slideOffset) {}
@Override
public void onPanelStateChanged(View panel, PanelState previousState, PanelState newState) {
if(scroll!=null) {
scroll.setScrollable(getPanelState() == PanelState.EXPANDED);
}
}
});
}
}
public class ScrollViewInsideSlidingUpPanelLayout extends ScrollView {
private boolean scrollable = false;
public void setScrollable(boolean scrollable){
this.scrollable = scrollable;
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (getScrollY() == 0) {
scrollable = false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(scrollable)
return super.onTouchEvent(event);
else
return false;
}
}
用法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
sliding_layout.setScrollViewInside(scroll);
}
答案 6 :(得分:0)
想添加到PAD的答案中。
创建一个新的NestedScrollableViewHelper
类,它将扩展ScrollableViewHelper
按照https://github.com/umano/AndroidSlidingUpPanel#scrollable-sliding-views
,然后在您的mainActivity中设置它:
slidingUpPanelLayout = findViewById(R.id.slidingpanel);
slidingUpPanelLayout.setScrollableViewHelper(new NestedScrollableViewHelper());
如果没有此设置,则在向下滚动时,SlidingUpPanel不会拦截触摸 内部nestedscrollview或scrollview,并向下滚动SlidingUpPanel本身。
向上滚动效果很好。