ردیابی حرکات لمسی و اشاره گر

روش نوشتن را امتحان کنید
Jetpack Compose ابزار رابط کاربری پیشنهادی برای اندروید است. یاد بگیرید که چگونه از لمس و ورودی در Compose استفاده کنید.

این درس نحوه ردیابی حرکت در رویدادهای لمسی را شرح می‌دهد.

هر زمان که موقعیت، فشار یا اندازه تماس لمسی فعلی تغییر کند، یک onTouchEvent() جدید با یک رویداد ACTION_MOVE فعال می‌شود. همانطور که در بخش تشخیص حرکات رایج توضیح داده شد، همه این رویدادها در پارامتر MotionEvent از onTouchEvent() ثبت می‌شوند.

از آنجا که لمس مبتنی بر انگشت همیشه دقیق‌ترین شکل تعامل نیست، تشخیص رویدادهای لمسی اغلب بیشتر مبتنی بر حرکت است تا تماس ساده. برای کمک به برنامه‌ها در تشخیص حرکات مبتنی بر حرکت (مانند کشیدن انگشت) و حرکات غیر حرکتی (مانند یک ضربه)، اندروید مفهوم touch slop را در نظر گرفته است. touch slop به فاصله‌ای بر حسب پیکسل اشاره دارد که لمس کاربر می‌تواند قبل از اینکه حرکت به عنوان یک حرکت مبتنی بر حرکت تفسیر شود، جابجا شود. برای اطلاعات بیشتر در مورد این موضوع، به مدیریت رویدادهای لمسی در یک ViewGroup مراجعه کنید.

بسته به نیازهای برنامه شما، روش‌های مختلفی برای ردیابی حرکت در یک ژست وجود دارد. در زیر نمونه‌هایی از آنها آمده است:

  • موقعیت شروع و پایان یک اشاره‌گر، مانند حرکت یک شیء روی صفحه از نقطه A به نقطه B.
  • جهتی که اشاره‌گر در آن حرکت می‌کند، همانطور که توسط مختصات X و Y تعیین می‌شود.
  • تاریخچه. شما می‌توانید اندازه تاریخچه یک حرکت را با فراخوانی متد getHistorySize() از MotionEvent پیدا کنید. سپس می‌توانید موقعیت‌ها، اندازه‌ها، زمان و فشارهای هر یک از رویدادهای تاریخی را با استفاده از متدهای getHistorical <Value> رویداد حرکت به دست آورید. تاریخچه هنگام رندر کردن رد انگشت کاربر، مانند ترسیم لمسی، مفید است. برای جزئیات بیشتر به مرجع MotionEvent مراجعه کنید.
  • سرعت اشاره‌گر هنگام حرکت در صفحه لمسی.

به منابع مرتبط زیر مراجعه کنید:

سرعت آهنگ

شما می‌توانید یک ژست مبتنی بر حرکت داشته باشید که بر اساس مسافت یا جهت حرکت اشاره‌گر باشد. با این حال، سرعت اغلب یک عامل تعیین‌کننده در ردیابی ویژگی‌های یک ژست یا تصمیم‌گیری در مورد وقوع یا عدم وقوع ژست است. برای آسان‌تر کردن محاسبه سرعت، اندروید کلاس VelocityTracker را ارائه می‌دهد. VelocityTracker به شما کمک می‌کند تا سرعت رویدادهای لمسی را ردیابی کنید. این برای ژست‌هایی مفید است که در آن‌ها سرعت بخشی از معیارهای ژست است، مانند پرتاب کردن.

در اینجا مثالی آورده شده است که هدف متدهای موجود در API VelocityTracker را نشان می‌دهد:

کاتلین

private const val DEBUG_TAG = "Velocity"

class MainActivity : Activity() {
    private var mVelocityTracker: VelocityTracker? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                // Reset the velocity tracker back to its initial state.
                mVelocityTracker?.clear()
                // If necessary, retrieve a new VelocityTracker object to watch
                // the velocity of a motion.
                mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                // Add a user's movement to the tracker.
                mVelocityTracker?.addMovement(event)
            }
            MotionEvent.ACTION_MOVE -> {
                mVelocityTracker?.apply {
                    val pointerId: Int = event.getPointerId(event.actionIndex)
                    addMovement(event)
                    // When you want to determine the velocity, call
                    // computeCurrentVelocity(). Then, call getXVelocity() and
                    // getYVelocity() to retrieve the velocity for each pointer
                    // ID.
                    computeCurrentVelocity(1000)
                    // Log velocity of pixels per second. It's best practice to
                    // use VelocityTrackerCompat where possible.
                    Log.d("", "X velocity: ${getXVelocity(pointerId)}")
                    Log.d("", "Y velocity: ${getYVelocity(pointerId)}")
                }
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker?.recycle()
                mVelocityTracker = null
            }
        }
        return true
    }
}

جاوا

public class MainActivity extends Activity {
    private static final String DEBUG_TAG = "Velocity";
        ...
    private VelocityTracker mVelocityTracker = null;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int index = event.getActionIndex();
        int action = event.getActionMasked();
        int pointerId = event.getPointerId(index);

        switch(action) {
            case MotionEvent.ACTION_DOWN:
                if(mVelocityTracker == null) {
                    // Retrieve a new VelocityTracker object to watch the
                    // velocity of a motion.
                    mVelocityTracker = VelocityTracker.obtain();
                }
                else {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker.clear();
                }
                // Add a user's movement to the tracker.
                mVelocityTracker.addMovement(event);
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                // When you want to determine the velocity, call
                // computeCurrentVelocity(). Then call getXVelocity() and
                // getYVelocity() to retrieve the velocity for each pointer ID.
                mVelocityTracker.computeCurrentVelocity(1000);
                // Log velocity of pixels per second. It's best practice to use
                // VelocityTrackerCompat where possible.
                Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId));
                Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId));
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                // Return a VelocityTracker object back to be re-used by others.
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
}

استفاده از ضبط اشاره‌گر

برخی از برنامه‌ها، مانند بازی‌ها و کلاینت‌های دسکتاپ از راه دور و مجازی‌سازی، از کنترل نشانگر ماوس بهره‌مند می‌شوند. ضبط نشانگر ویژگی‌ای است که در اندروید ۸.۰ (سطح API ۲۶) و بالاتر موجود است و این کنترل را با ارائه تمام رویدادهای ماوس به یک نمای متمرکز در برنامه شما فراهم می‌کند.

درخواست ضبط اشاره‌گر

یک نمای (view) در برنامه شما فقط زمانی می‌تواند درخواست ضبط اشاره‌گر (pointer capture) را بدهد که سلسله مراتب نمای (view hierarchy) که شامل آن است، فوکوس (focus) داشته باشد. به همین دلیل، زمانی که یک اقدام خاص کاربر روی نمای مورد نظر انجام می‌شود، مانند رویداد onClick() یا در رویداد onWindowFocusChanged() در activity شما، درخواست ضبط اشاره‌گر (pointer capture) را ارسال کنید.

برای درخواست ضبط اشاره‌گر، متد requestPointerCapture() را در view فراخوانی کنید. مثال کد زیر نحوه درخواست ضبط اشاره‌گر را هنگامی که کاربر روی یک view کلیک می‌کند نشان می‌دهد:

کاتلین

fun onClick(view: View) {
    view.requestPointerCapture()
}

جاوا

@Override
public void onClick(View view) {
    view.requestPointerCapture();
}

زمانی که درخواست برای گرفتن اشاره‌گر موفقیت‌آمیز باشد، اندروید تابع onPointerCaptureChange(true) را فراخوانی می‌کند. سیستم رویدادهای ماوس را به نمای متمرکز در برنامه شما ارسال می‌کند، البته تا زمانی که در همان سلسله مراتب نمای درخواست‌کننده برای گرفتن باشد. سایر برنامه‌ها تا زمانی که ضبط آزاد نشود، دریافت رویدادهای ماوس، از جمله رویدادهای ACTION_OUTSIDE ، را متوقف می‌کنند. اندروید رویدادهای اشاره‌گر را از منابعی غیر از ماوس به طور معمول ارسال می‌کند، اما اشاره‌گر ماوس دیگر قابل مشاهده نیست.

مدیریت رویدادهای اشاره‌گر ضبط‌شده

زمانی که یک نما با موفقیت نشانگر ماوس را ثبت کرد، اندروید رویدادهای ماوس را ارائه می‌دهد. نمای متمرکز شما می‌تواند با انجام یکی از وظایف زیر، رویدادها را مدیریت کند:

مثال کد زیر نحوه پیاده‌سازی onCapturedPointerEvent(MotionEvent) را نشان می‌دهد:

کاتلین

override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
    // Get the coordinates required by your app.
    val verticalOffset: Float = motionEvent.y
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true
}

جاوا

@Override
public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
  // Get the coordinates required by your app.
  float verticalOffset = motionEvent.getY();
  // Use the coordinates to update your view and return true if the event is
  // successfully processed.
  return true;
}

مثال کد زیر نحوه ثبت یک OnCapturedPointerListener را نشان می‌دهد:

کاتلین

myView.setOnCapturedPointerListener { view, motionEvent ->
    // Get the coordinates required by your app.
    val horizontalOffset: Float = motionEvent.x
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    true
}

جاوا

myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
  @Override
  public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
    // Get the coordinates required by your app.
    float horizontalOffset = motionEvent.getX();
    // Use the coordinates to update your view and return true if the event is
    // successfully processed.
    return true;
  }
});

چه از یک نمای سفارشی استفاده کنید و چه یک شنونده (listener) ثبت کنید، نمای شما یک MotionEvent با مختصات اشاره‌گر دریافت می‌کند که حرکات نسبی مانند دلتاهای X یا Y را مشخص می‌کند، مشابه مختصاتی که توسط یک دستگاه trackball ارائه می‌شود. می‌توانید مختصات را با استفاده از getX() و getY() بازیابی کنید.

رهاسازی ضبط اشاره‌گر

نمای (view) موجود در برنامه شما می‌تواند با فراخوانی تابع releasePointerCapture() ‎، همانطور که در مثال کد زیر نشان داده شده است، نشانگر را آزاد کند:

کاتلین

override fun onClick(view: View) {
    view.releasePointerCapture()
}

جاوا

@Override
public void onClick(View view) {
    view.releasePointerCapture();
}

سیستم می‌تواند بدون اینکه شما صریحاً تابع releasePointerCapture() را فراخوانی کنید، عملیات ضبط را از نما (view) بگیرد، که معمولاً به این دلیل است که سلسله مراتب نمای حاوی نمایی که درخواست ضبط را دارد، فوکوس را از دست می‌دهد.