如何将长按侦听器添加到多点触控手势检测库

时间:2017-03-26 17:41:59

标签: android multi-touch ontouchlistener onlongclicklistener android-event

示例应用程序(如下所示,我的添加内容)包含在Almeros library中。它允许用户执行多点触摸手势,以在不变的背景上移动,调整大小,旋转和推动对象:

screen shots of sample application

我想实现一个长按一下监听器,但是显而易见(添加implements View.OnLongClickListener并覆盖public boolean onLongClick(View v)不起作用(永远不会调用该方法)。

我试图通过长按来将地球改为篮球(这两个图像已经包含在github上的示例中)。

我的问题:如何在保留库提供的现有手势功能的同时使用长按事件?

这是the sample code,其中包含少量添加内容(已使用20160326标记)。

/**
 * Test activity for testing the different GestureDetectors.
 * 
 * @author Almer Thie (code.almeros.com)
 * Copyright (c) 2013, Almer Thie (code.almeros.com)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 
 *  in the documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 */
// 20170326 - Commented 1, Added 1
//public class TouchActivity extends Activity implements OnTouchListener {
public class TouchActivity extends AppCompatActivity implements View.OnTouchListener, View.OnLongClickListener {
    private Matrix mMatrix = new Matrix();
    private float mScaleFactor = .4f;
    private float mRotationDegrees = 0.f;
    private float mFocusX = 0.f;
    private float mFocusY = 0.f;  
    private int mAlpha = 255;
    private int mImageHeight, mImageWidth;

    private ScaleGestureDetector mScaleDetector;
    private RotateGestureDetector mRotateDetector;
    private MoveGestureDetector mMoveDetector;
    private ShoveGestureDetector mShoveDetector; 

    @SuppressWarnings("deprecation")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Determine the center of the screen to center 'earth'
        Display display = getWindowManager().getDefaultDisplay();
        mFocusX = display.getWidth()/2f;
        mFocusY = display.getHeight()/2f;

        // Set this class as touchListener to the ImageView
        ImageView view = (ImageView) findViewById(R.id.imageToMove);
        view.setOnTouchListener(this);
        //20160326 - Added 1
        view.setOnLongClickListener(this);

        // Determine dimensions of 'earth' image
        Drawable d      = this.getResources().getDrawable(R.drawable.earth);
        mImageHeight    = d.getIntrinsicHeight();
        mImageWidth     = d.getIntrinsicWidth();

        // View is scaled and translated by matrix, so scale and translate initially
        float scaledImageCenterX = (mImageWidth*mScaleFactor)/2; 
        float scaledImageCenterY = (mImageHeight*mScaleFactor)/2;

        mMatrix.postScale(mScaleFactor, mScaleFactor);
        mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
        view.setImageMatrix(mMatrix);

        // Setup Gesture Detectors
        mScaleDetector  = new ScaleGestureDetector(getApplicationContext(), new ScaleListener());
        mRotateDetector = new RotateGestureDetector(getApplicationContext(), new RotateListener());
        mMoveDetector   = new MoveGestureDetector(getApplicationContext(), new MoveListener());
        mShoveDetector  = new ShoveGestureDetector(getApplicationContext(), new ShoveListener());
    }

    @SuppressWarnings("deprecation")
    public boolean onTouch(View v, MotionEvent event) {
        mScaleDetector.onTouchEvent(event);
        mRotateDetector.onTouchEvent(event);
        mMoveDetector.onTouchEvent(event);
        mShoveDetector.onTouchEvent(event);

        float scaledImageCenterX = (mImageWidth*mScaleFactor)/2;
        float scaledImageCenterY = (mImageHeight*mScaleFactor)/2;

        mMatrix.reset();
        mMatrix.postScale(mScaleFactor, mScaleFactor);
        mMatrix.postRotate(mRotationDegrees,  scaledImageCenterX, scaledImageCenterY);
        mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);

        ImageView view = (ImageView) v;
        view.setImageMatrix(mMatrix);
        view.setAlpha(mAlpha);

        return true; // indicate event was handled
    }

    // 20170326 - Added method
    @Override
    public boolean onLongClick(View v) {
        Log.v("20170326", "onLongClick called <<<<<<<<<<<<<<<<<<<<<<<<<<<");
        ImageView view = (ImageView) findViewById(R.id.imageToMove);
        view.setImageDrawable(this.getResources().getDrawable(R.drawable.basketball));
        return true;
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor(); // scale change since previous event

            // Don't let the object get too small or too large.
            mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f)); 

            return true;
        }
    }

    private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
        @Override
        public boolean onRotate(RotateGestureDetector detector) {
            mRotationDegrees -= detector.getRotationDegreesDelta();
            return true;
        }
    }   

    private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
        @Override
        public boolean onMove(MoveGestureDetector detector) {
            PointF d = detector.getFocusDelta();
            mFocusX += d.x;
            mFocusY += d.y;     

            // mFocusX = detector.getFocusX();
            // mFocusY = detector.getFocusY();
            return true;
        }
    }       

    private class ShoveListener extends ShoveGestureDetector.SimpleOnShoveGestureListener {
        @Override
        public boolean onShove(ShoveGestureDetector detector) {
            mAlpha += detector.getShovePixelsDelta();
            if (mAlpha > 255)
                mAlpha = 255;
            else if (mAlpha < 0)
                mAlpha = 0;

            return true;
        }
    }   

}

3 个答案:

答案 0 :(得分:1)

如用户mudit pant所示,同时拥有TouchClick听众是个问题。因此,方法应该是使用原始sample code重新开始,并以与实现其他侦听器相同的方式实现LongPressListener私有类:

// 20160327 - Added class
private class LongPressListener extends GestureDetector.SimpleOnGestureListener {
    @Override
    public void onLongPress(MotionEvent e) {
        super.onLongPress(e);
        ImageView mView = (ImageView) findViewById(R.id.imageView);
        mView.setImageDrawable(getApplicationContext().getResources().getDrawable(R.drawable.basketball));
    }
}

然后,以同样的方式将其他检测器集成到onTouch()方法中,添加新的监听器:

public boolean onTouch(View v, MotionEvent event) {
    mScaleDetector.onTouchEvent(event);
    mRotateDetector.onTouchEvent(event);
    mShoveDetector.onTouchEvent(event);
    mMoveDetector.onTouchEvent(event);

    // 20160327 - Added 1
    mLongPressDetector.onTouchEvent(event);
    .
    .
    .
}

同样地,在onCreate()中创建另一个探测器:

public void onCreate(Bundle savedInstanceState) {
    .
    .
    .

    // Setup Gesture Detectors
    .
    .
    .
    // 20170327 - Added 1
    mLongPressDetector = new GestureDetector(getApplicationContext(), new LongPressListener());
    .
    .
    .
}

长按,图像变为篮球:

image changed to basket ball after long press

答案 1 :(得分:0)

问题是你有Touch和clicklisteners。触摸设备后立即调用触摸侦听器,并且由于您从那里返回true,因此此事件不会传播到单击侦听器。因此您的代码没有执行。处理此问题的最佳方法是在触摸时启动计时器以模仿长按行为。您应该在补足/移动事件中取消此计时器。如果您的计时器到期,那么长按事件就会起作用。希望这可以帮助。 :)

答案 2 :(得分:0)

answer here会触发不需要的长按事件。换句话说,如果用户使用的不仅仅是GestureDetector LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(),则会触发长按事件,即使用户可能只是试图移动或旋转或调整大小。快速移动工作正常,当他们试图获得精确时,长按事件会中断。我的第一个想法是增加超时,但它似乎是所有视图的系统范围,设置为500毫秒,并且不可自定义。

因此,非常简单且不需要或不使用GestureDetector备用解决方案是使用Handler.postDelayed()Runnable

public boolean onTouch(View v, MotionEvent event) {
    mScaleDetector.onTouchEvent(event);
    mRotateDetector.onTouchEvent(event);
    mMoveDetector.onTouchEvent(event);
    mShoveDetector.onTouchEvent(event);

    // 20170329 - removed 1, added 'if' block
    //mLongPressDetector.onTouchEvent(event);
    if(event.getAction() == MotionEvent.ACTION_DOWN) {
        // Start a timer on each ACTION_DOWN
        handler.postDelayed(mLongPressed, 1000);
    }
    .
    .
    .
}

在每个sample code onTouch()方法中,计时器都会被删除。在除onMove()之外的所有情况下,无条件地删除回调。但是在MoveListener的情况下,即使零偏移也会触发它。所以我只会在这里粘贴更改后的MoveListener,但是你需要在所有的监听器中放置handler.removeCallbacks(mLongPressed);

private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
    @Override
    public boolean onMove(MoveGestureDetector detector) {
        PointF d = detector.getFocusDelta();
        mFocusX += d.x;
        mFocusY += d.y;

        // 20170329 - Added 'if' block - remove listener if there is non-zero offset
        if ((d.x + d.y) != 0f) {
            handler.removeCallbacks(mLongPressed);
            Log.v(TAG, "onMove removed " + d.x + ", " + d.y);
        }
        return true;
    }
}       

最后,在您的活动中,您实例化Handler并实施Runnable

// 20170329 - Added Handler and Runnable
final Handler handler = new Handler();
Runnable mLongPressed = new Runnable() {
    public void run() {
        Log.v(TAG, "mLongPressed Runnable triggered.");
        ImageView mView = (ImageView) findViewById(R.id.imageView);
        mView.setImageDrawable(getApplicationContext().getResources().getDrawable(R.drawable.basketball));
    }
};

此解决方案来自this MSquare answer