又来一个库,WebView,RecyclerView-多布局连贯滑动

文章详细描述了如何通过ScrollUtils类判断子View的垂直滑动能力,以及在ConsecutiveScrollerLayout中拦截和处理滑动事件的逻辑,包括使用VelocityTracker追踪滑动速度、Scroller实现惯性滑动和dispatchScroll方法的滑动分发机制。
摘要由CSDN通过智能技术生成

public class ScrollUtils {

static boolean canScrollVertically(View view) {
return canScrollVertically(view, 1) || canScrollVertically(view, -1);
}

static boolean canScrollVertically(View view, int direction) {
return view.canScrollVertically(direction);
}
}

判断是否需要拦截事件,主要是通过判断触摸的子view是否可以垂直滑动,如果可以垂直滑动,就拦截事件,让事件由ConsecutiveScrollerLayout自己处理。如果不是,就不拦截,一般不能滑动的view不会消费滑动事件,所以事件最终会由ConsecutiveScrollerLayout所消费。之所以不直接拦截,是为了能让子view尽可能的获得事件处理和分发给下面的view的机会。

这里有一个isConsecutive的LayoutParams属性,它是ConsecutiveScrollerLayout.LayoutParams的自定义属性,用于表示一个子view是否允许ConsecutiveScrollerLayout拦截它的滑动事件,默认为true。如果把它设置为false,父布局将不会拦截这个子view的事件,而是完全交由子view处理。这使得子view有了自己处理滑动事件的机会和分发事件的主动权。

这对于实现一些需要实现局部区域内滑动的特殊需求十分有用。我在GitHub中提供的demo和使用介绍中对isConsecutive有详细的说明,在这就不做过多介绍了。

滑动处理

把事件拦截后,就要在onTouchEvent方法中处理滑动事件。

@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录触摸点
mTouchY = (int) ev.getY();
// 追踪滑动速度
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mTouchY == 0) {
mTouchY = (int) ev.getY();
return true;
}
int y = (int) ev.getY();
int dy = y - mTouchY;
mTouchY = y;
// 滑动布局
scrollBy(0, -dy);
// 追踪滑动速度
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mTouchY = 0;

if (mVelocityTracker != null) {
// 处理惯性滑动
mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int yVelocity = (int) mVelocityTracker.getYVelocity();
recycleVelocityTracker();
fling(-yVelocity);
}
break;
}
return true;
}

// 惯性滑动
private void fling(int velocityY) {
if (Math.abs(velocityY) > mMinimumVelocity) {
mScroller.fling(0, mOwnScrollY,
1, velocityY,
0, 0,
Integer.MIN_VALUE, Integer.MAX_VALUE);
invalidate();
}
}

@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int curY = mScroller.getCurrY();
// 滑动布局
dispatchScroll(curY);
invalidate();
}
}

onTouchEvent方法的逻辑非常简单,就是根据手指的滑动距离通过view的scrollBy方法滑动布局内容,同时通过VelocityTracker追踪手指的滑动速度,使用Scroller配合computeScroll()方法实现惯性滑动。

滑动距离的分发

在处理惯性滑动是时候,我们调用了dispatchScroll()方法,这个方法是整个ConsecutiveScrollerLayout的核心,它决定了应该由谁来消费这次滑动,应该滑动那个布局。

其实ConsecutiveScrollerLayout的scrollBy()和scrollTo()方法最终都是调用它来处理滑动的分发的。

这里有个mOwnScrollY属性,是用于记录ConsecutiveScrollerLayout的整体滑动距离的,相当于View的mScrollY属性。

dispatchScroll()方法把滑动分成向上和向下两部分处理。让我们先看向上滑动部分的处理。

private void scrollUp(int offset) {
int scrollOffset = 0;  // 消费的滑动记录
int remainder = offset; // 未消费的滑动距离
do {
scrollOffset = 0;
// 是否滑动到底部
if (!isScrollBottom()) {
// 找到当前显示的第一个View
View firstVisibleView = findFirstVisibleView();
if (firstVisibleView != null) {
awakenScrollBars();
// 获取View滑动到自身底部的偏移量
int bottomOffset = ScrollUtils.getScrollBottomOffset(firstVisibleView);
if (bottomOffset > 0) {
// 如果bottomOffset大于0,表示这个view还没有滑动到自身的底部,那么就由这个view来消费这次的滑动距离。
int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(firstVisibleView);
// 计算需要滑动的距离
scrollOffset = Math.min(remainder, bottomOffset);
// 滑动子view
scrollChild(firstVisibleView, scrollOffset);
// 计算真正的滑动距离
scrollOffset = ScrollUtils.computeVerticalScrollOffset(firstVisibleView) - childOldScrollY;
} else {
// 如果子view已经滑动到自身的底部,就由父布局消费滑动距离,直到把这个子view滑出屏幕
int selfOldScrollY = getScrollY();
// 计算需要滑动的距离
scrollOffset = Math.min(remainder,
firstVisibleView.getBottom() - getPaddingTop() - getScrollY());
// 滑动父布局
scrollSelf(getScrollY() + scrollOffset);
// 计算真正的滑动距离
scrollOffset = getScrollY() - selfOldScrollY;
}
// 计算消费的滑动距离,如果还没有消费完,就继续循环消费。
mOwnScrollY += scrollOffset;
remainder = remainder - scrollOffset;
}
}
} while (scrollOffset > 0 && remainder > 0);
}

public boolean isScrollBottom() {
List children = getNonGoneChildren();
if (children.size() > 0) {
View child = children.get(children.size() - 1);
return getScrollY() >= mScrollRange && !child.canScrollVertically(1);
}
return true;
}

public View findFirstVisibleView() {
int offset = getScrollY() + getPaddingTop();
List children = getNonGoneChildren();
int count = children.size();
for (int i = 0; i < count; i++) {
View child = children.get(i);
if (child.getTop() <= offset && child.getBottom() > offset) {
return child;
}
}
return null;
}

private void scrollSelf(int y) {
int scrollY = y;

// 边界检测
if (scrollY < 0) {
scrollY = 0;
} else if (scrollY > mScrollRange) {
scrollY = mScrollRange;
}
super.scrollTo(0, scrollY);
}

private void scrollChild(View child, int y) {
child.scrollBy(0, y);
}

向上滑动的处理逻辑是,先找到当前显示的第一个子view,判断它的内容是否已经滑动到它的底部,如果没有,则由它来消费滑动距离。

如果已经滑动到它的底部,则由ConsecutiveScrollerLayout来消费滑动距离,直到把这个子view滑出屏幕。这样下一次获取显示的第一个view就是它的下一个view了,重复以上的操作,直到把ConsecutiveScrollerLayout和所有的子view都滑动到底部,这样就整体都滑动到底部了。

这里使用了一个while循环操作,这样做是因为一次滑动距离,可能会由多个对象来消费,比如需要滑动50px的距离,但是当前显示的第一个子view还需要10px滑动到自己的底部,那么这个子view会消费10px的距离,剩下40px的距离就要进行下一次的分发,找到需要消费它的对象,以此类推。

向下滑动的处理跟向上滑动是一摸一样的,只是判断的对象和滑动的方向不同。

private void scrollDown(int offset) {
int scrollOffset = 0;  // 消费的滑动记录
int remainder = offset;  // 未消费的滑动距离
do {
scrollOffset = 0;
// 是否滑动到顶部
if (!isScrollTop()) {
// 找到当前显示的最后一个View
View lastVisibleView = findLastVisibleView();
if (lastVisibleView != null) {
awakenScrollBars();
// 获取View滑动到自身顶部的偏移量
int childScrollOffset = ScrollUtils.getScrollTopOffset(lastVisibleView);
if (childScrollOffset < 0) {
// 如果childScrollOffset大于0,表示这个view还没有滑动到自身的顶部,那么就由这个view来消费这次的滑动距离。
int childOldScrollY = ScrollUtils.computeVerticalScrollOffset(lastVisibleView);
// 计算需要滑动的距离
scrollOffset = Math.max(remainder, childScrollOffset);
// 滑动子view
scrollChild(lastVisibleView, scrollOffset);
// 计算真正的滑动距离
scrollOffset = ScrollUtils.computeVerticalScrollOffset(lastVisibleView) - childOldScrollY;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

以上分享【我的阿里P7移动开发架构师学习笔记】七大模块整套学习资料均免费分享,需要的小伙伴,我已经上传到石墨文档了,大家自取就可以了。白嫖可以,别忘了给我点个关注哈。

当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。

如果你需要,我把他放在GitHub了,无偿分享的。

【Android架构视频+BATJ面试专题PDF+学习笔记】

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
是一个道理。

如果你需要,我把他放在GitHub了,无偿分享的。

【Android架构视频+BATJ面试专题PDF+学习笔记】

[外链图片转存中…(img-XnO8L1JZ-1710902856055)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值