说来说去都不如画图示意简单易懂啊!!!真是的! 来吧先上张图!
(一)首先明确一下android 中的坐标系统:
屏幕的左上角是坐标系统原点(0,0)
原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向
(二)关于Scroll: 屏幕显示的内容很多时,会有超出一屏的情况,于是就产生了Scroll的概念。
在View类中有个方法:
getScrollY() 英文原文描述是:
Return the scrolled top position of this view. This is the top edge of the displayed part of your
view....
其实理解起来仍然就是:就是这个view相对于“坐标系统原点”(见上图)在Y轴上的偏移量.
(getScrollX同理)
【哇哈,有了图就是好说明啊~ 省了多少语言描述啊,而且还不一定能说清楚~】
getScrollY()就是当前视图相对于屏幕原点在Y轴上的偏移量.
(三)MotionEvent类中getRowX()和getX()的区别:
event.getRowX():触摸点相对于屏幕原点的x坐标event.getX(): 触摸点相对于其所在组件
原点的x坐标
于是乎: view.getScrollY() + event.getY() 就得到了view中的触摸点在Y轴上的偏移量
(四)TextView类中
有个getLayout()方法:the Layout that is currently being used to display the text. This
can be null if the text or width has recently changes.
其返回类型是Layout ,也就是返回textView的布局。
然后重要的是通过这个layout调用一个方法:
getLineForVertical(int verticalPointPosition) //得到某点在垂直方向上的行数值
于是综上所述,在实际的触摸事件中可以这样使用:
Layout layout=textView.getLayout();
int line = layout.getLineForVertical(textView.getScrollY() + (int) event.getY());
//得到触摸点在textView中垂直方向上的行数值。参数是触摸点在Y轴上的偏移量
接下来继续介绍一个方法, 要用到上边的layout 和line:
layout.getOffsetForHorizontal( line , (int) event.getX() );
//得到触摸点在某一行水平方向上的偏移量。
参数分别是: 该行行数值和触摸点在该行X轴上的偏移量。
此方法得到的该值会根据该行上的文字的多少而变化,并不是横向上的像素大小;
整个坐标系是以手机屏幕左上角为原点(0,0),如果在屏幕没有滑动之前,这一理解肯定是ok
的,但在滑屏之后,就会产生很多歧义和混淆,原因在于使用过程当中,很多方法的参数并非是
参照屏幕,而是相对于父视图,对这整个过程和后面自定义控件的坐标变化带来的各个参数变化
来说理解起来就并不那么适合了,最开始给人的感觉是越来越不清楚这个坐标该怎么设置,好像
坐标系总在发生变化,后来才慢慢在思维当中构建起视图与视图容器以及屏幕之间的关系。
android.view.View.layout(int l, int t, int r, int b) layout的过程就是确定View在屏幕上显示的具体位
置,在代码中就是设置其成员变量mLeft,mTop,mRight,mBottom的值,这几个值构成的矩形
区域就是该View显示的位置, 不过这里的具体位置都是相对与父视图的位置。mLeft代表当前
view.layout的这个view的左边缘离它的父视图左边缘的距离,拿上面“子视图2.layout(int l, int t,
int r, int b) ”来说,它的父视图便是子视图1,2,3合起来形成的整个大矩形,那么这里将父视图的左
上角定为(0,0),那么可以确定mLeft为一个子视图宽度320,以此类推, mTop指当前view的上边
缘离父视图上边缘的距离。而以此为界, mRight所指的是当前view的右边缘离父视图左边缘的
距离,一眼可以看出值为640(mLeft+自己的宽度),mBottom也是指当前view的下边缘离父视
图的上边缘的距离。至于为何如此,大概是因为坐标系的缘故,坐标中的任何点都必须以(0,0)为
起点,XY轴为衡量。
视图左侧位置view.getLeft() 视图右侧位置view.getRight()
视图顶部位置view.getTop(); 视图底部位置view.getBottom(); 这四个方法所获取到的各个左上右
下的值与layout的四个参数代表的是一样的,都是相对父视图的左边缘与上边缘。
视图宽度view.getWidth(); 视图高度view.getHeight() ; 这两个方法获取的是该view的高和宽,仅
仅在滑动的情况下,或者说该view的大小如果不发生变化,它的值是不会变的。
getMeasuredWidth(); getMeasuredHeight(); 说到这里就不得不提getWidth()、getHeight()和
getMeasuredWidth()、getMeasuredHeight()这两对函数之间的区别,getMeasuredWidth()、
getMeasuredHeight()返回的是measure过程得到的mMeasuredWidth和mMeasuredHeight的值,
而getWidth()和getHeight()返回的是mRight - mLeft和mBottom - mTop的值。
一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子视图的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子视图的具体位置。
view.getX(); view.getY(); getX和getY获取到的值为相对于父视图而言的两个左边缘和上边缘的距
离。
view.getLocationOnScreen(location); 该方法可以获取到当前view与屏幕的关系,location(0)代表
X值,表示该view的左边缘与屏幕的左边缘之间的距离。可以想象,当滑屏产生,view开始移动该
值肯定会改变的。location(1)代表Y值,表示该view的上边缘与屏幕的上边缘之间的距离,该距离
肯定是包含标题栏的高度的。getLocationInWindow();
ps:View.getLocationInWindow()和View.getLocationOnScreen()在window占据全部screen时,
返回值相同,不同的典型情况是在Dialog中时。当Dialog出现在屏幕中间时,
View.getLocationOnScreen()取得的值要比View.getLocationInWindow()取得的值要大。
VelocityTracker.getXVelocity() 指滑动速度包括速率和方向两个方面,往左滑动小于0,值为负;
往右滑动大于0,值为正。
view.scrollTo (x,y) 将整个父视图的左上角定为(0,0),再移动这个屏幕的左上角到父视图的点(x,y)
处,注意此处的x和y是根据父视图的坐标系来定的。view.scrollBy(x,y) x代表横向移动的距离,y
代表纵向移动的距离
view.getScrollX view.getScrollY 将整个父视图的左上角定为(0,0),那么子view. getScrollX会获取
到屏幕左边缘减去父视图的左边缘为0的距离,特别当滑屏时,父视图会被迫隐藏一部分,因为屏
幕的大小是固定的。getScrollY以此类推。
event.getX() event.getY() 该方法是不受视图影响的,X和Y的值仅仅代表手指在以左上角(0,0)为原
点的屏幕触摸点的坐标值。
Scroller.getCurrY() Scroller.getCurrX() 该方法拿横轴来说,代表屏幕的左边缘离父视图的左边缘的
距离。
Scroller.startScroll( int startX, int startY, int dx, int dy) 四个参数分别表示起点的坐标和滑动的向
量,即从( startX, startY)开始滑动,横向滑动dx的距离,纵向滑动dy的距离(正值向左滑,负值向
右滑),而这里的startX, startY又是参照的父视图左上角为原点坐标的坐标系,滑屏时经常使用
getScrollX()和getScrollY()来代表屏幕左边缘和上边缘处于父视图坐标系的具体位置
TranslateAnimation() 参数参照:http://blog.sina.com.cn/s/blog_90b91bf10101ai3e.html
以上是在做滑屏控件经常用到的方法,一方面需要了解layout和measure的基本流程,更重要一方
面,当你想要实现某一个效果的时候,比如slidingmenu那样的控件,查看源码我们可以知道它是
继承的ViewGroup,该怎样入手去做呢。
首先,需要了解它的父视图是什么,slidingmenu为例,打开程序,第一眼,是一个很普通的视图
页面,当向右滑动手指,这个视图页面开始向右边移动,而从左边会慢慢移出来另一部分视图,
看上去像是抽出来的或者是隐藏的,事实上抛开阴影效果来讲,想象手机屏幕的左边有一部分我
们看不到的视图,它就是这个被抽出来的menu视图了。
概括来说,一个主view,一个menu其实
是并排于一个大视图上面的。找到了父视图,接下来就好办了,认定这个父视图的宽度就是主
view的宽度和menu的宽度之和(暂不考虑padding之类),高度就是屏幕的高度,那么在思维当中
这个二维平面就产生了,将它想成一张纸,然后对准主view将这张纸贴到手机屏幕上,左右滑
动,会看到其实slidingmenu也就是这么个效果。
然后,实现的思路会清晰很多。定义这个父视图为myview继承viewgroup,原因在于尽管主view
和menu并排在一个大view下,但毕竟两者的内容不同,后面需要放进不同的控件处理不同的事
件,这个父视图内包含着两个view,到时候处理起来会方便很多,setcontentview为这个父视
图,那么打开程序的第一眼就会看到它。再定义这两个view设置好两个内容布局,并将它们
addview添加到myview当中。外部工作基本就完成了,可以呈现父视图,并且父视图内有两个子
view。
接下来,需要去完善一些细节,父视图内的子view该如何放置,这是关乎成败的一环,也就是如
何将这张纸贴到我们希望的位置,这时就是onlayout的处理了,处理好屏幕,父视图子view之间
的位置关系,通过各自的layout参数设置来摆放妥当各个view,比如开始的时候menu是隐藏的,
这个就是通过位置的摆放设置的,然后它是从左边滑出来的,说明它处于父视图的左边位置,而
主view处于相对右边的位置,而屏幕刚好也处于父视图右边的位置,恰好能看到主view的全貌,
在脑海里如果能有清晰的画面出现,实现起来就会轻松很多。当实现了这个摆放,就可以理解
menudrawer里面上下左右都可以滑出menu的结构了。
最后,便是滑动效果,请相信这样的控件里面,任何处理肯定都会和view位置的摆放扯上关系,
滑动方向,滑动距离等等都涉及到坐标的处理。这也是为何上面列出那些常用的获取view坐标的方法。
总结下来,构建类似这样的控件,也就这三点,明确父子视图和屏幕的关系,通过坐标和位置参
数设置它们的关系,处理这些关系发生变化的情况。
当然,事实上slidingmenu远远没这么简单,其中为了方便后续开发,它内置了很多接口和处理,
大多数都是位置坐标和事件监听相关联,而万变不离其宗的是,它也肯定有这三个方面的构建,
理解了这些基本的东西,尝试做一些自己想象的效果,对自定义的理解来说,进步会非常大。