滑动是Android开发中非常重要的UI效果,几乎所有应用都包含了滑动效果,而本文将对滑动的使用以及原理进行介绍。

一、scrollTo与ScrollBy

View提供了专门的方法用于实现滑动效果,分别为scrollTo与scrollBy。先来看看它们的源码:

/*** Set the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the x position to scroll to* @param y the y position to scroll to*/
public void scrollTo(int x, int y) { if (mScrollX != x || mScrollY != y) { int oldX = mScrollX; int oldY = mScrollY; mScrollX = x; mScrollY = y; invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { postInvalidateOnAnimation(); } } } /** * Move the scrolled position of your view. This will cause a call to * {@link #onScrollChanged(int, int, int, int)} and the view will be * invalidated. * @param x the amount of pixels to scroll by horizontally * @param y the amount of pixels to scroll by vertically */ public void scrollBy(int x, int y) { scrollTo(mScrollX + x, mScrollY + y); }

从源码中可以看出scrollBy实际上是调用了scrollTo函数来实现它的功能。scrollBy实现的是输入参数的相对滑动,而scrollTo是绝对滑动。需要说明的是mScrollX、mScrollY这两个View的属性,这两个属性可以通过getScrollX、getScrollY获得。

  • mScrollX : View的左边缘与View内容的左边缘在水平方向上的距离,即从右向左滑动时,为正值,反之为负值。
  • mScrollY : View的上边缘与View内容的上边缘在竖直方向上的距离,即从下向上滑动时,为正值,反之为负值。
  • 下面我们来实现一个滑动的效果:
public class HorizontalScroller extends ViewGroup { private int mTouchSlop; private float mLastXIntercept=0; private float mLastYIntercept=0; private float mLastX=0; private float mLastY=0; private int leftBorder; private int rightBorder; public HorizontalScroller(Context context) { super(context); init(context); } public HorizontalScroller(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public HorizontalScroller(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } private void init(Context context){ ViewConfiguration configuration = ViewConfiguration.get(context); // 获取TouchSlop值 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercept = false; float xIntercept = ev.getX(); float yIntercept = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: intercept = false; break; case MotionEvent.ACTION_MOVE: float deltaX = xIntercept-mLastXIntercept; float deltaY = yIntercept-mLastYIntercept; // 当水平方向的滑动距离大于竖直方向的滑动距离,且手指拖动值大于TouchSlop值时,拦截事件 if (Math.abs(deltaX)>Math.abs(deltaY) && Math.abs(deltaX)>mTouchSlop) { intercept=true; }else { intercept = false; } break; case MotionEvent.ACTION_UP: intercept = false; break; default: break; } mLastX = xIntercept; mLastY = yIntercept; mLastXIntercept = xIntercept; mLastYIntercept = yIntercept; return intercept; } @Override public boolean onTouchEvent(MotionEvent event) { float xTouch = event.getX(); float yTouch = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: float deltaX = xTouch-mLastX; float deltaY = yTouch-mLastY; float scrollByStart = deltaX; if (getScrollX() - deltaX < leftBorder) { scrollByStart = getScrollX()-leftBorder; } else if (getScrollX() + getWidth() - deltaX > rightBorder) { scrollByStart = rightBorder-getWidth()-getScrollX(); } scrollBy((int) -scrollByStart, 0); break; case MotionEvent.ACTION_UP: // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面 int targetIndex = (getScrollX() + getWidth() / 2) / getWidth(); int dx = targetIndex * getWidth() - getScrollX(); scrollTo(getScrollX()+dx,0); break; default: break; } mLastX = xTouch; mLastY = yTouch; return super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 测量每一个子控件的大小 measureChild(childView, widthMeasureSpec, heightMeasureSpec); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (changed) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 在水平方向上对子控件进行布局 childView.layout(i * getMeasuredWidth(), 0, i * getMeasuredWidth()+childView.getMeasuredWidth()+getPaddingLeft(), childView.getMeasuredHeight()); } // 初始化左右边界值 leftBorder = 0; rightBorder = getChildCount()*getMeasuredWidth(); } } }

现在我们来分析下这段代码:

  • 首先在构造函数中获取了最小滑动距离TouchSlop。
  • 重写onInterceptTouchEvent拦截事件,记录当前坐标。点下时,默认不拦截,只有当滑动还未完成的情况下,才继续拦截。在移动时,对滑动冲突进行了处理,当水平方向的移动距离大于竖直方向的移动距离,并且移动距离大于最小滑动距离时,我们判断此时为水平滑动,拦截事件自己处理;否则不拦截,交由子View处理。提起手指时,同样不拦截事件。
  • 重写onTouchEvent处理事件,记录当前坐标。在手指按下时,与拦截事件时做相似处理。在ACTION_MOVE时,向左滑动,如果滑动距离超过左边界,则对滑动距离进行处理,相对的滑动距离超出又边界,也是一样处理,之后把滑动的距离交给scrollBy进行处理。当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面,然后使用scrollTo滑动到那个子控件。
  • 重写了onMeasure和onLayout方法,在onMeasure中测量每一个子控件的大小值,在onLayout中对每一个子view在水平方向上进行布局。子view的layout的right增加父类的paddingLeft参数,来处理设置padding的情况。这两个函数的流程分析将会放在之后的文章中详细说明。

这个类的使用方法如下 :

<?xml version="1.0" encoding="utf-8"?> <com.idtk.customscroll.HorizontalScroller xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" tools:context="com.idtk.customscroll.MainActivity"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/zhiqinchun" android:clickable="true"/> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/hanzhan" android:clickable="true"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/shengui" android:clickable="true"/> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/dayu" android:clickable="true"/> </com.idtk.customscroll.HorizontalScroller>

HorizontalScroller设置全屏,padding为10dp。使用4个ImageView作为子View,并且都设置为可点击状态。示例效果图如下:

二、Scroller

可以看到上面使用scrollTo与ScrollBy方法的滑动都是瞬时完成的,这有些无法满足我们在切换子view时的需求。我们希望切换子View时,可以拥有滑动过程的效果,而Scroller正好可以完成这一点。

Scroller的使用方法:

* 1、创建Scroller实例

* 2、使用startScroll方法,对其进行初始化

* 3、重写computeScroll()方法,在其内部调用scrollTo或ScrollBy方法,完成滑动过程。

//创建实例
mScroller = new Scroller(context);public void smoothScrollTo(){ int ScrollX = getScrollX(); int ScrollY = getScrollY(); //初始化,1000ms内缓慢滑动到deltaX mScroller.startScroll(ScrollX, 0, 0, deltaX, 1000); invalidate(); } @Override public void computeScroll() { if(mScroller.computeScrollOffset()){ int currX = mScroller.getCurrX(); int currY = mScroller.getCurrY(); scrollTo(currX, currY); postInvalidate(); } }

上面的代码是Scroller的典型用法,也就是传说中的套路。当时Scroller使用startScroll方法时,只是对一系列参数进行了初始化。我们从下面的源码中可以看出。

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; }

参数中,startX、startY是滑动的起点,dx、dy是滑动的距离,duration是滑动的时间系统设置为250ms。我们可以看到startScroll只是进行了滑动时间、是否滑动完成、起点、终点、滑动距离等的参数的设置,那么是如何调用computeScroll()函数的呢?其实computeScroll()的调用是由之后的invalidate()函数来完成的,invalidate可以请求View重绘,在View重绘时会调用draw方法,draw方法又会去调用computeScroll函数。但computeScroll()函数在view中是一个空的函数,需要我们去实现它。

computeScroll()函数的实现已经在上面给出了,有了computeScroll方法之后,就可以实现View的弹性滑动了。来看下computeScroll()的实现过程,首先要进行computeScrollOffset()的判断,来看下它的源码 :

public boolean computeScrollOffset() { if (mFinished) { 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); 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)); // Pin to mMinX <= mCurrX <= mMaxX mCurrX = Math.min(mCurrX, mMaxX); mCurrX = Math.max(mCurrX, mMinX); mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY)); // Pin to mMinY <= mCurrY <= mMaxY 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; }

computeScrollOffset()首先检测scroller是否完成滑动,完成则返回false,未完成则继续AnimationUtils.currentAnimationTimeMillis获取当前的毫秒值,减去之前startScroll方法时获得毫秒值,就是当前滑动的执行时间。之后判断执行时间是否小于设置的总时间,如果小于,根据startScroll时设置的模式SCROLL_MODE,然后根据Interpolator计算出当前滑动的mcurrX、mcurrY(顺便提一下在实例化scroller的时候,是可以设置动画插值器。);如果执行时间大于或者等于设置的总时间,则直接设置mcurrX、mcurrY为终点值,并且设置mFinished,表示动画已经完成。

Scroller弹性滑动的流程如下

现在使用Scroller方法来更改一下上面的代码,当ACTION_UP时,子View的滑动可以有一个过程,而不是瞬时完成。

    private Scroller mScroller;...private void init(Context context){ // 第一步,创建Scroller的实例 mScroller = new Scroller(context); ... } @Override public boolean onTouchEvent(MotionEvent event) { float xTouch = event.getX(); float yTouch = event.getY(); switch (event.getAction()) { ... case MotionEvent.ACTION_UP: // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面 int targetIndex = (getScrollX() + getWidth() / 2) / getWidth(); int dx = targetIndex * getWidth() - getScrollX(); // 第二步,使用startScroll方法,对其进行初始化 mScroller.startScroll(getScrollX(), 0, dx, 0); invalidate(); break; default: break; } mLastX = xTouch; mLastY = yTouch; return super.onTouchEvent(event); } ... @Override public void computeScroll() { // 第三步,重写computeScroll()方法,在其内部调用scrollTo或ScrollBy方法,完成滑动过程 if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } }

上面就是代码中需要增加和修改的部分,我们来简单分析下。

  • 在构造函数中增加对 Scroller进行了实例化 。
  • 替换onTouchEvent中手指抬起后的方法,改为 使用startScroll方法,对mScroller进行初始化 ,之后invalidate请求重绘。
  • 增加 重写的computeScroll()方法 ,在其内部调用scrollTo或ScrollBy方法,完成滑动过程,之后使用postInvalidate()请求view重绘。

示例效果图如下 :

三、回弹效果

从上面的效果图可以看出,我们已经实现了view的平滑滚动,滑动位置超过当前view的1/2时,松手之后变会自动滑出此item的View。可是如果想要在首位两端实现回弹效果,该如何做呢?其实只要修改onTouchEvent方法即可。

@Override
public boolean onTouchEvent(MotionEvent event) { float xTouch = event.getX(); float yTouch = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) mScroller.abortAnimation(); break; case MotionEvent.ACTION_MOVE: float deltaX = xTouch-mLastX; float deltaY = yTouch-mLastY; float scrollByStart = deltaX; //如果超出边界,则把滑动距离缩小到1/3 if (getScrollX() - deltaX < leftBorder) { scrollByStart = deltaX/3; } else if (getScrollX() + getWidth() - deltaX > rightBorder) { scrollByStart = deltaX/3; } scrollBy((int) -scrollByStart, 0); break; case MotionEvent.ACTION_UP: // 当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面 int targetIndex = (getScrollX() + getWidth() / 2) / getWidth(); //如果超过右边界,则回弹到最后一个View if (targetIndex>getChildCount()-1){ targetIndex = getChildCount()-1; //如果超过左边界,则回弹到第一个View }else if (targetIndex<0){ targetIndex =0; } int dx = targetIndex * getWidth() - getScrollX(); // 第二步,使用startScroll方法,对其进行初始化 mScroller.startScroll(getScrollX(), 0, dx, 0); invalidate(); break; default: break; } mLastX = xTouch; mLastY = yTouch; return super.onTouchEvent(event); }

来简单分析下修改的onTouchEvent方法:

在滑动的过程中,如果滑动的位置超过了试图的左、右边界,则缩小View的滑动距离,使之为手指滑动距离的1/3。当手指离开时,如果通过view宽度获得的当前inder小与0,则index为第一个View;如果获得的当前index超过了子View的数量-1,则index为最后一个View。View的回弹效果如下:

四、小结

本文介绍弹性滑动的实现方法,并对弹性滑动的过程进行了详细分析。在之后通过例子实现了view的弹性滑动以及回弹效果,但 最后还留有两个问题,即invalidate与postInvalidate的区别又在哪里呢?invalidate是如何调用computeScroll()函数的呢? ,这些问题我将在下一篇文章中进行详细的分析。

来自:http://www.idtkm.com/customview/customview8/

扩展阅读

iOS开发者必备:自己总结的iOS、mac开源项目及库
Android开发之自定义View
Android自定义View系列(一)——打造一个爱心进度条
android中正确保存view的状态 
iOS、mac开源项目及库汇总

为您推荐

Android自定义下拉刷新动画--仿百度外卖下拉刷新
Android Activity悬浮并可拖动(访悬浮歌词)
Android SwipeBackLayout源码解析
ScratchView:一步步打造万能的 Android 刮奖效果控件
自定义View系列教程01--常用工具介绍

更多

安卓开发
Android
View
Android开发

同类热门新闻

  1. Android开发周报:跨平台技术分析、Chrome For Android开源
  2. Android开发技术周报 Issue#32
  3. Android开发周报:Play Store搜索广告推出、ListView源码解析
  4. Android开发技术周报 Issue#42
  5. Android开发技术周报 Issue#38
  6. 最棒的 5 款开源 Android/iOS 自动化工具

同类热门经验

  1. 上百个Android开源项目分享
  2. android json解析及简单例子
  3. Android 软件自动更新功能的实现
  4. 自定义 Android 对话框 (AlertDialog) 的样式
  5. adb shell 命令详解
  6. android定位和地图开发实例

阅读目录

  • 一、scrollTo与ScrollBy
  • 二、Scroller
  • 三、回弹效果
  • 四、小结
  • 扩展阅读
  • 为您推荐
  • 更多

相关文档  — 更多
  • Android 的 View 和 ViewGroup 分析.doc
  • Android游戏开发20回合.doc
  • Android View防止重复点击.docx
  • Android系统下连连看游戏开发 - 毕业论文.doc
  • Android之ListView绘制的过程.docx
  • 第7章 Android UI基础知识.ppt
  • Android UI开发第十六篇 - 分享一个popuwindow实例.pdf
  • Expert Android.pdf
  • Android 布局详解.pdf
  • Android 布局精解.doc
  • Android OpenGL ES 开发教程.pdf
  • Android-UI 基本控件.ppt
  • Android常见面试题2.docx
  • android定位和地图开发实例.docx
  • Android应用程序界面编程.pptx
  • 用 IntelliJ IDEA 开发Android程序.doc
  • 如何使用Android样式和主题.doc
  • 第八章 Android GWES.pdf
  • Android游戏开发系列博文.chm
  • Android 游戏开发(快速入门必备).doc
相关经验  — 更多
  • iOS开发者必备:自己总结的iOS、mac开源项目及库
  • Android开发之自定义View
  • Android自定义View系列(一)——打造一个爱心进度条
  • android中正确保存view的状态
  • iOS、mac开源项目及库汇总
  • iOS及Mac开源项目和学习资料【超级全面】
  • Android应用开发之自定义View触摸相关工具类全解
  • compassView - an 3D Android CompassView 一款3D 的安卓自定义view 指南针
  • Android开源工具项目集合
  • 2015最流行的Android组件、工具、框架大全
  • Android 开发有哪些新技术出现?
  • Android资源库列表
  • Android开源库集锦
  • Android自定义View之刻度尺
相关讨论  — 更多
  • Android中ListView分页加载数据
  • Android Fragment 完全解析(上)
  • Android开发教程ZoomControls控件详解
  • 求解:Android slidemenu 左滑菜单框架怎么监听菜单项的点击事件?
  • 在Android 中使用KSOAP2调用WebService
  • Android中数据存储--采用SQLite存储数据及在SDCard中创建数据库
  • Android:自定义ListView

转载于:https://www.cnblogs.com/liupengfei005257/p/7448558.html

(转载)自定义View——弹性滑动相关推荐

  1. 自定义View——弹性滑动

    滑动是Android开发中非常重要的UI效果,几乎所有应用都包含了滑动效果,而本文将对滑动的使用以及原理进行介绍. 一.scrollTo与ScrollBy View提供了专门的方法用于实现滑动效果,分 ...

  2. 自定义View—弹性滑动

    滑动是Android开发中非常重要的UI效果,几乎所有应用都包含了滑动效果,而本文将对滑动的使用以及原理进行介绍. 一.scrollTo与ScrollBy View提供了专门的方法用于实现滑动效果,分 ...

  3. 自定义view - 收藏集 - 掘金

    Android 从 0 开始自定义控件之 View 的 draw 过程 (九) - Android - 掘金 转载请标明出处: http://blog.csdn.net/airsaid/... 本文出 ...

  4. Android弹性滑动在自定义View中的高级应用

    本文出自门心叼龙的博客,属于原创类容,转载请注明出处. 好久没有更新博客了,特意的看了博客最后的更新时间为2019年7月21日,今天是10月24日掐指一算已经有三个月时间了,自从上篇<开发杂谈: ...

  5. 【朝花夕拾】Android自定义View篇之(十一)View的滑动,弹性滑动与自定义PagerView...

    前言 转载请声明,转载自[https://www.cnblogs.com/andy-songwei/p/11213718.html],谢谢! 由于手机屏幕尺寸有限,但是又经常需要在屏幕中显示大量的内容 ...

  6. 【5年Android从零复盘系列之二十】Android自定义View(15):Matrix详解(图文)【转载】

    [转载]本文转载自麻花儿wt 的文章<android matrix 最全方法详解与进阶(完整篇)> [5年Android从零复盘系列之二十]Android自定义View(15):Matri ...

  7. 自定义View入门 --转载自武老师博客160303

    原文地址:http://blog.csdn.net/risky78125/article/details/50609538 自定义View入门 long time no see,这次写一个灰常简单的一 ...

  8. java雪花纷飞_分析自定义view的实现过程-实现雪花飞舞效果(转载有改动)

    声明:本文源码出自实现雪花飞舞效果(有改动)主要通过这篇文来分析自定义view的实现过程. 没事时,比较喜欢上网看看一些新的东西,泡在网上的日子就是一个很不错的网站. 下面开始了,哈哈.^_^ 大家都 ...

  9. 《Android进阶之光》--View体系与自定义View

    No1: View的滑动 1)layout()方法的 public class CustomView extends View{private int lastX;private int lastY; ...

最新文章

  1. 【机器视觉】 dev_close_window算子
  2. arm cortex-a8 天梯图_ARM正式推出CortexA78C核心:针对笔记本电脑设计、支持8个大核心...
  3. Java打印三角形(双层for循环)
  4. Pandas to_string
  5. TensorBoard 1.15.0 at http://DESKTOP-DV74NQ2:6006/ 打开html后无法展示解决方案
  6. OpenBlock:针对EveryBlock.com源码的开源拓展项目
  7. 12)hInstance和hWnd写进子类
  8. 我的面试标准:能干活、基础要好、有潜力!
  9. python 自动交易股票_Python从零开始学股票自动交易视频教程百度网盘下载
  10. 耳机煲机软件测试自学,乐味煲耳机软件教程 只需三步轻松煲耳机
  11. java编译jni错误_JNI开发的常见错误
  12. Linux系统简介、安装RHEL7系统、RHEL7基本操作
  13. springboot项目+多个启动类部署到linux服务器上
  14. Oracle greatest函数
  15. 一个测试工程师应具备那些素质和技能?
  16. 阿里大鱼短信功能使用
  17. 如何用计算机算余数,数学余数在计算机的用途
  18. 关于使用dosbox与masm/MASMplus进行汇编语言的编译,link与执行中遇到问题的解决法小汇总(慢慢汇总更新)
  19. 一个移动互联网自媒体的运营手记
  20. 暑假好看的日剧来啦~~

热门文章

  1. ffmpeg 将文本转换成音频以及多个音频合成一个音频的方法
  2. 第三天---随机小方块
  3. 使用Selenium从IEEE与谷歌学术批量爬取BibTex文献引用
  4. mysql 主辅_Mysql的实时同步 - 主辅同步
  5. C盘空间不足?扩充C盘
  6. 各行业容灾备份架构#容灾#,
  7. /dev/sr0 3.7G 3.7G 0 100% /media/CentOS_6.8_Final no space left on device磁盘空间不足处理
  8. 《金融怪杰》读书笔记
  9. SQL对时间的操作,比如在当前时间上增加减少一天,在当前的时间上增加减少一个月
  10. 昇腾Mindstudio官方样例黑白图片上色