美文网首页
Android 多点触控,绘制滑动轨迹和十字光标

Android 多点触控,绘制滑动轨迹和十字光标

作者: 3石 | 来源:发表于2017-03-08 17:00 被阅读0次

    这个测试项,要捕捉当前有几个触摸点,当前触摸点坐标,滑动事件在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上完成。

    • 接下来遇到了一些坑,都踩了一遍。

      1. 因为这个绘图是发生在一个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:每当有新的触摸事件时,以前的滑动轨迹会被清空。

    相关文章

      网友评论

          本文标题:Android 多点触控,绘制滑动轨迹和十字光标

          本文链接:https://www.haomeiwen.com/subject/zombgttx.html