@linux1s1s
2017-01-22T16:24:23.000000Z
字数 5393
阅读 2103
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;
@Override
public 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() {
@Override
public void onClick(View v) {
mScroller.startScroll(0, 0, -30, -30, 500);
//开始滚动,设置初始位置--》结束位置 & 持续时间
}
});
btn2.setOnClickListener(new OnClickListener() {
@Override
public 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);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, this.toString() + " onDraw------");
}
}
class MyLinearLayout extends LinearLayout {
public MyLinearLayout(Context ctx) {
super(ctx);
}
@Override
public 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);
}
@Override
protected void dispatchDraw(Canvas canvas) {
Log.d(TAG, "contentview dispatchDraw");
super.dispatchDraw(canvas);
}
@Override
public void computeScroll() {
}
}
}
上面的代码我们重点看一下L54 和 L61 的mScroller.startScroll(...);
方法
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE; //mScroller.computeScrollOffset() 将返回true
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;
}
初看这个方法以为会引发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系统就会来一次自上而下的绘制了。