
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>
参考链接:
热门源码