1、Scroller源码分析
下面是对Scroller源码的分析,并附有源码,如下:
- <span style="font-size:18px;">package android.widget;
-
- import android.content.Context;
- import android.hardware.SensorManager;
- import android.os.Build;
- import android.util.FloatMath;
- import android.view.ViewConfiguration;
- import android.view.animation.AnimationUtils;
- import android.view.animation.Interpolator;
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public class Scroller {
- private int mMode;
-
- private int mStartX;
- private int mStartY;
- private int mFinalX;
- private int mFinalY;
-
- private int mMinX;
- private int mMaxX;
- private int mMinY;
- private int mMaxY;
-
- private int mCurrX;
- private int mCurrY;
- private long mStartTime;
- private int mDuration;
- private float mDurationReciprocal;
- private float mDeltaX;
- private float mDeltaY;
- private boolean mFinished;
-
- private Interpolator mInterpolator;
- private boolean mFlywheel;
-
- private float mVelocity;
- private float mCurrVelocity;
- private int mDistance;
-
-
- private float mFlingFriction = ViewConfiguration.getScrollFriction();
-
- private static final int DEFAULT_DURATION = 250;
- private static final int SCROLL_MODE = 0;
- private static final int FLING_MODE = 1;
-
- private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
- private static final float INFLEXION = 0.35f;
- private static final float START_TENSION = 0.5f;
- private static final float END_TENSION = 1.0f;
- private static final float P1 = START_TENSION * INFLEXION;
- private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
-
- private static final int NB_SAMPLES = 100;
- private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
- private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
-
- private float mDeceleration;
- private final float mPpi;
-
-
- private float mPhysicalCoeff;
-
- static {
- float x_min = 0.0f;
- float y_min = 0.0f;
- for (int i = 0; i < NB_SAMPLES; i++) {
- final float alpha = (float) i / NB_SAMPLES;
-
- float x_max = 1.0f;
- float x, tx, coef;
- while (true) {
- x = x_min + (x_max - x_min) / 2.0f;
- coef = 3.0f * x * (1.0f - x);
- tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
- if (Math.abs(tx - alpha) < 1E-5) break;
- if (tx > alpha) x_max = x;
- else x_min = x;
- }
- SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
-
- float y_max = 1.0f;
- float y, dy;
- while (true) {
- y = y_min + (y_max - y_min) / 2.0f;
- coef = 3.0f * y * (1.0f - y);
- dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
- if (Math.abs(dy - alpha) < 1E-5) break;
- if (dy > alpha) y_max = y;
- else y_min = y;
- }
- SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
- }
- SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
-
-
- sViscousFluidScale = 8.0f;
-
- sViscousFluidNormalize = 1.0f;
- sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
-
- }
-
- private static float sViscousFluidScale;
- private static float sViscousFluidNormalize;
-
-
-
-
- public Scroller(Context context) {
- this(context, null);
- }
-
-
-
-
-
-
- public Scroller(Context context, Interpolator interpolator) {
- this(context, interpolator,
- context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
- }
-
-
-
-
-
-
- public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
- mFinished = true;
- mInterpolator = interpolator;
- mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
- mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
- mFlywheel = flywheel;
-
- mPhysicalCoeff = computeDeceleration(0.84f);
- }
-
-
-
-
-
-
-
-
-
- public final void setFriction(float friction) {
- mDeceleration = computeDeceleration(friction);
- mFlingFriction = friction;
- }
-
- private float computeDeceleration(float friction) {
- return SensorManager.GRAVITY_EARTH
- * 39.37f
- * mPpi
- * friction;
- }
-
-
-
-
-
-
-
- public final boolean isFinished() {
- return mFinished;
- }
-
-
-
-
-
-
- public final void forceFinished(boolean finished) {
- mFinished = finished;
- }
-
-
-
-
-
-
- public final int getDuration() {
- return mDuration;
- }
-
-
-
-
-
-
- public final int getCurrX() {
- return mCurrX;
- }
-
-
-
-
-
-
- public final int getCurrY() {
- return mCurrY;
- }
-
-
-
-
-
-
-
- public float getCurrVelocity() {
- return mMode == FLING_MODE ?
- mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
- }
-
-
-
-
-
-
- public final int getStartX() {
- return mStartX;
- }
-
-
-
-
-
-
- public final int getStartY() {
- return mStartY;
- }
-
-
-
-
-
-
- public final int getFinalX() {
- return mFinalX;
- }
-
-
-
-
-
-
- public final int getFinalY() {
- return mFinalY;
- }
-
-
-
-
-
-
-
- public boolean computeScrollOffset() {
- if (mFinished) {
- return false;
- }
-
- int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
-
- if (timePassed < mDuration) {
- switch (mMode) {
- case SCROLL_MODE:
- float x = timePassed * mDurationReciprocal;
-
- if (mInterpolator == null)
- x = viscousFluid(x);
- else
- x = mInterpolator.getInterpolation(x);
-
- mCurrX = mStartX + Math.round(x * mDeltaX);
- mCurrY = mStartY + Math.round(x * mDeltaY);
- break;
- case FLING_MODE:
- final float t = (float) timePassed / mDuration;
- final int index = (int) (NB_SAMPLES * t);
- float distanceCoef = 1.f;
- float velocityCoef = 0.f;
- if (index < NB_SAMPLES) {
- final float t_inf = (float) index / NB_SAMPLES;
- final float t_sup = (float) (index + 1) / NB_SAMPLES;
- final float d_inf = SPLINE_POSITION[index];
- final float d_sup = SPLINE_POSITION[index + 1];
- velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
- distanceCoef = d_inf + (t - t_inf) * velocityCoef;
- }
-
- mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
-
- mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
-
- mCurrX = Math.min(mCurrX, mMaxX);
- mCurrX = Math.max(mCurrX, mMinX);
-
- mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
-
- mCurrY = Math.min(mCurrY, mMaxY);
- mCurrY = Math.max(mCurrY, mMinY);
-
- if (mCurrX == mFinalX && mCurrY == mFinalY) {
- mFinished = true;
- }
-
- break;
- }
- }
- else {
- mCurrX = mFinalX;
- mCurrY = mFinalY;
- mFinished = true;
- }
- return true;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void startScroll(int startX, int startY, int dx, int dy) {
- startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void startScroll(int startX, int startY, int dx, int dy, int duration) {
- mMode = SCROLL_MODE;
- 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;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- public void fling(int startX, int startY, int velocityX, int velocityY,
- int minX, int maxX, int minY, int maxY) {
-
- if (mFlywheel && !mFinished) {
- float oldVel = getCurrVelocity();
-
- float dx = (float) (mFinalX - mStartX);
- float dy = (float) (mFinalY - mStartY);
- float hyp = FloatMath.sqrt(dx * dx + dy * dy);
-
- float ndx = dx / hyp;
- float ndy = dy / hyp;
-
- float oldVelocityX = ndx * oldVel;
- float oldVelocityY = ndy * oldVel;
- if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
- Math.signum(velocityY) == Math.signum(oldVelocityY)) {
- velocityX += oldVelocityX;
- velocityY += oldVelocityY;
- }
- }
-
- mMode = FLING_MODE;
- mFinished = false;
-
- float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
-
- mVelocity = velocity;
- mDuration = getSplineFlingDuration(velocity);
- mStartTime = AnimationUtils.currentAnimationTimeMillis();
- mStartX = startX;
- mStartY = startY;
-
- float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
- float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
-
- double totalDistance = getSplineFlingDistance(velocity);
- mDistance = (int) (totalDistance * Math.signum(velocity));
-
- mMinX = minX;
- mMaxX = maxX;
- mMinY = minY;
- mMaxY = maxY;
-
- mFinalX = startX + (int) Math.round(totalDistance * coeffX);
-
- mFinalX = Math.min(mFinalX, mMaxX);
- mFinalX = Math.max(mFinalX, mMinX);
-
- mFinalY = startY + (int) Math.round(totalDistance * coeffY);
-
- mFinalY = Math.min(mFinalY, mMaxY);
- mFinalY = Math.max(mFinalY, mMinY);
- }
-
- private double getSplineDeceleration(float velocity) {
- return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
- }
-
- private int getSplineFlingDuration(float velocity) {
- final double l = getSplineDeceleration(velocity);
- final double decelMinusOne = DECELERATION_RATE - 1.0;
- return (int) (1000.0 * Math.exp(l / decelMinusOne));
- }
-
- private double getSplineFlingDistance(float velocity) {
- final double l = getSplineDeceleration(velocity);
- final double decelMinusOne = DECELERATION_RATE - 1.0;
- return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
- }
-
- static float viscousFluid(float x)
- {
- x *= sViscousFluidScale;
- if (x < 1.0f) {
- x -= (1.0f - (float)Math.exp(-x));
- } else {
- float start = 0.36787944117f;
- x = 1.0f - (float)Math.exp(1.0f - x);
- x = start + x * (1.0f - start);
- }
- x *= sViscousFluidNormalize;
- return x;
- }
-
-
-
-
-
-
-
-
- public void abortAnimation() {
- mCurrX = mFinalX;
- mCurrY = mFinalY;
- mFinished = true;
- }
-
-
-
-
-
-
-
-
-
- public void extendDuration(int extend) {
- int passed = timePassed();
- mDuration = passed + extend;
- mDurationReciprocal = 1.0f / mDuration;
- mFinished = false;
- }
-
-
-
-
-
-
- public int timePassed() {
- return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
- }
-
-
-
-
-
-
-
-
- public void setFinalX(int newX) {
- mFinalX = newX;
- mDeltaX = mFinalX - mStartX;
- mFinished = false;
- }
-
-
-
-
-
-
-
-
- public void setFinalY(int newY) {
- mFinalY = newY;
- mDeltaY = mFinalY - mStartY;
- mFinished = false;
- }
-
-
-
-
- public boolean isScrollingInDirection(float xvel, float yvel) {
- return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
- Math.signum(yvel) == Math.signum(mFinalY - mStartY);
- }
- }</span><span style="font-size:24px;">
- </span>
2、Scroller调用关系
对于Scroller的调用,我先使用下面一张图片来阐述:

Scroller的调用过程以及View的重绘:
1 调用public void startScroll(int startX, int startY, int dx, int dy)
该方法为scroll做一些准备工作.
比如设置了移动的起始坐标,滑动的距离和方向以及持续时间等.
该方法并不是真正的滑动scroll的开始,感觉叫prepareScroll()更贴切些.
2 调用invalidate()或者postInvalidate()使View(ViewGroup)树重绘
重绘会调用View的draw()方法
draw()一共有六步:
Draw traversal performs several drawing steps which must be executed
in the appropriate order:
1. Draw the background
2. If necessary, save the canvas' layers to prepare for fading
3. Draw view's content
4. Draw children
5. If necessary, draw the fading edges and restore layers
6. Draw decorations (scrollbars for instance)
其中最重要的是第三步和第四步
第三步会去调用onDraw()绘制内容
第四步会去调用dispatchDraw()绘制子View
重绘分两种情况:
2.1 ViewGroup的重绘
在完成第三步onDraw()以后,进入第四步ViewGroup重写了
父类View的dispatchDraw()绘制子View,于是这样继续调用:
dispatchDraw()-->drawChild()-->child.computeScroll();
2.2 View的重绘
当View调用invalidate()方法时,会导致整个View树进行
从上至下的一次重绘.比如从最外层的Layout到里层的Layout,直到每个子View.
在重绘View树时ViewGroup和View时按理都会经过onMeasure()和onLayout()以及
onDraw()方法.当然系统会判断这三个方法是否都必须执行,如果没有必要就不会调用.
看到这里就明白了:当这个子View的父容器重绘时,也会调用上面提到的线路:
onDraw()-->dispatchDraw()-->drawChild()-->child.computeScroll();
于是子View(比如此处举例的ButtonSubClass类)中重写的computeScroll()方法
就会被调用到.
3 View树的重绘会调用到View中的computeScroll()方法
4 在computeScroll()方法中
在View的源码中可以看到public void computeScroll(){}是一个空方法.
具体的实现需要自己来写.在该方法中我们可调用scrollTo()或scrollBy()
来实现移动.该方法才是实现移动的核心.
4.1 利用Scroller的mScroller.computeScrollOffset()判断移动过程是否完成
注意:该方法是Scroller中的方法而不是View中的!!!!!!
public boolean computeScrollOffset(){ }
Call this when you want to know the new location.
If it returns true,the animation is not yet finished.
loc will be altered to provide the new location.
返回true时表示还移动还没有完成.
4.2 若动画没有结束,则调用:scrollTo(By)();
使其滑动scrolling
5 再次调用invalidate().
调用invalidate()方法那么又会重绘View树.
从而跳转到第3步,如此循环,直到computeScrollOffset返回false
通俗的理解:
从上可见Scroller执行流程里面的三个核心方法
mScroller.startScroll()
mScroller.computeScrollOffset()
view.computeScroll()
1 在mScroller.startScroll()中为滑动做了一些初始化准备.
比如:起始坐标,滑动的距离和方向以及持续时间(有默认值)等.
其实除了这些,在该方法内还做了些其他事情:
比较重要的一点是设置了动画开始时间.
2 computeScrollOffset()方法主要是根据当前已经消逝的时间
来计算当前的坐标点并且保存在mCurrX和mCurrY值中.
因为在mScroller.startScroll()中设置了动画时间,那么
在computeScrollOffset()方法中依据已经消逝的时间就很容易
得到当前时刻应该所处的位置并将其保存在变量mCurrX和mCurrY中.
除此之外该方法还可判断动画是否已经结束.
所以在该示例中:
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
invalidate();
}
}
先执行mScroller.computeScrollOffset()判断了滑动是否结束
2.1 返回false,滑动已经结束.
2.2 返回true,滑动还没有结束.
并且在该方法内部也计算了最新的坐标值mCurrX和mCurrY.
就是说在当前时刻应该滑动到哪里了.
既然computeScrollOffset()如此贴心,盛情难却啊!
于是我们就覆写View的computeScroll()方法,
调用scrollTo(By)滑动到那里
3、Android中View绘制流程以及invalidate()等相关方法分析
关于这部分内容,大家可以查看Android中View绘制流程以及invalidate()等相关方法分析 这篇文章分析,里面详细介绍了onMeasure、onLayout、draw以及他们之间的关系,这里引用里面的一张图:
4、VelocityTracker、ViewConfiguration
VelocityTracker从字面意思理解那就是速度追踪器了,在滑动效果的开发中通常都是要使用该类计算出当前手势的初始速度,对应的方法是velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity))并通过getXVelocity或getYVelocity方法得到对应的速度值initialVelocity,并将获得的速度值传递给Scroller类的fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) 方法进行控件滚动时各种位置坐标数值的计算,API中对fling 方法的解释是基于一个fling手势开始滑动动作,滑动的距离将由所获得的初始速度initialVelocity来决定。关于ViewConfiguration 的使用主要使用了该类的下面三个方法:
configuration.getScaledTouchSlop() //获得能够进行手势滑动的距离
configuration.getScaledMinimumFlingVelocity()//获得允许执行一个fling手势动作的最小速度值
configuration.getScaledMaximumFlingVelocity()//获得允许执行一个fling手势动作的最大速度值
需要重写的方法至少要包含下面几个方法:
onTouchEvent(MotionEvent event)//有手势操作必然少不了这个方法了
computeScroll()//必要时由父控件调用请求或通知其一个子节点需要更新它的mScrollX和mScrollY的值。典型的例子就是在一个子节点正在使用Scroller进行滑动动画时将会被执行。所以,从该方法的注释来看,继承这个方法的话一般都会有Scroller对象出现。
VelocityTracker的初始化以及资源释放的方法:
- <span style="font-family:SimSun;font-size:18px;">private void obtainVelocityTracker(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- }
-
- private void releaseVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }</span>
- }
5、实例开发
- package com.jwzhangjie.scrollview;
-
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.VelocityTracker;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.Scroller;
-
-
-
-
-
- public class MultiViewGroup extends ViewGroup {
-
- private VelocityTracker mVelocityTracker;
- private static final int SNAP_VELOCITY = 600;
- private Scroller mScroller;
- private int mCurScreen;
- private int mDefaultScreen = 0;
- private float mLastMotionX;
- private int deltaX;
-
- private OnViewChangeListener mOnViewChangeListener;
-
- public MultiViewGroup(Context context) {
- this(context, null);
- }
-
- public MultiViewGroup(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(getContext());
- }
-
- private void init(Context context) {
- mScroller = new Scroller(context);
- mCurScreen = mDefaultScreen;
- }
-
- @Override
- public void computeScroll() {
- if (mScroller.computeScrollOffset()) {
- scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
- postInvalidate();
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
- getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
- }
- scrollTo(mCurScreen * width, 0);
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int margeLeft = 0;
- int size = getChildCount();
- for (int i = 0; i < size; i++) {
- View view = getChildAt(i);
- if (view.getVisibility() != View.GONE) {
- int childWidth = view.getMeasuredWidth();
-
- view.layout(margeLeft, 0, margeLeft + childWidth,
- view.getMeasuredHeight());
- margeLeft += childWidth;
- }
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getAction();
- float x = event.getX();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- obtainVelocityTracker(event);
- if (!mScroller.isFinished()) {
- mScroller.abortAnimation();
- }
- mLastMotionX = x;
- break;
- case MotionEvent.ACTION_MOVE:
- deltaX = (int) (mLastMotionX - x);
- if (canMoveDis(deltaX)) {
- obtainVelocityTracker(event);
- mLastMotionX = x;
-
- scrollBy(deltaX, 0);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
-
- obtainVelocityTracker(event);
- mVelocityTracker.computeCurrentVelocity(1000);
- float velocityX = mVelocityTracker.getXVelocity();
-
-
- if (velocityX > SNAP_VELOCITY && mCurScreen > 0) {
-
- snapToScreen(mCurScreen - 1);
- } else if (velocityX < -SNAP_VELOCITY
- && mCurScreen < getChildCount() - 1) {
-
- snapToScreen(mCurScreen + 1);
- } else {
- snapToDestination();
- }
- releaseVelocityTracker();
- break;
- }
-
- return true;
- }
-
-
-
-
-
-
-
- private boolean canMoveDis(int deltaX) {
- int scrollX = getScrollX();
-
- if (deltaX < 0) {
- if (scrollX <= 0) {
- return false;
- } else if (deltaX + scrollX < 0) {
- scrollTo(0, 0);
- return false;
- }
- }
-
- int leftX = (getChildCount() - 1) * getWidth();
- if (deltaX > 0) {
- if (scrollX >= leftX) {
- return false;
- } else if (scrollX + deltaX > leftX) {
- scrollTo(leftX, 0);
- return false;
- }
- }
- return true;
- }
-
-
-
-
-
-
- public void snapToScreen(int whichScreen) {
- int scrollX = getScrollX();
- if (scrollX != (whichScreen * getWidth())) {
- int delta = whichScreen * getWidth() - scrollX;
- mScroller.startScroll(scrollX, 0, delta, 0, Math.abs(delta) * 2);
- mCurScreen = whichScreen;
- invalidate();
- if (mOnViewChangeListener != null) {
- mOnViewChangeListener.OnViewChange(mCurScreen);
- }
- }
- }
-
-
-
-
- private void snapToDestination() {
- int screenWidth = getWidth();
- int whichScreen = (getScrollX() + (screenWidth / 2)) / screenWidth;
- snapToScreen(whichScreen);
- }
-
- private void obtainVelocityTracker(MotionEvent event) {
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.addMovement(event);
- }
-
- private void releaseVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- public void SetOnViewChangeListener(OnViewChangeListener listener) {
- mOnViewChangeListener = listener;
- }
-
- public interface OnViewChangeListener {
- public void OnViewChange(int page);
- }
- }
- package com.jwzhangjie.scrollview;
-
- import com.jwzhangjie.scrollview.MultiViewGroup.OnViewChangeListener;
-
- import android.os.Bundle;
- import android.support.v4.app.FragmentActivity;
- import android.view.View;
- import android.widget.Toast;
-
- public class MultiActivity extends FragmentActivity implements
- OnViewChangeListener {
-
- private MultiViewGroup multiViewGroup;
- private int allScreen;
- private int curreScreen = 0;
-
- @Override
- protected void onCreate(Bundle bundle) {
- super.onCreate(bundle);
- setContentView(R.layout.activity_main);
- multiViewGroup = (MultiViewGroup) findViewById(R.id.screenParent);
- multiViewGroup.SetOnViewChangeListener(this);
- allScreen = multiViewGroup.getChildCount() - 1;
- }
-
- public void nextScreen(View view) {
- if (curreScreen < allScreen) {
- curreScreen++;
- } else {
- curreScreen = 0;
-
- }
- multiViewGroup.snapToScreen(curreScreen);
- }
-
- public void preScreen(View view) {
- if (curreScreen > 0) {
- curreScreen--;
- } else {
- curreScreen = allScreen;
- }
- multiViewGroup.snapToScreen(curreScreen);
- }
-
- @Override
- public void OnViewChange(int page) {
- Toast.makeText(getApplicationContext(),
- getString(R.string.currePage, page), Toast.LENGTH_SHORT).show();
- }
-
- }
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
-
- <Button
- android:id="@+id/nextPage"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="10dip"
- android:background="@drawable/bg_item_num_3_button"
- android:onClick="nextScreen"
- android:text="下一页" />
-
- <Button
- android:id="@+id/prePage"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_margin="10dip"
- android:background="@drawable/bg_item_num_3_button"
- android:onClick="preScreen"
- android:text="上一页" />
-
- <com.jwzhangjie.scrollview.MultiViewGroup
- android:id="@+id/screenParent"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_below="@id/nextPage" >
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#f00" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="第一页" />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#0f0" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="第二页" />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="#00f" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="第三页" />
- </LinearLayout>
- </com.jwzhangjie.scrollview.MultiViewGroup>
-
- </RelativeLayout>

下载地址:https://github.com/jwzhangjie/MultiViewGroup
6、引用
1、Android中View绘制流程以及invalidate()等相关方法分析
2、Android 带你从源码的角度解析Scroller的滚动实现原理
3、 Android中滑屏实现----手把手教你如何实现触摸滑屏以及Scroller类详解
4、Android学习Scroller(五)——详解Scroller调用过程以及View的重绘