本开篇是整个自定义 view 系列的开始,主要探讨安卓屏幕坐标系,颜色以及 MotionEvent 的 getX, getY 和 getRawX, getRawY 的区别。
屏幕坐标系
回想初中数学建立的直角坐标系,如图1-1
那在安卓中的屏幕会是什么样子呢? 它应该是这个样子的。如图 1-2
根据自己的理解去验证是否符合猜想,就在屏幕中去绘制这样的一个坐标系。在自定义控件过程中,有些坐标想不清楚其实也是和没有深刻理解手机坐标系的而导致的。如图1-3 是我在画布中绘制而成。通过对比可以看出,手机屏幕的 y 轴正半轴向下,这就是和数学坐标系的区别。想要看代码预览代码的点击此处 ,注释很清楚的,当然不懂也是没关系的,本篇只是介绍坐标系,如果是小白可能对代码中使用的 api 不了解,别慌,问题不大。往后看会懂的😊, 你只需要理解屏幕坐标系和数学中的坐标系的区别即可。
颜色系统
将颜色之前先说说光的三原色,也就是红、绿、蓝,光学三原色组成显示屏显示颜色。在安卓系开发中颜色无处不再,在自定义 view 中更是经常用到。常用的颜色设置方法如下:
// 通过十六进制的方式设置
mPaint.setColor(Color.parseColor("#E2C0D6"));
// 没有透明度设置通道
mPaint.setColor(Color.rgb(100, 100,100));
// 有透明度, 其中 a 表示 alpha , r 表示red, g 表示 green, b 表示 b
mPaint.setColor(Color.argb(200,100,100, 100));
值得注意的是,安卓手机屏幕是不可能有透明度的变化的,我们设置的透明度其实是将 R、G、B 与画布的颜色进行混合而的到的。这样达到透明度的效果。有很多这样的图形混合方式,将在后续讲解。
MotionEvent 的 getX,getY 和 getRawX, getRawY
讲这个区别通过例子来讲解,在屏幕上画一个圆,然后当手指按下后进行移动。分析这个自定义 view 的实现,无非就是需要处理按下的手指是否在圆上,以及怎样记录移动的坐标,根据记录的坐标不断更新圆的位置, 效果如下:
首先新建 MotionEventView 继承自 View 重写带有两个参数的构造方法,这是为了在布局中能使用,布局中使用的自定义控件会调用带有两个参数的构造方法。接着初始化画笔。
private void initPaint() {
// 初始化画笔并设置抗拒址以及抗抖动.
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
// 设置填充.
mPaint.setStyle(Paint.Style.FILL);
// 设置画笔的颜色
//mPaint.setColor(Color.parseColor("#E2C0D6"));
mPaint.setColor(Color.parseColor("#880000"));
}
以及记录移动的坐标,初始化时为 view 的中心点坐标,当前的 view 和屏幕宽高一致。
public MotionEventView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
// 记录手指移动的坐标
mPoint = new PointF();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
// 开始初始化为 view 的中心点坐标
mPoint.set((float) mWidth / 2, (float) mHeight / 2);
}
注意的是 onSizeChanged 是 view 经过测量后的到确切的大小。其中 oldw,oldh 是当 view 发生该变导致重新绘制,记录的是上一次 view 测量的大小。而 w,h 是 view 最终经过测量确定的大小。
接下来,在 onDraw 方法中绘制圆。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制一个圆, 确定一个圆需要一个圆心坐标和半径,然后以半径旋转一周得到一个圆,
// 这里第一个和第二个参数为圆心坐标,
// 第三个参数为 半径,
// 第四个参数是画笔
canvas.drawCircle(mPoint.x, mPoint.y, radius, mPaint);
}
最后就是处理手势移动过程啦!在安卓中要处理手势移动,只需实现 onTouchEvent 方法或者通过 view 的 setOnTouchListener 实现 onTouch 方法也可。处理其中手势按下,移动,抬起 或意外事件终结各个状态。
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN: // 根据名字可以看出,这是手势按下
// 手指按下时需要检查是否在圆上
mHasPressed = hasPressed(event);
if (mHasPressed) {
Toast.makeText(getContext(), "点击到圆上", Toast.LENGTH_SHORT).show();
}
break;
case MotionEvent.ACTION_MOVE:
if (mHasPressed) {
// 手势移动的时,将新的坐标赋值给 mPoint
mPoint.set(event.getX(), event.getY());
}
// 调用这个方法会重新绘制,调用 onDraw 方法。
invalidate();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 手势抬起或意外终结将圆还原到初始位置,以及重置标志位。
mPoint.set((float) mWidth / 2, (float) mHeight / 2);
mHasPressed = false;
invalidate();
break;
}
上面检查手势按下的坐标是否在圆上,这也是在初等数学中学到的。也就是按下的点与圆心的距离和半径的差值,如果大于0,在圆外,小于等于0 在圆内。其本质就是勾股定理,看一张图。 只要想象 O 点为圆心坐标, B 点为手势按下的坐标。通过公式就可以轻松求出距离。
所以 hasPressed 的计算方法如下:
/**
* 检查是否 down 事件在圆上
* @param event
* @return
*/
public boolean hasPressed(MotionEvent event) {
// 当前按下的位置距离 view 的左上角位置.
float x = event.getX();
float y = event.getY();
// 要判断一个点是否在圆上,根据公式 按下的点和圆心的距离小于等于半径, 实际上就是初中学的勾股定理
// Math.pow 为求一个数的几次方, 这里是 2 次方
double distance = Math.sqrt(Math.pow(x - mPoint.x, 2) + Math.pow(y - mPoint.y, 2));
if (distance <= radius) {
return true;
}
return false;
}
如果留心看代码的话,可以看到我们使用的是 event.getX(), event.getY() 来获取的按下的坐标。这个点的含义是对于当前的视图, 比如这个就是针对的是我们自定义的 MotionEventView, 而 getRawX(), getRawY() 是以屏幕的坐标系为参考。读者可以自行将 event.getY() 改为 getRawY() 试试看,看看是不是会发现按下的点要在圆上方一点。这是因为 getRawY 的坐标多了一个 ActionBar 的高度。