原文: Android 桌面小组件使用-Stars-One的杂货小窝

借助公司上的几个项目,算是学习了Android桌面小组件的用法,记下踩坑记录

基本步骤

1.创建小组件布局

这里需要注意的事,小组件布局里不能使用自定义View,只能使用原生的组件,比如说LinearLayout,TextView,连约束布局都不能使用

<?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/white"
android:orientation="vertical"
android:padding="16dp"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"> <TextView
android:id="@+id/tvDate"
style="@style/textStyle14"
android:textColor="#313131"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2023-12-10" /> <ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" /> <TextView
android:id="@+id/tvTime"
android:textColor="#313131"
style="@style/textStyle14"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="12:10" /> </LinearLayout> <LinearLayout
android:layout_marginTop="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/result_clean"/> <LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_marginStart="9dp"
android:gravity="center_vertical"
android:layout_height="match_parent"
android:layout_weight="1" >
<TextView
style="@style/textStyle14"
android:textColor="#313131"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="125.4MB"/>
<TextView
style="@style/textStyle14"
android:textColor="#313131"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Junk"/>
</LinearLayout> <TextView
android:layout_gravity="center_vertical"
android:id="@+id/tvClean"
android:textColor="#313131"
style="@style/textStyle14"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Clean" /> </LinearLayout> </LinearLayout>

2.创建provider

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import android.widget.RemoteViews.RemoteView
import ten.jou.recover.R class CleaningWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
appWidgetIds.forEach {
//如果小组件布局中使用不支持的组件,这里创建RemoteViews时候,IDE会报红提示!
val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
//绑定数据
remoteView.setTextViewText(R.id.tv1,"hello world")
appWidgetManager.updateAppWidget(it, remoteView)
} }
}

AppWidgetProvider本质就是一个广播接收器,所以在清单文件需要声明(见步骤4)

这里先补充下,RemoteViews对于TextView,ImageView等View,有设置文本,字体颜色,图片等相关方法,但并不是所有方法都支持,绑定数据的时候需要注意下小组件是否支持!

3.创建xml属性声明

在xml文件夹里新建widget_info.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:targetCellWidth="4"
android:targetCellHeight="2"
android:minWidth="250dp"
android:minHeight="110dp"
android:updatePeriodMillis="0"
android:initialLayout="@layout/widget_layout"
tools:targetApi="s">
</appwidget-provider>

Android12版本以上新增的2个属性,声明组件是4*2大小

  • targetCellWidth
  • targetCellHeight

4.清单文件声明

<receiver
android:name=".view.CleaningWidget"
android:enabled="true"
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/widget_info" />
</receiver>

5.代码添加小组件

官方说Android12不允许直接通过代码添加小组件,只能让用户手动去桌面拖动添加,但是我手头的三星系统却是支持的(也是Android12),具体还没有细究...

而官方文档上的写的例子如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val context = this@DesktopWidgetActivity val appWidgetManager: AppWidgetManager =
context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, CleaningWidget::class.java) //判断启动器是否支持小组件pin
val successCallback = if (appWidgetManager.isRequestPinAppWidgetSupported) {
// Create the PendingIntent object only if your app needs to be notified
// that the user allowed the widget to be pinned. Note that, if the pinning
// operation fails, your app isn't notified.
Intent(context, CleaningWidget::class.java).let { intent ->
// Configure the intent so that your app's broadcast receiver gets
// the callback successfully. This callback receives the ID of the
// newly-pinned widget (EXTRA_APPWIDGET_ID).
//适配android12的
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
PendingIntent.getBroadcast(
context,
0,
intent,
flags
)
}
} else {
null
} appWidgetManager.requestPinAppWidget(myProvider, null, successCallback)
}

这里提下,上面的设置flags方法

val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.FLAG_MUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}

有个新项目的targetSdk为34(即Android14),如果使用上面的代码会出现下面崩溃错误提示

Targeting U+ (version 34 and above) disallows creating or retrieving a PendingIntent with FLAG_MUTABLE, an implicit Intent within and without FLAG_NO_CREATE and FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT for security reasons. To retrieve an already existing PendingIntent, use FLAG_NO_CREATE, however, to create a new PendingIntent with an implicit Intent use FLAG_IMMUTABLE.

实际上提示已经告诉我们怎么去改代码了,我这里把PendingIntent.FLAG_MUTABLE改为FLAG_IMMUTABLE就不会出现了上述的崩溃问题

应该是Android14添加的限制:

  • 如果Intent不传数据,必须使用PendingIntent.FLAG_IMMUTABLE
  • 如果是需要传递数据,则还是需要使用PendingIntent.FLAG_MUTABLE

定时刷新小组件UI

首先,我们得知道,如何主动去更新数据:

val context = it.context
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, CleaningWidget::class.java)
val remoview = CleaningWidget.getRemoteViewTest(context) //更新某类组件
appWidgetManager.updateAppWidget(myProvider,remoview)
//更新具体某个组件id
appWidgetManager.updateAppWidget(widgetId,remoview)

getRemoteViewTest方法就是创建一个remoteview,然后调用remoteview相关方法设置文本之类的进行数据填充,代码就略过不写了,详见上述基本步骤2

上面的方法我们注意到updateAppWidget可以传不同的参数,一般我们用的第二个方法,指定更新某个组件

但这里又是需要我们传一个组件id,所以就是在步骤2的时候,我们根据需要需要存储下widgetId比较好,一般存入数据库,或者使用SharePreference也可

然后,就是对于定时的情况和对应方案:

  1. 如果是间隔多长更新一次,可以使用开一个服务,在服务中开启协程进行
  2. 如果是单纯的时间文本更新,可以使用TextClock组件,比如说 12:21这种
  3. 小组件的xml中默认可以设置定时更新时长,不过最短只能需要15分钟
  4. 可以使用闹钟服务AlarmManager来实现定时,不过此用法需要结合pendingintent和广播接收器使用,最终要在广播接收器里调用更新数据方法
  5. JobScheduler来实现定时更新,似乎受系统省电策略影响,适用于不太精确的定时事件(官方文档上推荐这个)
  6. WorkManager来实现定时更新(实际上算是JobScheduler升级版),似乎受系统省电策略影响,适用于不太精确的定时事件

应该是除了第一种方法,其他都是可以在应用被杀死的情况进行更新小组件UI

小组件播放动画

progressbar实现

帧动画不手动调用anim.start()方法是不会播放的,然后在网上看到一篇文章,使用了progressbar来实现,步骤如下:

在drawable文件夹准备帧动画文件

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" android:visible="true">
<item android:drawable="@drawable/cat_1" android:duration="100" />
<item android:drawable="@drawable/cat_2" android:duration="100" />
<item android:drawable="@drawable/cat_3" android:duration="100" />
<item android:drawable="@drawable/cat_4" android:duration="100" />
</animation-list>
<ProgressBar
android:indeterminateDrawable="@drawable/cat_animdrawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

indeterminateDrawable设置为上面的帧动画文件即可

layoutanim实现

主要是利用viewgroup的初次显示的时候,会展示当前view的添加动画效果,从而实现比较简单的动画效果,如平移,缩放等

可以看实现的敲木鱼一文Android-桌面小组件RemoteViews播放木鱼动画 - 掘金

使用ViewFlipper

ViewFlipper主要是轮播使用的

里面可放几个元素,之后通过设置autoStart为true,则保证自动轮播

flipInterval属性则是每个元素的间隔时间(帧动画的时间),单位为ms

不过在remoteview中使用的话,缺点就是里面的元素数目只能固定死

否则只能通过定义不同layout文件(如3个元素则是某个layout,4个元素则是某个layout,然后根据选择来创建remoteview)

<ViewFlipper
android:id="@+id/viewFlipper"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_margin="4dp"
android:autoStart="true"
android:flipInterval="800"> <ImageView
android:id="@+id/vf_img_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/peace_talisman_1" /> <ImageView
android:id="@+id/vf_img_2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
android:src="@drawable/peace_talisman_2" />
</ViewFlipper>

补充

获取当前桌面的组件id列表

//获得当前桌面已添加的组件的id列表(可能用户添加了多个)
val context = it.context
val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(context, CleaningWidget::class.java) val info =appWidgetManager.getAppWidgetIds(myProvider)
toast(info.size.toString())

参考

Android 桌面小组件使用的更多相关文章

  1. Android桌面小组件的使用

    一:建立一个类继承AppWidgetProvider 二:建立AWP的布局文件: 布局自己定义一个,但是在使用控件上是有要求的: 以上是Widget目前支持的控件. 三:编写AWP的信息文件:需要在r ...

  2. Android Widget小组件开发(一)——Android实现时钟Widget组件的步骤开发,这些知识也是必不可少的!

    Android Widget小组件开发(一)--Android实现时钟Widget组件的步骤开发,这些知识也是必不可少的! PS:学习自某网站(不打广告) 这个小组件相信大家都很熟悉吧,以前的墨迹天气 ...

  3. Android-Widget桌面小组件

    1, 掌握Widget的用:Widget的用途,能够添加到手机桌面的程序 2, Widget的特点和用法步骤: 特点:快捷,方便,个性化,可自定义功能,可及时控制更新Widget显示内容 3, 用法步 ...

  4. Android桌面小插件——Widget

    Android桌面小插件--Widget 效果图 实现 1. 创建Widget类 创建一个Widget类,并实现页面创建的时候,就实现显示时间 package com.kongqw.kqwwidget ...

  5. Android 桌面小部件

    1. 添加AppWidgetProvider 实际上就是个带有界面的BroadcastReceiver public class SimpleWidgetProvider extends AppWid ...

  6. android 桌面小工具(Widget)开发教程

    刚学做了哥Widget,感觉不错哦,先来秀下效果(用朋友手机截的图) 这个Widget会每隔5秒钟自动切换内容和图片,图片最好使用小图,大图会导致你手机桌面(UI)线程卡顿 教程开始: 1.首先创建一 ...

  7. android桌面小火箭升空动画

    public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceS ...

  8. Android开发工程师文集-1 小时学会Widget小组件开发

    前言 大家好,给大家带来Android开发工程师文集-1 小时学会Widget小组件开发的概述,希望你们喜欢 学会用Widget (小组件) Widget小组件很方便,很快捷,可以个性化,自己定制,相 ...

  9. Android开发中实现桌面小部件

    详细信息请参考原文:Android开发中实现桌面小部件 在Android开发中,有时候我们的App设计的功能比较多的时候,需要根据需要更简洁的为用户提供清晰已用的某些功能的时候,用桌面小部件就是一个很 ...

  10. Android中有四大组件的简单总结

    Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器. 一:了解四大基本组件 Activity ...

随机推荐

  1. IT技术:开篇 - IT技术系列文章

    笔者已经工作了十多年,在这十多年里,笔者在博客上也对自己所学的技术方面的事情记录成了博文.这些博文有些是笔者自己所学所记,有些是将网上的博文进行的转载.在经历了这么多的技术学习之后,笔者将自己的经验记 ...

  2. 华硕ROG Zenith Extreme Alpha主板评测:主板界的航空母舰

    在AMD推第一代锐龙线程撕裂者的时期,华硕ROG Zenith Extreme主板凭借给力的用料,各种便利的超频设计,出色的SupermeFX音效还有万兆网卡赢得了不少用户的好评.现在AMD推出了第二 ...

  3. 资深工程师 VSCode C/C++ 必备开发插件

    1.前言 俗话说"工欲善其事,必先利其器",下面介绍几个VSCode提高开发效率的插件,资深工程师必备. 2.基础插件 2.1.Chinese(Simplified) vscode ...

  4. SP28304 ADATEAMS - Ada and Teams 题解

    题目传送门 前置知识 乘法逆元 | 排列组合 解法 简单的排列组合.从 \(n\) 个学校中选出 \(a\) 个学校,共有 \(\dbinom{n}{a}\) 种不同的方案数.选出的 \(a\) 个学 ...

  5. NC24755 [USACO 2010 Dec S]Apple Delivery

    题目链接 题目 题目描述 Bessie has two crisp red apples to deliver to two of her friends in the herd. Of course ...

  6. NC26257 小雨坐地铁

    题目链接 题目 题目描述 小雨所在的城市一共有 \(m\) 条地铁线,分别标号为 1 号线,2 号线,--,m 号线.整个城市一共有 \(n\) 个车站,编号为 \(1 \sim n\) .其中坐 i ...

  7. win32 - Direct3D 11的demo创建

    我们可以使用D3D为游戏,科学和桌面应用程序创建3-D图形. 非官方demo实例: https://github.com/Ray1024/D3D11Tutorial 当然,我们第一步要开始认识里面的基 ...

  8. 【Android 抓包对抗】客户端证书和域名校验绕过

    1. 按照之前的方式(https://www.cnblogs.com/gradyblog/p/17197707.html)进行抓包发现证书校验失败 SSL handshake with client ...

  9. Docker实践之07-数据管理

    目录 一.数据卷概述 二.创建数据卷 三.查看数据卷 四.挂载数据卷 五.删除数据卷 六.挂载主机目录或文件 七.挂载数据卷与主机目录/文件的比较 一.数据卷概述 数据卷是一个可供一个或多个容器使用的 ...

  10. dart的map方法如何获取index

    一.前言 我们常常用dart中的map方法遍历List,但是直接用map,只能取到value,得不到index,这是因为map方法就只给了一个value,map的实现如下图: 下面就看看获取index ...