- CompoundButton 源码分析
- LinearLayout 源码分析
- SearchView 源码解析
- LruCache 源码解析
- ViewDragHelper 源码解析
- BottomSheets 源码解析
- Media Player 源码分析
- NavigationView 源码解析
- Service 源码解析
- Binder 源码分析
- Android 应用 Preference 相关及源码浅析 SharePreferences 篇
- ScrollView 源码解析
- Handler 源码解析
- NestedScrollView 源码解析
- SQLiteOpenHelper/SQLiteDatabase/Cursor 源码解析
- Bundle 源码解析
- LocalBroadcastManager 源码解析
- Toast 源码解析
- TextInputLayout
- LayoutInflater 和 LayoutInflaterCompat 源码解析
- TextView 源码解析
- NestedScrolling 事件机制源码解析
- ViewGroup 源码解析
- StaticLayout 源码分析
- AtomicFile 源码解析
- AtomicFile 源码解析
- Spannable 源码分析
- Notification 之 Android 5.0 实现原理
- CoordinatorLayout 源码分析
- Scroller 源码解析
- SwipeRefreshLayout 源码分析
- FloatingActionButton 源码解析
- AsyncTask 源码分析
- TabLayout 源码解析
3. 源码分析
我们依然通过调用流程来分析 Scroller
的实现:
1. 构造方法
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 }
最终都会调用最后一个构造方法。必须传入 Context
对象。可以传入自定义的 interpolator
和是否支持飞轮 flywheel
的功能,当然这两个并不是必须的。如果不传入 interpolator
会默认创建一个 ViscousFluidInterpolator
,从字面意义上看是一个粘性流体插值器。
对于 flywheel
是指是否支持在滑动过程中,如果有新的 fling()
方法调用是否累加加速度。如果不传默认在 2.3 以上都会支持。剩下就是初始化了一些用于计算的参数。这样就完成了 Scroller
的初始化了。下面我们来看看 startScroll()
方法的实现:
2. startScroll() 方法的实现
public void startScroll(int startX, int startY, int dx, int dy, int duration) { // mMode 分两种方式 1.滑动:SCROLL_MODE 2. 加速度滑动:FLING_MODE mMode = SCROLL_MODE; // 是否滑动结束 这里是开始所以设置为 false mFinished = false; // 滑动的时间 mDuration = duration; // 开始的时间 mStartTime = AnimationUtils.currentAnimationTimeMillis(); // 开始滑动点的 X 坐标 mStartX = startX; // 开始滑动点的 Y 坐标 mStartY = startY; // 最终滑动到位置的 X 坐标 mFinalX = startX + dx; // 最终滑动到位置的 Y 坐标 mFinalY = startY + dy; // X 方向上滑动的偏移量 mDeltaX = dx; // Y 方向上滑动的偏移量 mDeltaY = dy; // 持续时间的倒数 最终用来计算得到插值器返回的值 mDurationReciprocal = 1.0f / (float) mDuration; }
很简单只是一些变量的赋值。根据我们前面使用方法里的分析,最终会调用 computeScrollOffset()
方法:
3. computeScrollOffset() 方法中 SCROLL_MODE 的实现
// 当你需要知道新的位置的时候调用这个方法,如果动画还未结束则返回 true public boolean computeScrollOffset() { //如果已经结束 则直接返回 false if (mFinished) { return false; } //得到以及度过的时间 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); //如果还在动画时间内 if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: // 根据 timePassed * mDurationReciprocal,从 mInterpolator 中取出当前需要偏移量的比例 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal); // 赋值给 mCurrX,mCurrY 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; }
首先的到当前时间与滑动开始时间的时间差,如果还在滑动时间内则通过插值器获得当前的进度并乘以总偏移量并赋值给 mCurrX
, mCurrY
。如果已经结束则直接将 mFinalX
和 mFinalY
赋值并将 mFinished
设置�为 true
。所以这样我们就能通过 getCurrX()
和 getCurrY()
来得到对应的 mCurrX
和 mCurrY
来做相应的处理了。整个 Scroll
的过程就是这样了。
4. fling() 方法的实现
public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { // 如果前一次滑动还未结束,又调用了新的 fling() 方法时, // 则累加相同方向上加速度 if (mFlywheel && !mFinished) { float oldVel = getCurrVelocity(); float dx = (float) (mFinalX - mStartX); float dy = (float) (mFinalY - mStartY); float hyp = FloatMath.sqrt(dx * dx + dy * 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; } } //设置为 FLING_MODE mMode = FLING_MODE; mFinished = false; //根据勾股定理获得总加速度 float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY); mVelocity = velocity; // 通过加速度得到滑动持续时间 mDuration = getSplineFlingDuration(velocity); mStartTime = AnimationUtils.currentAnimationTimeMillis(); 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)); mMinX = minX; mMaxX = maxX; mMinY = minY; mMaxY = maxY; mFinalX = startX + (int) Math.round(totalDistance * coeffX); // Pin to mMinX <= mFinalX <= mMaxX mFinalX = Math.min(mFinalX, mMaxX); mFinalX = Math.max(mFinalX, mMinX); mFinalY = startY + (int) Math.round(totalDistance * coeffY); // Pin to mMinY <= mFinalY <= mMaxY mFinalY = Math.min(mFinalY, mMaxY); mFinalY = Math.max(mFinalY, mMinY); }
依然是为计算需要的各种变量赋值。因为引入了加速度的概念所以变得相对复杂,首先先判断了如果一次滑动未结束又触发另一次滑动时,是否需要累加加速度。然后是设置 mMode
为 FLING_MODE
。然后根据 velocityX
和 velocityY
算出总的加速度 velocity
,紧接着算出这个加速度下可以滑动的距离 mDistance
。最后再通过 x
或 y
方向上的加速度比值以及我们设定的最大值和最小值来给 mFinalX
或 mFinalY
赋值。赋值结束后,通过调用 invalidate()
,最终依然会调用 computeScrollOffset()
方法:
5. computeScrollOffset() 方法中 FLING_MODE 的实现
public boolean computeScrollOffset() { if (mFinished) { return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); if (timePassed < mDuration) { switch (mMode) { case SCROLL_MODE: ... break; 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 与 mCurrY mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX)); // 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 { mCurrX = mFinalX; mCurrY = mFinalY; mFinished = true; } return true; }
由于 fling()
方法中将 mMode
赋值为 FLING_MODE
。所以我们直接来看 FLING_MODE
中的代码。可以看出根据当前滑动时间与总滑动时间的比例。再根据一个 SPLINE_POSITION
数组计算出了距离系数 distanceCoef
与加速度系数 velocityCoef
。再根据这两个系数计算出当前加速度与当前的 mCurrX
与 mCurrY
。关于 SPLINE_POSITION
的初始化是在下面的静态代码块里赋值的:
static { float x_min = 0.0f; float y_min = 0.0f; for (int i = 0; i < NB_SAMPLES; i++) { final float alpha = (float) i / NB_SAMPLES; float x_max = 1.0f; float x, tx, coef; while (true) { x = x_min + (x_max - x_min) / 2.0f; coef = 3.0f * x * (1.0f - x); tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x; if (Math.abs(tx - alpha) < 1E-5) break; if (tx > alpha) x_max = x; else x_min = x; } SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x; float y_max = 1.0f; float y, dy; while (true) { y = y_min + (y_max - y_min) / 2.0f; coef = 3.0f * y * (1.0f - y); dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y; if (Math.abs(dy - alpha) < 1E-5) break; if (dy > alpha) y_max = y; else y_min = y; } SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y; } SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f; }
我并没有看懂这段代码的实际意义。网上也没有找到比较清晰的解释。通过 debug
得知 SPLINE_POSITION
是一个长度为 101
并且从 0-1
递增数组。猜想这应该是一个函数模型并且最终用于计算出滑动过程中的加速度与位置。至此 Scroller
的两个主要方法的实现我们就分析完了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论