Android的坐标系是从左上角开始,向左为X正方向,向下为Y正方向,与普通的坐标系有些区别。
getTop(); //获取子View左上角距父View顶部的距离
getLeft(); //获取子View左上角距父View左侧的距离
getBottom(); //获取子View右下角距父View顶部的距离
getRight(); //获取子View右下角距父View左侧的距离
Android3.0 之后还添加了 x,y,translationX,translationY 四个值,这四个值也是相对于父View的,默认 translationX和translationY 等于0,并且存在以下关系:
x = left + translationX
y = top + translationY
当View发生平移时,left 和 top 不会改变,改变的是 x,y,translationX,translationY 四个值
event.getX(); //触摸点相对于其所在组件坐标系的坐标
event.getY();
event.getRawX(); //触摸点相对于屏幕默认坐标系的坐标
event.getRawY();
public void SloopView(Context context) {}
public void SloopView(Context context, AttributeSet attrs) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr) {}
public void SloopView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {}
构造函数是以上四种,最常用的是带一个参数何两个参数的。调用的时机为:
SloopView view = new SloopView(this); // 调用一个参数的构造函数
//调用两个参数的构造函数
<com.sloop.study.SloopView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
如果使用了自定义属性,则需要定义3个参数的构造函数,这里省略。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthsize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int widthmode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int heightsize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
int heightmode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
}
模式 | 二进制数值 | 描述 |
---|---|---|
UNSPECIFIED | 00 | 默认值,父控件没有给子view任何限制,子View可以设置为任意大小。 |
EXACTLY | 01 | 表示父控件已经确切的指定了子View的大小。 |
AT_MOST | 10 | 表示子View具体大小没有尺寸限制,但是存在上限,上限一般为父View大小。 |
注意:
如果对View的宽高进行修改了,不要调用super.onMeasure(widthMeasureSpec,heightMeasureSpec);要调用setMeasuredDimension(widthsize,heightsize); 这个函数。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
child.layout(l, t, r, b);
名称 | 说明 | 对应的函数 |
---|---|---|
l | View左侧距父View左侧的距离 | getLeft(); |
t | View顶部距父View顶部的距离 | getTop(); |
r | View右侧距父View左侧的距离 | getRight(); |
b | View底部距父View顶部的距离 | getBottom(); |
真正绘制View的部分,当计算玩View的大小,并且通过onLayout确定了它的位置,就可以通过onDraw() 绘制出View。
这样一个View层级:
结构如下:
事件分发流程:
当有点击事件,首先是Activity捕获到,一直传递到View,如果这一个过程事件都没有被处理,则事件会被反向传播给Activity,如果还没有被处理,则抛弃。
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
如果在View1 上发生touch事件,会依次调用下面的方法,红色是正向传播,绿色是回传:
当一个点击事件发生时,一般会经历下面3个重要的方法:
dispatchTouchEvent:用来进行事件分发,如果事件能传递给当前View,这个方法一定会被执行
onInterceptTouchEvent:在上述方法内部调用,用来判断是否拦截某个事件
onTouchEvent:在 dispatchTouchEvent 中调用,用于处理点击事件
onTouchListener 的优先级比 onTouch 高,如果 listener中返回 true,表示已经处理,就不会将事件继续传递给 onTouch,这样做的好处是方便外界处理点击事件。
这里先省略,详细请看《Android开发艺术探索》
一般有以下几种常用的方法:
1. scrollTo和scrollBy
2. 通过动画(平移动画,属性动画两种)
3. 改变View的LayoutParams使得View重新布局
mLauncher1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
float x = mLauncher1.getX();
float y = mLauncher1.getY();
ObjectAnimator.ofFloat(mLauncher1, "translationX", x, x + 100).setDuration(100).start();
ObjectAnimator.ofFloat(mLauncher1, "translationY", y, y + 100).setDuration(100).start();
}
});
需要注意的是,这种情况会引起View的重新绘制,效率要低一些
mlauncher2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mlauncher2.getLayoutParams();
params.leftMargin += 100;
params.topMargin += 100;
mlauncher2.requestLayout();
}
});
MeasureSpec是一个int型的值, 由两个值 Mode(高2位) 和 Size(低30位)组成。
子View的 MeasureSpec由 父容器的MeasureSpec和子View的LayoutParams共同决定
UNSPECIFIED:父容器不对View由任何的限制,一般系统中用,不用关注。
EXACTLY:父容器已经测量出View所需的大小,View的大小由 SpecSize所指定,它对对应于LayoutParams中的match_parent
AT_MOST:父容器指定一个可用的大小 SpecSize给View,对应 wrap_content
分为View的Measure过程和ViewGroup的Measure过程,ViewGroup的其实就是计算自己的,并且调用所有子View的Measure方法。
如何获取View的宽高:
1. 如果要获取某个View的宽高,在onCcreate,onResume,onStart这些方法中都是不能获取的。因为Activity和View的Measure不是同步执行的。可以在 onWindowFocusChanged 这个方法中获取,表示View的初始化已经完毕,会调用这个方法。但是它会被多吃调用,失去焦点和得到焦点都会被调用依次。
测量自己应该在父容器中的位置
一般在onMeasure 中可以获取View的最终大小,但是极端情况也可能不正确。例如 onLayout中调用:super.layout(l,t,r+100,b+100),就会导致view的宽高都加100
一般分为下面4个步骤:
1. background.draw(canvas) 画背景
2. onDraw 画自己
3. dispatchDraw 画children
4. onDrawScrollBar 画装饰
《Android开发艺术探索》
自定义View合集