Android应用开发Scroller详解及源码浅析,字节跳动Andorid岗25k+的面试题

}

@Override

public void computeScroll() {

if (mScroller.computeScrollOffset()) {

this.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

postInvalidate();

}

}

}

简单吧,使用Scroller就能这么优雅的滑动,不解释,简单的Demo,哈哈;有了这个基本映像我们直接高速——源码探测,搞清源码基本原理流程就能用的顺手喽。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

3 Scroller源码浅析

==================

通过上面实例我们可以发现在自定义View的过程中使用Scroller的流程如下图所示:

这里写图片描述

既然有了这么明确的流程图,那我们下面就来依据这个流程简单分析下Scroller的源码。可以发现Scroller这类的代码不多哇,确实是一个工具类,哈哈,我们先看下构造方法:

public Scroller(Context context) {

this(context, null);

}

public Scroller(Context context, Interpolator interpolator) {

this(context, interpolator,

context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);

}

public Scroller(Context context, Interpolator interpolator, boolean flywheel) {

mFinished = true;

if (interpolator == null) {

mInterpolator = new ViscousFluidInterpolator();

} else {

mInterpolator = interpolator;

}

mPpi = context.getResources().getDisplayMetrics().density * 160.0f;

//摩擦力计算单位时间减速度

mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());

mFlywheel = flywheel;

mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning

}

可以看见,构造方法没啥特殊的,只是一些基础的设置,唯一要重点关注可能自定义的也就动画插值器那个参数了,默认是ViscousFluidInterpolator的(不懂动画插值器的请看《Android应用开发之所有动画使用详解》),我们可以自定义修改。两参构造方法中其实也就是对第三个参数做了HONEYCOMB兼容性处理,三参是所有构造方法最终调运的方法,其实也就是初始化了一些变量而已,没啥重要的。

下面我们看看与Scroller相关的startScroll()和fling()方法,源码如下:

//在我们想要滚动的地方调运,准备开始滚动,默认滚动时间为DEFAULT_DURATION

public void startScroll(int startX, int startY, int dx, int dy) {

startScroll(startX, startY, dx, dy, DEFAULT_DURATION);

}

//在我们想要滚动的地方调运,准备开始滚动,手动设置滚动时间

public void startScroll(int startX, int startY, int dx, int dy, int duration) {

mMode = SCROLL_MODE;

mFinished = false;

mDuration = duration;

mStartTime = AnimationUtils.currentAnimationTimeMillis();

mStartX = startX;

mStartY = startY;

mFinalX = startX + dx;

mFinalY = startY + dy;

mDeltaX = dx;

mDeltaY = dy;

mDurationReciprocal = 1.0f / (float) mDuration;

}

//在快速滑动松开的基础上开始惯性滚动,滚动距离取决于fling的初速度

public void fling(int startX, int startY, int velocityX, int velocityY,

int minX, int maxX, int minY, int maxY) {

mMode = FLING_MODE;

mFinished = false;

mStartX = startX;

mStartY = startY;

mDistance = (int) (totalDistance * Math.signum(velocity));

mMinX = minX;

mMaxX = maxX;

mMinY = minY;

mMaxY = maxY;

mFinalY = Math.min(mFinalY, mMaxY);

mFinalY = Math.max(mFinalY, mMinY);

}

可以看见,上面这几个美其名曰滑动的Scroller方法其实都只是一个幌子,没有进行滑动,而是初始化了一堆成员变量;譬如滚动模式、开始时间、持续时间等,也就是说他们都只是工具方法而已,实质的滑动其实是需要我们在他后面手动调运View的invalidate()进行刷新,然后在View进行刷新时又会调运自己的View.computeScroll()方法(不了解View绘制的请看《Android应用层View绘制流程与源码分析》一文),在View.computeScroll()方法中进行Scroller.computeScrollOffset()判断与触发View的滑动方法。

既然这样那我们粗略给出View的绘制流程,详细的请看《Android应用层View绘制流程与源码分析》一文。当我们调运invalidate()会触发View的如下方法:

public void draw(Canvas canvas) {

/*

  • Draw traversal performs several drawing steps which must be executed

  • in the appropriate order:

  •  1. Draw the background
    
  •  2. If necessary, save the canvas' layers to prepare for fading
    
  •  3. Draw view's content
    
  •  4. Draw children
    
  •  5. If necessary, draw the fading edges and restore layers
    
  •  6. Draw decorations (scrollbars for instance)
    

*/

// Step 4, draw the children

dispatchDraw(canvas);

}

可以发现,View的draw()方法被触发时总共会进行6步,最重要的一步我们看第四步,下面是第四步dispatchDraw()方法源码:

protected void dispatchDraw(Canvas canvas) {}

可以看见,View的该方法为空方法,那我们看下他子类ViewGroup的该方法,如下:

protected void dispatchDraw(Canvas canvas) {

for (int i = 0; i < childrenCount; i++) {

more |= drawChild(canvas, child, drawingTime);

}

}

可以发现,ViewGroup的dispatchDraw()方法实质又跑到了drawChild()方法,如下:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

return child.draw(canvas, this, drawingTime);

}

额额,实质又是child的另一个draw()方法而已,我们回到View去看下这个方法,如下:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

if (!drawingWithRenderNode) {

computeScroll();

sx = mScrollX;

sy = mScrollY;

}

}

额额,这就解释了为何View调运invalidate()就会触发computeScroll()方法了。而ViewGroup最终调运scrollTo()方法都只能滚动内部子View的问题其实是因为ViewGroup它本身并没有任何可画的东西,它是一个透明的控件,所以一般不会触发onDraw()方法,但是当你给他设置背景等就会调用onDraw方法了,可是走的是绘制背景流程。

View相关的扯完了,下面我们来看看Scroller的computeScrollOffset()方法,下面我们简单分析这个方法,如下:

//判断滚动是否还在继续,true继续,false结束

public boolean computeScrollOffset() {

//mFinished为true表示已经完成了滑动,直接返回为false

if (mFinished) {

return false;

}

//mStartTime为开始时的时间戳,timePassed就是当前滑动持续时间

int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

//mDuration为我们设置的持续时间,当当前已滑动耗时timePassed小于总设置持续时间时才进入if

if (timePassed < mDuration) {

//mMode有两中,如果调运startScroll()则为SCROLL_MODE模式,调运fling()则为FLING_MODE模式

switch (mMode) {

case SCROLL_MODE:

//根据Interpolator插值器计算在该时间段里移动的距离赋值给mCurrX和mCurrY

final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);

mCurrX = mStartX + Math.round(x * mDeltaX);

mCurrY = mStartY + Math.round(x * mDeltaY);

break;

case FLING_MODE:

//各种数学运算获取mCurrY、mCurrX,实质类似上面SCROLL_MODE,只是这里时惯性的

// Pin to mMinX <= mCurrX <= mMaxX

mCurrX = Math.min(mCurrX, mMaxX);

mCurrX = Math.max(mCurrX, mMinX);

mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));

// Pin to mMinY <= mCurrY <= mMaxY

mCurrY = Math.min(mCurrY, mMaxY);

mCurrY = Math.max(mCurrY, mMinY);

if (mCurrX == mFinalX && mCurrY == mFinalY) {

mFinished = true;

}

break;

}

}

else {

//认为滑动结束,mFinished置位true,标记结束,下一次再触发该方法时一进来就判断返回false了

mCurrX = mFinalX;

mCurrY = mFinalY;

mFinished = true;

}

return true;

}

可以看见该方法的作用其实就是实时计算滚动的偏移量(也是一个工具方法),同时判断滚动是否结束(true代表没结束,false代表结束)。

到此整个Scroller就分析完了,剩下的全是各种getXXX、setXXX方法就没啥意思了。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

4 Scroller总结

================

基于上面的例子和分析我们进行如下总结:

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后我想说

为什么很多程序员做不了架构师?
1、良好健康的职业规划很重要,但大多数人都忽略了
2、学习的习惯很重要,持之以恒才是正解。
3、编程思维没能提升一个台阶,局限在了编码,业务,没考虑过选型、扩展
4、身边没有好的架构师引导、培养。所处的圈子对程序员的成长影响巨大。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

什么很多程序员做不了架构师?**
1、良好健康的职业规划很重要,但大多数人都忽略了
2、学习的习惯很重要,持之以恒才是正解。
3、编程思维没能提升一个台阶,局限在了编码,业务,没考虑过选型、扩展
4、身边没有好的架构师引导、培养。所处的圈子对程序员的成长影响巨大。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-3iCjBwwy-1711915298065)]

里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中…

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值