تعاملی کردن نمای سفارشی

امتحان کردن روش Compose
Jetpack Compose جعبه‌ابزار واسط کاربر توصیه‌شده برای Android است. با نحوه کار با چیدمان‌ها در Compose آشنا شوید.

رسم واسط کاربر فقط بخشی از ایجاد نمای سفارشی است. همچنین باید نمای خود را طوری به ورودی کاربر پاسخ دهید که بسیار شبیه به کنش دنیای واقعی باشد که تقلید می‌کنید.

کاری کنید که اشیاء در برنامه‌تان مانند اشیاء واقعی عمل کنند. برای مثال، اجازه ندهید تصاویر در برنامه‌تان ناگهان ناپدید شوند و در جای دیگری ظاهر شوند، زیرا اشیاء در دنیای واقعی این کار را نمی‌کنند. درعوض، تصاویرتان را از جایی به جای دیگر منتقل کنید.

کاربران حتی رفتار یا حس ظریف را در میانجی حس می‌کنند و بهترین واکنش را به ظرافت‌هایی نشان می‌دهند که از دنیای واقعی تقلید می‌کند. برای مثال، وقتی کاربران شیء میانای کاربری را پرتاب می‌کنند، در ابتدا به آن‌ها حس لختی بدهید که حرکت را به‌تأخیر می‌اندازد. در پایان حرکت، به آن‌ها حس تکانه‌ای بدهید که شیء را فراتر از پرتاب می‌برد.

این صفحه نشان می‌دهد که چگونه از ویژگی‌های چارچوب Android برای افزودن این رفتارهای دنیای واقعی به نمای سفارشی‌تان استفاده کنید.

می‌توانید اطلاعات مرتبط بیشتری را در نمای کلی رویدادهای ورودی و نمای کلی پویانمایی ویژگی پیدا کنید.

مدیریت اشاره‌های ورودی

مانند بسیاری از چارچوب‌های رابط کاربری دیگر، Android از مدل رویداد ورودی پشتیبانی می‌کند. کنش‌های کاربر به رویدادهایی تبدیل می‌شوند که فراخوانی‌های برگشتی را راه‌اندازی می‌کنند و می‌توانید فراخوانی‌های برگشتی را لغو کنید تا نحوه پاسخ برنامه به کاربر را سفارشی‌سازی کنید. رایج‌ترین رویداد ورودی در سیستم Android لمس است که onTouchEvent(android.view.MotionEvent) را راه‌اندازی می‌کند. این روش را برای رسیدگی به رویداد به‌صورت زیر لغو کنید:

کاتلین

override fun onTouchEvent(event: MotionEvent): Boolean {
    return super.onTouchEvent(event)
}

جاوا

@Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

رویدادهای لمسی به‌خودی‌خود چندان مفید نیستند. رابط‌های کاربری لمسی مدرن تعاملات را براساس اشاره‌هایی مانند ضربه زدن، کشیدن، فشار دادن، پرتاب کردن، و بزرگ‌نمایی تعریف می‌کنند. برای تبدیل رویدادهای لمسی خام به اشاره‌ها، Android GestureDetector را ارائه می‌دهد.

با ارسال نمونه‌ای از کلاسی که GestureDetector.OnGestureListener را پیاده‌سازی می‌کند، GestureDetector بسازید. اگر فقط می‌خواهید چند اشاره را پردازش کنید، می‌توانید به‌جای پیاده‌سازی میانای GestureDetector.OnGestureListener، GestureDetector.SimpleOnGestureListener را گسترش دهید. برای مثال، این کد کلاسی ایجاد می‌کند که GestureDetector.SimpleOnGestureListener را گسترش می‌دهد و onDown(MotionEvent) را لغو می‌کند.

کاتلین

private val myListener =  object : GestureDetector.SimpleOnGestureListener() {
    override fun onDown(e: MotionEvent): Boolean {
        return true
    }
}

private val detector: GestureDetector = GestureDetector(context, myListener)

جاوا

class MyListener extends GestureDetector.SimpleOnGestureListener {
   @Override
   public boolean onDown(MotionEvent e) {
       return true;
   }
}
detector = new GestureDetector(getContext(), new MyListener());

چه از GestureDetector.SimpleOnGestureListener استفاده کنید یا نه، همیشه یک روش onDown() پیاده‌سازی کنید که true را برگرداند. این کار ضروری است چون همه اشاره‌ها با پیام onDown() شروع می‌شود. اگر false را از onDown() برگردانید، همان‌طور که GestureDetector.SimpleOnGestureListener انجام می‌دهد، سیستم فرض می‌کند که می‌خواهید بقیه اشاره را نادیده بگیرید و روش‌های دیگر GestureDetector.OnGestureListener فراخوانی نمی‌شوند. فقط false را از onDown() برگردانید اگر می‌خواهید کل اشاره را نادیده بگیرید.

پس‌از پیاده‌سازی GestureDetector.OnGestureListener و ایجاد نمونه‌ای از GestureDetector، می‌توانید از GestureDetector برای تفسیر رویدادهای لمسی دریافتی در onTouchEvent() استفاده کنید.

کاتلین

override fun onTouchEvent(event: MotionEvent): Boolean {
    return detector.onTouchEvent(event).let { result ->
        if (!result) {
            if (event.action == MotionEvent.ACTION_UP) {
                stopScrolling()
                true
            } else false
        } else true
    }
}

جاوا

@Override
public boolean onTouchEvent(MotionEvent event) {
   boolean result = detector.onTouchEvent(event);
   if (!result) {
       if (event.getAction() == MotionEvent.ACTION_UP) {
           stopScrolling();
           result = true;
       }
   }
   return result;
}

وقتی رویداد لمسی‌ای را به onTouchEvent() ارسال می‌کنید که آن را به‌عنوان بخشی از اشاره تشخیص نمی‌دهد، false را برمی‌گرداند. سپس می‌توانید کد تشخیص اشاره سفارشی خودتان را اجرا کنید.

ایجاد حرکت قابل‌قبول فیزیکی

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

برای مثال، فرض کنید می‌خواهید اشاره تندافقی را پیاده‌سازی کنید که مورد ترسیم‌شده در نما را تنظیم می‌کند تا حول محور عمودی‌اش بچرخد. این اشاره زمانی منطقی است که واسط کاربر با حرکت سریع در جهت پرتاب، سپس کاهش سرعت، پاسخ دهد، گویی کاربر روی چرخ طیار فشار می‌آورد و آن را می‌چرخاند.

اسناد مربوط به نحوه پویانمایی اشاره پیمایش توضیح دقیقی درباره نحوه پیاده‌سازی رفتار پیمایش خودتان ارائه می‌دهد. اما شبیه‌سازی حس چرخ لنگر کار ساده‌ای نیست. برای اینکه مدل چرخ لنگر به‌درستی کار کند، به مقدار زیادی فیزیک و ریاضی نیاز است. خوشبختانه، Android کلاس‌های کمکی برای شبیه‌سازی این رفتار و رفتارهای دیگر ارائه می‌دهد. کلاس Scroller پایه مدیریت اشاره‌های پرتابی سبک چرخ‌لنگر است.

برای شروع پرتاب، fling() را با سرعت شروع و حداقل و حداکثر مقادیر x و y پرتاب فراخوانی کنید. برای مقدار سرعت، می‌توانید از مقدار محاسبه‌شده توسط GestureDetector استفاده کنید.

کاتلین

fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
    scroller.fling(
            currentX,
            currentY,
            (velocityX / SCALE).toInt(),
            (velocityY / SCALE).toInt(),
            minX,
            minY,
            maxX,
            maxY
    )
    postInvalidate()
    return true
}

جاوا

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
   scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
   postInvalidate();
    return true;
}

فراخوانی fling() مدل فیزیک را برای اشاره پرتابی راه‌اندازی می‌کند. سپس، Scroller را با فراخوانی Scroller.computeScrollOffset() در فواصل زمانی منظم به‌روز کنید. computeScrollOffset() با خواندن زمان فعلی و استفاده از مدل فیزیک برای محاسبه موقعیت x و y در آن زمان، وضعیت داخلی شیء Scroller را به‌روز می‌کند. برای بازیابی این مقادیر، getCurrX() و getCurrY() را فراخوانی کنید.

اکثر نماها موقعیت‌های x و y شیء Scroller را مستقیماً به scrollTo() ارسال می‌کنند. این مثال کمی متفاوت است: از موقعیت x پیمایش فعلی برای تنظیم زاویه چرخش نما استفاده می‌کند.

کاتلین

scroller.apply {
    if (!isFinished) {
        computeScrollOffset()
        setItemRotation(currX)
    }
}

جاوا

if (!scroller.isFinished()) {
    scroller.computeScrollOffset();
    setItemRotation(scroller.getCurrX());
}

کلاس Scroller موقعیت‌های پیمایش را برای شما محاسبه می‌کند، اما این موقعیت‌ها را به‌طور خودکار در نمای شما اعمال نمی‌کند. مختصات جدید را به‌اندازه کافی اعمال کنید تا پویانمایی پیمایش روان به‌نظر برسد. دو روش برای انجام این کار وجود دارد:

  • با فراخوانی postInvalidate() پس‌از فراخوانی fling()، ترسیم مجدد را اجباری کنید. این تکنیک مستلزم این است که انحراف‌های پیمایش را در onDraw() محاسبه کنید و هربار که انحراف پیمایش تغییر می‌کند، postInvalidate() را فراخوانی کنید.
  • ‫ValueAnimator را برای پویانمایی درطول مدت پرتاب تنظیم کنید و شنونده‌ای برای پردازش به‌روزرسانی‌های پویانمایی با فراخوانی addUpdateListener() اضافه کنید. این تکنیک به شما امکان می‌دهد ویژگی‌های یک View را پویانمایی کنید.

گذارها را روان کنید

کاربران انتظار دارند که یک میانای کاربر مدرن به‌آرامی بین حالت‌ها جابه‌جا شود: عناصر میانای کاربر به‌جای ظاهر شدن و ناپدید شدن، به‌تدریج ظاهر و محو شوند، و حرکات به‌جای شروع و توقف ناگهانی، به‌آرامی شروع و پایان یابند. چارچوب پویانمایی ویژگی Android انتقال‌های روان را آسان‌تر می‌کند.

برای استفاده از سیستم پویانمایی، هرگاه ویژگی‌ای تغییر می‌کند که بر ظاهر نمای شما تأثیر می‌گذارد، ویژگی را مستقیماً تغییر ندهید. درعوض، از ValueAnimator برای ایجاد تغییر استفاده کنید. در مثال زیر، اصلاح مؤلفه فرزند انتخاب‌شده در نما باعث می‌شود کل نمای رندرشده بچرخد تا نشانگر انتخاب در مرکز قرار گیرد. ValueAnimator چرخش را درطول چند صد میلی‌ثانیه تغییر می‌دهد، به‌جای اینکه مقدار چرخش جدید را فوراً تنظیم کند.

کاتلین

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply {
    setIntValues(targetAngle)
    duration = AUTOCENTER_ANIM_DURATION
    start()
}

جاوا

autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0);
autoCenterAnimator.setIntValues(targetAngle);
autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
autoCenterAnimator.start();

اگر مقداری که می‌خواهید تغییر دهید یکی از ویژگی‌های پایه View باشد، انجام پویانمایی حتی آسان‌تر است، زیرا نماها دارای یک ViewPropertyAnimator داخلی هستند که برای پویانمایی هم‌زمان چندین ویژگی بهینه‌سازی شده است، مانند مثال زیر:

کاتلین

animate()
    .rotation(targetAngle)
    .duration = ANIM_DURATION
    .start()

جاوا

animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();