-
2022-01-07 15:35:50
Luuncher onCreate stupViews 初始化com.android.launcher3
.Workspace 控件,构造方法中实现setOnTouchListener(new WorkspaceTouchListener(mLauncher, this));
在 WorkspaceTouchListener的onLongPress中弹出OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);,长按桌面弹出小部件选择,选择小部件
/** Returns WidgetsFullSheet that was opened, or null if nothing was opened. /
@Nullable
public static WidgetsFullSheet openWidgets(Launcher launcher) {
if (launcher.getPackageManager().isSafeMode()) {
// 在安全模式下無法使用小工具
Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show();
return null;
} else {
return WidgetsFullSheet.show(launcher, true / animated */);
}
}WidgetsFullSheet 控件中加载小部件到 WidgetsRecyclerView中
/** Gets the {@link WidgetsRecyclerView} which shows all widgets in {@link WidgetsFullSheet}. */
@VisibleForTesting
public static WidgetsRecyclerView getWidgetsView(Launcher launcher) {
return launcher.findViewById(R.id.primary_widgets_list_view);
}
在BaseWidgetSheet的onLongClick中开发创建Widget, beginDraggingWidget方法具体创建,走到startDrag方法private boolean beginDraggingWidget(WidgetCell v) {
// Get the widget preview as the drag representation
WidgetImageView image = v.getWidgetView();// If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and // we abort the drag. if (image.getDrawable() == null && v.getAppWidgetHostViewPreview() == null) { return false; } PendingItemDragHelper dragHelper = new PendingItemDragHelper(v); dragHelper.setRemoteViewsPreview(v.getRemoteViewsPreview()); dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview()); if (image.getDrawable() != null) { int[] loc = new int[2]; getPopupContainer().getLocationInDragLayer(image, loc); dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(), image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions()); } else { View preview = v.getAppWidgetHostViewPreview(); int[] loc = new int[2]; getPopupContainer().getLocationInDragLayer(preview, loc); Rect r = new Rect(0, 0, preview.getWidth(), preview.getHeight()); dragHelper.startDrag(r, preview.getMeasuredWidth(), preview.getMeasuredWidth(), new Point(loc[0], loc[1]), this, new DragOptions()); } close(true); return true;
}
首先调用AppWidgetHostView.setAppWidget(int appWidgetId, AppWidgetProviderInfo info)
,设置AppWidget的显示View和padding,appWidgetId默认-1拖动Widget ,DragController.DragListener的实现WidgetHostViewLoader.onDragStart
(),/** * Start preloading the widget. */ private boolean preloadWidget() { final LauncherAppWidgetProviderInfo pInfo = mInfo.info; if (pInfo.isCustomWidget()) { return false; } final Bundle options = mInfo.getDefaultSizeOptions(mLauncher); // If there is a configuration activity, do not follow thru bound and inflate. if (mInfo.getHandler().needsConfigure()) { mInfo.bindOptions = options; return false; } mBindWidgetRunnable = new Runnable() { @Override public void run() { mWidgetLoadingId = mLauncher.getAppWidgetHost().allocateAppWidgetId(); if (LOGD) { Log.d(TAG, "Binding widget, id: " + mWidgetLoadingId); } if (new WidgetManagerHelper(mLauncher).bindAppWidgetIdIfAllowed( mWidgetLoadingId, pInfo, options)) { // Widget id bound. Inflate the widget. mHandler.post(mInflateWidgetRunnable); } } }; mInflateWidgetRunnable = new Runnable() { @Override public void run() { if (LOGD) { Log.d(TAG, "Inflating widget, id: " + mWidgetLoadingId); } if (mWidgetLoadingId == -1) { return; } AppWidgetHostView hostView = mLauncher.getAppWidgetHost().createView( (Context) mLauncher, mWidgetLoadingId, pInfo); mInfo.boundWidget = hostView; // We used up the widget Id in binding the above view. mWidgetLoadingId = -1; hostView.setVisibility(View.INVISIBLE); int[] unScaledSize = mLauncher.getWorkspace().estimateItemSize(mInfo); // We want the first widget layout to be the correct size. This will be important // for width size reporting to the AppWidgetManager. DragLayer.LayoutParams lp = new DragLayer.LayoutParams(unScaledSize[0], unScaledSize[1]); lp.x = lp.y = 0; lp.customPosition = true; hostView.setLayoutParams(lp); if (LOGD) { Log.d(TAG, "Adding host view to drag layer"); } mLauncher.getDragLayer().addView(hostView); mView.setTag(mInfo); } }; if (LOGD) { Log.d(TAG, "About to bind/inflate widget"); } mHandler.post(mBindWidgetRunnable); return true; }
第一步,getAppWidgetHost().allocateAppWidgetId获取服务器分配的 appWidgetId
第二部,AppWidgetManager#bindAppWidgetIdIfAllowed(int, UserHandle, ComponentName, Bundle)
为给Widget设置appWidgetId。如果成功,应用程序的AppWidgetProvider将收到ACTION_APPWIDGET_UPDATE更新广播。
绑定AppWidgetID成功后,初始化Widget界面,最终调用mLauncher.getDragLayer().addView(hostView);
添加布局到Draglayer 底层布局中。更多相关内容 -
Android Widget 桌面组件开发介绍
2021-01-05 03:10:52Android widget 桌面组件开发 Widget是Android1.5版所引进的特性之一.Widget,可让用户在主屏幕界面及时了解程序显示的重要信息.标准的Android系统已包含几个Widget的示例,如模拟时钟,音乐播放器等. 一、AppWidget ... -
简单掌握Android Widget桌面小部件的创建步骤
2021-01-04 22:17:37一、Widget设计步骤 需要修改三个XML,一个class: 1.第一个xml是布局XML文件(如:main.xml),是这个widget的。一般来说如果用这个部件显示时间,那就只在这个布局XML中声明一个textview就OK了。 2.第二个xml... -
AppWidget桌面时钟插件的实现
2015-07-08 20:47:50用AppWidget来实现桌面时钟插件,与系统时间相同。点击进入到应用 -
实现Android Widget 桌面小部件
2021-06-28 09:42:00Android实现桌面小部件(widget) Android实现桌面小部件 可以切换标题显示不同列表,列表中可以显示不同类型布局以及显示和隐藏操作菜单。 图4显示不同类型的item显示的效果一样。 想体验效果可以下载apk运行...实现Android Widget 桌面小部件
Android实现桌面小部件
可以切换标题显示不同列表,列表中可以显示不同类型布局以及显示和隐藏操作菜单。
图4显示不同类型的item显示的效果一样。
想体验效果可以下载apk运行体验:app-release.apk
提取码:uduw运行程序后找到widget,拖到桌面就可以体验了
实现方法
1.创建一个布局文件file_app_widget.xml用于显示整个widget的布局,代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/file_app_widget_bg" android:padding="@dimen/widget_margin" android:theme="@style/ThemeOverlay.Nxfilemanager.AppWidgetContainer"> <LinearLayout android:id="@+id/widget_title_lay" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingTop="4dp" android:paddingBottom="4dp" android:paddingRight="16dp" android:paddingLeft="16dp"> <TextView android:id="@+id/widget_title1" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:paddingTop="10dp" android:paddingBottom="10dp" android:gravity="center" android:text="@string/widget_title1" android:textColor="@color/widget_font_black" android:textSize="12sp" /> <TextView android:id="@+id/widget_title2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:paddingTop="10dp" android:paddingBottom="10dp" android:gravity="center" android:text="@string/widget_title2" android:textColor="@color/widget_font_black" android:textSize="12sp" /> <TextView android:id="@+id/widget_title3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:paddingTop="10dp" android:paddingBottom="10dp" android:gravity="center" android:text="@string/widget_title3" android:textColor="@color/widget_font_black" android:textSize="12sp" /> <TextView android:id="@+id/widget_title4" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:paddingTop="10dp" android:paddingBottom="10dp" android:gravity="center" android:text="@string/widget_title4" android:textColor="@color/widget_font_black" android:textSize="12sp" /> </LinearLayout> <ListView android:id="@+id/widget_list" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/widget_title_lay" android:paddingLeft="18dp" android:paddingRight="18dp" /> </RelativeLayout>
布局中有4个TextView用于显示4个标题,一个ListView用于显示列表。
2.创建一个item布局文件,用于显示列表里的item信息,在这就不插入代码了。
3.创建一个资源文件file_app_widget_info.xml,放在res/xml目录下,file_app_widget_info.xml文件代码如下:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/file_app_widget" android:initialLayout="@layout/file_app_widget" android:minWidth="250dp" android:minHeight="250dp" android:previewImage="@drawable/example_appwidget_preview" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="86400000" android:widgetCategory="home_screen"></appwidget-provider>
属性含义:
initialLayout表示要初始化显示的布局信息;minWidth和minHeight表示要显示的最小占屏幕的,250dp表示占桌面的44格子,计算公式为 (70 * n - 30),其中n表示占屏幕的几格;previewImage表示在查找预览widget时显示的图;resizeMode表示widget小部件是否可改变长宽的大小。
更多属性可查看官网:构建应用微件
4.创建FileAppWidget.kt文件用于创建和更新widget小部件,代码如下:
class FileAppWidget : AppWidgetProvider() { override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { Logger.t(TAG).v("onUpdate") // There may be multiple widgets active, so update all of them for (appWidgetId in appWidgetIds) { updateAppWidget(context, appWidgetManager, appWidgetId) } } override fun onAppWidgetOptionsChanged( context: Context?, appWidgetManager: AppWidgetManager?, appWidgetId: Int, newOptions: Bundle? ) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions) Logger.t(TAG).v("onAppWidgetOptionsChanged") } override fun onDeleted(context: Context?, appWidgetIds: IntArray?) { super.onDeleted(context, appWidgetIds) Logger.t(TAG).v("onDeleted") } override fun onRestored(context: Context?, oldWidgetIds: IntArray?, newWidgetIds: IntArray?) { super.onRestored(context, oldWidgetIds, newWidgetIds) Logger.t(TAG).v("onRestored") } override fun onEnabled(context: Context) { // Enter relevant functionality for when the first widget is created Logger.t(TAG).v("onEnabled") } override fun onDisabled(context: Context) { // Enter relevant functionality for when the last widget is disabled Logger.t(TAG).v("onDisabled") } override fun onReceive(context: Context?, intent: Intent?) { Logger.t(TAG).v("onReceive") intent?.also { val action = intent.action action?.also { when (action) { WIDGET_COLLECTION_TITLE_ACTION -> {//标题 ... } WIDGET_COLLECTION_VIEW_ACTION -> {//列表item ... } } } } super.onReceive(context, intent) } companion object { val TAG = "FileAppWidget-" const val WIDGET_COLLECTION_TITLE_ACTION = "WIDGET_COLLECTION_TITLE_ACTION" const val WIDGET_COLLECTION_TITLE_EXTRA = "WIDGET_COLLECTION_TITLE_EXTRA" const val WIDGET_COLLECTION_VIEW_ACTION = "WIDGET_COLLECTION_VIEW_ACTION" const val WIDGET_COLLECTION_VIEW_EXTRA = "WIDGET_COLLECTION_VIEW_EXTRA" const val WIDGET_COLLECTION_VIEW_EXTRA_POSITION = "WIDGET_COLLECTION_VIEW_EXTRA_POSITION" const val WIDGET_COLLECTION_VIEW_EXTRA_PATH = "WIDGET_COLLECTION_VIEW_EXTRA_PATH" } } ... internal fun refreshWidget(context: Context, remoteViews: RemoteViews, refreshList: Boolean) { val appWidgetManager = AppWidgetManager.getInstance(context) val componentName = ComponentName(context, FileAppWidget::class.java) appWidgetManager.updateAppWidget(componentName, remoteViews) if (refreshList) { appWidgetManager.notifyAppWidgetViewDataChanged( appWidgetManager.getAppWidgetIds( componentName ), R.id.widget_list ) } } internal fun refreshList(context: Context) { val appWidgetManager = AppWidgetManager.getInstance(context) val componentName = ComponentName(context, FileAppWidget::class.java) appWidgetManager.notifyAppWidgetViewDataChanged( appWidgetManager.getAppWidgetIds(componentName), R.id.widget_list ) } internal fun updateAppWidget( context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int ) { // Construct the RemoteViews object val views = RemoteViews(context.packageName, R.layout.file_app_widget) views.setTextColor(R.id.widget_title1, context.getColor(R.color.widget_font_blue)) //标题1 val title1Intent = Intent(context, FileAppWidget::class.java) title1Intent.action = WIDGET_COLLECTION_TITLE_ACTION title1Intent.data = Uri.parse(title1Intent.toUri(Intent.URI_INTENT_SCHEME)) title1Intent.putExtra(WIDGET_COLLECTION_TITLE_EXTRA, 0) val uDiskPendingIntent = PendingIntent.getBroadcast(context, 0, title1Intent, PendingIntent.FLAG_UPDATE_CURRENT) views.setOnClickPendingIntent(R.id.widget_title1, uDiskPendingIntent) //标题2 val title2Intent = Intent(context, FileAppWidget::class.java) title2Intent.action = WIDGET_COLLECTION_TITLE_ACTION title2Intent.data = Uri.parse(title2Intent.toUri(Intent.URI_INTENT_SCHEME)) title2Intent.putExtra(WIDGET_COLLECTION_TITLE_EXTRA, 1) val favoritePendingIntent = PendingIntent.getBroadcast(context, 1, title2Intent, PendingIntent.FLAG_UPDATE_CURRENT) views.setOnClickPendingIntent(R.id.widget_title2, favoritePendingIntent) //标题3 val title3Intent = Intent(context, FileAppWidget::class.java) title3Intent.action = WIDGET_COLLECTION_TITLE_ACTION title3Intent.data = Uri.parse(title3Intent.toUri(Intent.URI_INTENT_SCHEME)) title3Intent.putExtra(WIDGET_COLLECTION_TITLE_EXTRA, 2) val downloadPendingIntent = PendingIntent.getBroadcast(context, 2, title3Intent, PendingIntent.FLAG_UPDATE_CURRENT) views.setOnClickPendingIntent(R.id.widget_title3, downloadPendingIntent) //标题4 val title4Intent = Intent(context, FileAppWidget::class.java) title4Intent.action = WIDGET_COLLECTION_TITLE_ACTION title4Intent.data = Uri.parse(title4Intent.toUri(Intent.URI_INTENT_SCHEME)) title4Intent.putExtra(WIDGET_COLLECTION_TITLE_EXTRA, 3) val recentPendingIntent = PendingIntent.getBroadcast(context, 3, title4Intent, PendingIntent.FLAG_UPDATE_CURRENT) views.setOnClickPendingIntent(R.id.widget_title4, recentPendingIntent) //列表适配器 val serviceIntent = Intent(context, FileAppWidgetService::class.java) serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)) views.setRemoteAdapter(R.id.widget_list, serviceIntent) //列表item点击 val listIntent = Intent(context, FileAppWidget::class.java) listIntent.action = WIDGET_COLLECTION_VIEW_ACTION listIntent.data = Uri.parse(listIntent.toUri(Intent.URI_INTENT_SCHEME)) val listPendingIntent = PendingIntent.getBroadcast(context, 5, listIntent, PendingIntent.FLAG_UPDATE_CURRENT) views.setPendingIntentTemplate(R.id.widget_list, listPendingIntent) // Instruct the widget manager to update the widget appWidgetManager.updateAppWidget(appWidgetId, views) }
5.创建一个FileAppWidgetFactory.kt文件用于处理ListView列表的item,代码如下:
class FileAppWidgetFactory( val context: Context, val intent: Intent? ) : RemoteViewsService.RemoteViewsFactory { override fun onCreate() { Logger.t(TAG).d("onCreate") mContext = context titleType = 0 } override fun getLoadingView(): RemoteViews? { Logger.t(TAG).d("getLoadingView") return null } override fun getItemId(position: Int): Long { Logger.t(TAG).d("getItemId") return position.toLong() } override fun onDataSetChanged() { Logger.t(TAG).d("onDataSetChanged") if (isClickFill) { isClickFill = false } else { initData() } } override fun hasStableIds(): Boolean { Logger.t(TAG).d("hasStableIds") return true } override fun getViewAt(position: Int): RemoteViews { Logger.t(TAG).d("getViewAt") val views = RemoteViews(context.packageName, R.layout.widget_item_view) val bean = fileWidgetBeans[position] //设置item布局信息 ... //菜单点击 ... return views } override fun getCount(): Int { Logger.t(TAG).d("getCount") return fileWidgetBeans.size } override fun getViewTypeCount(): Int { Logger.t(TAG).d("getViewTypeCount") return 1 } override fun onDestroy() { Logger.t(TAG).d("onDestroy") titleType = -1 fileWidgetBeans.clear() } companion object { private val TAG = "FileAppWidgetFactory" private lateinit var mContext: Context var titleType = 0 var isClickFill = false private var fileWidgetBeans = mutableListOf<FileWidgetBean>() private var subscribe: Disposable? = null fun showMenu(position: Int) { if (position >= 0 && fileWidgetBeans.isNotEmpty()) { if (fileWidgetBeans[position].isShowMenu) { fileWidgetBeans[position].isShowMenu = false dispose() } else { hideMenu() fileWidgetBeans[position].isShowMenu = true downTime() } } } fun hideMenu(position: Int) { if (position >= 0 && fileWidgetBeans.isNotEmpty()) { fileWidgetBeans[position].isShowMenu = false dispose() } } private fun hideMenu() { for (bean in fileWidgetBeans) { bean.isShowMenu = false } } ... } private var isLoading = true private fun initData() { setData(titleType) } private fun setData(type: Int) { isLoading = true fileWidgetBeans.clear() when (type) { 0 -> {//标题1 setTitle1Data() } 1 -> {//标题2 setTitle2Data() } 2 -> {//标题3 setTitle3Data() } 3 -> {//标题4 setTitle4Data() } else -> isLoading = false } } ... }
6.创建一个FileAppWidgetService.kt文件用于创建RemoteViewsService.RemoteViewsFactory,代码如下:
class FileAppWidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { return FileAppWidgetFactory(this, intent) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return Service.START_STICKY } }
7.最后还需要在AndroidManifest.xml文件中注册receiver和server,代码如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.desktopwidget"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".widget.FileAppWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/file_app_widget_info" /> </receiver> <service android:name=".widget.FileAppWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" /> </application> </manifest>
到这代码基本完成了。
在实习widget的过程中要注意一些问题
在代码实现后,运行程序发现widget中的界面显示异常或者一直显示在加载中,这个时候就去布局看一下,是否使用了RemoteViews不支持的布局,大部分都是这个问题导致的,我在实际编写过程中也碰到过这个问题,所以与大家分享一下,避免入坑。
RemoteViews 对象(因而应用微件)可以支持以下布局类:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
以及以下微件类:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
不支持这些类的后代。
RemoteViews 还支持
ViewStub
,它是一个大小为零的不可见视图,您可以使用它在运行时以懒散的方式扩充布局资源。
具体源码可下载参考 源码
-
Widget桌面
2012-12-10 11:48:28该资源是我所编写的widge桌面插件,里面的代码都是比较基础的,你们可以考虑从基础的代码扩展。 -
Widget桌面小组件-Android学习笔记,总结到位
2022-03-05 19:58:06Created with Raphaël 2.1.2 绘制Widget布局 配置Widget的基本属性 定义AppWidgetProvider 提供Configuration Activity 学习目标 掌握Android Widget基础用法及步骤 Android Widget实现一个基本的时钟功能 ...特点
–
-
快捷、方便
-
个性化、可定义功能
-
可及时控制更新Widget显示内容
用法步骤流程图
Created with Raphaël 2.1.2 绘制Widget布局 配置Widget的基本属性 定义AppWidgetProvider 提供Configuration Activity
学习目标
-
掌握Android Widget基础用法及步骤
-
Android Widget实现一个基本的时钟功能
效果
–
-
可以自动更新时间
-
可以添加删除时钟Widget
实验过程
绘制Widget布局
以下代码写在widget.xml中:
<TextView
android:id="@+id/tv"
android:text=“时间显示”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content” />
配置Widget的基本属性
xml中新建widgetconfig.xml,配置widget的基本属性
<appwidget-provoider xmls:android=“http://schems.android.com/apk/res/android”
android:initialLayout="@layout/widget"
android:minHeight=“40dp”
android:minWidth=“100dp”
android:updtatePeriodMillis=“864000” /> //刷新时间
定义AppWidgetProvider
定义并实现provider的各种方法。更新、移除、刷新等。
public class WidgetProvider extends AppWidgetProvoider{
@Override
public void onEnabled(Context context){
super.onEnabled(context);
//第一个widget添加到屏幕执行
}
@Override
public void onReceive(Context context,Intent intent){
super.onReceive(context, intent);
}
@Override
public void onDeleted(Context context, int[] aapWidgetIds){
super.onDeleted(context, appWidgetIds);
//widget被屏幕移除
}
@Override
public void onDisabled(Context context){
super.onDisabled(context);
//最后一个widget从屏幕移除执行
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
super.onUpdate(context, appWidgetManager, appWidgetIds);
//刷新widget
//remoteView和AppWidgetManager
}
}
配置TimerSevice
public class TimerService extends Service{
private Timer timer;
新的开始
改变人生,没有什么捷径可言,这条路需要自己亲自去走一走,只有深入思考,不断反思总结,保持学习的热情,一步一步构建自己完整的知识体系,才是最终的制胜之道,也是程序员应该承担的使命。
如果有需要进阶Android高级工程师系统学习资料的,我可以免费分享给大家,需要完整版的朋友,【点这里可以看到全部内容】。
《系列学习视频》
《系列学习文档》
《我的大厂面试之旅》
%E8%96%AA%EF%BC%81.md)】。**
《系列学习视频》
[外链图片转存中…(img-T9ef5pxV-1646481469014)]《系列学习文档》
[外链图片转存中…(img-kIGnxQYy-1646481469016)]
《我的大厂面试之旅》
[外链图片转存中…(img-X7NCCAKR-1646481469016)]
-
-
androidwidget桌面插件的简单事例讲解.docx
2021-11-17 09:01:15androidwidget桌面插件的简单事例讲解.docx -
android的widget桌面开关例子
2016-08-16 15:43:16android的widget桌面开关例子,本来打算做一个对2G,4G开关的程序,满足在4G信号不好的时候手动切换到2G的状态,由于需要系统授权,所以只做了界面没有具体实现。 -
Android心情记录器(App widget 桌面组件)应用例子.rar
2019-07-10 11:18:09可将你的心情讯录并用桌面组件的形式展示于桌面上,并有丰富的表情可供选择并加载在桌面上,此功能类似于QQ上的各性签名,可以看到手机主人的心情状况不是很好,注:因为这个小程序完全是App widget 桌面组件,所以... -
AppWidget桌面小控件
2016-04-20 16:41:28AppWidget桌面小控件 -
Android widget桌面插件
2015-03-25 13:36:26Android widget是桌面插件,在android系统应用开发层面有特殊用途。AppWidget是把一个进程的控件嵌入到别外一个进程的窗口里的一种方法。悬浮窗的效果与Widget很类似,但是它比Widget要灵活的多。 -
Android App Widget桌面小部件开发实践
2021-08-16 11:05:55App Widget桌面小部件已经被很多APP所使用,在日常的生活中也有很多人在使用,最常见的就是时钟、天气、日历、记事本这样的小工具,但是不少主流APP也提供了一些快捷功能的小部件,例如音乐快捷播放、代办事项、课程...App Widget桌面小部件已经被很多APP所使用,在日常的生活中也有很多人在使用,最常见的就是时钟、天气、日历、记事本这样的小工具,但是不少主流APP也提供了一些快捷功能的小部件,例如音乐快捷播放、代办事项、课程表、股票、购物快捷搜索、邮箱邮件等等,桌面小部件是APP部分业务功能的快捷入口,可以有效提升用户的使用体验缩短触达链路。
一、小部件基本组成
1.1 小部件UI布局
和Activity类似,在 XML 资源中为定义初始布局并将其保存在项目的 res/layout/目录中,App Widget 布局基于RemoteViews,它不支持所有类型的容器布局和视图控件。
RemoteViews 可支持的容器和控件如下:
容器
- FrameLayout (帧布局)
- LinearLayout (线性布局)
- RelativeLayout (相对布局)
控件
- AnalogClock (时钟)
- Button (按钮)
- Chronometer (计时器)
- ImageButton (图片按钮)
- ImageView (图片)
- ProgressBar (进度条)
- TextView (文本框)
- ListView (纵向列表)
- GridView (宫格)
- StackView (堆叠)
- ViewFlipper (翻转控件)
- AdapterViewFlipper (翻转控件,但需要Adapter)
并且,不支持继承了上述容器和控件的View,也就是不支持自定义View。
例如:
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/my_widget_background"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
1.2 AppWidgetProviderInfo 元数据
AppWidgetProviderInfo 用于描述一个 App Widget 的基本属性,如布局文件、布局尺寸、布局可变方向、最小可变布局尺寸、预览图、更新频率、配置Activity。
使用单个
<appwidget-provider>
元素在 XML 资源文件中定义 AppWidgetProviderInfo 对象,并将其保存在项目的 res/xml 文件夹中。例如:
<appwidget-provider xmlns:android = "http://schemas.android.com/apk/res/android" android:minWidth = "250dp" android:minHeight = "110dp" android:updatePeriodMillis = "86400000" android:previewImage = "@drawable/preview" android:initialLayout = "@layout/example_appwidget" android:configure = "com.example.android.ExampleAppWidgetConfigure" android:resizeMode = "horizontal|vertical" />
以下是
<appwidget-provider>
属性的摘要:- minWidth和minHeight 属性的值指定默认情况下App Widget 消耗的最小空间量 。默认主屏幕根据具有定义高度和宽度的单元格网格在其窗口中定位应用小部件,如果一个App Widget的最小宽度或高度的值不匹配的单元的尺寸,则会调整到最接近的小区大小。
【注意】为适配不同的屏幕尺寸,小部件的最小尺寸不应大于 4 x 4 单元格。
- minResizeWidth和minResizeHeight属性指定 App Widget 的绝对最小尺寸。这些值应指定应用小部件即将无法辨认或无法使用的大小,使用这些属性允许用户将窗口小部件的大小调整为可能小于由minWidth和minHeight属性定义的默认窗口小部件大小的大小 。
- updatePeriodMillis属性定义了系统更新 App Widget 的频率。不能保证使用此值准确按时进行更新,官方建议尽可能不频繁地进行更新,建议每小时不超过一次以节省电池电量,并且最小值为1800000——即30分钟。
【注意】如果设备在更新时处于睡眠状态,则设备将唤醒以执行更新。如果每小时更新不超过一次,这可能不会对电池寿命造成重大问题。但是,如果需要更频繁地更新或不需要在设备休眠时更新,那么可以根据不会唤醒设备的闹钟执行更新,使用 AppWidgetProvider 接收的 Intent 设置 AlarmManager,将闹钟类型设置为ELAPSED_REALTIME或 RTC,然后设置 updatePeriodMillis为0。
- initialLayout属性指向定义 App Widget 布局的布局资源。
- configure属性定义了在用户添加 App Widget 时启动的配置Activity,以便用户配置 App Widget 属性。
- previewImage属性指定应用小部件在的外观预览图,用户在选择应用小部件时会看到该预览图。如果未提供,用户将看到APP的启动图标。
- resizeMode属性指定可以调整小部件大小的规则。此属性使主屏幕小部件可调整大小——水平、垂直或在两个轴上。用户按住小部件以显示其调整大小手柄,然后拖动水平和/或垂直手柄以更改布局网格上的大小。该resizeMode属性的值 包括“horizontal”、“vertical”和“none”。要将小部件声明为水平和垂直均可调整大小,则使用“horizontal|vertical”。
1.3 AppWidgetProvider 类实现
AppWidgetProvider类是小部件的核心类,继承了BroadcastReceiver,可以处理广播消息。
AppWidgetProvider 仅接收与 App Widget 相关的事件广播,例如 App Widget 何时更新、删除、启用和禁用。
当这些广播事件发生时,AppWidgetProvider 的主要生命周期方法会被调用:
- onUpdate()
此方法会根据配置的 updatePeriodMillis 属性定义的时间间隔进行调用,作用为更新小部件,当用户添加小部件时也会调用此方法,因此它应该执行一些基本的设置。但是,如果你已经声明了一个ConfigureActivity,这个方法在用户添加小部件时不会调用,而是在后续更新时调用。ConfigureActivity负责在配置完成后执行第一次更新。 - onDeleted(Context, int[])
每次从桌面删除小部件时都会调用此方法。 - onEnabled(Context)
只有在桌面上添加对应小部件的第一个实例时会调用此方法。例如,如果用户连续添加了一个小部件两次,只有第一次添加时会调用此方法。 - onDisabled(Context)
当对应小部件的最后一个实例从 App Widget 主机中删除时,会调用此方法,在此方法中应执行资源释放等工作。 - onReceive(Context, Intent)
每次接收到广播和上述每个回调方法之前会调用此方法,主要用于处理点击事件、手动更新等事项。
例如:
public class ExampleAppWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final int N = appWidgetIds.length; for (int i=0; i<N; i++) { int appWidgetId = appWidgetIds[i]; // 创建一个打开Activity的Intent Intent intent = new Intent(context, ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); // 点击事件的处理通过setOnClickPendingIntent而不是setOnclickListener RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout); views.setOnClickPendingIntent(R.id.button, pendingIntent); appWidgetManager.updateAppWidget(appWidgetId, views); } } }
1.4 在清单中声明应用小部件
小部件和广播的注册方式类似,AppWidgetProvider在AndroidManifest.xml文件中声明后即可生效:
<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>
二、高级功能开发
2.1 提升更新频率
【1.2 AppWidgetProviderInfo 元数据】中提到:updatePeriodMillis属性定义了系统更新 App Widget 的频率,且最小值为1800000——即30分钟。
但是大部分情况下,30分钟更新一次数据会产生很大的误差,在用户使用时必然是希望实时刷新的,这时候自动刷新已经无法满足用户的需求了,必须要进行手动触发刷新,提升更新的频率。
App Widget 是一个广播接收器,它的生命周期里的onUpdate()是一个特定的广播触发的,可以通过Service定时发送刷新的广播,达到提升更新频率的目的。
/** * 控制 桌面小部件 更新 */ public class WidgetService extends Service { /** * 周期性更新 widget 的周期 */ private static final int UPDATE_TIME = 1000; private Timer mTimer; private TimerTask mTimerTask; @Override public void onCreate() { super.onCreate(); // 每经过指定时间,发送一次广播 mTimer = new Timer(); mTimerTask = new TimerTask() { @Override public void run() { Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); sendBroadcast(updateIntent); } }; mTimer.schedule(mTimerTask, UPDATE_TIME, UPDATE_TIME); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); mTimerTask.cancel(); mTimer.cancel(); } }
2.2 列表、宫格、轮播实现
这一类型样式的实现有一个共同点,需要依赖Adapter完成item的UI以及逻辑的编写。
平时开发中,需要通过目标控件的setAdapter()方法将对应的Adapter绑定到控件上,但是RemoteViews不能通过findViewById()等方法拿到目标控件的实例,也就无法使用Adapter。
这时候就需要使用RemoteViews.setRemoteAdapter(int viewId, Intent intent)方法设置,但这里用的是Intent,这个Intent指向一个RemoteViewsService(实现RemoteViewsFactory onGetViewFactory(Intent intent)方法)。
真实的Adapter就是RemoteViewsFactory实例了,通过其RemoteViews getViewAt(int position)方法完成item的UI以及逻辑的编写。
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); } } 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 mCount = 10; private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>(); private Context mContext; private int mAppWidgetId; public StackRemoteViewsFactory(Context context, Intent intent) { mContext = context; mAppWidgetId = 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 < mCount; i++) { mWidgetItems.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(mContext.getPackageName(), R.layout.widget_item); rv.setTextViewText(R.id.widget_item, mWidgetItems.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; } ... }
2.3 小部件配置Activity
如果希望用户在添加新的 App Widget 时配置一些自定义的设置,可以创建一个 App Widget 配置Activity,这时创建App Widget时不会调用 onUpdate() 方法。这个Activity 将自动启动,并允许用户在创建时配置 App Widget 的一些自定义设置,例如 App Widget 颜色、大小、更新周期或其他功能设置。
清单文件中添加:
<activity android:name=".ExampleAppWidgetConfigure"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity>
此外,必须在 AppWidgetProviderInfo XML 文件中声明 Activity,并带有 android:configure属性:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:configure="com.example.android.ExampleAppWidgetConfigure" ... > </appwidget-provider>
当 App Widget 使用配置 Activity 时,Activity 负责在配置完成后更新 App Widget:
Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); appWidgetManager.updateAppWidget(mAppWidgetId, views); Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish();
【注意】当你的配置Activity打开时,将Activity结果设置为RESULT_CANCELED。这样,如果用户在到达结束之前退出 Activity,则通知 App Widget 配置已取消,并且不会添加 App Widget。
完毕
今天的分享就到这里,文章多有不足,各位小伙伴有什么想法可以直接评论或是私信,要是对你有所帮助就给我一个赞吧,喜欢我的小伙伴可以关注我哦~
支持我的小伙伴们可以微信搜索“Android思维库”,或者微信扫描下方二维码,关注我的公众号,每天都会推送新知识~
-
Android widget 桌面插件
2013-01-17 09:53:26Android widget 桌面插件,详细源码, 详细注释 -
Android准确获取AppWidget桌面控件的宽高
2021-06-03 00:48:48由于自己桌面控件需要实现圆角+裁剪的图片显示,必须获取准确的AppWidget桌面控件宽高。查了百度一大堆资料都是不全的,然后上了下StackOverFlow终于找到答案并解决问题。class WidgetSizeProvider(private val ... -
Android之AppWidget(桌面小部件)开发浅析
2021-01-05 11:57:25AppWidget 即桌面小部件,也叫桌面控件,就是能直接显示在Android系统桌面上的小程序,先看图: 图中我用黄色箭头指示的即为AppWidget,一些用户使用比较频繁的程序,可以做成AppWidget,这样能方便地使用。典型... -
Android-Widget桌面小组件
2016-09-24 11:14:331.创建布局文件widget.xml http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orienta -
android widget 桌面便签程序源代码
2010-12-23 16:43:55android widget 开发实例 : 桌面便签程序的实现详解和源码。本资源为源代码部分,讲解部分见本人blog: http://blog.csdn.net/silenceburn/archive/2010/12/23/6093074.aspx -
Android widget 桌面组件开发
2015-04-02 11:48:47Widget是Android1.5版所引进的特性之一.Widget,可让用户在主屏幕界面及时了解程序显示的重要信息.标准的Android系统已包含几个Widget的示例,如模拟时钟,音乐播放器等. 1、AppWidget 框架类 1、AppWidgetProvider... -
Android的Widget桌面应用学习
2016-05-04 14:10:03Android的Widget,后台Service发送广播,固定时间更新 -
【J】Android-Widget桌面小组件
2015-12-20 17:16:571、Widget用途 2、特点和用法步骤 便捷方便、个性化可自定义功能、可及时控制更新Widget显示内容绘制Widget布局、配置Widget的基本属性、定义AppWidgetProvider、提供Configuration Activity ———后续更新—– -
Android自定义View之widget桌面小控件: 我可以控件时间啦
2017-01-26 14:43:56实现原理玩了这么久的安卓,一直想要自己的一个桌面小控件,显示时间、温度之类的,今天雅致来了,就学习了,今天就写在这里了。安卓桌面小控件大家都知道,即使源程序被用户抹杀掉、退出了程序,它依然在更新数据。... -
Android桌面组件App Widget完整案例
2020-09-03 07:51:46主要介绍了Android桌面组件App Widget完整案例,较为详细的分析了Android桌面组件App Widget的功能、定义及实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下 -
Android桌面插件App Widget用法分析
2020-09-01 21:03:12主要介绍了Android桌面插件App Widget用法,结合实例形式分析了桌面插件App Widget的功能、布局、创建及使用方法,需要的朋友可以参考下