Scroller源码详解
View弹性滑动详解
之前写了一个滚动选择控件
WheelView,在这个控件中我设计了弹性滚动的实现机制,再了解View弹性滚动之前,我们先来学习一下View滚动机制的实现.
View的scrollTo/scrollBy
这里基于Android5.0版本的源码介绍View类中这两个函数的具体实现.
scrollTo源码如下:
/*** 对View设置滚动的x和y轴坐标.* @param x x轴滚动的终点坐标* @param y y轴滚动的终点坐标*/
public void scrollTo(int x, int y) {if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}
}
scrollBy源码如下:
/*** 设置View的x轴和y轴的滚动增量.* @param x x轴的滚动增量* @param y y轴的滚动增量*/
public void scrollBy(int x, int y) {scrollTo(mScrollX + x, mScrollY + y);
}
从上述源码可以看出,scrollBy依赖于scrollTo的实现.他俩的区别是:
scrollBy的x和y是滚动增量,即在上次滚动的终点坐标上增加x和y,是相对滑动.(注意:x和y可能为负数)
scrollTo的x和y是滚动的终点坐标,是绝对滑动.
而且,scrollTo的实现也仅仅是修改了mScrollX和mScrollY的值,然后调用了invalidate方法重绘了View.那么,mScrollX和mScrollY的含义是什么呢?
- mScrollX:View的左边缘和View内容左边缘在x轴上的距离,即View左边缘x轴坐标-View内容左边缘的x轴坐标.
- mScrollY:View的上边缘和View内容上边缘在y轴上的距离,即View上边缘y轴坐标-View内容上边缘的y轴坐标.
同时,需要明确很重要的一点:View的滑动并非是View的滑动,而是View内容的滑动.
提供一个图示来理解View的滑动:
缺陷:
虽然调用View的scrollBy和scrollTo方法可以很方便的实现View的滚动,但是这种滚动是瞬间完成的(调用invalidate方法),没有弹性滑动的效果,为了达到弹性滑动的目的,我们开始介绍本篇文章的主角:Scroller.
Scroller
在介绍Scroller之前,我们需要明确知道:
Scroller代码和View代码完全解耦,Scroller代码本身不会引起View的滑动,通过Scroller代码,我们可以平滑的获取当前View需要滑动的位置,然后调用View的scrollTo/scrollBy进行移动.
构造函数
我们先来看一下Scroller的构造函数注释源码:
/*** 使用默认的滑动时间和插值器构造Scroller.*/
public Scroller(Context context) {this(context, null);
}/*** 使用给定的插值器来构造Scroller.*/
public Scroller(Context context, Interpolator interpolator) {this(context, interpolator,context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
}/*** 使用给定的插值器来构造Scroller.Android3.0以上的版本支持"flywheel"的行为.*/
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {// 设置滑动停止标识位为truemFinished = true;// 构造插值器if (interpolator == null) {mInterpolator = new ViscousFluidInterpolator();} else {mInterpolator = interpolator;}// 获取屏幕的密度(每英寸的像素数)mPpi = context.getResources().getDisplayMetrics().density * 160.0f;// 计算摩擦力mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());// 标记是否支持flying模式mFlywheel = flywheel;mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
}
两种模式
Scroller支持两种模式的滑动,分别是:
- SCROLL_MODE:调用startScroll,正常滚动模式.
- FLING_MODE:调用fling,抛掷模式.
接下来,针对这两种模式进行分别讲解.
SCROLL_MODE
我们直接看一下startScroll的源码做了哪些事情:
/*** 给定滚动起始点坐标,在指定的时间内滚动指定的偏移量.* 距离计算:* dx=view左边缘-view内容左边缘;dx为正,代表内容向左移动;dx为负,代表内容向右移动.* dy=view上边缘-view内容上边缘;dy为正,代表内容向上移动;dx为负,代表内容向下移动.** @param startX x轴方向滚动起始点坐标.* @param startY y轴方向滚动起始点坐标.* @param dx x轴方向滚动距离.* @param dy y轴方向滚动距离.* @param duration 滚动持续的时间(默认滚动时间为250ms).*/
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;
}
通过注释的源码,我们可以验证最初的结论:Scroller和View完全解耦,Scroller并不会直接控制View的滑动,它只是为View提供滑动的参数.具体参数包括:
- mMode: 设置为滑动模式.
- mFinished: 设置滑动结束标识为false.
- mDuration: 设置滑动时间间隔.
- mStartTime: 设置滑动的起始时间.
- mStartX: 设置x轴的起始点坐标.
- mStartY: 设置Y轴的起始点坐标
- mFinalX: 设置x轴的终点坐标.
- mFinalY: 设置y轴的终点坐标.
- mDeltaX: 设置x轴的滑动距离.
- mDeltaY: 设置y轴的滑动距离.
- mDurationReciprocal: 设置时间的倒数.
computeScrollOffset
之所以这里提前介绍computeScrollOffset函数,是因为View只有配合computeScrollOffset函数,才能实现真正的滑动.源码中跟SCROLL_MODE相关代码如下:
public boolean computeScrollOffset() {// 如果已经结束,直接返回false.if (mFinished) {return false;}// 计算已经度过的时间.int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);if (timePassed < mDuration) {switch (mMode) {// 处理滚动模式case SCROLL_MODE:// 根据过度的时间计算偏移比例final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);mCurrX = mStartX + Math.round(x * mDeltaX);mCurrY = mStartY + Math.round(x * mDeltaY);break;// 处理fling模式......}} else {// 当时间结束时,直接将x和y坐标置为终止状态的x和y坐标,同时将终止标志位置为true.mCurrX = mFinalX;mCurrY = mFinalY;mFinished = true;}return true;
}
可以看出,computeScrollOffset也只是根据时间偏移计算x轴和y轴应该到达的坐标.
SCROLL_MODE实战
介绍了SCROLL_MODE的具体实现,接下来就通过代码演示一下Scroller是如何和View进行互动的.这里提供一个例子,在40秒内将TextView的内容在x轴向右移动400:
private void initScrollCase() {mImageView = (ImageView) findViewById(R.id.id_img_tv);// 获取起始滑动点坐标int startX = mImageView.getScrollX();int startY = mImageView.getScrollY();mScroller = new Scroller(getApplicationContext());Log.e("zhengyi.wzy", "startX=" + startX + ", startY=" + startY);mScroller.startScroll(startX, startY, -400, 0, 40000);mImageView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mHandler.postDelayed(new Runnable() {@Overridepublic void run() {boolean isFinished = mScroller.computeScrollOffset();if (!isFinished) {return;}// 获取当前滑动点坐标int x = mScroller.getCurrX();int y = mScroller.getCurrY();mImageView.scrollTo(x, y);mHandler.postDelayed(this, 25);}}, 25);}});
}
千万注意:起始点是View.getScrollX()和View.getScrollY(),而不是View.getLeft()或者View.getTop()
FLING_MODE
Scroller还提供一种FLING模式,我认为它的中文翻译应该叫“抛掷模式”.fling的英文注释源码如下:
/*** Start scrolling based on a fling gesture. The distance travelled will* depend on the initial velocity of the fling.** @param startX x轴的起始坐标.* @param startY y轴的起始坐标.* @param velocityX x轴方向的初始速率.* @param velocityY y轴方向的初始速率.* @param minX x轴终点最小值.* @param maxX x轴终点最大值.* @param minY y轴终点最小值.* @param maxY y轴终点最大值.*/
public void fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY) {// 如果上次滑动也是FLING_MODE并且滑动没有结束if (mFlywheel && !mFinished) {// 获取之前的总速率float oldVel = getCurrVelocity();float dx = (float) (mFinalX - mStartX);float dy = (float) (mFinalY - mStartY);float hyp = (float) Math.sqrt(dx * dx + dy * dy);float ndx = dx / hyp;float ndy = dy / hyp;// 通过距离比例计算出x轴和y轴的速率float oldVelocityX = ndx * oldVel;float oldVelocityY = ndy * oldVel;// 如果速率方向相同,则进行速率累加if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&Math.signum(velocityY) == Math.signum(oldVelocityY)) {velocityX += oldVelocityX;velocityY += oldVelocityY;}}// 设置模式为FLING_MODEmMode = FLING_MODE;// 设置结束标志位为false.mFinished = false;// 根据勾股定理获取总的速率float velocity = (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY);mVelocity = velocity;// 通过速率获取滑动的持续时间mDuration = getSplineFlingDuration(velocity);// 获取滑动起始时间mStartTime = AnimationUtils.currentAnimationTimeMillis();// 获取起始x轴和y轴坐标mStartX = startX;mStartY = startY;float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;// 获取最大滑动距离double totalDistance = getSplineFlingDistance(velocity);mDistance = (int) (totalDistance * Math.signum(velocity));// 计算终点的x轴和y轴坐标mMinX = minX;mMaxX = maxX;mMinY = minY;mMaxY = maxY;mFinalX = startX + (int) Math.round(totalDistance * coeffX);// Pin to mMinX <= mFinalX <= mMaxXmFinalX = Math.min(mFinalX, mMaxX);mFinalX = Math.max(mFinalX, mMinX);mFinalY = startY + (int) Math.round(totalDistance * coeffY);// Pin to mMinY <= mFinalY <= mMaxYmFinalY = Math.min(mFinalY, mMaxY);mFinalY = Math.max(mFinalY, mMinY);
}
computeScrollOffset
跟FLING_MODE模式相关源码如下:
/*** 用来返回当前View需要移动到的x轴和y轴坐标.*/
public boolean computeScrollOffset() {// 如果已经结束,直接返回false.if (mFinished) {return false;}// 计算已经度过的时间.int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);if (timePassed < mDuration) {switch (mMode) {// 处理fling模式case FLING_MODE:// 获取当前滑动时间与总时间的比例final float t = (float) timePassed / mDuration;final int index = (int) (NB_SAMPLES * t);float distanceCoef = 1.f;float velocityCoef = 0.f;if (index < NB_SAMPLES) {final float t_inf = (float) index / NB_SAMPLES;final float t_sup = (float) (index + 1) / NB_SAMPLES;final float d_inf = SPLINE_POSITION[index];final float d_sup = SPLINE_POSITION[index + 1];velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);distanceCoef = d_inf + (t - t_inf) * velocityCoef;}mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));// Pin to mMinX <= mCurrX <= mMaxXmCurrX = Math.min(mCurrX, mMaxX);mCurrX = Math.max(mCurrX, mMinX);mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));// Pin to mMinY <= mCurrY <= mMaxYmCurrY = Math.min(mCurrY, mMaxY);mCurrY = Math.max(mCurrY, mMinY);if (mCurrX == mFinalX && mCurrY == mFinalY) {mFinished = true;}break;}} else {// 当时间结束时,直接将x和y坐标置为终止状态的x和y坐标,同时将终止标志位置为true.mCurrX = mFinalX;mCurrY = mFinalY;mFinished = true;}return true;
}
FLING_MODE在通过速率计算当前的位置的代码我还不是特别清楚,主要是算法的实现,可能我物理太久没碰生疏了.但是用法都是统一的,至于FLING模式的使用场景,大家可以结果手势检测类(GestureDetector)去进行使用.
Scroller源码详解相关推荐
- 【Live555】live555源码详解(九):ServerMediaSession、ServerMediaSubsession、live555MediaServer
[Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的三个类所在的位置: ServerMediaSession.ServerMediaSubsession.Dy ...
- 【Live555】live555源码详解系列笔记
[Live555]liveMedia下载.配置.编译.安装.基本概念 [Live555]live555源码详解(一):BasicUsageEnvironment.UsageEnvironment [L ...
- 【Live555】live555源码详解(八):testRTSPClient
[Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的testRTSPClient实现的三个类所在的位置: ourRTSPClient.StreamClient ...
- 【Live555】live555源码详解(七):GenericMediaServer、RTSPServer、RTSPClient
[Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的三个类所在的位置: GenericMediaServer.RTSPServer.RTSPClient 14 ...
- 【Live555】live555源码详解(六):FramedSource、RTPSource、RTPSink
[Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的三个类所在的位置: FramedSource.RTPSource.RTPSink 11.FramedSou ...
- 【Live555】live555源码详解(五):MediaSource、MediaSink、MediaSession、MediaSubsession
[Live555]live555源码详解系列笔记 继承协作关系图 下面红色表示本博客将要介绍的四个类所在的位置: MediaSource.MediaSink.MediaSession.MediaSub ...
- 【Live555】live555源码详解(四):Medium媒体基础类
[Live555]live555源码详解系列笔记 7.Media Medai所依赖关系图 依赖Medai关系图 Media和UsageEnvironment关联图
- 【Live555】live555源码详解(二):BasicHashTable、DelayQueue、HandlerSet
[Live555]live555源码详解系列笔记 3.BasicHashTable 哈希表 协作图: 3.1 BasicHashTable BasicHashTable 继承自 HashTable 重 ...
- 【Live555】live555源码详解(一):BasicUsageEnvironment、UsageEnvironment
[Live555]live555源码详解系列笔记 类关系图 1.UsageEnvironment 详解 1.1 BasicUsageEnvironment BasicUsageEnvironment ...
最新文章
- Thinkphp5 开发 OA 办公系统 - 数据库设计
- 龙剑服务器为什么总是维修,《龙剑》2014年3月13日更新维护公告
- 利用python计算偏差-方差权衡
- java虚拟机学习-JVM调优总结-新一代的垃圾回收算法(11)
- python 提升效率_@Python 程序员,如何最大化提升编码效率?
- 论得失。。。技术方向
- 防止sql注入的方法
- python3.7安装tensorflow-gpu_tensorflow-gpu安装的常见问题及解决方案
- 02 ZooKeeper分布式集群安装
- 学生食堂信息管理系统
- windows无法格式化u盘_U盘无法格式化的解决方法
- 服务器KVM虚拟键盘怎么打开,KVM虚拟机键盘布局问题的解决
- RT-thread 环境下使用 HASH hwcrypto 配置使用底层硬件HAH库问题记录
- 白蛋白纳米粒|莫西沙星小鼠血清白蛋白MSA纳米粒|利多卡因大鼠血清白蛋白RSA纳米粒
- 摄像头拍照及解析QR二维码
- apache和nginx对比
- 转载:揭秘内容付费的三种商业模式(原作者:小马宋)
- Java核心编程(22)
- newman执行测试_newman执行postman脚本
- java rsi_高频交易算法研发心得--RSI指标及应用