Android实现桌面小部件:今天吃什么

发布于:2024-11-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

今天吃什么桌面小部件是专为解决日常选择困难症而设计的安卓小部件,它通过轻松愉快的滚动机制,帮助玩家在诸如“今天吃什么”这样的日常琐事中迅速做出决定。
不知道吃什么?别担心,点我帮你选择!

创建各个UI背景

app_widget_background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="?attr/appWidgetRadius" />
    <solid android:color="#BF000000" />
    <gradient android:startColor="#66CA740B" android:endColor="#99EACB10" android:angle="80"/>
</shape>

shape_eat_background.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:width="110dp"
        android:height="50dp" />
    <corners android:radius="5dp" />
    <solid android:color="#FFD154" />
    <stroke
        android:width="5dp"
        android:color="#FFD154" />
</shape>

shape_eat_foreground.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:width="110dp"
        android:height="50dp" />
    <corners android:radius="5dp" />
    <stroke
        android:width="5dp"
        android:color="#FFE58A" />
</shape>

shape_start_btn.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <size android:width="110dp" android:height="50dp"/>
    <solid android:color="#E63A75FF"/>
    <corners android:radius="10dp"/>
    <gradient android:startColor="#fd7147" android:endColor="#FFB39C" android:angle="50"/>
    <stroke android:color="#33FFFFFF" android:width="12dp"/>
</shape>

创建小部件布局layout_eat_app_widget.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    tools:layout_height="150dp"
    tools:layout_width="150dp"
    android:orientation="vertical">

    <RelativeLayout
        style="@style/Widget.TodayEatWhat.AppWidget.Container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.TodayEatWhat.AppWidgetContainer">

        <ImageView
            android:id="@+id/iv_background"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:adjustViewBounds="true"
            android:contentDescription="@null"
            android:scaleType="centerCrop"
            android:src="@drawable/app_widget_background" />

        <RelativeLayout
            style="@style/Widget.TodayEatWhat.AppWidget.ContainerInnerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignTop="@id/iv_background"
            android:layout_alignBottom="@id/iv_background">

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="50dp"
                android:layout_alignStart="@id/fl_container"
                android:layout_alignTop="@id/fl_container"
                android:layout_alignEnd="@id/fl_container"
                android:layout_marginTop="5dp"
                android:background="@drawable/shape_eat_foreground"
                android:backgroundTint="@color/white"
                android:contentDescription="@null" />

            <FrameLayout
                android:id="@+id/fl_container"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="@drawable/shape_eat_background" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="50dp"
                android:layout_alignStart="@id/fl_container"
                android:layout_alignTop="@id/fl_container"
                android:layout_alignEnd="@id/fl_container"
                android:background="@drawable/shape_eat_foreground"
                android:contentDescription="@null" />

            <TextView
                android:id="@+id/tv_last_food"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:gravity="center"
                android:shadowColor="#FFE58A"
                android:shadowDy="1"
                android:shadowRadius="1"
                android:text="吃点什么?"
                android:textColor="@color/white"
                android:textSize="14sp" />

            <TextView
                android:id="@+id/go_btn"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:layout_alignParentBottom="true"
                android:background="@drawable/shape_start_btn"
                android:gravity="center"
                android:text="吃点别的"
                android:textColor="@color/white"
                android:textSize="13sp"
                android:textStyle="bold|italic" />
        </RelativeLayout>
    </RelativeLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="8dp"
        android:text="@string/app_widget_label"
        android:textColor="@color/white"
        android:textSize="12sp" />
</LinearLayout>

创建layoutAnimation动画widget_food_animation.xml

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

创建set>translate动画widget_food_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:interpolator="@android:anim/overshoot_interpolator">
    <translate
        android:duration="800"
        android:fromYDelta="0%"
        android:toYDelta="-900%p" />
</set>

创建动画布局layout_eat_app_widget_textview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layContent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layoutAnimation="@anim/widget_food_animation_controller"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text3"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text4"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text5"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text6"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text7"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text8"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text9"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text10"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/text11"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="@color/white"
        android:shadowColor="#FFE58A"
        android:shadowDy="1"
        android:shadowRadius="1"
        android:textSize="14sp" />

</LinearLayout>

创建EatAppWidget.kt 继承 AppWidgetProvider

package com.actionbar.todayeatwhat

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.os.Bundle
import android.util.Log
import android.widget.RemoteViews
import java.util.*

/**
 * Implementation of App Widget functionality.
 */
class EatAppWidget : AppWidgetProvider() {
    companion object {
        private const val TAG = "EatAppWidget"
        private const val ACTION_EAT_WHAT_UPDATE =
            "com.actionbar.todayeatwhat.action.EAT_WHAT_UPDATE"
        private val foodList = arrayListOf(
            "螺蛳粉",
            "酸菜鱼",
            "黄焖鸡米饭",
            "麻辣烫",
            "新疆炒米粉",
            "花甲米线",
            "酸辣粉",
            "鸭血粉丝汤",
            "兰州拉面",
            "炸酱面",
            "南昌拌粉",
            "云吞面",
            "麻辣香锅",
            "蛋炒饭",
            "沙县小吃",
            "卤肉饭",
            "炸鸡汉堡",
            "水饺馄饨",
            "烧烤烤串",
            "烤冷面",
            "北京烤鸭",
            "蒸饺小笼包",
            "披萨",
            "串串香",
            "钵钵鸡",
            "意大利面",
            "寿司饭团",
            "拉面",
            "煎饼",
            "手抓饼",
            "肉夹馍",
            "过桥米线",
            "慕斯蛋糕",
            "羊肉串",
            "烤鱼",
            "墨西哥卷饼",
            "蒜蓉扇贝",
            "小龙虾",
            "剁椒鱼头",
            "盖浇饭",
            "凉面凉皮",
            "手撕兔",
            "豆腐脑",
            "土豆泥",
            "糯米饭",
            "臭豆腐",
            "大盘鸡",
            "辣卤卤菜",
            "曹氏鸭脖",
            "轻食沙拉",
            "湖南米粉",
            "烤苕皮豆干",
            "章鱼小丸子",
            "吐司三明治",
            "春卷",
            "铁板烤鱿鱼",
            "牛排",
            "韩式部队锅",
            "烤肠热狗",
            "水煮肉片",
            "生煎包",
            "锅包肉",
            "糖醋排骨",
            "金汤肥牛",
            "叉烧饭",
            "广东肠粉",
            "热干面",
            "潮汕牛肉火锅",
            "豉汁蒸排骨",
            "酥皮蛋挞",
            "麻辣脆皮鸭",
            "福鼎肉片",
            "东北大拉皮",
            "小炒黄牛肉",
            "羊肉泡馍",
            "油泼面",
            "酱香饼",
            "铁板烧",
            "甜不辣",
            "沙茶面",
            "牛肉炒河粉",
            "台湾炸鸡排",
            "鲜肉蛋堡",
            "捞汁海鲜",
            "冬阴功海鲜",
            "猪扒包",
            "里脊夹饼",
            "菠萝海鲜炒饭",
            "油炸串串",
            "蛋烘糕",
            "糯米鸡",
            "酸汤鱼",
            "鸡汤饭",
            "梅菜扣肉饼",
            "油炸串串",
            "口水鸡",
            "椒麻鸡",
            "烤肉",
            "柴火鸡",
            "葱油饼",
            "辣炒年糕",
            "驴肉火烧",
            "麻辣火锅",
            "重庆小面",
            "烤鳗鱼饭",
            "盐水鸭",
            "烧腊",
            "猪脚面"
        )
    }

    private var lastFood: String = "吃点什么?"

    override fun onReceive(context: Context?, intent: Intent?) {
        super.onReceive(context, intent)
        Log.d(TAG, "onReceive() called with: context = $context, intent = $intent")
        context ?: return
        val action = intent?.action ?: return
        when (ACTION_EAT_WHAT_UPDATE) {
            action -> {
                val componentName = ComponentName(context, EatAppWidget::class.java)
                val appWidgetManager = AppWidgetManager.getInstance(context)
                val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
                val views = RemoteViews(context.packageName, R.layout.layout_eat_app_widget)
                doAnimation(context, views, foodList.shuffled())
                appWidgetManager.partiallyUpdateAppWidget(appWidgetIds, views)
            }
        }
    }

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        Log.d(
            TAG,
            "onUpdate() called with: context = $context, appWidgetManager = $appWidgetManager, appWidgetIds = $appWidgetIds"
        )
        for (appWidgetId in appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId)
        }
    }

    override fun onEnabled(context: Context) {
        Log.d(TAG, "onEnabled() called with: context = $context")
    }

    override fun onDisabled(context: Context) {
        Log.d(TAG, "onDisabled() called with: context = $context")
    }

    override fun onAppWidgetOptionsChanged(
        context: Context?,
        appWidgetManager: AppWidgetManager?,
        appWidgetId: Int,
        newOptions: Bundle?
    ) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
        Log.d(
            TAG,
            "onAppWidgetOptionsChanged() called with: context = $context, appWidgetManager = $appWidgetManager, appWidgetId = $appWidgetId, newOptions = $newOptions"
        )
    }

    private fun updateAppWidget(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int
    ) {
        val views = RemoteViews(context.packageName, R.layout.layout_eat_app_widget)
        views.removeAllViews(R.id.fl_container)
        views.setTextViewText(R.id.tv_last_food, lastFood)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            UUID.randomUUID().hashCode(),
            Intent(context, EatAppWidget::class.java).setAction(ACTION_EAT_WHAT_UPDATE),
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        views.setOnClickPendingIntent(R.id.go_btn, pendingIntent)
        appWidgetManager.updateAppWidget(appWidgetId, views)
    }

    private fun doAnimation(context: Context, remoteViews: RemoteViews, shuffled: List<String>) {
        // 根据layout_eat_app_widget_textview布局里面的TextView的数量来取
        val foodList = shuffled.subList(0, 11)
        remoteViews.removeAllViews(R.id.fl_container)
        // 清空
        lastFood = ""
        remoteViews.setTextViewText(R.id.tv_last_food, lastFood)
        // 下标9是动画结束后显示的结果
        lastFood = foodList[9]
        val views = RemoteViews(context.packageName, R.layout.layout_eat_app_widget_textview)
        foodList.forEachIndexed { index: Int, food: String ->
            val id = context.resources.getIdentifier("text${index + 1}", "id", context.packageName)
            if (id > 0) {
                // 如果是第一条的话,将下标9的内容显示在第一条,防止用户长按小部件拖动时会显示第一条数据的问题
                if (index == 0) {
//                    views.setTextViewText(id, lastFood+index.toString())
                    views.setTextViewText(id, lastFood)
                } else {
//                    views.setTextViewText(id, food+index.toString())
                    views.setTextViewText(id, food)
                }
            }
        }
        remoteViews.addView(R.id.fl_container, views)
    }
}

strings.xml

<resources>
    <string name="app_name">TodayEatWhat</string>
    <string name="appwidget_text">EXAMPLE</string>
    <string name="app_widget_label">今天吃什么</string>
    <string name="app_widget_description">不知道吃什么?别担心,点我帮你选择!</string>
</resources>

res/xml资源目录中创建eat_app_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:description="@string/app_widget_description"
    android:initialKeyguardLayout="@layout/layout_eat_app_widget"
    android:initialLayout="@layout/layout_eat_app_widget"
    android:minWidth="110dp"
    android:minHeight="110dp"
    android:previewImage="@drawable/eat_appwidget_preview"

    android:previewLayout="@layout/layout_eat_app_widget"
    android:targetCellWidth="2"
    android:targetCellHeight="2"
    android:updatePeriodMillis="1800000"
    android:widgetCategory="home_screen"
    tools:targetApi="s" />

配置AndroidManifest.xml

<receiver
    android:name=".EatAppWidget"
    android:enabled="true"
    android:label="@string/app_widget_label"
    android:description="@string/app_widget_description"
    android:exported="false">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        <action android:name="${applicationId}.action.EAT_WHAT_UPDATE" />
    </intent-filter>

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

在themes.xml中加入

<style name="Theme.TodayEatWhat.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault">
    <!-- 用于制作圆角的小部件外部边界的半径 -->
    <item name="appWidgetRadius">16dp</item>
    <!--
    内部视图的小部件边界的半径,以形成圆角。它需要是8dp或小于appWidgetRadius的值
    -->
    <item name="appWidgetInnerRadius">8dp</item>
</style>

<style name="Theme.TodayEatWhat.AppWidgetContainer" parent="Theme.TodayEatWhat.AppWidgetContainerParent">
    <!-- 应用填充以避免小部件的内容与圆角碰撞 -->
    <item name="appWidgetPadding">10dp</item>
</style>

在styles.xml中加入

<style name="Widget.TodayEatWhat.AppWidget.Container" parent="android:Widget">
    <item name="android:id">@android:id/background</item>
    <item name="android:background">@drawable/app_widget_background</item>
</style>

<style name="Widget.TodayEatWhat.AppWidget.ContainerInnerView" parent="android:Widget">
    <item name="android:padding">?attr/appWidgetPadding</item>
</style>