@linux1s1s
2017-01-22T08:24:23.000000Z
字数 5393
阅读 2366
AndroidWidget 2015-04
/*** 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(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}}/*** 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);}
上面两二货是干嘛的,通过字面意思应该能猜出来,他们俩是完成从一个地方移动到另外一个地方。当我们想完成一个View的移动功能时,这哥俩都能很好的实现,唯一遗憾的是由于没有平滑过度,用户使用起来有点眩晕,这个时候 Scroller 类就粉墨登场了,它就是为了提高用户体验而生的。为了更形象的认识 Scroller,先来个Demo
package cn.supersugar.tablelayout;import android.widget.LinearLayout;import android.widget.Scroller;import android.app.Activity;import android.content.Context;import android.graphics.Canvas;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.view.View.OnClickListener;public class TestMyTableLayoutActivity extends Activity {private static final String TAG = "TestScrollerActivity";LinearLayout lay1, lay2, lay0;private Scroller mScroller;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mScroller = new Scroller(this);lay1 = new MyLinearLayout(this);lay2 = new MyLinearLayout(this);lay1.setBackgroundDrawable(getResources().getDrawable(R.drawable.ic_launcher));lay2.setBackgroundColor(this.getResources().getColor(android.R.color.white));lay0 = new ContentLinearLayout(this);lay0.setOrientation(LinearLayout.VERTICAL);LinearLayout.LayoutParams p0 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);this.setContentView(lay0, p0);LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);p1.weight = 1;lay0.addView(lay1, p1);LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,LinearLayout.LayoutParams.FILL_PARENT);p2.weight = 1;lay0.addView(lay2, p2);MyButton btn1 = new MyButton(this);MyButton btn2 = new MyButton(this);btn1.setText("btn in layout1");btn2.setText("btn in layout2");btn1.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {mScroller.startScroll(0, 0, -30, -30, 500);//开始滚动,设置初始位置--》结束位置 & 持续时间}});btn2.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {mScroller.startScroll(20, 20, -50, -50, 500);}});lay1.addView(btn1);lay2.addView(btn2);}class MyButton extends Button {public MyButton(Context ctx) {super(ctx);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Log.d(TAG, this.toString() + " onDraw------");}}class MyLinearLayout extends LinearLayout {public MyLinearLayout(Context ctx) {super(ctx);}@Overridepublic void computeScroll() {Log.d(TAG, this.toString() + " computeScroll-----------");if (mScroller.computeScrollOffset())//如果mScroller没有调用startScroll,这里将会返回false。{//因为调用computeScroll函数的是MyLinearLayout实例,//所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例scrollTo(mScroller.getCurrX(), 0);Log.d(TAG, "getCurrX = " + mScroller.getCurrX());//继续让系统重绘getChildAt(0).invalidate();}}}class ContentLinearLayout extends LinearLayout {public ContentLinearLayout(Context ctx) {super(ctx);}@Overrideprotected void dispatchDraw(Canvas canvas) {Log.d(TAG, "contentview dispatchDraw");super.dispatchDraw(canvas);}@Overridepublic void computeScroll() {}}}
上面的代码我们重点看一下L54 和 L61 的mScroller.startScroll(...);方法
public void startScroll(int startX, int startY, int dx, int dy, int duration) {mMode = SCROLL_MODE; //mScroller.computeScrollOffset() 将返回truemFinished = 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;}
初看这个方法以为会引发View的重绘,其实这个方法就是记录一些信息,既然startScroll()这个方法没有引发重绘,那么是谁引发的重绘呢?
我们知道,当点击某个Button,首先会触发 onTouchEvent 事件,接着会检查背景并适合改变背景,对此没有概念的话,你可以阅读博文 Android Drawable 初步,这里稍微提一下这方面的知识。
我们知道,一般在设置用户点击效果的时候,会对这个View设置drawable,在布局文件中引用这个xml文件或者在代码中setBackgroundDrawable的时候使用此xml就可以实现控件按下或有焦点等不同状态的效果。
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@drawable/ad_close_clicked" android:state_pressed="true"/><item android:drawable="@drawable/ad_close_clicked" android:state_focused="true"/><item android:drawable="@drawable/ad_close"/></selector>
Drawable 状态的改变最终会调用方法 invalidateDrawable(...)
public void invalidateDrawable(Drawable drawable) {if (verifyDrawable(drawable)) {final Rect dirty = drawable.getBounds();final int scrollX = mScrollX;final int scrollY = mScrollY;invalidate(dirty.left + scrollX, dirty.top + scrollY,dirty.right + scrollX, dirty.bottom + scrollY);}}
这个方法中我们感兴趣的是 invalidate(...)方法,如果你读过博文 Android View 分析初步(下) 就会知道这个方法会引起View从头到脚的重绘,所以startScroll(...) 方法只是虚晃一枪,真正引起重绘的是Drawable的invalidate(...)方法。
好了,既然重绘请求已发出了,那么整个View系统就会来一次自上而下的绘制了。
