【问题标题】:How to reduce sensitivity of android ScaleGestureDetector.SimpleOnScaleGestureListener如何降低 android ScaleGestureDetector.SimpleOnScaleGestureListener 的灵敏度
【发布时间】:2017-03-27 09:50:51
【问题描述】:

我有一个自定义的 SwipeRefreshLayout,里面有一个自定义的 GridView。我想根据捏/缩放手势更改该 GridView 的列号。我已经成功实施了。问题是,尺度太敏感了。

例如,我的列号为 3-5。缩放到 3 或 5 很容易,但要变成 4 则很难,因为缩放本身太敏感了。

这是我的自定义 SwipeRefreshLayout 类

/**
 * This class contains fix from http://stackoverflow.com/questions/23989910/horizontalscrollview-inside-swiperefreshlayout
 */
public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {

    private int mTouchSlop;
    private float mPrevX;
    private ScaleGestureDetector mScaleGestureDetector;
    private ScaleListener mScaleListener;

    public CustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    public void setScaleListener(Context context, ScaleListener scaleListener) {
        this.mScaleListener = scaleListener;
        mScaleGestureDetector = new ScaleGestureDetector(context, new MyOnScaleGestureListener());
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mScaleGestureDetector != null) {
            mScaleGestureDetector.onTouchEvent(event);
        }

        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_POINTER_DOWN:
                setEnabled(false);
                if (mScaleListener != null) {
                    mScaleListener.onTwoFingerStart();
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                setEnabled(true);
                mPrevX = MotionEvent.obtain(event).getX();
                if (mScaleListener != null) {
                    mScaleListener.onTwoFingerEnd();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mPrevX = MotionEvent.obtain(event).getX();
                break;

            case MotionEvent.ACTION_MOVE:
                final float eventX = event.getX();
                float xDiff = Math.abs(eventX - mPrevX);

                if (xDiff > mTouchSlop) {
                    return false;
                }
        }

        return super.onInterceptTouchEvent(event);
    }

    class MyOnScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        private static final String TAG = "MyOnScaleGestureListene";

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            if (mScaleListener != null) {
                // Too sensitive, must change to other approach
                float scaleFactor = detector.getScaleFactor();
                Log.d(TAG, "onScale: " + scaleFactor);
                if (scaleFactor > 1F) {
                    mScaleListener.onScaleUp(scaleFactor);
                } else if (scaleFactor < 1F) {
                    mScaleListener.onScaleDown(scaleFactor);
                } else {
                    // no scale
                }
            }
            return true;
        }

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            return true;
        }

        @Override
        public void onScaleEnd(ScaleGestureDetector detector) {
        }
    }

    public interface ScaleListener {
        void onTwoFingersStart();

        void onTwoFingersEnd();

        void onScaleUp(float scaleFactor);

        void onScaleDown(float scaleFactor);
    }
}

有什么办法可以降低 ScaleGestureDetector.SimpleOnScaleGestureListener 的灵敏度?如果没有办法,有什么办法可以解决吗?

这是一个显示问题的短视频 https://www.youtube.com/watch?v=0MItDNZ_o4c

【问题讨论】:

    标签: java android scale gesture gesture-recognition


    【解决方案1】:

    你应该有一个公差距离,这在 Android 世界中被称为“slop”。如果手势小于那个容差,你应该忽略它。

    private static final int SPAN_SLOP = 7;
    
    ...
    
    @Override
    public boolean onScale(@NonNull ScaleGestureDetector detector) {
        if (gestureTolerance(detector)) {
            // performing scaling
        }
        return true;
    }
    
    private boolean gestureTolerance(@NonNull ScaleGestureDetector detector) {
        final float spanDelta = Math.abs(detector.getCurrentSpan() - detector.getPreviousSpan());
        return spanDelta > SPAN_SLOP;
    }
    

    作为示例,您可以看到由@rallat 制作的展示开源应用程序。

    在这里您可以找到source codepresentation at GOTO Copenhagen 2016

    【讨论】:

    • 为什么 SPAN_SLOP 是 7?是像素吗?如果是这样,您认为将其转换为 dp 会更好吗?
    • 我试过了,不符合我的要求,因为如果我的手指太慢,spanDelta 总是会重置。如果我的手指太快,仍然很难得到 4 个列号。
    • 正如您从docs 看到的那样,它是以像素为单位的值。基本上,您应该测试并为您的用例找到合适的容差值。
    • 但是从您提供的代码中,我想到了使用detector.getCurrentSpan() 保存初始距离,并在每次调用onScale 时将其直接与detector.getCurrentSpan() 进行比较。
    【解决方案2】:

    我能够解决问题以满足我的要求。 @azizbekian 的回答并没有真正满足我的要求,因为spanDelta 总是在我的手指太慢而我的手指太快时重置,它可以缩放,但仍然很难得到 4 列。但是从他的代码示例中,我能够创建一个解决方法。

    我只需要使用detector.getCurrentSpan()保存初始比例距离(跨度)

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        mInitialDistance = detector.getCurrentSpan();
        return true;
    }
    

    然后每次调用onScale()时直接与detector.getCurrentSpan()比较。如果发生缩放,则使用mInitialDistance = detector.getCurrentSpan(); 重置初始距离。

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        if (gestureTolerance(detector)) {
            if (mScaleListener != null) {
                float scaleFactor = detector.getScaleFactor();
                if (scaleFactor > 1F) {
                    mScaleListener.onScaleUp(scaleFactor);
                    mInitialDistance = detector.getCurrentSpan();
                } else if (scaleFactor < 1F) {
                    mScaleListener.onScaleDown(scaleFactor);
                    mInitialDistance = detector.getCurrentSpan();
                }
            }
        }
        return true;
    }
    
    private boolean gestureTolerance(@NonNull ScaleGestureDetector detector) {
        final float currentDistance = detector.getCurrentSpan();
        final float distanceDelta = Math.abs(mInitialDistance - currentDistance);
        return distanceDelta > mScaleTriggerDistance;
    }
    

    这是我的自定义 SwipeRefreshLayout 的完整代码

    public class MyCustomSwipeRefreshLayout extends SwipeRefreshLayout {
    
        private static final String TAG = "OneTouchRefreshFreeSwip";
        private static final float DEFAULT_SCALE_TRIGGER_DISTANCE = 48;// in dp
        private int mTouchSlop;
        private float mPrevX;
    
        private ScaleGestureDetector mScaleGestureDetector;
        private ScaleListener mScaleListener;
    
        private float mScaleTriggerDistance;
        private float mInitialDistance;
    
        public MyCustomSwipeRefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }
    
        public void setScaleListener(Context context, ScaleListener scaleListener) {
            this.mScaleListener = scaleListener;
            mScaleGestureDetector = new ScaleGestureDetector(context, new MyOnScaleGestureListener());
            mScaleTriggerDistance = Util.dp2px(DEFAULT_SCALE_TRIGGER_DISTANCE, context);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            if (mScaleGestureDetector != null) {
                mScaleGestureDetector.onTouchEvent(event);
            }
    
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_POINTER_DOWN:
                    setEnabled(false);
                    if (mScaleListener != null) {
                        mScaleListener.onTwoFingersStart();
                    }
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    if (mScaleListener != null) {
                        mScaleListener.onTwoFingersEnd();
                    }
                    mPrevX = MotionEvent.obtain(event).getX();
                    setEnabled(true);
                    return true;
                case MotionEvent.ACTION_DOWN:
                    mPrevX = MotionEvent.obtain(event).getX();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    final float eventX = event.getX();
                    float xDiff = Math.abs(eventX - mPrevX);
    
                    if (xDiff > mTouchSlop) {
                        return false;
                    }
            }
    
            return super.onInterceptTouchEvent(event);
        }
    
        private boolean gestureTolerance(@NonNull ScaleGestureDetector detector) {
            final float currentDistance = detector.getCurrentSpan();
            final float distanceDelta = Math.abs(mInitialDistance - currentDistance);
            return distanceDelta > mScaleTriggerDistance;
        }
    
        class MyOnScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    
            @Override
            public boolean onScale(ScaleGestureDetector detector) {
                if (gestureTolerance(detector)) {
                    if (mScaleListener != null) {
                        float scaleFactor = detector.getScaleFactor();
                        if (scaleFactor > 1F) {
                            mScaleListener.onScaleUp(scaleFactor);
                            mInitialDistance = detector.getCurrentSpan();
                        } else if (scaleFactor < 1F) {
                            mScaleListener.onScaleDown(scaleFactor);
                            mInitialDistance = detector.getCurrentSpan();
                        }
                    }
                }
                return true;
            }
    
            @Override
            public boolean onScaleBegin(ScaleGestureDetector detector) {
                mInitialDistance = detector.getCurrentSpan();
                return true;
            }
    
            @Override
            public void onScaleEnd(ScaleGestureDetector detector) {
            }
        }
    
        public interface ScaleListener {
            void onTwoFingersStart();
    
            void onTwoFingersEnd();
    
            void onScaleUp(float scaleFactor);
    
            void onScaleDown(float scaleFactor);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-30
      • 2013-05-13
      • 2014-04-14
      • 1970-01-01
      • 1970-01-01
      • 2021-02-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多