小工具知识网

当前位置: 首页 > 行业知识 > 一文掌握android小组件(AppWidget)开发

行业知识

一文掌握android小组件(AppWidget)开发

2026-11-02 18:50:14 来源:互联网转载

最近在学习android小组件开发,记之以便日后温习。桌面小组件可以方便使用app的隐藏功能,实现一键触达,提升用户体验。这样可以极大便捷家里的老人使用应用,享受到互联网带来的便捷。最近给家里的老人添加了追剧的小组件,这样她不需要学习怎么搜索、怎么选择,通过桌面小组件便可以快速触达。下面将分别介绍基础小组件开发、列表小组件开发、开发小组件的注意事项。

一、基础小组件的开发

实现android的小组件需要按照一定的范式,主要包括四步,下面将以扫一扫的小组件入口为例分别介绍:

第一步:创建ScanWidgetProvider.java类,继承自android的AppWidgetProvider。小组件的核心逻辑包括点击、展示都在此处。后文会详细介绍。目前此类可以保持空实现。

第二步:在AndroidManifest中配置小组件的receiver,了解android的都知道receiver配置的是广播接收器,难道小组件是一个广播接收器?通过查看AppWidgetProvider的继承类发现,其继承了BroadcastReceiver,所以小组件实际上就是一个广播接收器。

  //添加引导处展示的标签名称               //配置文件                                         

第三步:创建第二步中用到的resource文件widget_scan_resource.xml,创建在res/xml文件下,示例如下。

                           //更新时间间隔,单位是毫秒,

上述代码需重点介绍minHeight和minWidth。手机将桌面划分成了一个个独立网格,然后桌面内容都展示在网格中,一个应用图标就占一格。小组件在桌面同样展示在网格中,当占一个网格时就是1X1的组件;横向两个网格就是2X1的组件,那么横纵各两个网格自然就是2X2的组件。此处的minHeight和minWidth是指需要占用的网格的总长和宽,常用经验公式计算:(m为横向网格数,n为竖向网格数)

minWidth = 70 x m - 30(dp)

minWidth = 70 x n - 30(dp)

此时你可能不禁会问,如果设置的最小宽高和手机网格的宽高不匹配怎么办。没关系,系统会帮取整找到最近的网格倍数设置给小组件。这也是为什么此参数需要加上min(最小)了。

第四步:创建小组件的布局文件scan_widget_layout.xml

                                

至此一个简单的小组件就开发完成了。将以上代码放入项目中,手机中就可以添加扫一扫小组件了。如下图所示

目前上述小组件只有空壳,还没有灵魂,所以需要为他注入灵魂。想必你已经猜到,就是在第一步创建的ScanWidgetProvider.java类中注入小组件的“灵魂”。那么小组件的灵魂是什么呢?当然是曝光更能吸引用户的信息,点击后能拉起app到指定落地页。废话不多说,先上代码。

public class ScanWidgetProvider extends AppWidgetProvider {    private static final String TAG = "ScanWidgetProvider";    private static RemoteViews remoteViews;    @Override    public void onReceive(Context context, Intent intent) {        Log.d(TAG, "onReceive");        super.onReceive(context, intent);    }    //小组件更新时回调,所以加载小组件的逻辑都在此处    @Override    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {        Log.d(TAG, "onUpdate");        super.onUpdate(context, appWidgetManager, appWidgetIds);        if (appWidgetManager == null || appWidgetIds == null) {            Log.d(TAG, "onUpdate error, appWidgetManager=" + appWidgetManager + " appWidgetIds=" + appWidgetIds);            return;        }        //小组件是运行在独立进程中,所以涉及到跨进程的渲染,故只能使用remoteViews加载和设置ui        createRemoteViews(context);        updateDefaultWidget(context, appWidgetManager, appWidgetIds);    }    private static void updateDefaultWidget(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {        int roundCorner = Utils.dip2px(context, 14);    //dp转化为px,工具类代码不贴了,网上很多        int topWidth = Utils.dip2px(context, 80);        int topHeight = Utils.dip2px(context, 80);        //将图片压缩加载        Bitmap topBitmap = decodeSampleBitmap(context, R.drawable.demo_scan, topWidth, topHeight);        if (topBitmap!= null) {            //使用remoteViews设置图片视图            remoteViews.setImageViewBitmap(R.id.iv_scan, topBitmap);        }        //使用remoteViews设置展示文字和颜色        remoteViews.setTextViewText(R.id.tv_scan, "AR扫一扫");        remoteViews.setTextColor(R.id.tv_scan, Color.RED);        updateWidgetClickEvent(context, appWidgetManager, appWidgetIds);    }    private static void updateWidgetClickEvent(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {        DebugLog.log(TAG, "updateWidgetClickEvent");         Intent intent = new Intent("com.demo.scan.main");        intent.setPackage(context.getPackageName());        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);        intent.putExtra("WIDGET_NAME", "ScanWidgetProvider");                //remoteViews设置点击事件,只能使用PendingIntent        intent.putExtra("CLICK_ACTION", "scan_demo_image_click");        PendingIntent pendingIntent = PendingIntent.getActivity(context, R.id.iv_scan, intent, PendingIntent.FLAG_UPDATE_CURRENT);        remoteViews.setOnClickPendingIntent(R.id.iv_scan, pendingIntent);        //渲染UI        submitRender(appWidgetManager, appWidgetIds, remoteViews);    }    //渲染小组件    private static void submitRender(AppWidgetManager appWidgetManager, int[] appWidgetIds, RemoteViews remoteViews) {        for (int appWidgetId : appWidgetIds) {            Log.d(TAG, "submitRender,widgetId:" + appWidgetId);            appWidgetManager.updateAppWidget(appWidgetId, remoteViews);        }    }    private void createRemoteViews(Context context) {        if (remoteViews == null) {            DebugLog.log(TAG, "createRemoteViews");            remoteViews = new RemoteViews(context.getPackageName(), R.layout.scan_widget_layout);        }    }        public static Bitmap decodeSampleBitmap(Context context, int resId, int reqWidth, int reqHeight) {        BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;        BitmapFactory.decodeResource(context.getResources(), resId, options);        //计算采样比例        options.inSampleSize = calculateSampleSize(options, reqWidth, reqHeight);        DebugLog.log(TAG,"options.inSampleSize:" + options.inSampleSize);        options.inJustDecodeBounds = false;        return bitmap = BitmapFactory.decodeResource(context.getResources(), resId, options);    }}public static int calculateSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {        //获得图片的原宽高        int height = options.outHeight;        int width = options.outWidth;        int inSampleSize = 1;        if (height > reqHeight || width > reqWidth) {            // 计算出实际宽高和目标宽高的比率            final int heightRatio = Math.round((float) height / (float) reqHeight);            final int widthRatio = Math.round((float) width / (float) reqWidth);            inSampleSize = Math.min(heightRatio, widthRatio);        }        return inSampleSize;    }

小组件的更新代码都在onUpdate中实现,widget中只能使用RemoteViews提供的一些列set方法更新小组件的内容,例如使用setImageViewBitmap设置图片,使用setTextViewText设置文字,使用setOnClickPendingIntent设置点击事件等。

至此一个基础的小组件开发就完成了,由于小组件展示在“寸土寸金”的桌面,使用基础组件就可以完成绝大部分需求。但是如果碰到需要展示列表形式,则需要使用ListView、GridView。

二、列表组件的开发

通过上文介绍知道小组件并不能像普通安卓应用开发那样,找到指定view,然后对view进行操作。小组件需要通过RemoteViews提供的一些列的set方法对视图渲染更新。那么listview和gridview也是一样的。回忆一下列表视图的正常开发流程,先创建listview的视图,然后为视图设置layoutmanager设置布局样式,最后创建列表的adapter进行数据填充。widget采用同样的实现流程,只是需要使用符合widget规范的特有方式实现。接下来将在上文案例的基础上进行修改:

第一步:修改widget_scan_resource.xml的最小宽高,设置为4X4的组件

android:minHeight="250dp"   //最小高度android:minWidth="250dp"    //最小宽度

第二步:修改布局文件scan_widget_layout.xml,在命名为tv_scan的Textview下增加girdlist

    ...........

第三步:创建更新列表视图需要的“adapter”,此处打了双引号,就是说需要按照小组件的方式实现,下面两个类按照固定范式实现。

public class ListWidgetService extends RemoteViewsService {    @Override    public RemoteViewsFactory onGetViewFactory(Intent intent) {        return new ListWidgetFactory(this);    }}public class ListWidgetFactory implements RemoteViewsService.RemoteViewsFactory {    private final List data = new ArrayList();    private final Context context;    public ListWidgetFactory(Context context) {        this.context = context;    }    @Override    public void onCreate() {        initData();    }    private void initData() {        data.clear();        for (int i = 0; i  

这一步代码有点长,但是逻辑很简单。就是先创建RemoteViewsService ,通过此service返回一个RemoteViewsFactory ,RemoteViewsFactory 就是通常理解的列表更新的adapter,其实从内部的继承方法也可以看出。getItemId、getViewTypeCount、getCount是不是都似曾相识?没错,就是开发列表adapter常复写的方法。

此处重点介绍一下getViewAt,内部同样需要使用RemoteViews实现单个列表的view,列表内元素的信息更新需要使用RemoteViews提供的每个set方法。list_scan_item视图布局代码不再提供,很简单就是上图下文的形式。

第四步:更新ScanWidgetProvider,主要在onUpdate方法中设置列表的点击事件和“adapter”

//        设置列表的adapter        Intent listIntent = new Intent(context, ListWidgetService.class);        remoteViews.setRemoteAdapter(R.id.scan_list, listIntent);//设置列表的点击事件Intent intent1 = getCommenIntent(context);        intent1.putExtra("CLICK_ACTION", "scan_demo_list_click");        PendingIntent pendingIntent1 = PendingIntent.getActivity(context, R.id.scan_list, intent1, PendingIntent.FLAG_UPDATE_CURRENT);        remoteViews.setPendingIntentTemplate(R.id.scan_list, pendingIntent1);

上述代码需重点关注列表的点击事件,setPendingIntentTemplate是设置列表的通用点击Intent,然后单个item的点击如果需要增加参数,则需使用setOnClickFillInIntent设置补充信息。接收方收到的intent中包含这两部分的内容,于是便可以区分出具体是哪个item的点击了。至此一个列表的小组件便实现完成了。

三、小组件的开发原理和注意事项

1、小组件实际上是运行在系统进程中,所以和app进程涉及到跨进程通信,只能使用RemoteViews更新数据,使用PendingIntent执行延时意图。

2、RemoteViews支持的布局只有以下这些,开发的小组件布局不能超出这些范围。

布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout。

View:Button、TextView、Chronometer、ImageButton、ImageView、ProgressBar、ViewFlipper、AnalogClock、AdapterViewFlipper、ViewStub、ListView、GridView、StackView

3、小组件类继承自AppWidgetProvider,实际上就是一个广播,可以通过发送广播和接受广播进行通信更新,广播接受在onReceive中实现。

onUpdate :小组件创建时和达到更新周期时触发。

onEnabl:第一个改类型小组件添加时触发。

onDeleted:每次删除该小组件时触发。

onDisabled:最后一个该类型小组件添加时触发。

4、在AndroidManifest注册小组件必须要有android.appwidget.action.APPWIDGET_UPDATE这个action,否则将无法安装,其他的action和自定义的action可根据需要添加。

上一篇:服务器上面怎么共享文件?

下一篇:星幻征途服务器怎么进去?