איך מתחילים?

בדף הזה נסביר איך להגדיר את הסביבה ולפתח פרוסות באפליקציה.

הערה: מערכת Android Studio 3.2 ואילך כוללת תכונות נוספות כלים ופונקציונליות שיכולים לעזור לכם בפיתוח Slices:

  • כלי לארגון הקוד מחדש של AndroidX: נדרש אם עובדים בפרויקט משתמש בספריות AndroidX.
  • בדיקות לאיתור שגיאות בקוד: זיהוי שיטות נגד שיטות נפוצות במהלך הבנייה פרוסות
  • התבנית SliceProvider: מטפלת בתבנית הסטנדרטית כאשר בניית SliceProvider

הורדה והתקנה של מציג הפרוסות

הורדת הדוגמה האחרונה גרסת ה-APK של Slice Viewer שאפשר להשתמש בה כדי לבדוק את הפרוסות בלי ליישם את SliceView API.

אם ADB לא מוגדר כראוי בסביבה שלך, קראו את מידע נוסף זמין במדריך ADB.

מתקינים את Slice Viewer במכשיר על ידי הרצת הפקודה הבאה ב אותה ספרייה כמו הקובץ slice-viewer.apk שהורדתם:

adb install -r -t slice-viewer.apk

הפעלה של מציג הפרוסות

אפשר להפעיל את 'צפייה בפרוסות' (Slice) מפרויקט Android Studio או דרך שורת הפקודה:

הפעלת Slice Viewer מפרויקט Android Studio

  1. בפרויקט, בוחרים באפשרות Run > לעריכת ההגדרות האישיות...
  2. בפינה הימנית העליונה, לוחצים על סימן הפלוס הירוק
  3. בוחרים באפשרות אפליקציה ל-Android.

  4. מזינים slice בשדה השם

  5. בוחרים את מודול האפליקציה בתפריט הנפתח מודול

  6. בקטע Launch options (אפשרויות הפעלה), בוחרים באפשרות URL בתפריט הנפתח Launch (הפעלה).

  7. מזינים את slice-<your slice URI> בשדה כתובת ה-URL

    דוגמה: slice-content://com.example.your.sliceuri

  8. לוחצים על אישור.

הפעלת הכלי Slice Viewer באמצעות ADB (שורת פקודה)

מפעילים את האפליקציה מ-Android Studio:

adb install -t -r <yourapp>.apk

כדי להציג את הפלח, מריצים את הפקודה הבאה:

adb shell am start -a android.intent.action.VIEW -d slice-<your slice URI>

מציג Wi-Fi מציג פלח אחד של Wi-Fi

הצגת כל הפרוסות במקום אחד

בנוסף להשקה של פרוסה אחת, אפשר להציג רשימה של כל פרוסות.

  • אפשר להשתמש בסרגל החיפוש כדי לחפש באופן ידני את הפרוסות באמצעות URI (לדוגמה, content://com.example.android.app/hello). בכל פעם שמחפשים, הפלח נוספו לרשימה.
  • בכל פעם שמפעילים את הכלי 'צפייה בפרוסות' עם URI של פרוסה, הפרוסה תתווסף לרשימה.
  • אפשר להחליק פרוסה כדי להסיר אותה מהרשימה.
  • מקישים על ה-URI של הפלח כדי לראות דף שמכיל רק את הפלח הזה. זה כולל אותו אפקט כמו הפעלת Slice Viewer עם Slice URI.

מציג הפילוחים מציג רשימה של פרוסות

הצגת הפלח במצבים שונים

אפליקציה שמציגה פרוסה יכולה לשנות את SliceView#mode בזמן הריצה, לכן עליך לוודא שהפרוסה שלך נראית כצפוי בכל מצב. כדי לשנות את המצב, לוחצים על סמל התפריט באזור השמאלי העליון של הדף.

צפייה בפורמט פרוסה יחיד במצב מוגדר כ'קטן'

בניית הפלח הראשון

כדי ליצור פרוסה, פותחים את פרויקט Android Studio ולוחצים לחיצה ימנית על src חבילה, ובחר חדש... &gt; אחר > ספק הפלחים. הפעולה הזו יוצרת כיתה שאורכו SliceProvider, מוסיף רשומת הספק הנדרשת ב-AndroidManifest.xml, ומשנה את build.gradle כדי להוסיף את יחסי התלות הנדרשים של פרוסות.

השינוי ב-AndroidManifest.xml מוצג למטה:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.app">
    ...
    <application>
        ...
        <provider android:name="MySliceProvider"
            android:authorities="com.example.android.app"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.app.slice.category.SLICE" />
            </intent-filter>
        </provider>
        ...
    </application>

</manifest>

יחסי התלות הבאים מתווספים ל-build.gradle:

Kotlin

dependencies {
// ...
    implementation "androidx.slice:slice-builders-ktx:(latest version)"
// ...
}

Java

dependencies {
// ...
    implementation "androidx.slice:slice-builders:(latest version)"
// ...
}

לכל פרוסה יש URI משויך. כשמשטחים רוצים להציג פרוסה, שולחת בקשת קישור לאפליקציה עם ה-URI הזה. לאחר מכן האפליקציה תטפל בבעיה ותבנה את הפלח באופן דינמי דרך onBindSlice . לאחר מכן, המשטח יוכל להציג את הפלח במקרים הרלוונטיים.

בהמשך מוצגת דוגמה ל-method onBindSlice שבודקת את ה-URI /hello ומחזירה פלח של Hello World:

Kotlin

override fun onBindSlice(sliceUri: Uri): Slice? {
    val activityAction = createActivityAction()
    return if (sliceUri.path == "/hello") {
        list(context, sliceUri, ListBuilder.INFINITY) {
            row {
                primaryAction = activityAction
                title = "Hello World."
            }
        }
    } else {
        list(context, sliceUri, ListBuilder.INFINITY) {
            row {
                primaryAction = activityAction
                title = "URI not recognized."
            }
        }
    }
}

Java

@Override
public Slice onBindSlice(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    SliceAction activityAction = createActivityAction();
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
    // Create parent ListBuilder.
    if ("/hello".equals(sliceUri.getPath())) {
        listBuilder.addRow(new ListBuilder.RowBuilder()
                .setTitle("Hello World")
                .setPrimaryAction(activityAction)
        );
    } else {
        listBuilder.addRow(new ListBuilder.RowBuilder()
                .setTitle("URI not recognized")
                .setPrimaryAction(activityAction)
        );
    }
    return listBuilder.build();
}

משתמשים בתצורת הרצת הפרוסות שיצרתם בקטע 'צפייה בפרוסות' שלמעלה, העברת ה-URI של הפלח (לדוגמה, slice-content://com.android.example.slicesample/hello) של Hello World פרוסות כדי להציג אותו במציג הפרוסות.

פרוסות אינטראקטיביות

בדומה להתראות, אפשר לטפל בקליקים בתוך הפלח על ידי צירוף PendingIntent אובייקטים שהם מופעל בעקבות אינטראקציה של המשתמש. הדוגמה הבאה מפעילה Activity שיכול לקבל את האימיילים האלה ולטפל בהם Intents:

Kotlin

fun createSlice(sliceUri: Uri): Slice {
    val activityAction = createActivityAction()
    return list(context, sliceUri, INFINITY) {
        row {
            title = "Perform action in app"
            primaryAction = activityAction
        }
    }
}

fun createActivityAction(): SliceAction {
    val intent = Intent(context, MainActivity::class.java)
    return SliceAction.create(
        PendingIntent.getActivity(context, 0, Intent(context, MainActivity::class.java), 0),
        IconCompat.createWithResource(context, R.drawable.ic_home),
        ListBuilder.ICON_IMAGE,
        "Enter app"
    )
}

Java

public Slice createSlice(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    SliceAction activityAction = createActivityAction();
    return new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            .addRow(new ListBuilder.RowBuilder()
                    .setTitle("Perform action in app.")
                    .setPrimaryAction(activityAction)
            ).build();
}

public SliceAction createActivityAction() {
    if (getContext() == null) {
        return null;
    }
    return SliceAction.create(
            PendingIntent.getActivity(
                    getContext(),
                    0,
                    new Intent(getContext(), MainActivity.class),
                    0
            ),
            IconCompat.createWithResource(getContext(), R.drawable.ic_home),
            ListBuilder.ICON_IMAGE,
            "Enter app"
    );
}

פרוסות תומכות גם בסוגי קלט אחרים, כמו מתגים, שכוללים של הכוונה שנשלחת לאפליקציה.

Kotlin

fun createBrightnessSlice(sliceUri: Uri): Slice {
    val toggleAction =
        SliceAction.createToggle(
            createToggleIntent(),
            "Toggle adaptive brightness",
            true
        )
    return list(context, sliceUri, ListBuilder.INFINITY) {
        row {
            title = "Adaptive brightness"
            subtitle = "Optimizes brightness for available light"
            primaryAction = toggleAction
        }
        inputRange {
            inputAction = (brightnessPendingIntent)
            max = 100
            value = 45
        }
    }
}

fun createToggleIntent(): PendingIntent {
    val intent = Intent(context, MyBroadcastReceiver::class.java)
    return PendingIntent.getBroadcast(context, 0, intent, 0)
}

Java

public Slice createBrightnessSlice(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    SliceAction toggleAction = SliceAction.createToggle(
            createToggleIntent(),
            "Toggle adaptive brightness",
            true
    );
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            .addRow(new ListBuilder.RowBuilder()
                    .setTitle("Adaptive brightness")
                    .setSubtitle("Optimizes brightness for available light.")
                    .setPrimaryAction(toggleAction)
            ).addInputRange(new ListBuilder.InputRangeBuilder()
                    .setInputAction(brightnessPendingIntent)
                    .setMax(100)
                    .setValue(45)
            );
    return listBuilder.build();
}

public PendingIntent createToggleIntent() {
    Intent intent = new Intent(getContext(), MyBroadcastReceiver.class);
    return PendingIntent.getBroadcast(getContext(), 0, intent, 0);
}

המקבל יכול לבדוק את המצב שהוא מקבל:

Kotlin

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    Slice.EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show()
        }
    }

    companion object {
        const val EXTRA_MESSAGE = "message"
    }
}

Java

public class MyBroadcastReceiver extends BroadcastReceiver {

    public static String EXTRA_MESSAGE = "message";

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show();
        }
    }
}

פרוסות דינמיות

פרוסות יכולות גם להכיל תוכן דינמי. בדוגמה הבאה, עכשיו הפלח כולל את מספר השידורים שהתקבלו בתוכן שלו:

Kotlin

fun createDynamicSlice(sliceUri: Uri): Slice {
    return when (sliceUri.path) {
        "/count" -> {
            val toastAndIncrementAction = SliceAction.create(
                createToastAndIncrementIntent("Item clicked."),
                actionIcon,
                ListBuilder.ICON_IMAGE,
                "Increment."
            )
            list(context, sliceUri, ListBuilder.INFINITY) {
                row {
                    primaryAction = toastAndIncrementAction
                    title = "Count: ${MyBroadcastReceiver.receivedCount}"
                    subtitle = "Click me"
                }
            }
        }

        else -> {
            list(context, sliceUri, ListBuilder.INFINITY) {
                row {
                    primaryAction = createActivityAction()
                    title = "URI not found."
                }
            }
        }
    }
}

Java

public Slice createDynamicSlice(Uri sliceUri) {
    if (getContext() == null || sliceUri.getPath() == null) {
        return null;
    }
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
    switch (sliceUri.getPath()) {
        case "/count":
            SliceAction toastAndIncrementAction = SliceAction.create(
                    createToastAndIncrementIntent("Item clicked."),
                    actionIcon,
                    ListBuilder.ICON_IMAGE,
                    "Increment."
            );
            listBuilder.addRow(
                    new ListBuilder.RowBuilder()
                            .setPrimaryAction(toastAndIncrementAction)
                            .setTitle("Count: " + MyBroadcastReceiver.sReceivedCount)
                            .setSubtitle("Click me")
            );
            break;
        default:
            listBuilder.addRow(
                    new ListBuilder.RowBuilder()
                            .setPrimaryAction(createActivityAction())
                            .setTitle("URI not found.")
            );
            break;
    }
    return listBuilder.build();
}

public PendingIntent createToastAndIncrementIntent(String s) {
    Intent intent = new Intent(getContext(), MyBroadcastReceiver.class)
            .putExtra(MyBroadcastReceiver.EXTRA_MESSAGE, s);
    return PendingIntent.getBroadcast(getContext(), 0, intent, 0);
}

בדוגמה הזאת, כשהספירה מוצגת, היא לא מתעדכנת מעצמו. אפשר לשנות את מקלט השידורים כדי להודיע למערכת על כך שבוצע שינוי באמצעות ContentResolver#notifyChange

Kotlin

class MyBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
            Toast.makeText(
                context, "Toggled:  " + intent.getBooleanExtra(
                    Slice.EXTRA_TOGGLE_STATE, false
                ),
                Toast.LENGTH_LONG
            ).show()
            receivedCount++;
            context.contentResolver.notifyChange(sliceUri, null)
        }
    }

    companion object {
        var receivedCount = 0
        val sliceUri = Uri.parse("content://com.android.example.slicesample/count")
        const val EXTRA_MESSAGE = "message"
    }
}

Java

public class MyBroadcastReceiver extends BroadcastReceiver {

    public static int sReceivedCount = 0;
    public static String EXTRA_MESSAGE = "message";

    private static Uri sliceUri = Uri.parse("content://com.android.example.slicesample/count");

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(EXTRA_TOGGLE_STATE)) {
            Toast.makeText(context, "Toggled:  " + intent.getBooleanExtra(
                    EXTRA_TOGGLE_STATE, false),
                    Toast.LENGTH_LONG).show();
            sReceivedCount++;
            context.getContentResolver().notifyChange(sliceUri, null);
        }
    }
}

תבניות

הפרוסות תומכות במגוון תבניות. לפרטים נוספים על האפשרויות של תבניות בתבניות.