ใช้ไลบรารีแอป Android สำหรับรถยนต์

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

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

ก่อนเริ่มต้น

  1. อ่านหน้าDesign for Driving ที่ครอบคลุมถึงคลังแอปในรถ
  2. โปรดอ่านคําศัพท์และแนวคิดสําคัญในส่วนต่อไปนี้
  3. ทำความคุ้นเคยกับUI ของระบบ Android Auto และการออกแบบ Android Automotive OS
  4. โปรดอ่านบันทึกประจำรุ่น
  5. ตรวจสอบตัวอย่าง

คําศัพท์และแนวคิดสําคัญ

โมเดลและเทมเพลต
อินเทอร์เฟซผู้ใช้จะแสดงด้วยกราฟของออบเจ็กต์โมเดลที่สามารถจัดเรียงร่วมกันได้หลายวิธีตามที่เทมเพลตที่เป็นเจ้าของอนุญาต เทมเพลตคือชุดย่อยของโมเดลที่ทําหน้าที่เป็นรูทในกราฟเหล่านั้นได้ โมเดลประกอบด้วยข้อมูลที่แสดงต่อผู้ใช้ในรูปแบบข้อความและรูปภาพ รวมถึงแอตทริบิวต์ในการกำหนดค่าแง่มุมต่างๆ ของลักษณะที่ปรากฏของข้อมูล เช่น สีข้อความหรือขนาดรูปภาพ โฮสต์จะแปลงโมเดลเป็นมุมมองที่ออกแบบมาเพื่อปฏิบัติตามมาตรฐานความรบกวนของผู้ขับขี่ และคำนึงถึงรายละเอียดต่างๆ เช่น ปัจจัยด้านหน้าจอรถยนต์และรูปแบบอินพุตที่หลากหลาย
เป็นเจ้าภาพ
โฮสต์คือคอมโพเนนต์แบ็กเอนด์ที่ใช้ฟังก์ชันการทำงานที่ API ของไลบรารีนำเสนอเพื่อให้แอปทำงานในรถได้ ความรับผิดชอบของโฮสต์มีตั้งแต่การค้นพบแอปและจัดการวงจรของแอปไปจนถึงการเปลี่ยนรูปแบบเป็นมุมมองและการแจ้งเตือนแอปเกี่ยวกับการโต้ตอบของผู้ใช้ บนอุปกรณ์เคลื่อนที่ Android Auto จะใช้โฮสต์นี้ ใน Android Automotive OS ระบบจะติดตั้งโฮสต์นี้เป็นแอประบบ
ข้อจำกัดของเทมเพลต
เทมเพลตแต่ละแบบจะบังคับใช้ข้อจำกัดในเนื้อหาของโมเดล เช่น เทมเพลตรายการมีการจํากัดจํานวนรายการที่แสดงต่อผู้ใช้ได้ เทมเพลตยังมีข้อจํากัดในการเชื่อมต่อเพื่อสร้างเวิร์กโฟลว์ของงานด้วย เช่น แอปสามารถพุชเทมเพลตไปยังกองหน้าจอได้สูงสุด 5 รายการเท่านั้น ดูรายละเอียดเพิ่มเติมได้ในข้อจำกัดของเทมเพลต
Screen
Screen เป็นคลาสที่มาจากไลบรารีที่แอปนำมาใช้เพื่อจัดการอินเทอร์เฟซผู้ใช้ที่แสดงต่อผู้ใช้ Screen มีวงจรและให้กลไกสําหรับแอปในการส่งเทมเพลตเพื่อแสดงเมื่อหน้าจอมองเห็นได้ นอกจากนี้ คุณยังพุชและป๊อปอินสแตนซ์ Screen ไปยังและจากกอง Screen ได้ด้วย ซึ่งช่วยให้มั่นใจว่าอินสแตนซ์จะเป็นไปตามข้อจำกัดของโฟลว์เทมเพลต
CarAppService
CarAppService เป็นคลาส Service นามธรรมที่แอปของคุณต้องใช้และส่งออกเพื่อให้โฮสต์ค้นพบและจัดการได้ CarAppService ของแอปคุณมีหน้าที่รับผิดชอบในการตรวจสอบว่าการเชื่อมต่อโฮสต์เชื่อถือได้โดยใช้ createHostValidator และหลังจากนั้นให้อินสแตนซ์ Session สำหรับการเชื่อมต่อแต่ละรายการโดยใช้ onCreateSession
Session

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

เมื่อเริ่มSession เช่น เมื่อเปิดแอปเป็นครั้งแรก โฮสต์จะส่งคําขอScreenแรกให้แสดงโดยใช้เมธอดonCreateScreen

ติดตั้งไลบรารีแอปในรถ

ดูวิธีการเพิ่มไลบรารีลงในแอปได้จากหน้ารุ่นของไลบรารี Jetpack

กำหนดค่าไฟล์ Manifest ของแอป

ก่อนที่จะสร้างแอปรถยนต์ ให้กําหนดค่าไฟล์ Manifest ของแอปดังนี้

ประกาศ CarAppService

โฮสต์จะเชื่อมต่อกับแอปของคุณผ่านการใช้งาน CarAppService คุณต้องประกาศบริการนี้ในไฟล์ Manifest เพื่อให้โฮสต์ค้นพบและเชื่อมต่อกับแอปของคุณ

นอกจากนี้ คุณยังต้องประกาศหมวดหมู่ของแอปในองค์ประกอบ <category> ของตัวกรอง Intent ของแอปด้วย ดูรายการหมวดหมู่แอปที่รองรับสำหรับค่าที่อนุญาตสำหรับองค์ประกอบนี้

ข้อมูลโค้ดต่อไปนี้แสดงวิธีประกาศบริการแอปรถยนต์สําหรับแอปจุดที่น่าสนใจในไฟล์ Manifest

<application>
    ...
   <service
       ...
        android:name=".MyCarAppService"
        android:exported="true">
      <intent-filter>
        <action android:name="androidx.car.app.CarAppService"/>
        <category android:name="androidx.car.app.category.POI"/>
      </intent-filter>
    </service>

    ...
<application>

หมวดหมู่แอปที่รองรับ

ประกาศหมวดหมู่ของแอปโดยเพิ่มค่าหมวดหมู่ต่อไปนี้อย่างน้อย 1 รายการในตัวกรอง Intent เมื่อคุณประกาศ CarAppService ตามที่อธิบายไว้ในส่วนก่อนหน้า

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

ระบุชื่อและไอคอนของแอป

คุณต้องระบุชื่อแอปและไอคอนที่โฮสต์สามารถใช้เพื่อแสดงแอปของคุณใน UI ของระบบ

คุณระบุชื่อแอปและไอคอนที่ใช้แสดงแอปได้โดยใช้แอตทริบิวต์ label และ icon ของ CarAppService ดังนี้

...
<service
   android:name=".MyCarAppService"
   android:exported="true"
   android:label="@string/my_app_name"
   android:icon="@drawable/my_app_icon">
   ...
</service>
...

หากไม่ได้ประกาศป้ายกำกับหรือไอคอนในองค์ประกอบ <service> โฮสต์จะใช้ค่าที่ระบุสำหรับองค์ประกอบ <application>

ตั้งค่าธีมที่กำหนดเอง

หากต้องการตั้งค่าธีมที่กำหนดเองสำหรับแอปรถยนต์ ให้เพิ่มองค์ประกอบ <meta-data> ในไฟล์ Manifest ดังนี้

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

จากนั้นประกาศทรัพยากรสไตล์เพื่อกำหนดแอตทริบิวต์ต่อไปนี้สำหรับธีมแอปรถยนต์ที่กำหนดเอง

<resources>
  <style name="MyCarAppTheme">
    <item name="carColorPrimary">@layout/my_primary_car_color</item>
    <item name="carColorPrimaryDark">@layout/my_primary_dark_car_color</item>
    <item name="carColorSecondary">@layout/my_secondary_car_color</item>
    <item name="carColorSecondaryDark">@layout/my_secondary_dark_car_color</item>
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

ระดับ Car App API

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

ประกาศระดับ Car App API ขั้นต่ำที่แอปของคุณรองรับในไฟล์ AndroidManifest.xml ดังนี้

<manifest ...>
    <application ...>
        <meta-data
            android:name="androidx.car.app.minCarApiLevel"
            android:value="1"/>
    </application>
</manifest>

ดูรายละเอียดเกี่ยวกับวิธีรักษาความเข้ากันได้ย้อนหลังและประกาศระดับ API ขั้นต่ำที่จําเป็นในการใช้ฟีเจอร์ได้จากเอกสารประกอบของคำอธิบายประกอบ RequiresCarApi ดูคำจำกัดความของระดับ API ที่จำเป็นต้องใช้เพื่อใช้ฟีเจอร์บางอย่างของไลบรารีแอปสำหรับรถยนต์ได้ในเอกสารอ้างอิงสำหรับ CarAppApiLevels

สร้าง CarAppService และเซสชัน

แอปของคุณต้องขยายคลาส CarAppService และใช้งานเมธอด onCreateSession ของคลาส ซึ่งจะแสดงผลอินสแตนซ์ Session ที่สอดคล้องกันกับการเชื่อมต่อกับโฮสต์ปัจจุบัน ดังนี้

Kotlin

class HelloWorldService : CarAppService() {
    ...
    override fun onCreateSession(): Session {
        return HelloWorldSession()
    }
    ...
}

Java

public final class HelloWorldService extends CarAppService {
    ...
    @Override
    @NonNull
    public Session onCreateSession() {
        return new HelloWorldSession();
    }
    ...
}

อินสแตนซ์ Session มีหน้าที่รับผิดชอบในการส่งคืนอินสแตนซ์ Screen เพื่อใช้เมื่อเริ่มแอปเป็นครั้งแรก

Kotlin

class HelloWorldSession : Session() {
    ...
    override fun onCreateScreen(intent: Intent): Screen {
        return HelloWorldScreen(carContext)
    }
    ...
}

Java

public final class HelloWorldSession extends Session {
    ...
    @Override
    @NonNull
    public Screen onCreateScreen(@NonNull Intent intent) {
        return new HelloWorldScreen(getCarContext());
    }
    ...
}

หากต้องการจัดการสถานการณ์ที่แอปในรถต้องเริ่มต้นจากหน้าจอที่ไม่ใช่หน้าจอหลักหรือหน้าจอ Landing Page ของแอป เช่น การจัดการ Deep Link คุณสามารถกำหนดสแต็กหน้าจอที่ซ้อนกันไว้ล่วงหน้าได้โดยใช้ ScreenManager.push ก่อนกลับมาจาก onCreateScreen การจัดเตรียมข้อมูลล่วงหน้าช่วยให้ผู้ใช้ไปยังหน้าจอก่อนหน้าได้จากหน้าจอแรกที่แอปแสดง

สร้างหน้าจอเริ่มต้น

คุณสร้างหน้าจอที่แอปแสดงโดยกำหนดคลาสที่ขยายคลาส Screen และใช้งานเมธอด onGetTemplate ของคลาส ซึ่งจะแสดงผลอินสแตนซ์ Template ที่แสดงสถานะของ UI ในหน้าจอรถยนต์

ข้อมูลโค้ดต่อไปนี้แสดงวิธีประกาศ Screen ที่ใช้เทมเพลต PaneTemplate เพื่อแสดงสตริง "Hello world" ง่ายๆ

Kotlin

class HelloWorldScreen(carContext: CarContext) : Screen(carContext) {
    override fun onGetTemplate(): Template {
        val row = Row.Builder().setTitle("Hello world!").build()
        val pane = Pane.Builder().addRow(row).build()
        return PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build()
    }
}

Java

public class HelloWorldScreen extends Screen {
    @NonNull
    @Override
    public Template onGetTemplate() {
        Row row = new Row.Builder().setTitle("Hello world!").build();
        Pane pane = new Pane.Builder().addRow(row).build();
        return new PaneTemplate.Builder(pane)
            .setHeaderAction(Action.APP_ICON)
            .build();
    }
}

คลาส CarContext

คลาส CarContext เป็นคลาสย่อย ContextWrapper ที่เข้าถึงอินสแตนซ์ Session และ Screen ได้ ซึ่งจะให้สิทธิ์เข้าถึงบริการต่างๆ ในรถยนต์ เช่น ScreenManager สำหรับการจัดการกองซ้อนหน้าจอ, AppManager สำหรับฟังก์ชันการทำงานทั่วไปที่เกี่ยวข้องกับแอป เช่น การเข้าถึงออบเจ็กต์ Surface สำหรับการวาดแผนที่ และ NavigationManager ที่แอปเบนดิงแบบเลี้ยวต่อเลี้ยวใช้เพื่อสื่อสารข้อมูลเมตาการนำทางและเหตุการณ์อื่นๆ ที่เกี่ยวข้องกับการนำทางกับโฮสต์

ดูรายการฟังก์ชันการทำงานของคลังที่มีให้ใช้งานในแอปการนำทางอย่างครอบคลุมได้ที่เข้าถึงเทมเพลตการนำทาง

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

ใช้การไปยังส่วนต่างๆ ของหน้าจอ

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

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

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

Kotlin

val template = MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener { screenManager.push(NextScreen(carContext)) }
            .build())
    .build()

Java

MessageTemplate template = new MessageTemplate.Builder("Hello world!")
    .setHeaderAction(Action.BACK)
    .addAction(
        new Action.Builder()
            .setTitle("Next screen")
            .setOnClickListener(
                () -> getScreenManager().push(new NextScreen(getCarContext())))
            .build())
    .build();

ออบเจ็กต์ Action.BACK คือ Action มาตรฐานที่เรียกใช้ ScreenManager.pop โดยอัตโนมัติ คุณสามารถลบล้างลักษณะการทำงานนี้ได้โดยใช้อินสแตนซ์ OnBackPressedDispatcher ที่พร้อมใช้งานจาก CarContext

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

รีเฟรชเนื้อหาของเทมเพลต

แอปสามารถขอให้เนื้อหาของ Screen ใช้งานไม่ได้โดยการเรียกใช้เมธอด Screen.invalidate จากนั้นโฮสต์จะเรียกใช้เมธอด Screen.onGetTemplate ของแอปเพื่อเรียกข้อมูลเทมเพลตที่มีเนื้อหาใหม่

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

เราขอแนะนําให้คุณจัดโครงสร้างหน้าจอเพื่อให้มีการแมปแบบ 1:1 ระหว่าง Screen กับประเภทเทมเพลตที่แสดงผ่านการใช้งาน onGetTemplate

วาดแผนที่

แอปการนำทางและจุดที่น่าสนใจ (POI) ที่ใช้เทมเพลตต่อไปนี้จะวาดแผนที่ได้โดยเข้าถึง Surface

เทมเพลต สิทธิ์ของเทมเพลต คำแนะนำเกี่ยวกับหมวดหมู่
NavigationTemplate androidx.car.app.NAVIGATION_TEMPLATES การนำทาง
MapWithContentTemplate androidx.car.app.NAVIGATION_TEMPLATES หรือ
androidx.car.app.MAP_TEMPLATES
การนำทาง, จุดที่น่าสนใจ, สภาพอากาศ
MapTemplate (เลิกใช้งานแล้ว) androidx.car.app.NAVIGATION_TEMPLATES การนำทาง
PlaceListNavigationTemplate (เลิกใช้งานแล้ว) androidx.car.app.NAVIGATION_TEMPLATES การนำทาง
RoutePreviewNavigationTemplate (เลิกใช้งานแล้ว) androidx.car.app.NAVIGATION_TEMPLATES การนำทาง

ประกาศสิทธิ์ระดับแพลตฟอร์ม

นอกเหนือจากสิทธิ์ที่จําเป็นสําหรับเทมเพลตที่แอปใช้แล้ว แอปของคุณยังต้องประกาศสิทธิ์ androidx.car.app.ACCESS_SURFACE ในไฟล์ AndroidManifest.xml เพื่อเข้าถึงแพลตฟอร์มต่อไปนี้

<manifest ...>
  ...
  <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
  ...
</manifest>

เข้าถึงแพลตฟอร์ม

หากต้องการเข้าถึง Surface ที่โฮสต์ให้ คุณต้องติดตั้งใช้งาน SurfaceCallback และระบุการติดตั้งใช้งานนั้นให้กับบริการรถ AppManager ระบบจะส่ง Surface ปัจจุบันไปยัง SurfaceCallback ในพารามิเตอร์ SurfaceContainer ของ onSurfaceAvailable() และ onSurfaceDestroyed() callback

Kotlin

carContext.getCarService(AppManager::class.java).setSurfaceCallback(surfaceCallback)

Java

carContext.getCarService(AppManager.class).setSurfaceCallback(surfaceCallback);

ทําความเข้าใจพื้นที่ที่มองเห็นของพื้นผิว

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

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

รองรับธีมมืด

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

หากต้องการเลือกว่าจะวาดแผนที่แบบมืดหรือไม่ ให้ใช้วิธี CarContext.isDarkMode เมื่อใดก็ตามที่สถานะธีมมืดมีการเปลี่ยนแปลง คุณจะได้รับสายจาก Session.onCarConfigurationChanged

อนุญาตให้ผู้ใช้โต้ตอบกับแผนที่

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

เทมเพลต รองรับการโต้ตอบตั้งแต่ระดับ Car App API
NavigationTemplate 2
PlaceListNavigationTemplate (เลิกใช้งานแล้ว) 4
RoutePreviewNavigationTemplate (เลิกใช้งานแล้ว) 4
MapTemplate (เลิกใช้งานแล้ว) 5 (introduction of template)
MapWithContentTemplate 7 (introduction of template)

ใช้การติดต่อกลับแบบอินเทอร์แอกทีฟ

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

การโต้ตอบ SurfaceCallback วิธี รองรับตั้งแต่ระดับ Car App API
แตะ onClick 5
บีบและกางนิ้วเพื่อซูม onScale 2
การลากด้วยการแตะครั้งเดียว onScroll 2
การเลื่อนด้วยนิ้วแตะครั้งเดียว onFling 2
แตะสองครั้ง onScale (ที่มีตัวคูณมาตราส่วนซึ่งกำหนดโดยโฮสต์ของเทมเพลต) 2
การเลื่อนแบบหมุนในโหมดเลื่อน onScroll (โดยมีปัจจัยระยะทางที่โฮสต์เทมเพลตกำหนด) 2

เพิ่มแถบการดำเนินการบนแผนที่

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

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

หากแอปไม่มีปุ่ม Action.PAN ในแถบการดำเนินการบนแผนที่ แอปจะไม่รับอินพุตจากผู้ใช้จากเมธอด SurfaceCallback และโฮสต์จะออกจากโหมดการแพนที่เคยเปิดใช้งานไว้ก่อนหน้านี้

ปุ่มเลื่อนจะไม่แสดงบนหน้าจอสัมผัส

ทำความเข้าใจโหมดเลื่อน

ในโหมดเลื่อน โฮสต์ของเทมเพลตจะแปลอินพุตของผู้ใช้จากอุปกรณ์อินพุตแบบไม่สัมผัส เช่น ตัวควบคุมแบบหมุนและทัชแพด เป็นSurfaceCallbackเมธอดที่เหมาะสม ตอบสนองการดําเนินการของผู้ใช้เพื่อเข้าสู่หรือออกจากโหมดแพนด้วยวิธี setPanModeListener ใน NavigationTemplate.Builder โฮสต์สามารถซ่อนคอมโพเนนต์ UI อื่นๆ ในเทมเพลตขณะที่ผู้ใช้อยู่ในโหมดเลื่อน

โต้ตอบกับผู้ใช้

แอปสามารถโต้ตอบกับผู้ใช้โดยใช้รูปแบบที่คล้ายกับแอปบนอุปกรณ์เคลื่อนที่

จัดการอินพุตของผู้ใช้

แอปจะตอบสนองต่ออินพุตของผู้ใช้ได้โดยส่ง Listeners ที่เหมาะสมไปยังโมเดลที่รองรับ ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างรูปแบบ Action ที่ตั้งค่า OnClickListener ซึ่งเรียกกลับไปยังเมธอดที่กําหนดโดยโค้ดของแอป

Kotlin

val action = Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(::onClickNavigate)
    .build()

Java

Action action = new Action.Builder()
    .setTitle("Navigate")
    .setOnClickListener(this::onClickNavigate)
    .build();

จากนั้นเมธอด onClickNavigate จะเริ่มต้นแอปนำทางรถยนต์เริ่มต้นได้โดยใช้เมธอด CarContext.startCarApp ดังนี้

Kotlin

private fun onClickNavigate() {
    val intent = Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address))
    carContext.startCarApp(intent)
}

Java

private void onClickNavigate() {
    Intent intent = new Intent(CarContext.ACTION_NAVIGATE, Uri.parse("geo:0,0?q=" + address));
    getCarContext().startCarApp(intent);
}

ดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีเริ่มแอป รวมถึงรูปแบบของ Intent ACTION_NAVIGATE ได้ที่ส่วนเริ่มแอปรถยนต์ด้วย Intent

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

Kotlin

val row = Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(::openSettingsOnPhone))
    .build()

Java

Row row = new Row.Builder()
    .setTitle("Open Settings")
    .setOnClickListener(ParkedOnlyOnClickListener.create(this::openSettingsOnPhone))
    .build();

แสดงการแจ้งเตือน

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

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

Kotlin

val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build()

Java

Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
    .setContentTitle(titleOnThePhone)
    .extend(
        new CarAppExtender.Builder()
            .setContentTitle(titleOnTheCar)
            ...
            .build())
    .build();

การแจ้งเตือนอาจส่งผลต่อส่วนต่างๆ ของอินเทอร์เฟซผู้ใช้ต่อไปนี้

  • ระบบอาจแสดงการแจ้งเตือนล่วงหน้า (HUN) ต่อผู้ใช้
  • ระบบอาจเพิ่มรายการในศูนย์การแจ้งเตือน โดยอาจแสดงป้ายในแถบ
  • สําหรับแอปการนําทาง การแจ้งเตือนอาจแสดงในวิดเจ็ตแถบข้างตามที่อธิบายไว้ในการแจ้งเตือนแบบเลี้ยวต่อเลี้ยว

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

หากเรียกใช้ NotificationCompat.Builder.setOnlyAlertOnce ด้วยค่า true การแจ้งเตือนที่มีลำดับความสำคัญสูงจะแสดงเป็น HUN เพียงครั้งเดียว

ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีออกแบบการแจ้งเตือนของแอปในรถได้ที่คู่มือการแจ้งเตือนของ Google Design for Driving

แสดงข้อความโทสต์

แอปสามารถแสดงข้อความ Toast โดยใช้ CarToast ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

Kotlin

CarToast.makeText(carContext, "Hello!", CarToast.LENGTH_SHORT).show()

Java

CarToast.makeText(getCarContext(), "Hello!", CarToast.LENGTH_SHORT).show();

ขอสิทธิ์

หากแอปของคุณจำเป็นต้องเข้าถึงข้อมูลหรือการดําเนินการที่จํากัด เช่น ตำแหน่ง กฎมาตรฐานของสิทธิ์ Android จะมีผล หากต้องการขอสิทธิ์ ให้ใช้วิธี CarContext.requestPermissions()

ข้อดีของการใช้ CarContext.requestPermissions() เมื่อเทียบกับการใช้ Android API มาตรฐาน คือคุณไม่จําเป็นต้องเปิด Activity ของคุณเองเพื่อสร้างกล่องโต้ตอบสิทธิ์ นอกจากนี้ คุณยังใช้โค้ดเดียวกันทั้งใน Android Auto และ Android Automotive OS ได้โดยไม่ต้องสร้างขั้นตอนที่ขึ้นอยู่กับแพลตฟอร์ม

จัดรูปแบบกล่องโต้ตอบสิทธิ์ใน Android Auto

ใน Android Auto กล่องโต้ตอบสิทธิ์สำหรับผู้ใช้จะปรากฏในโทรศัพท์ โดยค่าเริ่มต้น จะไม่มีพื้นหลังด้านหลังกล่องโต้ตอบ หากต้องการตั้งค่าพื้นหลังที่กำหนดเอง ให้ประกาศธีมแอปรถยนต์ในไฟล์ AndroidManifest.xml และตั้งค่าแอตทริบิวต์ carPermissionActivityLayout สำหรับธีมแอปรถยนต์

<meta-data
    android:name="androidx.car.app.theme"
    android:resource="@style/MyCarAppTheme />

จากนั้นตั้งค่าแอตทริบิวต์ carPermissionActivityLayout สำหรับธีมของแอปรถยนต์ โดยทำดังนี้

<resources>
  <style name="MyCarAppTheme">
    <item name="carPermissionActivityLayout">@layout/my_custom_background</item>
  </style>
</resources>

เริ่มแอปรถยนต์ด้วย Intent

คุณสามารถเรียกใช้วิธี CarContext.startCarApp เพื่อดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้

ตัวอย่างต่อไปนี้แสดงวิธีสร้างการแจ้งเตือนที่มีการดำเนินการซึ่งเปิดแอปด้วยหน้าจอที่แสดงรายละเอียดการจองที่จอดรถ คุณขยายอินสแตนซ์การแจ้งเตือนด้วย Intent เนื้อหาที่มี PendingIntent ซึ่งรวม Intent ที่ชัดเจนไว้ในการดําเนินการของแอป

Kotlin

val notification = notificationBuilder
    ...
    .extend(
        CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(ComponentName(context, MyNotificationReceiver::class.java)),
                    0))
            .build())

Java

Notification notification = notificationBuilder
    ...
    .extend(
        new CarAppExtender.Builder()
            .setContentIntent(
                PendingIntent.getBroadcast(
                    context,
                    ACTION_VIEW_PARKING_RESERVATION.hashCode(),
                    new Intent(ACTION_VIEW_PARKING_RESERVATION)
                        .setComponent(new ComponentName(context, MyNotificationReceiver.class)),
                    0))
            .build());

นอกจากนี้ แอปของคุณยังต้องประกาศ BroadcastReceiver ที่เรียกใช้เพื่อประมวลผล Intent เมื่อผู้ใช้เลือกการดำเนินการในอินเทอร์เฟซการแจ้งเตือนและเรียกใช้ CarContext.startCarApp ด้วย Intent ที่มี URI ข้อมูล ดังนี้

Kotlin

class MyNotificationReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val intentAction = intent.action
        if (ACTION_VIEW_PARKING_RESERVATION == intentAction) {
            CarContext.startCarApp(
                intent,
                Intent(Intent.ACTION_VIEW)
                    .setComponent(ComponentName(context, MyCarAppService::class.java))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)))
        }
    }
}

Java

public class MyNotificationReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String intentAction = intent.getAction();
        if (ACTION_VIEW_PARKING_RESERVATION.equals(intentAction)) {
            CarContext.startCarApp(
                intent,
                new Intent(Intent.ACTION_VIEW)
                    .setComponent(new ComponentName(context, MyCarAppService.class))
                    .setData(Uri.fromParts(MY_URI_SCHEME, MY_URI_HOST, intentAction)));
        }
    }
}

สุดท้าย วิธีการ Session.onNewIntent ในแอปทํางานกับ Intent นี้โดยการพุชหน้าจอการจองที่จอดรถลงในกองซ้อน หากไม่ได้อยู่ด้านบนอยู่แล้ว

Kotlin

override fun onNewIntent(intent: Intent) {
    val screenManager = carContext.getCarService(ScreenManager::class.java)
    val uri = intent.data
    if (uri != null
        && MY_URI_SCHEME == uri.scheme
        && MY_URI_HOST == uri.schemeSpecificPart
        && ACTION_VIEW_PARKING_RESERVATION == uri.fragment
    ) {
        val top = screenManager.top
        if (top !is ParkingReservationScreen) {
            screenManager.push(ParkingReservationScreen(carContext))
        }
    }
}

Java

@Override
public void onNewIntent(@NonNull Intent intent) {
    ScreenManager screenManager = getCarContext().getCarService(ScreenManager.class);
    Uri uri = intent.getData();
    if (uri != null
        && MY_URI_SCHEME.equals(uri.getScheme())
        && MY_URI_HOST.equals(uri.getSchemeSpecificPart())
        && ACTION_VIEW_PARKING_RESERVATION.equals(uri.getFragment())
    ) {
        Screen top = screenManager.getTop();
        if (!(top instanceof ParkingReservationScreen)) {
            screenManager.push(new ParkingReservationScreen(getCarContext()));
        }
    }
}

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

ข้อจำกัดของเทมเพลต

โฮสต์จะจำกัดจำนวนเทมเพลตที่จะแสดงสำหรับงานหนึ่งๆ ไว้ที่ไม่เกิน 5 รายการ โดยเทมเพลตสุดท้ายต้องเป็นเทมเพลตประเภทใดประเภทหนึ่งต่อไปนี้

โปรดทราบว่าขีดจํากัดนี้มีผลกับจํานวนเทมเพลต ไม่ใช่จํานวนScreenอินสแตนซ์ในกอง ตัวอย่างเช่น หากแอปส่งเทมเพลต 2 รายการขณะอยู่ในหน้าจอ ก แล้วส่งหน้าจอ ข ต่อ ขณะนี้แอปจะส่งเทมเพลตเพิ่มได้อีก 3 รายการ หรือหากแต่ละหน้าจอมีโครงสร้างเพื่อส่งเทมเพลตเดียว แอปจะดันอินสแตนซ์หน้าจอ 5 รายการลงในกองScreenManagerได้

ข้อจำกัดเหล่านี้มีข้อยกเว้นบางกรณี เช่น การรีเฟรชเทมเพลต และการย้อนกลับและรีเซ็ตการดำเนินการ

การรีเฟรชเทมเพลต

การอัปเดตเนื้อหาบางอย่างจะไม่นับรวมในขีดจํากัดของเทมเพลต โดยทั่วไปแล้ว หากแอปพุชเทมเพลตใหม่ที่เป็นประเภทเดียวกันและมีเนื้อหาหลักเหมือนกับเทมเพลตก่อนหน้า ระบบจะไม่นับเทมเพลตใหม่รวมอยู่ในโควต้า เช่น การอัปเดตสถานะเปิด/ปิดของแถวใน ListTemplate จะไม่นับรวมในโควต้า ดูข้อมูลเพิ่มเติมเกี่ยวกับประเภทการอัปเดตเนื้อหาที่ถือว่าเป็นการรีเฟรชได้ในเอกสารประกอบของเทมเพลตแต่ละรายการ

การดำเนินการย้อนกลับ

หากต้องการเปิดใช้โฟลว์ย่อยภายในงาน โฮสต์จะตรวจจับเมื่อแอปแสดงScreenจากกอง ScreenManager และอัปเดตโควต้าที่เหลือตามจํานวนเทมเพลตที่แอปจะย้อนกลับ

ตัวอย่างเช่น หากแอปส่งเทมเพลต 2 รายการขณะอยู่ในหน้าจอ ก จากนั้นส่งเทมเพลตอีก 2 รายการในหน้าจอ ข แอปจะมีโควต้าเหลืออยู่ 1 รายการ หากแอปปรากฏขึ้นอีกครั้งในหน้าจอ ก โฮสต์จะรีเซ็ตโควต้าเป็น 3 เนื่องจากแอปย้อนกลับเทมเพลต 2 รายการ

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

รีเซ็ตการดำเนินการ

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

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

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

Connection API

คุณตรวจสอบได้ว่าแอปทำงานบน Android Auto หรือ Android Automotive OS โดยใช้ CarConnection API เพื่อดึงข้อมูลการเชื่อมต่อขณะรันไทม์

ตัวอย่างเช่น ใน Session ของแอปรถยนต์ ให้เริ่มต้น CarConnection และสมัครรับการอัปเดต LiveData ดังนี้

Kotlin

CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)

Java

new CarConnection(getCarContext()).getType().observe(this, this::onConnectionStateUpdated);

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

Kotlin

fun onConnectionStateUpdated(connectionState: Int) {
  val message = when(connectionState) {
    CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
    CarConnection.CONNECTION_TYPE_NATIVE -> "Connected to Android Automotive OS"
    CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
    else -> "Unknown car connection type"
  }
  CarToast.makeText(carContext, message, CarToast.LENGTH_SHORT).show()
}

Java

private void onConnectionStateUpdated(int connectionState) {
  String message;
  switch(connectionState) {
    case CarConnection.CONNECTION_TYPE_NOT_CONNECTED:
      message = "Not connected to a head unit";
      break;
    case CarConnection.CONNECTION_TYPE_NATIVE:
      message = "Connected to Android Automotive OS";
      break;
    case CarConnection.CONNECTION_TYPE_PROJECTION:
      message = "Connected to Android Auto";
      break;
    default:
      message = "Unknown car connection type";
      break;
  }
  CarToast.makeText(getCarContext(), message, CarToast.LENGTH_SHORT).show();
}

Constraints API

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

เริ่มต้นด้วยการรับ ConstraintManager จาก CarContext โดยทำดังนี้

Kotlin

val manager = carContext.getCarService(ConstraintManager::class.java)

Java

ConstraintManager manager = getCarContext().getCarService(ConstraintManager.class);

จากนั้นคุณสามารถค้นหาออบเจ็กต์ ConstraintManager ที่ดึงข้อมูลมาเพื่อดูขีดจํากัดของเนื้อหาที่เกี่ยวข้อง ตัวอย่างเช่น หากต้องการดูจํานวนรายการที่แสดงในตารางกริด ให้เรียกใช้ getContentLimit ด้วย CONTENT_LIMIT_TYPE_GRID ดังนี้

Kotlin

val gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)

Java

int gridItemLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID);

เพิ่มขั้นตอนการลงชื่อเข้าใช้

หากแอปของคุณมอบประสบการณ์การลงชื่อเข้าใช้สำหรับผู้ใช้ คุณสามารถใช้เทมเพลตอย่าง SignInTemplate และ LongMessageTemplate กับ Car App API ระดับ 2 ขึ้นไปเพื่อจัดการการลงชื่อเข้าใช้แอปในจอภาพหลักของรถยนต์

หากต้องการสร้าง SignInTemplate ให้กําหนด SignInMethod ปัจจุบันคลังแอปในรถรองรับวิธีการลงชื่อเข้าใช้ต่อไปนี้

  • InputSignInMethod สำหรับการลงชื่อเข้าใช้ด้วยชื่อผู้ใช้/รหัสผ่าน
  • PinSignInMethod สําหรับการลงชื่อเข้าใช้ด้วย PIN ซึ่งผู้ใช้ลิงก์บัญชีจากโทรศัพท์โดยใช้ PIN ที่แสดงบนจอภาพส่วนกลาง
  • ProviderSignInMethod สําหรับการลงชื่อเข้าใช้ของผู้ให้บริการ เช่น Google Sign-In และ One Tap
  • QRCodeSignInMethod สําหรับการลงชื่อเข้าใช้ด้วยคิวอาร์โค้ด ซึ่งผู้ใช้จะสแกนคิวอาร์โค้ดเพื่อลงชื่อเข้าใช้ในโทรศัพท์ ซึ่งใช้ได้กับ Car API ระดับ 4 ขึ้นไป

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

Kotlin

val callback = object : InputCallback {
    override fun onInputSubmitted(text: String) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    override fun onInputTextChanged(text: String) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
}

Java

InputCallback callback = new InputCallback() {
    @Override
    public void onInputSubmitted(@NonNull String text) {
        // You will receive this callback when the user presses Enter on the keyboard.
    }

    @Override
    public void onInputTextChanged(@NonNull String text) {
        // You will receive this callback as the user is typing. The update
        // frequency is determined by the host.
    }
};

ต้องมี InputCallback สำหรับ InputSignInMethod Builder

Kotlin

val passwordInput = InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build()

Java

InputSignInMethod passwordInput = new InputSignInMethod.Builder(callback)
    .setHint("Password")
    .setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
    ...
    .build();

สุดท้าย ให้ใช้ InputSignInMethod ใหม่เพื่อสร้าง SignInTemplate

Kotlin

SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build()

Java

new SignInTemplate.Builder(passwordInput)
    .setTitle("Sign in with username and password")
    .setInstructions("Enter your password")
    .setHeaderAction(Action.BACK)
    ...
    .build();

ใช้ AccountManager

แอป Android Automotive OS ที่มีการรับรองต้องใช้ AccountManager ด้วยเหตุผลต่อไปนี้

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

เพิ่มตัวแปรสตริงข้อความ

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

คุณเพิ่มตัวแปรสตริงข้อความลงใน CarText ได้โดยใช้เมธอด CarText.Builder.addVariant() ดังนี้

Kotlin

val itemTitle = CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build()

Java

CarText itemTitle = new CarText.Builder("This is a very long string")
    .addVariant("Shorter string")
    ...
    .build();

จากนั้นคุณจะใช้ CarText นี้ เช่น เป็นข้อความหลักของ GridItem ได้

Kotlin

GridItem.Builder()
    .addTitle(itemTitle)
    ...
    .build()

Java

new GridItem.Builder()
    .addTitle(itemTitle)
    ...
    build();

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

เพิ่ม CarIcons ในบรรทัดสำหรับแถว

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

Kotlin

  
val rating = SpannableString("Rating: 4.5 stars")
rating.setSpan(
    CarIconSpan.create(
        // Create a CarIcon with an image of four and a half stars
        CarIcon.Builder(...).build(),
        // Align the CarIcon to the baseline of the text
        CarIconSpan.ALIGN_BASELINE
    ),
    // The start index of the span (index of the character '4')
    8,
    // The end index of the span (index of the last 's' in "stars")
    16,
    Spanned.SPAN_INCLUSIVE_INCLUSIVE
)

val row = Row.Builder()
    ...
    .addText(rating)
    .build()
  
  

Java

  
SpannableString rating = new SpannableString("Rating: 4.5 stars");
rating.setSpan(
        CarIconSpan.create(
                // Create a CarIcon with an image of four and a half stars
                new CarIcon.Builder(...).build(),
                // Align the CarIcon to the baseline of the text
                CarIconSpan.ALIGN_BASELINE
        ),
        // The start index of the span (index of the character '4')
        8,
        // The end index of the span (index of the last 's' in "stars")
        16,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
);
Row row = new Row.Builder()
        ...
        .addText(rating)
        .build();
  
  

Car Hardware API

ตั้งแต่ Car App API ระดับ 3 เป็นต้นไป ไลบรารีแอปรถยนต์จะมี API ที่คุณสามารถใช้เพื่อเข้าถึงพร็อพเพอร์ตี้และเซ็นเซอร์ของยานพาหนะ

ข้อกำหนด

หากต้องการใช้ API กับ Android Auto ให้เริ่มด้วยการเพิ่ม Dependency ของ androidx.car.app:app-projected ลงในไฟล์ build.gradle สำหรับโมดูล Android Auto สำหรับ Android Automotive OS ให้เพิ่มการพึ่งพา androidx.car.app:app-automotive ลงในไฟล์ build.gradle สำหรับโมดูล Android Automotive OS

นอกจากนี้ ในไฟล์ AndroidManifest.xml คุณยังต้องประกาศสิทธิ์ที่เกี่ยวข้องที่จําเป็นในการขอข้อมูลรถยนต์ที่ต้องการใช้ โปรดทราบว่าผู้ใช้ต้องให้สิทธิ์เหล่านี้แก่คุณด้วย คุณสามารถใช้โค้ดเดียวกันทั้งใน Android Auto และ Android Automotive OS ได้โดยไม่ต้องสร้างขั้นตอนที่ขึ้นอยู่กับแพลตฟอร์ม แต่สิทธิ์ที่จําเป็นจะแตกต่างกัน

CarInfo

ตารางนี้อธิบายพร็อพเพอร์ตี้ที่แสดงโดย API ของ CarInfo และสิทธิ์ที่คุณต้องขอเพื่อใช้

วิธีการ คุณสมบัติ สิทธิ์ของ Android Auto สิทธิ์ของ Android Automotive OS รองรับตั้งแต่ระดับ Car App API
fetchModel ยี่ห้อ รุ่น ปี android.car.permission.CAR_INFO 3
fetchEnergyProfile ประเภทหัวชาร์จไฟฟ้า EV, ประเภทเชื้อเพลิง com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_INFO 3
fetchExteriorDimensions

ข้อมูลนี้ใช้ได้กับรถยนต์ Android Automotive OS บางรุ่นที่ใช้ API ระดับ 30 ขึ้นไปเท่านั้น

ขนาดภายนอก ไม่มี android.car.permission.CAR_INFO 7
addTollListener
removeTollListener
สถานะบัตรค่าผ่านทาง ประเภทบัตรค่าผ่านทาง 3
addEnergyLevelListener
removeEnergyLevelListener
ระดับแบตเตอรี่ ระดับน้ำมัน น้ำมันเหลือน้อย ระยะทางที่เหลือ com.google.android.gms.permission.CAR_FUEL android.car.permission.CAR_ENERGY,
android.car.permission.CAR_ENERGY_PORTS,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addSpeedListener
removeSpeedListener
ความเร็วดิบ ความเร็วที่แสดง (แสดงบนจอแผงหน้าปัดของรถยนต์) com.google.android.gms.permission.CAR_SPEED android.car.permission.CAR_SPEED,
android.car.permission.READ_CAR_DISPLAY_UNITS
3
addMileageListener
removeMileageListener
ระยะทางจากเครื่องวัดระยะทาง com.google.android.gms.permission.CAR_MILEAGE ข้อมูลนี้ไม่พร้อมใช้งานใน Android Automotive OS สำหรับแอปที่ติดตั้งจาก Play Store 3

เช่น หากต้องการช่วงที่เหลือ ให้สร้างอินสแตนซ์ของออบเจ็กต์ CarInfo จากนั้นสร้างและลงทะเบียน OnCarDataAvailableListener

Kotlin

val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo

val listener = OnCarDataAvailableListener<EnergyLevel> { data ->
    if (data.rangeRemainingMeters.status == CarValue.STATUS_SUCCESS) {
      val rangeRemaining = data.rangeRemainingMeters.value
    } else {
      // Handle error
    }
  }

carInfo.addEnergyLevelListener(carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener)

Java

CarInfo carInfo = getCarContext().getCarService(CarHardwareManager.class).getCarInfo();

OnCarDataAvailableListener<EnergyLevel> listener = (data) -> {
  if(data.getRangeRemainingMeters().getStatus() == CarValue.STATUS_SUCCESS) {
    float rangeRemaining = data.getRangeRemainingMeters().getValue();
  } else {
    // Handle error
  }
};

carInfo.addEnergyLevelListener(getCarContext().getMainExecutor(), listener);

// Unregister the listener when you no longer need updates
carInfo.removeEnergyLevelListener(listener);

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

CarSensors

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

Kotlin

val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors

val listener = OnCarDataAvailableListener<Compass> { data ->
    if (data.orientations.status == CarValue.STATUS_SUCCESS) {
      val orientation = data.orientations.value
    } else {
      // Data not available, handle error
    }
  }

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, listener)

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener)

Java

CarSensors carSensors = getCarContext().getCarService(CarHardwareManager.class).getCarSensors();

OnCarDataAvailableListener<Compass> listener = (data) -> {
  if (data.getOrientations().getStatus() == CarValue.STATUS_SUCCESS) {
    List<Float> orientations = data.getOrientations().getValue();
  } else {
    // Data not available, handle error
  }
};

carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, getCarContext().getMainExecutor(),
    listener);

// Unregister the listener when you no longer need updates
carSensors.removeCompassListener(listener);

หากต้องการเข้าถึงข้อมูลตำแหน่งจากรถยนต์ คุณจะต้องประกาศและขอสิทธิ์ android.permission.ACCESS_FINE_LOCATION ด้วย

การทดสอบ

หากต้องการจำลองข้อมูลเซ็นเซอร์เมื่อทดสอบใน Android Auto โปรดดูส่วนเซ็นเซอร์และการกำหนดค่าเซ็นเซอร์ของคู่มือเกี่ยวกับจอแสดงผลบนเดสก์ท็อป หากต้องการจําลองข้อมูลเซ็นเซอร์เมื่อทดสอบใน Android Automotive OS โปรดดูส่วนจําลองสถานะฮาร์ดแวร์ในคู่มือโปรแกรมจําลอง Android Automotive OS

วงจรชีวิตของ CarAppService, เซสชัน และหน้าจอ

คลาส Session และ Screen ใช้อินเทอร์เฟซ LifecycleOwner เมื่อผู้ใช้โต้ตอบกับแอป ระบบจะเรียกใช้การเรียกกลับของวงจรชีวิตของออบเจ็กต์ Session และ Screen ตามที่อธิบายไว้ในแผนภาพต่อไปนี้

วงจรชีวิตของ CarAppService และเซสชัน

รูปที่ 1 Sessionวงจร

ดูรายละเอียดทั้งหมดได้ที่เอกสารประกอบของวิธี Session.getLifecycle

วงจรชีวิตของหน้าจอ

รูปที่ 2 Screenวงจร

ดูรายละเอียดทั้งหมดได้ในเอกสารประกอบของวิธี Screen.getLifecycle

บันทึกจากไมโครโฟนของรถยนต์

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

สิทธิ์ในการบันทึก

ก่อนที่จะบันทึกเสียง คุณต้องประกาศสิทธิ์ในการบันทึกใน AndroidManifest.xml ก่อน และขอให้ผู้ใช้ให้สิทธิ์

<manifest ...>
   ...
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   ...
</manifest>

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

บันทึกเสียง

หลังจากผู้ใช้ให้สิทธิ์บันทึกแล้ว คุณจะบันทึกเสียงและประมวลผลไฟล์บันทึกได้

Kotlin

val carAudioRecord = CarAudioRecord.create(carContext)
        carAudioRecord.startRecording()

        val data = ByteArray(CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE)
        while(carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording()
 

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        carAudioRecord.startRecording();

        byte[] data = new byte[CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE];
        while (carAudioRecord.read(data, 0, CarAudioRecord.AUDIO_CONTENT_BUFFER_SIZE) >= 0) {
            // Use data array
            // Potentially call carAudioRecord.stopRecording() if your processing finds end of speech
        }
        carAudioRecord.stopRecording();
 

โฟกัสอัตโนมัติ

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

ตัวอย่างวิธีรับโฟกัสเสียงมีดังนี้

Kotlin

 
val carAudioRecord = CarAudioRecord.create(carContext)
        
        // Take audio focus so that user's media is not recorded
        val audioAttributes = AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
            // Use the most appropriate usage type for your use case
            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
            .build()
        
        val audioFocusRequest =
            AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                .setAudioAttributes(audioAttributes)
                .setOnAudioFocusChangeListener { state: Int ->
                    if (state == AudioManager.AUDIOFOCUS_LOSS) {
                        // Stop recording if audio focus is lost
                        carAudioRecord.stopRecording()
                    }
                }
                .build()
        
        if (carContext.getSystemService(AudioManager::class.java)
                .requestAudioFocus(audioFocusRequest)
            != AudioManager.AUDIOFOCUS_REQUEST_GRANTED
        ) {
            // Don't record if the focus isn't granted
            return
        }
        
        carAudioRecord.startRecording()
        // Process the audio and abandon the AudioFocusRequest when done

Java

CarAudioRecord carAudioRecord = CarAudioRecord.create(getCarContext());
        // Take audio focus so that user's media is not recorded
        AudioAttributes audioAttributes =
                new AudioAttributes.Builder()
                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                        // Use the most appropriate usage type for your use case
                        .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
                        .build();

        AudioFocusRequest audioFocusRequest =
                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
                        .setAudioAttributes(audioAttributes)
                        .setOnAudioFocusChangeListener(state -> {
                            if (state == AudioManager.AUDIOFOCUS_LOSS) {
                                // Stop recording if audio focus is lost
                                carAudioRecord.stopRecording();
                            }
                        })
                        .build();

        if (getCarContext().getSystemService(AudioManager.class).requestAudioFocus(audioFocusRequest)
                != AUDIOFOCUS_REQUEST_GRANTED) {
            // Don't record if the focus isn't granted
            return;
        }

        carAudioRecord.startRecording();
        // Process the audio and abandon the AudioFocusRequest when done
 

ไลบรารีการทดสอบ

ไลบรารีการทดสอบของ Android สำหรับรถยนต์มีคลาสเสริมที่คุณสามารถใช้เพื่อตรวจสอบลักษณะการทํางานของแอปในสภาพแวดล้อมการทดสอบ ตัวอย่างเช่น SessionController ช่วยให้คุณจําลองการเชื่อมต่อกับโฮสต์และยืนยันว่ามีการสร้างและแสดง Screen และ Template ที่ถูกต้อง

ดูตัวอย่างการใช้งานได้ที่ส่วนตัวอย่าง

รายงานปัญหาเกี่ยวกับไลบรารีแอป Android สำหรับรถยนต์

หากพบปัญหาเกี่ยวกับคลัง ให้รายงานปัญหาโดยใช้เครื่องมือติดตามปัญหาของ Google โปรดกรอกข้อมูลที่ขอทั้งหมดในเทมเพลตปัญหา

สร้างปัญหาใหม่

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