Android源码分析-Scroller
来源:互联网 发布:iphone4s4g网络补丁 编辑:程序博客网 时间:2024/06/06 21:06
本篇文章主要介绍了一下Scroller的使用并对其源码进行了简单的分析,感兴趣的朋友可以看一下。
基本用法:
Scroller有两个比较重要且常用的方法:startScroll和fling,特别是第一个方法我们在定义View或ViewGroup的时候经常用到,我写了一个简单的定义ViewGroup的小demo演示这两个方法的用法,先看下效果图:
startScroll方法:
;
fling方法:
主要代码
下面是主要的实现代码,比较简单,全部的代码会在最下面给出:
@Override public boolean onTouchEvent(MotionEvent event) { //fling方法用到 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: preX = event.getX(); if (!mScroller.isFinished()) mScroller.abortAnimation(); break; case MotionEvent.ACTION_MOVE: float curX = event.getX(); int dx = (int) (preX - curX); //滑动的范围是0=<x<=getMeasuredWidth() if (getScrollX() + (int) dx < 0) { this.scrollTo(0, 0); } else if (getScrollX() + (int) dx > getMeasuredWidth()) { this.scrollTo(getMeasuredWidth(), 0); } else { this.scrollBy((int) dx, 0); } preX = curX; break; case MotionEvent.ACTION_UP: //判断应该滑动到左右哪一侧(startScroll方法用到) int sX = this.getScrollX(); int index = sX / getMeasuredWidth(); int d = sX % (getMeasuredWidth()); if (d > getMeasuredWidth() / 2.0f) { index += 1; } //startScroll用法 smoothScrollToIndex(index); //fling用法 //smoothFling(); break; } //消费事件 return true; }
/** * startScroll的用法 * @param index */ private void smoothScrollToIndex(int index) { mScroller.startScroll(getScrollX(), 0, index * getMeasuredWidth() - getScrollX(), 0); //View重绘的时候draw方法会调用下面的computeScroll()方法 invalidate(); } /** * fling的用法 */ private void smoothFling() { mVelocityTracker.computeCurrentVelocity(1000); float fXV = mVelocityTracker.getXVelocity(); //mVelocityTracker得到的速度从左向右是正的,而scroller里是从右向左是负的,所认这里在fXV前面加了一个负号 mScroller.fling(getScrollX(), 0, -(int) fXV, 0, 0, getMeasuredWidth(), 0, 0); //View重绘的时候draw方法会调用下面的computeScroll()方法 invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) {//scroll没有结束 int curX = mScroller.getCurrX(); int curY = mScroller.getCurrY(); this.scrollTo(curX, curY); //继续调用computeScroll()方法 invalidate(); } }
源码浅析
先看Scroller的构造方法:
151 public Scroller(Context context) {152 this(context, null);153 }160 public Scroller(Context context, Interpolator interpolator) {161 this(context, interpolator,162 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);163 }170 public Scroller(Context context, Interpolator interpolator, boolean flywheel) {171 mFinished = true;172 if (interpolator == null) {173 mInterpolator = new ViscousFluidInterpolator();174 } else {175 mInterpolator = interpolator;176 }177 mPpi = context.getResources().getDisplayMetrics().density * 160.0f;178 mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());179 mFlywheel = flywheel;180181 mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning182 }
Scroller总共有三个构造方法,最终调用的都是第三个,可以看到我们可以传一个插值器Interpolator进来,Interpolator可以控制滑动的速度变化,比如我们可以用它实现均匀滑动或加速滑动以及其他一些效果,不清楚的可以自己google一下,如果我们没有提供Interpolator的话,Scroller会使用一个默认的插值器ViscousFluidInterpolator:
562 static class ViscousFluidInterpolator implements Interpolator {563564 private static final float VISCOUS_FLUID_SCALE = 8.0f;565566 private static final float VISCOUS_FLUID_NORMALIZE;567 private static final float VISCOUS_FLUID_OFFSET;568569 static {570571 // must be set to 1.0 (used in viscousFluid())572 VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);573 // account for very small floating-point error574 VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f);575 }576577 private static float viscousFluid(float x) {578 x *= VISCOUS_FLUID_SCALE;579 if (x < 1.0f) {580 x -= (1.0f - (float)Math.exp(-x));581 } else {582 float start = 0.36787944117f; // 1/e == exp(-1)583 x = 1.0f - (float)Math.exp(1.0f - x);584 x = start + x * (1.0f - start);585 }586 return x;587 }588589 @Override590 public float getInterpolation(float input) {591 final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);592 if (interpolated > 0) {593 return interpolated + VISCOUS_FLUID_OFFSET;594 }595 return interpolated;596 }597 }
其中getInterpolation方法可以控制scroll的滑动快慢。
再看startScroll方法:
369 public void startScroll(int startX, int startY, int dx, int dy) {370 startScroll(startX, startY, dx, dy, DEFAULT_DURATION);371 }387 public void startScroll(int startX, int startY, int dx, int dy, int duration) {388 mMode = SCROLL_MODE;//模式,startScroll方法是SCROLL_MODE389 mFinished = false;//标识是否滑动结束390 mDuration = duration;//滑动时间391 mStartTime = AnimationUtils.currentAnimationTimeMillis();//开始时间392 mStartX = startX;//开始x位置393 mStartY = startY;//开始y位置394 mFinalX = startX + dx;//结束x位置395 mFinalY = startY + dy;//结束y位置396 mDeltaX = dx;//x方向上的滑动间距397 mDeltaY = dy;//y方向上的滑动间距398 mDurationReciprocal = 1.0f / (float) mDuration;//滑动时间的倒数399 }
startScroll方法有两个实现,第一个方法调用的是第二个方法,如果我们没有提供滑动的时间的话,会有一个默认的,默认是250ms:
private static final int DEFAULT_DURATION = 250;
可以看到startScroll方法只是对Scroller里面的成员变进行了赋值操作。
在上面的小demo中,我们在调用完startScroll方法后接着调用了invalidate()方法,invalidate()方法会导致computeScroll()方法被调用(invalidate()方法会导致view绘,view的draw方法内部会调用computeScroll(),具体过程可以查看相关源码):
@Override public void computeScroll() { if (mScroller.computeScrollOffset()) {//scroll没有结束 int curX = mScroller.getCurrX(); int curY = mScroller.getCurrY(); this.scrollTo(curX, curY); //继续调用computeScroll()方法 invalidate(); } }
在computeScroll()方法内部我们调用了Scroller的computeScrollOffset()方法,看一下这个方法是干什么的:
300 public boolean computeScrollOffset() {301 if (mFinished) {//滑动结束,返回false302 return false;303 }304305 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);//当前过去的时间306 307 if (timePassed < mDuration) {//滑动没有结束308 switch (mMode) {309 case SCROLL_MODE://startScroll的mMode是SCROLL_MODE310 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);//调用插值器的getInterpolation方法,传进去的是当前经过的时间占总滑动时间的百分比,返回的是重新计算的百分比,这是插值器的一种常用用法。311 mCurrX = mStartX + Math.round(x * mDeltaX);//将当前的x位置赋值给mCurrX成员312 mCurrY = mStartY + Math.round(x * mDeltaY);//将当前的y位置赋值给mCurrY成员313 break;314 case FLING_MODE://fling的mMode是FLING_MODE315 final float t = (float) timePassed / mDuration;316 final int index = (int) (NB_SAMPLES * t);317 float distanceCoef = 1.f;318 float velocityCoef = 0.f;319 if (index < NB_SAMPLES) {320 final float t_inf = (float) index / NB_SAMPLES;321 final float t_sup = (float) (index + 1) / NB_SAMPLES;322 final float d_inf = SPLINE_POSITION[index];323 final float d_sup = SPLINE_POSITION[index + 1];324 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);325 distanceCoef = d_inf + (t - t_inf) * velocityCoef;326 }327328 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;329 330 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));331 // Pin to mMinX <= mCurrX <= mMaxX332 mCurrX = Math.min(mCurrX, mMaxX);333 mCurrX = Math.max(mCurrX, mMinX);334 335 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));336 // Pin to mMinY <= mCurrY <= mMaxY337 mCurrY = Math.min(mCurrY, mMaxY);338 mCurrY = Math.max(mCurrY, mMinY);339340 if (mCurrX == mFinalX && mCurrY == mFinalY) {341 mFinished = true;342 }343344 break;345 }346 }347 else {//滑动结束348 mCurrX = mFinalX;349 mCurrY = mFinalY;350 mFinished = true;351 }352 return true;353 }
看以看出computeScrollOffset()的处理逻辑是首先判断滑动是否结束,结束的话返回fasle,如果还没有结束则根据当前经过的时间计算出当前应该滑动的位置赋值给mCurrX,mCurrY,然后返回true。
所以我们就可以调用computeScrollOffset()计算出最新的位置,如果computeScrollOffset()返回true代表滑动还没有结束,我们就可以使用scrollTo滑动到最新的位置,然后继续通过通过调用invalidate()方法重复上面的逻辑。
fling方法的使用跟startScroll方法是差不多的,只不过计算的过程更复杂一些,这里就不分析了。
演示Demo的下载链接
如果大家有什么疑问或文章中有讲的不对的地方,欢迎大家提出来讨论,谢谢大家。
- Android源码分析-Scroller
- Android Scroller 源码笔记
- Android Scroller源码解析
- 【android】从源码的角度深入分析Scroller
- android Scroller原理分析
- Android Scroller分析
- Android Scroller分析
- android scroller的原理分析
- android中Scroller类分析
- android scroller的原理分析
- 关于android中scroller类源码浅析
- Android Scroller类的详细分析
- Android Scroller类的详细分析
- Android中Scroller类的分析
- Android中Scroller类的分析
- Android Scroller类的详细分析
- Android Scroller类的详细分析
- Android Scroller类的详细分析
- Ubuntu 秘笈之命令行下管理浏览器书签
- Tomcat在Linux上的安装与配置
- Android View坐标getLeft, getRight, getTop, getBottom解惑
- Android产品研发(十九)-->Android studio中的单元测试
- 瀑布式开发、迭代开发、敏捷开发、XP与SCRUM的区别
- Android源码分析-Scroller
- 数据结构实验之二叉树三:统计叶子数
- LeetCode 405. Convert a Number to Hexadecimal
- javaCV开发详解之技术杂烩:javaCV能帮我们做什么?能实现什么功能?ffmpeg和openCV能实现功能,javaCV如何做到更快、更简单的实现相应的功能?等等一堆实用话题
- [LeetCode] 141. Linked List Cycle
- Java之日期处理
- spring AOP事物管理(声明式事物)
- 均方差公式化简
- Android产品研发(二十)-->代码Review