Android Scroll分析(转)
一.滑动效果是如何产生的
滑动一个View的本质其实就是移动一个View,改变其当钱所在的位置,他的原理和动画效果十分的相似,就是通过不断的改变View的坐标来实现这一效果,动态且不断的改变View的坐标,从而实现View跟随用户触摸滑动而滑动
但是在讲解滑动效果之前,需要先了解一下Android中窗口坐标体系和屏幕的触控事件——MotionEvent
1.Android坐标系
在物理学上,要描述一个物体的运动,就必须选定一个参考系,所谓滑动,正是相对于参考系的运动,在Android,系统将屏幕最左上角的顶点作为Android坐标系的原点,从这个点向右是X轴正方向,从这个点向下是Y轴正方向,如图

系统提供了getLocationOnScreen(intlocation[])来获取Android坐标中的位置,即该视图左上角Android的坐标,另外,在触摸事件中使用getRawX(),getRawY()方法来获取坐标同样是Android坐标系中的坐标。
2.视图坐标系
Android中除了上面所说的这种坐标系之外们还有一个视图坐标系,他描述了子视图在父视图的位置关系,这两个坐标系并不复杂也不矛盾,他们的作用是相辅相成的,与Android坐标系类似,视图坐标系同样的以原点向右为X正方向,以原点向下为Y方法,只不过在视图坐标系中,原点不再是Android坐标系中的屏幕左上角,而是以父视图左上角为坐标原点,看图

在触控事件中通过getX,getY来获取的坐标就是视图坐标中的坐标
3.触控事件——MotionEvent
触控事件MotionEvent在用户的交互中,占着举足轻重的位置,学好触控事件是掌握后续内容的基础,首先,我们来看看MotionEvent中封装的一些常量,他定义了触摸事件的不同类型。

通常情况下,我们会在onTouchEvent(MotionEvent event)方法中通过event.getAction()来获取触摸事件的类型,并使用switch来判断,这个代码模块是固定的

在不涉及多点触控的前提下,通常可以使用以下代码来完成触摸事件的监听,不过这里只是一个代码模块,后面我们会讲具体的逻辑的
在Android中,系统提供了非常多的方法来获取坐标值,相对距离等,方法丰富固然好,但也给初学者带来了很多的困扰,不知道在什么情况下使用下图总结人一下一些常用的API

这些方法可以分成两个类别
- View提供的获取坐标方法
getTop():获取到的是View自身的顶部到其父布局顶部的距离
getLeft():获取到的是View自身的左边到其父布局左边的距离
getRight():获取到的是View自身的右边到其父布局右边的距离
getBottom():获取到的是View自身的底部到其父布局底部的距离
- MotionEvent提供的方法
getX():获取点击事件距离控件左边的距离,即视图坐标
getY():获取点击事件距离控件顶部的距离,即视图坐标
getRawX:获取点击事件整个屏幕左边的距离,即绝对坐标
getRawY:获取点击事件整个屏幕顶部的距离,即绝对坐标
相信现在大家对滑动都有一点点的认知了吧,我们再继续往下看
二.实现滑动的七种方法
当了解了Android坐标系和触控事件之后,我们再来看一看如何使用系统提供的API来实现动态的修改一个View的坐标,即滑动效果,而不管采用哪种方式,其实现的思路基本上是一样的,当触摸View的时候,系统几下View的坐标,从而获得到相对于之前坐标的偏移量,并通过偏移量来修改View的坐标,这样不断的重复就实现了滑动的过程。
下面我们通过实例来看看Android中如何实现滑动的效果没定义一个View,简单的实现一个布局

默认的显示样子

1.layout方法
我们都知道,在View的绘制上,会调用onLayout()方法来设置显示的位置,同样可以修改View的left,top,right,bottom四个属性来控制View的坐标,与前面提供的模板代码一样,每次调用onTouchEvent()的时候,我们来获取点的坐标,这里的逻辑很清楚,真的,我们来看看

这里我们就可以移动这个View了
2.offsetLeftAndRight()与offsetTopAndBottom()
这个方法相当于系统提供的一个对左右,上下移动的封装,当计算出偏移量的时候,只需要使用如下的代码就可以完成View的重新布局,效果和使用Layout()方法是一样的

3.LayoutParams
LayoutParams保留了一个View的布局参数,因此可以在程序中,通过改变LayoutParams来动态改变一个布局的位置参数,从而改变View位置的效果,我们可以很方便的在程序中使用getLayoutParams()来获取一个View的LayoutParams,当然,在计算偏移量的方法和Layout方法中计算offset是一样的,当获取到偏移量之后,可以通过setLayoutParams来改变LayoutParams,代码如下

不过这里需要注意的是,通过getLayoutParams()获取layoutParams时,需要根据View所在的跟布局的类型来设置不同的类型,,比如View放在LinearLayout里那就是 LinearLayout.LayoutParams,比如在RelativeLayout里就是 RelativeLayout.LayoutParams,不然系统是无法获取到layoutParams的.
在通过一个layoutParams来改变一个View的位置时,通常改变的是这个view的Margin属性,所以除了使用布局的layoutParams属性外,还需要 ViewGroup.MarginLayoutParams来实现这样的功能

我们可以发现,用ViewGroup更好,都不用去管父布局是什么
4.scrollTo与scrollBy
在一个View当中,系统提供了scrollTo与scrollBy这两种方式来实现移动一个View的位置,这两种方法的区别也很好理解,to和by, scrollTo(x,y);表示移动到一个具体的 点,scrollBy(dx,dy);表示移动的增量

但是,当我们拖动View的时候,你会发现View并没有移动,难道我们写错了?方法没有写错,View也的确移动了,但是他移动的并不是我们想要的东西,他只是移动了view的content,即让View的内容移动了,如果用ViewGroup使用to和by的话,那所有的子View都将移动,要是在View中使用的话,,那么移动的就是View的内容了,我们举个例子,比如TextView,content就是他的文本,ImageView,drawable就是对象
相信通过上面的分析,你也应该知道为什么不能再View里面使用这个两个方法来拖动这个view了,那么我们就该View所在的ViewGroup中使用scrollBy方法来移动这个view

但是,当再次拖动View的时候,你会发现View虽然移动了,但却在乱动,并不是我们想要的跟随触摸点的移动而移动,这里需要先了解一下视图移动的一些知识,大家在理解这个问题的时候,不妨想象一下手机是一个中空的盖板,盖板下面是一个巨大的画布,也就是我们想要显示的视图,当把这个盖板盖在画布的某处时,透过中间空着的矩形,我们看见了手机屏幕上显示的视图,而画布上其他的视图,则被盖板盖住了无法看见,我们的视图和这个例子事项相似,我们没有看见视图,但并不代表它不存在,有可能只是在屏幕外面而已,当调用scrollBy的方法时,可以想象外面的盖板在移动,这么说比较抽象,我们来看一下具体的例子

上图,中间的矩形相当于屏幕,即可是区域,后面的content相当于画布,代表视图,大家可以看到,只有视图的中间部分目前是可视的,其他部分都不可见,可见区域中设置一个button,他的坐标是(20.10),下面我们使用scrollBy方法来进行移动后如图

我们会发现,虽然设置scrollBy(20.10),偏移量均为XY的正方向,但是屏幕的可视区域,Button却向反方向移动了,这就是参考系选择的不同,而产生的不同效果。
通过上面的分析,可以发现,我们将scrollBy的参数dx,dy设置成正数,那么content将向坐标轴负方向移动,反之,则正方向

再去试验一下,大家可以发现,效果和前面的几种方法相同了,类似的,我们使用scrollTo也是可以实现的
5.Scroller
既然提到scrollTo与scrollBy,那就不得不提一下Scroller类了,Scroller和scrollTo与scrollBy十分的相似,有着千丝万缕的关系,那么他们有什么具体的区别尼?要解答这个问题,首先我们来看一个小栗子,假设要完成这样的一个效果:点击button,让一个viewgroup的子View移动100像素,问题看似很简单,只要使用scrollBy的方法就可以,的确,用这个方法确实可以,可是那都是一瞬间完成的事情,很突兀,而Scroller就可以实现平滑的效果,而不再是一瞬间的事情.
说道Scroller的原理,其实他与前面使用scrollTo与scrollBy的方法原理是一样的,下面我们通过一个小栗子来演示一下
- 初始化scroller
首先,通过他的构造方法来创建一个scroller对象

- 重写computeScroll,实现模拟滑动
下面我们需要重写computeScroll这个方法,他是使用Scroller的核心,系统在绘制View的同时,会在onDraw()方法中调用这个方法,这个方法实际上就是使用了ScrollTo()方法再结合Scroller对象,帮助获取到当前的滚动值,我们可以通过不断的瞬息移动一个小的距离来实现整体上的平滑移动效果,通常情况下,computeScroll的代码可以利用标准的写法:
Scroller类提供了computeScrollOffset()来判断是否完成了整个页面的滑动,,同时也提供了getCurrX(),getCurrY()来获取当前滑动坐标,上面唯一要注意的就是invalidate()了,因为只能在computeScroll中获得模拟过程中的scrollX,scrollY,坐标,但computeScroll方法是不会自动调用的,只能通过invalidate——》OnDraw——》computeScroll来简介调用,所以需要这个invalidate,而当模拟过程结束的时候,computeScrollOffset返回的是false,从而结束循
- startScroll开启模拟过程
最后,万事俱备只欠东风了,我们需要使用平滑移动事件,使用Scroller类的startScroll()方法来开启平滑过程,startScroll有两个重载的方法
public void startScroll(int startX,int startY,int dx,int dy)
public void startScroll(int startX,int startY,int dx,int dy)
可以看到他们的区别分别是具有指定的持续时长,而另一个每有,这个非常好理解,与在动画中的设置时间是一样的,而其他四个坐标,就是起始和偏移量,通过上述的步骤,就可以完成一个平移的效果了,下面我们回到实例,在构造分钟初始化Scroller对象,然后重写computeScroll方法,最后需要监听手指离开屏幕的事件,并在该事件之后调用startScroll()完成平移,所以我们在ACTION_UP中
case MotionEvent.ACTION_UP: //处理输入的离开动作 View view = ((View)getParent()); mScroller.startScroll(view.getScrollX(),view.getScrollY(),view.getScrollX(),view.getScrollY()); invalidate(); break;
6.属性动画
7.ViewDragHelper


浙公网安备 33010602011771号