以前做一个看书的项目时,有个翻书的功能,当手指滑动书页移动一段然后抬起后,需要页面view自动完成剩余的操作:
1、当滑动距离大于某个设定值时,自动滚动到末尾处,翻一页。
2、当滑动距离小于该设定值时,自动回滚到起始处,还原。
实现这个功能,当时是用了Scroller来实现的。

我一步步来说下吧:

[b]1、Scroller的最简单用法解释[/b]

网上有很多关于Scroller的用法,很多讲的真的很不错,但我觉得,他们的讲解还是过于繁琐,这里我就最最简单的说下吧,这个Scroller到底怎么回事,且看下面代码:

下面的这个demo1,就一个布局,linearlayout上放置一个button,点击button,我们就调用Scroller的相关方法:

布局文件xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_margin="10dp"
android:text="run Scroller " />
</LinearLayout>


控制器activity:


import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.Scroller;

public class MainActivity extends Activity {
private Scroller mScroller;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mScroller = new Scroller(this);
this.setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mScroller.startScroll(-10, -100,- 200, -300, 1000);
new Thread(){
public void run() {
while(mScroller.computeScrollOffset())// 如果mScroller没有调用startScroll,这里将会返回false。
{
Log.i("scroller", "getCurrX()= "+mScroller.getCurrX()+" getCurrY()="+mScroller.getCurrY());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}

}

};

}.start();
}
});
}
}


运行后,点击按钮,就可以看到效果,且看控制台日志输出:


[quote]05-02 11:30:55.455: I/scroller(30287): getCurrX()= -10 getCurrY()=-100


05-02 11:30:55.510: I/scroller(30287): getCurrX()= -26 getCurrY()=-124


05-02 11:30:55.557: I/scroller(30287): getCurrX()= -63 getCurrY()=-180


05-02 11:30:55.612: I/scroller(30287): getCurrX()= -111 getCurrY()=-251


05-02 11:30:55.658: I/scroller(30287): getCurrX()= -144 getCurrY()=-301


05-02 11:30:55.713: I/scroller(30287): getCurrX()= -166 getCurrY()=-334


05-02 11:30:55.760: I/scroller(30287): getCurrX()= -181 getCurrY()=-356


05-02 11:30:55.815: I/scroller(30287): getCurrX()= -191 getCurrY()=-371


05-02 11:30:55.862: I/scroller(30287): getCurrX()= -197 getCurrY()=-381


05-02 11:30:55.916: I/scroller(30287): getCurrX()= -201 getCurrY()=-387


05-02 11:30:55.963: I/scroller(30287): getCurrX()= -204 getCurrY()=-391


05-02 11:30:56.018: I/scroller(30287): getCurrX()= -206 getCurrY()=-394


05-02 11:30:56.065: I/scroller(30287): getCurrX()= -208 getCurrY()=-396


05-02 11:30:56.119: I/scroller(30287): getCurrX()= -208 getCurrY()=-398


05-02 11:30:56.166: I/scroller(30287): getCurrX()= -209 getCurrY()=-398


05-02 11:30:56.221: I/scroller(30287): getCurrX()= -209 getCurrY()=-399


05-02 11:30:56.268: I/scroller(30287): getCurrX()= -210 getCurrY()=-399


05-02 11:30:56.322: I/scroller(30287): getCurrX()= -210 getCurrY()=-400


05-02 11:30:56.369: I/scroller(30287): getCurrX()= -210 getCurrY()=-400


05-02 11:30:56.424: I/scroller(30287): getCurrX()= -210 getCurrY()=-400


05-02 11:30:56.471: I/scroller(30287): getCurrX()= -210 getCurrY()=-400[/quote]


从日志输出的数据一看,你应该能大致知道这个scroller的作用了吧


mScroller.startScroll(-10, -100,- 200, -300, 1000);


我们在button的事件处理里做的是调用了


public void startScroll (int startX, int startY, int dx, int dy,int duration)


这个方法,然后我们在线程中一直查看scroller的几个属性数值,然后打印了出来,从日志可以看出,scroller中的这些数值,是按(int startX, int startY, int dx, int dy)来变化的,并且是在intduration这个时间段内完成的。我们设置线程的睡眠时间是50毫秒,而打印了总共20条日志,20* 50 = 1000,正好是我们设置的这个时间。



如果我们在调用了


public void startScroll (int startX, int startY, int dx, int dy,in tduration)


让我们的view重新绘制,并且利用scroller的几个属性数值来确定view的位置或其他的什么,然后不断的循环调用,你说会出现什么呢?答案就不用说了吧(view会动起来吧),这个就是我们平时在项目中利用scroller的主要思路了,而一般的时候,我们会多绕了几道弯儿而已。



[b]2、Scroller结合view的用法[/b]



首先,我们简单的介绍下view,查看android的源码,你会发现如下的方法:


/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll(){}


该方法就是留给我们去覆写的,它一般都会被该view的父类viewGroup在绘制该view时调用,具体的,就不多说了,大家可以参见相关博客,如果我们在这个方法里,调用 scroller的相关属性来修改view的相关属性或调用其他方法,是不是可以做很多事呢?


下面看看改造后的demo2:



先介绍一个函数,view下面的:


[b]public void scrollTo (int x, int y)[/b] Added in API level 1


Set the scrolled position of your view. This will cause a call to onScrollChanged(int, int, int, int) and the view will be invalidated.



Parameters


x the x position to scroll to


y the y position to scroll to



该函数可以使view中的内容滚动到指定位置:


demo2:



该demo没有xml,直接就一个activity:


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.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Scroller;

public class MainActivity extends Activity {
LinearLayout demoSubview1, demoSubview2, demoViewGroup;
private Scroller mScrollerViewGroup;
private Scroller mScrollerView;
// private Scroller mScroller;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mScrollerViewGroup = new Scroller(this);
mScrollerView = new Scroller(this);
demoSubview1 = new DemoView(this);
demoSubview2 = new DemoView(this);

demoSubview1.setBackgroundColor(this.getResources().getColor(
android.R.color.darker_gray));
demoSubview2.setBackgroundColor(this.getResources().getColor(
android.R.color.white));
demoViewGroup = new DemoViewGroup(this);
demoViewGroup.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams p0 = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT);
this.setContentView(demoViewGroup, p0);

LinearLayout.LayoutParams p1 = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT);
p1.weight = 1;
demoViewGroup.addView(demoSubview1, p1);
LinearLayout.LayoutParams p2 = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT);
p2.weight = 1;
demoViewGroup.addView(demoSubview2, p2);
DemoButton btn1 = new DemoButton(this);
btn1.setText("run Scroller in viewGroup");
btn1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

mScrollerViewGroup.startScroll(-10, -100,- 200, -300, 1000);
new Thread(){
public void run() {
while(mScrollerViewGroup.computeScrollOffset())// 如果mScroller没有调用startScroll,这里将会返回false。
{
Log.i("scroller", "getCurrX()= "+mScrollerViewGroup.getCurrX()+" getCurrY()="+mScrollerViewGroup.getCurrY());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}

}

};

}.start();
}
});

demoSubview1.addView(btn1);
DemoButton btn2 = new DemoButton(this);
btn2.setText("run Scroller in view");
btn2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {

mScrollerView.startScroll(-10, -100,- 200, -300, 1000);
new Thread(){
public void run() {
while(mScrollerView.computeScrollOffset())// 如果mScroller没有调用startScroll,这里将会返回false。
{
Log.i("scroller", "getCurrX()= "+mScrollerView.getCurrX()+" getCurrY()="+mScrollerView.getCurrY());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}

}

};

}.start();
}
});

demoSubview2.addView(btn2);

}

class DemoButton extends Button {
public DemoButton(Context ctx) {
super(ctx);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.i("DemoButton", "------ onDraw------");
}

public void computeScroll() {
Log.i("DemoButton", " --------------------computeScroll-----------");
// Log.i(TAG, "getCurrX = " + mScroller.getCurrX());
if (mScrollerView.computeScrollOffset())// 如果mScroller没有调用startScroll,这里将会返回false。
{
// 因为调用computeScroll函数的是MyLinearLayout实例,
// 所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例
scrollTo(mScrollerView.getCurrX(), 0);
Log.i("DemoButton", "getCurrX = " + mScrollerView.getCurrX());

// 继续让系统重绘
invalidate();
}
}
}

class DemoView extends LinearLayout {
public DemoView(Context ctx) {
super(ctx);
}

@Override
public void computeScroll() {
Log.i("DemoView", " DemoView --------------------computeScroll-----------");
if (mScrollerViewGroup.computeScrollOffset())// 如果mScroller没有调用startScroll,这里将会返回false。
{
// 因为调用computeScroll函数的是MyLinearLayout实例,
// 所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例
scrollTo(mScrollerViewGroup.getCurrX(), 0);
Log.i("DemoView", "getCurrX = " + mScrollerViewGroup.getCurrX());
// 继续让系统重绘
getChildAt(0).invalidate();
}
}
}

class DemoViewGroup extends LinearLayout {
public DemoViewGroup(Context ctx) {
super(ctx);
}

@Override
protected void dispatchDraw(Canvas canvas) {
Log.i("DemoViewGroup", "contentview dispatchDraw");
super.dispatchDraw(canvas);
}
}
}


运行程序效果:

Android Scroller的使用及自我理解_android



点击第一个button后,日志如下:


[quote]05-02 13:38:02.291: I/DemoViewGroup(332): contentview dispatchDraw


05-02 13:38:02.291: I/DemoView(332): DemoView --------------------computeScroll------


05-02 13:38:02.301: I/DemoButton(332): --------------------computeScroll-----------


05-02 13:38:02.311: I/DemoButton(332): ------ onDraw------


05-02 13:38:02.311: I/DemoView(332): DemoView --------------------computeScroll------


05-02 13:38:02.311: I/DemoButton(332): --------------------computeScroll-----------


05-02 13:38:02.311: I/DemoButton(332): ------ onDraw------


05-02 13:39:50.432: I/scroller(332): getCurrX()= -11 getCurrY()=-101</b> 05-02 13:39:50.432: I/DemoViewGroup(332): contentview dispatchDraw


05-02 13:39:50.432: I/DemoView(332): DemoView --------------------computeScroll------


05-02 13:39:50.432: I/DemoView(332): getCurrX = -12


05-02 13:39:50.442: I/DemoButton(332): --------------------computeScroll-----------


05-02 13:39:50.442: I/DemoButton(332): ------ onDraw------


......................<b>此处省略部分日志</b>...................................................


05-02 13:39:51.412: I/DemoViewGroup(332): contentview dispatchDraw


05-02 13:39:51.412: I/DemoView(332): DemoView --------------------computeScroll------


05-02 13:39:51.412: I/DemoView(332): getCurrX = -210


05-02 13:39:51.412: I/DemoButton(332): --------------------computeScroll-----------


05-02 13:39:51.422: I/DemoButton(332): ------ onDraw------


05-02 13:39:51.432: I/DemoViewGroup(332): contentview dispatchDraw


05-02 13:39:51.432: I/DemoView(332): DemoView --------------------computeScroll------


05-02 13:39:51.432: I/DemoButton(332): --------------------computeScroll-----------


05-02 13:39:51.432: I/DemoButton(332): ------ onDraw------[/quote]


通过日志,看出什么了没?


另外,通过该日志,你了解了view的绘画机制没,应该能发现吧。



最终的效果图片:

Android Scroller的使用及自我理解_android_02


点击buttom2,日志就不给出了,效果图片如下:

Android Scroller的使用及自我理解_ide_03


button内的文字移动到了不可见处,



所以调用scrollTo移动的将是该view的内容,如果是viewGroup的话,就移动了它的孩子view了



所以调用scrollTo移动的将是该实例的孩子,也就是MyButton实例



这会儿明白了没?


后面我会尝试的自己模拟一个scrolelr,其实他的作用就是存储一些基本的数值,其他的啥也没做,也和view没有真正关系的,它只是被动的被调用的