ทำให้มุมมองที่กำหนดเองเป็นแบบอินเทอร์แอกทีฟ

ลองใช้วิธีแบบ Compose
Jetpack Compose เป็นชุดเครื่องมือ UI ที่แนะนำสำหรับ Android ดูวิธีใช้เลย์เอาต์ใน Compose

การวาด UI เป็นเพียงส่วนหนึ่งของการสร้างมุมมองที่กำหนดเอง คุณยังต้องทำให้มุมมองตอบสนองต่อข้อมูลจากผู้ใช้ในลักษณะที่คล้ายกับการกระทำในโลกจริงที่คุณกำลังจำลอง

ทำให้วัตถุในแอปของคุณทำงานเหมือนวัตถุจริง เช่น อย่าให้รูปภาพในแอปหายไปแล้วปรากฏขึ้นอีกครั้งที่อื่น เพราะวัตถุในโลกจริงไม่ทำเช่นนั้น แต่ให้ย้ายรูปภาพจากที่หนึ่งไปยังอีกที่หนึ่งแทน

ผู้ใช้จะรับรู้ถึงพฤติกรรมหรือความรู้สึกที่ละเอียดอ่อนในอินเทอร์เฟซและตอบสนองได้ดีที่สุดต่อความละเอียดอ่อนที่จำลองโลกจริง ตัวอย่างเช่น เมื่อผู้ใช้ปัดวัตถุ UI ให้ความรู้สึกถึงความเฉื่อยในช่วงเริ่มต้นที่ทำให้การเคลื่อนไหวช้าลง และเมื่อการเคลื่อนไหวสิ้นสุดลง ให้ความรู้สึกถึงแรงเฉื่อยที่ทำให้วัตถุเคลื่อนที่ต่อไปหลังจากปัด

หน้านี้แสดงวิธีใช้ฟีเจอร์ของเฟรมเวิร์ก Android เพื่อเพิ่มพฤติกรรมเหล่านี้ในโลกจริงลงในมุมมองที่กำหนดเอง

ดูข้อมูลเพิ่มเติมที่เกี่ยวข้องได้ใน ภาพรวมของเหตุการณ์การป้อนข้อมูล และ ภาพรวมของ ภาพเคลื่อนไหวพร็อพเพอร์ตี้

จัดการท่าทางสัมผัสการป้อนข้อมูล

Android รองรับโมเดลเหตุการณ์การป้อนข้อมูลเช่นเดียวกับเฟรมเวิร์ก UI อื่นๆ อีกมากมาย การกระทำของผู้ใช้จะเปลี่ยนเป็นเหตุการณ์ที่เรียกใช้การเรียกกลับ และคุณสามารถลบล้างการเรียกกลับเพื่อปรับแต่งวิธีที่แอปตอบสนองต่อผู้ใช้ เหตุการณ์การป้อนข้อมูลที่พบบ่อยที่สุดในระบบ Android คือ การสัมผัส ซึ่งเรียกใช้ onTouchEvent(android.view.MotionEvent) ลบล้างเมธอดนี้เพื่อจัดการเหตุการณ์ ดังนี้

Kotlin

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

Java

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

เหตุการณ์การสัมผัสเพียงอย่างเดียวไม่ได้มีประโยชน์มากนัก UI แบบสัมผัสสมัยใหม่กำหนดการโต้ตอบในรูปแบบท่าทางสัมผัส เช่น การแตะ การดึง การดัน การปัด และการซูม Android มี GestureDetector เพื่อแปลงเหตุการณ์การสัมผัสแบบดิบเป็นท่าทางสัมผัส

สร้าง GestureDetector โดยส่งอินสแตนซ์ของคลาส ที่ใช้ GestureDetector.OnGestureListener หากต้องการประมวลผลท่าทางสัมผัสเพียงไม่กี่ท่า คุณสามารถขยาย GestureDetector.SimpleOnGestureListener แทนการใช้อินเทอร์เฟซ GestureDetector.OnGestureListener ตัวอย่างเช่น โค้ดนี้สร้างคลาสที่ขยาย GestureDetector.SimpleOnGestureListener และลบล้าง onDown(MotionEvent)

Kotlin

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

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

Java

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() ได้

Kotlin

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
    }
}

Java

@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 จากนั้นคุณจะเรียกใช้โค้ดการตรวจหาท่าทางสัมผัสที่กำหนดเองได้

สร้างการเคลื่อนไหวที่สมเหตุสมผลทางกายภาพ

ท่าทางสัมผัสเป็นวิธีที่มีประสิทธิภาพในการควบคุมอุปกรณ์หน้าจอสัมผัส แต่อาจใช้งานยากและจดจำได้ยากหากไม่ให้ผลลัพธ์ที่สมเหตุสมผลทางกายภาพ

ตัวอย่างเช่น สมมติว่าคุณต้องการใช้ท่าทางสัมผัสปัดแนวนอนที่ทำให้รายการที่วาดในมุมมองหมุนรอบแกนแนวตั้ง ท่าทางสัมผัสนี้สมเหตุสมผลหาก UI ตอบสนองด้วยการเคลื่อนที่อย่างรวดเร็วในทิศทางของการปัด แล้วช้าลง ราวกับว่าผู้ใช้ดันล้อช่วยแรงและทำให้ล้อหมุน

เอกสารประกอบเกี่ยวกับวิธีสร้างภาพเคลื่อนไหวท่าทางสัมผัสเลื่อนจะอธิบายโดยละเอียดเกี่ยวกับวิธีใช้ลักษณะการเลื่อนของคุณเอง แต่การจำลองความรู้สึกของล้อช่วยแรงไม่ใช่เรื่องง่าย ต้องใช้ฟิสิกส์และคณิตศาสตร์จำนวนมากเพื่อให้โมเดลล้อช่วยแรงทำงานได้อย่างถูกต้อง โชคดีที่ Android มีคลาสตัวช่วยเพื่อจำลองพฤติกรรมนี้และพฤติกรรมอื่นๆ คลาส Scroller เป็นพื้นฐานสำหรับการจัดการท่าทางสัมผัสปัดแบบล้อช่วยแรง

หากต้องการเริ่มปัด ให้เรียกใช้ fling() ด้วยความเร็วเริ่มต้นและค่า x และ y ต่ำสุดและสูงสุดของการปัด สำหรับค่าความเร็ว คุณสามารถใช้ค่าที่คำนวณโดย GestureDetector

Kotlin

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
}

Java

@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.computeScrollOffset() เป็นระยะๆ เพื่ออัปเดต Scroller computeScrollOffset() จะอัปเดตสถานะภายในของออบเจ็กต์ Scroller โดยการอ่านเวลาปัจจุบันและใช้โมเดลฟิสิกส์เพื่อคำนวณตำแหน่ง x และ y ในเวลานั้น เรียกใช้ getCurrX() และ getCurrY() เพื่อดึงข้อมูลค่าเหล่านี้

มุมมองส่วนใหญ่จะส่งตำแหน่ง x และ y ของออบเจ็กต์ Scroller ไปยัง scrollTo() โดยตรง ตัวอย่างนี้แตกต่างออกไปเล็กน้อย โดยใช้ตำแหน่ง x การเลื่อนปัจจุบันเพื่อตั้งค่ามุมการหมุนของมุมมอง

Kotlin

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

Java

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

คลาส Scroller จะคำนวณตำแหน่งการเลื่อนให้คุณ แต่จะไม่ใช้ตำแหน่งเหล่านั้นกับมุมมองโดยอัตโนมัติ คุณต้องใช้พิกัดใหม่บ่อยพอที่จะทำให้ภาพเคลื่อนไหวการเลื่อนดูราบรื่น ซึ่งทำได้ 2 วิธีดังนี้

  • บังคับให้วาดใหม่โดยเรียกใช้ postInvalidate() หลังจากเรียกใช้ fling() เทคนิคนี้กำหนดให้คุณต้อง คำนวณออฟเซ็ตการเลื่อนใน onDraw() และเรียกใช้ postInvalidate() ทุกครั้งที่ออฟเซ็ตการเลื่อน เปลี่ยนแปลง
  • ตั้งค่า ValueAnimator ให้สร้างภาพเคลื่อนไหวตามระยะเวลาการปัด และเพิ่ม Listener เพื่อประมวลผล การอัปเดตภาพเคลื่อนไหวโดยเรียกใช้ addUpdateListener() เทคนิคนี้ช่วยให้คุณสร้างภาพเคลื่อนไหวพร็อพเพอร์ตี้ของ View ได้

ทำให้การเปลี่ยนผ่านราบรื่น

ผู้ใช้คาดหวังว่า UI สมัยใหม่จะเปลี่ยนผ่านระหว่างสถานะต่างๆ ได้อย่างราบรื่น เช่น องค์ประกอบ UI ค่อยๆ ปรากฏและหายไปแทนที่จะปรากฏและหายไปทันที และการเคลื่อนไหวเริ่มต้นและสิ้นสุดอย่างราบรื่นแทนที่จะเริ่มและหยุดอย่างกะทันหัน เฟรมเวิร์กภาพเคลื่อนไหวพร็อพเพอร์ตี้ของ Android ช่วยให้การเปลี่ยนผ่านราบรื่นขึ้น

หากต้องการใช้ระบบภาพเคลื่อนไหว เมื่อใดก็ตามที่พร็อพเพอร์ตี้มีการเปลี่ยนแปลงซึ่งส่งผลต่อลักษณะที่ปรากฏของมุมมอง ให้เปลี่ยนพร็อพเพอร์ตี้โดยใช้ `ValueAnimator` แทนการเปลี่ยนพร็อพเพอร์ตี้โดยตรง แต่ให้ใช้ ValueAnimator เพื่อทำการเปลี่ยนแปลงแทน ในตัวอย่างต่อไปนี้ การแก้ไขคอมโพเนนต์ย่อยที่เลือกในมุมมองจะทำให้มุมมองที่แสดงผลทั้งหมด หมุนเพื่อให้ตัวชี้การเลือกอยู่ตรงกลาง ValueAnimator จะเปลี่ยนการหมุนในช่วงเวลาหลายร้อยมิลลิวินาที แทนที่จะตั้งค่าการหมุนใหม่ทันที

Kotlin

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

Java

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

หากค่าที่คุณต้องการเปลี่ยนเป็นค่าใดค่าหนึ่งในพร็อพเพอร์ตี้ View พื้นฐาน การสร้างภาพเคลื่อนไหวจะง่ายยิ่งขึ้น เนื่องจากมุมมองมี ViewPropertyAnimator ในตัวซึ่งได้รับการเพิ่มประสิทธิภาพให้สร้างภาพเคลื่อนไหวพร็อพเพอร์ตี้หลายรายการพร้อมกันได้ ดังตัวอย่าง ต่อไปนี้

Kotlin

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

Java

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