Android 源码解析 - Scroller
分析版本 : Android API 26
介绍
Android开发中,如果我们希望使一个View滑动的话,除了使用属性动画外。
我们还可以使用系统提供给我们的两个类Scroller和OverScroller用来实现弹性滑动。下面分析一下Scroller的使用方法以及实现方式。
View中的scrollBy()和scrollTo()方法介绍
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
//判断当前的偏移量是否等于之前的偏移量
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
//回调onScrollChanged 方法
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
scrollTo()是指将前视图内容横向偏移x距离(x > 0 向左移动,否则反之),纵向偏移y距离(y > 0 向上移动,否则反之)。移动的是View 的内容, 不是View 本身。
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
scrollBy()方法里直接调用了scrollTo()方法,表示在当前偏移量的基础上继续偏移(x,y)
自定义可以滚动的 ScrollTextView
/**
* Created by Owen Chan
* On 2018-01-12.
*/
public class ScrollTextView extends android.support.v7.widget.AppCompatTextView {
private Scroller mScroller;
private int mLeft = 0;
private int mTop = 0;
public ScrollTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
public void startScrollerScroll() {
mScroller.startScroll(mLeft, mTop, 0, -160, 10000);
invalidate();
}
public void startScrollerFling() {
mScroller.fling(mLeft, mTop, 0, -5000, mLeft, mLeft, 200, 12000);
}
}
通过调用startScrollerScroll()与startScrollerFling() 方法我们发现View 中的内容位置发生了变化,首先我们看看流程图 怎么触发View 重新绘制的
Scroller类只负责计算,它并不负责操作View的滚动,调用了ScrollTextView 的startScrollerScroll()方法后调用了invalidate()方法。invalidate()方法导致View重新绘制,因此会调用View的draw()方法,在View的draw()方法中又会去调用computeScroll()方法,computeScroll()方法在View中是一个空实现,在ScrollTextView 中我们实现了computeScroll()方法。在上面的computeScroll()方法中,我们调用了mScroller.computeScrollOffset()方法来计算当前滑动的偏移量。如果还在滑动过程中就会返回true。所以我们就能在if中通过Scroller拿到当前的滑动坐标从而做任何我们想做的处理。从而形成了滑动动画。
下面我们解释一下Scroller的两个方法的具体作用:
/**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
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;
}
mStartX:起始滑动点的X坐标
startY:起始滑动点Y的坐标
dx:滑动的水平偏移量, dx > 0 向左滑动, 否则 向右滑动
dy:滑动的垂直偏移量,dy>0 向上滑动,否则向下滑动
duration:滑动的执行时间
/**
* Start scrolling based on a fling gesture. The distance travelled will
* depend on the initial velocity of the fling.
*
* @param startX Starting point of the scroll (X)
* @param startY Starting point of the scroll (Y)
* @param velocityX Initial velocity of the fling (X) measured in pixels per
* second.
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
* second
* @param minX Minimum X value. The scroller will not scroll past this
* point.
* @param maxX Maximum X value. The scroller will not scroll past this
* point.
* @param minY Minimum Y value. The scroller will not scroll past this
* point.
* @param maxY Maximum Y value. The scroller will not scroll past this
* point.
*/
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY) {
// Continue a scroll or fling in progress
if (mFlywheel && !mFinished) {
float oldVel = getCurrVelocity();
float dx = (float) (mFinalX - mStartX);
float dy = (float) (mFinalY - mStartY);
float hyp = (float) Math.hypot(dx, dy);
float ndx = dx / hyp;
float ndy = dy / hyp;
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;
}
}
startX 起始滑动点的X坐标
startY 起始滑动点的Y坐标
velocityX X方向上的加速度
velocityY Y方向上的加速度
minX X方向上滑动的最小值,不会滑动超过这个点
maxX X方向上滑动的最大值,不会滑动超过这个点
minY Y方向上滑动的最小值,不会滑动超过这个点
maxY Y方向上滑动的最大值,不会滑动超过这个点
Scroller源码解析
1、 构造方法
/**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used. Specify whether or
* not to support progressive "flywheel" behavior in flinging.
*/
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
}
注释写的很清楚,可以传入自定义的interpolator和是否支持飞轮flywheel的功能,当然这两个并不是必须的。如果不传入interpolator会默认创建一个ViscousFluidInterpolator,从字面意义上看是一个粘性流体插值器。对于flywheel是指是否支持在滑动过程中,如果有新的fling()方法调用是否累加加速度。
2、startScroll()方法的实现
/**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
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;
}
注释里有对变量进行解释
####3.computeScrollOffset() 方法中 SCROLL_MODE 的实现
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
*/
public boolean computeScrollOffset() {
//判断滚动是否结束
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;
case FLING_MODE:
......
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
到这整个的过程就结束了 。
下一篇: CSS技巧(1)清除浮动
推荐阅读
-
Android 源码解析 - Scroller
-
jQuery 源码解析(三十) 动画模块 $.animate()详解
-
spring5 源码深度解析----- AOP目标方法和增强方法的执行(100%理解AOP)
-
Scroller的使用及解析
-
Android开发:JSON简介及最全面解析方法(Gson、AS自带org.json、Jackson解析)
-
Android 网络图片查看器与网页源码查看器
-
Android自定义属性 format的深入解析
-
Android8.1 SystemUI源码分析之 Notification流程
-
spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)
-
PostgreSQL xlog文件名规范源码解析