一:学习于http://blog.csdn.net/mr_immortalz/article/details/51168278
view提供的方法
getTop:获取到的,是view自身的顶边到其父布局顶边的距离
getLeft:获取到的,是view自身的左边到其父布局左边的距离
getRight:获取到的,是view自身的右边到其父布局左边的距离
getBottom:获取到的,是view自身的底边到其父布局顶边的距离
MotionEvent提供的方法
getX():获取点击事件相对控件左边的x轴坐标,即点击事件距离控件左边的距离
getY():获取点击事件相对控件顶边的y轴坐标,即点击事件距离控件顶边的距离
getRawX():获取点击事件相对整个屏幕左边的x轴坐标,即点击事件距离整个屏幕左边的距离
getRawY():获取点击事件相对整个屏幕顶边的y轴坐标,即点击事件距离整个屏幕顶边的距离
做个测试
这里需要注意的是:
点击B点后(可以看到先是回调TestTextView中的onTouchEvent方法,然后才是MainActivity中的onTouchEvent,因为我在二者的onTouchEvent方法中都没有进行点击事件的消费处理,所以会往上传递,突然扯到了事件分发机制,2333~这里就是突然想补充一点,还是扯回坐标吧)
1.TestTextView中getY和getRawY取得的值不一样,这点我们可以理解
2.MainActivity中getY和getRawY取得的值一样!(我们注意到点击A,B点都是如此)
这里我们得到一条启发:
getY和getRawY这一系动作都是由MotionEvent来定义产生的。是得看最后MotionEvent是被哪个View所消耗。如果MotionEvent没有被任何View所消耗,最终返回Activity则getY和getRawY则一致。如果被View所消耗,则具体情况具体分析,getY,getRawY可能一致,也可能不一致。
类似在ListView这种有滚动轴的控件中会是什么样的呢?
贴上代码
@Override public boolean dispatchTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { LogUtil.m("SecondActivity getX " + event.getX() + " getY " + event.getY()); LogUtil.m("SecondActivity getrawx " + event.getRawX() + " getrawy " + event.getRawY()); } return super.dispatchTouchEvent(event); }
这次我们点击的是item7的顶部。
(PS:这时可能会有点好奇,我们明明点击的接近是item7的顶部,为啥得到的Y指却不是接近0呢,原因后面讲)
这里我们得到一条启发:
对于这种滑动的ViewGroup,我们在获取ViewGroup的坐标值时并不需要考虑它到底滑动了多少(实际滑动的我们应该看作是ViewGroup中的View在滑动)
二.获取
在上面我们留下了一个疑问:我们明明点击的接近是item7的顶部,得到的Y指却不是接近0。
原因在于getRawY返回的是点击事件距离整个屏幕顶边的距离,所以点击item7的顶部,得到的Y值其实就是状态栏的值。
当然我们有时候碰到的不仅就只有状态栏,有时候是状态栏与标题栏并存的。所以我们在获取ViewGroup的Y值是一定要注意是否需要减去状态栏,标题栏(如果有)的高度,否则计算得到的Y值并不是正确的。
这里为了让大家更清晰的了解,大家可以看看这篇文章http://bbs.51cto.com/thread-1072344-1.html(Android4.0窗口机制和创建过程分析 )
下面我们用图来初略说明(这是我的理解,有误欢迎指正)
那要是我们想要在onCreate中取到我们想要的值,我们应该怎么做呢?
大家可以参考这两篇博文
(http://www.cnblogs.com/kissazi2/p/4133927.html)
(http://blog.csdn.net/codezjx/article/details/45341309)
我觉得写得很好了
归纳如下:
监听Draw/Layout事件:ViewTreeObserver
将一个runnable添加到Layout队列中:View.post()
重写View的onLayout方法
重写Activity的onWindowFocusChanged方法,在该方法中获取
这里我推荐2,4这两种,即
view.post(new Runnable() {
@Override
public void run() {
view.getHeight();
}
});
或者
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//此处可以正常获取width、height等
}
三.计算能实现滑动这样炫酷的效果`(∩_∩)′)
首先我们需要建立一个概念
在Android的坐标系中,原点在屏幕左上角,向右x为正,向下y为正。
为了好计算,图片中的坐标单位是px)
(下面就以这个为例,我们要将View从原点移动到(200,400)的位置,即B点与C点重合)
想实现View移动大致有这几种方式(代码见下面)
XML文件
TestTextView也很简单,就是
/**
* Created by Mr_immortalZ on 2016/4/16.
* email : [email protected]
*/
public class TestTextView extends TextView {
public TestTextView(Context context) {
super(context);
}
public TestTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TestTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
/*LogUtil.m("TestTextView getX "+event.getX()+" getY "+event.getY());
LogUtil.m("TestTextView getrawx "+event.getRawX()+" getrawy "+event.getRawY());*/
//layout(getLeft() + 200, getTop() + 400, getRight() + 200, getBottom() + 400);
/*offsetLeftAndRight(200);
offsetTopAndBottom(400);*/
/* ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + 200;
lp.topMargin = getTop() + 400;
setLayoutParams(lp);*/
//((View)getParent()).scrollTo(-200,-400);
//scrollTo(-50,-10);
//scrollTo(300, 500);
//((View)getParent()).scrollBy(-200,-400);
/*AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(this, "translationX", 200),
ObjectAnimator.ofFloat(this,"translationY", 400)
);
set.start();*/
TranslateAnimation anim = new TranslateAnimation(0, 200, 0, 400);
anim.setFillAfter(true);
startAnimation(anim);
LogUtil.m("移动后 getX " + getX() + " getY " + getY());
LogUtil.m("移动后 getLeft " + getLeft() + "tv getTop " + getTop()
+ " tv getRight " + getRight() + " tv getBottom " + getBottom());
break;
}
return true;
}
}
我们移动到指定位置的有7种方式
1.layout
layout(getLeft() + 200, getTop() + 400, getRight() + 200, getBottom() + 400);
- 1
移动后getLeft等值改变
2.offsetLeftAndRight、offsetTopAndBottom
offsetLeftAndRight(200);
offsetTopAndBottom(400);
- 1
- 2
移动后getLeft等值改变
3.修改LayoutParams
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + 200;
lp.topMargin = getTop() + 400;
setLayoutParams(lp);
- 1
- 2
- 3
- 4
移动后getLeft等值不改变
4.scrollTo
((View)getParent()).scrollTo(-200,-400);
- 1
移动后getLeft等值不改变
5.scrollBy
((View)getParent()).scrollBy(-200,-400);
- 1
移动后getLeft等值不改变
对于scrollTo、scrollBy需要注意的有两个问题
问题1:
移动计算值 = 最开始点坐标 - 最后移动到的坐标
原因是因为最终会调用这个方法
—— invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
其中l,t,r,b为原来坐标点,scrollX,scrollY为目标坐标点,只有当目标坐标点值是负数时,移动到的位置才为正数!
例如scrollTo ,我们要从(0,0)移动到(200,400)这个点,根据上面的公式可知为负值
问题2
为什么需要加上 ((View)getParent())
TestTextView本身是View,scrollTo、scrollBy移动的都是View的Content,如果不加的话,使用的效果则是TestTextView的文字位置变化,而TestTextView本身不会变化。
如果在ViewGroup中使用scrollTo、scrollBy,则移动的是ViewGroup中的View.我们这里需要让TestTextView移动,则需要先 ((View)getParent()),然后再((View)getParent()).scrollTo…
6.属性动画
我就以ObjectAnimator为例子
AnimatorSet set = new AnimatorSet(); set.playTogether( ObjectAnimator.ofFloat(this, "translationX", 200), ObjectAnimator.ofFloat(this,"translationY", 400) ); set.start();
移动后getLeft等值不改变
7.位移动画
TranslateAnimation anim = new TranslateAnimation(0,200,0,400); anim.setFillAfter(true); startAnimation(anim);
移动后getLeft等值不改变
关于位移动画的补充点:
我们经常用这样的需求,要求一个popupwindow从屏幕底部弹出或者从屏幕顶部弹出。
这里的位移设置同样还是如此(原点在屏幕左上角,向右x为正,向下y为正)
这篇博文可以参考学习下,下面这张神图也是来自这篇博文(Android动画之translate(位移动画))
http://www.cnblogs.com/bavariama/archive/2013/01/29/2881225.html
注意点
属性动画是真实改变View的位置的,虽然属性动画、位移动画的getLeft等没有改变,但是属性动画的getX、getY是改变了的,位移动画的getX、getY仍未改变!
二.角度与弧度的定义
角度和弧度一样都是描述角的一种度量单位,下面是它们的定义:
名称定义
角度两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆周长的360分之一时,两条射线的夹角的大小为1度.弧度两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度.
如图:
三.角度和弧度的换算关系
根据角度和弧度的的定义和圆的相关知识非常容易就能得出两者的换算公式:
先设圆的周长为C. 半径为r
C = 2πr;
一周对应的角度为360度(角度),对应的弧度为2π弧度。
故: 180度 = π弧度.
可得:
公式例子
弧度 = 角度xπ/1802π = 360 x π / 180角度 = 弧度x180/π360 = 2π x 180 / π
维基百科的公式:
rad 是弧度, deg 是角度
四.一些细节问题
由于默认屏幕坐标系和常见数学坐标系的小差别(坐标系问题点这里),所以在角上必然也会存在一些区别,例如:
在常见的数学坐标系中角度增大方向为逆时针,
在默认的屏幕坐标系中角度增大方向为顺时针。
参考文章
◎本系列相关文章
http://www.gcssloop.com/customview/AngleAndRadian/
三.简单介绍颜色
安卓支持的颜色模式:
颜色模式备注
ARGB8888四通道高精度(32位)ARGB4444四通道低精度(16位)RGB565屏幕默认模式(16位)Alpha8仅有透明通道(8位)
PS:其中字母表示通道类型,数值表示该类型用多少位二进制来描述。如ARGB8888则表示有四个通道(ARGB),每个对应的通道均用8位来描述。
注意:我们常用的是ARGB8888和ARGB4444,而在所有的安卓设备屏幕上默认的模式都是RGB565,请留意这一点。
以ARGB8888为例介绍颜色定义:
类型解释0(0x00)255(0xff)
A(Alpha)透明度透明不透明R(Red)红色无色红色G(Green)绿色无色绿色B(Blue)蓝色无色蓝色
其中 A R G B 的取值范围均为0~255(即16进制的0x00~0xff)
A 从ox00到oxff表示从透明到不透明。
RGB 从0x00到0xff表示颜色从浅到深。
当RGB全取最小值(0或0x000000)时颜色为黑色,全取最大值(255或0xffffff)时颜色为白色
几种创建或使用颜色的方式
1.java中定义颜色
int color = Color.GRAY; //灰色
由于Color类提供的颜色仅为有限的几个,通常还是用ARGB值进行表示。
int color = Color.argb(127, 255, 0, 0); //半透明红色
int color = 0xaaff0000; //带有透明度的红色
2.在xml文件中定义颜色
在/res/values/color.xml 文件中如下定义:
#ff0000
#00ff00
详解: 在以上xml文件中定义了两个颜色,红色和蓝色,是没有alpha(透明)通道的。
定义颜色以‘#’开头,后面跟十六进制的值,有如下几种定义方式:
#f00 //低精度 - 不带透明通道红色
#af00 //低精度 - 带透明通道红色
#ff0000 //高精度 - 不带透明通道红色
#aaff0000 //高精度 - 带透明通道红色
3.在java文件中引用xml中定义的颜色:
int color = getResources().getColor(R.color.mycolor);
4.在xml文件(layout或style)中引用或者创建颜色
android:background="@color/red" //引用在/res/values/color.xml 中定义的颜色
android:background="#ff0000" //创建并使用颜色
三.取色工具
颜色都是用RGB值定义的,而我们一般是无法直观的知道自己需要颜色的值,需要借用取色工具直接从图片或者其他地方获取颜色的RGB值。
1.ColorPix(for Win)
简单的取色调色工具,可以从屏幕取色,非常小而精简。
点击这里下载ColorPix
2.Picpick(for Win)
功能更加强大的工具:PicPick。
PicPick具备了截取全屏、活动窗口、指定区域、固定区域、手绘区域功能,支持滚动截屏,屏幕取色,支持双显示器,具备白板、屏幕标尺、直角座标或极座标显示与测量,具备强大的图像编辑和标注功能。
点击这里获取PicPick
3.Sip(for Mac)
Sip作为Mac上的取色工具,也是十分优秀的,除了屏幕取色外还配备了不同类型的调色板,并且支持将颜色格式化为任何常用的格式。
点击这里获取Sip
四.颜色混合模式(Alpha通道相关)
通过前面介绍我们知道颜色一般都是四个通道(ARGB)的,其中(RGB)控制的是颜色,而A(Alpha)控制的是透明度。
因为我们的显示屏是没法透明的,因此最终显示在屏幕上的颜色里可以认为没有Alpha通道。Alpha通道主要在两个图像混合的时候生效。
默认情况下,当一个颜色绘制到Canvas上时的混合模式是这样计算的:
(RGB通道) 最终颜色 = 绘制的颜色 + (1 - 绘制颜色的透明度) × Canvas上的原有颜色。
注意:
1.这里我们一般把每个通道的取值从0(ox00)到255(0xff)映射到0到1的浮点数表示。
2.这里等式右边的“绘制的颜色”、“Canvas上的原有颜色” 都是经过预乘了自己的Alpha通道的值。如绘制颜色:0x88ffffff,那么参与运算时的每个颜色通道的值不是1.0,而是(1.0 * 0.5333 = 0.5333)。 (其中0.5333 = 0x88/0xff)
使用这种方式的混合,就会造成后绘制的内容以半透明的方式叠在上面的视觉效果。
其实还可以有不同的混合模式供我们选择,用Paint.setXfermode,指定不同的PorterDuff.Mode。
下表是各个PorterDuff模式的混合计算公式:(D指原本在Canvas上的内容dst,S指绘制输入的内容src,a指alpha通道,c指RGB各个通道)
混合模式计算公式
ADDSaturate(S + D)CLEAR[0, 0]DARKEN[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]DST[Da, Dc]DST_ATOP[Sa, Sa * Dc + Sc * (1 - Da)]DST_IN[Sa * Da, Sa * Dc]DST_OUT[Da * (1 - Sa), Dc * (1 - Sa)]DST_OVER[Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc]LIGHTEN[Sa + Da - SaDa, Sc(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)]MULTIPLY[Sa * Da, Sc * Dc]SCREEN[Sa + Da - Sa * Da, Sc + Dc - Sc * Dc]SRC[Sa, Sc]SRC_ATOP[Da, Sc * Da + (1 - Sa) * Dc]SRC_IN[Sa * Da, Sc * Da]SRC_OUT[Sa * (1 - Da), Sc * (1 - Da)]SRC_OVER[Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc]XOR[Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc]
用示例图来查看使用不同模式时的混合效果如下(src表示输入的图,dst表示原Canvas上的内容):
五.参考资料
◎本系列相关文章
安卓图形动画