You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Ionic Angular计数器组件创建支持双向绑定的Android Widget?

实现Ionic Angular应用与Android Widget的双向数据同步

核心思路

Ionic是跨平台框架,Android Widget属于原生Android组件,需通过Capacitor自定义插件实现Web端与原生Android的通信,同时借助SharedPreferences做数据共享,配合广播机制完成双向同步:

  • Widget操作通过广播通知Ionic应用更新数据
  • Ionic应用的数值变更通过插件调用原生接口同步到SharedPreferences,再触发Widget更新

具体步骤与实现

1. 准备工作:集成Capacitor

确保项目已接入Capacitor,未接入则执行:

npm install @capacitor/core @capacitor/android
npx cap add android

2. 开发Android原生Widget组件

2.1 创建Widget布局

在android/app/src/main/res/layout下新建widget_counter.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="16dp">

    <Button
        android:id="@+id/btn_decrement"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-"/>

    <TextView
        android:id="@+id/tv_counter"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:gravity="center"
        android:text="0"
        android:textSize="24sp"/>

    <Button
        android:id="@+id/btn_increment"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="+"/>
</LinearLayout>

2.2 创建Widget配置文件

在android/app/src/main/res/xml下新建widget_info.xml:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="180dp"
    android:minHeight="40dp"
    android:updatePeriodMillis="0" <!-- 禁用自动更新,手动触发 -->
    android:initialLayout="@layout/widget_counter"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"/>

2.3 实现AppWidgetProvider类

在android/app/src/main/java/com/yourpackage/下新建CounterWidget.java(替换com/yourpackage为你的项目包名):

package com.yourpackage;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.widget.RemoteViews;

public class CounterWidget extends AppWidgetProvider {
    public static final String ACTION_INCREMENT = "com.yourpackage.INCREMENT";
    public static final String ACTION_DECREMENT = "com.yourpackage.DECREMENT";
    public static final String PREFS_NAME = "CounterPrefs";
    public static final String COUNTER_KEY = "counter_value";

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {
            updateWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (ACTION_INCREMENT.equals(intent.getAction()) || ACTION_DECREMENT.equals(intent.getAction())) {
            SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
            int current = prefs.getInt(COUNTER_KEY, 0);
            int newValue = ACTION_INCREMENT.equals(intent.getAction()) ? current + 1 : current - 1;

            // 更新SharedPreferences
            prefs.edit().putInt(COUNTER_KEY, newValue).apply();

            // 发送广播通知Ionic应用更新
            Intent updateIntent = new Intent("com.yourpackage.COUNTER_UPDATED");
            updateIntent.putExtra("value", newValue);
            context.sendBroadcast(updateIntent);

            // 更新Widget视图
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
            int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
            if (appWidgetIds != null) {
                for (int appWidgetId : appWidgetIds) {
                    updateWidget(context, appWidgetManager, appWidgetId);
                }
            }
        }
    }

    private void updateWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
        int counter = prefs.getInt(COUNTER_KEY, 0);

        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_counter);
        views.setTextViewText(R.id.tv_counter, String.valueOf(counter));

        // 绑定增减按钮点击事件
        Intent incrementIntent = new Intent(context, CounterWidget.class);
        incrementIntent.setAction(ACTION_INCREMENT);
        incrementIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        PendingIntent incrementPendingIntent = PendingIntent.getBroadcast(context, appWidgetId, incrementIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        views.setOnClickPendingIntent(R.id.btn_increment, incrementPendingIntent);

        Intent decrementIntent = new Intent(context, CounterWidget.class);
        decrementIntent.setAction(ACTION_DECREMENT);
        decrementIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        PendingIntent decrementPendingIntent = PendingIntent.getBroadcast(context, appWidgetId + 1000, decrementIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
        views.setOnClickPendingIntent(R.id.btn_decrement, decrementPendingIntent);

        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}

2.4 注册Widget到AndroidManifest.xml

在<application>标签内添加:

<receiver android:name=".CounterWidget">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
        <action android:name="com.yourpackage.INCREMENT"/>
        <action android:name="com.yourpackage.DECREMENT"/>
    </intent-filter>
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/widget_info"/>
</receiver>

3. 开发自定义Capacitor插件

3.1 创建插件骨架

在项目根目录执行:

npm install @capacitor/cli -g
npx cap plugin create CounterWidgetPlugin

3.2 实现Android端插件逻辑

修改android/src/main/java/com/yourpackage/counterwidgetplugin/CounterWidgetPlugin.java:

package com.yourpackage.counterwidgetplugin;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

@CapacitorPlugin(name = "CounterWidget")
public class CounterWidgetPlugin extends Plugin {
    private static final String PREFS_NAME = "CounterPrefs";
    private static final String COUNTER_KEY = "counter_value";
    private BroadcastReceiver counterUpdateReceiver;

    @Override
    public void load() {
        // 监听Widget发送的计数器更新广播
        counterUpdateReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                int value = intent.getIntExtra("value", 0);
                JSObject data = new JSObject();
                data.put("value", value);
                notifyListeners("counterUpdated", data);
            }
        };
        getContext().registerReceiver(counterUpdateReceiver, new IntentFilter("com.yourpackage.COUNTER_UPDATED"));
    }

    @Override
    public void destroy() {
        super.destroy();
        getContext().unregisterReceiver(counterUpdateReceiver);
    }

    @PluginMethod
    public void getCounter(PluginCall call) {
        SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
        int value = prefs.getInt(COUNTER_KEY, 0);
        JSObject ret = new JSObject();
        ret.put("value", value);
        call.resolve(ret);
    }

    @PluginMethod
    public void setCounter(PluginCall call) {
        Integer value = call.getInt("value");
        if (value == null) {
            call.reject("Value is required");
            return;
        }
        SharedPreferences prefs = getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
        prefs.edit().putInt(COUNTER_KEY, value).apply();

        // 触发Widget更新
        Intent updateIntent = new Intent(getContext(), CounterWidget.class);
        updateIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        int[] appWidgetIds = AppWidgetManager.getInstance(getContext()).getAppWidgetIds(new ComponentName(getContext(), CounterWidget.class));
        updateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
        getContext().sendBroadcast(updateIntent);

        call.resolve();
    }
}

3.3 实现Web端插件接口

修改src/web.ts:

import { registerPlugin, PluginListenerHandle } from '@capacitor/core';

export interface CounterWidgetPlugin {
  getCounter(): Promise<{ value: number }>;
  setCounter(options: { value: number }): Promise<void>;
  addListener(eventName: 'counterUpdated', listenerFunc: (data: { value: number }) => void): Promise<PluginListenerHandle> & PluginListenerHandle;
}

const CounterWidget = registerPlugin<CounterWidgetPlugin>('CounterWidget');

export default CounterWidget;

4. 在Ionic Angular组件中集成

修改你的计数器组件代码:

import { Component, OnInit, OnDestroy } from '@angular/core';
import CounterWidget from '../path/to/counter-widget-plugin';
import { PluginListenerHandle } from '@capacitor/core';

@Component({
  selector: 'app-counter',
  templateUrl: './counter.component.html',
  styleUrls: ['./counter.component.scss'],
})
export class CounterComponent implements OnInit, OnDestroy {
  counter = 0;
  private listener?: PluginListenerHandle;

  async ngOnInit() {
    // 初始化获取计数器值
    const result = await CounterWidget.getCounter();
    this.counter = result.value;

    // 监听原生端的计数器更新事件
    this.listener = await CounterWidget.addListener('counterUpdated', (data) => {
      this.counter = data.value;
    });
  }

  async increment() {
    this.counter++;
    await CounterWidget.setCounter({ value: this.counter });
  }

  async decrement() {
    this.counter--;
    await CounterWidget.setCounter({ value: this.counter });
  }

  ngOnDestroy() {
    this.listener?.remove();
  }
}

组件模板(counter.component.html):

<ion-button (click)="decrement()">-</ion-button>
<ion-text class="counter-text">{{ counter }}</ion-text>
<ion-button (click)="increment()">+</ion-button>

关键注意事项

  • Android 12+适配:PendingIntent必须添加FLAG_IMMUTABLE或FLAG_MUTABLE,本文使用FLAG_IMMUTABLE
  • 权限声明:在AndroidManifest.xml中添加必要权限:
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    
  • 数据存储选型:轻量数据用SharedPreferences,复杂数据可改用Room数据库
  • Widget更新时机:通过广播手动触发,避免系统自动更新的延迟问题

内容的提问来源于stack exchange,提问作者Ariyan Ashfaque Mostafa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:39:57