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来混淆替换已经安装的程序,以此保证签名不同的包 ...
随机推荐
- shell脚本:批量修改文件名(添加/删除文件名中字符)
添加字符 举例如下:批量创建10个随机字符串的文件,要求每个文件名后面添加_aaa,后缀名不变: [root@localhost goodboy]# ls adddbbdedf.html baacj ...
- 使用 Visual Studio Code(VSCode)搭建简单的Python+Django开发环境的方法步骤
安装配置 VSCode [1]安装 VSCode: 下载地址:https://code.visualstudio.com/ 根据自己电脑对应的操作系统下载对应的版本即可,至于安装过程也和一般的软件一样 ...
- flutter 打包apk之后,安装在手机上无法访问网络解决方法
</application> <uses-permission android:name="android.permission.READ_PHONE_STATE" ...
- 华硕主板 Vmware虚拟机 二进制转换与此平台上的长模式不兼容
出现情况如下: 大概遇到过两次这个问题,第一次是在笔记本VM上装虚拟机,第二次是在台式机VM上装虚拟机. 原因是因为虚拟化(Intel Virtualization Technology)技术,在主板 ...
- LwIP应用开发笔记之七:LwIP无操作系统HTTP服务器
前面我们实现了TCP服务器和客户端的简单应用,接下来我们实现一个基于TCP协议的应用协议,那就是HTTP超文本传输协议 1. HTTP协议简介 超文本传输协议(Hyper Text Transf ...
- git worktree 目录修复
三种方式挨个尝试,1不行用2 2不行用3 1.拉取阶段失败 git worktree add -f -B xxx_branch ./xxx_branch origin/xxx_branch 强制拉取 ...
- OSI七层与TCP/IP四层(小结)
OSI 七层模型 我们一般使用的网络数据传输由下而上共有七层,分别为物理层.数据链路层.网络层.传输层.会话层.表示层.应用层,也被依次称为 OSI 第一层.第二层.⋯⋯. 第七层. 各层功能简介 1 ...
- Ubuntu16.0.4安装OpenCV3.4.2
(1)到官网下载opencv3.4.2,链接:https://opencv.org/releases.html (2)下载opencv_contrib,链接:https://github.com/op ...
- CentOS7开机进入紧急模式EmergencyMode的解决办法
这个情况主要是 修改了 /etc/fstab 文件 vi /etc/fstab 文件 如果之前添加过一行 把添加的一行注释掉 如果之前没有添加过,把挂载到 /home 这一行取消注释 操作之后 记得 ...
- jquery监听回车
jquery监听回车 <pre> $("#loginusername, #loginpassword, #code").keydown(function() { if( ...