Android 多點(diǎn)觸控,繪制滑動(dòng)軌跡和十字光標(biāo)

這個(gè)測(cè)試項(xiàng),要捕捉當(dāng)前有幾個(gè)觸摸點(diǎn),當(dāng)前觸摸點(diǎn)坐標(biāo),滑動(dòng)事件在x軸、y軸方向的速度等信息,在觸摸時(shí)跟隨觸摸點(diǎn)會(huì)出現(xiàn)十字光標(biāo),繪制出滑動(dòng)軌跡。

  • 首先繪制出暗色格子背景,采用了自定義View,較為簡(jiǎn)單,核心代碼如下:
     Paint paint;  //畫(huà)筆
    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));//設(shè)置畫(huà)筆顏色
        paint.setStrokeJoin(Paint.Join.ROUND);//設(shè)置畫(huà)筆圖形接觸時(shí)筆跡的形狀
        paint.setStrokeCap(Paint.Cap.ROUND);//設(shè)置畫(huà)筆離開(kāi)畫(huà)板時(shí)筆跡的形狀
        paint.setStrokeWidth(1);  //設(shè)置畫(huà)筆的寬度
    }

     /**
     * 這個(gè)方法可以獲得控件的寬高
     * @param canvas
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mWidth = w;
        mHeight = h;
    }
    /**
     * 繪制網(wǎng)格線
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.BLACK);
        int lineStart = 80;
        int space = lineStart;   //長(zhǎng)寬間隔
        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;
        }
    }
  • 接下來(lái),因?yàn)橐谶@個(gè)背景上畫(huà)圖,我在其上覆蓋一層透明ImageView,給該iv設(shè)置這個(gè)屬性:
    android:background="@android:color/transparent"
    接下來(lái)的繪制滑動(dòng)軌跡和十字光標(biāo)都在這個(gè)iv上完成。

  • 接下來(lái)遇到了一些坑,都踩了一遍。

    1. 因?yàn)檫@個(gè)繪圖是發(fā)生在一個(gè)Fragment里,我的繪圖界面要設(shè)置全屏,但是該Activity中的其他Fragment則不需要這個(gè)設(shè)置。于是就在這個(gè)Fragment中獲取到Window,然后設(shè)置全屏標(biāo)記,然后讓根視圖MATCH_PARENT。
 getActivity().getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);

      //mRootView是BaseFragment中設(shè)置的該Fragment的視圖
        this.mRootView.setLayoutParams(        
                new FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT));
  • 創(chuàng)建bitmap,設(shè)置bitmap的高寬時(shí),遇到了問(wèn)題。
    • 因?yàn)樵趏nCreateView中View.getWidth和View.getHeight無(wú)法獲得一個(gè)view的高度和寬度,這是因?yàn)閂iew組件布局要在onResume回調(diào)后完成。所以現(xiàn)在需要使用getViewTreeObserver().addOnGlobalLayoutListener()來(lái)獲得寬度或者高度。這是獲得一個(gè)view的寬度和高度的方法之一。

    • OnGlobalLayoutListener 是ViewTreeObserver的內(nèi)部類(lèi),當(dāng)一個(gè)視圖樹(shù)的布局發(fā)生改變時(shí),可以被ViewTreeObserver監(jiān)聽(tīng)到,這是一個(gè)注冊(cè)監(jiān)聽(tīng)視圖樹(shù)的觀察者(observer),在視圖樹(shù)的全局事件改變時(shí)得到通知。ViewTreeObserver不能直接實(shí)例化,而是通過(guò)getViewTreeObserver()獲得。

 mTouchScreenIv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

                mTouchScreenIvWidth = mTouchScreenIv.getWidth();
                mTouchScreenIvHeight = mTouchScreenIv.getHeight();
                // 創(chuàng)建空白圖片
                mBitmap1 = Bitmap.createBitmap(mTouchScreenIvWidth, mTouchScreenIvHeight, Bitmap.Config.ARGB_8888);
                mBitmap2 = Bitmap.createBitmap(mTouchScreenIvWidth, mTouchScreenIvHeight, Bitmap.Config.ARGB_8888);
                // 創(chuàng)建兩張畫(huà)布
                mCanvas1 = new Canvas(mBitmap1); //底層畫(huà)軌跡的畫(huà)布
                mCanvas2 = new Canvas(mBitmap2); //上面一層畫(huà)十字架的畫(huà)布
                // 創(chuàng)建畫(huà)筆
                mPaint1 = new Paint();      //畫(huà)軌跡的畫(huà)筆
                mPaint2 = new Paint();      //畫(huà)十字架的畫(huà)筆
                // 畫(huà)筆顏色為藍(lán)色
                mPaint1.setColor(getResources().getColor(R.color.lightBlue));
                mPaint2.setColor(getResources().getColor(R.color.lightBlue));
                // 寬度1個(gè)像素
                mPaint1.setStrokeWidth(1);
                mPaint2.setStrokeWidth(1);
                // 先將白色背景畫(huà)上
                mCanvas1.drawBitmap(mBitmap1, new Matrix(), mPaint1);
                mCanvas2.drawBitmap(mBitmap2, new Matrix(), mPaint2);

                mBitmap3 = mergeBitmap(mBitmap1, mBitmap2);//將兩張bitmap圖合為一張

                mTouchScreenIv.setImageBitmap(mBitmap3);

                //用完要解除監(jiān)聽(tīng)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    mTouchScreenIv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            }
        });
  • 把兩個(gè)bitmap合成一個(gè)bitmap
 /**
     * 把兩個(gè)位圖覆蓋合成為一個(gè)位圖,以底層位圖的長(zhǎng)寬為基準(zhǔn)
     * @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控件設(shè)置觸摸事件。因?yàn)槭嵌帱c(diǎn)觸摸事件,所以記錄down的起始點(diǎn)和move時(shí)的終止點(diǎn)都需要使用float數(shù)組。設(shè)置四個(gè)大小為10的數(shù)組。
  • 添加 觸摸事件的速度檢測(cè)器。
  • 單點(diǎn)觸摸事件ACTION_DOWN ,多點(diǎn)觸摸事件ACTION_POINTER_DOWN,使用case穿透將兩種事件一起監(jiān)聽(tīng),遍歷觸摸事件,獲得坐標(biāo),進(jìn)行操作。
@Override
    protected void setListener() {
        mTouchScreenCheck.setOnTouchListener(new View.OnTouchListener() {
            
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {

                //當(dāng)前DOWN或者UP的是手指的index
                int curPointerIndex = motionEvent.getActionIndex();

                //通過(guò)index獲得當(dāng)前手指的id
                int curPointerId = motionEvent.getPointerId(curPointerIndex);

                //添加事件的速度計(jì)算器
                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));

                        //設(shè)置當(dāng)前有幾個(gè)觸摸點(diǎn)
                        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:

                        //獲取當(dāng)前觸摸事件的個(gè)數(shù)
                        if (motionEvent.getPointerCount() > pointerCount) {
                            pointerCount = motionEvent.getPointerCount();
                        }

                        mTouchScreenTvP.setText("P:" + motionEvent.getPointerCount() + "/" + pointerCount);

                        //清除十字架
                        mCanvas2.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        
                        //在移動(dòng)時(shí)的操作
                        MovePoint(motionEvent);

                        mTouchScreenIv.setImageBitmap(mergeBitmap(mBitmap1, mBitmap2));

                        break;

                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_POINTER_UP:

                        //計(jì)算并顯示坐標(biāo)偏移量,并且設(shè)置背景顏色
                        setDxDy(motionEvent);

                        //清除十字架
                        mCanvas2.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

                        //清除這個(gè)觸摸事件的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 {

                //獲取觸摸點(diǎn)的X,y坐標(biāo)
                startXs[pointerId] = motionEvent.getX(pointerId);
                startYs[pointerId] = motionEvent.getY(pointerId);

                finalStartX = startXs[pointerId];
                finalStartY = startYs[pointerId];

                //設(shè)置上面的字變化并且背景顏色為白色
                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) {
                // 此處捕捉系統(tǒng)bug,以防程序停止
                e.printStackTrace();
            }
            mTouchScreenTvP.setText("P:" + pointerCount + "/" + pointerCount);
            // 在開(kāi)始和結(jié)束坐標(biāo)間畫(huà)一個(gè)點(diǎn)
            mCanvas1.drawPoint(startXs[pointerId], startYs[pointerId], mPaint1);

            //畫(huà)十字架
            mCanvas2.drawLine(0, startYs[pointerId], mTouchScreenIvWidth, startYs[pointerId], mPaint2);
            mCanvas2.drawLine(startXs[pointerId], 0, startXs[pointerId], mTouchScreenIvHeight, mPaint2);

        }
    }

    /**
     * 在移動(dòng)時(shí)對(duì)點(diǎn)的操作
     * @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 {
                // 獲取手移動(dòng)后的坐標(biāo)
                endXs[pointerId] = motionEvent.getX(pointerId);
                endYs[pointerId] = motionEvent.getY(pointerId);

                // 在開(kāi)始和結(jié)束坐標(biāo)間畫(huà)一條線
                mCanvas1.drawLine(startXs[pointerId], startYs[pointerId], endXs[pointerId], endYs[pointerId], mPaint1);

                //重新畫(huà)十字架
                mCanvas2.drawLine(0, endYs[pointerId], mTouchScreenIvWidth, endYs[pointerId], mPaint2);
                mCanvas2.drawLine(endXs[pointerId], 0, endXs[pointerId], mTouchScreenIvHeight, mPaint2);

                //設(shè)置顯示坐標(biāo)的數(shù)字變化并且背景顏色為白色
                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);

                //獲取當(dāng)前觸摸事件的速度
                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 + "");

                // 刷新開(kāi)始坐標(biāo)
                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 ");
            }
        }
    }
    /**
     * 計(jì)算并顯示坐標(biāo)偏移量,并且設(shè)置背景顏色
     * @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 :當(dāng)觸摸到第一個(gè)點(diǎn)時(shí),被觸發(fā)
  • ACTION_POINTER_DOWN:當(dāng)控件上已經(jīng)有點(diǎn)被觸摸,再次有點(diǎn)被觸摸時(shí),觸發(fā)該事件。
  • ACTION_UP 和 ACTION_POINTER_UP 也是類(lèi)似的,最后一個(gè)點(diǎn)抬起時(shí)才觸發(fā)ACTION_UP。
  • 但是ACTION_MOVE沒(méi)有類(lèi)似的方法,可以通過(guò)遍歷觸摸事件,獲得每一個(gè)觸摸事件。
  • 在觸摸時(shí)每一個(gè)觸摸事件會(huì)被分配一個(gè)id,通過(guò)不同的id獲取每一個(gè)觸摸點(diǎn)的坐標(biāo)。

遺留bug:每當(dāng)有新的觸摸事件時(shí),以前的滑動(dòng)軌跡會(huì)被清空。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容