ViewPager源码分析——滑动切换页面处理过程
上周客户反馈Contacts快速滑动界面切换tab有明显卡顿,让优化。
自己验证又没发现卡顿现象,但总得给客户一个技术性的回复,于是看了一下ViewPager源码中处理滑动切换tab的过程。
ViewPager 源码位置: android\frameworks\support\v4\java\android\support\v4\view\ViewPager.java
ViewPager其实就是一个重写的ViewGroup,使用ViewPager可以参考SDK中的demo:sdk\extras\android\support\samples
ViewPager.java开头的注释中有推荐一个demo,使用了supportv13
* <p>Here is a more complicated example of ViewPager, using it in conjuction
* with {@link android.app.ActionBar} tabs. You can find other examples of using
* ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
*
* {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
* complete}
ViewPager滑动是处理Touch事件,所以有必要了解Touch事件的分发过程。可以参考这篇 http://blog.csdn.net/guolin_blog/article/details/9153747
public class ViewPager extends ViewGroup{ ...... @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; // Always take care of the touch gesture being complete.
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Release the drag.
if (DEBUG) Log.v(TAG, "Intercept done!");
mIsBeingDragged = false;
mIsUnableToDrag = false;
mActivePointerId = INVALID_POINTER;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
return false;
} // Nothing more to do here if we have decided whether or not we
// are dragging.
if (action != MotionEvent.ACTION_DOWN) {
if (mIsBeingDragged) {
if (DEBUG) Log.v(TAG, "Intercept returning true!");
return true;
}
if (mIsUnableToDrag) {
if (DEBUG) Log.v(TAG, "Intercept returning false!");
return false;
}
} switch (action) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/ /*
* Locally do absolute value. mLastMotionY is set to the y value
* of the down event.
*/
final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
} final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float dx = x - mLastMotionX;
final float xDiff = Math.abs(dx);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mInitialMotionY); boolean isGutterDrag = isGutterDrag(mLastMotionX, dx);
boolean canScroll = canScroll(this, false, (int) dx, (int) x, (int) y); if (dx != 0 && !isGutterDrag && canScroll) {
// Nested view has scrollable area under this point. Let it be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
}
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollingCacheEnabled(true);
} else if (yDiff > mTouchSlop) {
// The finger has moved enough in the vertical
// direction to be counted as a drag... abort
// any attempt to drag horizontally, to work correctly
// with children that have scrolling containers.
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
mIsUnableToDrag = true;
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
if (performDrag(x)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
break;
} case MotionEvent.ACTION_DOWN: {
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
mIsUnableToDrag = false; mScroller.computeScrollOffset();
if (mScrollState == SCROLL_STATE_SETTLING &&
Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
// Let the user 'catch' the pager as it animates.
mScroller.abortAnimation();
mPopulatePending = false;
populate();
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
} else {
completeScroll(false);
mIsBeingDragged = false;
} if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ " mIsBeingDragged=" + mIsBeingDragged
+ "mIsUnableToDrag=" + mIsUnableToDrag);
break;
} case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
} if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev); /*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
} @Override
public boolean onTouchEvent(MotionEvent ev) {
if (mFakeDragging) {
// A fake drag is in progress already, ignore this real one
// but still eat the touch events.
// (It is likely that the user is multi-touching the screen.)
return true;
} if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
} if (mAdapter == null || mAdapter.getCount() == 0) {
// Nothing to present or scroll; nothing to touch.
return false;
} if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev); final int action = ev.getAction();
boolean needsInvalidate = false; switch (action & MotionEventCompat.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
mScroller.abortAnimation();
mPopulatePending = false;
populate(); // Remember where the motion event started
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
break;
}
case MotionEvent.ACTION_MOVE:
if (!mIsBeingDragged) {
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, pointerIndex);
final float xDiff = Math.abs(x - mLastMotionX);
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mLastMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
if (xDiff > mTouchSlop && xDiff > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true;
requestParentDisallowInterceptTouchEvent(true);
mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollState(SCROLL_STATE_DRAGGING);
setScrollingCacheEnabled(true); // Disallow Parent Intercept, just in case
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
// Not else! Note that mIsBeingDragged can be set above.
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = MotionEventCompat.findPointerIndex(
ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
needsInvalidate |= performDrag(x);
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
velocityTracker, mActivePointerId);
mPopulatePending = true;
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
final int currentPage = ii.position;
final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
final int activePointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
final int totalDelta = (int) (x - mInitialMotionX);
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
setCurrentItemInternal(nextPage, true, true, initialVelocity); mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged) {
scrollToItem(mCurItem, true, 0, false);
mActivePointerId = INVALID_POINTER;
endDrag();
needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
}
break;
case MotionEventCompat.ACTION_POINTER_DOWN: {
final int index = MotionEventCompat.getActionIndex(ev);
final float x = MotionEventCompat.getX(ev, index);
mLastMotionX = x;
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionX = MotionEventCompat.getX(ev,
MotionEventCompat.findPointerIndex(ev, mActivePointerId));
break;
}
if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
return true;
} ......
}
按照ACTION_DOWN, ACTION_MOVE, ACTION_UP顺序分析滑动页面
1,ACTION_DOWN
第一次按下时onInterceptTouchEvent先处理
第一次按下时mIsBeingDragged = false;所以ACTION_DOWN传给ViewPager当前页面子View处理——如:联系人列表,直至ACTION_DOWN处理完
2,ACTION_MOVE
如果处理ACTION_DOWN时没执行requestParentDisallowInterceptTouchEvent(true);则 onInterceptTouchEvent处理ACTION_MOVE
//判断水平移动,一般canScroll都为false,所以不会进入这里。如果进入,则onInterceptTouchEvent返回false,ACTION_MOVE传递给当前页面中的View处理。
if (dx != 0 && !isGutterDrag && canScroll) {
//isGutterDrag——是否从屏幕边缘滑动, canScroll——ViewPager当前页面中的子View是否支持水平滑动
//Contacts中canScroll始终未false, 所以不会进入这里。
// Nested view has scrollable area under this point. Let it be handled there.
mLastMotionX = x;
mLastMotionY = y;
mIsUnableToDrag = true;
return false;
} //判断水平,竖直位移
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
//mTouchSlop在ViewPager初始化时获得为16dp, 也就是(水平位移>16dp && 水平位移/2>竖直位移)
if (DEBUG) Log.v(TAG, "Starting drag!");
mIsBeingDragged = true; //mIsBeingDragged是onInterceptTouchEvent返回的返回值,true表示onTouchEvent要处理ACTION_MOVE,不再往下传递
requestParentDisallowInterceptTouchEvent(true);//disallowIntercept设为true,即将发生的ACTION_UP不会进入onInterceptTouchEvent
setScrollState(SCROLL_STATE_DRAGGING);
mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
setScrollingCacheEnabled(true);
} else if (yDiff > mTouchSlop) {
//如果不满足(水平位移>16dp && 水平位移/2>竖直位移),但——竖直位移>16dp, onInterceptTouchEvent返回false,ACTION_MOVE传递给当前页面中的View处理
// The finger has moved enough in the vertical
// direction to be counted as a drag... abort
// any attempt to drag horizontally, to work correctly
// with children that have scrolling containers.
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
mIsUnableToDrag = true;
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
if (performDrag(x)) {
ViewCompat.postInvalidateOnAnimation(this);//页面滑动
}
} //满足(水平位移>16dp && 水平位移/2>竖直位移)onTouchEvent处理ACTION_MOVE
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = MotionEventCompat.findPointerIndex(
ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
needsInvalidate |= performDrag(x);
} ACTION_MOVE事件是滑动页面时执行最多的,
3,ACTION_UP
如果第2步执行requestParentDisallowInterceptTouchEvent(true)并且return rue, 则由onTouchEvent直接处理。
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
......//获得要切换的页面
int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
totalDelta);
//这里实现页面切换
setCurrentItemInternal(nextPage, true, true, initialVelocity);
......
}
setCurrentItemInternal(....)方法实现页面切换,切换到哪个页面时由determineTargetPage(....)返回的值决定。
private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
int targetPage;
Log.i("antoon", TAG+", determineTargetPage, Math.abs(deltaX) = "+Math.abs(deltaX)+", mFlingDistance = "+mFlingDistance);
Log.i("antoon", TAG+", determineTargetPage, Math.abs(velocity) = "+Math.abs(velocity)+", mMinimumVelocity = "+mMinimumVelocity);
if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
targetPage = (int) (currentPage + pageOffset + truncator);
} if (mItems.size() > 0) {
final ItemInfo firstItem = mItems.get(0);
final ItemInfo lastItem = mItems.get(mItems.size() - 1); // Only let the user target pages we have items for
targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
} return targetPage;
}
经Log输出,mFlingDistance=75, mMinimumVelocity=1200, 所以对于快速滑动要满足 (水平滑动距离>75px && 滑动速率>1200px/s)才会切换页面
else {//这是对缓慢滑动的处理。 pageOffset决定切换哪个页面 。
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
targetPage = (int) (currentPage + pageOffset + truncator);
}
综上ViewPager快速滑动切换页面需要满足条件: 1,(水平位移>16dp && 水平位移/2>竖直位移)触发页面滑动,
2,(水平滑动距离>75px && 滑动速率>1200px/s)触发页面切换。
ViewPager源码分析——滑动切换页面处理过程的更多相关文章
- ViewPager 源码分析(一) —— setAdapter() 与 populate()
写在前面 做安卓也有一定时间了,虽然常用控件都已大致掌握,然而随着 Android N 的发布,不自觉的愈发焦虑起来.说来惭愧,Android L 的 Material Design 库里的许多控件都 ...
- 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02
百篇博客系列篇.本篇为: v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 百篇博客分析OpenHarmony源码 | v41.03
百篇博客系列篇.本篇为: v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁 ...
- SpringBoot源码分析之SpringBoot的启动过程
SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30 | 分类于 springboot | 0 Comments | 阅读次数 SpringB ...
- Envoy 源码分析--程序启动过程
目录 Envoy 源码分析--程序启动过程 初始化 main 入口 MainCommon 初始化 服务 InstanceImpl 初始化 启动 main 启动入口 服务启动流程 LDS 服务启动流程 ...
- Spring源码分析专题 —— IOC容器启动过程(上篇)
声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- tair源码分析——leveldb新增的CompactRangeSelfLevel过程
tair是一个分布式KV存储引擎,当新增机器或者有机器down掉的时候,tair的dataserver会根据ConfigServer生成的新的对照表进行数据的迁移和清理.在数据清理的过程中就用到了在t ...
- 【Spring源码分析】原型Bean实例化过程、byName与byType及FactoryBean获取Bean源码实现
原型Bean加载过程 之前的文章,分析了非懒加载的单例Bean整个加载过程,除了非懒加载的单例Bean之外,Spring中还有一种Bean就是原型(Prototype)的Bean,看一下定义方式: & ...
随机推荐
- 【pycharm】使用过程的相关问题
背景:安装scrapy后在cmd里可以正常import scrapy模块,但是在pycharm里不可以(python2.7) 问题:cmd中能正常导入模块,在pycharm报错 原因:pycharm里 ...
- ASP.NET 登录验证 ihttpmoudle
问题: 1.iis版本不同(IIS7.0,应用程序池采用的是集成模式,换成经典模式才起作用.) 在 IIS 7 以下的版本中,应用以下配置: <system.web> <httpMo ...
- node搭建简单的本地服务器
首先要安装node,方法很多,可以去网上找找,可以直接去官网下载安装,新版本的node是自带npm的: 安装好以后,新建一个js文件,名为server.js: let http = require(' ...
- as3.0划线带撤销功能
package com{ import flash.display.MovieClip; import flash.display.SimpleButton; import flash.events. ...
- 第十章 优先级队列 (xa1)左式堆:结构
- WINDOWS内核版本
Windows NT (NT=new technology) WOW64(Windows-on-Windows 64bit) 如何获取Windows 内核版本 1. cmd (win+r) 2. V ...
- python函数传入参数(默认参数、可变长度参数、关键字参数)
1.python中默认缺省参数----定义默认参数要牢记一点:默认参数必须指向不变对象! 1 def foo(a,b=1): 2 print a,b 3 4 foo(2) #2 1 5 foo(3,1 ...
- swift 数组 的一些快速方法
1. filter (过滤器):返回符合条件的一个数组 let arr = [1,5,6,7,10,0] //写法1 let arr1 = arr.filter { (item) -> Bool ...
- Java07-java语法基础(六)面向对象
Java07-java语法基础(六)面向对象 一.格式化输出 System.out.printf(“%格式字符”,输出项); 1.格式字符: d --->int.byte.short ld -- ...
- 使用gearman进行异步的邮件或短信发送
一.准备工作 1.为了防止,处理业务途中出现的宕机,请配置好gearman的持久化方式.2.使用gearmanManager来管理我们的worker脚本,方便测试. 上述两条请看我之前写的两篇文章 二 ...