appWidget

2023-05-16

构建应用微件

应用微件是可以嵌入其他应用(如主屏幕)并接收定期更新的微型应用视图。这些视图称为界面中的微件,您可以使用应用微件提供程序发布微件。能够容纳其他应用微件的应用组件称为应用微件托管应用。下面的屏幕截图显示了音乐应用微件。

本文档介绍如何使用应用微件提供程序来发布应用微件。如需了解如何创建您自己的 AppWidgetHost 来托管应用微件,请参阅应用微件托管应用。

注意:如需了解如何设计应用微件,请阅读应用微件概览。

基础知识

要创建应用微件,您需要:

AppWidgetProviderInfo 对象

描述应用微件的元数据,如应用微件的布局、更新频率和 AppWidgetProvider 类。此对象应在 XML 中定义。

AppWidgetProvider 类实现

定义允许您基于广播事件以编程方式与应用微件连接的基本方法。通过它,您会在更新、启用、停用和删除应用微件时收到广播。

视图布局

定义应用微件的初始布局,在 XML 中定义。

此外,您还可以实现应用微件配置 Activity。这是一个可选的 Activity,在用户添加您的应用微件时启动,并允许用户在应用微件创建时修改其设置。

下面几部分介绍如何设置上述各个组件。

在清单中声明应用微件

首先,在应用的 AndroidManifest.xml 文件中声明 AppWidgetProvider 类。例如:


    <receiver android:name="ExampleAppWidgetProvider" >
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data android:name="android.appwidget.provider"
                   android:resource="@xml/example_appwidget_info" />
    </receiver>
      

<receiver> 元素需要 android:name 属性,该属性指定应用微件使用的 AppWidgetProvider

<intent-filter> 元素必须包含一个具有 android:name 属性的 <action> 元素。此属性指定 AppWidgetProvider 接受 ACTION_APPWIDGET_UPDATE 广播。这是您必须明确声明的唯一一项广播。AppWidgetManager 会根据需要自动将其他所有应用微件广播发送到 AppWidgetProvider。

<meta-data> 元素指定 AppWidgetProviderInfo 资源,并且需要以下属性:

添加 AppWidgetProviderInfo 元数据

AppWidgetProviderInfo 定义应用微件的基本特性,如应用微件的最小布局尺寸、应用微件的初始布局资源、应用微件的更新频率,以及(可选)在应用微件创建时启动的配置 Activity。您可以使用单个 <appwidget-provider> 元素在 XML 资源中定义 AppWidgetProviderInfo 对象,并将其保存在项目的 res/xml/ 文件夹中。

例如:


    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:minWidth="40dp"
        android:minHeight="40dp"
        android:updatePeriodMillis="86400000"
        android:previewImage="@drawable/preview"
        android:initialLayout="@layout/example_appwidget"
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        android:resizeMode="horizontal|vertical"
        android:widgetCategory="home_screen">
    </appwidget-provider>
      

下面简要说明了 <appwidget-provider> 属性:

  • minWidth 和 minHeight 属性的值指定应用微件默认情况下占用的最小空间。默认的主屏幕根据定义了高度和宽度的单元格的网格在其窗口中放置应用微件。如果应用微件的最小宽度或高度的值与单元格的尺寸不匹配,则应用微件的尺寸会向上舍入到最接近的单元格大小。

    如需详细了解如何设置应用微件的大小,请参阅应用微件设计准则。

    注意:为使应用微件能够在设备间移植,应用微件的最小大小不得超过 4 x 4 单元格。

  • minResizeWidth 和 minResizeHeight 属性指定应用微件的绝对最小大小。这些值应指定应用微件的大小低于多大就会难以辨认或无法使用。使用这些属性,用户可以将微件的大小调整为可能小于由 minWidth 和 minHeight 属性定义的默认微件大小。这些属性是在 Android 3.1 中引入的。

    如需详细了解如何设置应用微件的大小,请参阅应用微件设计准则。

  • updatePeriodMillis 属性定义应用微件框架通过调用 onUpdate() 回调方法来从 AppWidgetProvider 请求更新的频率应该是多大。不能保证实际更新按此值正好准时发生,我们建议尽可能降低更新频率 - 或许不超过每小时一次,以节省电池电量。您也可以允许用户在配置中调整频率 - 有些人可能希望股票行情自动收录器每 15 分钟更新一次,另有一些人也可能希望它一天只更新 4 次。

    注意:如果设备在到了该更新的时候(由 updatePeriodMillis 定义)处于休眠状态,则设备会唤醒以执行更新。如果您的更新频率不超过每小时一次,这样或许不会给电池续航时间造成严重问题。不过,如果您需要更频繁地更新和/或不需要在设备处于休眠状态时进行更新,则可以改为基于不会唤醒设备的闹钟来执行更新。为此,请使用 AlarmManager 设置一个具有 AppWidgetProvider 会接收的 Intent 的闹钟。将闹钟类型设为 ELAPSED_REALTIME 或 RTC,这样只有在设备处于唤醒状态时,闹钟才会响起。然后,将 updatePeriodMillis 设为零 ("0")。

  • initialLayout 属性指向用于定义应用微件布局的布局资源。
  • configure 属性定义要在用户添加应用微件时启动以便用户配置应用微件属性的 Activity。这是可选的(请阅读下文的创建应用微件配置 Activity)。
  • previewImage 属性指定预览来描绘应用微件经过配置后是什么样子的,用户在选择应用微件时会看到该预览。如果未提供,则用户会看到应用的启动器图标。此字段对应于 AndroidManifest.xml 文件的 <receiver> 元素中的 android:previewImage 属性。如需详细了解如何使用 previewImage,请参阅设置预览图片。此属性是在 Android 3.0 中引入的。
  • autoAdvanceViewId 属性指定应由应用微件的托管应用自动跳转的应用微件子视图的视图 ID。此属性是在 Android 3.0 中引入的。
  • resizeMode 属性指定可以按什么规则来调整微件的大小。您可以使用此属性来让主屏幕微件在横轴上可调整大小、在纵轴上可调整大小,或者在这两个轴上均可调整大小。用户可轻触并按住微件以显示其大小调整手柄,然后拖动水平和/或垂直手柄以更改布局网格上的大小。resizeMode 属性的值包括“horizontal”、“vertical”和“none”。要将微件声明为在水平和垂直方向上均可调整大小,请提供值“horizontal|vertical”。此属性是在 Android 3.1 中引入的。
  • minResizeHeight 属性指定可将微件大小调整到的最小高度(以 dp 为单位)。如果此字段的值大于 minHeight 或未启用垂直大小调整(请参阅 resizeMode),则此字段不起作用。此属性是在 Android 4.0 中引入的。
  • minResizeWidth 属性指定可将微件大小调整到的最小宽度(以 dp 为单位)。如果此字段的值大于 minWidth 或未启用水平大小调整(请参阅 resizeMode),则此字段不起作用。此属性是在 Android 4.0 中引入的。
  • widgetCategory 属性声明应用微件是否可以显示在主屏幕 (home_screen) 和/或锁定屏幕 (keyguard) 上。只有低于 5.0 的 Android 版本才支持锁定屏幕微件。对于 Android 5.0 及更高版本,只有 home_screen 有效。

如需详细了解 <appwidget-provider> 元素接受的属性,请参阅 AppWidgetProviderInfo 类。

创建应用微件布局

您必须在 XML 中定义应用微件的初始布局,并将其保存在项目的 res/layout/ 目录中。您可以使用下面列出的视图对象来设计应用微件,但在开始设计应用微件之前,请先阅读并了解应用微件设计准则。

如果您熟悉布局,那么创建应用微件布局非常简单。不过,您必须知道,应用微件布局基于 RemoteViews,并不是每种布局或视图微件都受其支持。

RemoteViews 对象(因而应用微件)可以支持以下布局类:

以及以下微件类:

不支持这些类的后代。

RemoteViews 还支持 ViewStub,它是一个大小为零的不可见视图,您可以使用它在运行时以懒散的方式扩充布局资源。

向应用微件添加外边距

微件通常不应扩展到屏幕边缘,也不应在视觉上与其他微件齐平,因此您应在微件框架的四周添加外边距。

从 Android 4.0 开始,系统会自动在微件框架与应用微件的边界框之间为应用微件留出内边距,以使应用微件与用户主屏幕上的其他微件和图标更好地对齐。要利用这种强烈建议的行为,请将应用的 targetSdkVersion 设为 14 或更高版本。

一种简单的做法是编写单个布局,对较低版本的平台上的微件应用自定义外边距,不对 Android 4.0 及更高版本的平台上的微件应用额外的外边距:

  1. 将应用的 targetSdkVersion 设为 14 或更高版本。
  2. 创建如下所示的布局,为其外边距引用尺寸资源:

    
        <FrameLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:padding="@dimen/widget_margin">
    
          <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:background="@drawable/my_widget_background">
            …
          </LinearLayout>
    
        </FrameLayout>
          
  3. 创建两个尺寸资源:一个在 res/values/ 中,该资源为低于 Android 4.0 的平台上的微件提供自定义外边距;一个在 res/values-v14/ 中,该资源不为 Android 4.0 平台上的微件提供额外的内边距:

    res/values/dimens.xml

    
    <dimen name="widget_margin">8dp</dimen>  

    res/values-v14/dimens.xml

    
    <dimen name="widget_margin">0dp</dimen>  

另一种做法是默认情况下直接将额外的外边距内置到九宫格背景资源中,并为 API 级别 14 或更高级别的平台上的微件提供没有外边距的不同九宫格。

使用 AppWidgetProvider 类

AppWidgetProvider 类扩展了 BroadcastReceiver 作为一个辅助类来处理应用微件广播。AppWidgetProvider 仅接收与应用微件有关的事件广播,例如当更新、删除、启用和停用应用微件时发出的广播。当发生这些广播事件时,AppWidgetProvider 会接收以下方法调用:

onUpdate()

调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新应用微件(请参阅上文的添加 AppWidgetProviderInfo 元数据)。当用户添加应用微件时也会调用此方法,所以它应执行基本设置,如定义视图的事件处理脚本以及根据需要启动临时的 Service。不过,如果您已声明配置 Activity,则当用户添加应用微件时不会调用此方法,但会调用它来执行后续更新。由配置 Activity 负责在配置完成后执行首次更新。(请参阅下文的创建应用微件配置 Activity。)

onAppWidgetOptionsChanged()

当首次放置微件时以及每当调整微件的大小时,会调用此方法。您可以使用此回调来根据微件的大小范围显示或隐藏内容。您可以通过调用 getAppWidgetOptions() 来获取大小范围,该方法会返回包含以下各项的 Bundle

此回调是在 API 级别 16 (Android 4.1) 中引入的。如果您实现此回调,请确保您的应用不依赖于它,因为在旧款设备上不会调用它。

onDeleted(Context, int[])

每次从应用微件托管应用中删除应用微件时,都会调用此方法。

onEnabled(Context)

首次创建应用微件的实例时,会调用此方法。例如,如果用户添加应用微件的两个实例,只有首次添加时会调用此方法。如果您需要打开一个新的数据库或执行只需要对所有应用微件实例执行一次的其他设置,则此方法非常合适。

onDisabled(Context)

从应用微件托管应用中删除了应用微件的最后一个实例时,会调用此方法。您应使用此方法来清理在 onEnabled(Context) 中完成的所有工作,如删除临时数据库。

onReceive(Context, Intent)

针对每个广播调用此方法,并且是在上述各个回调方法之前调用。您通常不需要实现此方法,因为默认的 AppWidgetProvider 实现会过滤所有应用微件广播并视情况调用上述方法。

您必须在 AndroidManifest 中使用 <receiver> 元素将 AppWidgetProvider 类实现声明为广播接收器(请参阅上文的在清单中声明应用微件)。

最重要的 AppWidgetProvider 回调是 onUpdate(),因为向托管应用添加每个应用微件时都会调用它(除非您使用配置 Activity)。如果应用微件接受任何用户交互事件,则您需要在此回调中注册事件处理脚本。如果应用微件未创建临时文件或数据库,或者未执行其他需要清理的工作,则 onUpdate() 可能是您需要定义的唯一一个回调方法。例如,如果您希望应用微件具有一个在用户点击时会启动 Activity 的按钮,则可以使用以下 AppWidgetProvider 实现:

KotlinJava


    public class ExampleAppWidgetProvider extends AppWidgetProvider {

        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            final int N = appWidgetIds.length;

            // Perform this loop procedure for each App Widget that belongs to this provider
            for (int i=0; i<N; i++) {
                int appWidgetId = appWidgetIds[i];

                // Create an Intent to launch ExampleActivity
                Intent intent = new Intent(context, ExampleActivity.class);
                PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

                // Get the layout for the App Widget and attach an on-click listener
                // to the button
                RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
                views.setOnClickPendingIntent(R.id.button, pendingIntent);

                // Tell the AppWidgetManager to perform an update on the current app widget
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
        }
    }
      

此 AppWidgetProvider 仅定义了 onUpdate() 方法,目的是为了定义用于启动 Activity 的 PendingIntent,并使用 setOnClickPendingIntent(int, PendingIntent) 将其附加到应用微件的按钮。请注意,它包含一个遍历 appWidgetIds(这是一个 ID 数组,标识由此提供程序创建的每个应用微件)中每个条目的循环。这样一来,如果用户创建了应用微件的多个实例,则它们会全部同时更新。不过,对于应用微件的所有实例,只管理一个 updatePeriodMillis 时间表。例如,如果更新时间表定义为每两小时更新一次,并且在添加应用微件的第一个实例一小时后添加了第二个实例,则这两个实例都会按照第一个实例定义的周期进行更新,而第二个更新周期会被忽略(这两个实例都是每两小时更新一次,而不是每小时更新一次)。

注意:由于 AppWidgetProvider 是 BroadcastReceiver 的扩展,因此不能保证您的操作过程在回调方法返回结果后继续进行(如需了解广播生命周期,请参阅 BroadcastReceiver)。如果您的应用微件设置过程可能需要几秒钟(或许是在执行网络请求时),并且您要求您的操作过程继续进行,不妨考虑在 onUpdate() 方法中启动一个 Service。在该 Service 中,您可以按照自己的时间表对应用微件执行更新,而不必担心由于应用无响应 (ANR) 错误而导致 AppWidgetProvider 关闭。如需查看运行 Service 的应用微件的示例,请参阅维基词典示例的 AppWidgetProvider。

另请参阅 ExampleAppWidgetProvider.java 示例类。

接收应用微件广播 Intent

AppWidgetProvider 只是一个辅助类。如果您希望直接接收应用微件广播,您可以实现自己的 BroadcastReceiver 或替换 onReceive(Context, Intent) 回调。您需要关注的 Intent 如下所示:

固定应用微件

在搭载 Android 8.0(API 级别 26)及更高版本的设备上,允许您创建固定的快捷方式的启动器也允许您将应用微件固定到启动器上。与固定的快捷方式类似,这些固定的微件也能让用户访问应用中的特定任务。

在您的应用中,您可以创建一个请求,让系统将微件固定到支持的启动器上,只需完成下面一系列步骤即可:

  1. 在应用的清单文件中创建微件,如以下代码段所示:

    
        <manifest>
        ...
          <application>
            ...
            <receiver android:name="MyAppWidgetProvider">
                <intent-filter>
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                </intent-filter>
                <meta-data android:name="android.appwidget.provider"
                           android:resource="@xml/my_appwidget_info" />
            </receiver>
          </application>
        </manifest>
          
  2. 调用 requestPinAppWidget() 方法,如以下代码段所示:

    KotlinJava

    
        AppWidgetManager appWidgetManager =
                context.getSystemService(AppWidgetManager.class);
        ComponentName myProvider =
                new ComponentName(context, MyAppWidgetProvider.class);
    
        if (appWidgetManager.isRequestPinAppWidgetSupported()) {
            // Create the PendingIntent object only if your app needs to be notified
            // that the user allowed the widget to be pinned. Note that, if the pinning
            // operation fails, your app isn't notified.
            Intent pinnedWidgetCallbackIntent = new Intent( ... );
    
            // Configure the intent so that your app's broadcast receiver gets
            // the callback successfully. This callback receives the ID of the
            // newly-pinned widget (EXTRA_APPWIDGET_ID).
            PendingIntent successCallback = PendingIntent.getBroadcast(context, 0,
                    pinnedWidgetCallbackIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
            appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
        }
          

注意:如果无需通知您的应用系统是否已成功地将微件固定到受支持的启动器上,则您可以将 null 作为 requestPinAppWidget() 的第三个参数传入。

创建应用微件配置 Activity

如果您希望用户在添加新的应用微件时配置设置,您可以创建应用微件配置 Activity。此 Activity 将由应用微件托管应用自动启动,并允许用户在应用微件创建时为其配置可用设置,如应用微件的颜色、大小、更新周期或其他功能设置。

应在 Android 清单文件中将配置 Activity 声明为正常 Activity。不过,该 Activity 将由应用微件托管应用通过 ACTION_APPWIDGET_CONFIGURE 操作来启动,因此它需要接受此 Intent。例如:


    <activity android:name=".ExampleAppWidgetConfigure">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
        </intent-filter>
    </activity>
      

此外,还必须在 AppWidgetProviderInfo XML 文件中使用 android:configure 属性声明该 Activity(请参阅上文的添加 AppWidgetProviderInfo 元数据)。例如,可按如下方式声明配置 Activity:


    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        ...
        android:configure="com.example.android.ExampleAppWidgetConfigure"
        ... >
    </appwidget-provider>
      

请注意,该 Activity 是使用完全限定的命名空间声明的,因为将从您的软件包范围之外对其进行引用。

这就是您开始使用配置 Activity 所需的全部内容。现在您只需要实际 Activity 了。不过,当您实现该 Activity 时,需要记住下面两个要点:

  • 应用微件托管应用调用配置 Activity,并且配置 Activity 应始终返回结果。结果应包含由启动该 Activity 的 Intent 传递的应用微件 ID(在 Intent extra 中保存为 EXTRA_APPWIDGET_ID)。
  • 创建应用微件时,系统不会调用 onUpdate() 方法(启动配置 Activity 时,系统不会发送 ACTION_APPWIDGET_UPDATE 广播)。首次创建应用微件时,由配置 Activity 负责从 AppWidgetManager 请求更新。不过,系统会调用 onUpdate() 来执行后续更新,只在首次更新时不会调用它。

有关如何从配置返回结果并更新应用微件的示例,请参阅下一部分中的代码段。

通过配置 Activity 更新应用微件

当应用微件使用配置 Activity 时,由该 Activity 负责在配置完成后更新应用微件。为此,您可以直接从 AppWidgetManager 请求更新。

下面简要说明了正确更新应用微件并关闭配置 Activity 的过程:

  1. 首先,从启动该 Activity 的 Intent 获取应用微件 ID:

    KotlinJava

    
        Intent intent = getIntent();
        Bundle extras = intent.getExtras();
        if (extras != null) {
            appWidgetId = extras.getInt(
                    AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }
          
  2. 执行应用微件配置。
  3. 配置完成后,通过调用 getInstance(Context) 来获取 AppWidgetManager 的实例:

    KotlinJava

    
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
          
  4. 通过调用 updateAppWidget(int, RemoteViews) 来使用 RemoteViews 布局更新应用微件:

    KotlinJava

    
        RemoteViews views = new RemoteViews(context.getPackageName(),
        R.layout.example_appwidget);
        appWidgetManager.updateAppWidget(appWidgetId, views);
          
  5. 最后,创建返回 Intent,为其设置 Activity 结果,然后结束该 Activity:

    KotlinJava

    
        Intent resultValue = new Intent();
        resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        setResult(RESULT_OK, resultValue);
        finish();
          

提示:当配置 Activity 首次打开时,请将 Activity 结果设为 RESULT_CANCELED 并注明 EXTRA_APPWIDGET_ID,如上面的第 5 步所示。这样,如果用户在到达末尾之前退出该 Activity,应用微件托管应用就会收到配置已取消的通知,因此不会添加应用微件。

有关示例,请参阅 ApiDemos 中的 ExampleAppWidgetConfigure.java 示例类。

设置预览图片

Android 3.0 引入了 previewImage 字段,用于指定预览来描绘应用微件是什么样子的。此预览通过微件选择器显示给用户。如果未提供此字段,则应用微件的图标将用于预览。

在 XML 中指定此设置的方式如下:


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
      ...
      android:previewImage="@drawable/preview">
    </appwidget-provider>  

为了帮助创建应用微件的预览图片(在 previewImage 字段中指定),Android 模拟器包含一个名为“微件预览”的应用。要创建预览图片,请启动此应用,为您的应用选择应用微件并设置您希望如何显示预览图片,然后将其保存并放在您的应用的可绘制资源中。

使用包含集合的应用微件

Android 3.0 引入了包含集合的应用微件。这些类型的应用微件使用 RemoteViewsService 来显示由远程数据(如来自内容提供程序的数据)支持的集合。由 RemoteViewsService 提供的数据将使用以下某种视图类型(我们称之为“集合视图”)呈现在应用微件中。

ListView

一种在垂直滚动列表中显示项目的视图。有关示例,请查看 Gmail 应用微件。

GridView

一种在二维滚动网格中显示项目的视图。有关示例,请查看“书签”应用微件。

StackView

一种堆叠式卡片视图(有点像名片盒),用户可以分别向上/向下翻动前面的卡片来查看上一张/下一张卡片。示例包括 YouTube 和“图书”应用微件。

AdapterViewFlipper

一种由适配器支持的简单 ViewAnimator,可以在两个或更多视图之间呈现动画效果。一次只显示一个子级。

如上所述,这些集合视图显示由远程数据支持的集合。这意味着,它们使用 Adapter 将其界面绑定到其数据。Adapter 将一组数据中的各个项目绑定到各个 View 对象。由于这些集合视图由适配器支持,因此 Android 框架必须包含额外的架构来支持它们在应用微件中的使用。在应用微件的上下文中,Adapter 被 RemoteViewsFactory 取代,后者只是 Adapter 接口的瘦封装容器。请求集合中的特定项目时,RemoteViewsFactory 会为集合创建相应项目并将其作为 RemoteViews 对象返回。要在应用微件中添加集合视图,您必须实现 RemoteViewsService 和 RemoteViewsFactory

RemoteViewsService 是允许远程适配器请求 RemoteViews 对象的服务。RemoteViewsFactory 是集合视图(ListViewGridView 等等)与该视图的底层数据之间的适配器的接口。下面是您用来实现此服务和接口的样板代码的示例(来自 StackWidget 示例):

KotlinJava


    public class StackWidgetService extends RemoteViewsService {
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
        }
    }

    class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

    //... include adapter-like methods here. See the StackView Widget sample.

    }
      

示例应用

本部分中的代码段摘录自 StackWidget 示例:

此示例由一个包含 10 个视图的堆栈组成,这些视图显示值 "0!" 到 "9!"。该示例应用微件具有以下主要行为:

  • 用户可以垂直滑动该应用微件中的顶部视图以显示下一个或上一个视图。这是一种内置的 StackView 行为。
  • 如果没有任何用户交互,则该应用微件会自动按顺序显示其视图,就像播放幻灯片一样。这是因为在 res/xml/stackwidgetinfo.xml 文件中设置了 android:autoAdvanceViewId="@id/stack_view"。此设置适用于视图 ID,在本例中为堆栈视图的视图 ID。
  • 如果用户触摸顶部视图,则该应用微件会显示 Toast 消息“Touched view n”,其中“n”是触摸的视图的索引(位置)。如需详细了解如何实现此行为,请参阅向各个项目添加行为。

实现包含集合的应用微件

要实现包含集合的应用微件,您应执行的基本步骤与用来实现任何应用微件的步骤都一样。下面几部分介绍了实现包含集合的应用微件时需要执行的额外步骤。

包含集合的应用微件的清单

除了在清单中声明应用微件中列出的要求之外,要使包含集合的应用微件能够绑定到 RemoteViewsService,您还必须在清单文件中使用 BIND_REMOTEVIEWS 权限来声明该服务。这样可防止其他应用自由访问您的应用微件的数据。例如,在创建使用 RemoteViewsService 填充集合视图的应用微件时,清单条目可能如下所示:


<service android:name="MyWidgetService"
    ...
    android:permission="android.permission.BIND_REMOTEVIEWS" />  

代码行 android:name="MyWidgetService" 引用您的 RemoteViewsService 子类。

包含集合的应用微件的布局

对应用微件布局 XML 文件的主要要求是它必须包含某个集合视图:ListViewGridViewStackView 或 AdapterViewFlipper。下面是 StackWidget 示例的 widget_layout.xml


<?xml version="1.0" encoding="utf-8"?>

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <StackView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/stack_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:loopViews="true" />
        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/empty_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:background="@drawable/widget_item_background"
            android:textColor="#ffffff"
            android:textStyle="bold"
            android:text="@string/empty_view_text"
            android:textSize="20sp" />
    </FrameLayout>  

请注意,空视图必须是集合视图的同级,其中空视图表示空状态。

除了整个应用微件的布局文件之外,您必须再创建一个布局文件,用来定义集合中每个项目的布局(例如,一套图书中每本图书的布局)。StackWidget 示例只有一个布局文件 widget_item.xml,因为所有项目使用同一布局。

包含集合的应用微件的 AppWidgetProvider 类

与常规应用微件一样,AppWidgetProvider 子类中的大部分代码通常都在 onUpdate() 中。在创建包含集合的应用微件时,您的 onUpdate() 实现的主要区别在于,您必须调用 setRemoteAdapter()。这样将告知集合视图要从何处获取其数据。然后,RemoteViewsService 可以返回您的 RemoteViewsFactory 实现,并且微件可以提供适当的数据。当您调用此方法时,必须传递指向您的 RemoteViewsService 实现的 Intent,以及指定要更新的应用微件的应用微件 ID。

例如,以下代码段说明了 StackWidget 示例如何实现 onUpdate() 回调方法以将 RemoteViewsService 设为应用微件集合的远程适配器:

KotlinJava


    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
    int[] appWidgetIds) {
        // update each of the app widgets with the remote adapter
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Set up the intent that starts the StackViewService, which will
            // provide the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            // Add the app widget ID to the intent extras.
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            // Instantiate the RemoteViews object for the app widget layout.
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            // Set up the RemoteViews object to use a RemoteViews adapter.
            // This adapter connects
            // to a RemoteViewsService  through the specified intent.
            // This is how you populate the data.
            rv.setRemoteAdapter(R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items.
            // It should be in the same layout used to instantiate the RemoteViews
            // object above.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            //
            // Do additional processing specific to this app widget...
            //

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
      

RemoteViewsService 类

保留数据

如上所述,您的 RemoteViewsService 子类提供用于填充远程集合视图的 RemoteViewsFactory

具体而言,您需要执行以下步骤:

  1. RemoteViewsService 子类。RemoteViewsService 是一项服务,远程适配器可以通过它来请求 RemoteViews
  2. 在您的 RemoteViewsService 子类中,添加一个实现 RemoteViewsFactory 接口的类。RemoteViewsFactory 是远程集合视图(如 ListViewGridView 等等)与该视图的底层数据之间的适配器的接口。您的实现负责为数据集中的每个项目创建一个 RemoteViews 对象。此接口是 Adapter 的瘦封装容器。

您不能依赖于服务的单个实例,也不能保留它包含的任何数据。因此,您不应将任何数据存储在 RemoteViewsService 中(除非它是静态的)。如果您要保留应用微件的数据,最好的方法是使用 ContentProvider,它的数据在进程生命周期过后持续存在。

RemoteViewsService 实现的主要内容是它的 RemoteViewsFactory,如下所述。

RemoteViewsFactory 接口

实现 RemoteViewsFactory 接口的自定义类可以为应用微件包含的集合中的项目提供数据。为此,它会将应用微件项目 XML 布局文件与数据源相结合。此数据源可以是任何来源,从数据库到简单的数组均可。在 StackWidget 示例中,数据源是 WidgetItems 的数组。RemoteViewsFactory 充当将数据粘附到远程集合视图的适配器。

您需要为 RemoteViewsFactory 子类实现的两个最重要的方法是 onCreate() 和 getViewAt()

首次创建 RemoteViewsFactory 接口时,系统会调用 onCreate()。您可以在此方法中设置指向数据源的任何连接和/或游标。例如,StackWidget 示例使用 onCreate() 来初始化 WidgetItem 对象的数组。当应用微件处于活动状态时,系统会使用这些对象在数组中的索引位置来对其进行访问,并且会显示它们包含的文本。

以下代码段摘录自 StackWidget 示例的 RemoteViewsFactory 实现,显示了 onCreate() 方法的某些部分:

KotlinJava


    class StackRemoteViewsFactory implements
    RemoteViewsService.RemoteViewsFactory {
        private static final int count = 10;
        private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();
        private Context context;
        private int appWidgetId;

        public StackRemoteViewsFactory(Context context, Intent intent) {
            this.context = context;
            appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }

        public void onCreate() {
            // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            for (int i = 0; i < count; i++) {
                widgetItems.add(new WidgetItem(i + "!"));
            }
            ...
        }
    ...
      

RemoteViewsFactory 方法 getViewAt() 将返回与位于数据集中指定 position 的数据对应的 RemoteViews 对象。以下代码段摘录自 StackWidget 示例的 RemoteViewsFactory 实现:

KotlinJava


    public RemoteViews getViewAt(int position) {

        // Construct a remote views item based on the app widget item XML file,
        // and set the text based on the position.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
        rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

        ...
        // Return the remote views object.
        return rv;
    }
      

向各个项目添加行为

上面几部分介绍了如何将数据绑定到应用微件集合。但是,如果您要向集合视图中的各个项目添加动态行为,该怎么办呢?

如使用 AppWidgetProvider 类中所述,您通常使用 setOnClickPendingIntent() 来设置对象的点击行为 - 例如,让按钮启动 Activity。但是,不允许对各个集合项目中的子视图使用此方法(为了阐明这一点,我们举个例子,您可以使用 setOnClickPendingIntent() 在 Gmail 应用微件中设置一个用来启动应用的全局按钮,但不能在各个列表项上进行设置)。要向集合中的各个项目添加点击行为,应改用 setOnClickFillInIntent()。这需要为集合视图设置待定 Intent 模板,然后通过 RemoteViewsFactory 在集合中的每个项目上设置填充 Intent。

本部分通过 StackWidget 示例来说明如何向各个项目添加行为。在 StackWidget 示例中,如果用户触摸顶部视图,该应用微件会显示 Toast 消息“Touched view n”,其中“n”是触摸的视图的索引(位置)。其工作原理如下:

  • StackWidgetProviderAppWidgetProvider 子类)会创建一个待定 Intent,该 Intent 具有一项名为 TOAST_ACTION 的自定义操作。
  • 当用户触摸视图时,会触发该 Intent,并且它会广播 TOAST_ACTION
  • 此广播会被 StackWidgetProvider 的 onReceive() 方法拦截,并且应用微件会针对触摸的视图显示 Toast 消息。集合项目的数据由 RemoteViewsFactory 通过 RemoteViewsService 提供。

注意:StackWidget 示例使用了广播,但应用微件在这样的情况下通常会直接启动 Activity。

设置待定 Intent 模板

StackWidgetProviderAppWidgetProvider 子类)会设置一个待定 Intent。集合中的各个项目无法设置它们自己的待定 Intent,而是整个集合设置一个待定 Intent 模板,并且各个项目设置填充 Intent 来逐项创建唯一的行为。

此类还会接收用户触摸视图时发送的广播。它在自己的 onReceive() 方法中处理此事件。如果 Intent 的操作为 TOAST_ACTION,则应用微件会针对当前视图显示 Toast 消息。

KotlinJava


    public class StackWidgetProvider extends AppWidgetProvider {
        public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
        public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

        ...

        // Called when the BroadcastReceiver receives an Intent broadcast.
        // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget
        // displays a Toast message for the current item.
        @Override
        public void onReceive(Context context, Intent intent) {
            AppWidgetManager mgr = AppWidgetManager.getInstance(context);
            if (intent.getAction().equals(TOAST_ACTION)) {
                int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
                int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
                Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
            }
            super.onReceive(context, intent);
        }

        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            // update each of the app widgets with the remote adapter
            for (int i = 0; i < appWidgetIds.length; ++i) {

                // Sets up the intent that points to the StackViewService that will
                // provide the views for this collection.
                Intent intent = new Intent(context, StackWidgetService.class);
                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
                // When intents are compared, the extras are ignored, so we need to embed the extras
                // into the data so that the extras will not be ignored.
                intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
                RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
                rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

                // The empty view is displayed when the collection has no items. It should be a sibling
                // of the collection view.
                rv.setEmptyView(R.id.stack_view, R.id.empty_view);

                // This section makes it possible for items to have individualized behavior.
                // It does this by setting up a pending intent template. Individuals items of a collection
                // cannot set up their own pending intents. Instead, the collection as a whole sets
                // up a pending intent template, and the individual items set a fillInIntent
                // to create unique behavior on an item-by-item basis.
                Intent toastIntent = new Intent(context, StackWidgetProvider.class);
                // Set the action for the intent.
                // When the user touches a particular view, it will have the effect of
                // broadcasting TOAST_ACTION.
                toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
                toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
                intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
                PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
                rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

                appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
            }
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        }
    }
      

设置填充 Intent

您的 RemoteViewsFactory 必须在集合中的每个项目上设置一个填充 Intent。这样就可以区分给定项目的点击时的各项操作。填充 Intent 随后与 PendingIntent 模板相结合,以确定在点击相应项目时要执行的最终 Intent。

KotlinJava


    public class StackWidgetService extends RemoteViewsService {
        @Override
        public RemoteViewsFactory onGetViewFactory(Intent intent) {
            return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
        }
    }

    class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
        private static final int count = 10;
        private List<WidgetItem> widgetItems = new ArrayList<WidgetItem>();
        private Context context;
        private int appWidgetId;

        public StackRemoteViewsFactory(Context context, Intent intent) {
            this.context = context;
            appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                    AppWidgetManager.INVALID_APPWIDGET_ID);
        }

        // Initialize the data set.
            public void onCreate() {
                // In onCreate() you set up any connections / cursors to your data source. Heavy lifting,
                // for example downloading or creating content etc, should be deferred to onDataSetChanged()
                // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
                for (int i = 0; i < count; i++) {
                    widgetItems.add(new WidgetItem(i + "!"));
                }
               ...
            }
            ...

            // Given the position (index) of a WidgetItem in the array, use the item's text value in
            // combination with the app widget item XML file to construct a RemoteViews object.
            public RemoteViews getViewAt(int position) {
                // position will always range from 0 to getCount() - 1.

                // Construct a RemoteViews item based on the app widget item XML file, and set the
                // text based on the position.
                RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
                rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

                // Next, set a fill-intent, which will be used to fill in the pending intent template
                // that is set on the collection view in StackWidgetProvider.
                Bundle extras = new Bundle();
                extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
                Intent fillInIntent = new Intent();
                fillInIntent.putExtras(extras);
                // Make it possible to distinguish the individual on-click
                // action of a given item
                rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

                ...

                // Return the RemoteViews object.
                return rv;
            }
        ...
        }
      

使集合数据保持最新

下图说明了在发生更新时使用集合的应用微件中发生的流程。图中显示了应用微件代码如何与 RemoteViewsFactory 交互,以及您如何触发更新:

使用集合的应用微件的一项功能是能够为用户提供最新内容。以 Android 3.0 Gmail 应用微件为例,它可以为用户提供收件箱的快照。要做到这一点,您需要能够触发 RemoteViewsFactory 和集合视图以获取和显示新数据。您可以通过 AppWidgetManager 调用 notifyAppWidgetViewDataChanged() 来实现这一目标。此调用会导致对 RemoteViewsFactory 的 onDataSetChanged() 方法进行回调,从而让您有机会获取任何新数据。请注意,您可以在 onDataSetChanged() 回调中同步执行处理密集型操作。可以保证您会在从 RemoteViewsFactory 获取元数据或视图数据之前完成此调用。此外,您还可以在 getViewAt() 方法中执行处理密集型操作。如果此调用需要很长时间,则正在加载的视图(由 RemoteViewsFactory 的 getLoadingView() 方法指定)会显示在集合视图的相应位置,直到它返回结果。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

appWidget 的相关文章

  • linux C++ hello world

    我是Java程序员 xff0c 没怎么写过C 43 43 以前在windows下使用vs写过hello world 最近有个程序需要使用C 43 43 实现 我想在linux下写 xff0c 记录一下过程 安装gcc 查看gcc版本 gcc
  • mybatis foreach 批量删除 传两个参数

    需求 foreach中要传两个参数 xff0c 一个是id xff0c 一个是list 怎么传呢 xff1f 单list的情况 Mapper java span class token comment 批量删除 64 param teamL
  • ubuntu22.04 dlopen(): error loading libfuse.so.2

    报错如下 navicat16 premium cs AppImage dlopen error loading libfuse so 2 AppImages require FUSE to run You might still be ab
  • Android DataBinding在Activity、Fragment中的使用及数据共享

    本篇记录Activity Fragment使用DataBinding的不同方式 xff0c 以及Activity下的不同Fragment间的数据共享 开启DataBinding 首先我们要在app gradle中开启DataBinding
  • IDEA mybatis Mapper.xml报红的解决办法

    现象 在IDEA中已经配置好Database了 xff0c 但是打开mybatis的Mapper xml中的字段还是报红 如下 xff1a 随便不影响程序运行 xff0c 但是非常的不舒服 智能提示也不好用 解决办法 File gt Set
  • Windows 2008 R2 Server远程无法复制的解决办法

    结束进程rdpclip exe运行中重新运行rdpclip exe 两步完美解决 参考 https bbs huaweicloud com blogs 307039
  • WinScp密钥登录

    使用密码登录非常的方便 xff0c 但是有的客户的云服务器上是限定只能通过密钥登录 我一般使用命令行的scp命令就可以正常上传 xff0c 但是对于我一些同事来说 xff0c 就很不方便 生成密钥 这个不难 xff0c 可以参考我之前的文章
  • FileZilla密钥登录

    使用密码登录非常的方便 xff0c 但是有的客户的云服务器上是限定只能通过密钥登录 我一般使用命令行的scp命令就可以正常上传 xff0c 但是对于我一些同事来说 xff0c 就很不方便 生成密钥 这个不难 xff0c 可以参考我之前的文章
  • node js 设置淘宝源

    淘宝镜像源最新地址 span class token function npm span config span class token builtin class name set span registry https registry
  • 手推DNN,CNN池化层,卷积层反向传播

    反向传播算法是神经网络中用来学习的算法 xff0c 从网络的输出一直往输出方向计算梯度来更新网络参数 xff0c 达到学习的目的 xff0c 而因为其传播方向与网络的推理方向相反 xff0c 因此成为反向传播 神经网络有很多种 xff0c
  • 软件架构概念和面向服务的架构

    摘要 软件架构作为软件开发过程的一个重要组成部分 xff0c 有着各种各样的方法和路线图 xff0c 它们都有一些共同的原则 基于架构的方法作为控制系统构建和演化复杂性的一种手段得到了推广 引言 在计算机历史中 xff0c 软件变得越来越复
  • 初识强化学习,什么是强化学习?

    相信很多人都听过 机器学习 和 深度学习 但是听过 强化学习 的人可能没有那么多 那么 什么是强化学习呢 强化学习是机器学习的一个子领域 它可以随着时间的推移自动学习到最优的策略 在我们不断变化的纷繁复杂的世界里 从更广的角度来看 即使是单
  • 强化学习形式与关系

    在强化学习中有这么几个术语 智能体 Agent 环境 Environment 动作 Action 奖励 Reward 状态 State 有些地方称作观察 Observation 奖励 Reward 在强化学习中 奖励是一个标量 它是从环境中
  • 多层网络和反向传播笔记

    在我之前的博客中讲到了感知器 xff08 感知器 xff09 xff0c 它是用于线性可分模式分类的最简单的神经网络模型 xff0c 单个感知器只能表示线性的决策面 xff0c 而反向传播算法所学习的多层网络能够表示种类繁多的非线性曲面 对
  • 如何准备校招?

    秋招已经落尽尾声 xff0c 今天小牛想把自己的学习经验分享给大家 xff0c 避免大家多走弯路 1 首先需要确定自己想从事哪方面的工作 比如服务端开发 xff08 Java开发工程师 xff0c C 43 43 开发工程师 xff09 x
  • 在Kaggle手写数字数据集上使用Spark MLlib的朴素贝叶斯模型进行手写数字识别

    昨天我在Kaggle上下载了一份用于手写数字识别的数据集 xff0c 想通过最近学习到的一些方法来训练一个模型进行手写数字识别 这些数据集是从28 28像素大小的手写数字灰度图像中得来 xff0c 其中训练数据第一个元素是具体的手写数字 x
  • 在Kaggle手写数字数据集上使用Spark MLlib的RandomForest进行手写数字识别

    昨天我使用Spark MLlib的朴素贝叶斯进行手写数字识别 xff0c 准确率在0 83左右 xff0c 今天使用了RandomForest来训练模型 xff0c 并进行了参数调优 首先来说说RandomForest 训练分类器时使用到的
  • PyTorch模型保存与加载

    torch save xff1a 保存序列化的对象到磁盘 xff0c 使用了Python的pickle进行序列化 xff0c 模型 张量 所有对象的字典 torch load xff1a 使用了pickle的unpacking将pickle
  • Ubuntu18.04 上 安装微信(Deepin-Wechat)

    文章目录 一 安装Deepin Wine环境二 安装Deepin 版微信 微信什么时候支持在linux下的安装包啊 xff0c 我的天哪 xff0c 感觉受到了针对 xff0c 各位看官且看下图 xff1a 这里先作声明 xff1a 本文的

随机推荐

  • ROS机器人操作系统——ROS介绍

    AI is the new electricity 1 ROS发展史 本世纪开始 关于人工智能的研究进入了大发展阶段 包括全方位的具体的AI 例如斯坦福大学人工智能实验室STAIR Stanford Artificial Intellige
  • 如何快速学习一门计算机语言

    一 4步掌握一门计算机语言 1 学习语言的语法 xff0c 关键字 xff0c 以及基本的库 xff08 基础阶段 xff09 2 学习语言的第三方库和各个组件 xff08 OS xff0c 数据库 xff0c 网络 xff09 之间的连用
  • CentOs6.8离线安装svn,并设置自动更新

    CentOs6 8离线安装svn xff0c 并设置自动更新 离线安装所需依赖离线安装 GCC xff0c 如果系统已经有 GCC 了 xff0c 跳过这一步需要的 rpm 包安装顺序 离线安装 openssl需要的代码编译设置环境变量 离
  • Linux ping不通,连不上网的解决办法

    Linux ping不通 xff0c 连不上网的解决办法 可能原因是DNS没有配置好 方法一 xff1a 修改vi etc resolv conf 增加如下内容 xff1a nameserver 114 114 114 114 电信的DNS
  • Android——多进程

    之前我们了解了 Java 多线程浅析 Android Handler详解 Android HandlerThread浅析 Java ThreadPool线程池 让我们继续看看Android多进程 xff1a 1 概述 默认情况下 xff0c
  • React、Ant Desgin自定义加载动画,lottie-web 将json解析成动画

    在项目中 xff0c 遇到需要在网页首屏 xff0c 展示动画的需求 xff0c 你会想到怎么做 xff1f 思路一 xff1a 设计师导出gif图片 xff0c 用img进行展示 缺点 xff1a 图片失真 xff0c 影响效果 思路二
  • mac下安装多版本PHP及切换

    mac下安装多版本PHP及切换 工作环境一直是PHP5 6 xff0c 后来发布了PHP7 xff0c 性能提升不少 xff0c 如今打算试试PHP7 xff0c 所以就有了两个php版本的需求 本文的原理就是用一个php管理工具 xff0
  • 短视频拍摄脚本怎么写

    优质的短视频每一个镜头都经过精心设计 xff0c 镜头的设计就是利用镜头脚本 xff0c 提前设想好一切想要的镜头效果和画面 xff0c 最终作品才能一气呵成的呈现出来 xff0c 接下来就来分析一下短视频拍摄脚本怎么写 xff0c 短视频
  • 串口开发之环形缓冲区

    01 简介 串口的基本应用 xff0c 使用串口中断接收数据 xff0c 串口中断发送回包 xff08 一般可以使用非中断形式发送回包 xff0c 在数据接收不频繁的应用中 串口接收中断保证串口数据及时响应 xff0c 使用非中断方式发送回
  • fastboot 命令

    1 fastboot概念 fastboot fastboot是PC与bootloader的USB通信的命令行工具 xff0c 通过向bootloader传送刷机文件 xff08 img xff09 实现Android系统分区重烧 fastb
  • Android Studio 开启视图绑定 viewBinding

    Google 在 Android Studio 3 6 Canary 11 及更高版本中提供了一个 viewBinding 的开关 xff0c 可以开启视图绑定功能 xff0c 以此来替代 findViewById viewBinding功
  • ViewPager 装载fragment 页面显示空白

    ViewPager 装载fragment 页面显示空白 xff0c 这个时候有两种情况 xff1a 在分页面较多的情况下 使用了 FragmentPagerAdapter xff0c 可能会导致第二次加载页面显示空白或是多次滑动页面后页面空
  • The following packages have unmet dependencies: openssh-server : Depends: openssh-client (= 1:6.6p1

    在虚拟机中安装openssh server的时候报了这个错误 xff0c 不知道这台虚拟机抽了什么风 xff0c 别的虚拟机都能顺利安装 xff0c xff0c xff0c 提示说是openssh server 依赖于 openssh cl
  • Docker Desktop stopped 问题解决

    推广博客 xff1a Docker Desktop stopped 问题解决
  • windows连接远程桌面必须要有用户名和密码

    被远程连接的电脑如果有用户名但没有密码 xff0c 连接时需要输入密码时空着会导致无法连接 想想也是 xff0c 如果没有密码 xff0c 只要有人连入电脑所在局域网 xff0c 就可以通过ip地址和用户名连入电脑 xff0c 非常不安全
  • Android中APK签名工具之jarsigner和apksigner详解

    一 工具介绍 jarsigner是JDK提供的针对jar包签名的通用工具 位于JDK bin jarsigner exe apksigner是Google官方提供的针对Android apk签名及验证的专用工具 位于Android SDK
  • Android NumberPicker的基本用法及常见问题汇总

    前言 在项目中需要一个选择人数的控件 xff0c 于是想到了NumberPicker xff0c 这个控件相对不是那么热门 xff0c 我也是第一次用 xff0c 所以遇到了一些问题 xff0c 这里做个小结 正文 首先来看一下最终的效果
  • angular将html代码输出为内容

    在前端与后台的撕逼中 xff0c 很大一部分是因为数据的问题 使用angular会遇到这样的问题 xff0c 后台返回的数据不是自己想要的纯字符串 xff0c 而是带有html标签及属性的 xff0c 那么我们将它输出来后 xff0c 在页
  • Jetpack新成员,App Startup一篇就懂

    Android 11系统已经来了 xff0c 随之而来的是 xff0c Jetpack家族也引入了许多新的成员 其实以后Android的更新都会逐渐采用这种模式 xff0c 即特定系统相关的API会越来越少 xff0c 更多的编程API是以
  • appWidget

    构建应用微件 应用微件是可以嵌入其他应用 xff08 如主屏幕 xff09 并接收定期更新的微型应用视图 这些视图称为界面中的微件 xff0c 您可以使用应用微件提供程序发布微件 能够容纳其他应用微件的应用组件称为应用微件托管应用 下面的屏