Android Scroller 解读编写Demo
Scroller是一个专门用于处理滚动效果的工具类,大多数情况下,我们直接使用Scroller的场景并不多,但是很多大家所熟知的控件在内部都是使用Scroller来实现的,如ViewPager、ListView等。而如果能够把Scroller的用法熟练掌握的话,对于之后使用ViewPager更容易上手。
直接上源码,代码已经做了详细解读:
public class ScrollerLayout extends ViewGroup {
/**
* 用于完成滚动操作的实例
*/
private Scroller mScroller;
/**
* 判定为拖动的最小移动像素数
*/
private int mTouchSlop;
/**
* 手机按下时的屏幕坐标
*/
private float mXDown;
/**
* 手机当时所处的屏幕坐标
*/
private float mXMove;
/**
* 上次触发ACTION_MOVE事件时的屏幕坐标
*/
private float mXLastMove;
/**
* 界面可滚动的左边界
*/
private int leftBorder;
/**
* 界面可滚动的右边界
*/
private int rightBorder;
public ScrollerLayout(Context context, AttributeSet attr) {
super(context,attr);
// 第一步,创建Scroller的实例
mScroller=new Scroller(context);
ViewConfiguration configuration=ViewConfiguration.get(context);
//能够识别的最小滑动举例 ViewConfiguration.getScaledTouchSlop();
//获取TouchSlop值
mTouchSlop= configuration.getScaledTouchSlop();
}
//定义View的位置在ViewGroup中的位置
//1)参数changed表示view有新的尺寸或位置;
//2)参数l表示相对于父view的Left位置;
//3)参数t表示相对于父view的Top位置;
//4)参数r表示相对于父view的Right位置;
//5)参数b表示相对于父view的Bottom位置。.
@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 childView=getChildAt(i);
// 为ScrollerLayout中的每一个子控件在水平方向上进行布局
//layout(a,b,c,d)中的四个参数是相对于父布局定义的
//a和 b 是控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离。
// c 和 d是空间右边缘和下边缘相对于父类控件左边缘和上边缘的距离。
childView.layout(i * childView.getMeasuredWidth(), 0, (i + 1) * childView.getMeasuredWidth(), childView.getMeasuredHeight());
}
// 初始化左右边界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
}
//View本身大小多少,由onMeasure()来决定
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//返回包含View的个数
int childCount=getChildCount();
for(int i=0;i<childCount;i++){
//获取布局中的view
View childView=getChildAt(i);
//规定了每个子view的尺寸,与当前的父View保持一致
measureChild(childView,widthMeasureSpec,widthMeasureSpec);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
//手指按下
case MotionEvent.ACTION_DOWN:
//获取绝对坐标,相对于屏幕的X坐标
mXDown = ev.getRawX();
mXLastMove = mXDown;
break;
//手指滑动
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float diff = Math.abs(mXMove - mXDown);
mXLastMove = mXMove;
// 当手指拖动值大于TouchSlop值时,认为应该进行滚动,拦截子控件的事件
if (diff > mTouchSlop) {
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
mXMove=event.getRawX();
int scrolledX=(int)(mXLastMove-mXMove);
//getScrollX() 就是当前view的左上角相对于母视图的左上角的X轴偏移量
if(getScrollX()+scrolledX<leftBorder){
scrollTo(leftBorder, 0);
return true;
}else if(getScrollX() + getWidth()+scrolledX >rightBorder){
scrollTo(rightBorder - getWidth(), 0);
return true;
}
scrollBy(scrolledX, 0);
mXLastMove = mXMove;
//手指离开屏幕
case MotionEvent.ACTION_UP:
// 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面
int targetIndex = (getScrollX() + getWidth() / 2) / getWidth();
int dx = targetIndex * getWidth() - getScrollX();
// 第二步,调用startScroll()方法来初始化滚动数据并刷新界面
//invalidate()函数的主要作用是请求View树进行重绘
// startScroll执行过程中即在duration时间内,
// computeScrollOffset 方法会一直返回false,但当动画执行完成后会返回返加true.
mScroller.startScroll(getScrollX(), 0, dx, 0);
invalidate();
break;
}
return super.onTouchEvent(event);
}
@Override
public void computeScroll() {
// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//invalidate()函数的主要作用是请求View树进行重绘
invalidate();
}
}