ตำราอาหารที่มีหน้าจอขนาดใหญ่

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

การให้ดาว

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

คะแนน 5 ดาว ตรงตามเกณฑ์สำหรับระดับที่ 1 ความแตกต่างของหน้าจอขนาดใหญ่
คะแนน 4 ดาว ตรงตามเกณฑ์สำหรับระดับ 2 ที่เพิ่มประสิทธิภาพหน้าจอขนาดใหญ่
การให้คะแนน 3 ดาว ตรงตามเกณฑ์สำหรับระดับ 3, หน้าจอขนาดใหญ่พร้อมใช้งาน
การให้คะแนน 2 ดาว มีความสามารถบนหน้าจอขนาดใหญ่บางส่วน แต่ไม่เป็นไปตามหลักเกณฑ์ด้านคุณภาพของแอปที่มีหน้าจอขนาดใหญ่
การให้คะแนน 1 ดาว ตรงตามความต้องการของ Use Case เฉพาะ แต่ไม่รองรับหน้าจอขนาดใหญ่อย่างถูกต้อง

การรองรับกล้องของ Chromebook

การให้คะแนน 3 ดาว

ทำให้ผู้ใช้ Chromebook ค้นพบธุรกิจของคุณใน Google Play

หากแอปกล้องถ่ายรูปทำงานได้กับฟีเจอร์กล้องพื้นฐานเท่านั้น อย่าให้ร้านค้าแอปป้องกันไม่ให้ผู้ใช้ Chromebook ติดตั้งแอปเพียงเพราะคุณระบุฟีเจอร์กล้องขั้นสูงที่พบในโทรศัพท์ระดับไฮเอนด์โดยไม่ได้ตั้งใจ

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

แนวทางปฏิบัติแนะนำ

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

โปรดประกาศฟีเจอร์กล้องทั้งหมดที่แอปใช้เสมอ และระบุอย่างชัดเจนว่าต้องใช้ฟีเจอร์ดังกล่าวหรือไม่ เพื่อให้แน่ใจว่า App Store จะทำให้แอปพร้อมให้บริการสำหรับอุปกรณ์จำนวนมากที่สุด

ส่วนผสม

  • สิทธิ์ CAMERA: ให้สิทธิ์แอปของคุณเข้าถึงกล้องของอุปกรณ์
  • องค์ประกอบไฟล์ Manifest <uses-feature>: แจ้ง App Store ของฟีเจอร์ที่แอปของคุณใช้
  • แอตทริบิวต์ required: บอกให้ App Store ทราบว่าแอปทํางานได้โดยไม่ต้องมีฟีเจอร์ที่ระบุไว้หรือไม่

จำนวนก้าว

สรุป

ประกาศสิทธิ์ CAMERA ประกาศฟีเจอร์ของกล้องที่ให้การสนับสนุนกล้องขั้นพื้นฐาน ระบุว่าจำเป็นต้องใช้แต่ละฟีเจอร์หรือไม่

1. ประกาศสิทธิ์ CAMERA

เพิ่มสิทธิ์ต่อไปนี้ลงในไฟล์ Manifest ของแอป

<uses-permission android:name="android.permission.CAMERA" />
2. ประกาศฟีเจอร์พื้นฐานของกล้อง

เพิ่มฟีเจอร์ต่อไปนี้ลงในไฟล์ Manifest ของแอป

<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
3. ระบุว่าจำเป็นต้องใช้แต่ละฟีเจอร์หรือไม่

ตั้งค่า android:required="false" สำหรับฟีเจอร์ android.hardware.camera.any เพื่อเปิดใช้การเข้าถึงแอปของคุณโดยอุปกรณ์ที่มีกล้องในตัวหรือกล้องภายนอกทุกประเภท หรือไม่มีกล้องเลย

สำหรับฟีเจอร์อื่นๆ ให้ตั้งค่า android:required="false" เพื่อให้อุปกรณ์ เช่น Chromebook ที่ไม่มีกล้องหลัง ระบบโฟกัสอัตโนมัติ หรือ Flash เข้าถึงแอปของคุณใน App Store ได้

ผลลัพธ์

ผู้ใช้ Chromebook จะดาวน์โหลดและติดตั้งแอปของคุณจาก Google Play และ App Store อื่นๆ ได้ และอุปกรณ์ที่รองรับกล้องเต็มรูปแบบ เช่น โทรศัพท์ จะไม่ถูกจำกัดในฟังก์ชันการทำงานของกล้อง

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

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

โปรดดูข้อมูลเพิ่มเติมที่หัวข้อฟีเจอร์ของฮาร์ดแวร์ของกล้องในเอกสารประกอบของ <uses-feature>

การวางแนวของแอปบนโทรศัพท์ถูกจำกัด แต่ไม่รวมถึงอุปกรณ์ที่มีหน้าจอขนาดใหญ่

การให้คะแนน 2 ดาว

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

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

แนวทางปฏิบัติแนะนำ

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

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

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

ส่วนผสม

  • screenOrientation: การตั้งค่าไฟล์ Manifest ของแอปที่ช่วยให้คุณระบุวิธีที่แอปตอบสนองต่อการเปลี่ยนการวางแนวอุปกรณ์ได้
  • Jetpack WindowManager: ชุดไลบรารีที่ช่วยให้คุณกำหนดขนาดและสัดส่วนภาพของหน้าต่างแอปได้ เข้ากันได้กับ API ระดับ 14 แบบย้อนหลัง
  • Activity#setRequestedOrientation(): วิธีการที่คุณเปลี่ยนการวางแนวของแอปขณะรันไทม์

จำนวนก้าว

สรุป

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

1. ระบุการตั้งค่าการวางแนวในไฟล์ Manifest ของแอป

คุณจะหลีกเลี่ยงการประกาศองค์ประกอบ screenOrientation ของไฟล์ Manifest ของแอป (ในกรณีที่การวางแนวมีค่าเริ่มต้นเป็น unspecified) หรือตั้งการวางแนวหน้าจอเป็น fullUser ก็ได้ หากผู้ใช้ไม่ได้ล็อกการหมุนตามเซ็นเซอร์ แอปจะรองรับการวางแนวอุปกรณ์ทุกแบบ

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

ข้อแตกต่างระหว่างการใช้ unspecified กับ fullUser นั้นมีนัยสำคัญแต่ก็สำคัญ หากคุณไม่ได้ประกาศค่า screenOrientation ระบบจะเลือกการวางแนวและนโยบายที่ระบบใช้เพื่อกำหนดการวางแนวอาจแตกต่างกันไปในแต่ละอุปกรณ์ ในทางกลับกัน การระบุ fullUser จะตรงกับพฤติกรรมที่ผู้ใช้กำหนดไว้สำหรับอุปกรณ์มากยิ่งขึ้น หากผู้ใช้ล็อกการหมุนตามเซ็นเซอร์ไว้ แอปก็จะทำตามค่ากำหนดของผู้ใช้ มิฉะนั้น ระบบจะอนุญาตให้ใช้การวางแนวหน้าจอในสี่ระดับที่เป็นไปได้ (แนวตั้ง แนวนอน กลับแนวตั้ง หรือกลับด้านแนวนอน) โปรดดู android:screenOrientation

2. กำหนดขนาดหน้าจอ

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

เพิ่มไลบรารี Jetpack WindowManager ลงในไฟล์ build.gradle หรือ build.gradle.kts ของโมดูลโดยทำดังนี้

Kotlin

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

ดึงดูด

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

ใช้เมธอด Jetpack WindowManager WindowMetricsCalculator#computeMaximumWindowMetrics() เพื่อดูขนาดหน้าจอของอุปกรณ์เป็นออบเจ็กต์ WindowMetrics เมตริกหน้าต่างสามารถนำไปเปรียบเทียบกับคลาสขนาดหน้าต่างเพื่อตัดสินใจว่าจะจำกัดการวางแนวเมื่อใด

คลาสขนาดของ Windows จะระบุเบรกพอยท์ระหว่างหน้าจอขนาดเล็กและหน้าจอขนาดใหญ่

ใช้เบรกพอยท์ WindowWidthSizeClass#COMPACT และ WindowHeightSizeClass#COMPACT เพื่อกำหนดขนาดหน้าจอ ดังนี้

Kotlin

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass = WindowSizeClass.compute(width/density, height/density)

    return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT ||
        windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT
}

Java

/** Determines whether the device has a compact screen. **/
private boolean compactScreen() {
    WindowMetrics metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this);
    int width = metrics.getBounds().width();
    int height = metrics.getBounds().height();
    float density = getResources().getDisplayMetrics().density;
    WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
    return windowSizeClass.getWindowWidthSizeClass() == WindowWidthSizeClass.COMPACT ||
                windowSizeClass.getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT;
}
    หมายเหตุ:
  • ตัวอย่างข้างต้นถูกนำมาใช้เป็นวิธีของกิจกรรม ดังนั้น กิจกรรมนี้จึงลดการอ้างอิงเป็น this ในอาร์กิวเมนต์ของ computeMaximumWindowMetrics()
  • ระบบใช้เมธอด computeMaximumWindowMetrics() แทน computeCurrentWindowMetrics() เนื่องจากสามารถเปิดแอปได้ในโหมดหลายหน้าต่าง ซึ่งจะไม่สนใจการตั้งค่าการวางแนวหน้าจอ ไม่มีจุดใดในการกำหนดขนาดหน้าต่างแอปและลบล้างการตั้งค่าการวางแนว เว้นแต่หน้าต่างของแอปจะเป็นหน้าจออุปกรณ์ทั้งหน้าจอ

โปรดดู WindowManager สำหรับคำแนะนำเกี่ยวกับการประกาศทรัพยากร Dependency เพื่อทำให้เมธอด computeMaximumWindowMetrics() พร้อมใช้งานในแอปของคุณ

3. ลบล้างการตั้งค่าไฟล์ Manifest ของแอป

เมื่อคุณพิจารณาแล้วว่าอุปกรณ์มีขนาดหน้าจอที่กะทัดรัด คุณสามารถเรียกใช้ Activity#setRequestedOrientation() เพื่อลบล้างการตั้งค่า screenOrientation ของไฟล์ Manifest ได้ ดังนี้

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    val container: ViewGroup = binding.container

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(object : View(this) {
        override fun onConfigurationChanged(newConfig: Configuration?) {
            super.onConfigurationChanged(newConfig)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

Java

@Override
protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstanceState);
    if (compactScreen()) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
    }
    ...
    // Replace with a known container that you can safely add a
    // view to where the view won't affect the layout and the view
    // won't be replaced.
    ViewGroup container = binding.container;

    // Add a utility view to the container to hook into
    // View.onConfigurationChanged. This is required for all
    // activities, even those that don't handle configuration
    // changes. You can't use Activity.onConfigurationChanged,
    // since there are situations where that won't be called when
    // the configuration changes. View.onConfigurationChanged is
    // called in those scenarios.
    container.addView(new View(this) {
        @Override
        protected void onConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            if (compactScreen()) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
            }
        }
    });
}

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

ผลลัพธ์

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

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

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

หยุดเล่นสื่อชั่วคราวและเล่นต่อด้วย Spacebar ของแป้นพิมพ์ภายนอก

คะแนน 4 ดาว

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

เมื่อสื่อเป็นองค์ประกอบเดียว (เช่น การเล่นวิดีโอแบบเต็มหน้าจอ) ตอบสนองต่อเหตุการณ์การกดแป้น ที่ระดับกิจกรรมหรือใน Jetpack Compose ที่ระดับหน้าจอ

แนวทางปฏิบัติแนะนำ

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

ส่วนผสม

  • KEYCODE_SPACE: ค่าคงที่ของโค้ดคีย์สำหรับ Spacebar

เขียน

  • onPreviewKeyEvent: Modifier ที่ช่วยให้คอมโพเนนต์สกัดกั้นเหตุการณ์สำคัญของฮาร์ดแวร์เมื่อโฟกัสอยู่ (หรือเหตุการณ์ย่อย 1 รายการ)
  • onKeyEvent: คล้ายกับ onPreviewKeyEvent โดย Modifier นี้จะช่วยให้คอมโพเนนต์สกัดกั้นเหตุการณ์สำคัญของฮาร์ดแวร์เมื่อโฟกัสอยู่ (หรือองค์ประกอบย่อยของฮาร์ดแวร์)

ยอดดู

  • onKeyUp(): มีการเรียกใช้เมื่อมีการปล่อยคีย์และไม่ได้จัดการโดยมุมมองภายในกิจกรรม

จำนวนก้าว

สรุป

แอปที่อิงตามการดูและแอปที่อิงตาม Jetpack Compose จะตอบสนองต่อการกดแป้นบนแป้นพิมพ์ในลักษณะเดียวกัน กล่าวคือ แอปจะต้องคอยฟังเหตุการณ์การกดแป้น กรองเหตุการณ์ และตอบสนองต่อการกดแป้นที่เลือก เช่น การกดแป้น Spacebar

1. ฟังเหตุการณ์ของแป้นพิมพ์

ยอดดู

ในกิจกรรมในแอป ให้ลบล้างเมธอด onKeyUp() ดังนี้

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    ...
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    ...
}

จะมีการเรียกใช้เมธอดนี้ทุกครั้งที่มีการปล่อยแป้นกด จึงจะเริ่มทำงานหนึ่งครั้งต่อการกดแป้นพิมพ์ทุกครั้ง

เขียน

เมื่อใช้ Jetpack Compose คุณจะใช้ประโยชน์จากแป้นกดร่วม onPreviewKeyEvent หรือ onKeyEvent ภายในหน้าจอที่จัดการการกดแป้นพิมพ์ได้

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

หรือ

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

2. การกด Spacebar ตัวกรอง

ภายในเมธอด onKeyUp() หรือเมธอดเขียน onPreviewKeyEvent และ onKeyEvent ให้กรองหา KeyEvent.KEYCODE_SPACE เพื่อส่งเหตุการณ์ที่ถูกต้องไปยังคอมโพเนนต์สื่อ

ยอดดู

Kotlin

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback()
    return true
}
return false

Java

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback();
    return true;
}
return false;

เขียน

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

หรือ

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

ผลลัพธ์

ตอนนี้แอปของคุณสามารถตอบสนองต่อการกดแป้น Spacebar เพื่อหยุดชั่วคราวและเล่นวิดีโอหรือสื่ออื่นๆ ต่อได้แล้ว

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

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

การปฏิเสธฝ่ามือที่ใช้สไตลัส

คะแนน 5 ดาว

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

แนวทางปฏิบัติแนะนำ

แอปของคุณต้องระบุกิจกรรมการสัมผัสที่เกินมาและละเว้นกิจกรรมดังกล่าว Android ยกเลิกการสัมผัสฝ่ามือด้วยการส่งวัตถุ MotionEvent ตรวจสอบวัตถุเพื่อหา ACTION_CANCEL หรือ ACTION_POINTER_UP และ FLAG_CANCELED เพื่อดูว่าจะปฏิเสธท่าทางสัมผัสที่เกิดจากการแตะฝ่ามือหรือไม่

ส่วนผสม

  • MotionEvent: หมายถึงเหตุการณ์การแตะและการเคลื่อนไหว มีข้อมูลที่จำเป็นต่อการพิจารณาว่าควรละเว้นเหตุการณ์หรือไม่
  • OnTouchListener#onTouch(): รับออบเจ็กต์ MotionEvent รายการ
  • MotionEvent#getActionMasked(): แสดงผลการทำงานที่เชื่อมโยงกับเหตุการณ์การเคลื่อนไหว
  • ACTION_CANCEL: ค่าคงที่ MotionEvent ที่บ่งชี้ว่าท่าทางสัมผัสควรเลิกทำ
  • ACTION_POINTER_UP: ค่าคงที่ MotionEvent ที่บ่งชี้ว่ามีตัวชี้ที่ไม่ใช่ตัวชี้แรกขึ้น (กล่าวคือ ยกเลิกการสัมผัสหน้าจอของอุปกรณ์แล้ว)
  • FLAG_CANCELED: ค่าคงที่ MotionEvent ที่บ่งชี้ว่าตัวชี้เลื่อนขึ้นทำให้เกิดการแตะโดยไม่ได้ตั้งใจ เพิ่มลงใน ACTION_POINTER_UP และ ACTION_CANCEL เหตุการณ์ใน Android 13 (API ระดับ 33) ขึ้นไปแล้ว

จำนวนก้าว

สรุป

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

  • เหตุการณ์ที่มีจุดเดียว — ตรวจสอบเพื่อหา ACTION_CANCEL ใน Android 13 ขึ้นไป ให้ตรวจสอบ FLAG_CANCELED ด้วย
  • เหตุการณ์ที่มีหลายจุด — ใน Android 13 ขึ้นไป ให้ตรวจสอบ ACTION_POINTER_UP และ FLAG_CANCELED

ตอบกลับกิจกรรม ACTION_CANCEL และ ACTION_POINTER_UP/FLAG_CANCELED

1. รับออบเจ็กต์เหตุการณ์การเคลื่อนไหว

เพิ่ม OnTouchListener ลงในแอป

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        // Process motion event.
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    // Process motion event.
});
2. ระบุการดำเนินการและการแจ้งเหตุการณ์

ตรวจหา ACTION_CANCEL ซึ่งระบุเหตุการณ์ตัวชี้เดียวใน API ทุกระดับ ใน Android 13 ขึ้นไป ให้ตรวจสอบ ACTION_POINTER_UP เพื่อหา FLAG_CANCELED.

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        when (event.actionMasked) {
            MotionEvent.ACTION_CANCEL -> {
                //Process canceled single-pointer motion event for all SDK versions.
            }
            MotionEvent.ACTION_POINTER_UP -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                   (event.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                    //Process canceled multi-pointer motion event for Android 13 and higher.
                }
            }
        }
        true
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_CANCEL:
            // Process canceled single-pointer motion event for all SDK versions.
        case MotionEvent.ACTION_UP:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
               (event.getFlags() & MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                //Process canceled multi-pointer motion event for Android 13 and higher.
            }
    }
    return true;
});
3. เลิกทำท่าทางสัมผัส

เมื่อระบุการสัมผัสด้วยฝ่ามือแล้ว คุณสามารถยกเลิกเอฟเฟกต์บนหน้าจอของท่าทางสัมผัสได้

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

ผลลัพธ์

ตอนนี้แอปสามารถระบุและปฏิเสธการสัมผัสแบบใช้ฝ่ามือสำหรับกิจกรรมที่มีหลายตัวชี้ใน Android 13 ขึ้นไปในระดับ API และสำหรับกิจกรรมแบบใช้จุดเดียวในทุกระดับ API

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

โปรดดูข้อมูลต่อไปนี้สำหรับข้อมูลเพิ่มเติม

การจัดการสถานะ WebView

การให้คะแนน 3 ดาว

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

แนวทางปฏิบัติแนะนำ

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

ส่วนผสม

  • android:configChanges: แอตทริบิวต์ขององค์ประกอบ <activity> ของไฟล์ Manifest แสดงรายการการเปลี่ยนแปลงการกำหนดค่าที่จัดการโดยกิจกรรม
  • View#invalidate(): วิธีที่ทําให้เกิดมุมมองใหม่ รับค่าโดย WebView

จำนวนก้าว

สรุป

หากต้องการบันทึกสถานะ WebView ให้หลีกเลี่ยงการสร้าง Activity ใหม่ให้ได้มากที่สุด แล้วปล่อยให้ WebView เป็นโมฆะเพื่อให้ปรับขนาดในขณะที่รักษาสถานะไว้ได้

1. เพิ่มการเปลี่ยนแปลงการกำหนดค่าลงในไฟล์ AndroidManifest.xml ของแอป

หลีกเลี่ยงการสร้างกิจกรรมใหม่โดยการระบุการเปลี่ยนแปลงการกำหนดค่าที่แอปของคุณจัดการ (ไม่ใช่โดยระบบ) ดังนี้

<activity
  android:name=".MyActivity"
  android:configChanges="screenLayout|orientation|screenSize
      |keyboard|keyboardHidden|smallestScreenSize" />

2. ทำให้ WebView เป็นโมฆะทุกครั้งที่แอปได้รับการเปลี่ยนแปลงการกำหนดค่า

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    webView.invalidate()
}

Java

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    webview.invalidate();
}

ขั้นตอนนี้ใช้ได้กับระบบมุมมองเท่านั้น เนื่องจาก Jetpack Compose ไม่จำเป็นต้องทำให้สิ่งใดใช้งานไม่ได้เพื่อปรับขนาดองค์ประกอบ Composable อย่างถูกต้อง อย่างไรก็ตาม Compose จะสร้าง WebView ใหม่บ่อยครั้งหากจัดการไม่ถูกต้อง ใช้ Wrapper ของ Accompanist WebView เพื่อบันทึกและคืนค่าสถานะ WebView ในแอปเขียน

ผลลัพธ์

ตอนนี้คอมโพเนนต์ WebView ของแอปจะยังคงมีสถานะและตำแหน่งการเลื่อนเมื่อมีการเปลี่ยนแปลงการกำหนดค่าหลายรายการ ตั้งแต่การปรับขนาด การวางแนว ไปจนถึงการพับและการกางออก

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

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

การจัดการสถานะ RecyclerView

การให้คะแนน 3 ดาว

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

แนวทางปฏิบัติแนะนำ

RecyclerView ควรคงสถานะไว้โดยเฉพาะ โดยเฉพาะตำแหน่งการเลื่อน และสถานะขององค์ประกอบรายการระหว่างการเปลี่ยนแปลงการกำหนดค่าทั้งหมด

ส่วนผสม

  • RecyclerView.Adapter#setStateRestorationPolicy(): ระบุวิธีที่ RecyclerView.Adapter จะกู้คืนสถานะหลังจากการเปลี่ยนแปลงการกําหนดค่า
  • ViewModel: คงสถานะสำหรับกิจกรรมหรือส่วนย่อย

จำนวนก้าว

สรุป

ตั้งค่านโยบายการกู้คืนสถานะของ RecyclerView.Adapter เพื่อบันทึกตำแหน่งการเลื่อน RecyclerView บันทึกสถานะของรายการ RecyclerView รายการ เพิ่มสถานะของรายการสินค้าลงในอะแดปเตอร์ RecyclerView และคืนค่าสถานะของรายการเมื่อผูกกับ ViewHolder

1. เปิดใช้นโยบายการกู้คืนสถานะ Adapter

เปิดใช้นโยบายการกู้คืนสถานะของอะแดปเตอร์ RecyclerView เพื่อรักษาตำแหน่งการเลื่อนของ RecyclerView ในการเปลี่ยนแปลงการกำหนดค่า เพิ่มข้อกำหนดของนโยบายลงในตัวสร้างอะแดปเตอร์ดังนี้

Kotlin

class MyAdapter() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    init {
        stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
    }
    ...
}

Java

class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    public Adapter() {
        setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
    }
    ...
}

2. บันทึกสถานะของรายการการเก็บสถานะ

บันทึกสถานะของรายการ RecyclerView ที่ซับซ้อน เช่น รายการที่มีองค์ประกอบ EditText เช่น หากต้องการบันทึกสถานะของ EditText ให้เพิ่ม Callback ที่คล้ายกับเครื่องจัดการ onClick เพื่อบันทึกการเปลี่ยนแปลงข้อความ กำหนดข้อมูลที่จะบันทึกใน Callback ดังนี้

Kotlin

input.addTextChangedListener(
    afterTextChanged = { text ->
        text?.let {
            // Save state here.
        }
    }
)

Java

input.addTextChangedListener(new TextWatcher() {

    ...

    @Override
    public void afterTextChanged(Editable s) {
        // Save state here.
    }
});

ประกาศการติดต่อกลับใน Activity หรือ Fragment ใช้ ViewModel เพื่อจัดเก็บรัฐ

3. เพิ่มสถานะรายการใน Adapter

เพิ่มสถานะของรายการต่างๆ ในRecyclerView.Adapter ส่งสถานะรายการไปยังตัวสร้างอะแดปเตอร์เมื่อสร้างโฮสต์ Activity หรือ Fragment:

Kotlin

val adapter = MyAdapter(items, viewModel.retrieveState())

Java

MyAdapter adapter = new MyAdapter(items, viewModel.retrieveState());

4. กู้คืนสถานะรายการในViewHolderของอะแดปเตอร์

ใน RecyclerView.Adapter เมื่อคุณเชื่อมโยง ViewHolder กับรายการ ให้คืนค่าสถานะของรายการดังกล่าวดังนี้

Kotlin

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    ...
    val item = items[position]
    val state = states.firstOrNull { it.item == item }

    if (state != null) {
        holder.restore(state)
    }
}

Java

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ...
    Item item = items[position];
    Arrays.stream(states).filter(state -> state.item == item)
        .findFirst()
        .ifPresent(state -> holder.restore(state));
}

ผลลัพธ์

ตอนนี้ RecyclerView คืนค่าตำแหน่งการเลื่อนและสถานะของแต่ละรายการใน RecyclerView ได้แล้ว

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

การจัดการแป้นพิมพ์ที่ถอดออกได้

การให้คะแนน 3 ดาว

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

แนวทางปฏิบัติแนะนำ

แอปที่ปรับให้เหมาะกับหน้าจอขนาดใหญ่รองรับอุปกรณ์อินพุตทุกประเภทตั้งแต่ แป้นพิมพ์ซอฟต์แวร์และฮาร์ดแวร์กับสไตลัส เมาส์ แทร็กแพด และอุปกรณ์ต่อพ่วงอื่นๆ อุปกรณ์

การรองรับแป้นพิมพ์ภายนอกจะมีการเปลี่ยนแปลงการกำหนดค่า ซึ่งคุณสามารถ จัดการได้ 2 วิธี ได้แก่

  1. ปล่อยให้ระบบสร้างกิจกรรมที่กำลังทำงานอยู่ในขณะนี้อีกครั้ง โดยคุณมีหน้าที่จัดการสถานะของแอป
  2. จัดการการเปลี่ยนแปลงการกำหนดค่าด้วยตนเอง (ระบบจะไม่สร้างกิจกรรมใหม่) โดยทำดังนี้
    • ประกาศค่าของการกำหนดค่าทั้งหมดที่เกี่ยวข้องกับแป้นพิมพ์
    • สร้างเครื่องจัดการการเปลี่ยนแปลงการกำหนดค่า

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

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

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

ส่วนผสม

  • android:configChanges: แอตทริบิวต์ขององค์ประกอบ <activity> ของไฟล์ Manifest ของแอป แจ้งระบบเกี่ยวกับการเปลี่ยนแปลงการกำหนดค่าที่แอปจัดการ
  • View#onConfigurationChanged(): วิธีที่ตอบสนองต่อการแพร่พันธุ์ การกำหนดค่าแอป

จำนวนก้าว

สรุป

ประกาศแอตทริบิวต์ configChanges และเพิ่มค่าที่เกี่ยวข้องกับแป้นพิมพ์ เพิ่ม View ไปยังลำดับชั้นการแสดงผลของกิจกรรมและรอรับการเปลี่ยนแปลงการกำหนดค่า

1. ประกาศแอตทริบิวต์ configChanges

อัปเดตองค์ประกอบ <activity> ในไฟล์ Manifest ของแอปด้วยการเพิ่มค่า keyboard|keyboardHidden ลงในรายการการเปลี่ยนแปลงการกำหนดค่าที่มีการจัดการอยู่แล้ว ดังนี้

<activity
            android:configChanges="...|keyboard|keyboardHidden">

2. เพิ่มมุมมองว่างในลำดับชั้นการแสดงผล

ประกาศข้อมูลพร็อพเพอร์ตี้ใหม่และเพิ่มโค้ดเครื่องจัดการของคุณภายในเมธอด onConfigurationChanged() ของมุมมอง ดังนี้

Kotlin

val v = object : View(this) {
  override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // Handler code here.
  }
}

Java

View v = new View(this) {
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Handler code here.
    }
};

ผลลัพธ์

ตอนนี้แอปจะตอบสนองต่อแป้นพิมพ์ภายนอกที่แนบหรือถอดออกโดยไม่สร้างกิจกรรมที่ทำงานอยู่ในปัจจุบันใหม่

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

หากต้องการดูวิธีบันทึกสถานะ UI ของแอประหว่างการเปลี่ยนแปลงการกำหนดค่า เช่น การแนบแป้นพิมพ์หรือการถอดออก โปรดดูบันทึกสถานะ UI