使用场景:通知栏&桌面部件
自定义通知栏
- 通知权限申请
manifest配置
java
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
权限动态申请
java
package com.example.kotlinlearn.Common;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.widget.Toast;
import androidx.activity.ComponentActivity;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class PermissionUtils {
private static PermissionUtils permissionUtils;
private String[] permissions = {
Manifest.permission.POST_NOTIFICATIONS
};
private List<String> permissionList = new ArrayList<>();
private ActivityResultLauncher<String[]> permissionLauncher;
public static synchronized PermissionUtils getInstance() {
if (permissionUtils == null) {
permissionUtils = new PermissionUtils();
}
return permissionUtils;
}
private PermissionUtils() {
}
public void checkPermission(ComponentActivity activity) {
permissionList.clear(); // Clear previous permission requests
// Initialize the launcher if not already initialized
if (permissionLauncher == null) {
initLaunchers(activity);
}
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(permission);
}
}
permissionLauncher.launch(permissionList.toArray(new String[0]));
}
private void initLaunchers(ComponentActivity activity) {
// Initialize the launcher for requesting permissions
permissionLauncher = activity.registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
}
}
);
}
}
- 实现通知
kotlin
package com.example.kotlinlearn.RemoteView
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import com.example.kotlinlearn.R
object NotificationUtil {
private const val CHANNEL_ID = "my_channel_id"
private const val CHANNEL_NAME = "My Channel"
fun showCustomNotification(context: Context) {
// 创建通知渠道(仅适用于 Android O 及以上版本)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID, CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
).apply {
enableLights(true)
lightColor = Color.RED
enableVibration(true)
}
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
// 创建 RemoteView
val remoteViews = RemoteViews(context.packageName, R.layout.notification_layout).apply {
setTextViewText(R.id.notification_title, "自定义通知标题")
setTextViewText(R.id.notification_content, "这是自定义通知内容")
}
// 设置点击通知的行为
val intent = Intent(context, RemoteViewActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setCustomContentView(remoteViews)
.setContentIntent(pendingIntent)
.build()
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.notify(1, notification)
}
}
- activity中申请权限后直接调用就行
kotlin
package com.example.kotlinlearn.RemoteView
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.example.kotlinlearn.Common.PermissionUtils
import com.example.kotlinlearn.R
class RemoteViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PermissionUtils.getInstance().checkPermission(this)
setContentView(R.layout.activity_remote_view)
var button = findViewById<Button>(R.id.button)
button.setOnClickListener {
NotificationUtil.showCustomNotification(this);
}
}
}
效果
自定义桌面小组件
- 定义组件的布局样式widget_layout.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/widget_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello Widget"
android:textSize="18sp" />
<Button
android:id="@+id/widget_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Update" />
</LinearLayout>
- MyWidgetProvider,需要继承自AppWidgetProvider
kotlin
package com.example.kotlinlearn.RemoteView
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.widget.RemoteViews
import com.example.kotlinlearn.R
import java.util.Random
class MyWidgetProvider : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
for (appWidgetId in appWidgetIds) {
val views = RemoteViews(context.packageName, R.layout.widget_layout)
val intent = Intent(context, MyWidgetProvider::class.java)
intent.setAction(BUTTON_CLICKED)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_button, pendingIntent)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if (BUTTON_CLICKED == intent.action) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val views = RemoteViews(context.packageName, R.layout.widget_layout)
views.setTextViewText(R.id.widget_text, "Updated!" + Random().nextInt())
val componentName = ComponentName(context, MyWidgetProvider::class.java)
appWidgetManager.updateAppWidget(componentName, views)
}
}
companion object {
private const val BUTTON_CLICKED = "com.example.BUTTON_CLICKED"
}
}
- 在res/xml下创建组件的属性文件my_widget_info.xml,包括大小等值
xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="125dp"
android:minHeight="50dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/widget_layout" />
- 在manifest中配置receiver,与activity同级
xml
<receiver android:name=".RemoteView.MyWidgetProvider" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_widget_info" />
</receiver>
效果
点击后text会显示随机的数字。
原理
- 可以很简单的看到,RemoteViews实现了Parcelable,所以是可序列化的,可以在进程之间传递。
- 在官网可以看到,RemoteViews只支持基础的view,不支持自定义view,支持的布局以及组件如下所示
- 从上面的代码示例中可以知道,在更改组件属性时使用的是setTextViewText,而不是findById。
从以上的调用链可以知道,view的设置被封装在反射对象,存在mActions中,是在接收者进行真正的设置。
可以在Action的子类中找到getMethod(view, this.methodName, param, false /* async */).invoke(view, value);,接收者正是通过反射的方式调用action中封装的view设置方法。