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来混淆替换已经安装的程序,以此保证签名不同的包 ...
随机推荐
- 【iCore4 双核心板_FPGA】实验二十:NIOS II之UART串口通信实验
实验指导书及源代码下载地址: 链接:https://pan.baidu.com/s/1g_tWYYJxh4EgiGvlfkVu1Q 提取码:dwwa 复制这段内容后打开百度网盘手机App,操作更方便哦 ...
- Look Further to Recognize Better: Learning Shared Topics and Category-Specific Dictionaries for Open-Ended 3D Object Recognition
张宁 Look Further to Recognize Better: Learning Shared Topics and Category-Specific Dictionaries for O ...
- Ant Design Pro Vue 时间段查询 问题
<a-form-item label="起止日期" :labelCol="{lg: {span: 7}, sm: {span: 7}}" :wrapper ...
- python基础】——python添加模块搜索路径和包的导入
方法一:函数添加1 import sys2 查看sys.path3 添加sys.path.append("c:\\") 方法二:修改环境变量w用户可以修改系统环境变量PYTHONP ...
- [转]casperjs截图出现黑色背景
原文地址:https://my.oschina.net/tuxpy/blog/879509?utm_medium=referral 如果默认没有指定 body的background-color就会出现 ...
- [Arch] 域名解析常用两步设置
主站 A 记录 (对应IPv4) 主机记录: @ (表示解析到不带 www 主域名) 值为 IP 主站 CNAME 记录 (表示别名) 主机记录: www (表示解析到带 ...
- 多分类评测标准(micro 和 macro)
- 局域网-断网&劫持(kali)
1.查看局域网中的主机 fping –asg 192.168.1.0/24 2.断网 arpspoof -i wlan0 -t 192.168.100 192.168.1.1 (arpspoof - ...
- P4Merge的使用
(官网: https://www.perforce.com/products/helix-core-apps/merge-diff-tool-p4merge 可以作为一个stand alone app ...
- netcore 步骤
1.创建工程目录 d:\project 2.进入目录,创建解决方案 dotnet new sln 3.确定开发版本 dotnet --list-sdks //列出sdk版本 dotnet new gl ...