应用程序小部件可让您的用户轻松访问应用程序最常用的功能,同时使应用程序出现在用户的主屏幕上。 通过将小部件添加到您的项目中,您可以提供更好的用户体验,同时鼓励用户继续使用您的应用程序,因为每当他们瞥一眼主屏幕时,就会看到您的小部件,显示您的应用程序中最有用和最有用的部分。有趣的内容。
在这个由三部分组成的系列文章中,我们将构建一个应用程序小部件,它具有几乎在每个 Android应用程序小部件中都可以找到的所有功能。
在第一篇文章中, 我们创建了一个小部件,该小部件可检索和显示数据并响应onClick
事件执行唯一的操作。 然后在第二篇文章中, 我们扩展了小部件,以根据计划 并 响应用户交互 自动检索和显示新数据 。
我们从上次停下来的地方接机,因此,如果您没有我们在第一部分中创建的小部件的副本,则可以从GitHub下载它 。
通过配置活动增强小部件
尽管我们的窗口小部件具有开箱即用的功能,但是某些窗口小部件需要进行初始设置-例如,显示用户消息的窗口小部件将需要其电子邮件地址和密码。 您可能还希望使用户能够自定义窗口小部件,例如更改其颜色甚至修改其功能(例如窗口小部件更新的频率)。
如果您的窗口小部件是可自定义的或需要进行一些设置,则应包括一个配置Activity ,只要用户将窗口小部件放在其主屏幕上,该配置就会自动启动。
如果您对要包含在小部件中的信息和功能有很多想法,则配置活动也可能会派上用场。 您可以提供一个配置“活动”,用户在其中选择并选择对他们而言重要的内容,而不是将所有这些内容填充到一个复杂且可能引起混乱的布局中。
如果您确实包含配置Activity,那么不要气carried,因为在某些情况下选择会变成太多选择 。 设置小部件并不难,因此建议您将小部件限制为两个或三个配置选项。 如果您在努力不超过此限制,那么请考虑是否所有这些选择确实必要,或者是否只是在设置过程中增加了不必要的复杂性。
要创建配置活动,您需要执行以下步骤。
1.创建活动布局
这与构建常规Activity的布局完全相同,因此请创建一个新的布局资源文件,并照常添加所有UI元素。
配置活动是用户执行初始设置的地方,因此一旦完成此活动,他们就不太可能再次需要它。 由于小部件的布局已经比常规的“活动”布局小,因此您不应通过给用户一种直接从小部件布局重新启动配置“活动”的方式浪费宝贵的空间。
在这里,我正在创建一个简单的configuration_activity.xml布局资源文件,其中包含一个按钮,点击该按钮将创建应用程序小部件。
<?xml version="1.0" encoding="utf-8"?>
https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/setupWidget"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginTop="175dp"
android:text="Create widget"
android:textColor="#FFFFFF" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/setupWidget"
android:layout_alignParentLeft="true"
android:layout_marginBottom="67dp"
android:text="Configuration options go here!" />
</RelativeLayout>
2.创建您的配置类
您的配置活动必须包含启动配置活动的Intent
传递的应用程序小部件ID。
如果确实包含配置活动,则请注意,当用户创建窗口小部件实例时,不会调用onUpdate()
方法,因为启动配置活动时不会发送ACTION_APPWIDGET_UPDATE
广播。 配置活动的责任是直接从AppWidgetManager
请求此第一次更新。 对于所有后续更新,将正常调用onUpdate()
方法。
创建一个名为configActivity
的新Java类,并添加以下内容:
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.os.Bundle;
import android.widget.Button;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
public class configActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.configuration_activity);
setResult(RESULT_CANCELED);
Button setupWidget = (Button) findViewById(R.id.setupWidget);
setupWidget.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleSetupWidget();
}
});
}
private void handleSetupWidget() {
showAppWidget();
}
int appWidgetId;
private void showAppWidget() {
appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
//Retrieve the App Widget ID from the Intent that launched the Activity//
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
appWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
//If the intent doesn’t have a widget ID, then call finish()//
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
//TO DO, Perform the configuration and get an instance of the AppWidgetManager//
...
...
...
//Create the return intent//
Intent resultValue = new Intent();
//Pass the original appWidgetId//
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
//Set the results from the configuration Activity//
setResult(RESULT_OK, resultValue);
//Finish the Activity//
finish();
}
}
}
3.在项目清单中声明配置活动
在清单中声明配置活动时,需要指定它接受ACTION_APPWIDGET_CONFIGURE
操作:
<activity android:name=".configActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
4.在您的AppWidgetProviderInfo文件中声明配置活动
由于配置活动是在包范围之外引用的,因此您需要使用完全限定的名称空间对其进行声明:
<?xml version="1.0" encoding="utf-8"?>
http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/new_app_widget"
android:initialLayout="@layout/new_app_widget"
android:minHeight="40dp"
android:minWidth="40dp"
android:previewImage="@drawable/example_appwidget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1800000"
android:widgetCategory="home_screen"
//Add the following line//
android:configure="com.jessicathornsby.widget.configActivity">
</appwidget-provider>
现在,无论何时创建此窗口小部件的新实例,都会启动配置活动,并且需要在创建窗口小部件之前完成此活动中的所有选项。 在这种情况下,这仅意味着轻按“ 创建小部件”按钮。
请记住,您可以从GitHub 下载完成的窗口小部件 ,其中包含配置活动。
应用程序小部件最佳实践
在整个系列中,我们一直在构建一个应用程序小部件,以演示几乎在每个Android应用程序小部件中都可以找到的所有核心功能。 至此,您知道如何创建一个小部件,该小部件可检索和显示数据,对onClick
事件作出反应,根据计划并响应用户交互使用新信息进行更新,并具有自定义预览图像。
这些可能是Android应用程序小部件的基本构建块,但是创建仅能正常工作的东西是远远不够的-您还需要通过遵循最佳实践来确保小部件能够提供良好的用户体验。
在主线程上执行耗时的操作
小部件具有与普通广播接收器相同的运行时限制,因此它们有可能阻止Android的所有重要主UI线程。
Android具有一个默认情况下所有应用程序代码都在其中运行的线程,并且由于Android一次只能处理一个任务,因此很容易阻止此重要线程。 在主线程上执行任何长时间运行或密集的操作,在操作完成之前,您应用的用户界面将无响应。 在最坏的情况下,这可能会导致Android的Application Not Responding ( ANR )错误并导致应用程序崩溃。
如果窗口小部件确实需要执行任何耗时或劳动密集的任务,则应使用服务而不是主线程。 您可以正常创建服务,然后在AppWidgetProvider
启动它。 例如,在这里我们使用服务来更新小部件:
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
context.startService(new Intent(context, AppWidgetService.class));
}
}
创建灵活的布局
创建可适应各种屏幕配置的布局是针对Android开发的最重要规则之一,并且该规则还扩展到小部件。 就像常规活动一样,小部件的布局需要足够灵活以能够正确显示和正常工作,而与屏幕配置无关,但是还有其他一些原因使得小部件布局需要尽可能灵活。
首先,如果您的窗口小部件是可调整大小的,则用户可以手动增大和减小窗口小部件的大小,这是传统活动无需担心的事情。
其次,不能保证小部件的minWidth
和minHeight
测量值将与特定设备的主屏幕网格完美对齐。 当窗口小部件不合适时,Android操作系统将水平和/或垂直拉伸该窗口小部件,以占用满足窗口小部件的minWidth
和minHeight
约束所需的最小单元格数。 尽管这并不是一个明显的增长,但仍然值得一提的是,从一开始,您的小部件可能会占据比预期更多的空间。
创建灵活的小部件布局遵循与设计灵活的Activity布局相同的最佳实践。 关于此主题的信息已经很多,但是在创建窗口小部件布局时,需要牢记一些最重要的要点:
- 避免使用绝对的度量单位,例如以像素为单位定义按钮。 由于屏幕密度不同,因此在每个设备上50像素的像素都不会转换为相同的物理尺寸。 因此,一个
50px
按钮将在低密度屏幕上显示较大,而在高密度屏幕上显示较小。 您应该始终以与密度无关的单位(dpi)指定布局的尺寸,并使用诸如wrap_content
和match_parent
类的灵活元素。 - 提供针对不同屏幕密度优化的备用资源。 您还可以使用配置限定符(例如
smallestWidth
(sw<N>dp
))提供针对不同屏幕尺寸进行了优化的布局。 别忘了提供每种资源的默认版本,因此,如果您的应用遇到遇到没有针对特定资源的特征的屏幕,那么您的应用就会有所依赖。 您应该为普通 , 中等密度的屏幕设计这些默认资源。 - 使用Android模拟器在尽可能多的屏幕上测试您的小部件。 创建AVD时,可以使用“ 配置此硬件配置文件”菜单中显示的“ 屏幕”控件来指定确切的屏幕尺寸和分辨率。 不要忘记测试如何在所有这些不同的屏幕上调整窗口小部件的大小!
不要唤醒设备
更新小部件需要电池电量,并且Android操作系统对于唤醒睡眠设备以执行更新没有任何限制,这会放大小部件对设备电池的影响。
如果用户意识到您的小部件正在消耗电池电量,那么至少他们将要从其主屏幕删除该小部件。 在最坏的情况下,他们甚至可能会完全删除您的应用程序。
您应该始终以尽可能少地更新小部件为目标,同时仍显示及时且相关的信息。 但是,某些窗口小部件可能有需要频繁更新的正当理由-例如,如果窗口小部件包含高度时间敏感的内容。
在这种情况下,您可以根据不会唤醒睡眠设备的警报执行更新,从而减少这些频繁更新对电池寿命的影响。 如果在设备进入睡眠状态时此警报响起,则只有在设备下次唤醒时,更新才会传递。
要基于警报进行更新,您需要使用AlarmManager
来设置AppWidgetProvider
将接收到的Intent
警报,然后将警报类型设置为ELAPSED_REALTIME
或RTC
,因为这些警报类型不会唤醒睡眠设备。 例如:
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
final Intent i = new Intent(context, WidgetService.class);
if (service == null) {
service = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
}
manager.setRepeating(AlarmManager.ELAPSED_REALTIME,
//Set your update interval. 60000 milliseconds (60 seconds) is the minimum interval you can use//
SystemClock.elapsedRealtime(), 60000, service);
}
}
如果确实使用了警报,请确保打开项目的AppWidgetProviderInfo文件( res / xml / new_app_widget_info.xml ),并将updatePeriodMillis
设置为零(“ 0
”)。 如果您忘记了此步骤,则updatePeriodMillis
值将覆盖AlarmManager
并且小部件仍将在每次需要更新时唤醒设备。
结论
在这个由三部分组成的系列文章中,我们创建了一个Android应用程序小部件,该小部件演示了如何实现应用程序小部件中所有最常见的功能。
如果您从一开始就一直遵循,那么到现在为止,您将构建一个可自动更新并响应用户输入的小部件,并且该小部件能够对onClick
事件做出反应。 最后,我们研究了确保您的小部件提供良好的用户体验的一些最佳做法,并讨论了如何通过配置Activity来增强您的小部件。