Android开发之桌面小部件Widget的基本用法

news2024/11/19 1:20:45

咱们有这么一个需求,在桌面添加一个app的小部件,小部件展示app里面的热门数据,点击小部件的刷新按钮实现刷新小部件上面的数据的功能。

 

咱们先看实现的效果图:

小部件的基本需求实现如上:

说明,先创建一个AppWidgetProvider

HimalayanWidgets.kt
package com.gwm.widget.widget

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.RemoteViews
import com.gwm.widget.MainActivity
import com.gwm.widget.R
import com.gwm.widget.WidgetBroadcast

/**
 * Implementation of App Widget functionality.
 */
class HimalayanWidgets : AppWidgetProvider() {
    private val TAG = "HimalayanWidgets"
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        Log.e(TAG, "onUpdate")
        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {
        Log.e(TAG, "onEnabled")
        // Enter relevant functionality for when the first widget is created
    }

    override fun onDisabled(context: Context) {
        Log.e(TAG, "onDisabled")
        // Enter relevant functionality for when the last widget is disabled
    }
}

val bundle = Bundle()
internal fun updateAppWidget(
    context: Context,
    appWidgetManager: AppWidgetManager,
    appWidgetId: Int
) {
    Log.e("HimalayanWidgets", "updateAppWidget")
    val widgetText = context.getString(R.string.hot)
    // Construct the RemoteViews object
    val views = RemoteViews(context.packageName, R.layout.himalayan_widgets)
    views.setTextViewText(R.id.hot, widgetText)
    views.setTextViewText(R.id.recommend, context.getString(R.string.recommend))
    bundle.putString("update", context.getString(R.string.add_widget))
    val pendingIntent = PendingIntent.getActivity(
        context,
        0,
        Intent(context, MainActivity::class.java).putExtras(bundle),
        PendingIntent.FLAG_UPDATE_CURRENT,
        bundle
    )
    views.setOnClickPendingIntent(R.id.hot, pendingIntent)
    val broadcast = PendingIntent.getBroadcast(
        context,
        10086,
        Intent(context, WidgetBroadcast::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT
    )
    views.setOnClickPendingIntent(R.id.iv_refresh, broadcast)

    // Instruct the widget manager to update the widget
    appWidgetManager.updateAppWidget(appWidgetId, views)
}

 配置小部件对应的清单文件

<receiver
            android:name=".widget.HimalayanWidgets"
            android:exported="true">
            <intent-filter>
                //下面的action不要动
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/himalayan_widgets_info" />
        </receiver>

在配置下小部件的 himalayan_widgets_info对应的resource文件

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialKeyguardLayout="@layout/himalayan_widgets"
    android:initialLayout="@layout/himalayan_widgets"
    android:minWidth="200dp"
    android:minHeight="100dp"
    android:previewImage="@drawable/xm"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000"
    android:widgetCategory="home_screen" />

再看下小部件对应的layout布局文件himalayan_widgets.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/replace_layout"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_shape"
    android:theme="@style/ThemeOverlay.HimalayanWidget.AppWidgetContainer">

    <LinearLayout
        android:id="@+id/ll_refresh"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layoutAnimation="@anim/layout_rotate_refresh">

        <ImageView
            android:id="@+id/iv_refresh"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@string/news"
            android:src="@drawable/refresh" />
    </LinearLayout>


    <TextView
        android:id="@+id/hot"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        android:layout_marginTop="30dp"
        android:background="@drawable/hot_shape"
        android:contentDescription="@string/hot"
        android:padding="10dp"
        android:paddingStart="15dp"
        android:paddingEnd="15dp"
        android:text="@string/hot"
        android:textColor="?attr/appWidgetTextColor"
        android:textSize="18sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/recommend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="30dp"
        android:layout_toEndOf="@+id/hot"
        android:background="@drawable/recommend_shape"
        android:contentDescription="@string/recommend"
        android:padding="10dp"
        android:paddingStart="15dp"
        android:paddingEnd="15dp"
        android:text="@string/recommend"
        android:textColor="?attr/appWidgetTextColor"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/rink"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/hot"
        android:layout_margin="30dp"
        android:background="@drawable/recommend_shape"
        android:contentDescription="@string/recommend"
        android:padding="10dp"
        android:paddingStart="15dp"
        android:paddingEnd="15dp"
        android:text="@string/rink"
        android:textColor="?attr/appWidgetTextColor"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/news"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/recommend"
        android:layout_toEndOf="@+id/rink"
        android:background="@drawable/hot_shape"
        android:contentDescription="@string/recommend"
        android:padding="10dp"
        android:paddingStart="15dp"
        android:paddingEnd="15dp"
        android:text="@string/news"
        android:textColor="?attr/appWidgetTextColor"
        android:textSize="18sp" />
</RelativeLayout>

上面布局文件有个小细节,刷新按钮的动画是使用这个文件做的,layout_rotate_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/rotate_refresh" />

再看下核心动画文件rotate_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fillAfter="true"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360" />

然后说下核心逻辑HimalayanWidgets.kt文件中的onEnabled、onDisabled方法是在添加小部件和移除小部件会执行的方法,onUpdate方法会在第一添加小部件和himalayan_widgets_info.xml文件中配置的间隔时间到了后会在执行onUpdate方法。

在第一次添加小部件的时候就会执行onUpdate方法,然后咱们在这个方法里面设置默认数据(或者从网络请求对应的数据),然后设置给小部件对应的TextView或者ImageView上面,设置方法很简单如下

val widgetText = context.getString(R.string.hot)
    // 拿到小部件的布局文件
    val views = RemoteViews(context.packageName, R.layout.himalayan_widgets)
    //设置TextView的文本
    views.setTextViewText(R.id.hot, widgetText)
    views.setTextViewText(R.id.recommend, context.getString(R.string.recommend))
    bundle.putString("update", context.getString(R.string.add_widget))
    //点击文本打开一个activity的方法,先得到一个PengIntenet
    val pendingIntent = PendingIntent.getActivity(
        context,
        0,
        Intent(context, MainActivity::class.java).putExtras(bundle),
        PendingIntent.FLAG_UPDATE_CURRENT,
        bundle
    )
    //设置文本的点击事件,点击后打开对应的activity
    views.setOnClickPendingIntent(R.id.hot, pendingIntent)
    //拿到一个广播的PengIntent,用于点击刷新按钮的时候发送一个更新数据的广播
    val broadcast = PendingIntent.getBroadcast(
        context,
        10086,
        Intent(context, WidgetBroadcast::class.java),
        PendingIntent.FLAG_UPDATE_CURRENT
    )
    //设置刷新按钮的点击事件,点击后发送对应的广播
    views.setOnClickPendingIntent(R.id.iv_refresh, broadcast)

    // 最后执行更新小部件
    appWidgetManager.updateAppWidget(appWidgetId, views)

上面打开的MainActivity.kt文件如下:

package com.gwm.widget

import android.os.Bundle
import android.util.Log
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val data = intent.extras?.getString("update")
        data?.let {
            findViewById<TextView>(R.id.home).text = data
            Log.e("HimalayanWidgets", data)
        }
    }
}

上面的布局文件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">

    <TextView
        android:id="@+id/home"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

下面是要打开发送的广播类WidgetBroadcast.kt

package com.gwm.widget

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.gwm.widget.bridge.UseCase
import com.gwm.widget.utils.WidgetUtils

/**
@author xiayiye5
@date 2023/4/4 17:44
 */
class WidgetBroadcast : BroadcastReceiver() {
    private val useCase: UseCase by lazy { UseCase() }
    override fun onReceive(context: Context?, intent: Intent?) {
        context?.let {
            //发送请求最新数据
            useCase.request()
            //收到数据后刷新小部件
            WidgetUtils.liveData.observeForever { widgetData ->
                //刷新最新数据
                WidgetUtils.updateWidget(it, widgetData)
            }
        }
    }
}

上面UseCase.kt文件用于模拟请求数据的类

package com.gwm.widget.bridge

import com.gwm.widget.bean.WidgetData
import com.gwm.widget.utils.WidgetUtils


/**
@author xiayiye5
@date 2023/4/6 10:18
 */
class UseCase {
    private val dataArray =
        arrayOf(
            "最火", "最热", "最爆", "最辣", "最长", "最短", "最冷", "精选", "红火", "排名", "第一",
            "最久", "天南", "地北", "天长", "地久", "搞笑", "喜剧", "娱乐", "最佳", "折腰", "投放",
            "最近", "最久", "最短", "耗时", "少动", "多跑", "最赞", "少赞", "秒赞", "谬赞", "热情"
        )

    fun request() {
        //请求到数据后发送给小部件
        WidgetUtils.liveData.value = WidgetData(
            dataArray.random(),
            dataArray.random(),
            dataArray.random(),
            dataArray.random()
        )
    }
}

上面的WidgetData.java文件bean对象如下:

package com.gwm.widget.bean;

/**
 * @author xiayiye5
 * @date 2023/4/6 9:58
 */
public class WidgetData {
    private String title;
    private String name;
    private String des;
    private String body;

    public WidgetData(String title, String name, String des, String body) {
        this.title = title;
        this.name = name;
        this.des = des;
        this.body = body;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

在广播中我们使用useCase.request()方法模拟请求数据,拿到数据后设置给LiveData,同时在广播页面监听LiveData,数据获取成功后我们使用WidgetUtils.updateWidget(it, widgetData)方法更新小部件上面的数据。

WidgetUtils.kt文件
package com.gwm.widget.utils

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.util.Log
import android.widget.RemoteViews
import androidx.annotation.IdRes
import androidx.lifecycle.MutableLiveData
import com.gwm.widget.R
import com.gwm.widget.WidgetBroadcast
import com.gwm.widget.bean.WidgetData
import com.gwm.widget.widget.HimalayanWidgets
import java.util.*

/**
@author xiayiye5
@date 2023/4/6 9:23
 */
object WidgetUtils {
    fun updateWidget(it: Context, widgetData: WidgetData) {
        Log.e("WidgetUtils", "收到更新通知了……")
        val views = RemoteViews(it.packageName, R.layout.himalayan_widgets)
        val instance = AppWidgetManager.getInstance(it)
        val appWidgetIds =
            instance.getAppWidgetIds(ComponentName(it, HimalayanWidgets::class.java))
        views.removeAllViews(R.id.replace_layout)
        views.addView(R.id.replace_layout, RemoteViews(it.packageName, R.layout.himalayan_widgets))
        views.setTextViewText(R.id.hot, widgetData.title)
        setTextColor(views, R.id.hot)
        views.setTextViewText(R.id.recommend, widgetData.name)
        setTextColor(views, R.id.recommend)
        views.setTextViewText(R.id.rink, widgetData.des)
        setTextColor(views, R.id.rink)
        views.setTextViewText(R.id.news, widgetData.body)
        setTextColor(views, R.id.news)
        val broadcast = PendingIntent.getBroadcast(
            it, 10086,
            Intent(it, WidgetBroadcast::class.java),
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        views.setOnClickPendingIntent(R.id.iv_refresh, broadcast)
        instance.updateAppWidget(appWidgetIds, views)
        //也可以使用下面的方法更新小部件
//        instance.updateAppWidget(ComponentName(it, HimalayanWidgets::class.java), views)
    }

    /**
     * 设置文本颜色的通用方法
     */
    private fun setTextColor(views: RemoteViews, @IdRes viewId: Int) {
        views.setTextColor(
            viewId, Color.rgb(
                Random().nextInt(255).toFloat(),
                Random().nextInt(255).toFloat(),
                Random().nextInt(255).toFloat()
            )
        )
    }

    val liveData = MutableLiveData<WidgetData>()
}

最后来看下整个项目的结构:

 此项目已上传gitee,不过目前权限默认为私有,后面我会上传到GitHub,然后粘贴源码地址,如果紧急需要源码的可以私信我,或者留言邮箱,我会发你一份。

在此谢谢大家。

GitHub源码直达

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/437715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

EEG源定位

导读 自从脑电图(EEG)被发现以来&#xff0c;人们希望EEG能提供一个了解大脑的窗口&#xff0c;研究人员一直试图用EEG无创定位大脑中产生头皮电位的神经元活动。20世纪50年代的早期探索使用电场理论从头皮电位分布推断大脑中电流偶极子的位置和方向&#xff0c;引发了大量定量…

网络请求实战-实战websocket聊天程序

目录 WebSocket协议初探 Socket连接的建立过程 聊天室&#xff1a;node.js端 聊天室&#xff1a;web端 小结 WebSocket协议初探 一个基于TCP的通信协议 复用HTTP的握手基于TCP传输协议 101切换协议 WebSocket连接之后&#xff0c;传输的都是二进制数据了 Socket连接的建…

Jmeter前置处理器和后置处理器

1. 后置处理器(Post Processor) 本质上是⼀种对sampler发出请求后接受到的响应数据进⾏处理 &#xff08;后处理&#xff09;的⽅法 正则表达式后置处理器 &#xff08;1&#xff09;引⽤名称&#xff1a;下⼀个请求要引⽤的参数名称&#xff0c;如填写title&#xff0c;则可…

Transformer的原理及应用分析

上一篇博文重点介绍了Transformer的核心组件MultiHeadAttention多头注意力机制&#xff0c;本篇继续介绍transformer的原理。下图为transformer的结构图&#xff0c;其主要由位置编码、多组编码器和多组解码器。以下将重点介绍三个部分。 1. 位置编码 Positional Encoding Att…

基于html+css的图片展示18

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Scala循环中断

目录 1.使用抛出和捕获异常的方法跳出当前循环2.使用Scala中的Breaks类的break方法3.测试4.简化 使用 ._ 来引入全部内容 方便调用 在scala中无法直接使用break关键字跳出当前循环&#xff0c;但有其他方法 1.使用抛出和捕获异常的方法跳出当前循环 def main(args: Array[Str…

DAB-DETR代码学习记录之Transformer模块解析

DAB-DETR是吸收了Deformable-DETR&#xff0c;Conditional-DETR&#xff0c;Anchor-DETR等基础上完善而来的。其主要贡献为将query初始化为x,y,w,h思维坐标形式。 这篇博文主要从代码角度来分析DAB-DETR所完成的工作。 DAB-DETR主要是对Decoder模型进行改进。 位置编码的温度值…

「线性DP-步入」传球游戏

传球游戏 题目描述 ​ 上体育课的时候&#xff0c;小蛮的老师经常带着同学们一起做游戏。这次&#xff0c;老师带着同学们一起做传球游戏。 ​ 游戏规则是这样的&#xff1a;n个同学站成一个圆圈&#xff0c;其中的一个同学手里拿着一个球&#xff0c;当老师吹哨子时开始传球…

Java -枚举的使用

一、背景及定义 枚举是在JDK1.5以后引入的。主要用途是&#xff1a;将一组常量组织起来&#xff0c;在这之前表示一组常量通常使用定义常量的方式&#xff1a; public static int final RED 1; public static int final GREEN 2; public static int final BLACK 3;但是常量…

Python3 OpenCV4 计算机视觉学习手册:1~5

原文&#xff1a;Learning OpenCV 4 Computer Vision with Python 3 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 计算机视觉 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 当别人说你没有底线的时候&a…

goland 启动go module 之后goland标红,unresolved reference 无法正常追踪代码

程序是别的同事写的,你这边从git上面拉下来 1.go build之后 所有的依赖都弄好了 2.go module也开启了 3.go程序能正常运行 最后还是依赖无法正常追踪 unresolved reference 解决步骤: 1.先让程序正常运行 go clean --modcache。再执行go run main.go或者go build重新编译 2.id…

如何通过开源项目搭建私有云平台--第四步上:安装rancher,搭建K8s集群

第四步上&#xff1a;安装rancher&#xff0c;搭建K8s集群 第四步比较复杂&#xff0c;因此准备分上中下三篇文章来介绍&#xff0c;本次采用rancher来部署K8s集群&#xff0c;选择rancher如下&#xff1a; 1&#xff09;部署K8s简单&#xff0c;有一个不是那么复杂的管理界面…

163种中草药(中药材)数据集说明(含下载地址)

163种中草药(中药材)数据集说明(含下载地址) 目录 163种中草药(中药材)数据集说明(含下载地址) 1. Chinese-Medicine-163数据集说明 2. Chinese-Medicine-163数据集下载 3. 深度学习实现中草药(中药材)识别 本文将分享一个大规模的中草药(中药材)图片数据集(Chinese-Medic…

Ubuntu18.04安装ROS Melodic

1.设置安装源 为了安装ROS Melodic&#xff0c;首先需要在Ubuntu 18.04 LTS上添加安装源到source.list&#xff0c;方法如下 sudo sh -c echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list 加…

元宇宙场景下的实时互动RTI技术能力构建

元宇宙可谓是处在风口浪尖&#xff0c;无数的厂商都对元宇宙未来抱有非常美好的憧憬。正因如此&#xff0c;许许多多厂商都在用他们自己的方案&#xff0c;为元宇宙更快、更好的实现&#xff0c;在自己的领域贡献力量。LiveVideoStack 2022北京站邀请到了 ZEGO 即构科技的解决方…

MobPush Android SDK厂商通道申请指南

华为厂商申请 创建应用 登录华为开发者联盟&#xff0c;注册您的应用&#xff0c;在应用信息中获取APP ID和Client Secret 配置SHA256证书指纹 在华为开发者联盟配置SHA256证书指纹。获取及配置请参见华为官方文档配置AppGallery Connect 设置消息回执 集成华为厂商通道SDK…

数据结构—双向链表

目录 1. 链表的种类 2. 最实用的两种链表类型 3. 实现双向带头循环链表 3.1 创建头节点 3.2 实现双向循环功能—返回头指针 3.3 尾插 3.4 头插 3.5 尾删 3.6 头删 4. 实现两个重要接口函数 4.1 随机插入 4.2 随机删除 5. 顺序表和链表总结 1. 链表的种类 由上面…

【Nginx网站服务】

安装Nginx服务 1.先去官网下载软件包 2.关闭防火墙&#xff0c;将安装nginx所需软件包传到/opt目录下 systemctl stop firewalld systemctl disable firewalld setenforce 0nginx-1.18.0.tar.gz nginx-1.22.0.tar.gz3.安装依赖包 #nginx的配置及运行需要pcre、zlib等软件…

javaEE汽车用油加油站销售管理系统servlet

经过我的实地考察&#xff0c;我发现现在的销售管理有以下弊端&#xff1a; (1)、大多数都是人工记录&#xff0c;人工开票&#xff0c;这样既费时费力&#xff0c;还费财&#xff1b; (2)、由于品种种类的增多&#xff0c;记录货品的资料变得麻烦&#xff1b; (3)、对一些顾客…

COMSOL锂离子电池仿真技术与应用

背景&#xff1a; 随着各国燃油车禁售时间表的推出&#xff0c;新能源汽车的地位愈发稳固。而锂离子电池作为电动车的核心动力源&#xff0c;也越来越受到市场的追捧。锂离子电池在制作过程中涉及正极、电解液、负极、隔膜等材料的选取与匹配&#xff0c;极片设计参数的选择等…