1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 【Android 手势冲突】Colin带你彻底解决RecyclerView与ScrollView滑动冲突问题 并实

【Android 手势冲突】Colin带你彻底解决RecyclerView与ScrollView滑动冲突问题 并实

时间:2021-12-02 15:50:46

相关推荐

【Android 手势冲突】Colin带你彻底解决RecyclerView与ScrollView滑动冲突问题 并实

在新一期的需求中,产品要求我们做出和美团某个页面类似的功能,即一个页面包含在scrollView中,上面一个部分放置一些常用的广告banner、宫格tab等,下面放置一个RecyclerView用于展示具体的产品列表。

要想实现上述功能,不可避免地要用到ScrollView嵌套RecyclerView。为什么要用RecyclerView?因为下面的产品列表项非常多,有60条,如果一次性加载到内存里肯定不现实,所以下方一定要用到可复用的RecyclerView。

而RecyclerView和ScrollView怎么嵌套使用呢?在以前,我总是习惯性地把RecyclerView设置为wrap_content,并且把RecyclerView的setNestedScrollingEnbaled设置为false,这样从来没有遇到过滑动冲突的问题,并且我看到团队里的很多大咖也是这么用。

然而,我们的产品有个需求是在滑动RecyclerView的过程中,RecyclerView顶部的悬停导航栏是要跟着滑动的,于是我就想到在RecyclerView的addOnScrollingListener里设置监听,并且利用linearLayouManager的findLastVisibleItemPosition、findFirstVisibleItemPosition、getChildCount这几个方法来判断当前滑动到RecyclerView的什么位置了,然后去对顶部悬停的导航栏进行联动。问题出现了。无论我怎么滑动,firstVisiblePosition永远为0,lastVisiblePosition永远为item总数-1,getChildCount永远为item总数。WTF,这是什么情况?后来查看资料发现,把RecyclerView高度设置为wrap_content居然是把所有的item都一次性加载进来,并没有用到复用和回收!!!!

对于一直强调代码性能的我,这绝对是我无法忍受的。那么,在为RecyclerView设置一个高度,并把setNestedScrollingEnabled(是否允许嵌套滑动)方法设置为true之后,滑动冲突问题出现了。那么,怎么解决呢?

只需要对ScrollView进行简单的修改,就可以实现。实现原理是,在进到页面中默认把滑动事件交给ScrollView,同时屏蔽RecyclerView的滑动事件;在RecyclerView滑动到顶部的时候,把滑动事件交给RecyclerView。

那么,怎么判断RecyclerView是否滑动到了屏幕顶部了呢?实现方法也是非常简单!通过recyclerview的getTop方法得到recyclerview距离顶部的距离,然后通过scrollView的getScrollY方法得到ScrollView滑动的距离。只需要比较这两个值就可以了。这里,我设置了两个接口回调,在Activity里设置ReyclerView的setNestedScrollingEnabled方法。

package com.example.zhshan.hoveringScrollView;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.View;import android.widget.LinearLayout;import android.widget.ScrollView;/*** @author Zhenhua on /5/24 11:15.* @email zhshan@*/public class MyscrollView extends ScrollView{public MyscrollView(Context context) {super(context);}public MyscrollView(Context context, AttributeSet attrs) {super(context, attrs);}public MyscrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}View view;@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);if(changed){LinearLayout v = (LinearLayout) getChildAt(0);if(v != null){for(int i=0;i<v.getChildCount();i++){if(v.getChildAt(i).getTag() != null && ((String)v.getChildAt(i).getTag()).equals("aaa")){view = v.getChildAt(i);break;}}}}}@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if(getScrollY() >= view.getTop()){fixHead();

//此处代码为实现悬停导航栏,如果只是单纯想解决滑动冲突,可删掉

canvas.save();canvas.translate(0,getScrollY());canvas.clipRect(0,0,view.getWidth(),view.getHeight());view.draw(canvas);canvas.restore();}else {resetHead();}}private OnFixHeadListener listener;private void fixHead() {if (listener != null) {listener.onFix();}}private void resetHead() {if (listener != null) {listener.onReset();}}public void setFixHeadListener(OnFixHeadListener listener) {this.listener = listener;}public interface OnFixHeadListener {void onFix();void onReset();}}canvas.save();canvas.translate(0,getScrollY());canvas.clipRect(0,0,view.getWidth(),view.getHeight());view.draw(canvas);canvas.restore();}else {resetHead();}}private OnFixHeadListener listener;private void fixHead() {if (listener != null) {listener.onFix();}}private void resetHead() {if (listener != null) {listener.onReset();}}public void setFixHeadListener(OnFixHeadListener listener) {this.listener = listener;}public interface OnFixHeadListener {void onFix();void onReset();}}

通过这样的方法能够非常完美的实现 解决RecyclerView和ScrollView滑动冲突,与RecyclerView悬停导航栏功能。

下面附上demo(点击下载),并贴上两张demo截图。

PS: demo中也完美地实现了ReyclerView指定item置顶功能。

~~~~~~~华丽丽的分割线:问题进一步升级!!5.0以下手机无法解决滑动冲突问题~~~~~~~~~~

1、问题的背景:在RecyclerView需要把滑动事件处理权力交给ScrollView时,调用RecyclerView的setnestedscrollenable(false)方法;在ScrollView需要把滑动事件处理权力交给RecyclerView时,调用RecyclerView的setnestedscrollenable(true)方法。本以为这样就可以完美解决滑动冲突问题,然而测试却在我提测之后第一天就提了bug,我心灰意冷地打开后发现,5.0以下的手机仍然存在滑动冲突问题。去查了下recyclerview的setnestedscrollenable方法的文档才发现,这个方法只有在5.0以上手机有用。擦。。

2、问题如何得到解决?

这个时候就只能按照最原始的方法,根据Android的事件传递原理去一步步解决滑动冲突了。其实,我在的时候就解决过一个滑动冲突问题,并做了一些总结。解决滑动冲突问题其实很简单!!

解决滑动冲突的原理:

(1)刚开始把滑动事件给ScrollView,在ScrollView滑动到某一个位置时,再把滑动事件给RecyclerView。

(2)在RecyclerView滑动到某一个位置时,再把滑动事件交给ScrollView。

总结一下,解决滑动冲突需要知道(1)什么时候把滑动事件传给内部View;(2)什么时候内部View再把滑动事件传给外部View。

先来看一下,如何把事件传给内部View?只需要在ScrollView滑动到某个位置后,使用接口回调,并且让RecyclerView在接口回调里,getParent().requestdisallowintercept()。具体代码如下:

protected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);if (getScrollY() >= fixView.getTop()) {fix();} else {dismiss();}}

在fix回调里,requestdisallowintercept(true)来让ScrollView不拦截。

那么,RecyclerView如何把事件传给外部?

需要给RecyclerView设置ontouchlistener,然后在RecyclerView滑动到第一个item,并且正在向下滑动时,requestdisallowintercept(false)来让ScrollView拦截。

~~~~~~~~~~~~~华丽丽的分割线:滑动冲突完全解析~~~~~~~~~~~~~~~~~~~~~~

1、外部拦截法

所有点击事件都先经过父容器拦截处理

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch(ev.getAction()) {case MotionEvent.ACTION_DOWN://必须返回false,否则子控件永远无法拿到焦点return false;case MotionEvent.ACTION_MOVE:if(事件交给子控件的条件) {return false;} else {return super.onInterceptTouchEvent(ev);}case MotionEvent.ACTION_UP://必须返回false,否则子控件永远无法拿到焦点return false;default:return super.onInterceptTouchEvent(ev);}}

2、内部拦截法

所有点击事件都先交给子控件处理

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch(ev.getAction()) {case MotionEvent.ACTION_DOWN://父容器禁止拦截getParent().requestDisallowInterceptTouchEvent(true);break;case MotionEvent.ACTION_MOVE:if(事件交给父容器的条件) {getParent().requestDisallowInterceptTouchEvent(false);}break;case MotionEvent.ACTION_UP:break;default:break;}return super.dispatchTouchEvent(ev);}

在使用内部拦截法的时候,必须在父容器的Touch_down方法里返回false

demo在这里(点击下载)

~~~~~~~~~~~~~华丽丽的分割线:框架进一步升级,使用RecyclerView代替~~~~~~~~~~~~~~~~~~~~~~

时隔几个月后,确实发现之前的框架存在一定问题,ScrollView嵌套RecyclerView的方式着实让人头痛。趁着不忙的时候,我已经对框架进行了升级,抛弃了过去ScrollView嵌套RecyclerView的方式,而采用多类型RecylerView的方式。这种方式能很好地实现悬停!!

具体请看我的下一篇文章《RecyclerView实现悬停导航栏》。

~~~~~~~~~~~~~华丽丽的分割线:框架进一步升级,开发出了嵌套滑动的终极解决办法~~~~~~~~~~~~~~~~~~~~~~

重写了ScrollView!!只有两个类!!使用起来非常容易!!完美解决了各种滑动冲突问题!!

public class HeaderScrollHelper {private int sysVersion; //当前sdk版本,用于判断api版本private ScrollableContainer mCurrentScrollableContainer;public HeaderScrollHelper() {sysVersion = Build.VERSION.SDK_INT;}/** 包含有 ScrollView ListView RecyclerView 的组件 */public interface ScrollableContainer {/** @return ScrollView ListView RecyclerView 或者其他的布局的实例 */View getScrollableView();}public void setCurrentScrollableContainer(ScrollableContainer scrollableContainer) {this.mCurrentScrollableContainer = scrollableContainer;}private View getScrollableView() {if (mCurrentScrollableContainer == null) return null;return mCurrentScrollableContainer.getScrollableView();}/*** 判断是否滑动到顶部方法,ScrollAbleLayout根据此方法来做一些逻辑判断* 目前只实现了AdapterView,ScrollView,RecyclerView* 需要支持其他view可以自行补充实现*/public boolean isTop() {View scrollableView = getScrollableView();if (scrollableView == null) {throw new NullPointerException("You should call ScrollableHelper.setCurrentScrollableContainer() to set ScrollableContainer.");}if (scrollableView instanceof AdapterView) {return isAdapterViewTop((AdapterView) scrollableView);}if (scrollableView instanceof ScrollView) {return isScrollViewTop((ScrollView) scrollableView);}if (scrollableView instanceof RecyclerView) {return isRecyclerViewTop((RecyclerView) scrollableView);}if (scrollableView instanceof WebView) {return isWebViewTop((WebView) scrollableView);}throw new IllegalStateException("scrollableView must be a instance of AdapterView|ScrollView|RecyclerView");}private boolean isRecyclerViewTop(RecyclerView recyclerView) {if (recyclerView != null) {RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();if (layoutManager instanceof LinearLayoutManager) {int firstVisibleItemPosition = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();View childAt = recyclerView.getChildAt(0);if (childAt == null || (firstVisibleItemPosition == 0 && childAt.getTop() == 0)) {return true;}}}return false;}private boolean isAdapterViewTop(AdapterView adapterView) {if (adapterView != null) {int firstVisiblePosition = adapterView.getFirstVisiblePosition();View childAt = adapterView.getChildAt(0);if (childAt == null || (firstVisiblePosition == 0 && childAt.getTop() == 0)) {return true;}}return false;}private boolean isScrollViewTop(ScrollView scrollView) {if (scrollView != null) {int scrollViewY = scrollView.getScrollY();return scrollViewY <= 0;}return false;}private boolean isWebViewTop(WebView scrollView) {if (scrollView != null) {int scrollViewY = scrollView.getScrollY();return scrollViewY <= 0;}return false;}/*** 将特定的view按照初始条件滚动** @param velocityY 初始滚动速度* @param distance 需要滚动的距离* @param duration 允许滚动的时间*/@SuppressLint("NewApi")public void smoothScrollBy(int velocityY, int distance, int duration) {View scrollableView = getScrollableView();if (scrollableView instanceof AbsListView) {AbsListView absListView = (AbsListView) scrollableView;if (sysVersion >= 21) {absListView.fling(velocityY);} else {absListView.smoothScrollBy(distance, duration);}} else if (scrollableView instanceof ScrollView) {((ScrollView) scrollableView).fling(velocityY);} else if (scrollableView instanceof RecyclerView) {((RecyclerView) scrollableView).fling(0, velocityY);} else if (scrollableView instanceof WebView) {((WebView) scrollableView).flingScroll(0, velocityY);}}}

public class HeaderScrollView extends LinearLayout {private static final int DIRECTION_UP = 1;private static final int DIRECTION_DOWN = 2;private int topOffset = 0;//滚动的最大偏移量private Scroller mScroller;private int mTouchSlop; //表示滑动的时候,手的移动要大于这个距离才开始移动控件。private int mMinimumVelocity; //允许执行一个fling手势动作的最小速度值private int mMaximumVelocity; //允许执行一个fling手势动作的最大速度值private int sysVersion; //当前sdk版本,用于判断api版本private View mHeadView; //需要被滑出的头部private int mHeadHeight; //滑出头部的高度private int maxY = 0; //最大滑出的距离,等于 mHeadHeightprivate int minY = 0; //最小的距离, 头部在最顶部private int mCurY; //当前已经滚动的距离private VelocityTracker mVelocityTracker;private int mDirection;private int mLastScrollerY;private boolean mDisallowIntercept; //是否允许拦截事件private boolean isClickHead; //当前点击区域是否在头部private OnScrollListener onScrollListener; //滚动的监听private HeaderScrollHelper mScrollable;public interface OnScrollListener {void onScroll(int currentY, int maxY);}public void setOnScrollListener(OnScrollListener onScrollListener) {this.onScrollListener = onScrollListener;}public HeaderScrollView(Context context) {this(context, null);}public HeaderScrollView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public HeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CTTourHeaderScrollView);topOffset = a.getDimensionPixelSize(R.styleable.CTTourHeaderScrollView_top_offset, topOffset);a.recycle();mScroller = new Scroller(context);mScrollable = new HeaderScrollHelper();ViewConfiguration configuration = ViewConfiguration.get(context);mTouchSlop = configuration.getScaledTouchSlop(); //表示滑动的时候,手的移动要大于这个距离才开始移动控件。mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); //允许执行一个fling手势动作的最小速度值mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); //允许执行一个fling手势动作的最大速度值sysVersion = Build.VERSION.SDK_INT;}@Overrideprotected void onFinishInflate() {super.onFinishInflate();if (mHeadView != null && !mHeadView.isClickable()) {mHeadView.setClickable(true);}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {mHeadView = getChildAt(0);measureChildWithMargins(mHeadView, widthMeasureSpec, 0, MeasureSpec.UNSPECIFIED, 0);mHeadHeight = mHeadView.getMeasuredHeight();maxY = mHeadHeight - topOffset;//让测量高度加上头部的高度super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) + maxY, MeasureSpec.EXACTLY));}/** @param disallowIntercept 作用同 requestDisallowInterceptTouchEvent */public void requestHeaderViewPagerDisallowInterceptTouchEvent(boolean disallowIntercept) {super.requestDisallowInterceptTouchEvent(disallowIntercept);mDisallowIntercept = disallowIntercept;}private float mDownX; //第一次按下的x坐标private float mDownY; //第一次按下的y坐标private float mLastY; //最后一次移动的Y坐标private boolean verticalScrollFlag = false; //是否允许垂直滚动@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {float currentX = ev.getX(); //当前手指相对于当前view的X坐标float currentY = ev.getY(); //当前手指相对于当前view的Y坐标float shiftX = Math.abs(currentX - mDownX); //当前触摸位置与第一次按下位置的X偏移量float shiftY = Math.abs(currentY - mDownY); //当前触摸位置与第一次按下位置的Y偏移量float deltaY; //滑动的偏移量,即连续两次进入Move的偏移量obtainVelocityTracker(ev);//初始化速度追踪器switch (ev.getAction()) {//Down事件主要初始化变量case MotionEvent.ACTION_DOWN:mDisallowIntercept = false;verticalScrollFlag = false;mDownX = currentX;mDownY = currentY;mLastY = currentY;checkIsClickHead((int) currentY, mHeadHeight, getScrollY());mScroller.abortAnimation();break;case MotionEvent.ACTION_MOVE:if (mDisallowIntercept) break;deltaY = mLastY - currentY; //连续两次进入move的偏移量mLastY = currentY;if (shiftX > mTouchSlop && shiftX > shiftY) {//水平滑动verticalScrollFlag = false;} else if (shiftY > mTouchSlop && shiftY > shiftX) {//垂直滑动verticalScrollFlag = true;}/*** 这里要注意,对于垂直滑动来说,给出以下三个条件* 头部没有固定,允许滑动的View处于第一条可见,当前按下的点在头部区域* 三个条件满足一个即表示需要滚动当前布局,否者不处理,将事件交给子View去处理*/if (verticalScrollFlag && (!isStickied() || mScrollable.isTop() || isClickHead)) {//如果是向下滑,则deltaY小于0,对于scrollBy来说//正值为向上和向左滑,负值为向下和向右滑,这里要注意scrollBy(0, (int) (deltaY + 0.5));invalidate();}break;case MotionEvent.ACTION_UP:if (verticalScrollFlag) {puteCurrentVelocity(1000, mMaximumVelocity); //1000表示单位,每1000毫秒允许滑过的最大距离是mMaximumVelocityfloat yVelocity = mVelocityTracker.getYVelocity(); //获取当前的滑动速度mDirection = yVelocity > 0 ? DIRECTION_DOWN : DIRECTION_UP; //下滑速度大于0,上滑速度小于0//根据当前的速度和初始化参数,将滑动的惯性初始化到当前View,至于是否滑动当前View,取决于computeScroll中计算的值//这里不判断最小速度,确保computeScroll一定至少执行一次mScroller.fling(0, getScrollY(), 0, -(int) yVelocity, 0, 0, -Integer.MAX_VALUE, Integer.MAX_VALUE);mLastScrollerY = getScrollY();invalidate(); //更新界面,该行代码会导致computeScroll中的代码执行//阻止快读滑动的时候点击事件的发生,滑动的时候,将Up事件改为Cancel就不会发生点击了if ((shiftX > mTouchSlop || shiftY > mTouchSlop)) {if (isClickHead || !isStickied()) {int action = ev.getAction();ev.setAction(MotionEvent.ACTION_CANCEL);boolean dd = super.dispatchTouchEvent(ev);ev.setAction(action);return dd;}}}recycleVelocityTracker();break;case MotionEvent.ACTION_CANCEL:recycleVelocityTracker();break;default:break;}//手动将事件传递给子View,让子View自己去处理事件super.dispatchTouchEvent(ev);//消费事件,返回True表示当前View需要消费事件,就是事件的TargetViewreturn true;}private void checkIsClickHead(int downY, int headHeight, int scrollY) {isClickHead = ((downY + scrollY) <= headHeight);}private void obtainVelocityTracker(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);}private void recycleVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}@Overridepublic void computeScroll() {if (puteScrollOffset()) {final int currY = mScroller.getCurrY();if (mDirection == DIRECTION_UP) {// 手势向上划if (isStickied()) {//这里主要是将快速滚动时的速度对接起来,让布局看起来滚动连贯int distance = mScroller.getFinalY() - currY; //除去布局滚动消耗的时间后,剩余的时间int duration = calcDuration(mScroller.getDuration(), mScroller.timePassed()); //除去布局滚动的距离后,剩余的距离mScrollable.smoothScrollBy(getScrollerVelocity(distance, duration), distance, duration);//外层布局已经滚动到指定位置,不需要继续滚动了mScroller.abortAnimation();return;} else {scrollTo(0, currY); //将外层布局滚动到指定位置invalidate(); //移动完后刷新界面}} else {// 手势向下划,内部View已经滚动到顶了,需要滚动外层的Viewif (mScrollable.isTop() || isClickHead) {int deltaY = (currY - mLastScrollerY);int toY = getScrollY() + deltaY;scrollTo(0, toY);if (mCurY <= minY) {mScroller.abortAnimation();return;}}//向下滑动时,初始状态可能不在顶部,所以要一直重绘,让computeScroll一直调用//确保代码能进入上面的if判断invalidate();}mLastScrollerY = currY;}}@SuppressLint("NewApi")private int getScrollerVelocity(int distance, int duration) {if (mScroller == null) {return 0;} else if (sysVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {return (int) mScroller.getCurrVelocity();} else {return distance / duration;}}/** 对滑动范围做限制 */@Overridepublic void scrollBy(int x, int y) {int scrollY = getScrollY();int toY = scrollY + y;if (toY >= maxY) {toY = maxY;} else if (toY <= minY) {toY = minY;}y = toY - scrollY;super.scrollBy(x, y);}/** 对滑动范围做限制 */@Overridepublic void scrollTo(int x, int y) {if (y >= maxY) {y = maxY;} else if (y <= minY) {y = minY;}mCurY = y;if (onScrollListener != null) {onScrollListener.onScroll(y, maxY);}super.scrollTo(x, y);}/** 头部是否已经固定 */public boolean isStickied() {return mCurY == maxY;}private int calcDuration(int duration, int timepass) {return duration - timepass;}public int getMaxY() {return maxY;}public boolean isHeadTop() {return mCurY == minY;}/** 是否允许下拉,与PTR结合使用 */public boolean canPtr() {return verticalScrollFlag && mCurY == minY && mScrollable.isTop();}public void setTopOffset(int topOffset) {this.topOffset = topOffset;}public void setCurrentScrollableContainer(HeaderScrollHelper.ScrollableContainer scrollableContainer) {mScrollable.setCurrentScrollableContainer(scrollableContainer);}}

福利!!福利!!此代码已经应用在我们的产品里,并且已经上线,且稳定运行了三个大版本。可直接拿去用!如有不理解,可直接留言提问,博主每天都会查看!

~~~~~~~~~~~~~华丽丽的分割线:解答朋友们关心的几个问题~~~~~~~~~~~~~~~~~~~~~~

首先感谢各位朋友的支持,看到你们能给我的github一个star或者fork我写的demo,我的内心充满了感恩。

由于最近工作比较忙,迟迟没有把最终解决方案上传到github上,只是在本博文里贴上了代码。

最近有很多朋友通过留言或邮件的方式联系我,希望我能进行更细致的讲解。

为了能给更大家的开发带来更大的便捷,我决定还是更新一下github,并且把最新的代码合到了demo里,欢迎下载。

看到有一些朋友问为什么不采取多类型recyclerview的方式,我这里试着解答一下。问这个问题的朋友,我相信你肯定只是没有遇到这样必须要scrollview嵌套另外一个可滑动layout的需求。在我们的产品详情页里需要把webview置顶,如果你来实现能有什么更好的方式吗?只能用ScrollView嵌套一个webview吧。另外,我在博文里确实也已经提到过,如果只是实现吸顶功能,确实使用recyclerview就可以实现了,我也已经实现过并且代码也已经上线几个月了。我的一篇博文《【Android 编程架构 程序设计】多Item类型的RecyclerView替代scrollView(附demo)》介绍了一种多类型RecyclerView的编程框架,该代码已经上线并且稳定运行几个月了,如有需要可以去查看。

~~~~~~~~~~~~~华丽丽的分割线:新增高效便捷实现悬停的代码~~~~~~~~~~~~~~~~~~~~~~

以上代码其实不光实现了悬停,更解决了滑动冲突。那么,如果不需要解决滑动冲突,我只希望能将ScrollView中的一个View实现悬停,并没有在ScrollView嵌套RecyclerView的场景中的话,我的代码该怎么写呢?我在demo中新增了单纯实现悬停的方法,这里只有一个view,代码看起来逻辑清晰,并且很清爽,并没有采用两个View隐藏展示的方法。如有需要,欢迎下载demo查看。

【Android 手势冲突】Colin带你彻底解决RecyclerView与ScrollView滑动冲突问题 并实现RecyclerView悬停导航栏(附demo哦)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。