Android Notification使用

发表于 2年以前  | 总阅读数:2291 次

在应用的开发中,我们必然会接触到应用通知这个知识,而在通知是随着Android版本进行不断变化,为了能在高版本和低版本中使用,就需要开发者去做适配,也属于查漏补缺。了解之前,先看一个效果图吧。

通知的使用的内容还是比较多的,此篇文章将会尽可能详细的介绍Notification的内容。

通知的变化

1. Android 4.1,API 16- 推出了展开式通知模板(称为通知样式),可以提供较大的通知内容区域来显示信息。

  • 用户可以通过单指向上/向下滑动的手势来展开通知。还支持以按钮的形式向通知添加其他操作。
  • 允许用户在设置中按应用关闭通知。

2. Android 4.4,API 19 和 20 - 向 API 中添加了通知监听器服务。

  • API 级别 20 中新增了 Android Wear(现已更名为 Wear OS)支持。

3. Android 5.0,API 21- 推出了锁定屏幕和提醒式通知。

  • 用户现在可以将手机设为勿扰模式,并配置允许哪些通知在设备处于“仅限优先事项”模式时打扰他们。
  • 向 API 集添加了通知是否在锁定屏幕上显示的方法 (setVisibility()),以及指定通知文本的“公开”版本的方法。
  • 添加了 setPriority() 方法,告知系统通知的“干扰性”(例如,将其设为“高”可使通知以提醒式通知的形式显示)。
  • 向 Android Wear(现已更名为 Wear OS)设备添加了通知堆栈支持。使用 setGroup() 将通知放入堆栈。请注意,平板电脑和手机尚不支持通知堆栈。通知堆栈以后会称为组或 Bundle。

4. Android 7.0,API 24- 重新设置了通知模板的样式以强调主打图片和头像。

  • 添加了三个通知模板:一个用于短信应用,另外两个用于借助展开式选项和其他系统装饰来装饰自定义内容视图。
  • 向手持设备(手机和平板电脑)添加了对通知组的支持。使用与 Android 5.0(API 级别 21)中推出的 Android Wear(现已更名为 Wear OS)通知堆栈相同的 API。
  • 用户可以使用内嵌回复功能直接在通知内进行回复(他们输入的文本将转发到通知的父应用)。

5. Android 8.0,API 26- 必须将各个通知放入特定渠道中。

  • 用户可以按渠道关闭通知,而非关闭来自某个应用的所有通知。
  • 包含有效通知的应用将在主屏幕/启动器屏幕上相应应用图标的上方显示通知“标志”。
  • 用户可以从抽屉式通知栏中暂停某个通知。您可以为通知设置自动超时时间。
  • 您还可以设置通知的背景颜色。
  • 部分与通知行为相关的 API 已从 Notification 移至 NotificationChannel。例如,在搭载 Android 8.0 及更高版本的设备中,使用 NotificationChannel.setImportance(),而非 NotificationCompat.Builder.setPriority()。

6. Android 12.0,API 31自定义通知,提供通知模板。更改了完全自定义通知的外观和行为。下面就开始我们的适配之旅吧。

创建项目

新建一个名为NotificationStudy的项目,如下图所示: 点击Finish完成项目创建,然后在app的build.gradle中的android{}闭包中开启viewBinding,代码如下:

buildFeatures {
   viewBinding true
}

项目创建好之后,我们首先改动一下activity_main.xml布局。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_show"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="显示通知"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

放一个按钮,然后再修改一下MainActivity中的代码,如下所示:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        //显示通知
        binding.btnShow.setOnClickListener {

        }
    }   
}

这里就是绑定ViewBinding,然后设置按钮点击监听,下面我们就要开始做显示通知的操作了。

显示通知

常规的通知由三个内容构成:标题、内容和图标。在8.0中还有一个通知渠道,所以我们需要先创建一个通知渠道。

① 创建通知渠道

创建通知渠道需要三个参数,渠道ID、渠道名称和渠道重要性。

首先在MainActivity中增加几个变量。

  //渠道Id
    private val channelId = "test"
    //渠道名
    private val channelName = "测试通知"
    //渠道重要级
    private val importance = NotificationManagerCompat.IMPORTANCE_HIGH
    //通知管理者
    private lateinit var notificationManager: NotificationManager
    //通知
    private lateinit var notification: Notification
    //通知Id
    private val notificationId = 1

发送通知首先要通过通知服务得到通知管理者,在onCreate方法中增加如下代码:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        //获取系统通知服务
        notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        ...
        }
    }

然后是创建通知渠道,在MainActivity中新增如下函数。

    /**
     * 创建通知渠道
     */
    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNotificationChannel(channelId: String, channelName: String, importance: Int) =
        notificationManager.createNotificationChannel(NotificationChannel(channelId, channelName, importance))

因为通知渠道是Android8.0才有的,因此我们添加一个注解,然后将数据构建通知渠道的参数传入进来,注意我们通过notificationManager的createNotificationChannel()函数去创建渠道的,如果notificationManager没有初始化的话,毫无疑问你的这一行代码会报错。

② 初始化通知

通知渠道创建好了,下面我们去初始化通知,同样在MainActivity中新增一个initNotification()函数,代码如下:

    /**
     * 初始化通知
     */
    private fun initNotification() {
        notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //创建通知渠道
            createNotificationChannel(channelId,channelName,importance)
            NotificationCompat.Builder(this, channelId)
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))//大图标(显示在通知上)
            setContentTitle("打工人")//标题
            setContentText("我要搞钱!!!")//内容
        }.build()
    }

这里首先通过版本判断检查是否需要创建通知渠道,然后会得到一个通知的Builder,通过Builder去配置通知所需要的基本参数,这里我设置了图标,标题,内容,配置完之后调用build(),完成通知的构建,最后返回一个notification,现在我们的通知就构建好了。

③ 显示通知

然后我们再修改一下onCreate中的代码,如下所示

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        //获取系统通知服务
        notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        //初始化通知
        initNotification()
        //显示通知
        binding.btnShow.setOnClickListener {
            notificationManager.notify(notificationId, notification)
        }
    }

这里我们调用了initNotification()函数,然后在点击按钮时发送通知。

    binding.btnShow.setOnClickListener {
        notificationManager.notify(notificationId, notification)
    }

运行一下,效果如下图所示:

通知点击

在上面的gif中,我们成功显示了通知,其中我们尝试去做了点击通知的动作,但是没有什么反应,因为这方面的功能还没有写,下面我们就来写。

首先要想一下点击之后要干嘛?通常来说都是跳转到指定的Activity,我们当前只有一个MainActivity,因此我们需要创建一个。

① 创建目的Activity

我们在com.llw.notification下创建DetailsActivity,对应activity_details.xml布局文件,修改一下布局文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DetailsActivity">

    <TextView
        android:id="@+id/tv_notification_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="TextView"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

这里增加一个TextView,然后回到DetailsActivity中,修改代码如下:

class DetailsActivity : AppCompatActivity() {

    private lateinit var binding: ActivityDetailsBinding

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityDetailsBinding.inflate(layoutInflater)
        setContentView(binding.root)

        intent?.let {
            binding.tvNotificationContent.text =
                "${it.getStringExtra("title")}\n" + "${it.getStringExtra("content")}"
        }
    }
}

这里的代码也很简单,就是获取传递过来的intent中携带的参数显示在TextView上,假设我们在MainActivity中点击时传递title和content。

② PendingIntent使用

我们经常使用Intent,但是接触PendingIntent是比较少的,PendingIntent可以看作是对Intent的一个封装,但它不是立刻执行某个行为,而是满足某些条件或触发某些事件后才执行指定的行为。

PendingIntent获取有三种方式:Activity、Service和BroadcastReceiver获取。PendingIntent有一个比较简单的使用方式,例如我们现在要在Activity中使用,修改initNotification()函数中的代码:

    private fun initNotification() {
        val title = "打工人"
        val content = "我要搞钱!!!"
        // 为DetailsActivity 创建显式 Intent
        val intent = Intent(this, DetailsActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            putExtra("title", title).putExtra("content", content)
        }
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)

        notification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //创建通知渠道
            createNotificationChannel(channelId, channelName, importance)
            NotificationCompat.Builder(this, channelId)
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))//大图标(显示在通知上)
            setContentTitle(title)//标题
            setContentText(content)//内容
            setContentIntent(pendingIntent)//设置内容意图
        }.build()
    }

这里的代码解释一下,首先将title、content抽离出来,然后创建intent,再创建pendingIntent 。最后在配置build中设置setContentIntent(pendingIntent),下面我们运行一下。

这里已经完成了点击通知并传递的操作,这里还有一个细节就是常规来说我们点击了这个通知表示我们已经看到了,或者已经知晓了内容,因此通知就会消失,而这里通知并没有消息。

只需要一个配置就可以做到。

setAutoCancel(true)//设置自动取消

添加位置如下图所示:

运行测试一下就行,我就不用gif说明了。

折叠通知

有时候我们设置通知的内容可能一行放不下,这个时候就需要收缩和展开通知,让用户看到完整的信息。

① 长文本

现在我将content的内容修改一下:

val content = "我要搞钱!!!富强、明主、文明、和谐、自由、平等、公正、法治、爱国、敬业、诚信、友善"

然后我们什么都不做去运行看看。

这里并没有显示多行,同时也没有那个向下展开的按钮,内容文字做了省略,因此这说明那个展开需要我们去设置。

一行代码搞定:

setStyle(NotificationCompat.BigTextStyle().bigText(content))

通过设置通知的风格样式,这里使用的是长文本信息样式,将content传递进去。添加位置如下图所示:

运行一下看看

② 显示图片

有时候我们会在通知中展开时看到图片,实际上就是使用了另一个样式,也是一行代码解决。

setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources,R.drawable.logo)))//设置样式

这行代码的位置和替换掉刚才哪一行,通知只能设置一个样式,后面设置的样式会覆盖掉前面的样式,这里我们使用大图片样式,传进入一个logo.jpg图片,你在写的时候随便用什么都行,然后我们再运行一下。

回复通知

有时候我们的手机收到短信消息,是可以直接回复的。当然了这个功能是需要手动去写的。流程就是点击按钮出现一个通知,通知中回复消息,广播或服务收到消息后更新通知,然后关闭通知。

① 创建广播

这里我们先来写这个接收回复消息的处理,这里用广播来处理,首先我们需要创建一个广播,在com.llw.notification下新建一个ReplyMessageReceiver类,里面的代码如下:

class ReplyMessageReceiver : BroadcastReceiver() {

    private val TAG = ReplyMessageReceiver::class.java.simpleName

    override fun onReceive(context: Context, intent: Intent) {
        //获取回复消息的内容
        val inputContent =
            RemoteInput.getResultsFromIntent(intent)?.getCharSequence("key_text_reply")?.toString()
        Log.d(TAG, "onReceive: $inputContent")

        if (inputContent == null) {
            Log.e(TAG, "onReceive: 没有回复消息!")
            return
        }
        //构建回复消息通知
        val repliedNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationCompat.Builder(context, "reply")
        } else {
            NotificationCompat.Builder(context)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setContentTitle("1008666")//标题
            setContentText("消息发送成功!")//内容
        }.build()

        val notificationManager =
            context.getSystemService(AppCompatActivity.NOTIFICATION_SERVICE) as NotificationManager
        //发送通知
        notificationManager.notify(2, repliedNotification)
        //1秒后取消通知
        Timer().schedule(1000){
            notificationManager.cancel(2)
        }
    }
}

这里说明一下:首先是RemoteInput表示远程输入,也就是通知栏上输入框,这里就是获取输入框的内容,注意"key_text_reply"这个值,我们在构建RemoteInput时使用的值要与这个一致,不一致你在广播中就拿不到输入的值。

然后是构建通知了,这里的设置都是常规操作,渠道id我是写死的,因此在Activity中创建通知时渠道Id也要一致。最后就是在发送通知之后加了一个1秒的延时去取消通知,表示收到回复的处理。

广播是需要在AndroidManifest.xml注册的,代码如下:

<receiver android:name=".ReplyMessageReceiver"/>

位置如下: 下面就是要构建回复通知了。因为要区别于之前的普通通知,所以这里我需要更改一下activity_main.xml中的布局代码

    <Button
        android:id="@+id/btn_show_reply"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="显示回复通知"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_show" />

增加一个按钮,点击事件后面再写。

② RemoteInput

为了区分普通通知和回复通知,在MainActivity中定义一下。

   //回复通知Id
    private val replyNotificationId = 2
    //回复通知
    private lateinit var replyNotification: Notification

下面我们新建一个initReplyNotification()函数,在里面对RemoteInput进行构建。

    private fun initReplyNotification() {
        //远程输入
        val remoteInput = RemoteInput.Builder("key_text_reply").setLabel("快速回复").build()
    }

这里RemoteInput的构建也很简单,最关键的就是"key_text_reply",刚才在适配器中已经说了,而Label就是一个输入框的提示文本。

③ PendingIntent

现在我们要通过BroadcastReceiver获取PendingIntent,在initReplyNotification()函数中新增代码:

    private fun initReplyNotification() {
        ...
        //构建回复pendingIntent
        val replyIntent = Intent(this, ReplyMessageReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(this, 0, replyIntent, PendingIntent.FLAG_ONE_SHOT)
    }

④ NotificationCompat.Action

通知动作,我们在输入框写入信息,需要一个按钮发送这个按钮,这个Action就是用来做这个事情的,在initReplyNotification()函数中新增代码:

    private fun initReplyNotification() {
        ...
        //点击通知的发送按钮
        val action = NotificationCompat.Action.Builder(0, "回复", pendingIntent).addRemoteInput(remoteInput).build()
    }

⑤ 构建通知

下面就是构建通知,在initReplyNotification()函数中新增代码:

   private fun initReplyNotification() {
        ...
        //构建通知
        replyNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel("reply", "回复消息", importance)
            NotificationCompat.Builder(this, "reply")
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))//大图标(显示在通知上)
            setContentTitle("1008666")//标题
            setContentText("你的账号已欠费2000元!")//内容
            addAction(action)
        }.build()
    }

这里的关键就在于这个addAction(action),下面我们运行一下:

然后我们看看日志:

横幅通知

横幅通知我相信你一定见过,例如收到短信、QQ、微信、钉钉的消息,都会有出现在屏幕顶部,主要是用于提醒用户的。

首先我们修改布局,在activity_main.xml中新增一个按钮,代码如下:

   <Button
        android:id="@+id/btn_show_banner"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="显示横幅通知"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_show_reply" />

① 检查横幅通知是否打开

首先在MainActivity中定义几个变量

   //横幅通知
    private lateinit var bannerNotification: Notification
    //横幅通知Id
    private val bannerNotificationId = 3
    //开启横幅通知返回
    private val bannerLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == RESULT_OK) {
                Log.d("TAG", "返回结果")
            }
        }

然后新增一个openBannerNotification()函数,代码如下:

   private fun openBannerNotification() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val bannerImportance = notificationManager.getNotificationChannel("banner").importance
        if (bannerImportance == NotificationManager.IMPORTANCE_DEFAULT) {
            val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
                .putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
                .putExtra(Settings.EXTRA_CHANNEL_ID, "banner")
            bannerLauncher.launch(intent); false
        } else true
    } else true

这里通过检查通知遇到的重要级判断是否开启横幅通知。

② 构建横幅通知渠道

在MainActivity中新增createBannerNotificationChannel()函数,代码如下:

 @RequiresApi(Build.VERSION_CODES.O)
    private fun createBannerNotificationChannel(channelId: String, channelName: String, importance: Int) =
        notificationManager.createNotificationChannel(
            NotificationChannel(channelId, channelName, importance).apply {
                description = "提醒式通知"//渠道描述
                enableLights(true)//开启闪光灯
                lightColor = Color.BLUE//设置闪光灯颜色
                enableVibration(true)//开启震动
                vibrationPattern = longArrayOf(0, 1000, 500, 1000)//震动模式
                setSound(null, null)//没有提示音
            }
        )

这里的内容相对于之前来说就多一些,有注释也好理解。

③ 构建横幅通知

在MainActivity中新增initBannerNotification()函数,代码如下:

    private fun initBannerNotification() {
        //构建通知
        bannerNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createBannerNotificationChannel("banner", "提醒消息", importance)
            NotificationCompat.Builder(this, "banner")
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))//大图标(显示在通知上)
            setContentTitle("落魄Android在线炒粉")//标题
            setContentText("不要9块9,不要6块9,只要3块9。")//内容
            setWhen(System.currentTimeMillis())//通知显示时间
            setAutoCancel(true)//设置自动取消
        }.build()
    }

这里也就是一些常规的设置。

④ 显示横幅通知

最后我们在onCreate()函数中,新增如下代码:

       //显示横幅通知
        binding.btnShowBanner.setOnClickListener {
            //检查是否授予访问权限
            if (openBannerNotification()) {
                notificationManager.notify(bannerNotificationId, bannerNotification)
            }
        }

OK,这样就可以了。

常驻通知

我们知道有一些通知,当程序运行的时候就会出现,例如我们最常见的音乐类App,而且这个通知并不是马上出现的,在此之前还有很多内容要初始化,一切就绪之后出现这个通知,就可以通过通知去控制音乐了。

我们并不需要这种复杂的操作,只有有一个通知能在App启动的时候显示出来,并且App进入后台时,通知也还在。

在MainActivity创建变量。

    //常驻通知
    private lateinit var permanentNotification: 
    //常驻通知Id
    private val permanentNotificationId = 4Notification

然后在MainActivity中新增一个showPermanentNotification()函数,代码如下:

    private fun showPermanentNotification() {
        //构建回复pendingIntent
        val permanentIntent = Intent(this, MainActivity::class.java)
        val pendingIntent =
            PendingIntent.getActivity(this, 0, permanentIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        //构建通知
        permanentNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel("permanent", "我一直存在", importance)
            NotificationCompat.Builder(this, "permanent")
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))//大图标(显示在通知上)
            setContentTitle("你在努力些什么?")//标题
            setContentText("搞钱!搞钱!还是搞钱!")//内容
            setWhen(System.currentTimeMillis())//通知显示时间
            setContentIntent(pendingIntent)
        }.build()
        permanentNotification.flags = Notification.FLAG_ONGOING_EVENT
        notificationManager.notify(permanentNotificationId, permanentNotification)
    }

这里就很简单了,主要就是去掉通知取消设置,同时设置FLAG_ONGOING_EVENT,另外在点击通知的时候进入主页面。在onCreate()函数中调用。

可以看到这里我用别的通知显示出来之后,清理一下,其他通知就没有了,而常驻通知还在,然后我程序进入后台,点击常驻通知,又会启动到前台。

自定义样式

现在我们使用的都是常规的样式通知,实际上我们可以自定义的,就是自定义通知布局,我们先来设计自定义布局的样式,就做一个音乐通知栏吧,首先是三个图标。

① 自定义通知布局

在drawable文件夹下新建ic_previous.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="36dp"
    android:height="36dp"
    android:tint="@color/white"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M7,6c0.55,0 1,0.45 1,1v10c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L6,7c0,-0.55 0.45,-1 1,-1zM10.66,12.82l5.77,4.07c0.66,0.47 1.58,-0.01 1.58,-0.82L18.01,7.93c0,-0.81 -0.91,-1.28 -1.58,-0.82l-5.77,4.07c-0.57,0.4 -0.57,1.24 0,1.64z" />
</vector>

ic_play.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="36dp"
    android:height="36dp"
    android:tint="@color/white"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M8,6.82v10.36c0,0.79 0.87,1.27 1.54,0.84l8.14,-5.18c0.62,-0.39 0.62,-1.29 0,-1.69L9.54,5.98C8.87,5.55 8,6.03 8,6.82z" />
</vector>

ic_next.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="36dp"
    android:height="36dp"
    android:tint="@color/white"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M7.58,16.89l5.77,-4.07c0.56,-0.4 0.56,-1.24 0,-1.63L7.58,7.11C6.91,6.65 6,7.12 6,7.93v8.14c0,0.81 0.91,1.28 1.58,0.82zM16,7v10c0,0.55 0.45,1 1,1s1,-0.45 1,-1V7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1z" />
</vector>

然后在layout下新建一个layout_custom_notification.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/black"
    android:gravity="center_vertical">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_song_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:text="雨下一整晚"
            android:textColor="@color/white"
            android:textSize="16sp" />

        <TextView
            android:id="@+id/tv_singer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="4dp"
            android:text="周杰伦"
            android:textColor="@color/white"
            android:textSize="12sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center">

        <ImageButton
            android:id="@+id/iv_previous"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@null"
            android:src="@drawable/ic_previous" />

        <ImageButton
            android:id="@+id/iv_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:background="@null"
            android:src="@drawable/ic_play" />

        <ImageButton
            android:id="@+id/iv_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@null"
            android:src="@drawable/ic_next" />
    </LinearLayout>

    <ImageView
        android:id="@+id/iv_avatar"
        android:layout_width="72dp"
        android:layout_height="72dp"
        android:src="@drawable/jay" />
</LinearLayout>

这里要注意一点自定义通知的界面布局只支持LinearLayout、RelativeLayout、FrameLayout,目前不支持ConstraintLayout通知布局里有ConstraintLayout的话,弹通知不会显示。

jpg图标用自己的,或者用我源码里面都可以,然后就很简单了,回到MainActivity。

② 显示自定义通知

  //自定义通知
    private lateinit var customNotification: Notification
    //自定义通知Id
    private val customNotificationId = 5

然后创建initCustomNotification()函数,代码如下:

 @SuppressLint("RemoteViewLayout")
    private fun initCustomNotification() {
        //RemoteView
        val remoteViews = RemoteViews(packageName, R.layout.layout_custom_notification)
        customNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel("custom", "自定义通知", importance)
            NotificationCompat.Builder(this, "custom")
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setCustomContentView(remoteViews)//设置自定义内容视图
            setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            setOnlyAlertOnce(true)
            setOngoing(true)
        }.build()
    }

在onCreate中调用

然后我们在activity_main.xml中新增一个按钮:

  <Button
        android:id="@+id/btn_show_custom"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="显示自定义通知"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_show_banner" />

最后在MainActivity中的onCreate()函数中新增按钮点击事件,同时点击调用通知显示:

        //显示自定义通知
        binding.btnShowCustom.setOnClickListener {
            notificationManager.notify(customNotificationId, customNotification)
        }

③ 自定义通知大小

通知布局视图布局的高度上限为 64 dp,展开后的视图布局的高度上限为 256 dp,刚才我们只设置了小的通知,那么如果要展开一个大一点的通知,最好是能够滑动通知的时候有大小变化。

其实很简单,首先我们同样要定义一个大一点同通知布局,在layout下新建一个layout_custom_notification_big.xml,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/black"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/tv_song_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="雨下一整晚"
        android:textColor="@color/white"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_singer"
        android:layout_below="@+id/tv_song_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="6dp"
        android:text="周杰伦"
        android:textColor="@color/white"
        android:textSize="16sp" />

    <LinearLayout
        android:layout_marginTop="16dp"
        android:layout_below="@+id/tv_singer"
        android:layout_alignParentStart="true"
        android:layout_toStartOf="@+id/iv_avatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center">

        <ImageButton
            android:id="@+id/iv_previous"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@null"
            android:src="@drawable/ic_previous" />

        <ImageButton
            android:id="@+id/iv_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:background="@null"
            android:src="@drawable/ic_play" />

        <ImageButton
            android:id="@+id/iv_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@null"
            android:src="@drawable/ic_next" />
    </LinearLayout>

    <ImageView
        android:layout_alignParentEnd="true"
        android:id="@+id/iv_avatar"
        android:layout_width="144dp"
        android:layout_height="144dp"
        android:src="@drawable/jay" />
</RelativeLayout>

然后我们修改initCustomNotification()函数中的代码:

@SuppressLint("RemoteViewLayout")
    private fun initCustomNotification() {
        //RemoteView
        val remoteViews = RemoteViews(packageName, R.layout.layout_custom_notification)
        val bigRemoteViews = RemoteViews(packageName, R.layout.layout_custom_notification_big)
        customNotification = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel("custom", "自定义通知", importance)
            NotificationCompat.Builder(this, "custom")
        } else {
            NotificationCompat.Builder(this)
        }.apply {
            setSmallIcon(R.mipmap.ic_launcher)//小图标(显示在状态栏)
            setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            setCustomContentView(remoteViews)
            setCustomBigContentView(bigRemoteViews)
            setOnlyAlertOnce(true)
            setOngoing(true)
        }.build()
    }

我们再创建一个RemoteView,然后通过setCustomBigContentView设置一下就可以了,下面运行一下,看看效果。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Ed0lxmlhYXul3YRdEpzbVA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
简化Android的UI开发 5年以前  |  521237次阅读
Android 深色模式适配原理分析 4年以前  |  29629次阅读
Android阴影实现的几种方案 2年以前  |  12215次阅读
Android 样式系统 | 主题背景覆盖 4年以前  |  10290次阅读
 目录