Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Widget開発再訪

934a9e49edc3174d09ab2e09daed5062?s=47 ymnder
February 05, 2018

 Widget開発再訪

DroidKaigi2018, Room 2 - 2018/02/08 14:00-14:30
https://droidkaigi.jp/2018/timetable?session=16949

934a9e49edc3174d09ab2e09daed5062?s=128

ymnder

February 05, 2018
Tweet

Transcript

  1. Widget։ൃ࠶๚ Re-Introduction to Widget Development DroidKaigi2018, Room 2 ʵ 2018/02/08

    14:00-14:30
  2. Sample App https://github.com/ymnder/WidgetSample 2

  3. Today's menu 1. About Widget 2. Widget Design 3. Let's

    make Widget 4. New API 3
  4. About Widget

  5. What Widget • Widget͸ϛχΞϓϦͰ͋Δ • ϝΠϯͷΞϓϦʹόϯυϧͯ͠ఏڙ͞ΕΔ • ΞϓϦͷίΞػೳΛΞϓϦͷ֎Ͱ࢖͑Δ • ΞϓϦͷػೳ΍ίϯςϯπʹϗʔϜը໘͔Β৮ΕΔ

    • ϗʔϜը໘ͰίϯςϯπΛ໨ʹ͢Δػձ͕૿͑Δ • ΞϓϦΛ։͔ͳͯ͘΋ΞΫηεͰ͖Δʂʂ 5
  6. Why Widget now? • ϝΠϯΞϓϦͷ֎Ͱίϯςϯπʹ৮Εͯ΋Β͑Δ • ։ൃʹ؆୯ʹऔΓֻ͔ΕΔ • AndroidO͔Β৽͍͠API͕௥Ճ͞Εͨ 6

  7. What is New Api • Widget Dialog • WidgetͷΠϯετʔϧϑϩʔ͕վળ͞Εͨ 7

  8. Widget Picker • StoreΠϯετʔϧޙʹࣗ෼Ͱ௥Ճ͢Δඞཁ͕͋Δ • ϗʔϜը໘Λϩϯάλοϓ͢Δ • ϥϯνϟʔʹΑͬͯ௥Ճํ๏͕ҟͳΔ • ଞͷΞϓϦͱಉ͡Ϧετʹஔ͔Εͯ୳͠ʹ͍͘

    8
  9. Widget Picker 9

  10. Widget Dialog • ΞϓϦ಺͔Β௚઀WidgetΛ͸Γ͚ͭΔ͜ͱ͕Ͱ͖Δ • ௥Ճϑϩʔ͕ܶతʹ෼͔Γ΍͘͢ͳͬͨʂʂ ref: https://developers-jp.googleblog.com/2017/08/whats-new-for-shortcuts-and-widgets-in.html 10

  11. Widget Dialog 11

  12. Widget Design

  13. ͲΜͳWidget͕͋Δ͔ ͍ΖΜͳWidget 13

  14. Widget Category ᶃ Information widgets ᶄ Collection widgets ᶅ Control

    widgets ᶆ Hybrid widgets ΨΠυϥΠϯɿhttps://material.io/guidelines/components/widgets.html#widgets-types-of-widgets چΨΠυϥΠϯɿhttps://developer.android.com/design/patterns/widgets.html 14
  15. Widget Category ᶃ Information widgets ᶄ Collection widgets ᶅ Control

    widgets ᶆ Hybrid widgets ΨΠυϥΠϯɿhttps://material.io/guidelines/components/widgets.html#widgets-types-of-widgets چΨΠυϥΠϯɿhttps://developer.android.com/design/patterns/widgets.html 15
  16. Information widgets • ࣌ܭɾఱؾͳͲγϯϓϧͳ৘ใΛग़͢ • λοϓͨ͠Βৄࡉͳ৘ใΛΞϓϦͰදࣔ͢Δ 16

  17. Widget Category ᶃ Information widgets ᶄ Collection widgets ᶅ Control

    widgets ᶆ Hybrid widgets ΨΠυϥΠϯɿhttps://material.io/guidelines/components/widgets.html#widgets-types-of-widgets چΨΠυϥΠϯɿhttps://developer.android.com/design/patterns/widgets.html 17
  18. Collection widgets • هࣄͳͲͷಉ͡λΠϓͷෳ਺ͷΞΠςϜΛ·ͱΊΔ • ίϨΫγϣϯΛҰཡ͢Δ • λοϓͯ͠ৄࡉΛ֬ೝ͢Δ 18

  19. Widget Category ᶃ Information widgets ᶄ Collection widgets ᶅ Control

    widgets ᶆ Hybrid widgets ΨΠυϥΠϯɿhttps://material.io/guidelines/components/widgets.html#widgets-types-of-widgets چΨΠυϥΠϯɿhttps://developer.android.com/design/patterns/widgets.html 19
  20. Control widgets • ΞϓϦΛίϯτϩʔϧ͢ΔͨΊͷΞΠςϜΛදࣔ͢Δ • ࠶ੜϓϨʔϠʔͷϘλϯ΍ϥϯνϟʔ 20

  21. Widget Category ᶃ Information widgets ᶄ Collection widgets ᶅ Control

    widgets ᶆ Hybrid widgets ΨΠυϥΠϯɿhttps://material.io/guidelines/components/widgets.html#widgets-types-of-widgets چΨΠυϥΠϯɿhttps://developer.android.com/design/patterns/widgets.html 21
  22. Hybrid widgets • Ҏ্ͷཁૉΛ૊Έ߹Θͤͨ΋ͷ • ྫɿԻָϓϨʔϠʔͷϥϯνϟʔˍδϟέοτදࣔ 22

  23. Widget Design: News Widget Case

  24. Layout ᶃ Ұߦܕ • ҰߦͰχϡʔεΛίϯύΫτʹදࣔ ᶄ Ϧετܕ • هࣄΛϦετͰදࣔ͢Δ(GridΑΓList͕ଟ͍) ᶅ

    ΢Υʔϧϖʔύʔܕ • ڧௐ͍ͨࣸ͠ਅΛนࢴͷΑ͏ʹදࣔ͢Δ 24
  25. Layout ᶃ Ұߦܕ • ҰߦͰχϡʔεΛίϯύΫτʹදࣔ ᶄ Ϧετܕ • هࣄΛϦετͰදࣔ͢Δ(GridΑΓList͕ଟ͍) ᶅ

    ΢Υʔϧϖʔύʔܕ • ڧௐ͍ͨࣸ͠ਅΛนࢴͷΑ͏ʹදࣔ͢Δ 25
  26. Layout: Ұߦܕ 26

  27. Layout ᶃ Ұߦܕ • ҰߦͰχϡʔεΛίϯύΫτʹදࣔ ᶄ Ϧετܕ • هࣄΛϦετͰදࣔ͢Δ(GridΑΓList͕ଟ͍) ᶅ

    ΢Υʔϧϖʔύʔܕ • ڧௐ͍ͨࣸ͠ਅΛนࢴͷΑ͏ʹදࣔ͢Δ 27
  28. Layout: Ϧετܕ 28

  29. Layout ᶃ Ұߦܕ • ҰߦͰχϡʔεΛίϯύΫτʹදࣔ ᶄ Ϧετܕ • هࣄΛϦετͰදࣔ͢Δ(GridΑΓList͕ଟ͍) ᶅ

    ΢Υʔϧϖʔύʔܕ • ڧௐ͍ͨࣸ͠ਅΛนࢴͷΑ͏ʹදࣔ͢Δ 29
  30. Layout: ΢Υʔϧϖʔύʔܕ 30

  31. Configuration ઃఆՄೳͳ߲໨͸ҎԼͷΑ͏ͳ΋ͷ͕ఏڙ͞Ε͍ͯΔ • ৭ɿന஍, ࠇ஍… • ߋ৽ස౓ɿ30min, 1h, more… •

    χϡʔεͷ಺༰ɿtop, ranking… 31
  32. NewsܥWidgetͷදࣔཁૉͷಛ௃ ᶃ هࣄຊମ ᶄ اۀϩΰ ᶅ ߋ৽Ϙλϯ ᶆ ઃఆϘλϯ ᶇ

    ࠷ऴߋ৽೔࣌ 32
  33. Let's make Widget

  34. Widget Classes • AppWidgetService • WidgetIdͷൃߦ΍ϥϯνϟʔ΁ͷϦΫΤετΛߦ͏ • AppWidgetHost • WidgetͱϗʔϜը໘ͷ஥ཱͪΛߦ͏

    • AppWidgetHostView • WidgetΛදࣔ͢ΔͨΊͷදࣔྖҬΛఏڙ͢Δ • AppWidgetManager • Widgetͷঢ়ଶΛߋ৽ͨ͠Γɺ৘ใΛऔಘ͢Δ؅ཧΫϥε • AppWidgetProvider • Widgetͷ֤छͷΠϕϯτΛϋϯυϦϯά͢Δ • AppWidgetProviderInfo • WidgetͷϝλσʔλΛѻ͏ 34
  35. Widget Classes • AppWidgetService • WidgetIdͷൃߦ΍ϥϯνϟʔ΁ͷϦΫΤετΛߦ͏ • AppWidgetHost • WidgetͱϗʔϜը໘ͷ஥ཱͪΛߦ͏

    • AppWidgetHostView • WidgetΛදࣔ͢ΔͨΊͷදࣔྖҬΛఏڙ͢Δ • AppWidgetManager • Widgetͷঢ়ଶΛߋ৽ͨ͠Γɺ৘ใΛऔಘ͢Δ؅ཧΫϥε • AppWidgetProvider • Widgetͷ֤छͷΠϕϯτΛϋϯυϦϯά͢Δ • AppWidgetProviderInfo • WidgetͷϝλσʔλΛѻ͏ 35 ←࣮૷Ͱ࢖͏
  36. Widget Classes: Simplify • AppWidgetManager • WidgetͷViewͷߋ৽Λߦ͏ • AppWidgetProvider •

    Widgetͷߋ৽Λϋϯυϧ͢Δ • AppWidgetProviderInfo • Widgetͷجຊ৘ใΛxmlʹهड़͢Δ • xml/layout • WidgetͷϨΠΞ΢τɺ௨ৗͷϨΠΞ΢τͱ֓Ͷಉ͡ 36
  37. How to make it: Simplify ᶃ manifestʹreceiverΛ௥Ճ ᶄ AppWidgetProviderInfoͰWidget৘ใΛઃఆ ᶅ

    AppWidgetProviderͰRemoteViewΛੜ੒ˍUpdate 37
  38. Help me AndroidStudio!! • AndroidStudioͷFile>New͔Β࡞੒ը໘Λग़ͤΔʂʂ • [W]idget͔ͩΒϦετͷҰ൪Լʹ͋Δ 38

  39. Help me AndroidStudio!! 39

  40. Help me AndroidStudio!! 40

  41. Help me AndroidStudio!! 41

  42. Let’s make Widget Layout

  43. WidgetͷView • جຊతʹ௨ৗͷΞϓϦͱಉ༷ʹ૊Ή͜ͱ͕Ͱ͖·͢ • WidgetͰ͸ɺ • @RemoteView͕෇͍ͨView͕࢖͑Δ • @RemotableViewMethod͕෇͍ͨϝιου͕࢖͑Δ 43

  44. View Overview 44 Home Launcher

  45. View Overview 45 Home Launcher

  46. View: Layout • FrameLayout • LinearLayout • RelativeLayout • GridLayout

    ※ConstraintLayout͸࢖͑ͳ͍ʂʂ 46
  47. View: جຊతͳ΋ͷ • Button • ImageButton • ImageView • ProgressBar

    • TextView • ViewFlipper 47
  48. View: λΠϚʔܥ • Chronometer -> λΠϚʔΛͭ͘Δ • TextClock -> σδλϧ࣌ܭΛදࣔͤ͞Δ

    • AnalogClock -> deprecated 48
  49. View: Collection • ListView • GridView • StackView • AdapterViewFlipper

    49
  50. View Overview 50 Home Launcher

  51. RemoteViews • RemoteViews • ϏϡʔώΤϥϧΩʔΛ࣋ͭ • Widgetͷදࣔͷཁ 51

  52. View Overview 52 Home Launcher

  53. RemoteViewsFactory • RemoteViewsFactory • ListViewͷItemͳͲΛ࡞੒͢Δ͜ͱ͕Ͱ͖Δ • AdapterͷΑ͏ͳଘࡏ 53

  54. RemoteViewsFactory: Lifecycle 54 sourceɿhttps://developer.android.com/guide/topics/appwidgets/index.html

  55. RemoteViewsFactory: Lifecycle 55

  56. Let’s make Widget Provider

  57. AppWidgetProvider • BroadcastReceiverΛwrapͨ͠ศརͳΫϥε • onReceivedͰɺΠϕϯτΛϋϯυϦϯά • Widgetͷ࡞੒ʗߋ৽ʗ࡟আͳͲ • onReceivedΛ΋ͱʹࣗྗͰ࣮૷΋Մೳ 57

  58. onRecieve 58 public void onReceive(Context context, Intent intent) { String

    action = intent.getAction(); if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { this.onUpdate(…); } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { this.onDeleted(…); } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) { this.onAppWidgetOptionsChanged(…); } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { this.onEnabled(…); } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) { this.onDisabled(…); } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) { this.onRestored(…); this.onUpdate(…); } }
  59. AppWidgetProviderͷϥΠϑαΠΫϧ • onEnabled • onUpdated • onDeleted • onDisabled •

    onAppWidgetOptionsChanged • onRestored 59
  60. AppWidgetProviderͷϥΠϑαΠΫϧ 60

  61. onUpdated͸͍ͭݺ͹ΕΔ͔ • onEnabledͷ௚ޙ • onRestoredͷ௚ޙ • AppWidgetManager.ACTION_APPWIDGET_UPDATE 61

  62. When call ACTION_APPWIDGET_UPDATE • ΢ΟδΣοτઃஔ࣌ • updatePeriodMillisͷ͕࣌ؒདྷͨͱ͖ • systemͷىಈ࣌ 62

  63. updatePeriodMills • WidgetͷΞοϓσʔτִؒɺProviderInfoͰࢦఆ͢Δ • ࠷௿ߋ৽ִؒ͸30minͰ͋Δ • 0ҎԼΛࢦఆ͢Δͱݺ͹Εͳ͘ͳΔ • 30minະຬͷ৔߹͸ɺ30minʹ͞ΕΔ •

    ߋ৽ִؒΛ୹͍ͨ͘͠৔߹͸AlarmManagerͳͲΛ࢖͏ • updatePeriodMills͸0Λࢦఆ͓ͯ͘͜͠ͱ 63
  64. AppWidgetProviderͷϥΠϑαΠΫϧ • onEnabled • onUpdated • onDeleted • onDisabled •

    onAppWidgetOptionsChanged • onRestored 64
  65. onAppWidgetOptionsChanged • ϦαΠζ͞Εͨͱ͖ʹݺ͹ΕΔ • added in API level 16 •

    ը໘αΠζΛdpͰऔಘͰ͖Δ • খ͘͞ͳͬͨͱ͖ʹཁૉΛඇදࣔͨ͠ΓͰ͖Δ •OPTION_MIN_WIDTHɿݱࡏͷwidthͷԼݶ •OPTION_MAX_WIDTHɿݱࡏͷwidthͷ্ݶ •OPTION_MIN_HEIGHTɿݱࡏͷheightͷԼݶ •OPTION_MAX_HEIGHTɿݱࡏͷheightͷ্ݶ 65
  66. onRestored • ௚ޙʹonUpdateΛݺͼग़͢ • added in API level 21 •

    backup͔Βprovider͕ϦετΞ͞Εͨͱ͖ʹݺ͹ΕΔ • widgetʹඥ෇͍ͨσʔλΛอ͍࣋ͨ͠৔߹ʹ࢖͏ • ݹ͍id͔Β৽͍͠id΁ͷҾ͖ܧ͗Λߦ͏ • widgetIdͱ͸ʁʔʼAppendix΁ʂ 66
  67. Sample Code

  68. ࣮૷ 68 class SampleAppWidgetProvider : AppWidgetProvider() { override fun onUpdate(…)

    { for (appWidgetId in appWidgetIds) { val intent = Intent(context, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0) val views = RemoteViews(context.packageName, R.layout.widget_container) views.setTextViewText(R.id.textWidget, "Hello:)") views.setOnClickPendingIntent(R.id.button, pendingIntent) appWidgetManager.updateAppWidget(appWidgetId, views) } } }
  69. ղઆ 69 //RemoteViewsʹ࢖༻͍ͨ͠ϨΠΞ΢τΛ౉͢ val views = RemoteViews(context.packageName, R.layout.widget_container) //TextΛಈతʹॻ͖׵͑Δ views.setTextViewText(R.id.textWidget,

    "Hello:)") //Click࣌ͷಈ࡞Ληοτ͍ͨ͠ͱ͖ views.setOnClickPendingIntent(R.id.button, pendingIntent)
  70. ղઆ 70 views.setTextViewText(R.id.textWidget, "Hello:)") //setTextViewText͸͜ͷϝιουΛwrapͨ͠΋ͷ setCharSequence(viewId, "setText", text) //͜ͷΑ͏ʹ͢Ε͹RemoteViewsʹੜ͑ͯͳ͍ϝιου΋ݺ΂Δ views.setInt(viewId,

    "setBackgroundColor", color)
  71. ࣮૷ɿListViewͷέʔε 71 fun updateWidget(context: Context, appWidgetId: Int) { val manager

    = AppWidgetManager.getInstance(context) val remoteViews = RemoteViews(context.getPackageName(), R.layout.widget_container) // ListItemʹߋ৽௨஌Λߦ͏ͨΊͷintentΛ࡞੒ val intent = Intent(context, SampleWidgetService::class.java) .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) remoteViews.setRemoteAdapter(R.id.widgetListView, intent) // ListItem͕ۭͩͬͨ৔߹ͷEmptyදࣔ remoteViews.setEmptyView(R.id.widgetListView, android.R.id.empty) // ListItemΛλοϓͨ͠ͱ͖ͷintentΛηοτ // ListItemͷଆͰηοτ͢ΔͷͰͳ͘ɺ͜͜Ͱηοτ͢Δ val onClickIntent = Intent(context, DetailActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val onClickPendingIntent = PendingIntent.getActivity(context, 0, onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT) remoteViews.setPendingIntentTemplate(R.id.widgetListView, onClickPendingIntent) manager.updateAppWidget(appWidgetId, remoteViews) }
  72. ࣮૷ɿListViewͷέʔε 72 fun updateWidget(context: Context, appWidgetId: Int) { val manager

    = AppWidgetManager.getInstance(context) val remoteViews = RemoteViews(context.getPackageName(), R.layout.widget_container) // ListItemʹߋ৽௨஌Λߦ͏ͨΊͷintentΛ࡞੒ val intent = Intent(context, SampleWidgetService::class.java) .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) remoteViews.setRemoteAdapter(R.id.widgetListView, intent) // ListItem͕ۭͩͬͨ৔߹ͷEmptyදࣔ remoteViews.setEmptyView(R.id.widgetListView, android.R.id.empty) // ListItemΛλοϓͨ͠ͱ͖ͷintentΛηοτ // ListItemͷଆͰηοτ͢ΔͷͰͳ͘ɺ͜͜Ͱηοτ͢Δ val onClickIntent = Intent(context, DetailActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val onClickPendingIntent = PendingIntent.getActivity(context, 0, onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT) remoteViews.setPendingIntentTemplate(R.id.widgetListView, onClickPendingIntent) manager.updateAppWidget(appWidgetId, remoteViews) }
  73. ࣮૷ɿListViewͷέʔε 73 fun updateWidget(context: Context, appWidgetId: Int) { val manager

    = AppWidgetManager.getInstance(context) val remoteViews = RemoteViews(context.getPackageName(), R.layout.widget_container) // ListItemʹߋ৽௨஌Λߦ͏ͨΊͷintentΛ࡞੒ val intent = Intent(context, SampleWidgetService::class.java) .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) remoteViews.setRemoteAdapter(R.id.widgetListView, intent) // ListItem͕ۭͩͬͨ৔߹ͷEmptyදࣔ remoteViews.setEmptyView(R.id.widgetListView, android.R.id.empty) // ListItemΛλοϓͨ͠ͱ͖ͷintentΛηοτ // ListItemͷଆͰηοτ͢ΔͷͰͳ͘ɺ͜͜Ͱηοτ͢Δ val onClickIntent = Intent(context, DetailActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) val onClickPendingIntent = PendingIntent.getActivity(context, 0, onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT) remoteViews.setPendingIntentTemplate(R.id.widgetListView, onClickPendingIntent) manager.updateAppWidget(appWidgetId, remoteViews) }
  74. ࣮૷ɿListView(Item)ͷέʔε 74 override fun getViewAt(position: Int): RemoteViews { val rv

    = RemoteViews(context.packageName, R.layout.list_item) rv.setTextViewText(R.id.list_text, items[position]) //͜͜ͰIntentΛ٧ΊΔɻ͜ͷintent͸͜ͷΫϥεͷݺͼग़͠ݩ΁౉͞ΕΔ //ListItemͷ࣋ͭσʔλ͕ཉ͍͠৔߹͸͜ͷintentʹ٧ΊΔ rv.setOnClickFillInIntent(R.id.list_container, createIntent(position)) return rv }
  75. Let’s make Widget ProviderInfo

  76. AppWidgetProviderInfo Widgetͦͷ΋ͷͷઃఆΛxmlͰهड़͢Δ 76 <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minHeight="40dp" android:minResizeWidth="40dp" android:minWidth="250dp" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="1800000"

    android:configure="com.example.ymnd.widgetsampleapp.ConfigureActivity" android:widgetCategory="home_screen|keyguard"> </appwidget-provider>
  77. ը໘ͷେ͖͞Λࢦఆ͢Δ΋ͷ • int minWidth / int minHeight • σϑΥϧτͷ෯ͱߴ͞ΛdpͰࢦఆ͢Δ •

    int minResizeWidth / int minResizeHeight • ϦαΠζͷԼݶΛdpͰࢦఆ͢Δ • minResizeWidth <= minWidth • minResizeHeight <= minHeight • int resizeMode(horizontal|vertical or none) • Ͳͷํ޲ͷϦαΠζΛڐՄ͢Δ͔ 77
  78. ϓϨϏϡʔ • int previewImage • Widget PickerʗDialogͷϓϨϏϡʔʹ༻͍ΒΕΔ 78

  79. ϨΠΞ΢τ • int initialKeyguardLayout • KeyGuardʹදࣔͨ͠ͱ͖ͷϨΠΞ΢τΛࢦఆ͢Δ • int initialLayout •

    ΢ΟδΣοτͷϨΠΞ΢τΛࢦఆ͢Δ • int widgetCategory(home_screen|keyguard) • ΢ΟδΣοτΛͲ͜ʹஔ͚Δ͔ࢦఆ͢Δ • keyguard͸Android 5.0Ҏ্Ͱ͸࢖༻Ͱ͖ͳ͍ 79
  80. ը໘ߋ৽ܥ • int updatePeriodMillis • ϛϦඵͰWidgetͷߋ৽ִؒΛࢦఆ͢Δ(30minҎ্) • ComponentName configure •

    WidgetΛ௥Ճͨ͠৔߹ʹࢦఆͨ͠ActivityΛݺͼग़ͯ͘͠ΕΔ • onUpdate͸ݺ͹Εͳ͍ͷͰผ్ݺͿඞཁ͕͋Δ • Widget Dialog͔Βઃஔͨ͠ͱ͖ʹ͸ݺ͹Εͳ͍ • int autoAdvanceViewId • StackViewͳͲࢠཁૉΛ࣋ͭϨΠΞ΢τͷidΛࢦఆ͢Δ • Ϣʔβʔͷૢ࡞͕ͳͯ͘΋ࣗಈͰViewΛਐΊΔ 80
  81. configureิ଍ • ίϯϑΟάը໘ʹ࢖༻͢ΔActivity͸manifestʹintent- filterΛηοτ͢Δ 81 <activity android:name=".ColorConfigureActivity" android:theme="@style/TransParentTheme"> <intent-filter> <action

    android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> </intent-filter> </activity>
  82. ProviderͱProviderInfoͷิ଍ • ProviderͱProviderInfo͸manifestͰҎԼͷΑ͏ʹࢦఆ ͢Δ 82 <receiver android:name=".ResizableAppWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"

    /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/resizable_app_widget_info" /> </receiver>
  83. New API

  84. requestPinAppWidget & isRequestPinAppWidgetSupported

  85. what is requestPinAppWidget? • AndroidO͔Β௥Ճ͞ΕͨAPI • ΞϓϦͷத͔ΒWidgetͷ௥ՃΛߦ͑ΔΑ͏ʹʂʂ 85

  86. API's • requestPinAppWidget • ΞϓϦ಺ʹWidgetΛ௥Ճ͢Δ • isRequestPinAppWidgetSupported • ϗʔϜը໘ʹ௥ՃͰ͖Δ͔νΣοΫ͢Δ 86

  87. ࣮૷ 87 val widgetManager = AppWidgetManager.getInstance(this) val provider = ComponentName(this,

    SampleAppWidgetProvider::class.java) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (widgetManager.isRequestPinAppWidgetSupported) { val callbackIntent = Intent(this, SubActivity::class.java) val successCallback = PendingIntent.getActivity(this, 0, callbackIntent, 0) val extras = Bundle() val remoteViews = RemoteViews(this.packageName, R.layout.widget_container) remoteViews.setTextViewText(R.id.button, "ͯ͢ͱ") extras.putParcelable(EXTRA_APPWIDGET_PREVIEW, remoteViews) widgetManager.requestPinAppWidget(provider, extras, null) }
  88. ࣮૷ 88 val widgetManager = AppWidgetManager.getInstance(this) val provider = ComponentName(this,

    SampleAppWidgetProvider::class.java) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (widgetManager.isRequestPinAppWidgetSupported) { val callbackIntent = Intent(this, SubActivity::class.java) val successCallback = PendingIntent.getActivity(this, 0, callbackIntent, 0) val extras = Bundle() val remoteViews = RemoteViews(this.packageName, R.layout.widget_container) remoteViews.setTextViewText(R.id.button, "ͯ͢ͱ") extras.putParcelable(EXTRA_APPWIDGET_PREVIEW, remoteViews) widgetManager.requestPinAppWidget(provider, extras, null) }
  89. ղઆ: isRequestPinAppWidgetSupported • request api͕࢖༻Ͱ͖Δ͔࣮ߦલʹ֬ೝ͢Δ • requestPinAppWidgetͷํͰ΋booleanΛฦ͢ • →AppendixʹԿΛ֬ೝ͍ͯ͠Δ͔·ͱΊͨ 89

  90. ࣮૷ 90 val widgetManager = AppWidgetManager.getInstance(this) val provider = ComponentName(this,

    SampleAppWidgetProvider::class.java) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (widgetManager.isRequestPinAppWidgetSupported) { val callbackIntent = Intent(this, SubActivity::class.java) val successCallback = PendingIntent.getActivity(this, 0, callbackIntent, 0) val extras = Bundle() val remoteViews = RemoteViews(this.packageName, R.layout.widget_container) remoteViews.setTextViewText(R.id.button, "ͯ͢ͱ") extras.putParcelable(EXTRA_APPWIDGET_PREVIEW, remoteViews) widgetManager.requestPinAppWidget(provider, extras, null) }
  91. ղઆ: callbackIntent • ੒ޭ࣌ͷcallbackͰݺͼ͍ͨintentΛ͜͜Ͱ༻ҙ͢Δ • bundleΛηοτ͢Δ͜ͱ΋Ͱ͖Δ • EXTRA_APPWIDGET_ID͕intentʹηοτ͞ΕΔ • ग़͠෼͚Δཁૉ͕͋Ε͹widgetIdΛอଘ͢Δ͜ͱ

    • config͸request੒ޭ࣌ʹݺ͹Εͳ͍ͷͰ஫ҙ 91
  92. ࣮૷ 92 val widgetManager = AppWidgetManager.getInstance(this) val provider = ComponentName(this,

    SampleAppWidgetProvider::class.java) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (widgetManager.isRequestPinAppWidgetSupported) { val callbackIntent = Intent(this, SubActivity::class.java) val successCallback = PendingIntent.getActivity(this, 0, callbackIntent, 0) val extras = Bundle() val remoteViews = RemoteViews(this.packageName, R.layout.widget_container) remoteViews.setTextViewText(R.id.button, "ͯ͢ͱ") extras.putParcelable(EXTRA_APPWIDGET_PREVIEW, remoteViews) widgetManager.requestPinAppWidget(provider, extras, null) }
  93. ղઆ: successCallback • ઃஔޙʹݺͿcallbackΛPendingIntentͱͯ͠ηοτ • callback͸Widgetͷ௥ՃμΠΞϩάͰඞཁͱͳΔ • ࣗಈͰ௥ՃorखಈͰ௥Ճͷ̎௨Γ͋Δ • ࣗಈͰ௥Ճ

    • ΞϓϦͷը໘͔ΒભҠ͠ͳ͍ • खಈͰ௥Ճ • ϗʔϜϥϯνϟʔʹભҠ • ࣗ෼ͷΞϓϦʹ໭ͬͯ͜ͳ͍ • BroadcastͳͲΛ࢖༻ͯ͠෮ؼͯ͠΋Β͏ 93
  94. ղઆ: successCallback • ઃஔޙʹݺͿcallbackΛPendingIntentͱͯ͠ηοτ • callback͸Widgetͷ௥ՃμΠΞϩάͰඞཁͱͳΔ • ࣗಈͰ௥ՃorखಈͰ௥Ճͷ̎௨Γ͋Δ • ࣗಈͰ௥Ճ

    • ΞϓϦͷը໘͔ΒભҠ͠ͳ͍ • खಈͰ௥Ճ • ϗʔϜϥϯνϟʔʹભҠ • ࣗ෼ͷΞϓϦʹ໭ͬͯ͜ͳ͍ • BroadcastͳͲΛ࢖༻ͯ͠෮ؼͯ͠΋Β͏ 94
  95. ղઆ: successCallback • ઃஔޙʹݺͿcallbackΛPendingIntentͱͯ͠ηοτ • callback͸Widgetͷ௥ՃμΠΞϩάͰඞཁͱͳΔ • ࣗಈͰ௥ՃorखಈͰ௥Ճͷ̎௨Γ͋Δ • ࣗಈͰ௥Ճ

    • ΞϓϦͷը໘͔ΒભҠ͠ͳ͍ • खಈͰ௥Ճ • ϗʔϜϥϯνϟʔʹભҠ • ࣗ෼ͷΞϓϦʹ໭ͬͯ͜ͳ͍ • BroadcastͳͲΛ࢖༻ͯ͠෮ؼͯ͠΋Β͏ 95
  96. ࣮૷ 96 val widgetManager = AppWidgetManager.getInstance(this) val provider = ComponentName(this,

    SampleAppWidgetProvider::class.java) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (widgetManager.isRequestPinAppWidgetSupported) { val callbackIntent = Intent(this, SubActivity::class.java) val successCallback = PendingIntent.getActivity(this, 0, callbackIntent, 0) val extras = Bundle() val remoteViews = RemoteViews(this.packageName, R.layout.widget_container) remoteViews.setTextViewText(R.id.button, "ͯ͢ͱ") extras.putParcelable(EXTRA_APPWIDGET_PREVIEW, remoteViews) widgetManager.requestPinAppWidget(provider, extras, null) }
  97. ղઆ: extras • custom previewΛηοτͰ͖Δ • previewImageͱҟͳΔ • RemoteViewsΛಈతʹΧελϜͯ͠ηοτՄೳ •

    preview༻Ͱ͋ΓɺonUpdate͞ΕΔࡍʹߋ৽͞ΕΔ 97
  98. ࣮૷ 98 val widgetManager = AppWidgetManager.getInstance(this) val provider = ComponentName(this,

    SampleAppWidgetProvider::class.java) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (widgetManager.isRequestPinAppWidgetSupported) { val callbackIntent = Intent(this, SubActivity::class.java) val successCallback = PendingIntent.getActivity(this, 0, callbackIntent, 0) val extras = Bundle() val remoteViews = RemoteViews(this.packageName, R.layout.widget_container) remoteViews.setTextViewText(R.id.button, "ͯ͢ͱ") extras.putParcelable(EXTRA_APPWIDGET_PREVIEW, remoteViews) widgetManager.requestPinAppWidget(provider, extras, null) }
  99. ղઆ: requestPinAppWidget • WidgetDialogΛදࣔ͢Δϝιου • ฦΓ஋͸booleanͰ͋Δ • true͸ɺrequestPinAppWidgetʹରԠ͍ͯ͠Δ • Widget͕௥ՃͰ͖ͨ͜ͱΛҙຯ͠ͳ͍ʂʂ

    99
  100. requestPinAppWidgetͰWidgetͷ഑ஔʹࣦഊͨ͠ΒͲ͏ͳΔ͔ • Widgetͷ࡞੒੒ޭ͔࣌͠successCallbackΛݺ͹Εͳ͍ • fallback͕ͳ͍ • खಈ഑ஔͰࣦഊͨ͠৔߹ΞϓϦΛ࠶౓։͍ͯ΋Β͏ඞ ཁ͕͋Δ 100

  101. requestPinAppWidgetͷิ଍ • ௥ՃμΠΞϩά͸ࣗ෼ͰΧελϜ͢Δ͜ͱ͸Ͱ͖ͳ͍ 101

  102. Conclusion • WidgetDialogͰ௥Ճํ๏͕ܶతʹ෼͔Γ΍͘͢ͳͬͨ • ৽نAPIΛؚΊ࣮૷͕γϯϓϧͳͷͰ࡞Γ΍͍͢ • ΞϓϦΛΑΓࣗ෼ͷελΠϧʹ߹Θͤͯ࢖ͬͯ΋Β͑Δ 102

  103. Conclusion • WidgetDialogͰ௥Ճํ๏͕ܶతʹ෼͔Γ΍͘͢ͳͬͨ • ৽نAPIΛؚΊ࣮૷͕γϯϓϧͳͷͰ࡞Γ΍͍͢ • ΞϓϦΛΑΓࣗ෼ͷελΠϧʹ߹Θͤͯ࢖ͬͯ΋Β͑Δ 103

  104. whoami • twitter:@ymnd, github:@ymnder • Application Engineer • Android ೔ܦిࢠ൛ΞϓϦ

    • Android ࢴ໘ϏϡʔΞʔΞϓϦ • ٕज़ॻయ̐Ͱ೔ܦిࢠ൛ͷ஌ݟΛ·ͱΊͨຊΛग़͠·͢ 104
  105. Widget΍͍͖ͬͯ

  106. Appendix

  107. How to Calculate minWidth and minHeight WidgetͷminWidth/minHeightͷࢉग़ࣜ 70 × cell

    − 30 = size ྫɿ1ߦ2ྻͷwidgetΛ࡞੒͢Δ৔߹͸ minHeight =70 x 1 - 30 = 40 minWidth = 70 x 2 - 30 = 110 107
  108. How to Calculate minWidth and minHeight • API14ҎલͰܭࢉํ๏͕Ξοϓσʔτ͞Εͨʂ • targetSDK͕API14Ҏ্ͷ৔߹ɺϗʔϜը໘ͷ΢ΟδΣ

    οτͷ֤୺ʹϚʔδϯ͕ࣗಈతʹ௥Ճ͞ΕΔ • ͦͷͨΊɺAndroid4.0Ҏ্Ͱ͸widgetʹ༧Ί༨നΛ௥ Ճ͢Δඞཁ͸ͳ͍ 108
  109. Config Best Practice(from Material Design Guideline) • config͸؆ܿʹ͠ɺ̎ʙ̏Ҏ্ͷઃఆ߲໨Λઃ͚ͳ͍ • ϑϧεΫϦʔϯΑΓμΠΞϩάͰબ୒ͯ͠΋Β͏

    • WidgetͷઃఆͰ͋Δ͜ͱ͕෼͔Γ΍͍͢ • setupޙ͸Widgetʹ͸ઃఆ༻ͷϘλϯΛදࣔ͠ͳ͍ 109
  110. how is appWidgetId created? • appWidgetId͸WidgetΛߋ৽͢Δͱ͖ʹ༻͍ΔID • ͜ͷID͕ͲͷΑ͏ʹৼΒΕ͍ͯΔ͔͕ؾʹͳΔ • ຊ౰ʹϢχʔΫͳͷʁ

    • AppWidgetHost.java#allocateAppWidgetId 110 //Get a appWidgetId for a host in the calling process. //@return a appWidgetId public int allocateAppWidgetId() {…}
  111. AppWidgetServiceImpl.java 111 public int allocateAppWidgetId(String callingPackage, int hostId) { ...

    if (mNextAppWidgetIds.indexOfKey(userId) < 0) { // ॳظԽॲཧ // AppWidgetManager.INVALID_APPWIDGET_ID͸0 // Widget͸1͔Β࠾൪͞Ε͍ͯ͘ɻ mNextAppWidgetIds.put(userId, AppWidgetManager.INVALID_APPWIDGET_ID + 1); } final int appWidgetId = incrementAndGetAppWidgetIdLocked(userId); ... }
  112. ͲͷΑ͏ʹ൪߸͕ৼΒΕΔ͔ʁ • mNextAppWidgetIds • {userId, appWidgetId}ͷϖΞͰid͕อଘ͞ΕΔ • ಡΈग़ͨ͠ޙɺ+1ͯ͠࠶౓อଘ͍ͯ͠Δ 112 private

    final SparseIntArray mNextAppWidgetIds = new SparseIntArray(); final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1; return mNextAppWidgetIds.get(userId);
  113. ͲͷΑ͏ʹ൪߸͕ৼΒΕΔ͔ʁ • ݁࿦ɿಛผͳ஋Λੜ੒͍ͯ͠ΔΘ͚Ͱ͸ͳ͍ɻ • AppWidgetServiceImpl͸ServiceΫϥε • ଞͷΞϓϦʹ΋ׂΓ౰ͯΒΕΔ͕ϢχʔΫʹͳΔ • औಘ͍ͨ͠৔߹͸͜ΕΛ࢖͑͹औಘͰ͖Δ 113

  114. requestPinAppWidget & isRequestPinAppWidgetSupportedͷฦΓ஋ • isRequestPinAppWidgetSupported • Return {@code TRUE} if

    the default launcher supports • updateAppWidget • {@code TRUE} if the launcher supports this feature. Note the API will return without waiting for the user to respond, so getting {@code TRUE} from this API does not mean the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature. • ͜Εʹ͸ͲΜͳҧ͍͕͋Δͷ͔ 114
  115. isRequestPinAppWidgetSupportedͷฦΓ஋ AppWidgetManager → AppWidgetServiceImpl.java#isRequestPinAppWidgetSupported → ShortcutService.java#isRequestPinItemSupported → ShortcutRequestPinProcessor.java#isRequestPinItemSupported → ShortcutRequestPinProcessor.java#getRequestPinConfirmationActivity

    ShortcutService.java#getRequestPinConfirmationActivity 115
  116. ShortcutService.java#getRequestPinConfirmationActivity • default launcher͔Βػೳ͕ϋϯυϧ͞Ε͍ͯΔActivity Λ୳͍ͯ͠Δ • औಘͰ͖ͳ͔ͬͨ৔߹͸nullΛฦ͢ • nullͷ৔߹falseͰɺ͜Ε͕ඇରԠͱ͍͏൑ఆͱͳΔ 116

  117. requestPinAppWidgetͷฦΓ஋ AppWidgetManager → AppWidgetServiceImpl → ShortcutService#requestPinAppWidget → ShortcutService.java#requestPinItem → ShortcutRequestPinProcessor.java#requestPinItemLocked

    → ShortcutService.java#getRequestPinConfirmationActivity 117
  118. requestPinAppWidgetͷฦΓ஋ • default launcher͔Βػೳ͕ϋϯυϧ͞Ε͍ͯΔActivity Λ୳͍ͯ͠Δ • isRequestPinAppWidgetSupportedͱಉ͡൑ఆΛߦ͏ • ΋͠trueͩͬͨ৔߹͸requestॲཧ͕ਐΜͰ͍͘ 118

  119. ฦΓ஋ͷࠩҟ • ݁࿦ɿಉ͡΋ͷͰ͋Δ • ϦΫΤετલʹ൑ఆΛ͢΂͖ͳͷͰɺ isRequestPinAppWidgetSupportedΛ࢖͏ • docͷ௨ΓɺrequestPinAppWidget͸ɺWidgetΛஔ͚ ͔ͨͲ͏͔Λฦ͞ͳ͍ 119

  120. requestPinAppWidgetͷcallback͸Ͳ͜Ͱݺ͹ΕΔ͔ʁ • ԿΛ΋ͬͯsuccessͱ൑ఆ͍ͯ͠ΔͷͩΖ͏͔ • ·ͨfallbackͱͯ͠Կ͔ݺ΂ͨΓ͸͠ͳ͍ͩΖ͏͔ 120

  121. callback͸Ͳ͜Ͱݺ͹ΕΔ͔ʁ AppWidgetManager → AppWidgetServiceImpl → ShortcutService#requestPinAppWidget → ShortcutService.java#requestPinItem 121

  122. callback͸ͲͷλΠϛϯάͰݺ͹ΕΔ͔ʁ → LauncherApps.java#PinItemRequest ੒ޭ࣌ʹacceptΛݺͿ → ShortcutRequestPinProcessor.java#PinAppWidgetRequestInner → ShortcutRequestPinProcessor.java#PinItemRequestInner ͜͜Ͱacceptͷத਎͕࣮૷͞Ε͍ͯΔ →

    ShortcutRequestPinProcessor.java#directPinShortcut ྫ֎ΛΩϟον͍ͯͯ͠ɺ࡞੒Ͱ͖ͳ͔ͬͨ৔߹͸falseΛฦ͢ tryAccept͕falseʹͳΓɺίʔϧόοΫ͕࣮ߦ͞Εͳ͍ → ShortcutService.java#fixUpIncomingShortcutInfo 122
  123. callback͸ͲͷλΠϛϯάͰݺ͹ΕΔ͔ʁʢऴ఺ʣ • tryAccept͕trueͷͱ͖sendResultIntent͕࣮ߦ͞ΕΔ • ͢ͳΘͪ͜͜Ͱcallback͕࣮ߦ͞ΕΔ • ࣦഊ࣌͸Կ΋͞Εͳ͍Ͱfalse͕ฦΔ 123 // PinItemRequestInner#accept

    // Pin it and send the result intent. if (tryAccept()) { mProcessor.sendResultIntent(mResultIntent, extras); return true; } else { return false; }
  124. WidgetͷCustomView • @RemoteViewΛ͚ͭΕ͹ಠࣗͷViewΛఆٛͰ͖Δʁ • Ͱ͖ͳ͍Α͏Ͱ͢ • http://d.hatena.ne.jp/westzaki/20120303/1330776734 124

  125. References [ΨΠυϥΠϯ] https://developer.android.com/guide/topics/appwidgets/index.html [RemoteViews] https://qiita.com/KeithYokoma/items/9e021e2f26a9a784287d [Widgetͷͭ͘Γ͔ͨ] http://y-anz-m.blogspot.jp/2011/06/androidappwidget.html [Widget Dialogʹ͍ͭͯ] https://medium.com/wearebase/android-oreo-widget-pinning-in-

    kotlin-398d529eab28 ੋඇ͝ҰಡΛ :) 125
  126. Widget΍͍͖ͬͯ