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

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

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

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

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

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

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

مدیریت حرکات ورودی

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

کاتلین

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

جاوا

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

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

با ارسال نمونه‌ای از کلاسی که GestureDetector.OnGestureListener را پیاده‌سازی می‌کند، یک GestureDetector بسازید. اگر فقط می‌خواهید چند حرکت را پردازش کنید، می‌توانید به جای پیاده‌سازی رابط GestureDetector.SimpleOnGestureListener GestureDetector.OnGestureListener را ارث‌بری کنید. برای مثال، این کد کلاسی ایجاد می‌کند که 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() شروع می‌شوند. اگر از onDown() false را برگردانید، همانطور که GestureDetector.SimpleOnGestureListener انجام می‌دهد، سیستم فرض می‌کند که شما می‌خواهید بقیه حرکت را نادیده بگیرید و سایر متدهای GestureDetector.OnGestureListener فراخوانی نمی‌شوند. فقط در صورتی از onDown() false را برگردانید که می‌خواهید کل یک حرکت را نادیده بگیرید.

پس از پیاده‌سازی 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 برمی‌گرداند. سپس می‌توانید کد تشخیص ژست حرکتی سفارشی خود را اجرا کنید.

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

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

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

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

برای شروع یک fling، تابع fling() را با سرعت شروع و حداقل و حداکثر مقادیر x و y مربوط به fling فراخوانی کنید. برای مقدار سرعت، می‌توانید از مقداری که توسط 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() مدل فیزیک را برای ژست 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 تنظیم کنید تا در طول مدت fling انیمیشن اجرا کند و با فراخوانی addUpdateListener() یک listener برای پردازش به‌روزرسانی‌های انیمیشن اضافه کنید. این تکنیک به شما امکان می‌دهد ویژگی‌های یک View را متحرک کنید.

انتقال‌های خود را روان انجام دهید

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

برای استفاده از سیستم انیمیشن، هر زمان که یک ویژگی، آنچه را که بر ظاهر نمای شما تأثیر می‌گذارد تغییر می‌دهد، آن ویژگی را مستقیماً تغییر ندهید. در عوض، 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 باشد، انجام انیمیشن حتی ساده‌تر هم می‌شود، زیرا Viewها دارای یک ViewPropertyAnimator داخلی هستند که برای انیمیشن همزمان چندین ویژگی بهینه شده است، مانند مثال زیر:

کاتلین

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

جاوا

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