Android官方开发文档Training系列课程中文版:手势处理之拖拽或缩放

简介: 原文地址:https://developer.android.com/training/gestures/scale.html这节课主要学习如何使用触摸手势来拖动、放大屏幕上的对象。

原文地址:https://developer.android.com/training/gestures/scale.html

这节课主要学习如何使用触摸手势来拖动、放大屏幕上的对象。

拖动对象

如果你的重点在Android 3.0以上的版本,那么你可以使用内置的拖拽事件监听器View.OnDragListener

触摸手势最常见的操作就是使用它来拖动屏幕上的对象。下面的代码允许用户拖动屏幕上的图像。要注意以下几点:

  • 在拖动操作中,APP会一直保持手指拖动的轨迹,就算是另一只手指触到屏幕也是。举个例子,想象一根手指在拖动着一张图像,这时用户将第二根手指放置到屏幕上,如果APP只是追踪单根手指的轨迹,那么它会将第二根手指作为默认位置,并会将图像移动到这个位置。
  • 为了防止这样的事件发生,APP需要区分第一根手指与其它手指。为此,需要追踪 ACTION_POINTER_DOWNACTION_POINTER_UPACTION_POINTER_DOWNACTION_POINTER_UP在第二根手指落下或抬起的时候由onTouchEvent()方法传回。
  • ACTION_POINTER_UP的情况下,示例提取了这个事件的索引,并确保当前活动的指针不是那个已经不在屏幕上的指针。如果是那个指针的话,那么APP会选择一个不同的指针使其活动并保存它的X及Y的位置。一旦这个值被保存下来,那么APP将会使用正确指针的数据一直计算剩余移动的距离。

下面的代码允许用户在屏幕上拖动对象。它记录了当前活动指针的初始位置,计算了它所位移的距离,并将对象移动到新的位置上。

这里要注意,代码段使用了getActionMasked()方法。你应该一直使用这个方法来接收MotionEvent对象的活动。与getAction()方法不同,getActionMasked()工作于多点触控模式下。它会返回被执行的掩饰活动,不包括指针的索引比特。

// The ‘active pointer’ is the one currently moving our object.
private int mActivePointerId = INVALID_POINTER_ID;
@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);

    final int action = MotionEventCompat.getActionMasked(ev); 

    switch (action) { 
    case MotionEvent.ACTION_DOWN: {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev); 
        final float x = MotionEventCompat.getX(ev, pointerIndex); 
        final float y = MotionEventCompat.getY(ev, pointerIndex); 

        // Remember where we started (for dragging)
        mLastTouchX = x;
        mLastTouchY = y;
        // Save the ID of this pointer (for dragging)
        mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
        break;
    }

    case MotionEvent.ACTION_MOVE: {
        // Find the index of the active pointer and fetch its position
        final int pointerIndex = 
                MotionEventCompat.findPointerIndex(ev, mActivePointerId);  

        final float x = MotionEventCompat.getX(ev, pointerIndex);
        final float y = MotionEventCompat.getY(ev, pointerIndex);

        // Calculate the distance moved
        final float dx = x - mLastTouchX;
        final float dy = y - mLastTouchY;
        mPosX += dx;
        mPosY += dy;
        invalidate();
        // Remember this touch position for the next move event
        mLastTouchX = x;
        mLastTouchY = y;
        break;
    }

    case MotionEvent.ACTION_UP: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_CANCEL: {
        mActivePointerId = INVALID_POINTER_ID;
        break;
    }

    case MotionEvent.ACTION_POINTER_UP: {

        final int pointerIndex = MotionEventCompat.getActionIndex(ev); 
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 
        if (pointerId == mActivePointerId) {
            // This was our active pointer going up. Choose a new
            // active pointer and adjust accordingly.
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastTouchX = MotionEventCompat.getX(ev, newPointerIndex); 
            mLastTouchY = MotionEventCompat.getY(ev, newPointerIndex); 
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
        break;
    }
    }       
    return true;
}

平移

上面的部分展示了如何在屏幕上拖动对象。另一个通用的场景就是平移了,平移的意思是:用户的拖动动作引起的x及y轴方向上的滚动。上面的代码直接将MotionEvent拦截实现拖动。这部分的代码将会采用另一种更具有优势的方法,以便支持通用手势。它重写了GestureDetector.SimpleOnGestureListeneronScroll()方法。

只有用户在使用手指移动内容时,onScroll()才会被调用。onScroll()只有在手指按下的时候才会调用,一旦手指离开屏幕,那么平移手势也随之终止。

下面是onScroll()的使用摘要:

// The current viewport. This rectangle represents the currently visible
// chart domain and range.
private RectF mCurrentViewport =
        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);

// The current destination rectangle (in pixel coordinates) into which the
// chart data should be drawn.
private Rect mContentRect;

private final GestureDetector.SimpleOnGestureListener mGestureListener
            = new GestureDetector.SimpleOnGestureListener() {
...

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
            float distanceX, float distanceY) {
    // Scrolling uses math based on the viewport (as opposed to math using pixels).

    // Pixel offset is the offset in screen pixels, while viewport offset is the
    // offset within the current viewport.
    float viewportOffsetX = distanceX * mCurrentViewport.width()
            / mContentRect.width();
    float viewportOffsetY = -distanceY * mCurrentViewport.height()
            / mContentRect.height();
    ...
    // Updates the viewport, refreshes the display.
    setViewportBottomLeft(
            mCurrentViewport.left + viewportOffsetX,
            mCurrentViewport.bottom + viewportOffsetY);
    ...
    return true;
}

下面是setViewportBottomLeft()方法的实现,它主要实现了移动内容的逻辑:

/**
 * Sets the current viewport (defined by mCurrentViewport) to the given
 * X and Y positions. Note that the Y value represents the topmost pixel position,
 * and thus the bottom of the mCurrentViewport rectangle.
 */
private void setViewportBottomLeft(float x, float y) {
    /*
     * Constrains within the scroll range. The scroll range is simply the viewport
     * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the
     * extremes were 0 and 10, and the viewport size was 2, the scroll range would
     * be 0 to 8.
     */

    float curWidth = mCurrentViewport.width();
    float curHeight = mCurrentViewport.height();
    x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
    y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));

    mCurrentViewport.set(x, y - curHeight, x + curWidth, y);

    // Invalidates the View to update the display.
    ViewCompat.postInvalidateOnAnimation(this);
}

缩放

Detecting Common Gestures中,我们讨论到GestureDetector可以帮助我们来检测比如滑动、滚动、长按等手势。而对于缩放,Android提供了ScaleGestureDetector. GestureDetector 以及 ScaleGestureDetector

为了可以反馈检测到的手势事件,手势探测器使用了监听器对象ScaleGestureDetector.OnScaleGestureListener。如果你只关心部分手势的话,Android还提供了ScaleGestureDetector.SimpleOnScaleGestureListener,你可以通过重写它的方法来使用。

缩放基础示例

下面的代码是缩放所需要的基础:

private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;

public MyCustomView(Context mContext){
    ...
    // View code goes here
    ...
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);
    return true;
}

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.scale(mScaleFactor, mScaleFactor);
    ...
    // onDraw() code goes here
    ...
    canvas.restore();
}

private class ScaleListener
        extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        mScaleFactor *= detector.getScaleFactor();

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

        invalidate();
        return true;
    }
}

稍微复杂点的示例

下面是一个稍微复杂一点的示例,它摘自与这节课所提供的示例InteractiveChart(PS:示例工程请参见原网页)。InteractiveChart同时支持平移、缩放,它使用了ScaleGestureDetector的“平移”(getCurrentSpanX/Y)及“焦点” (getFocusX/Y)特性:

@Override
private RectF mCurrentViewport =
        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
private Rect mContentRect;
private ScaleGestureDetector mScaleGestureDetector;
...
public boolean onTouchEvent(MotionEvent event) {
    boolean retVal = mScaleGestureDetector.onTouchEvent(event);
    retVal = mGestureDetector.onTouchEvent(event) || retVal;
    return retVal || super.onTouchEvent(event);
}

/**
 * The scale listener, used for handling multi-finger scale gestures.
 */
private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
        = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    /**
     * This is the active focal point in terms of the viewport. Could be a local
     * variable but kept here to minimize per-frame allocations.
     */
    private PointF viewportFocus = new PointF();
    private float lastSpanX;
    private float lastSpanY;

    // Detects that new pointers are going down.
    @Override
    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
        lastSpanX = ScaleGestureDetectorCompat.
                getCurrentSpanX(scaleGestureDetector);
        lastSpanY = ScaleGestureDetectorCompat.
                getCurrentSpanY(scaleGestureDetector);
        return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {

        float spanX = ScaleGestureDetectorCompat.
                getCurrentSpanX(scaleGestureDetector);
        float spanY = ScaleGestureDetectorCompat.
                getCurrentSpanY(scaleGestureDetector);

        float newWidth = lastSpanX / spanX * mCurrentViewport.width();
        float newHeight = lastSpanY / spanY * mCurrentViewport.height();

        float focusX = scaleGestureDetector.getFocusX();
        float focusY = scaleGestureDetector.getFocusY();
        // Makes sure that the chart point is within the chart region.
        // See the sample for the implementation of hitTest().
        hitTest(scaleGestureDetector.getFocusX(),
                scaleGestureDetector.getFocusY(),
                viewportFocus);

        mCurrentViewport.set(
                viewportFocus.x
                        - newWidth * (focusX - mContentRect.left)
                        / mContentRect.width(),
                viewportFocus.y
                        - newHeight * (mContentRect.bottom - focusY)
                        / mContentRect.height(),
                0,
                0);
        mCurrentViewport.right = mCurrentViewport.left + newWidth;
        mCurrentViewport.bottom = mCurrentViewport.top + newHeight;
        ...
        // Invalidates the View to update the display.
        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);

        lastSpanX = spanX;
        lastSpanY = spanY;
        return true;
    }
};
目录
相关文章
|
12天前
|
Android开发 Kotlin
kotlin开发安卓app,如何让布局自适应系统传统导航和全面屏导航
使用`navigationBarsPadding()`修饰符实现界面自适应,自动处理底部导航栏的内边距,再加上`.padding(bottom = 10.dp)`设定内容与屏幕底部的距离,以完成全面的布局适配。示例代码采用Kotlin。
57 15
|
4天前
|
前端开发 Android开发 iOS开发
探索安卓与iOS开发的差异性与互补性
在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统各据一方,引领着市场潮流。它们在技术架构、开发环境及用户群体等方面展现出独特的差异性,同时也存在着潜在的互补性。本文将深入剖析这两种平台的开发细节,从不同角度揭示其各自优势及相互之间的协同潜力,为开发者提供全面而深刻的视角。
10 2
|
10天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异性与互操作性
【7月更文挑战第17天】在移动应用开发的广阔天地中,安卓和iOS这两大操作系统如同双子星座般璀璨夺目。它们各自拥有独特的开发环境、编程语言和用户群体,为开发者提供了不同的挑战和机遇。本文将从多个维度深入剖析安卓与iOS开发的差异性,并探讨它们之间的互操作性如何实现,以期为开发者们提供一份实用的指南。
24 7
|
8天前
|
Java Android开发 Swift
探索iOS与安卓开发的差异与挑战
本文深入探讨了iOS和安卓两大移动操作系统在应用开发领域的不同点及其所面临的挑战。通过对开发环境、编程语言、用户界面设计、性能优化及市场策略的比较分析,揭示了各自平台的独特性以及开发者需要克服的技术与市场障碍。 【7月更文挑战第19天】
|
8天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的对比分析
【7月更文挑战第19天】在移动开发的广阔天地中,安卓与iOS两大阵营各据一方,它们在开发环境、用户界面设计、性能优化等方面展现出独特的魅力与挑战。本文旨在深入探讨这两个平台在技术开发和用户体验上的根本差异,并分析这些差异如何影响开发者的策略和最终用户的选择。通过比较两者的编程语言、工具、框架以及设计理念,我们将揭示各自平台的优势与局限,为开发者提供实用的参考,并为消费者呈现一个更加清晰的平台选择视角。
|
13天前
|
存储 API Android开发
kotlin开发安卓app,使用webivew 触发 onShowFileChooser, 但只能触发一次,第二次无法触发,是怎么回事。 如何解决
在Android WebView开发中,`onShowFileChooser`方法用于开启文件选择。当用户只能选择一次文件可能是因为未正确处理选择回调。解决此问题需确保:1) 实现`WebChromeClient`并覆写`onShowFileChooser`;2) 用户选择文件后调用`ValueCallback.onReceiveValue`传递URI;3) 传递结果后将`ValueCallback`设为`null`以允许再次选择。下面是一个Kotlin示例,展示如何处理文件选择和结果回调。别忘了在Android 6.0+动态请求存储权限,以及在Android 10+处理分区存储。
|
9天前
|
安全 Java Android开发
探索安卓与iOS开发的差异:构建未来应用的关键考量
【7月更文挑战第18天】在移动应用开发的广阔天地中,安卓和iOS两大平台各领风骚。本文将深入探讨这两个平台在开发过程中的主要差异,包括编程语言、用户界面设计、性能优化、安全性以及市场策略等方面。通过比较分析,旨在为开发者提供决策支持,帮助他们选择最适合自己项目需求的平台,同时考虑到用户体验和市场需求的变化,为未来的应用开发指明方向。
|
10天前
|
监控 开发工具 Android开发
探索安卓与iOS开发的差异:平台特性、工具和市场趋势
在移动应用开发的广阔舞台上,安卓与iOS两大操作系统扮演着主角。它们各自拥有独特的平台特性、开发工具和市场定位,这些差异深刻影响着开发者的决策和产品的最终形态。本文将深入分析这两大平台的关键技术差异,探讨各自的开发环境和工具集,以及它们在市场上的表现和未来的趋势,为开发者提供一个全面的视角,帮助他们在这两个平台上做出更明智的开发选择。
|
7天前
|
开发工具 Android开发 Swift
探索Android与iOS开发的差异与挑战
【7月更文挑战第20天】在移动应用开发的广阔天地中,Android和iOS两大平台如同双子星座,各自闪耀着独特的光芒。本文将深入探讨这两个平台在开发过程中的主要差异,以及开发者面临的技术挑战。我们将从开发环境、编程语言、用户界面设计、性能优化、安全性考量等多个维度展开讨论,旨在为那些即将踏入或已在这片星空下航行的开发者提供一盏明灯。
|
8天前
|
Java Android开发 iOS开发
探索安卓与iOS开发的差异:平台特性与用户体验的对比分析
在移动应用开发的广阔天地中,安卓与iOS两大阵营各自占据着半壁江山。本文将深入探讨这两个平台在开发环境、编程语言、用户界面设计以及性能优化方面的差异,并分析这些差异如何影响最终的用户体验。通过数据支持的案例分析和最新的市场研究,我们将揭示开发者如何在这两个不同的生态系统中做出战略决策,以及这些决策对应用成功的潜在影响。

推荐镜像

更多