Android最全百度APP核心技术:Android多子view嵌套最佳通用解决方案,Android面试回忆录

结语

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!以下是目录截图:

由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

再附一部分Android架构面试视频讲解:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

trackVelocity(velocityY);
return false;
} else { // 由联动容器消费fling手势
parentFling(velocityY);
return true;
}
}
}

onNestedPreFling()回调是google嵌套滑动机制NestedScrollingParent接口中的方法。当子view发生fling行为时,会先通过此方法询问父view是否要消费这次fling手势,如果返回true,表示父view要消费这次fling手势,反之不消费。

第6行根据velocityY正负值记录本次的fling的方向;

第7行,当联动容器scrollY值等于触摸子view的top值,fling手势由子view处理,同时联动容器对本次fling手势的速度进行追踪,目的是当子view内容滚到顶或者底时,能够获得剩余速度以让联动容器继续fling;

第12行,由联动容器消费本次fling手势。下面看下联动容器和子view交替fling的细节:

public class ELinkageScrollLayout extends ViewGroup implements NestedScrollingParent {
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
int y = mScroller.getCurrY();
y = y < 0 ? 0 : y;
y = y > mScrollRange ? mScrollRange : y;
// 获取联动容器下个滚动边界值,如果达到边界值,速度会传给下个子view,让子view继续快速滑动
int edge = getNextEdge();
// 边界检查
if (mFlingOrientation == FLING_ORIENTATION_UP) {
y = y > edge ? edge : y;
}
// 边界检查
if (mFlingOrientation == FLING_ORIENTATION_DOWN) {
y = y < edge ? edge : y;
}
// 联动容器滚动子view
scrollTo(x, y);
int scrollY = getScrollY();
// 联动容器最新的scrollY是否达到了边界值
if (scrollY == edge) {
// 获取剩余的速度
int velocity = (int) mScroller.getCurrVelocity();
if (mFlingOrientation == FLING_ORIENTATION_UP) {
velocity = velocity > 0? velocity : - velocity;
}
if (mFlingOrientation == FLING_ORIENTATION_DOWN) {
velocity = velocity < 0? velocity : - velocity;
}
// 获取top为edge的子view
View target = getTargetByEdge(edge);
// 子view根据剩余的速度继续fling
((ILinkageScroll) target).provideScrollHandler()
.flingContent(target, velocity);
trackVelocity(velocity);
}
invalidate();
}
}
/**

  • 根据fling的方向获取下一个滚动边界,
  • 内部会判断下一个子View是否isScrollable,
  • 如果为false,会顺延取下一个target的edge。
    /
    private int getNextEdge() {
    int scrollY = getScrollY();
    if (mFlingOrientation == FLING_ORIENTATION_UP) {
    for (View target : mLinkageChildren) {
    LinkageScrollHandler handler
    = ((ILinkageScroll)target).provideScrollHandler();
    int topEdge = target.getTop();
    if (topEdge > scrollY
    && isTargetScrollable(target)
    && handler.canScrollVertically(1)) {
    return topEdge;
    }
    }
    } else if (mFlingOrientation == FLING_ORIENTATION_DOWN) {
    for (View target : mLinkageChildren) {
    LinkageScrollHandler handler
    = ((ILinkageScroll)target).provideScrollHandler();
    int bottomEdge = target.getBottom();
    if (bottomEdge >= scrollY
    && isTargetScrollable(target)
    && handler.canScrollVertically(-1)) {
    return target.getTop();
    }
    }
    }
    return mFlingOrientation == FLING_ORIENTATION_UP ? mScrollRange : 0;
    }
    /
    *
  • child view的滚动事件
    */
    private ChildLinkageEvent mChildLinkageEvent = new ChildLinkageEvent() {
    @Override
    public void onContentScrollToTop(View target) {
    // 子view内容滚动到顶部回调
    if (mVelocityScroller.computeScrollOffset()) {
    // 从速度追踪器中获取剩余速度
    float currVelocity = mVelocityScroller.getCurrVelocity();
    currVelocity = currVelocity < 0 ? currVelocity : - currVelocity;
    mVelocityScroller.abortAnimation();
    // 联动容器根据剩余速度继续fling
    parentFling(currVelocity);
    }
    }
    @Override
    public void onContentScrollToBottom(View target) {
    // 子view内容滚动到底部回调
    if (mVelocityScroller.computeScrollOffset()) {
    // 从速度追踪器中获取剩余速度
    float currVelocity = mVelocityScroller.getCurrVelocity();
    currVelocity = currVelocity > 0 ? currVelocity : - currVelocity;
    mVelocityScroller.abortAnimation();
    // 联动容器根据剩余速度继续fling
    parentFling(currVelocity);
    }
    }
    };
    }

fling的速度传递分为:

  1. 从联动容器向子view传递;2. 从子view向联动容器传递。

先看速度从联动容器向子view传递。核心代码在computeScroll()回调方法中。第9行,获取联动容器下一个滚动边界值,如果达到下一个滚动边界值,联动容器需要将剩余速度传给下个子view,让其继续滚动。

第46行,getNextEdge()方法内部整体逻辑:遍历所有子view,将联动容器当前的scrollY与子view的top/bottom进行比较来获取下一个滑动边界。

第34行,当联动容器检测到滑动到下个边界时,则调用ILinkageScroll.flingContent()让子view根据剩余速度继续滚动。

再看速度从子view向联动容器传递,核心代码在第76行。当子view内容滚动到顶或者底,会回调onContentScrollToTop()方法或者onContentScrollToBottom()方法,联动容器收到回调后,在第86行和第98行,继续执行后续滚动。fling手势处理流程图如下:

4. 滚动条

4.1 Android系统的ScrollBar

对于内容可滚动的页面,ScrollBar则是一个不可或缺的UI组件,所以,ScrollBar也是联动容器必须要实现的功能。

好在Android系统对滚动条的抽象非常友好,自定义控件只需要重写View中的几个方法,Android系统就能帮助你正确绘制出滚动条。我们先看下View中的相关方法:

/**

  • Compute the vertical offset of the vertical scrollbar's thumb within the horizontal range. This value is used to compute the position

  • of the thumb within the scrollbar’s track.
  • The range is expressed in arbitrary units that must be the same as the units used by {@link #computeVerticalScrollRange()} and

  • {@link #computeVerticalScrollExtent()}.
  • @return the vertical offset of the scrollbar’s thumb
    /
    protected int computeVerticalScrollOffset() {
    return mScrollY;
    }
    /
    *
  • Compute the vertical extent of the vertical scrollbar's thumb within the vertical range. This value is used to compute the length

  • of the thumb within the scrollbar’s track.
  • The range is expressed in arbitrary units that must be the same as the units used by {@link #computeVerticalScrollRange()} and

  • {@link #computeVerticalScrollOffset()}.
  • @return the vertical extent of the scrollbar’s thumb
    /
    protected int computeVerticalScrollExtent() {
    return getHeight();
    }
    /
    *
  • Compute the vertical range that the vertical scrollbar represents.

  • The range is expressed in arbitrary units that must be the same as the units used by {@link #computeVerticalScrollExtent()} and

  • {@link #computeVerticalScrollOffset()}.
  • @return the total vertical range represented by the vertical scrollbar
    */
    protected int computeVerticalScrollRange() {
    return getHeight();
    }
    复制代码

对于垂直Scrollbar,我们只需要重写computeVerticalScrollOffset(),computeVerticalScrollExtent(),computeVerticalScrollRange()这三个方法即可。Android对这三个方法注释已经非常详细了,这里再简单解释下:

computeVerticalScrollOffset()表示当前页面内容滚动的偏移值,这个值是用来控制Scrollbar的位置。缺省值为当前页面Y方向上的滚动值。

computeVerticalScrollExtent()表示滚动条的范围,也就是滚动条在垂直方向上所能触及的最大界限,这个值也会被系统用来计算滚动条的长度。缺省值是View的实际高度。

computeVerticalScrollRange()表示整个页面内容可滚动的数值范围,缺省值为View的实际高度。

需要注意的是:offset,extent,range三个值在单位上必须保持一致。

4.2 联动容器实现ScrollBar

联动容器是由系统中可滚动的子view组成的,这些子view(ListView、RecyclerView、WebView)肯定都实现了ScrollBar功能,那么联动容器实现ScrollBar就非常简单了,联动容器只需拿到所有子view的offset,extent,range值,然后再根据联动容器的滑动逻辑把所有子view的这些值转换成联动容器对应的offset,extent,range即可。接口设计如下:

public interface LinkageScrollHandler {
// …省略无关代码
/**

  • get scrollbar extent value
  • @return extent
    /
    int getVerticalScrollExtent();
    /
    *
  • get scrollbar offset value
  • @return extent
    /
    int getVerticalScrollOffset();
    /
    *
  • get scrollbar range value
  • @return extent
    */
    int getVerticalScrollRange();
    }

LinkageScrollHandler接口在3.2小节解释过,这里不在赘述。这里面三个方法由子view去实现,联动容器会通过这三个方法获取子view与滚动条相关的值。下面看下联动容器中关于ScrollBar的详细逻辑:

public class ELinkageScrollLayout extends ViewGroup implements NestedScrollingParent {
/** 构造方法 /
public ELinkageScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
// …省略了无关代码
// 确保联动容器调用onDraw()方法
setWillNotDraw(false);
// enable vertical scrollbar
setVerticalScrollBarEnabled(true);
}
/
* child view的滚动事件 */
private ChildLinkageEvent mChildLinkageEvent = new ChildLinkageEvent() {
// …省略了无关代码
@Override
public void onContentScroll(View target) {
// 收到子view滚动事件,显示滚动条
awakenScrollBars();
}
}
@Override
protected int computeVerticalScrollExtent() {
// 使用缺省的extent值
return super.computeVerticalScrollExtent();
}
@Override
protected int computeVerticalScrollRange() {
int range = 0;
// 遍历所有子view,获取子view的Range
for (View child : mLinkageChildren) {
ILinkageScroll linkageScroll = (ILinkageScroll) child;
int childRange = linkageScroll.provideScrollHandler().getVerticalScrollRange();
range += childRange;
}
return range;
}
@Override
protected int computeVerticalScrollOffset() {
int offset = 0;
// 遍历所有子view,获取子view的offset
for (View child : mLinkageChildren) {
ILinkageScroll linkageScroll = (ILinkageScroll) child;
int childOffset = linkageScroll.provideScrollHandler().getVerticalScrollOffset();
offset += childOffset;
}
// 加上联动容器自身在Y方向上的滚动偏移
offset += getScrollY();
return offset;
}
}

以上就是联动容器实现ScrollBar的核心代码,注释也非常详细,这里再重点强调几点:

系统为了提高效率,ViewGroup默认不调用onDraw()方法,这样就不会走ScrollBar的绘制逻辑。所以在第6行,需要调用setWillNotDraw(false)打开ViewGroup绘制流程;

第16行,收到子view的滚动回调,调用awakenScrollBars()触发滚动条的绘制;

对于extent,直接使用缺省的extent,即联动容器的高度;

对于range,对所有子view的range进行求和,最后得到值即为联动容器的range;

对于offset,同样先对所有子view的offset进行求和,之后还需要加上联动容器自身的scrollY值,最终得到的值即为联动容器的offset。

大家可以返回到文章开头,再看下Demo中滚动条的效果,相比于市面上其它使用类似联动技术的App,本文对滚动条的实现非常接近原生了。

5. 注意事项

联动容器执行fling操作时,借助OverScroller工具类完成的。代码如下:

private void parentFling(float velocityY) {
// … 省略了无关代码

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。**

[外链图片转存中…(img-hnLhxRD4-1715246736416)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值