• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

为您的Android应用编写小部件:更新小部件

应用程序小部件使您的用户可以轻松访问应用程序最常用的功能,同时让您的应用程序出现在用户的主屏幕上。通过将小部件添加到您的项目,您可以提供更好的用户体验,同时鼓励用户保持与您的应用程序的互动,因为每次他们浏览主屏幕时,他们都会看到您的小部件,显示您的应用程序中最有用和有趣的内容。

在这个由三部分组成的系列中,我们将构建一个应用程序小部件,它具有几乎所有android 应用程序小部件中的所有功能。 

在第一篇文章中, 我们创建了一个小部件,用于检索和显示数据,并执行独特的操作以响应onClick 事件。然而,我们的小部件仍然缺少一个关键的功能:它永远不会用新信息更新s。从来没有提供任何新东西的小部件并不是那么有用,因此我们将为我们的小部件提供两种不同的更新方式。 

在本系列结束时,我们将扩展小部件以根据计划自动检索和显示新数据,并响应用户交互。

我们从上次中断的地方继续,所以如果您没有我们在第一篇文章中创建的小部件的副本,那么您可以从 GitHub 下载它。

更新布局

第一步是更新我们的布局以显示一些随时间变化的数据。为了帮助我们准确了解我们的小部件何时收到每次更新,我将指示小部件在执行更新时检索并显示当前时间。 

我将在小部件布局中添加以下内容: 

  • TextView显示上次更新标签的A。

  • TextView将显示上次更新时间的A。

在我们处理new_app_widget.xml文件时,我还将添加TextView最终允许用户手动触发更新的文件: 

<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="@dimen/widget_margin"
   android:background="@drawable/widget_background"
   android:orientation="vertical" >
   
   <LinearLayout
       android:background="@drawable/tvbackground"
       style="@style/widget_views"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal">
       
       <TextView
           android:id="@+id/id_label"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@string/widget_id"
           style="@style/widget_text" />
           
       <TextView
           android:id="@+id/id_value"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="."
           style="@style/widget_text"  />
           
   </LinearLayout>
   
   <TextView
       android:id="@+id/launch_url"
       style="@style/widget_views"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/URL"
       android:background="@drawable/tvbackground"/>
//Add a ‘Tap to update’ TextView//
   <TextView
       android:id="@+id/update"
       style="@style/widget_views"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/update"
       android:background="@drawable/tvbackground"/>
   <LinearLayout
       android:background="@drawable/tvbackground"
       style="@style/widget_views"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
//Add a TextView that’ll display our ‘Last Update’ label//
   <TextView
       android:id="@+id/update_label"
       style="@style/widget_text"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="@string/last_update"   />
//Add the TextView that’ll display the time of the last update// 
   <TextView
       android:id="@+id/update_value"
       style="@style/widget_text"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="@string/time" />
   </LinearLayout>
</LinearLayout

接下来,定义我们在布局中引用的新字符串资源: 

<string name="update">Tap to update</string>
<string name="last_update">Last Update</string>
<string name="time">%1$s</string>

看起来与您的@string/time典型字符串不同,因为它只是一个占位符,将在运行时替换d 。

这给了我们完成的布局:

为您的Android应用编写小部件:更新小部件  第1张

根据计划更新您的小部件 

因为它是最容易实现的方法,所以我将首先在一段时间后自动更新我们的小部件。 

创建这种自动更新计划时,1800000 毫秒(30 分钟)是您可以使用的最小间隔。即使您将项目设置updatePeriodMillis为少于 30 分钟,您的小部件仍将每半小时更新一次。

决定你的小部件应该多久更新一次从来都不是一件容易的事。频繁更新会消耗更多的设备电池电量,但如果将这些更新设置得太远,您的小部件可能最终会向用户显示明显过时的信息。

要找到适合您的特定项目的平衡点,您需要在一系列更新频率范围内测试您的小部件,并测量每个频率对电池寿命的影响,同时监控小部件的内容是否明显过时。 

由于频率太频繁是那些没有“正确”答案的令人沮丧的主观问题之一,因此可以通过安排一些用户测试来获得第二意见。您甚至可以设置一些 A/B 测试来检查某些更新频率是否比其他更新频率更积极。 

打开项目的AppWidgetProviderInfo文件 ( res/xml/new_app_widget_info.xml ),您会看到它已经定义了 86400000 毫秒(24 小时)的默认更新间隔。 

android:updatePeriodMillis="86400000"

有很多小部件每 24 小时更新一次,例如显示天气预报的小部件可能只需要每天检索一次新信息。但是,即使您的小部件每天只需要更新一次,这在测试您的应用程序时也不理想。没有人愿意等待 24 小时来查看他们的onUpdate()方法是否正确触发,因此您通常会在执行初始测试时使用尽可能短的时间间隔,然后在需要时更改此值d.

为了更容易测试我们的应用程序,我将使用尽可能小的间隔: 

android:updatePeriodMillis="1800000"

检索和显示当前时间 

该onUpdate()方法负责Views用新信息更新您的小部件。如果您打开项目的小部件提供程序 ( java/values/NewAppWidget.java ),您会看到它已经包含onUpdate()方法的大纲: 

   @Override
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
       // There may be multiple widgets active, so update all of them//
       for (int appWidgetId : appWidgetIds) {
           updateAppWidget(context, appWidgetManager, appWidgetId);
       }
   }

无论您的小部件是自动更新还是响应用户交互更新,更新过程都是完全相同的:小部件管理器发送带有ACTION_APPWIDGET_UPDATE操作的广播意图,小部件提供者类通过调用该onUpdate()方法来响应,该方法又调用updateAppWidget()辅助方法。 

一旦我们更新了我们的NewAppWidget.java类以检索和显示当前时间,我们的项目将使用同一段代码,而不管onUpdate()方法是如何被触发的: 

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import java.text.DateFormat;
import java.util.Date;
public class NewAppWidget extends AppWidgetProvider {
   static void updateAppWidget(Context context,
                               AppWidgetManager appWidgetManager,
                               int appWidgetId) {
//Retrieve the time//
       String timeString =
               DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
 //construct the RemoteViews object//
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
       views.setTextViewText(R.id.id_value, String.valueOf(appWidgetId));
//Retrieve and display the time// 
       views.setTextViewText(R.id.update_value,
               context.getResources().getString(
                       R.string.time, timeString));
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.weixiaolive.com/en/"));
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
       views.setOnClickPendingIntent(R.id.launch_url, pendingIntent);
       appWidgetManager.updateAppWidget(appWidgetId, views);
   }
   @Override
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
 //If multiple widgets are active, then update them all//
       for (int appWidgetId : appWidgetIds) {
           updateAppWidget(context, appWidgetManager, appWidgetId);
       }
   }
}

上面的代码有一个问题:如果用户TextView在一分钟内多次点击,则不会有视觉指示小部件已更新,只是因为没有新的时间显示。这可能不会对您的最终用户造成问题,他们不太可能坐在那里,TextView每分钟点击多次并想知道为什么时间没有改变。onUpdate()但是,在测试您的应用程序时,这可能是一个问题,因为这意味着您必须在触发该方法 之间等待至少 60 秒。

为了确保总是有某种视觉确认该onUpdate()方法已成功运行,我将在调用时显示一个toastonUpdate(): 

import android.widget.Toast;
...
...
...
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
   for (int appWidgetId : appWidgetIds) {
       updateAppWidget(context, appWidgetManager, appWidgetId);
       Toast.makeText(context, "Widget has been updated! ", Toast.LENGTH_SHORT).show();
  }
   }
}

为您的Android应用编写小部件:更新小部件  第2张

更新您的小部件以响应用户操作

我们的小部件现在将每半小时自动更新一次,但是响应用户交互而更新呢? 

通过在布局中添加Tap to Update TextView并扩展NewAppWidget.java类来检索和显示当前时间,我们已经奠定了很多基础onUpdate()。

剩下要做的就是创建一个Intentwith AppWidgetManager.ACTION_APPWIDGET_UPDATE,这是一个通知小部件是时候更新的动作,然后调用这个 Intent 以响应用户与Tap to Update TextView的交互。 

这是完成的NewAppWidget.java类:

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import java.text.DateFormat;
import java.util.Date;
import android.widget.Toast;
public class NewAppWidget extends AppWidgetProvider {
   static void updateAppWidget(Context context,
                               AppWidgetManager appWidgetManager,
                               int appWidgetId) {
//Retrieve the current time//
       String timeString =
               DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
       RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.new_app_widget);
       views.setTextViewText(R.id.id_value, String.valueOf(appWidgetId));
       views.setTextViewText(R.id.update_value,
               context.getResources().getString(
                       R.string.time, timeString));
//Create an Intent with the AppWidgetManager.ACTION_APPWIDGET_UPDATE action//
       Intent intentUpdate = new Intent(context, NewAppWidget.class);
       intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
//Update the current widget instance only, by creating an array that contains the widget’s unique ID// 
       int[] idArray = new int[]{appWidgetId};
       intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, idArray);
//Wrap the intent as a PendingIntent, using PendingIntent.getBroadcast()//
       PendingIntent pendingUpdate = PendingIntent.getBroadcast(
               context, appWidgetId, intentUpdate,
               PendingIntent.FLAG_UPDATE_CURRENT);
//Send the pending intent in response to the user tapping the ‘Update’ TextView//
       views.setOnClickPendingIntent(R.id.update, pendingUpdate);
       Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.weixiaolive.com/en/"));
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
       views.setOnClickPendingIntent(R.id.launch_url, pendingIntent);
//Request that the AppWidgetManager updates the application widget//
       appWidgetManager.updateAppWidget(appWidgetId, views);
   }
   @Override
   public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
       for (int appWidgetId : appWidgetIds) {
           updateAppWidget(context, appWidgetManager, appWidgetId);
           Toast.makeText(context, "Widget has been updated! ", Toast.LENGTH_SHORT).show();
       }
   }
}

在测试项目时,能够按需更新小部件是非常宝贵的,因为这意味着您永远不必等待更新间隔结束。即使您没有在完成的应用程序中包含此功能,您也可能希望添加一个临时的“点击更新”按钮,以便在测试项目时让您的生活更轻松。 

创建预览图像 

我们的小部件现在看起来与 Android Studio 自动为我们生成的小部件完全不同,但目前它仍在使用自动生成的预览图像。

为您的Android应用编写小部件:更新小部件  第3张

理想情况下,您的预览图像应该鼓励用户从小部件选择器中选择您的小部件,但至少它应该是您的小部件实际外观的准确表示!目前,我们的预览图像都没有勾选这些框,所以我们需要创建一个新的。 

最简单的方法是使用Android 模拟器中包含的Widget Preview应用程序。

创建小部件预览

首先,在 Android 虚拟设备 (AVD) 上安装您的项目。然后打开 AVD 的应用程序抽屉并启动 Widget Preview 应用程序。AVD 将显示设备上安装的每个应用程序的列表——从列表中选择您的应用程序。 

您的小部件将显示在空白背景上。花一些时间调整您的小部件的大小和调整,使其看起来最好,一旦您对结果感到满意,请选择拍摄快照。屏幕截图将以 PNG 格式保存在 AVD 的下载文件夹中。要检索此文件,请切换回 Android Studio 并打开Device File Explorer,方法是从工具栏中选择View > Tool Windows > Device File Explorer。在设备文件资源管理器视图中,导航到sdcard/Download文件夹,您将在其中找到保存为的预览图像:[app_name]_ori_[orientation].png

为您的Android应用编写小部件:更新小部件  第4张

将此图像拖出 Android Studio 并将其放在易于访问的位置,例如您的桌面。给图像一个更具描述性的名称,然后将图像拖放到项目的可绘制文件夹中。现在您可以打开AppWidgetProviderInfo文件(在本例中为new_app_widget_info.xml)并更改以下行以引用您的新预览图像: 

android:previewImage="@drawable/example_appwidget_preview"

最后,您可以从项目中删除不必要的example_appwidget_preview drawable。 

测试你的小部件

是时候对完成的小部件进行测试了!

首先,在您的物理 Android 设备或 AVD 上安装更新的项目。从您的主屏幕中删除此小部件的所有现有实例,以便您知道您正在使用最新版本。 

要添加您的小部件,请按主屏幕上的任何空白区域,点击小部件,然后选择您的小部件。您将有机会根据需要调整小部件的大小和位置。 

Last Update值将显示您创建此小部件的时间。按点击更新,小部件应显示敬酒和新时间。如果您在主屏幕上放置小部件的第二个实例,它应该显示与第一个不同的时间。 

为您的Android应用编写小部件:更新小部件  第5张

您可以更新这些小部件实例中的任何一个,方法是给它的Tap to Update TextView一个水龙头。如果您为第一个小部件触发手动更新,则第二个小部件将不会更新,反之亦然。 

除了手动更新之外,App Widget Manager 还会根据您创建第一个实例的时间,每 30 分钟更新一次您的所有小部件实例。尽管手动更新可能导致实例显示不同的时间,但每半小时一次,所有实例将同时更新一次,此时它们将显示相同的时间。

您可能希望在主屏幕上保留此小部件的一些实例,以便监控它们的内容如何随时间变化以响应自动和手动更新的组合。

结论

在本系列中,我们一直在创建一个 Android 应用小部件,以演示如何实现应用小部件中的所有最常见功能。如果您从一开始就一直在关注,那么此时您将构建一个小部件,该小部件会自动更新并响应用户输入,并且能够对 onClick 事件做出反应(您也可以 下载完整的项目来自 GitHub)。 

文章目录
  • 更新布局
  • 根据计划更新您的小部件
    • 检索和显示当前时间
  • 更新您的小部件以响应用户操作
  • 创建预览图像
  • 测试你的小部件
  • 结论