public final int getScrollX() {可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也类似。偏移量mScrollX的正、负代表着,滑动控件中的 内容相对于 初始位置在水平方向上偏移情况, mScrollX为正代表着当前内容相对于初始位置向左偏移了 mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。
return mScrollX;
}
@Override public boolean onTouchEvent(MotionEvent event) { int y = (int) event.getY(); int action = event.getAction(); switch (action){ case MotionEvent.ACTION_DOWN: mLastY = y; break; case MotionEvent.ACTION_MOVE: int dy = mLastY - y;//本次手势滑动了多大距离 int oldScrollY = getScrollY();//先计算之前已经偏移了多少距离 int scrollY = oldScrollY + dy;//本次需要偏移的距离=之前已经偏移的距离+本次手势滑动了多大距离 if(scrollY < 0){ scrollY = 0; } if(scrollY > getHeight() - mScreenHeight){ scrollY = getHeight() - mScreenHeight; } scrollTo(getScrollX(),scrollY); mLastY = y; break; } return true; }
public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x;//赋值新的x偏移量 mScrollY = y;//赋值新的y偏移量 invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } }
public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }
public class MyViewPager extends ViewGroup { private int mLastX; public MyViewPager(Context context) { super(context); init(context); } public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MyViewPager(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); for(int i = 0; i < count; i++){ View child = getChildAt(i); child.measure(widthMeasureSpec,heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b); for(int i = 0; i < count; i++){ View child = getChildAt(i); child.layout(i * getWidth(), t, (i+1) * getWidth(), b); } } @Override public boolean onTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: mLastX = x; break; case MotionEvent.ACTION_MOVE: int dx = mLastX - x; int oldScrollX = getScrollX();//原来的偏移量 int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量 if(preScrollX > (getChildCount() - 1) * getWidth()){ preScrollX = (getChildCount() - 1) * getWidth(); } if(preScrollX < 0){ preScrollX = 0; } scrollTo(preScrollX,getScrollY()); mLastX = x; break; } return true; } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <com.scu.lly.viewtest.view.MyViewPager android:layout_width="match_parent" android:layout_height="300dp" > <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test1" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test2" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test3" /> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@drawable/test4" /> </com.scu.lly.viewtest.view.MyViewPager> </LinearLayout>
public void startScroll(int startX, int startY, int dx, int dy, int duration)
public boolean computeScrollOffset()
public class Scroller {
private int mStartX;//水平方向,滑动时的起点偏移坐标
private int mStartY;//垂直方向,滑动时的起点偏移坐标
private int mFinalX;//滑动完成后的偏移坐标,水平方向
private int mFinalY;//滑动完成后的偏移坐标,垂直方向
private int mCurrX;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,水平方向
private int mCurrY;//滑动过程中,根据消耗的时间计算出的当前的滑动偏移距离,垂直方向
private int mDuration; //本次滑动的动画时间
private float mDeltaX;//滑动过程中,在达到mFinalX前还需要滑动的距离,水平方向
private float mDeltaY;//滑动过程中,在达到mFinalX前还需要滑动的距离,垂直方向
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
* 开始一个动画控制,由(startX , startY)在duration时间内前进(dx,dy)个单位,即到达偏移坐标为(startX+dx , startY+dy)处
*/
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;
}
/**
* 滑动过程中,根据当前已经消逝的时间计算当前偏移的坐标点,保存在mCurrX和mCurrY值中
* @return
*/
public boolean computeScrollOffset() {
if (mFinished) {//已经完成了本次动画控制,直接返回为false
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);//计算出当前的滑动偏移位置,x轴
mCurrY = mStartY + Math.round(x * mDeltaY);//计算出当前的滑动偏移位置,y轴
break;
...
}
}else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
...
}
/** * 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. * 由父视图调用用来请求子视图根据偏移值 mScrollX,mScrollY重新绘制 */ public void computeScroll() { //空方法 ,自定义滑动功能的ViewGroup必须实现方法体 }
@Override public boolean onTouchEvent(MotionEvent ev) { initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); int x = (int) ev.getX(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } mLastX = x; break; case MotionEvent.ACTION_MOVE: int dx = mLastX - x; int oldScrollX = getScrollX();//原来的偏移量 int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量 if(preScrollX > (getChildCount() - 1) * getWidth()){ preScrollX = (getChildCount() - 1) * getWidth(); } if(preScrollX < 0){ preScrollX = 0; } //开始滑动动画 mScroller.startScroll(mScroller.getFinalX(),mScroller.getFinalY(),dx,0);//第一步 //注意,一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制 invalidate(); mLastX = x; break; } return true; } @Override public void computeScroll() { super.computeScroll(); if(mScroller.computeScrollOffset()){//第二步 scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//第三步 invalidate(); } }
public class MyViewPager3 extends ViewGroup { private int mLastX; private Scroller mScroller; private VelocityTracker mVelocityTracker; private int mTouchSlop; private int mMaxVelocity; /** * 当前显示的是第几个屏幕 */ private int mCurrentPage = 0; public MyViewPager3(Context context) { super(context); init(context); } public MyViewPager3(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MyViewPager3(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context) { mScroller = new Scroller(context); ViewConfiguration config = ViewConfiguration.get(context); mTouchSlop = config.getScaledPagingTouchSlop(); mMaxVelocity = config.getScaledMinimumFlingVelocity(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int count = getChildCount(); for(int i = 0; i < count; i++){ View child = getChildAt(i); child.measure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); Log.d("TAG","--l-->"+l+",--t-->"+t+",-->r-->"+r+",--b-->"+b); for(int i = 0; i < count; i++){ View child = getChildAt(i); child.layout(i * getWidth(), t, (i + 1) * getWidth(), b); } } @Override public boolean onTouchEvent(MotionEvent ev) { initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); int x = (int) ev.getX(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } mLastX = x; break; case MotionEvent.ACTION_MOVE: int dx = mLastX - x; /* 注释的里面是使用startScroll()来进行滑动的 int oldScrollX = getScrollX();//原来的偏移量 int preScrollX = oldScrollX + dx;//本次滑动后形成的偏移量 if (preScrollX > (getChildCount() - 1) * getWidth()) { preScrollX = (getChildCount() - 1) * getWidth(); dx = preScrollX - oldScrollX; } if (preScrollX < 0) { preScrollX = 0; dx = preScrollX - oldScrollX; } mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, 0); //注意,使用startScroll后面一定要进行invalidate刷新界面,触发computeScroll()方法,因为单纯的startScroll()是属于Scroller的,只是一个辅助类,并不会触发界面的绘制 invalidate(); */ //但是一般在ACTION_MOVE中我们直接使用scrollTo或者scrollBy更加方便 scrollBy(dx,0); mLastX = x; break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000); int initVelocity = (int) velocityTracker.getXVelocity(); if(initVelocity > mMaxVelocity && mCurrentPage > 0){//如果是快速的向右滑,则需要显示上一个屏幕 Log.d("TAG","----------------快速的向右滑--------------------"); scrollToPage(mCurrentPage - 1); }else if(initVelocity < -mMaxVelocity && mCurrentPage < (getChildCount() - 1)){//如果是快速向左滑动,则需要显示下一个屏幕 Log.d("TAG","----------------快速的向左滑--------------------"); scrollToPage(mCurrentPage + 1); }else{//不是快速滑动的情况,此时需要计算是滑动到 Log.d("TAG","----------------慢慢的滑动--------------------"); slowScrollToPage(); } recycleVelocityTracker(); break; } return true; } /** * 缓慢滑动抬起手指的情形,需要判断是停留在本Page还是往前、往后滑动 */ private void slowScrollToPage() { //当前的偏移位置 int scrollX = getScrollX(); int scrollY = getScrollY(); //判断是停留在本Page还是往前一个page滑动或者是往后一个page滑动 int whichPage = (getScrollX() + getWidth() / 2 ) / getWidth() ; scrollToPage(whichPage); } /** * 滑动到指定屏幕 * @param indexPage */ private void scrollToPage(int indexPage) { mCurrentPage = indexPage; if(mCurrentPage > getChildCount() - 1){ mCurrentPage = getChildCount() - 1; } //计算滑动到指定Page还需要滑动的距离 int dx = mCurrentPage * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(),0,dx,0,Math.abs(dx) * 2);//动画时间设置为Math.abs(dx) * 2 ms //记住,使用Scroller类需要手动invalidate invalidate(); } @Override public void computeScroll() { Log.d("TAG", "---------computeScrollcomputeScrollcomputeScroll--------------"); super.computeScroll(); if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); invalidate(); } } private void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } private void initVelocityTrackerIfNotExists() { if(mVelocityTracker == null){ mVelocityTracker = VelocityTracker.obtain(); } } }
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.lusheep.viewtest.view.MyViewPager3
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#999" >
<ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test1" />
<ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test2" />
<ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test3" />
<ImageView
android:layout_width="300dp"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/test4" />
</com.lusheep.viewtest.view.MyViewPager3>
</LinearLayout>
参考链接: