当前位置: 首页 > news >正文

c做网站教程哈尔滨学网页设计

c做网站教程,哈尔滨学网页设计,九龙坡网站建设,要进一步增强门户网站建设合力心血来潮&#xff0c;由于小米记账组件都需要收费&#xff0c;因此使用google的gemini-cli开发了一个记账app&#xff0c;在此记录下桌面小组件开发流程。一&#xff0c;创建组件布局注意&#xff0c;RemoteView目前只支持如下布局<?xml version"1.0" encoding&q…

心血来潮,由于小米记账组件都需要收费,因此使用google的gemini-cli开发了一个记账app,在此记录下桌面小组件开发流程。

一,创建组件布局

注意,RemoteView目前只支持如下布局

<?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/ll_widget_container"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="8dp"android:background="@drawable/rounded_corner_background"><!-- Top Section: Date and Total Expense --><RelativeLayoutandroid:paddingStart="16dp"android:paddingTop="16dp"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_date_expense_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/daily_expense_format"android:textColor="@color/dark_on_surface"android:textSize="18sp"android:textStyle="bold" /><TextViewandroid:id="@+id/tv_total_expense"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_date_expense_title"android:layout_marginTop="4dp"android:text="@string/total_expense_format"android:textColor="#FF5722"android:textSize="24sp"android:textStyle="bold" /><!-- Income and Balance --><TextViewandroid:id="@+id/tv_income_label"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_total_expense"android:layout_marginTop="18dp"android:text="@string/income_label"android:textColor="@color/dark_on_surface"android:textSize="12sp" /><TextViewandroid:id="@+id/tv_income_value"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_income_label"android:text="@string/total_expense_format"android:textColor="@color/dark_on_surface"android:textSize="14sp" /><TextViewandroid:id="@+id/tv_balance_label"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignTop="@id/tv_income_label"android:layout_marginStart="26dp"android:layout_toEndOf="@id/tv_income_label"android:text="@string/balance_label"android:textColor="@color/dark_on_surface"android:textSize="12sp" /><TextViewandroid:layout_alignStart="@id/tv_balance_label"android:id="@+id/tv_balance_value"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignTop="@id/tv_income_value"android:layout_toEndOf="@id/tv_income_value"android:text="@string/total_expense_format"android:textColor="@color/dark_on_surface"android:textSize="14sp" /><TextViewandroid:id="@+id/tv_monthly_balance_label"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_income_label"android:layout_marginTop="16dp"android:text="@string/monthly_balance_label"android:textColor="@color/dark_on_surface"android:textSize="12sp" /><TextViewandroid:id="@+id/tv_monthly_balance_value"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_below="@id/tv_monthly_balance_label"android:text="@string/monthly_balance_format"android:textColor="@color/dark_on_surface"android:textSize="14sp" /><!-- Balance Status Icon --><ImageViewandroid:id="@+id/iv_balance_status"android:layout_width="60dp"android:layout_height="60dp"android:layout_alignParentEnd="true"android:layout_centerVertical="true"android:src="@drawable/ic_balance_positive"android:contentDescription="@string/balance_label" /><!-- Legend Items --><LinearLayoutandroid:id="@+id/ll_legend_placeholder"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_toStartOf="@id/iv_balance_status"android:layout_marginEnd="8dp"android:orientation="vertical"android:layout_centerVertical="true"><!-- Food Expense --><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"><ImageViewandroid:layout_width="14dp"android:layout_height="14dp"android:src="@drawable/circle_food_color"android:layout_marginEnd="4dp" /><TextViewandroid:id="@+id/tv_food_expense_widget"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/expense_category_food"android:textColor="@color/dark_on_surface"android:textSize="14sp" /></LinearLayout><!-- Transport Expense --><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"><ImageViewandroid:layout_width="14dp"android:layout_height="14dp"android:src="@drawable/circle_transport_color"android:layout_marginEnd="4dp" /><TextViewandroid:id="@+id/tv_transport_expense_widget"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/expense_category_transport"android:textColor="@color/dark_on_surface"android:textSize="14sp" /></LinearLayout><!-- Shopping Expense --><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"><ImageViewandroid:layout_width="14dp"android:layout_height="14dp"android:src="@drawable/circle_shopping_color"android:layout_marginEnd="4dp" /><TextViewandroid:id="@+id/tv_shopping_expense_widget"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/expense_category_shopping"android:textColor="@color/dark_on_surface"android:textSize="14sp" /></LinearLayout><!-- Other Expense --><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"><ImageViewandroid:layout_width="14dp"android:layout_height="14dp"android:src="@drawable/circle_other_color"android:layout_marginEnd="4dp" /><TextViewandroid:id="@+id/tv_other_expense_widget"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/expense_category_other"android:textColor="@color/dark_on_surface"android:textSize="14sp" /></LinearLayout></LinearLayout></RelativeLayout><!-- Bottom Navigation/Action Bar --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center_vertical"android:layout_marginBottom="20dp"android:layout_marginTop="8dp"><ImageButtonandroid:id="@+id/fab_add_record"android:layout_width="0dp"android:layout_height="36dp"android:layout_weight="2"android:background="@drawable/rounded_button_background"android:src="@drawable/ic_add_white_24dp"android:contentDescription="@string/add_record"android:tint="@color/dark_on_primary" /></LinearLayout></LinearLayout>

二,声明组件xml

在res目录下新建一个xml文件夹,AI自动创建了account_widget_info.xml文件,内容如下

这里面声明了组件的最小宽高、更新频率、初始化布局、组件分类等信息

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:minWidth="250dp"android:minHeight="110dp"android:updatePeriodMillis="86400000"android:initialLayout="@layout/account_widget"android:resizeMode="horizontal|vertical"android:widgetCategory="home_screen"android:label="@string/account_widget_name">
</appwidget-provider>

三,实现AccountWidget类

此类中,在onUpdate方法中,可以通过appWidgetId对指定的组件进行更新

 @Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {// There may be multiple widgets active, so update all of themfor (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}}
package com.zjw.weight;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.graphics.Color;
import android.widget.RemoteViews;import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;/*** Implementation of App Widget functionality.*/
public class AccountWidget extends AppWidgetProvider {static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,int appWidgetId) {// Construct the RemoteViews objectRemoteViews views = new RemoteViews(context.getPackageName(), R.layout.account_widget);// Get current dateCalendar calendar = Calendar.getInstance();String currentDate = String.format(Locale.US, "%04d-%02d-%02d",calendar.get(Calendar.YEAR),calendar.get(Calendar.MONTH) + 1,calendar.get(Calendar.DAY_OF_MONTH));// Load data for the current dateMap<String, List<ExpenseItem>> dailyExpenses = AccountDataUtil.loadDailyExpenses(context, currentDate);// Calculate totalsfloat totalFood = AccountDataUtil.getCategoryTotal(context, currentDate, AccountDataUtil.getFoodKey());float totalTransport = AccountDataUtil.getCategoryTotal(context, currentDate, AccountDataUtil.getTransportKey());float totalShopping = AccountDataUtil.getCategoryTotal(context, currentDate, AccountDataUtil.getShoppingKey());float totalOther = AccountDataUtil.getCategoryTotal(context, currentDate, AccountDataUtil.getOtherKey());float totalExpense = totalFood + totalTransport + totalShopping + totalOther;// 获取每月预算并计算每日计划金额float monthlyBudget = SettingsActivity.getMonthlyBudget(context);// 获取当月天数Calendar cal = Calendar.getInstance();int daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);// 计算每日计划金额float dailyPlan = daysInMonth > 0 ? monthlyBudget / daysInMonth : 0;// 计算结余 = 每日计划 - 当日总支出float balance = dailyPlan - totalExpense;// 计算当月结余// 获取当月已过天数int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);// 计算当月总预算float monthlyTotalBudget = dailyPlan * dayOfMonth;// 计算当月总支出float monthlyTotalExpense = 0;for (int i = 1; i <= dayOfMonth; i++) {String dateStr = String.format(Locale.US, "%04d-%02d-%02d",cal.get(Calendar.YEAR),cal.get(Calendar.MONTH) + 1,i);monthlyTotalExpense += AccountDataUtil.getTotalExpenseForDate(context, dateStr);}// 计算当月结余 = 当月总预算 - 当月总支出float monthlyBalance = monthlyTotalBudget - monthlyTotalExpense;// Update text fields with real dataint greenColor = Color.parseColor("#4CAF50");int redColor = Color.parseColor("#F44336");views.setTextViewText(R.id.tv_date_expense_title, String.format(context.getString(R.string.daily_expense_format), String.valueOf(calendar.get(Calendar.DAY_OF_MONTH))));views.setTextViewText(R.id.tv_total_expense, String.format(context.getString(R.string.total_expense_format), totalExpense));views.setTextViewText(R.id.tv_income_value, String.format(context.getString(R.string.daily_plan_format), dailyPlan));views.setTextColor(R.id.tv_income_value,greenColor);views.setTextViewText(R.id.tv_balance_value, String.format(context.getString(R.string.total_expense_format), balance));views.setTextViewText(R.id.tv_monthly_balance_value, String.format(context.getString(R.string.monthly_balance_format), monthlyBalance));if (monthlyBalance >= 0) {views.setTextColor(R.id.tv_monthly_balance_value,greenColor);} else {views.setTextColor(R.id.tv_monthly_balance_value, redColor);}views.setTextViewText(R.id.tv_income_label, context.getString(R.string.daily_plan_label));views.setTextViewText(R.id.tv_balance_label, context.getString(R.string.balance_label));views.setTextViewText(R.id.tv_monthly_balance_label, context.getString(R.string.monthly_balance_label));// 根据结余状态设置图标if (balance >= 0) {views.setImageViewResource(R.id.iv_balance_status, R.drawable.ic_balance_positive);views.setTextColor(R.id.tv_balance_value, greenColor);} else {views.setImageViewResource(R.id.iv_balance_status, R.drawable.ic_balance_negative);views.setTextColor(R.id.tv_balance_value, redColor);}views.setContentDescription(R.id.fab_add_record, context.getString(R.string.add_record));// Update individual expense category totalsviews.setTextViewText(R.id.tv_food_expense_widget,String.format(context.getString(R.string.expense_category_food) + " %.1f", totalFood));views.setTextViewText(R.id.tv_transport_expense_widget,String.format(context.getString(R.string.expense_category_transport) + " %.1f", totalTransport));views.setTextViewText(R.id.tv_shopping_expense_widget,String.format(context.getString(R.string.expense_category_shopping) + " %.1f", totalShopping));views.setTextViewText(R.id.tv_other_expense_widget,String.format(context.getString(R.string.expense_category_other) + " %.1f", totalOther));// Set up click listeners for buttonsIntent addRecordIntent = new Intent(context, AccountEditActivity.class);PendingIntent addRecordPendingIntent = PendingIntent.getActivity(context, 0, addRecordIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);views.setOnClickPendingIntent(R.id.fab_add_record, addRecordPendingIntent);// Set up click listener for the entire widget to launch AccountEditActivityIntent launchEditIntent = new Intent(context, AccountEditActivity.class);PendingIntent launchEditPendingIntent = PendingIntent.getActivity(context, 0, launchEditIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);views.setOnClickPendingIntent(R.id.ll_widget_container, launchEditPendingIntent);// 只保留上一天、下一天和添加记录按钮的点击事件Intent prevDayIntent = new Intent(context, AccountWidget.class);prevDayIntent.setAction("ACTION_PREV_DAY_CLICK");PendingIntent prevDayPendingIntent = PendingIntent.getBroadcast(context, 4, prevDayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);Intent nextDayIntent = new Intent(context, AccountWidget.class);nextDayIntent.setAction("ACTION_NEXT_DAY_CLICK");PendingIntent nextDayPendingIntent = PendingIntent.getBroadcast(context, 5, nextDayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);// Instruct the widget manager to update the widgetappWidgetManager.updateAppWidget(appWidgetId, views);}@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {// There may be multiple widgets active, so update all of themfor (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}}@Overridepublic void onReceive(Context context, Intent intent) {super.onReceive(context, intent);if (intent != null) {String action = intent.getAction();if (action != null) {// Handle button clicks (for now, just update the widget)AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);ComponentName thisWidget = new ComponentName(context, AccountWidget.class);int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget);onUpdate(context, appWidgetManager, appWidgetIds);// 只处理上一天和下一天按钮的点击事件switch (action) {case "ACTION_PREV_DAY_CLICK":// Handle previous daybreak;case "ACTION_NEXT_DAY_CLICK":// Handle next daybreak;}}}}@Overridepublic void onEnabled(Context context) {// Enter relevant functionality for when the first widget is created}@Overridepublic void onDisabled(Context context) {// Enter relevant functionality for when the last widget is disabled}
}

 总结下,AppWidgetProvider主要逻辑如下

 1,使用RemoteViews传入布局

 2,根据数据对view进行自定义更新

 3,调用appWidgetManager.updateAppWidget方法传入id和remoteView,即可刷新组件

 4,设计点击事件,这通过PendingIntent触发

 四,Androidmanifest中声明组件

AI自动在Androidmanifest中创建了一个静态receiver,其AccountWidget继承AppWidgetProvider,

可以选择性实现如下模版方法

<receiverandroid:name=".AccountWidget"android:exported="true"><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/account_widget_info" /></receiver>

以上,即创建完毕了一个桌面组件。

五,原理

1,launcher通过PKMS查询声明了action是"android.appwidget.action.APPWIDGET_UPDATE"的receiver,

2,解析Metadata,这样launcher就可以解析到目标应用的xml声明

<meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/account_widget_info" />

3,解析appwidget xml,保存此组件的基本信息

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:minWidth="250dp"android:minHeight="110dp"android:updatePeriodMillis="86400000"android:initialLayout="@layout/account_widget"android:resizeMode="horizontal|vertical"android:widgetCategory="home_screen"android:label="@string/account_widget_name">
</appwidget-provider>

4,通过RemoteView保存一个update的全部action行为,action保存了view id和行为name,主要用于反射

以setTextColor为例,

这样就将viewId,methodName通过binder传递给了launcher,launcher再通过遍历action列表调用ReflectionAction#apply方法,即可实现行为传递,本质是反射调用。

5,组件应用可通过发送广播,强行更新指定id的组件,id可通过AppWidgetManager获取,如下

// Trigger widget updateIntent intent = new Intent(this, AccountWidget.class);intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);int[] ids = AppWidgetManager.getInstance(getApplication()).getAppWidgetIds(new ComponentName(getApplication(), AccountWidget.class));intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids);sendBroadcast(intent);

随后,发送一个AppWidgetManager.ACTION_APPWIDGET_UPDATE广播,即可触发AppWidgetProvider#onReceiver,如下,进而调用到AccountWidget,

6,组件应用更新组件参数后,通过appWidgetManager.updateAppWidget(appWidgetId, views);即触发组件的实际更新

内部通过一个Service,将packageName,view和appWeightId传递给组件Service,即实现更新

http://www.dtcms.com/a/471722.html

相关文章:

  • 什么网站是专门做艺术字的网站一定要备案
  • 二手房网站排行屯济宁做网站公司
  • 内存频率重要吗?对游戏影响大不大?玖合异刃DDR5 8000Mhz评测
  • mem 设备控制 GPIO - C程序通过sysfs文件系统使用GPIO中断
  • 简约风格装修seo排名如何
  • 有关使用AVX,EIGEN等加速方法过程中cmake选项的说明
  • 二手书交易网站开发背景WordPress发邮件4.4.1
  • 【项目开发Trip第2站】casbin库与身份权限划分
  • POET 宣布投资7500万美元
  • wordpress底部插件郑州seo顾问热狗网
  • 韩国网站免费模板美丽定制 网站模板
  • 栾城网站制作产品推广策划案
  • 如何做网站联盟营销网站设计中的js
  • Wazuh vs. 安全洋葱:开源SOC核心平台用哪个呢?
  • 容桂网站制作价位晋江论坛手机版
  • 有做网站看病的吗用vs2010做网站应用程序脱机
  • 如何评价一个网站的网站建设林和西网站建设
  • 云服务器的应用场景
  • 网站开发需要什么工程师软装工作室
  • 怎么建立一个网站好文创产品设计方案模板
  • 提高网站排名怎么做小程序定制开发公司前十名
  • ins做甜品网站手车做网课网站
  • 【AI图片生成】图片生成,这里特别注意根据实际需要换成对应适合自己需求的图片大小和尺寸
  • 框架--Lombok
  • 要找人做公司网站应该怎么做开发工具控件属性怎么打开
  • Ubuntu 22.04 安装 AppImage
  • 电商网站开发流程图百度有刷排名软件
  • 昆明百度搜索排名优化如何做网站的优化
  • 业主验证超时问题解决方案
  • 新乡网站开发的公司电话亚马逊图书官网