Android Scroller详解
一、View的scrollTo()、scrollBy()
public final int getScrollX() {
return mScrollX;
}
可以看到getScrollX()直接返回的就是mScrollX,代表水平方向上的偏移量,getScrollY()也类似。偏移量mScrollX的正、负代表着,滑动控件中的内容相对于初始位置在水平方向上偏移情况,mScrollX为正代表着当前内容相对于初始位置向左偏移了mScrollX的距离,mScrollX为负表示当前内容相对于初始位置向右偏移了mScrollX的距离。
说明:图中黄色矩形区域表示的是一个可滑动的View控件,绿色虚线矩形为滑动控件中的滑动内容。注意这里的坐标是相反的。(例子来源于:http://blog.csdn.net/bigconvience/article/details/26697645)
- @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;
- }
= getScrollY();获得滑动内容之前已经距初始位置便宜了多少;第三是,计算本次需要偏移的参数int scrollY =
oldScrollY + dy;
后面通过两个if条件进行了边界处理,然后调用scrollTo进行滑动。调用完scrollTo后,新的偏移量又重新产生了。从scrollTo源码中可以看到:
- 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>
二、Scroller滑动辅助类
但是注意的是,Scroller本身不会去移动View,它只是一个移动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法完成View的移动的。
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>
Android Scroller详解的更多相关文章
- Android Notification 详解(一)——基本操作
Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...
- Android Notification 详解——基本操作
Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...
- Android ActionBar详解
Android ActionBar详解 分类: Android2014-04-30 15:23 1094人阅读 评论(0) 收藏 举报 androidActionBar 目录(?)[+] 第4 ...
- Android 签名详解
Android 签名详解 AndroidOPhoneAnt设计模式Eclipse 在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...
- Android编译系统详解(一)
++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...
- Android布局详解之一:FrameLayout
原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6702273 FrameLayout是最简单的布局了.所有放在布局里的 ...
- 【整理修订】Android.mk详解
Android.mk详解 1. Android.mk 的应用范围 Android.mk文件是GNU Makefile的一小部分,它用来对Android程序进行编译. 一个Android.mk文件可以编 ...
- Android菜单详解(四)——使用上下文菜单ContextMenu
之前在<Android菜单详解(二)——创建并响应选项菜单>和<Android菜单详解(三)——SubMenu和IconMenu>中详细讲解了选项菜单,子菜单和图标菜单.今天接 ...
- Android签名详解(debug和release)
Android签名详解(debug和release) 1. 为什么要签名 1) 发送者的身份认证 由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,以此保证签名不同的包 ...
随机推荐
- 【C++】C++中的异常解析
异常是程序在执行期间产生的问题.C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作. 异常提供了一种转移程序控制权的方式.C++ 异常处理涉及到三个关键字:try.catch.throw ...
- ASP.NET Core中的运行状况检查
由卢克·莱瑟姆和格伦Condron ASP.NET Core提供了运行状况检查中间件和库,用于报告应用程序基础结构组件的运行状况. 运行状况检查由应用程序公开为HTTP终结点.可以为各种实时监视方案配 ...
- 嵌入式开发之内核内存异常排查---关闭oom killer
通过执行以下命令,可以在1分钟内对系统资源使用情况有个大致的了解.uptimedmesg | tailvmstat 1mpstat -P ALL 1pidstat 1iostat -xz 1free ...
- 003-Python3-基础语法-运行方式、代码基础要求、运算符[算数运算符、比较运算符、赋值运算符、位运算符、逻辑运算符、成员运算符、身份运算符]、运算符优先级
一.基础语法 参看地址:https://www.runoob.com/python3/python3-tutorial.html 1.1.运行方式 1.文件方式 编写一个hello.py文件, pri ...
- 使用leaflet绘制geoJson中国边界
绘制中国边界 代码如下: function drawChina() { //设置样式 var myStyle = { "color": "#00f", &quo ...
- 【NPDP笔记】第四章 文化组织与团队
此为临时链接,仅用于预览,将在短期内失效.关闭 [NPDP笔记]第四章 文化组织与团队 小康 小康哥的产品之路 9月6日 4.1 文化和氛围对创新的重要性 文化:信念,价值观,假设,与期望 氛围:直接 ...
- 待补充 MySQL必知必会第29章--------数据库维护
备份数据 由于MySQL数据库是基于磁盘的文件,普通的备份系统和里程就能备份MySQL的数据.但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不一定总是生效.
- springboot 读取Jar 类路径下的文件
Resource resource = new DefaultResourceLoader().getResource("classpath:download/WORKER_OVERTIME ...
- Java开发笔记(一百四十二)JavaFX的对话框
JavaFX的对话框主要分为提示对话框和文件对话框两类,其中提示对话框又分作消息对话框.警告对话框.错误对话框.确认对话框四种.这四种对话框都使用Alert控件表达,并通过对话框类型加以区分,例如Al ...
- Codeforces Round #604
Beautiful Regional Contest 题意 题解 代码 Beautiful Sequence 题意 题解 代码 一个思路不够清晰的代码 Beautiful Mirrors with q ...