说到Scroller,就必须说到两个方法:
scrollTo():让View相对于初始的位置滚动某段距离
scrollBy():让View相对于当前的位置滚动某段距离
还有一点就是这两个方法都只能滚动ViewGroup内部的子布局
这一点写一个简单的布局就可以验证:
<RelativeLayout 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"
>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dip"
android:background="@android:color/darker_gray"
/>
<Button
android:id="@+id/leftButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView移向屏幕的左边"
android:layout_centerHorizontal="true"
android:layout_marginTop="150dip"/>
<Button
android:id="@+id/rightButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView移向屏幕的右边"
android:layout_centerHorizontal="true"
android:layout_marginTop="250dip"/>
</RelativeLayout>
public class MainActivity extends Activity {
private TextView mTextView;
private Button mLeftButton;
private Button mRightButon;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
}
private void init(){
mTextView=(TextView) findViewById(R.id.textView);
mLeftButton=(Button) findViewById(R.id.leftButton);
mLeftButton.setOnClickListener(new ClickListenerImpl());
mRightButon=(Button) findViewById(R.id.rightButton);
mRightButon.setOnClickListener(new ClickListenerImpl());
}
private class ClickListenerImpl implements OnClickListener {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.leftButton:
//让mTextView的内容往左移
mTextView.scrollBy(30, 0);
//让mLeftButton的内容也往左移
mLeftButton.scrollBy(20, 0);
break;
case R.id.rightButton:
//让mTextView的内容往右移直接到-30的位置
mTextView.scrollTo(-30, 0);
break;
default:
break;
}
}
}
}
Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤:
1. 创建Scroller的实例
2. 调用startScroll()方法来初始化滚动数据并刷新界面
3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
举个例子:
public class ScrollLayout extends ViewGroup {
/**
* 完成滚动操作
*/
private Scroller mScroller;
/**
* 最小移动像素说
*/
private int mTouchSlop;
/**
* 手指按下时的屏幕坐标
*/
private float mDownX;
/**
* 手机当时所处的屏幕坐标
*/
private float mMoveX;
/**
* 上次触发ACTION_MOVE事件时的屏幕坐标
*/
private float mXLastMove;
/**
* 界面可滚动的左边界
*/
private int leftBorder;
/**
* 界面可滚动的右边界
*/
private int rightBorder;
public ScrollLayout(Context context) {
this(context, null);
}
public ScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// 第一步,创建Scroller的实例
mScroller = new Scroller(context);
//这个类主要定义了UI中所使用到的标准常量,
// 像超时、尺寸、距离,如果我们需要得到这些常量的数据,
// 我们就可以通过这个类来获取
ViewConfiguration configuration = ViewConfiguration.get(context);
// ViewConfigurationCompat 干什么用的?
// 获取TouchSlop值 getScaledPagingTouchSlop: 触发移动事件的最短距离
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}
public ScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 为ScrollerLayout中的每一个子控件测量大小
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 为ScrollLayout中的每一个子控件在水平方向上进行布局
child.layout(i * child.getMeasuredWidth(),
0, (i + 1) * child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
//初始化左右边界
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
Log.e("leftBorder>>>", leftBorder + "");
Log.e("rightBorder>>>", rightBorder + "");
}
private boolean isScrollToNext = false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getRawX();
mXLastMove = mDownX;
LastTime = System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
mMoveX = ev.getRawX();
float diff = Math.abs(mMoveX - mDownX);
Log.e("diff>>>>>", diff + "");
Log.e("mTouchSlop>>>>>", mTouchSlop + "");
mXLastMove = mMoveX;
// 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
if (diff > mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
long LastTime;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Long currentTime = System.currentTimeMillis();
//getRawX :表示相对于屏幕左上角的x坐标值
//getX :是表示Widget相对于自身左上角的x坐标,
mMoveX = event.getRawX();
int scrolledX = (int) (mXLastMove - mMoveX);
if (getScrollX() + scrolledX < leftBorder) {
//边界保护,,防止用户拖出边界
scrollTo(leftBorder, 0);
return true;
} else if (getScrollX() + getWidth() + scrolledX > rightBorder) {
scrollTo(rightBorder - getWidth(), 0);
return true;
}
scrollBy(scrolledX, 0);
Log.e("移动熟读",scrolledX/(currentTime-LastTime)+"");
if(scrolledX/(currentTime-LastTime)>0){
isScrollToNext = true;
}else {
isScrollToNext = false;
}
mXLastMove = mMoveX;
LastTime = currentTime;
break;
case MotionEvent.ACTION_UP:
// 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
// 第二步,调用startScroll()方法来初始化滚动数据并刷新界面
Log.e("是否已到下一页",isScrollToNext+"");
mScroller.startScroll(getScrollX(), 0, dx, 0);
invalidate();
break;
}
return super.onTouchEvent(event);
}
/**
* 在其内部完成平滑滚动的逻辑 。在整个后续的平滑滚动过程中,computeScroll()方法是会一直被调用的,
* 我们需要不断调用Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了
* 如果还没完成的话,那就继续调用scrollTo()方法,并把Scroller的curX和curY坐标传入,然后刷新界面从而完成平滑滚动的操作。
*/
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
}
1、首先在ScrollerLayout的构造函数里面我们进行了上述步骤中的第一步操作,即创建Scroller的实例,
2、在构建函数中我们还初始化的TouchSlop的值,这个值在后面将用于判断当前用户的操作是否是拖动
3、重写onMeasure()方法和onLayout()方法,在onMeasure()方法中测量ScrollerLayout里的每一个子控件的大小,在onLayout()方法中为ScrollerLayout里的每一个子控件在水平方向上进行布局。
4、重写onInterceptTouchEvent()方法, 在这个方法中我们记录了用户手指按下时的X坐标位置,以及用户手指在屏幕上拖动时的X坐标位置,当两者之间的距离大于TouchSlop值时,就认为用户正在拖动布局,然后我们就将事件在这里拦截掉,阻止事件传递到子控件当中。
5、将事件交给ScrollerLayout的onTouchEvent()方法来处理。如果当前事件是ACTION_MOVE,说明用户正在拖动布局,那么我们就应该对布局内容进行滚动从而影响拖动事件,实现的方式就是使用我们刚刚所学的scrollBy()方法,用户拖动了多少这里就scrollBy多少。另外为了防止用户拖出边界这里还专门做了边界保护,当拖出边界时就调用scrollTo()方法来回到边界位置。
6、当前事件是ACTION_UP时,说明用户手指抬起来了,但是目前很有可能用户只是将布局拖动到了中间,我们不可能让布局就这么停留在中间的位置,因此接下来就需要借助Scroller来完成后续的滚动操作。首先这里我们先根据当前的滚动位置来计算布局应该继续滚动到哪一个子控件的页面,然后计算出距离该页面还需滚动多少距离。接下来我们就该进行上述步骤中的第二步操作,调用startScroll()方法来初始化滚动数据并刷新界面。startScroll()方法接收四个参数,第一个参数是滚动开始时X的坐标,第二个参数是滚动开始时Y的坐标,第三个参数是横向滚动的距离,正值表示向左滚动,第四个参数是纵向滚动的距离,正值表示向上滚动。紧接着调用invalidate()方法来刷新界面。
7、重写computeScroll()方法,并在其内部完成平滑滚动的逻辑 。在整个后续的平滑滚动过程中,computeScroll()方法是会一直被调用的,因此我们需要不断调用Scroller的computeScrollOffset()方法来进行判断滚动操作是否已经完成了,如果还没完成的话,那就继续调用scrollTo()方法,并把Scroller的curX和curY坐标传入,然后刷新界面从而完成平滑滚动的操作。
原文链接:http://blog.csdn.net/guolin_blog/article/details/48719871