一文掌握android小组件(AppWidget)开发 您所在的位置:网站首页 vivo怎么设置桌面小组件图标显示时间 一文掌握android小组件(AppWidget)开发

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

2024-07-12 20:58| 来源: 网络整理| 查看: 265

最近在学习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 < 10; i++) { Random random = new Random(); String msg = "扫一扫" + random.nextInt(101); data.add(msg); } } @Override public void onDataSetChanged() { } @Override public void onDestroy() { data.clear(); } @Override public int getCount() { return data.size(); } //重点实现 @Override public RemoteViews getViewAt(int position) { RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.list_scan_item); remoteViews.setTextViewText(R.id.list_item_tv, data.get(position)); remoteViews.setImageViewResource(R.id.list_item_image, R.drawable.demo_scan); Intent intent = new Intent(); intent.putExtra("CLICK_POSITION", position); remoteViews.setOnClickFillInIntent(R.id.list_item_root, intent); return remoteViews; } @Override public RemoteViews getLoadingView() { return null; } @Override public int getViewTypeCount() { return 1; } @Override public long getItemId(int position) { return position; } @Override public boolean hasStableIds() { return true; } }

这一步代码有点长,但是逻辑很简单。就是先创建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可根据需要添加。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有