[关闭]
@linux1s1s 2017-01-22T08:24:23.000000Z 字数 5393 阅读 1375

Android Scroller

AndroidWidget 2015-04


View.scrollTo

  1. /**
  2. * Set the scrolled position of your view. This will cause a call to
  3. * {@link #onScrollChanged(int, int, int, int)} and the view will be
  4. * invalidated.
  5. * @param x the x position to scroll to
  6. * @param y the y position to scroll to
  7. */
  8. public void scrollTo(int x, int y) {
  9. if (mScrollX != x || mScrollY != y) {
  10. int oldX = mScrollX;
  11. int oldY = mScrollY;
  12. mScrollX = x;
  13. mScrollY = y;
  14. invalidateParentCaches();
  15. onScrollChanged(mScrollX, mScrollY, oldX, oldY);
  16. if (!awakenScrollBars()) {
  17. postInvalidateOnAnimation();
  18. }
  19. }
  20. }
  21. /**
  22. * Move the scrolled position of your view. This will cause a call to
  23. * {@link #onScrollChanged(int, int, int, int)} and the view will be
  24. * invalidated.
  25. * @param x the amount of pixels to scroll by horizontally
  26. * @param y the amount of pixels to scroll by vertically
  27. */
  28. public void scrollBy(int x, int y) {
  29. scrollTo(mScrollX + x, mScrollY + y);
  30. }

上面两二货是干嘛的,通过字面意思应该能猜出来,他们俩是完成从一个地方移动到另外一个地方。当我们想完成一个View的移动功能时,这哥俩都能很好的实现,唯一遗憾的是由于没有平滑过度,用户使用起来有点眩晕,这个时候 Scroller 类就粉墨登场了,它就是为了提高用户体验而生的。为了更形象的认识 Scroller,先来个Demo

Scroller

  1. package cn.supersugar.tablelayout;
  2. import android.widget.LinearLayout;
  3. import android.widget.Scroller;
  4. import android.app.Activity;
  5. import android.content.Context;
  6. import android.graphics.Canvas;
  7. import android.os.Bundle;
  8. import android.util.Log;
  9. import android.view.View;
  10. import android.widget.Button;
  11. import android.view.View.OnClickListener;
  12. public class TestMyTableLayoutActivity extends Activity {
  13. private static final String TAG = "TestScrollerActivity";
  14. LinearLayout lay1, lay2, lay0;
  15. private Scroller mScroller;
  16. @Override
  17. public void onCreate(Bundle savedInstanceState) {
  18. super.onCreate(savedInstanceState);
  19. mScroller = new Scroller(this);
  20. lay1 = new MyLinearLayout(this);
  21. lay2 = new MyLinearLayout(this);
  22. lay1.setBackgroundDrawable(getResources().getDrawable(R.drawable.ic_launcher));
  23. lay2.setBackgroundColor(this.getResources().getColor(
  24. android.R.color.white));
  25. lay0 = new ContentLinearLayout(this);
  26. lay0.setOrientation(LinearLayout.VERTICAL);
  27. LinearLayout.LayoutParams p0 = new LinearLayout.LayoutParams(
  28. LinearLayout.LayoutParams.FILL_PARENT,
  29. LinearLayout.LayoutParams.FILL_PARENT);
  30. this.setContentView(lay0, p0);
  31. LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(
  32. LinearLayout.LayoutParams.FILL_PARENT,
  33. LinearLayout.LayoutParams.FILL_PARENT);
  34. p1.weight = 1;
  35. lay0.addView(lay1, p1);
  36. LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(
  37. LinearLayout.LayoutParams.FILL_PARENT,
  38. LinearLayout.LayoutParams.FILL_PARENT);
  39. p2.weight = 1;
  40. lay0.addView(lay2, p2);
  41. MyButton btn1 = new MyButton(this);
  42. MyButton btn2 = new MyButton(this);
  43. btn1.setText("btn in layout1");
  44. btn2.setText("btn in layout2");
  45. btn1.setOnClickListener(new OnClickListener() {
  46. @Override
  47. public void onClick(View v) {
  48. mScroller.startScroll(0, 0, -30, -30, 500);
  49. //开始滚动,设置初始位置--》结束位置 & 持续时间
  50. }
  51. });
  52. btn2.setOnClickListener(new OnClickListener() {
  53. @Override
  54. public void onClick(View v) {
  55. mScroller.startScroll(20, 20, -50, -50, 500);
  56. }
  57. });
  58. lay1.addView(btn1);
  59. lay2.addView(btn2);
  60. }
  61. class MyButton extends Button {
  62. public MyButton(Context ctx) {
  63. super(ctx);
  64. }
  65. @Override
  66. protected void onDraw(Canvas canvas) {
  67. super.onDraw(canvas);
  68. Log.d(TAG, this.toString() + " onDraw------");
  69. }
  70. }
  71. class MyLinearLayout extends LinearLayout {
  72. public MyLinearLayout(Context ctx) {
  73. super(ctx);
  74. }
  75. @Override
  76. public void computeScroll() {
  77. Log.d(TAG, this.toString() + " computeScroll-----------");
  78. if (mScroller.computeScrollOffset())//如果mScroller没有调用startScroll,这里将会返回false。
  79. {
  80. //因为调用computeScroll函数的是MyLinearLayout实例,
  81. //所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例
  82. scrollTo(mScroller.getCurrX(), 0);
  83. Log.d(TAG, "getCurrX = " + mScroller.getCurrX());
  84. //继续让系统重绘
  85. getChildAt(0).invalidate();
  86. }
  87. }
  88. }
  89. class ContentLinearLayout extends LinearLayout {
  90. public ContentLinearLayout(Context ctx) {
  91. super(ctx);
  92. }
  93. @Override
  94. protected void dispatchDraw(Canvas canvas) {
  95. Log.d(TAG, "contentview dispatchDraw");
  96. super.dispatchDraw(canvas);
  97. }
  98. @Override
  99. public void computeScroll() {
  100. }
  101. }
  102. }

上面的代码我们重点看一下L54 和 L61 的mScroller.startScroll(...);方法

  1. public void startScroll(int startX, int startY, int dx, int dy, int duration) {
  2. mMode = SCROLL_MODE; //mScroller.computeScrollOffset() 将返回true
  3. mFinished = false;
  4. mDuration = duration;
  5. mStartTime = AnimationUtils.currentAnimationTimeMillis();
  6. mStartX = startX;
  7. mStartY = startY;
  8. mFinalX = startX + dx;
  9. mFinalY = startY + dy;
  10. mDeltaX = dx;
  11. mDeltaY = dy;
  12. mDurationReciprocal = 1.0f / (float) mDuration;
  13. }

初看这个方法以为会引发View的重绘,其实这个方法就是记录一些信息,既然startScroll()这个方法没有引发重绘,那么是谁引发的重绘呢?

我们知道,当点击某个Button,首先会触发 onTouchEvent 事件,接着会检查背景并适合改变背景,对此没有概念的话,你可以阅读博文 Android Drawable 初步,这里稍微提一下这方面的知识。

我们知道,一般在设置用户点击效果的时候,会对这个View设置drawable,在布局文件中引用这个xml文件或者在代码中setBackgroundDrawable的时候使用此xml就可以实现控件按下或有焦点等不同状态的效果。

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3. <item android:drawable="@drawable/ad_close_clicked" android:state_pressed="true"/>
  4. <item android:drawable="@drawable/ad_close_clicked" android:state_focused="true"/>
  5. <item android:drawable="@drawable/ad_close"/>
  6. </selector>

Drawable 状态的改变最终会调用方法 invalidateDrawable(...)

  1. public void invalidateDrawable(Drawable drawable) {
  2. if (verifyDrawable(drawable)) {
  3. final Rect dirty = drawable.getBounds();
  4. final int scrollX = mScrollX;
  5. final int scrollY = mScrollY;
  6. invalidate(dirty.left + scrollX, dirty.top + scrollY,
  7. dirty.right + scrollX, dirty.bottom + scrollY);
  8. }
  9. }

这个方法中我们感兴趣的是 invalidate(...)方法,如果你读过博文 Android View 分析初步(下) 就会知道这个方法会引起View从头到脚的重绘,所以startScroll(...) 方法只是虚晃一枪,真正引起重绘的是Drawable的invalidate(...)方法。

好了,既然重绘请求已发出了,那么整个View系统就会来一次自上而下的绘制了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注