Androidで表示できる通知まとめ

Android Notification

Androidの通知には様々なスタイルがあります。

今回は通知のスタイルに焦点を絞って解説いたします。

一般的なもの

何も指定しない場合、こちらの通知が表示されます。

内容が長い場合は省略されてしまうので、簡単な通知を表示する際に利用しましょう。

val notification = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("Sample")
        .setContentText("Sample notification")
        .build()
notificationManager.notify(SystemClock.uptimeMillis().toInt(), notification)

長文の表示

では文章が長いものに対応してみましょう!

val longText = "Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, “and what is the use of a book,” thought Alice, “without pictures or conversations?”\nSo she was considering, in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her.Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, “and what is the use of a book,” thought Alice, “without pictures or conversations?”"
val notification = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("Sample")
        .setContentText(longText)
        .setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
        .setStyle(NotificationCompat.BigTextStyle()
                .setBigContentTitle("DOWN THE RABBIT HOLE")
                .bigText(longText)
        )
        .build()
notificationManager.notify(SystemClock.uptimeMillis().toInt(), notification)

BigTextStyleを利用します。(今回はcompat利用)

最大何文字表示されるか気になるところです。

public class NotificationCompat {
    public static class Builder {
        /**
         * Maximum length of CharSequences accepted by Builder and friends.
         * Avoids spamming the system with overly large strings such as full e-mails.
         */
        private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024;

コード上では5 * 1024までとされていますが、実際には通知の高さが限定されているため、サンプルのように600文字程度で見切れてしまいます。画面が埋まることを避けているようです。

業務でも「何文字表示できますか?」という質問をされることがあると思いますが、明確に提示できません。画像を見せて、「これくらい表示できます」が文字数の保証はできないことをご了承いただきましょう。

また、タブレットでも試してみました。

変わりませんね‥

通知はあくまでもプレビューまでとして、アプリへ遷移してもらいましょう!

画像をメインに表示する

画像コンテンツの通知を目的とした場合に利用します。

新商品の通知などで画像を一緒に通知してあげると、より効果的な通知になります。

まずは実装例です。

val notification = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("Big Picture")
        .setContentText("Big Picture notification")
        .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.sample))
        .setStyle(NotificationCompat.BigPictureStyle()
                .bigPicture(BitmapFactory.decodeResource(resources, R.drawable.sample))
                .bigLargeIcon(null)
        )
        .build()
notificationManager.notify(SystemClock.uptimeMillis().toInt(), notification)

bigPictureでメインとなる画像を設定します。

bigLargeIconには通知を広げた際にサムネイルを非表示にするためnullを設定しています。

また、setLargeIconは通知を畳んだ際にサムネイルとして表示するために設定しています。

グループ化

Android 7.0(API level 24)以上から、任意の通知を1つのグループに表示することができます。

Android 7.0以上にのみ対応する場合は以下の実装になります。

val group = "sample-group"
val notification1 = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("Sample1")
        .setContentText("Sample1 text")
        .setGroup(group)
        .build()
val notification2 = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("Sample2")
        .setContentText("Sample2 text")
        .setGroup(group)
        .build()
val summary = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("New message!")
        .setContentText("2 new messages")
        .setGroup(group)
        .setGroupSummary(true)
        .build()
notificationManager.notify(SystemClock.uptimeMillis().toInt(), notification1)
notificationManager.notify(SystemClock.uptimeMillis().toInt(), notification2)
notificationManager.notify(999, summary)

実装の流れは以下のようになります。

  1. グループにしたい通知のsetGroupに同じ文字列を設定
  2. サマリ用の通知を作り、1と同じグループに設定
  3. サマリ用通知にsetGroupSummary(true)を設定

あとは通常通りに通知するだけです。

※サマリ用通知はIDを固定しましょう。固定することでサマリは1つとなり、グループ内の通知が増えた際にサマリ1つにまとめることができます。

Android 7.0未満に対応する

では、グループ化が使えないバージョンにも対応していきます。

先程作ったサマリ用通知にInboxStyleを適用します。

val summary = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setContentTitle("New message!")
        .setContentText("2 new messages")
        .setStyle(NotificationCompat.InboxStyle()
                .addLine("Sample2 text")
                .addLine("Sample1 text")
                .setBigContentTitle("2 new messages")
                .setSummaryText("hoge@classmethod.jp")
        )
        .setGroup(group)
        .setGroupSummary(true)
        .build()

これでグループ化に対応していないバージョンでも、1つの通知に情報をまとめることができます。

但し、こちらは各通知を開くことはできないので、バージョンによって動作に差が出ます。

メディアプレイヤー表示

こちらについてはゼロから学ぶメディアプレイヤーの実装 おまけ編を参照してください(宣伝)

メッセージ表示

ハングアウトなど、メッセージ系アプリで利用される通知です。

通知から直接返信できるようにする

Android N以降から、Direct Replyと呼ばれる機能が利用できます。

val notification = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.sample2))
        .setCategory(Notification.CATEGORY_MESSAGE)
        .setColor(resources.getColor(R.color.colorPrimary, null))
        .setContentTitle("Alex")
        .setContentText("Hello!!")
// Create reply action.
val remoteInput = RemoteInput.Builder(INPUT_REPLY).run {
    setLabel("REPLY")
    build()
}
val replyIntent = PendingIntent.getBroadcast(
        context,
        0,
        Intent(context, ReplyReceiver::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT
)
val replyAction = NotificationCompat.Action.Builder(R.drawable.ic_send_black_24dp, "REPLY", replyIntent)
        .addRemoteInput(remoteInput)
        .build()
notification.addAction(replyAction)
notificationManager.notify(NotificationConst.NOTIFICATION_REPLY, notification.build())

通知の部分は通常の通知を利用しています。後述しますが、Styleをあてた通知にも設定可能です。

RemoteInputで入力エリアを作り、actionとして設定する流れとなっています。

入力された値はActivity, Service, BroadcastReceiverで受け取ることが可能です。今回はBroadcastReceiverを選択しました。

続いて、受け取り側を作ります。

class ReplyReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        JobIntentService.enqueueWork(context, ReplyService::class.java, 0, intent)
    }
}
class ReplyService : JobIntentService() {

    override fun onHandleWork(intent: Intent) {
        val reply = RemoteInput.getResultsFromIntent(intent)?.getCharSequence(NotificationFragment.INPUT_REPLY)
        if (reply != null) {
            Timber.d("Reply: $reply")
            val notification = NotificationCompat.Builder(baseContext, NotificationConst.CHANNEL_SAMPLE)
                    .setSmallIcon(R.drawable.ic_notifications_black_24dp)
                    .setContentText("Replied!")
                    .build()
            NotificationManagerCompat.from(baseContext).notify(NotificationConst.NOTIFICATION_REPLY, notification)
        }
    }
}

BroadcastReceiverからServiceを起動して処理をさせています。必須ではありませんが、API処理など終了が不定の処理にも対応できます。

また、メッセージが正常に送信できたことをユーザーに知らせるため、先程の通知と同じIDで通知を更新しています。

あとはManifestに登録しましょう。

<receiver android:name=".presentation.notification.ReplyReceiver" />
<service
    android:name=".presentation.notification.ReplyService"
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"
    />

Android 7.0未満に対応する

Direct ReplyはAndroid N以降で利用可能でした。

それ以前の端末では、通知で直接入力できないので入力する画面に遷移させる必要があります。

class ReplyService : JobIntentService() {
    override fun onHandleWork(intent: Intent) {
        val reply = RemoteInput.getResultsFromIntent(intent)?.getCharSequence(NotificationFragment.INPUT_REPLY)
        if (reply != null) {
            ...
        } else {
            startActivity(Intent(baseContext, MaterialSampleActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
        }
    }
}

RemoteInput.getResultsFromIntent(intent)でnullが返却されるので、その場合は画面遷移させます。

通常のActionボタンが表示されるのと、変わりませんね。

メッセージを並べて通知する

Direct Replyだけでは、直前のメッセージしか見えないので、少し不便です。

NotificationCompat.MessagingStyleを使ってみましょう。

Notification.Builder#setRemoteInputHistoryでも代用可能ですが、こちらを推奨。

val me = Person.Builder()
        .setIcon(IconCompat.createWithResource(context, R.drawable.sample))
        .setName("Kenya")
        .build()
val member = Person.Builder()
        .setIcon(IconCompat.createWithResource(context, R.drawable.sample2))
        .setName("Alex")
        .build()
val notification = NotificationCompat.Builder(requireContext(), NotificationConst.CHANNEL_SAMPLE)
        .setSmallIcon(R.drawable.ic_notifications_black_24dp)
        .setCategory(Notification.CATEGORY_MESSAGE)
        .setColor(resources.getColor(R.color.colorPrimary, null))
        .setStyle(NotificationCompat.MessagingStyle(me)
                .setConversationTitle("cm-random")
                .addMessage("Hi", Date().time, me)
                .addMessage("What's up?", Date().time, member)
                .addMessage("Not much", Date().time, me)
                .addMessage("How about lunch?", Date().time, member)
        )

Reply部分は変わらないので、省略しています。

特筆すべき点は少ないですが、Personを使って発言者を表します。

ちなみに、Android 7未満ではこのような表示になります。※Compat利用時

まとめ

今回は通知の種類についてまとめてみました。

Androidでは様々なスタイルの通知を表示することができます。用途にあわせて適切なものを使うことで、より効果的に利用者に通知を送ることが出来ます。設計する際に意識してみてください。