这个测试项,要捕捉当前有几个触摸点,当前触摸点坐标,滑动事件在x轴、y轴方向的速度等信息,在触摸时跟随触摸点会出现十字光标,绘制出滑动轨迹。
- 首先绘制出暗色格子背景,采用了自定义View,较为简单,核心代码如下:
Paint paint; //画笔
private int mWidth;
private int mHeight;
public Check(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setColor(getResources().getColor(R.color.deepGray));//设置画笔颜色
paint.setStrokeJoin(Paint.Join.ROUND);//设置画笔图形接触时笔迹的形状
paint.setStrokeCap(Paint.Cap.ROUND);//设置画笔离开画板时笔迹的形状
paint.setStrokeWidth(1); //设置画笔的宽度
}
/**
* 这个方法可以获得控件的宽高
* @param canvas
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;
mHeight = h;
}
/**
* 绘制网格线
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
int lineStart = 80;
int space = lineStart; //长宽间隔
int vertz = lineStart;
int hortz = lineStart;
for (int i = 0; i < 100; i++) {
canvas.drawLine(0, vertz, mWidth, vertz, paint);
canvas.drawLine(hortz, 0, hortz, mHeight, paint);
vertz += space;
hortz += space;
}
}
接下来,因为要在这个背景上画图,我在其上覆盖一层透明ImageView,给该iv设置这个属性:
android:background="@android:color/transparent"
接下来的绘制滑动轨迹和十字光标都在这个iv上完成。-
接下来遇到了一些坑,都踩了一遍。
- 因为这个绘图是发生在一个Fragment里,我的绘图界面要设置全屏,但是该Activity中的其他Fragment则不需要这个设置。于是就在这个Fragment中获取到Window,然后设置全屏标记,然后让根视图MATCH_PARENT。
getActivity().getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
//mRootView是BaseFragment中设置的该Fragment的视图
this.mRootView.setLayoutParams(
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
- 创建bitmap,设置bitmap的高宽时,遇到了问题。
因为在onCreateView中View.getWidth和View.getHeight无法获得一个view的高度和宽度,这是因为View组件布局要在onResume回调后完成。所以现在需要使用getViewTreeObserver().addOnGlobalLayoutListener()来获得宽度或者高度。这是获得一个view的宽度和高度的方法之一。
OnGlobalLayoutListener 是ViewTreeObserver的内部类,当一个视图树的布局发生改变时,可以被ViewTreeObserver监听到,这是一个注册监听视图树的观察者(observer),在视图树的全局事件改变时得到通知。ViewTreeObserver不能直接实例化,而是通过getViewTreeObserver()获得。
mTouchScreenIv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mTouchScreenIvWidth = mTouchScreenIv.getWidth();
mTouchScreenIvHeight = mTouchScreenIv.getHeight();
// 创建空白图片
mBitmap1 = Bitmap.createBitmap(mTouchScreenIvWidth, mTouchScreenIvHeight, Bitmap.Config.ARGB_8888);
mBitmap2 = Bitmap.createBitmap(mTouchScreenIvWidth, mTouchScreenIvHeight, Bitmap.Config.ARGB_8888);
// 创建两张画布
mCanvas1 = new Canvas(mBitmap1); //底层画轨迹的画布
mCanvas2 = new Canvas(mBitmap2); //上面一层画十字架的画布
// 创建画笔
mPaint1 = new Paint(); //画轨迹的画笔
mPaint2 = new Paint(); //画十字架的画笔
// 画笔颜色为蓝色
mPaint1.setColor(getResources().getColor(R.color.lightBlue));
mPaint2.setColor(getResources().getColor(R.color.lightBlue));
// 宽度1个像素
mPaint1.setStrokeWidth(1);
mPaint2.setStrokeWidth(1);
// 先将白色背景画上
mCanvas1.drawBitmap(mBitmap1, new Matrix(), mPaint1);
mCanvas2.drawBitmap(mBitmap2, new Matrix(), mPaint2);
mBitmap3 = mergeBitmap(mBitmap1, mBitmap2);//将两张bitmap图合为一张
mTouchScreenIv.setImageBitmap(mBitmap3);
//用完要解除监听
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mTouchScreenIv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
- 把两个bitmap合成一个bitmap
/**
* 把两个位图覆盖合成为一个位图,以底层位图的长宽为基准
* @param backBitmap 在底部的位图
* @param frontBitmap 盖在上面的位图
* @return
*/
public Bitmap mergeBitmap(Bitmap backBitmap, Bitmap frontBitmap) {
if (backBitmap == null || backBitmap.isRecycled()
|| frontBitmap == null || frontBitmap.isRecycled()) {
Log.e(TAG, "backBitmap=" + backBitmap + ";frontBitmap=" + frontBitmap);
return null;
}
Bitmap bitmap = backBitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(bitmap);
Rect baseRect = new Rect(0, 0, backBitmap.getWidth(), backBitmap.getHeight());
Rect frontRect = new Rect(0, 0, frontBitmap.getWidth(), frontBitmap.getHeight());
canvas.drawBitmap(frontBitmap, frontRect, baseRect, null);
return bitmap;
}
- 给iv控件设置触摸事件。因为是多点触摸事件,所以记录down的起始点和move时的终止点都需要使用float数组。设置四个大小为10的数组。
- 添加 触摸事件的速度检测器。
- 单点触摸事件ACTION_DOWN ,多点触摸事件ACTION_POINTER_DOWN,使用case穿透将两种事件一起监听,遍历触摸事件,获得坐标,进行操作。
@Override
protected void setListener() {
mTouchScreenCheck.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
//当前DOWN或者UP的是手指的index
int curPointerIndex = motionEvent.getActionIndex();
//通过index获得当前手指的id
int curPointerId = motionEvent.getPointerId(curPointerIndex);
//添加事件的速度计算器
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(motionEvent);
int actionMasked = motionEvent.getActionMasked();
Log.i(TAG, "actionMasked === " + actionMasked);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
mCanvas1.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mCanvas2.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
mTouchScreenIv.setImageBitmap(mergeBitmap(mBitmap1, mBitmap2));
//设置当前有几个触摸点
pointerCount = motionEvent.getPointerCount();
if (pointerCount > 10) {
pointerCount = 10;
}
mActivePointers.append(curPointerId, curPointerId);
//在down事件中的操作
DownPoint(motionEvent);
mTouchScreenIv.setImageBitmap(mergeBitmap(mBitmap1, mBitmap2));
break;
case MotionEvent.ACTION_MOVE:
//获取当前触摸事件的个数
if (motionEvent.getPointerCount() > pointerCount) {
pointerCount = motionEvent.getPointerCount();
}
mTouchScreenTvP.setText("P:" + motionEvent.getPointerCount() + "/" + pointerCount);
//清除十字架
mCanvas2.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//在移动时的操作
MovePoint(motionEvent);
mTouchScreenIv.setImageBitmap(mergeBitmap(mBitmap1, mBitmap2));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
//计算并显示坐标偏移量,并且设置背景颜色
setDxDy(motionEvent);
//清除十字架
mCanvas2.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//清除这个触摸事件的ID
mActivePointers.remove(curPointerId);
mTouchScreenTvP.setText("P:" + 0 + "/" + pointerCount);
mTouchScreenIv.setImageBitmap(mergeBitmap(mBitmap1, mBitmap2));
break;
}
return true;
}
});
}
/**
* 在down事件中的操作
* @param motionEvent
*/
private void DownPoint(MotionEvent motionEvent) {
for (int i = 0; i < motionEvent.getPointerCount(); i++) {
int pointerId = mActivePointers.get(motionEvent.getPointerId(i));
try {
//获取触摸点的X,y坐标
startXs[pointerId] = motionEvent.getX(pointerId);
startYs[pointerId] = motionEvent.getY(pointerId);
finalStartX = startXs[pointerId];
finalStartY = startYs[pointerId];
//设置上面的字变化并且背景颜色为白色
mTouchScreenTvDx.setText("X:" + Math.round(motionEvent.getX(pointerId) * 10) / 10.0);
mTouchScreenTvDx.setBackgroundColor(Color.WHITE);
mTouchScreenTvDy.setText("Y:" + Math.round(motionEvent.getY(pointerId) * 10) / 10.0);
mTouchScreenTvDy.setBackgroundColor(Color.WHITE);
} catch (IllegalArgumentException e) {
// 此处捕捉系统bug,以防程序停止
e.printStackTrace();
}
mTouchScreenTvP.setText("P:" + pointerCount + "/" + pointerCount);
// 在开始和结束坐标间画一个点
mCanvas1.drawPoint(startXs[pointerId], startYs[pointerId], mPaint1);
//画十字架
mCanvas2.drawLine(0, startYs[pointerId], mTouchScreenIvWidth, startYs[pointerId], mPaint2);
mCanvas2.drawLine(startXs[pointerId], 0, startXs[pointerId], mTouchScreenIvHeight, mPaint2);
}
}
/**
* 在移动时对点的操作
* @param motionEvent
*/
private void MovePoint(MotionEvent motionEvent) {
for (int i = 0; i < motionEvent.getPointerCount(); i++) {
int pointerId = mActivePointers.get(motionEvent.getPointerId(i));
Log.i(TAG, "1111111 move pointerId" + pointerId);
Log.i(TAG, "1111111 endXS size " + endXs.length);
try {
// 获取手移动后的坐标
endXs[pointerId] = motionEvent.getX(pointerId);
endYs[pointerId] = motionEvent.getY(pointerId);
// 在开始和结束坐标间画一条线
mCanvas1.drawLine(startXs[pointerId], startYs[pointerId], endXs[pointerId], endYs[pointerId], mPaint1);
//重新画十字架
mCanvas2.drawLine(0, endYs[pointerId], mTouchScreenIvWidth, endYs[pointerId], mPaint2);
mCanvas2.drawLine(endXs[pointerId], 0, endXs[pointerId], mTouchScreenIvHeight, mPaint2);
//设置显示坐标的数字变化并且背景颜色为白色
mTouchScreenTvDx.setText("X:" + Math.round(endXs[pointerId] * 10) / 10.0);
mTouchScreenTvDx.setBackgroundColor(Color.WHITE);
mTouchScreenTvDy.setText("Y:" + Math.round(endYs[pointerId] * 10) / 10.0);
mTouchScreenTvDy.setBackgroundColor(Color.WHITE);
//获取当前触摸事件的速度
mVelocityTracker.computeCurrentVelocity(10);
mTouchScreenTvXv.setText("Xv:" +
Math.round(mVelocityTracker.getXVelocity(0) * 1000) / 1000.0 + "");
mTouchScreenTvYv.setText("Yv:" +
Math.round(mVelocityTracker.getYVelocity(0) * 1000) / 1000.0 + "");
// 刷新开始坐标
startXs[pointerId] = (int) motionEvent.getX(pointerId);
startYs[pointerId] = (int) motionEvent.getY(pointerId);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.i(TAG, "11111:IllegalArgumentException ");
}
}
}
/**
* 计算并显示坐标偏移量,并且设置背景颜色
* @param motionEvent
*/
private void setDxDy(MotionEvent motionEvent) {
float dx = motionEvent.getX() - finalStartX;
float dy = motionEvent.getY() - finalStartY;
mTouchScreenTvDx.setText("dX:" + Math.round(dx * 10) / 10.0);
if (dx > 0.1 || dx < -0.1) {
mTouchScreenTvDx.setBackgroundColor(Color.RED);
} else {
mTouchScreenTvDx.setBackgroundColor(Color.WHITE);
}
mTouchScreenTvDy.setText("dY:" + Math.round(dy * 10) / 10.0);
if (dy > 0.1 || dy < -0.1) {
mTouchScreenTvDy.setBackgroundColor(Color.RED);
} else {
mTouchScreenTvDy.setBackgroundColor(Color.WHITE);
}
}
- ACTION_DOWN :当触摸到第一个点时,被触发
- ACTION_POINTER_DOWN:当控件上已经有点被触摸,再次有点被触摸时,触发该事件。
- ACTION_UP 和 ACTION_POINTER_UP 也是类似的,最后一个点抬起时才触发ACTION_UP。
- 但是ACTION_MOVE没有类似的方法,可以通过遍历触摸事件,获得每一个触摸事件。
- 在触摸时每一个触摸事件会被分配一个id,通过不同的id获取每一个触摸点的坐标。
遗留bug:每当有新的触摸事件时,以前的滑动轨迹会被清空。