ฟีเจอร์สไตลัสขั้นสูง

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

MotionEvent

คลาส MotionEvent แสดงการโต้ตอบอินพุตของผู้ใช้ เช่น ตำแหน่ง และการเคลื่อนไหวของตัวชี้แบบสัมผัสบนหน้าจอ สำหรับอินพุตสไตลัส MotionEvent นอกจากนี้ยังจะแสดงข้อมูลความดัน การวางแนว การเอียง และการวางเมาส์เหนือ

ข้อมูลเหตุการณ์

วันที่

หากต้องการเข้าถึงวัตถุ MotionEvent ของสไตลัส ให้เพิ่มตัวแก้ไข pointerInteropFilter ลงในพื้นผิวการวาด ใช้คลาส ViewModel ที่มีเมธอดซึ่งประมวลผลเหตุการณ์การเคลื่อนไหว โดยส่งเมธอดเป็น onTouchEvent lambda ของตัวแปร pointerInteropFilter ดังนี้

@Composable
@OptIn(ExperimentalComposeUiApi::class)
fun DrawArea(modifier: Modifier = Modifier) {
   Canvas(modifier = modifier
       .clipToBounds()
       .pointerInteropFilter {
           viewModel.processMotionEvent(it)
       }

   ) {
       // Drawing code here.
   }
}

ออบเจ็กต์ MotionEvent ให้ข้อมูลที่เกี่ยวข้องกับด้านต่างๆ ต่อไปนี้ของ UI กิจกรรม:

  • การทำงาน: การโต้ตอบทางกายภาพกับอุปกรณ์ เช่น การแตะหน้าจอ การย้ายตัวชี้เหนือพื้นผิวหน้าจอ การเลื่อนตัวชี้เหนือหน้าจอ แพลตฟอร์ม
  • เคอร์เซอร์: ตัวระบุของวัตถุที่โต้ตอบกับหน้าจอ เช่น นิ้ว สไตลัส, เมาส์
  • แกน: ประเภทข้อมูล เช่น พิกัด x และ y, ความดัน, การเอียง, การวางแนว และลอยอยู่ (ระยะทาง)

การดำเนินการ

หากต้องการใช้การรองรับสไตลัส คุณต้องเข้าใจสิ่งที่ผู้ใช้ทำ ที่มีประสิทธิภาพสูง

MotionEvent มีค่าคงที่ ACTION ที่หลากหลายซึ่งเป็นตัวกำหนดการเคลื่อนไหว กิจกรรม การดำเนินการที่สำคัญที่สุดสำหรับสไตลัสมีดังนี้

การทำงาน คำอธิบาย
ACTION_DOWN
ACTION_POINTER_DOWN
เคอร์เซอร์สัมผัสหน้าจอแล้ว
การดำเนินการ เคอร์เซอร์กำลังเคลื่อนไหวบนหน้าจอ
ACTION_UP
ACTION_POINTER_UP
เคอร์เซอร์ไม่สัมผัสกับหน้าจออีกต่อไป
การดำเนินการยกเลิก ควรยกเลิกชุดการเคลื่อนไหวก่อนหน้าหรือปัจจุบันเมื่อใด

แอปของคุณจะทำงานต่างๆ ได้ เช่น การเริ่มเส้นโครงร่างใหม่เมื่อ ACTION_DOWN เกิดขึ้น โดยวาดเส้นโครงร่างด้วย ACTION_MOVE, และสิ้นสุดเส้นโครงร่างเมื่อ ทริกเกอร์ ACTION_UP แล้ว

ชุดของการกระทำ MotionEvent จาก ACTION_DOWN เป็น ACTION_UP สำหรับ เรียกว่าชุดการเคลื่อนไหว

เคอร์เซอร์

หน้าจอส่วนใหญ่เป็นแบบมัลติทัช โดยระบบจะกำหนดตัวชี้ให้กับนิ้วแต่ละนิ้ว สไตลัส เมาส์ หรือวัตถุชี้ตำแหน่งอื่นๆ ที่โต้ตอบกับหน้าจอ ตัวชี้ ดัชนีช่วยให้คุณสามารถดูข้อมูลแกนสำหรับตัวชี้ที่เจาะจง เช่น เมื่อนิ้วแรกแตะบนหน้าจอหรือนิ้วที่สอง

ช่วงดัชนีเคอร์เซอร์มีตั้งแต่ 0 จนถึงจำนวนเคอร์เซอร์ที่แสดงผลโดย MotionEvent#pointerCount() ลบ 1

คุณเข้าถึงค่าแกนของเคอร์เซอร์ได้ด้วยเมธอด getAxisValue(axis, pointerIndex) เมื่อละเว้นดัชนีตัวชี้ ระบบจะแสดงค่าสำหรับดัชนีแรก ตัวชี้, ตัวชี้ศูนย์ (0)

ออบเจ็กต์ MotionEvent มีข้อมูลเกี่ยวกับประเภทของตัวชี้ที่ใช้อยู่ คุณ รับประเภทตัวชี้ได้โดยทำซ้ำผ่านดัชนีตัวชี้และการเรียก เวลา getToolType(pointerIndex)

ดูข้อมูลเพิ่มเติมเกี่ยวกับเคอร์เซอร์ได้ที่จัดการมัลติทัช ท่าทางสัมผัส

อินพุตสไตลัส

คุณกรองหาอินพุตสไตลัสได้ด้วย TOOL_TYPE_STYLUS:

val isStylus = TOOL_TYPE_STYLUS == event.getToolType(pointerIndex)

สไตลัสยังสามารถรายงานได้ว่ามีการใช้เป็นยางลบ TOOL_TYPE_ERASER:

val isEraser = TOOL_TYPE_ERASER == event.getToolType(pointerIndex)

ข้อมูลแกนสไตลัส

ACTION_DOWN และ ACTION_MOVE ให้ข้อมูลแกนเกี่ยวกับสไตลัส ซึ่งได้แก่ x และ พิกัด y ความดัน การวางแนว การเอียง และการลอยตัว

MotionEvent API จะเตรียมการเข้าถึงข้อมูลนี้ไว้ให้ getAxisValue(int), โดยที่พารามิเตอร์เป็นตัวระบุแกนต่อไปนี้

Axis ผลลัพธ์ getAxisValue()
AXIS_X พิกัด X ของเหตุการณ์การเคลื่อนไหว
AXIS_Y พิกัด Y ของเหตุการณ์การเคลื่อนไหว
AXIS_PRESSURE สำหรับหน้าจอสัมผัสหรือทัชแพด แรงกดที่ใช้นิ้ว สไตลัส หรือตัวชี้อื่นๆ สำหรับเมาส์หรือแทร็กบอล ให้ใช้ 1 หากกดปุ่มหลัก มิฉะนั้นจะเป็น 0
AXIS_ORIENTATION สำหรับหน้าจอสัมผัสหรือทัชแพด จะมีการวางแนวของนิ้วมือ สไตลัส หรือตัวชี้อื่นๆ ที่สัมพันธ์กับระนาบแนวตั้งของอุปกรณ์
AXIS_TILT มุมเอียงของสไตลัสเป็นเรเดียน
AXIS_DISTANCE ระยะห่างของสไตลัสจากหน้าจอ

ตัวอย่างเช่น MotionEvent.getAxisValue(AXIS_X) จะแสดงผลพิกัด x ของ ตัวชี้แรก

โปรดดูหัวข้อจัดการมัลติทัช ท่าทางสัมผัส

ตำแหน่ง

คุณสามารถเรียกพิกัด x และ y ของตัวชี้ได้ด้วยการเรียกต่อไปนี้:

สไตลัสกำลังวาดบนหน้าจอโดยแมปพิกัด x และ y ไว้
รูปที่ 1 พิกัด X และ y บนหน้าจอของตัวชี้สไตลัส

ความกดอากาศ

คุณสามารถดูความกดดันตัวชี้ได้ด้วย MotionEvent#getAxisValue(AXIS_PRESSURE) หรือสำหรับตัวชี้แรก MotionEvent#getPressure()

ค่าแรงดันสำหรับหน้าจอสัมผัสหรือทัชแพดคือค่าระหว่าง 0 (no กด) และ 1 แต่อาจแสดงผลค่าที่สูงกว่า ทั้งนี้ขึ้นอยู่กับหน้าจอ การเทียบมาตรฐาน

วันที่ เส้นสไตลัสแสดงถึงความดันต่ำไปสูงอย่างต่อเนื่อง โรคหลอดเลือดสมองแคบและจางทางด้านซ้ายแสดงถึงความกดอากาศต่ำ เส้นโครงร่างกว้างขึ้นและมืดจากซ้ายไปขวาจนกว่าจะกว้างที่สุดและมืดที่สุดฝั่งขวาสุด แสดงถึงความกดอากาศสูงสุด
รูปที่ 2 การแสดงด้วยแรงดัน - ความกดอากาศต่ำด้านบนซ้าย ความกดอากาศสูงด้านขวา

การวางแนว

การวางแนวจะระบุว่าสไตลัสกำลังชี้ไปในทิศทางใด

คุณดึงข้อมูลการวางแนวของเคอร์เซอร์ได้โดยใช้ getAxisValue(AXIS_ORIENTATION) หรือ getOrientation() (สำหรับตัวชี้ตัวแรก)

สำหรับสไตลัส การวางแนวจะส่งคืนค่าเป็นค่าเรเดียนระหว่าง 0 ถึงพาย (π) ตามเข็มนาฬิกา หรือ 0 ถึง -pi ทวนเข็มนาฬิกา

การวางแนวช่วยให้คุณใช้แปรงในชีวิตจริงได้ ตัวอย่างเช่น หาก สไตลัสแสดงถึงแปรงแบน ความกว้างของแปรงแบนจะขึ้นอยู่กับ การวางแนวของสไตลัส

วันที่
รูปที่ 3 สไตลัสชี้ไปทางซ้ายประมาณ -.57 เรเดียน

ทิลท์

การเอียงจะวัดความเอียงของสไตลัสโดยสัมพันธ์กับหน้าจอ

การเอียงจะแสดงมุมบวกของสไตลัสเป็นเรเดียน โดยที่ 0 เท่ากับ ตั้งฉากกับหน้าจอ และ π/2 แบนบนพื้นผิว

สามารถดึงข้อมูลมุมเอียงได้โดยใช้ getAxisValue(AXIS_TILT) (ไม่มีทางลัดสำหรับ เคอร์เซอร์ตัวแรก)

การเอียงสามารถใช้เพื่อทำให้ได้เครื่องมือในชีวิตจริงที่ใกล้เคียงที่สุดเท่าที่จะทำได้ เช่น ที่เลียนแบบการลงสีด้วยดินสอเอียง

วันที่ สไตลัสเอียงจากพื้นผิวหน้าจอประมาณ 40 องศา
รูปที่ 4 สไตลัสเอียงที่ประมาณ 0.785 เรเดียน หรือ 45 องศาจากตั้งฉาก

วางเมาส์

ระยะห่างของสไตลัสจากหน้าจอสามารถวัดได้ด้วย getAxisValue(AXIS_DISTANCE) เมธอดแสดงค่าจาก 0.0 (ติดต่อกับ หน้าจอ) เป็นค่าที่สูงขึ้นเมื่อสไตลัสเลื่อนออกจากหน้าจอ โฮเวอร์ ระยะห่างระหว่างหน้าจอกับปลายนิ้ว (จุด) ของสไตลัสขึ้นอยู่กับ ผู้ผลิตทั้งหน้าจอและสไตลัส เนื่องจากการติดตั้งใช้งานอาจ ปรับเปลี่ยนได้ อย่าใช้ค่าที่แน่นอนสำหรับฟังก์ชันการทำงานที่สำคัญของแอป

การวางเมาส์เหนือสไตลัสสามารถใช้เพื่อดูตัวอย่างขนาดแปรงหรือบ่งชี้ว่า จะถูกเลือก

วันที่
รูปที่ 5 สไตลัสวางอยู่เหนือหน้าจอ แอปตอบสนองแม้ว่าสไตลัสจะไม่สัมผัสพื้นผิวหน้าจอ

หมายเหตุ: Compose มีตัวแก้ไขที่ส่งผลต่อสถานะแบบอินเทอร์แอกทีฟขององค์ประกอบ UI ดังนี้

  • hoverable: กำหนดค่าคอมโพเนนต์ให้วางเมาส์เหนือองค์ประกอบได้โดยใช้ตัวชี้ในเหตุการณ์การเข้าและออก
  • indication: วาดเอฟเฟกต์ภาพสำหรับคอมโพเนนต์นี้เมื่อมีการโต้ตอบ

การปฏิเสธฝ่ามือ การนำทาง และข้อมูลที่ไม่ต้องการ

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

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

ACTION_CANCEL และ FLAG_CANCELED

ACTION_CANCEL และ FLAG_CANCELED คือ ทั้งคู่ออกแบบมาเพื่อแจ้งให้คุณทราบว่า MotionEvent ชุดก่อนหน้าควร ยกเลิกตั้งแต่ ACTION_DOWN ที่แล้ว เพื่อให้คุณสามารถเลิกทำ เส้นสำหรับแอปพลิเคชันวาดภาพสำหรับตัวชี้ที่ระบุ

การดำเนินการยกเลิก

เพิ่มใน Android 1.0 (API ระดับ 1) แล้ว

ACTION_CANCEL บ่งชี้ว่าควรยกเลิกเหตุการณ์การเคลื่อนไหวชุดก่อนหน้า

ACTION_CANCEL จะทริกเกอร์เมื่อตรวจพบสิ่งต่อไปนี้

  • ท่าทางสัมผัสสำหรับการนำทาง
  • การปฏิเสธปาล์ม

เมื่อ ACTION_CANCEL ทริกเกอร์ คุณควรระบุตัวชี้ที่ใช้งานอยู่ด้วย getPointerId(getActionIndex()) จากนั้นลบเส้นโครงร่างที่สร้างด้วยตัวชี้นั้นออกจากประวัติการป้อนข้อมูล แล้วแสดงผลฉากอีกครั้ง

FLAG_CANCELED

เพิ่มใน Android 13 (API ระดับ 33) แล้ว

FLAG_CANCELED ชี้ให้เห็นว่าตัวชี้ที่เพิ่มขึ้นเป็นการแตะโดยไม่ได้ตั้งใจ ธงคือ ซึ่งมักจะเกิดขึ้นเมื่อผู้ใช้แตะหน้าจอโดยไม่ตั้งใจ เช่น การจับ หรือวางฝ่ามือลงบนหน้าจอ

คุณเข้าถึงค่า Flag ดังนี้

val cancel = (event.flags and FLAG_CANCELED) == FLAG_CANCELED

หากตั้งค่าไว้ คุณจะต้องเลิกทำ MotionEvent จากชุดสุดท้าย ACTION_DOWN จากจุดนี้

ซึ่งเหมือนกับ ACTION_CANCEL คุณจะพบเครื่องหมายดังกล่าวได้ด้วย getPointerId(actionIndex)

รูปที่ 6 เส้นสไตลัสและการสัมผัสฝ่ามือสร้างชุดอุปกรณ์ MotionEvent ชุด การแตะแบบใช้ฝ่ามือถูกยกเลิกและการแสดงผลอีกครั้ง

ท่าทางสัมผัสแบบเต็มหน้าจอ ขอบต่อขอบ และการนำทาง

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

รูปที่ 7 ท่าทางสัมผัสด้วยการปัดเพื่อย้ายแอปไปยังพื้นหลัง

หากต้องการป้องกันไม่ให้ท่าทางสัมผัสทำให้เกิดการแตะที่ไม่ต้องการในแอป คุณสามารถทำดังนี้ ข้อได้เปรียบของตัวอย่างและ ACTION_CANCEL

ดูการปฏิเสธฝ่ามือ การนำทาง และการป้อนข้อมูลที่ไม่พึงประสงค์ด้วย

ใช้เมนู setSystemBarsBehavior() วิธีการและ BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE จาก WindowInsetsController วิธีป้องกันไม่ให้ท่าทางสัมผัสการนำทางก่อให้เกิดกิจกรรมการแตะที่ไม่พึงประสงค์

// Configure the behavior of the hidden system bars.
windowInsetsController.systemBarsBehavior =
    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE

ดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการด้านการตั้งค่าและท่าทางสัมผัสได้ที่

เวลาในการตอบสนองต่ำ

เวลาในการตอบสนองคือเวลาที่ฮาร์ดแวร์ ระบบ และแอปพลิเคชันต้องการในการประมวลผล และแสดงผลข้อมูลจากผู้ใช้

เวลาในการตอบสนอง = การประมวลผลอินพุตฮาร์ดแวร์และระบบปฏิบัติการ + การประมวลผลแอป + การจัดวางองค์ประกอบของระบบ

  • การแสดงภาพฮาร์ดแวร์
เวลาในการตอบสนองทำให้เส้นโครงร่างที่แสดงผลช้ากว่าตำแหน่งสไตลัส ช่องว่างระหว่างเส้นโครงร่างที่แสดงและตำแหน่งสไตลัสแสดงถึงเวลาในการตอบสนอง
รูปที่ 8 เวลาในการตอบสนองทำให้เส้นโครงร่างที่แสดงผลช้ากว่าตำแหน่งสไตลัส

แหล่งที่มาของเวลาในการตอบสนอง

  • กำลังลงทะเบียนสไตลัสด้วยหน้าจอสัมผัส (ฮาร์ดแวร์): การเชื่อมต่อไร้สายเริ่มต้น เมื่อสไตลัสและระบบปฏิบัติการสื่อสารกันเพื่อลงทะเบียนและซิงค์
  • อัตราการสุ่มตัวอย่างการแตะ (ฮาร์ดแวร์): จำนวนครั้งต่อวินาทีบนหน้าจอสัมผัส ตรวจสอบว่าตัวชี้สัมผัสพื้นผิวหรือไม่ มีค่าตั้งแต่ 60 ถึง 1000Hz
  • การประมวลผลอินพุต (แอป): การใช้สี เอฟเฟกต์กราฟิก และการเปลี่ยนรูปแบบ ในข้อมูลจากผู้ใช้
  • การแสดงผลกราฟิก (ระบบปฏิบัติการ + ฮาร์ดแวร์): การเปลี่ยนบัฟเฟอร์ การประมวลผลด้วยฮาร์ดแวร์

กราฟิกที่มีเวลาในการตอบสนองต่ำ

ไลบรารีกราฟิกที่มีเวลาในการตอบสนองต่ำของ Jetpack ซึ่งช่วยลดเวลาในการประมวลผลระหว่างการป้อนข้อมูลของผู้ใช้และการแสดงผลบนหน้าจอ

ไลบรารีจะลดเวลาในการประมวลผลโดยหลีกเลี่ยงการแสดงผลแบบบัฟเฟอร์หลายตัว ใช้เทคนิคการแสดงผลแบบ Front-Buffers ซึ่งหมายความว่าเขียน หน้าจอ

การแสดงภาพบัฟเฟอร์ด้านหน้า

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

วันที่ แอปเขียนไปยังบัฟเฟอร์หน้าจอและอ่านจากบัฟเฟอร์หน้าจอ
รูปที่ 9 การแสดงภาพบัฟเฟอร์ด้านหน้า
แอปเขียนไปยังโหมดบัฟเฟอร์หลายรายการ ซึ่งสลับกับบัฟเฟอร์หน้าจอ แอปอ่านจากบัฟเฟอร์หน้าจอ
รูปที่ 10 การแสดงภาพแบบ Multi-buffer

ขณะที่การแสดงผลแบบบัฟเฟอร์หน้าเป็นเทคนิคที่ยอดเยี่ยมในการแสดงผลพื้นที่เล็กๆ ของกราฟ ไม่ได้ออกแบบมาให้ใช้เพื่อรีเฟรชทั้งหน้าจอ ด้วย การแสดงผลแบบฟรอนท์-บัฟเฟอร์ โดยแอปกำลังแสดงเนื้อหาลงในบัฟเฟอร์ที่ จอแสดงผลกำลังอ่านอยู่ จึงมีโอกาสแสดงผล สิ่งประดิษฐ์หรือการฉีกขาด (ดูด้านล่าง)

ไลบรารีที่มีเวลาในการตอบสนองต่ำพร้อมใช้งานตั้งแต่ Android 10 (API ระดับ 29) ขึ้นไป และในอุปกรณ์ ChromeOS ที่ใช้ Android 10 (API ระดับ 29) ขึ้นไป

การขึ้นต่อกัน

ไลบรารีที่มีเวลาในการตอบสนองต่ำมอบคอมโพเนนต์สำหรับการแสดงภาพบัฟเฟอร์ด้านหน้า การใช้งานของคุณ เพิ่มไลบรารีเป็นทรัพยากร Dependency ในโมดูลของแอป build.gradle ไฟล์:

dependencies {
    implementation "androidx.graphics:graphics-core:1.0.0-alpha03"
}

Callback ของ GLFrontBufferRenderer

ไลบรารีที่มีเวลาในการตอบสนองต่ำประกอบด้วย GLFrontBufferRenderer.Callback ของอินเทอร์เฟซ ซึ่งจะกำหนดวิธีการดังต่อไปนี้

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

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

Callback

หากต้องการเปิดใช้การแสดงผล Callback ให้ใช้ GLFrontBufferedRenderer.Callback และ ลบล้าง onDrawFrontBufferedLayer() และ onDrawDoubleBufferedLayer() GLFrontBufferedRenderer ใช้ Callback เพื่อแสดงผลข้อมูลของคุณใน ทางที่ดีที่สุดเท่าที่จะเป็นไปได้

val callback = object: GLFrontBufferedRenderer.Callback<DATA_TYPE> {
   override fun onDrawFrontBufferedLayer(
       eglManager: EGLManager,
       bufferInfo: BufferInfo,
       transform: FloatArray,
       param: DATA_TYPE
   ) {
       // OpenGL for front buffer, short, affecting small area of the screen.
   }
   override fun onDrawMultiDoubleBufferedLayer(
       eglManager: EGLManager,
       bufferInfo: BufferInfo,
       transform: FloatArray,
       params: Collection<DATA_TYPE>
   ) {
       // OpenGL full scene rendering.
   }
}
ประกาศอินสแตนซ์ของ GLFrontBufferedRenderer

จัดเตรียม GLFrontBufferedRenderer ด้วยการระบุ SurfaceView และ Callback ที่คุณสร้างไว้ก่อนหน้านี้ GLFrontBufferedRenderer เพิ่มประสิทธิภาพการแสดงผล ข้างหน้าและบัฟเฟอร์คู่โดยใช้ Callback ของคุณ:

var glFrontBufferRenderer = GLFrontBufferedRenderer<DATA_TYPE>(surfaceView, callbacks)
การแสดงภาพ

การแสดงผลบัฟเฟอร์ด้านหน้าจะเริ่มเมื่อคุณเรียก renderFrontBufferedLayer() ซึ่งจะทริกเกอร์ Callback onDrawFrontBufferedLayer()

การแสดงผลแบบบัฟเฟอร์คู่จะกลับมาทำงานอีกครั้งเมื่อคุณเรียกฟังก์ชัน commit() ซึ่งจะทริกเกอร์ Callback onDrawMultiDoubleBufferedLayer()

ในตัวอย่างต่อไปนี้ กระบวนการแสดงผลไปยังบัฟเฟอร์ด้านหน้า ( การแสดงผล) เมื่อผู้ใช้เริ่มวาดบนหน้าจอ (ACTION_DOWN) และเคลื่อนที่ ตัวชี้ไปรอบๆ (ACTION_MOVE) กระบวนการแสดงผลเป็นบัฟเฟอร์คู่ เมื่อตัวชี้ออกจากพื้นผิวของหน้าจอ (ACTION_UP)

คุณสามารถใช้ requestUnbufferedDispatch() ระบบอินพุตไม่ได้รวมกลุ่มเหตุการณ์การเคลื่อนไหว แต่ส่งแทน ทันทีที่พร้อมใช้งาน ให้ทำดังนี้

when (motionEvent.action) {
   MotionEvent.ACTION_DOWN -> {
       // Deliver input events as soon as they arrive.
       view.requestUnbufferedDispatch(motionEvent)
       // Pointer is in contact with the screen.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
   }
   MotionEvent.ACTION_MOVE -> {
       // Pointer is moving.
       glFrontBufferRenderer.renderFrontBufferedLayer(DATA_TYPE)
   }
   MotionEvent.ACTION_UP -> {
       // Pointer is not in contact in the screen.
       glFrontBufferRenderer.commit()
   }
   MotionEvent.CANCEL -> {
       // Cancel front buffer; remove last motion set from the screen.
       glFrontBufferRenderer.cancel()
   }
}

สิ่งที่ควรและไม่ควรทำเกี่ยวกับการแสดงภาพ

✓ ทำ

พื้นที่เล็กๆ ของหน้าจอ การเขียนด้วยลายมือ การวาดภาพ การร่างภาพ

✗ อย่า

การอัปเดตแบบเต็มหน้าจอ การเลื่อน การซูม อาจทำให้น้ำตาไหล

น้ำตาไหล

การฉีกขาดเกิดขึ้นเมื่อหน้าจอรีเฟรชขณะบัฟเฟอร์หน้าจอ ที่ถูกแก้ไขในเวลาเดียวกัน หน้าจอบางส่วนแสดงข้อมูลใหม่ ส่วนอีกหน้าจอหนึ่ง แสดงข้อมูลเก่า

วันที่ ส่วนบนและล่างของรูปภาพ Android วางไม่ตรงแนวเนื่องจากมีการฉีกขาดเมื่อรีเฟรชหน้าจอ
รูปที่ 11 ฉีกขาดเมื่อหน้าจอรีเฟรชจากบนลงล่าง

การคาดการณ์การเคลื่อนไหว

การคาดการณ์การเคลื่อนไหว Jetpack การลดไลบรารี เวลาในการตอบสนองที่รับรู้ได้โดยการประมาณเส้นทาง ของโรคหลอดเลือดสมองของผู้ใช้ และระบุชั่วคราว ไปยังตัวแสดงผล

ไลบรารีการคาดการณ์การเคลื่อนไหวได้รับข้อมูลจริงจากผู้ใช้เป็นออบเจ็กต์ MotionEvent วัตถุต่างๆ มีข้อมูลเกี่ยวกับพิกัด x และ y, ความดัน และเวลา ซึ่งเครื่องมือพยากรณ์การเคลื่อนไหวใช้ประโยชน์จากเพื่อคาดการณ์ MotionEvent ในอนาคต ออบเจ็กต์

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

ไลบรารีการคาดการณ์การเคลื่อนไหวมีให้บริการใน Android 4.4 (API ระดับ 19) และ และในอุปกรณ์ ChromeOS ที่ใช้ Android 9 (API ระดับ 28) ขึ้นไป

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

การขึ้นต่อกัน

ไลบรารีการคาดการณ์การเคลื่อนไหวจะนำเสนอการใช้การคาดการณ์ เพิ่มไลบรารีเป็นทรัพยากร Dependency ในไฟล์โมดูล build.gradle ของแอปแล้ว:

dependencies {
    implementation "androidx.input:input-motionprediction:1.0.0-beta01"
}

การใช้งาน

ไลบรารีการคาดการณ์การเคลื่อนไหวประกอบด้วย MotionEventPredictor ของอินเทอร์เฟซ ซึ่งจะกำหนดวิธีการดังต่อไปนี้

  • record(): จัดเก็บออบเจ็กต์ MotionEvent รายการเป็นบันทึกการดำเนินการของผู้ใช้
  • predict(): แสดงผล MotionEvent ที่คาดการณ์ไว้
ประกาศอินสแตนซ์ของ MotionEventPredictor
var motionEventPredictor = MotionEventPredictor.newInstance(view)
ป้อนข้อมูลให้กับตัวคาดการณ์
motionEventPredictor.record(motionEvent)
คาดการณ์

when (motionEvent.action) {
   MotionEvent.ACTION_MOVE -> {
       val predictedMotionEvent = motionEventPredictor?.predict()
       if(predictedMotionEvent != null) {
            // use predicted MotionEvent to inject a new artificial point
       }
   }
}

สิ่งที่ควรและไม่ควรทำสำหรับการคาดการณ์การเคลื่อนไหว

✓ ทำ

นำจุดการคาดการณ์ออกเมื่อมีการเพิ่มจุดที่คาดการณ์ใหม่

✗ อย่า

อย่าใช้จุดการคาดการณ์สำหรับการแสดงผลขั้นสุดท้าย

แอปสำหรับจดโน้ต

ChromeOS จะช่วยให้แอปของคุณประกาศการดำเนินการจดโน้ตบางอย่าง

หากต้องการลงทะเบียนแอปเป็นแอปสำหรับจดโน้ตบน ChromeOS โปรดดูการป้อนข้อมูล ความเข้ากันได้

หากต้องการลงทะเบียนแอปเป็นการจดโน้ตใน Android โปรดดูสร้างการจดโน้ต แอป

Android 14 (API ระดับ 34) เปิดตัว ACTION_CREATE_NOTE Intent ซึ่งทำให้แอปเริ่มกิจกรรมการจดบันทึกบนล็อก บนหน้าจอ

การจดจำหมึกดิจิทัลด้วย ML Kit

ด้วยหมึกดิจิทัล ML Kit การจดจำเสียง แอปของคุณสามารถจดจำข้อความที่เขียนด้วยลายมือบนพื้นผิวดิจิทัลหลายร้อยรายการ ภาษา และจำแนกประเภทภาพร่างได้ด้วย

ML Kit มอบ Ink.Stroke.Builder เพื่อสร้างออบเจ็กต์ Ink รายการที่โมเดลแมชชีนเลิร์นนิงจะประมวลผลได้ เพื่อแปลงการเขียนด้วยลายมือเป็นข้อความ

นอกเหนือจากการจดจำลายมือแล้ว โมเดลยังรับรู้การ ท่าทางสัมผัส เช่น ลบและแวดวง

ดูหมึกดิจิทัล การจดจำ เพื่อดูข้อมูลเพิ่มเติม

แหล่งข้อมูลเพิ่มเติม

คู่มือนักพัฒนาซอฟต์แวร์

Codelab