关于Android的Scroller类和View
来源:互联网 发布:怎么做产品的网络推广 编辑:程序博客网 时间:2024/06/04 18:15
关于Android Scroller和View
android程序中,大部分比较炫酷UI,都是基于SCroller或者是动画来实现的。本文主要讲解下Scroller类是如何配合View组件来使用的。
我们首先来看下View.scrollTo(int x,int y)和View.ScrollBy(int dx,int dy)两个方法:这两个方法其实很简单:
- scrollTo方法是滑动到一个X,Y(参数中的)坐标上去,传入的参数X,Y是具体的屏幕的坐标的数值.
scrollBy方法是从在当前的坐标数值上,滑动传入的参数dx,dy的那么多的距离。
说白了一个是滑动到具体的坐标点,一个是从当前内容的坐标开始滑动一个传入的参数的距离。注意,这里说的滑动和动画中的滑动不是一个概念:动画中的滑动是将View组件整体的坐标值改变来进行滑动,改变了View组件在屏幕中的坐标点,而Scrollto或者ScrollBy中的滑动,是滑动的组件的内容。所以嘛,一般能用到scrollto或者scrollby这两个方法的组件,肯定都是内容比较大,超出屏幕显示的。比如android常用的scrollView或者HorizontalScrollView 组件,就是基于这两个方法来的。
那这两个方法一般在什么情形下去使用呢?其实用膝盖想也知道是onTouchEvent();在onTounchEvent()中使用监听事件计算手势划过的距离,然后调用scrollBy()方法,然View的内容进行滑动。关于滑动参数的正负型:x轴,正数代表内容向左滑动,负数代表内容向右滑动。y轴:正数代表内容向上滑动,负数代表向下滑动。
了解这两个方法之后,我们来说说View.getScrollX()和View.getScrollY()的含义。其实很简单,scrollX就是代表View左边缘距离手机屏幕左边的宽大小,scrollY就是代表View上边缘距离手机屏幕右边的大小(感谢任玉刚主席书中的解释!)所以,可以这样理解:从左向右滑动的时候,getScrollX为负值,反之亦然;从上向下滑动的时候,getScrollY为正数值,反之亦然。
好吧,现在扯了那么多,说好的Scroller类呢?这就开始说Scroller类,其实上面的篇章将的都是View关于滑动的动作,而Scroller类就是辅助计算View滑动的,也就是说,Scroller类是一个辅助计算类。也许你会问:这不是搞笑吗?我们既然能在onTouchEvent中计算滑动的距离,还需要Scroller类吗?其实,ScrollBy或者ScrollTo确实都是配合手势来用的,但是这些滑动都是瞬间的滑动,并没有给用户滚动的过程,其实造成的用户体验不是很好,所以需要使用到scroller类。
我们可以思考如下场景:
- 1.比如一个侧滑菜单,我只是随意滑动了一下,就要让侧滑的菜单从侧面平缓地显示出来呢?(所有的侧滑都是这样的)
- 2.比如一个竖直的页面,是滚动类型的,内容分为A,B两块,A在上,B在下,现在我随意地轻轻地滑动,而且希望能够根据滑动的速度,A完全消失,让B显示,又改如何做呢?(比如360的软件详情页面)
这些问题都是涉及到比较复杂的滑动计算的,而这些滑动计算的功能,都需要Scroller类来帮助我们计算。
其实刚刚接触到Scroller类的人都会觉得很难理解:其实我们要思考清楚两个问题:
- Scroller是怎样去计算辅助View的滑动的?
- Scroller是的事件怎么样被出发的?
我们从一段示例的代码中来看抛砖引玉:
package slidingmenu.dreamfly.org.slidingmenu.custom.app;import android.content.Context;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.app.FragmentStatePagerAdapter;import android.support.v4.view.PagerAdapter;import android.support.v4.view.VelocityTrackerCompat;import android.support.v4.view.ViewPager;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.VelocityTracker;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.view.animation.BounceInterpolator;import android.widget.LinearLayout;import android.widget.ListView;import android.widget.OverScroller;import android.widget.RelativeLayout;import android.widget.ScrollView;import android.widget.Scroller;import slidingmenu.dreamfly.org.slidingmenu.R;/** * Created by asus on 2015/10/20. */public class DetailRootLayout extends LinearLayout { private View mTop; private View mBottom; private int mTopViewHeight; private OverScroller mScroller; private VelocityTracker mVelocityTracker; private int mTouchSlop; private int mMaximumVelocity, mMinimumVelocity; private float mLastY; private boolean isDragging; public DetailRootLayout(Context context, AttributeSet attrs) { super(context, attrs); setOrientation(LinearLayout.VERTICAL); this.mVelocityTracker=VelocityTracker.obtain(); this.mScroller=new OverScroller(context); this.mTouchSlop=ViewConfiguration.get(context).getScaledTouchSlop(); this.mMaximumVelocity=ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); this.mMinimumVelocity=ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w,h,oldw,oldh); this.mTopViewHeight=this.mTop.getMeasuredHeight(); Log.i("lzw","topHeight"+this.mTopViewHeight); } /** * startScroll(滑动的起点的X,滑动的起点的Y,滑动的最终距离X,滑动的最终距离Y) * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event){ this.mVelocityTracker.addMovement(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: this.mLastY = event.getY(); if (this.mScroller.isFinished()) { this.mScroller.abortAnimation(); } break; case MotionEvent.ACTION_MOVE: float disY = event.getY() - this.mLastY; if (!this.isDragging && Math.abs(disY) > this.mTouchSlop) { this.isDragging = true; } if (this.isDragging) { this.scrollBy(0, -(int) disY); this.mLastY=event.getY(); } break; case MotionEvent.ACTION_UP: this.isDragging = false;// this.mVelocityTracker.computeCurrentVelocity(1000,this.mMaximumVelocity);// float velocityY=this.mVelocityTracker.getYVelocity();// //向下滑动,速率的计算值肯定是负数// if(Math.abs(velocityY)>this.mMinimumVelocity){// this.fling(-(int)velocityY);// } float disTmp=event.getY()-this.mLastY; this.mScroller.startScroll(this.mScroller.getFinalX(), this.getScrollY(),0,-(int)disTmp); this.invalidate(); break; } return(true); } /** * fling函数 * startX:开始滑动的X起点 * startY:开始滚动的Y起点 * velocityX:滑动的速度X * velocityY:滑动的速度Y * minx:X方向的最小值 * maxX:X方向的最大值 * minY:Y方向的最小值 * maxY:Y方向的最大值 * @param velocity */ private void fling(int velocity){ this.mScroller.fling(0,this.getScrollY(),0,velocity,0,0,0,this.mTopViewHeight); this.invalidate(); } /** * 在fling或者startScroll方法后,调用invalidate方法后执行的函数 * scroller.getCurY() 返回当前Y方向的偏移 */ @Override public void computeScroll() { if(this.mScroller.computeScrollOffset()){ this.scrollTo(0,this.mScroller.getCurrY()); invalidate(); } } /** * * @param x * @param y */ @Override public void scrollTo(int x,int y){ if(y<0) { y = 0; } //指定一个滑动的上限 if(y>this.mTopViewHeight){ y=mTopViewHeight; } if(y!=this.getScrollY()){ super.scrollTo(x,y); } } @Override protected void onFinishInflate() { super.onFinishInflate(); this.mTop=this.findViewById(R.id.topView); this.mBottom=this.findViewById(R.id.bottomView); }}
<slidingmenu.dreamfly.org.slidingmenu.custom.app.DetailRootLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:id="@+id/mTopView" android:layout_width="match_parent" android:background="#4400ff00" android:layout_height="400dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="22sp" android:gravity="center" android:text="软件介绍" /> </RelativeLayout> <RelativeLayout android:id="@+id/mBottomView" android:layout_width="match_parent" android:background="#4400ff00" android:layout_height="400dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textSize="22sp" android:gravity="center" android:text="软件详情" /> </RelativeLayout></slidingmenu.dreamfly.org.slidingmenu.custom.app.DetailRootLayout>
从上面的XML和java代码中,我们可以看到,自定义的LinearLayout的是一个模仿ScrollView类的自定义控件。这个LinearLayout含有两个布局:mTop和mBottom。其中,mTop布局和mBottom布局不能同时显示(mTop可以全部显示,mBottom只能显示一个部分)当我们需要手指向下滑动的时候,我们需要这个自定义的LinearLayout经过滑动后,将mTop完全隐藏,将mBottom完全显示。这时候,就需要Scroller类来辅助滑动了:我们仔细看这段代码中的这几个部分:
case MotionEvent.ACTION_UP: this.isDragging = false; float disTmp=event.getY()-this.mLastY; this.mScroller.startScroll(this.mScroller.getFinalX(), this.getScrollY(),0,-(int)disTmp); break;
这里监听到手势抬起的动作,然后计算move的最后一点的动作y和up动作的Y的差值,然后调用Scroller的startScroll函数。这里先讲下startScroll函数的参数都是什么含义:
public void startScroll (int startX, int startY, int dx, int dy, int duration)
以提供的起始点和将要滑动的距离开始滚动。
startX 水平方向滚动的偏移值,以像素为单位。正值表明滚动将向左滚动(滑动的起点)
startY 垂直方向滚动的偏移值,以像素为单位。正值表明滚动将向上滚动(滑动的终点)
dx 水平方向滑动的距离,正值会使滚动向左滚动
dy 垂直方向滑动的距离,正值会使滚动向上滚动
duration 滚动持续时间,以毫秒计。默认是以250ms计算的
可以看到,这个函数并没有实现滑动的动作,而是记录了滑动的距离(在Scroller类的源代码中可以看到)那滑动的动作是如何实现呢?当然靠view啦,我们看接下来的代码:在startScroll之后,我们调用了 this.invalidate()方法,这个方法的意思是请求View重新绘制,这时候,view就会调用computeScroll()这个方法,这个方法在View中是空,就是等着子类来覆写这个方法。于是,我们在这里覆写方法的代码为:
@Override public void computeScroll() { if(this.mScroller.computeScrollOffset()){ this.scrollTo(0,this.mScroller.getCurrY()); invalidate(); } }
这个方法里面我们使用了Scroller类的computeScrollOffset()方法,和scroller类的getCurrY()方法。这都是什么意思呢?其实很简单,在computeScrollOffset()方法中,根据startScroll方法中的被赋值的参数来进行计算现在滑动的距离,这个方法返回的是boolean数值,代表是否计算完成了,何时计算完成了呢?其实也是在startScroll中被赋值的。computeScrollOffset()不断的计算更新当前应该滑动到的curX或者curY(也就是scroller.getCurX()/getY())的数值,直到computeScrollOffset()的数值已经达到了startScroll中的指定的数值,就会返回false。
因此我们需要有这样一个不断循环的逻辑 rcomputeScroll()->getCurX/getCurY->invalidate(),来完成View的内容上的滑动/那么我们怎么控制滑动的最大间距呢?可以同通过覆写scrollTo方法:
/** * * @param x * @param y */ @Override public void scrollTo(int x,int y){ if(y<0) { y = 0; } //指定一个滑动的上限 if(y>this.mTopViewHeight){ y=mTopViewHeight; } if(y!=this.getScrollY()){ super.scrollTo(x,y); } }
来控制滑动的最大距离。
这就是,使用scroller类来实现View平缓滑动的一种方式,总结一下:View.scrollBy和View.scrollTo都是瞬时过度的,要像让View平缓过度,我们就需要利用Scroller的辅助计算,把一些复杂的过程呢个计算交给scroller,把然后配合scroller的计算结果(currY/currX)来调用View.scrollTo或者scrollBy方法。
接下来我们,看看借助scroller实现平缓滑动的其他方式(在文档中被注释掉的)那部分代码:
this.mVelocityTracker.computeCurrentVelocity(1000,this.mMaximumVelocity); float velocityY=this.mVelocityTracker.getYVelocity(); //向下滑动,速率的计算值肯定是负数 if(Math.abs(velocityY)>this.mMinimumVelocity){ this.fling(-(int)velocityY);
可以看到,实现View的平缓滑动,可以是使用scroller.fling()方法,我们首先通过mVelocityTracker(速度追踪器类)来计算滑动的速度,然后调用scroller.fling()方法:我们来看看fling方法的参数是都代表什么意思?
* public void fling (int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
在fling(译者注:快滑,用户按下触摸屏、快速移动后松开)手势基础上开始滚动。滚动的距离取决于fling的初速度。
参数
- startX 滚动起始点X坐标
- startY 滚动起始点Y坐标
- velocityX 当滑动屏幕时X方向初速度,以每秒像素数计算
- velocityY 当滑动屏幕时Y方向初速度,以每秒像素数计算
- minX X方向的最小值,scroller不会滚过此点。
- maxX X方向的最大值,scroller不会滚过此点。
- minY Y方向的最小值,scroller不会滚过此点。
maxY Y方向的最大值,scroller不会滚过此点。
所以,fling方法主要也是做数据记录,记录下这些数据,之后呢?我们再次调用 invalidate();来请求重新绘制,然后和之前的逻辑是一样的了,仍然是循环逻辑,然后不断地调用View.scrollTo/By等方法来实现View的缓慢滑动。有两点与之前的方法不同的是:这里可以设置根据手势设置View滑动的速度,比较灵活一点;同时也可以不用覆写scrollTo的方法,去设置最大上限,因为在fling方法中已经设置过了。
好吧,现在我们已经了解了scroller的用法了,总结下:- 1.View有两种滑动的方式:scrollTo和scrollBy两种方法,这两个方法都是实现view内容滑动的主要方法,但是,这两个方法都是瞬时完成的,没有什么过渡的阶段。
- 2.Scroller类是一个辅助View滑动的计算类,主要做数据存储和数据计算,通过覆写View.computeScroll()方法来,调用Scroller.copmuteOffset()和不断地请求View重新绘制,这样循环的逻辑,来实现View的有过程的滑动。
Q&A:- View.getScrollY(),View.getScrollX() 和View.getX(),View.getY() 区别?这个问题其实就是Scroller滑动和View的动画的区别,如果我们实现了View内容的滑动(比如借助scroller类),那getScrollX/或者是getScrollY是经常变化的,getX/Y是不变化的。
- 借助scroller类实现滑动的时候,View.scrollTo() 的参数为什么使用Scroller.getCurX/Y()?这个问题其实在文中已经说了,每次循环的逻辑的都是先调用Scroller.copmuteOffert()方法,在这个方法中,Scroller类会根据startScroll或者是fling中的方法去计算下一个时间段中的要滑动到地方,然后赋值给内部的类curX/curY,之后调用View.scrollTo/scrollBy方法的时候,在将计算好的额curX/curY取出来就可以了
View.getScrollY() 和 Scroller.getCurY() 的有何区别?View.getScrollY()方法代表是组件边缘和手机屏幕的距离,Scroller.getCurY()是在滑动中,下一步滑动要去的坐标的位置,两者基本没有什么太多关联
- 关于Android的Scroller类和View
- Android View Scroller类,scrollTo(...)和scrollBy(...)方法
- Android View 的滚动原理和 Scroller、VelocityTracker 类的使用
- View的基础知识和Scroller的使用
- scroller的使用和view的位移
- Android View 的弹性滑动: Scroller使用说明
- Android View Scroller问答
- Android Scroller和VelocityTracker类
- Android View视图系统分析和Scroller和OverScroller分析
- android scroller类的使用
- android scroller类的使用
- android scroller类的使用
- 关于android中scroller类源码浅析
- Android学习Scroller(五)——详解Scroller调用过程以及View的重绘
- 在Android中动画移动一个View的位置,采用Scroller类实现
- View滑动的原理,解析scrollTo,ScrollBy和Scroller
- Android Scroller类详解--实现View内容弹性滑动
- View的滚动与Scroller
- 纹理映射(Texture Mapping)
- Gson-----7、Map处理(下)
- 对指针的进一步理解
- python中的文件操作
- Python学习(一)常用函数
- 关于Android的Scroller类和View
- 利用矩阵快速幂加速二维dp hdu 5318
- 启动busybox自带的ftp服务器
- Linux(CentOS6) 调整 /home 挂载 分区大小
- 多线程连接数据库问题
- HashMap和LinkedHashMap的存取顺序问题
- Xcode 7注释插件不能用 解决办法
- mac终端命令大全介绍
- WebKit之HTMLInputElement介绍