Android widget 小部件使用指南强化版

Android widget 小部件使用指南强化版

  • 一、简单UI的小部件
  • 二、含集合的小部件
  • 三、可配置的小部件
  • 四、可控制的小部件
  • 五、Android 12 Widget 更新

小部件是主屏幕定制的一个重要方面。您可以将它们视为应用程序最重要的数据和功能的“概览”视图,这些数据和功能可以直接在用户的主屏幕上访问。用户可以在主屏幕面板上移动小部件,如果支持的话,还可以调整它们的大小以根据自己的喜好定制小部件中的信息量。
请添加图片描述

一、简单UI的小部件

此类小部件通常仅显示关键信息元素,布局简单。小部件属于RemoteViews,常用的控件是支持的,如TextView、Images,但是不支持自定义的控件,具体参考:创建应用微件布局

  1. 声明AppWidgetProviderInfoXML:
    定义了小部件的基本品质。AppWidgetProviderInfo使用单个元素在 XML 资源文件中 定义对象<appwidget-provider>并将其保存在项目的res/xml/文件夹中。
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:initialLayout="@layout/all_note_appwidget_layout"android:minWidth="150dp"android:minHeight="54dp"android:previewImage="@drawable/todo_appwidget_preview"android:resizeMode="none"android:updatePeriodMillis="86400000"android:widgetCategory="home_screen" />
  • initialLayout:指向定义小部件布局的布局资源
  • previewImage:在选择应用小部件时看到的预览图
  • updatePeriodMillis:小部件更新频率,实际更新不完全按时进行
  • widgetCategory:声明您的小部件是否可以显示在主屏home_screen、锁定屏幕keyguard 或两者上
  1. 在清单中声明一个小部件:AndroidManifest.xml中声明小部件信息
<receiverandroid:name=".widget.AllNoteAppWidgetProvider"android:label="@string/all_notes"android:exported="true"><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <!-- 必须 --><action android:name="com.android.note.widget.UPDATE_ALL_NOTE"/> <!-- 自定义 --></intent-filter><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/all_note_appwidget_info" />
</receiver>
  • receiver:指定AppWidgetProvider小部件
  • intent-filter:指定接受 AppWidgetProvider广播。ACTION_APPWIDGET_UPDATE 这是您必须显式声明的唯一广播。根据需要自动 AppWidgetManager 将所有其他小部件广播发送到AppWidgetProvider。还可以自定义其他广播用于更新小部件的数据视图
  • meta-data:android:name指定元数据名称,用于 android.appwidget.provider将数据标识为 AppWidgetProviderInfo描述符。android:resource指定AppWidgetProviderInfo资源位置。
  1. 使用AppWidgetProvider来处理小部件广播
    类AppWidgetProvider扩展 BroadcastReceiver为一个便利类来处理小部件广播,它仅接收与小部件相关的事件广播,例如小部件何时更新、删除、启用和禁用。所以我们重写AppWidgetProvider处理小部件广播并更新小部件以响应小部件生命周期事件。
public class AllNoteAppWidgetProvider extends AppWidgetProvider {public static final String UPDATE_ALL_NOTE_COUNT_ACTION = "com.android.note.widget.UPDATE_ALL_NOTE";@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {// 当添加相同的小部件时,需要全部遍历更新for (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}}@Overridepublic void onReceive(Context context, Intent intent) {// 接收到自定义的广播,用于做处理后更新小部件if (UPDATE_ALL_NOTE_COUNT_ACTION.equals(intent.getAction())) {final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context,this.getClass()));for (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}} else {super.onReceive(context, intent);}}static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {// 小部件的布局,实际就是RemoteViewsRemoteViews views = new RemoteViews(context.getPackageName(), R.layout.all_note_appwidget_layout);//做数据处理NoteDataManager manager = NoteDataManagerImpl.getInstance(context);int size = manager.getNotesForPresetFolder(NoteDataManagerImpl.ALL_NOTE_FOLDER).size();// 更新视图,作为RemoteViews,只能使用其通过的方法来设置值views.setTextViewText(R.id.size, context.getString(R.string.widget_note_count, size));// 添加一个跳转应用的意图Intent composeIntent = new Intent(context, NoteEditorActivity.class);composeIntent.putExtra(NoteEditorPresenter.OPEN_TYPE, 1);composeIntent.putExtra(FolderUtil.KEY_ID, 0);composeIntent.putExtra(FolderUtil.KEY_FILTER_TYPE, 0);PendingIntent composeNoteIntent = PendingIntent.getActivity(context, WidgetUtil.getUniqueCode(),composeIntent, PendingIntent.FLAG_IMMUTABLE);views.setOnClickPendingIntent(R.id.btn_add, composeNoteIntent);Intent intent = new Intent(context, MainActivity.class);intent.putExtra(Constants.KEY_DEFAULT_VIEW, Constants.TAG_NOTE);PendingIntent pendingIntent = PendingIntent.getActivity(context, WidgetUtil.getUniqueCode(),intent, PendingIntent.FLAG_MUTABLE);views.setOnClickPendingIntent(R.id.header, pendingIntent);// 通过 appWidgetManager 更新视图appWidgetManager.updateAppWidget(appWidgetId, views);}// 用于外部调用更新小部件public static void updateCount(Context context) {context.sendBroadcast(new Intent(UPDATE_ALL_NOTE_COUNT_ACTION, null, context, AllNoteAppWidgetProvider.class));}
}
  • onReceive(Context, Intent):接收来自小部件的广播或者自定义的广播,以便来做一些操作。
  • onUpdate():更新小部件调用此方法。当用户添加小部件时也会调用此方法,因此它应该执行基本设置,例如为 View对象定义事件处理程序或启动作业来加载要在小部件中显示的数据。
  1. 需要更新小部件时的更新方法
AllNoteAppWidgetProvider.updateCount(context);
  1. 效果参考
    在这里插入图片描述

二、含集合的小部件

集合小部件专门用于显示相同类型的许多元素,例如来自图库应用程序的图片集合、来自新闻应用程序的文章或来自通信应用程序的消息。集合小部件通常专注于两个用例:浏览集合以及将集合的元素打开到其详细视图。集合小部件可以垂直滚动。小部件使用以下视图类型之一呈现数据,这些视图类型称为集合视图:

  • ListView:显示垂直滚动列表中的项目的视图。
  • GridView:显示二维滚动网格中的项目的视图。
  • StackView:堆叠卡片视图(有点像名片盒),用户可以向上或向下轻拂前面的卡片以分别查看上一张或下一张卡片。
  • AdapterViewFlipper:一个由适配器支持的简单动画 ViewAnimator,可以在两个或多个视图之间进行动画处理。一次只显示一个孩子。

由于这些集合视图由适配器支持,因此 Android 框架必须包含额外的架构来支持它们在小部件中的使用。在小部件的上下文中,Adapter被替换为 RemoteViewsFactory,它是界面的薄包装Adapter。当请求集合中的特定项目时,RemoteViewsFactory将创建集合的项目并将其作为对象返回 RemoteViews。要在您的小部件中包含集合视图,请实现RemoteViewsServiceRemoteViewsFactory

关于创建小部件的大部分配置同简单UI小部件操作,关于集合视图的小部件其他操作步骤如下:

  1. 小部件的配置的initialLayout中含有集合视图,如ListView
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/app_widget_background"android:orientation="vertical"android:padding="16dp"><ListViewandroid:id="@+id/note_list"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@+id/widget_header"android:listSelector="@drawable/transparent_selector"android:scrollbars="none" /></RelativeLayout>
  1. AndroidManifest.xml中声明RemoteViewsService
<serviceandroid:name=".widget.NoteListWidgetService"android:exported="true"android:permission="android.permission.BIND_REMOTEVIEWS" />
  • permission:通过在清单文件中使用 权限 声明该服务来执行此操作 BIND_REMOTEVIEWS,可以防止其他应用程序随意访问您的小部件的数据。
  1. 实现RemoteViewsServiceRemoteViewsFactory
public class NoteListWidgetService extends RemoteViewsService {private static final String TAG = "NoteListWidgetService";public NoteListWidgetService() {super();}@Overridepublic IBinder onBind(Intent intent) {return super.onBind(intent);}@Overridepublic RemoteViewsFactory onGetViewFactory(Intent intent) {// 创建 RemoteViewsFactoryreturn new ListViewRemoteViewsFactory(this, intent);}private static class ListViewRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {private Context mContext;private int mAppWidgetId;private ArrayList<NoteItem> mItems = new ArrayList<>();private AppWidgetManager mAppWidgetManager;public ListViewRemoteViewsFactory(Context context, Intent intent) {mContext = context;// 获取传递来的 AppWidgetIdmAppWidgetId =intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}@Overridepublic void onCreate() {// 加载数据NoteDataManager mManager = NoteDataManagerImpl.getInstance(mContext);mItems = (ArrayList)mManager.getNotesForPresetFolder(2);}@Overridepublic void onDataSetChanged() {// 更新数据NoteDataManager mManager = NoteDataManagerImpl.getInstance(mContext);mItems = (ArrayList) mManager.getNotesForPresetFolder(2);}@Overridepublic void onDestroy() {mItems.clear();}@Overridepublic int getCount() {return Constants.WIDGET_LIST_COUNT;}@Overridepublic RemoteViews getViewAt(int i) {int size = mItems.size();if (i < size) {NoteItem item = mItems.get(i);Date date = new Date(item.getLongDate());// 创建的ListView项的第一种布局类型RemoteViews itemView = new RemoteViews(mContext.getPackageName(), R.layout.widget_listview_item_note);itemView.setTextViewText(R.id.note_time, DateUtil.getDisplayNoteTime(mContext, date));itemView.setTextViewText(R.id.note_title, NoteUtils.parseContent(mContext, item.getContent(), true));// 点击跳转应用,及传递参数,这里没有使用PendingIntent,而是在AppWidgetProvider中//通过setPendingIntentTemplate创建了模板,这里只需要Intent即可Intent fillInIntent = new Intent();fillInIntent.putExtra(FolderUtil.KEY_ID, item.getId());fillInIntent.putExtra(NoteEditorPresenter.OPEN_TYPE, NoteEditorPresenter.TYPE_EDIT_NOTE);itemView.setOnClickFillInIntent(R.id.listview_linearlayout, fillInIntent);return itemView;}//这里属于ListView项的第二种布局类型return new RemoteViews(mContext.getPackageName(), R.layout.app_widget_note_placeholder);}@Overridepublic RemoteViews getLoadingView() {return null;}@Overridepublic int getViewTypeCount() {// 布局类型的数目return 2;}@Overridepublic long getItemId(int i) {return i;}@Overridepublic boolean hasStableIds() {return false;}}
}

关于 RemoteViewsService.RemoteViewsFactory:类似于Adapter的使用

  • onCreate:首次加载,提供数据
  • onDataSetChanged:后续更新,提供新数据
  • onDestroy:清除数据
  • getCount:ListView显示数据的条数
  • getViewAt:返回ListView每项的视图,数据和界面更新的操作都在这里
  • getViewTypeCount:ListView项的视图种类的个数,用于实现不同的布局,至少返回1,否则有数据也不显示,显示正在加载中
  • getItemId:返回当前项的位置
  1. AppWidgetProvider更新时候调用
// 通过 NoteListWidgetService 更新,需要传递appWidgetId
Intent serviceIntent = new Intent(context, NoteListWidgetService.class);
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetIds[i]);
views.setRemoteAdapter(R.id.note_list, serviceIntent);// 创建 PendingIntent 模板
// 但是不支持添加多个PendingIntent 模板,那就意味着所有数据交互都只能走这一个PendingIntent
Intent itemIntent = new Intent(context, NoteEditorActivity.class);
PendingIntent listPendingIntent = PendingIntent.getActivity(context, 1,itemIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
views.setPendingIntentTemplate(R.id.note_list, listPendingIntent);// 通过appWidgetManager更新 ListView,调用 RemoteViewsFactory 的 onDataSetChanged
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds[i], R.id.note_list);
  1. 效果参考:默认显示3条数据,若无,则使用空白占位
    在这里插入图片描述

三、可配置的小部件

如果您想让用户配置您的小部件的设置,请创建小部件配置Activity,此活动由应用程序小部件主机在创建小部件时或稍后自动启动,具体取决于您指定的配置选项。

  • 应用程序小部件宿主调用配置活动,并且配置活动必须始终返回结果。结果必须包括启动该活动的意图传递的应用程序小部件 ID - 在意图附加中保存为 EXTRA_APPWIDGET_ID.
  • 启动配置活动时系统不会发送广播 ,这意味着创建小部件时ACTION_APPWIDGET_UPDATE 不会调用该方法。onUpdate()配置活动有责任在AppWidgetManager第一次创建小部件时请求更新。但是, onUpdate()会在后续更新中调用 - 仅在第一次时跳过。

此类小部件可以根据配置来显示不同用的小部件内容。

关于创建小部件的大部分配置同简单UI小部件操作,关于可配置的小部件其他操作步骤如下:

  1. 声明配置Activity:在 AndroidManifest.xml 中将配置活动声明为普通Activity
<activity android:name=".widget.FolderAppWidgetConfigure"android:exported="true"><intent-filter><action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/></intent-filter>
</activity>
  • intent-filter:应用程序小部件主机通过 ACTION_APPWIDGET_CONFIGURE 操作启动它,因此活动需要接受此意图
  1. AppWidgetProviderInfo.xml使用属性声明配置的Activity
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:configure="com.android.note.widget.FolderAppWidgetConfigure"android:initialLayout="@layout/folder_appwidget_layout"android:minWidth="250dp"android:minHeight="110dp"android:previewImage="@drawable/todo_appwidget_preview"android:resizeMode="none"android:updatePeriodMillis="86400000"android:widgetCategory="home_screen" />
  • android:configure:必须配置才可以生效
  1. 小部件Activity配置的具体实现
public class ExampleAppWidgetConfigure extends Activity {static final String TAG = "ExampleAppWidgetConfigure";private static final String PREFS_NAME= "com.example.android.apis.appwidget.ExampleAppWidgetProvider";private static final String PREF_PREFIX_KEY = "prefix_";int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;EditText mAppWidgetPrefix;public ExampleAppWidgetConfigure() {super();}@Overridepublic void onCreate(Bundle icicle) {super.onCreate(icicle);// 设置Activity布局,可以当作一个普通Activity来操作即可,例如设置文本、设置点击事件等setContentView(R.layout.appwidget_configure);mAppWidgetPrefix = (EditText)findViewById(R.id.appwidget_prefix);findViewById(R.id.save_button).setOnClickListener(mOnClickListener);// 必须:从intent获取 widget idIntent intent = getIntent();Bundle extras = intent.getExtras();if (extras != null) {mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);}// 如果 widget id 无效则放弃if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {finish();}mAppWidgetPrefix.setText(loadTitlePref(ExampleAppWidgetConfigure.this, mAppWidgetId));}View.OnClickListener mOnClickListener = new View.OnClickListener() {public void onClick(View v) {final Context context = ExampleAppWidgetConfigure.this;// 点击确定按钮时,根据AppWidgetId保存一些我们需要的信息到prefs中// 你可以保存更多您需要的信息,用于后续更新小部件的内容String titlePrefix = mAppWidgetPrefix.getText().toString();saveTitlePref(context, mAppWidgetId, titlePrefix);// 使用新设置的前缀将小部件更新推送到视图上AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);ExampleAppWidgetProvider.updateAppWidget(context, appWidgetManager,mAppWidgetId, titlePrefix);// 必须:需要返回接受到的原始的appWidgetId// 你可以传递更多的值,用于后续更新小部件的内容Intent resultValue = new Intent();resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);setResult(RESULT_OK, resultValue);finish();}};// 将此前缀写入该小部件的 SharedPreferences 对象static void saveTitlePref(Context context, int appWidgetId, String text) {SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();prefs.putString(PREF_PREFIX_KEY + appWidgetId, text);prefs.commit();}// 从该小部件的 SharedPreferences 对象中读取前缀。如果没有保存首选项,则从资源获取默认值static String loadTitlePref(Context context, int appWidgetId) {SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);String prefix = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null);if (prefix != null) {return prefix;} else {return context.getString(R.string.appwidget_prefix_default);}}
}
  • EXTRA_APPWIDGET_ID:必须接收从intent中的AppWidgetId,最后完成配置后也必须返回此AppWidgetId
  • SharedPreferences存值:我们需要用appWidgetId 作为key,以便添加多个小部件时不混淆
  1. 允许用户重新配置放置的小部件

默认情况下,应用程序小部件主机仅在用户将小部件添加到主屏幕后立即启动配置活动一次。但是,您可以指定选项,使用户能够重新配置现有小部件或通过提供默认小部件配置来跳过初始小部件配置。

要让用户重新配置现有小部件,需要在widgetFeatures中指定标志 reconfigurable

<appwidget-providerandroid:configure="com.myapp.ExampleAppWidgetConfigurationActivity"android:widgetFeatures="reconfigurable">
</appwidget-provider>
  • reconfigurable标志:在 Android 9(API 级别 28)中引入的,但直到 Android 12 才得到广泛支持。

四、可控制的小部件

控制小部件的主要目的是显示常用功能,以便用户可以从主屏幕触发它们,而无需打开应用程序。例如待办事项的复选框的,与元素交互,将它们标记为完成,由于从Android 12(API 级别 31)才开始支持这种复合按钮:CheckBoxSwitchRadioButton
在这里插入图片描述
而在 Android 12 之前我们也可以实现,没有可以通过使用ImageView来达到这种效果,RemoteViews 提供了 setImageViewResource 用于更新ImageView资源,我们只需要准备2张图片资源即可。

RemoteViews itemView = new RemoteViews(mContext.getPackageName(), R.layout.widget_listview_item_todo);
itemView.setImageViewResource(R.id.todo_check, item.isComplete() ? R.drawable.todo_on : R.drawable.todo_off);

然而这仅仅是更新了小部件的UI,我们如何更新UI背后的数据逻辑呢? 这里是有方案,但是有缺陷,由于待办事项属于ListView布局,而本身具备点击事件,点击跳转到对应的待办内容详情页(Activity),因此需要在AppWidgetProvider中设置PendingIntent 模板,但在我们需要更新待办完成数据时,就还需要利用这个PendingIntent 模板,如下:

Intent intent = new Intent();
intent.setAction(TodoAppWidgetProvider.CHECK_TODO_ACTION);
intent.putExtra(TodoAppWidgetProvider.KEY_ITEM_CHECK_ID, item.getId());
itemView.setOnClickFillInIntent(R.id.todo_check, intent);

这个时候可以在Activity中完成数据的更新操作,但是会跳转到Activity中,这个是我们不想要的,所以跳转后就需要finish此Activity,但实际测试仍会出现闪一下Activity的现象,这就是缺陷,使用子线程仍会出现。

if (action != null && action.equals(TodoAppWidgetProvider.CHECK_TODO_ACTION)) {updateTodoWidget(intent);finish();return;
}private void updateTodoWidget(Intent intent) {new Thread(new Runnable() {@Overridepublic void run() {int id = intent.getIntExtra(TodoAppWidgetProvider.KEY_ITEM_CHECK_ID, 0);TodoItem todoItem = mNoteDataManager.getTodoItem(id);// 这里省略了对此todoItem的一些数据操作,使其完成或未完成if (mNoteDataManager.updateTodo(todoItem) > 0) {TodoAppWidgetProvider.updateWidget(mContext);}}}).start();
}

五、Android 12 Widget 更新

Google官方文档:[1] Android 12 微件改进 [2] AppWidget 示例 - Android 12 - Kotlin

  • 实现圆角:Android 12 中的微件采用圆角设计。将应用微件用在搭载 Android 12 或更高版本的设备上时,启动器会自动识别微件的背景,并将其剪裁成具有圆角。
  • 添加设备主题:微件可以为按钮、背景及其他组件使用设备主题颜色,包括浅色主题和深色主题。
  • 使微件的个性化设置更容易:使用 appwidget-provider 的 configure 属性指定配置 activity,添加了一些新选项
  • 添加新的复合按钮:CheckBoxSwitchRadioButton
  • 使用改进的 API 设置微件大小和布局
  • 改进应用的微件选择器体验
  • 实现更流畅的过渡
  • 使用简化的 RemoteViews 集合:Android 12 添加了 setRemoteAdapter(int viewId, RemoteViews.RemoteCollectionItems items) 方法,可让您的应用在填充 ListView 时直接传递集合。
  • 使用 RemoteViews 的运行时修改:Android 12 添加了几个 RemoteViews 方法,可用于在运行时修改 RemoteViews 属性。

相关参考:
[1] https://developer.android.google.cn/develop/ui/views/appwidgets/overview
[2] https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/appwidget

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/143934.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

第十章_祖冲之_圆周率

倒数1又2/3章&#xff0c;keep_writting的一天&#xff1a; 第十章10.1.7 运行程序资源下载网站为何打不开呢&#xff1f;

Linux socket 字节序

socket介绍 字节序 验证什么字节序 #include<stdio.h> int main() {union {short value;char btypes[sizeof(short)];} test;test.value 0x0102;if(test.btypes[0] 1 && test.btypes[1] 2) {printf("大端字节序\n");}else{printf("小端字节序…

JVM111

JVM1 字节码与多语言混合编程 字节码 我们平时说的java字节码&#xff0c; 指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为:jvm字节码。不同的编译器&#xff0c;可以编译出相同的字节码文件&#xff0c;字节码文件…

DataExcel控件读取和保存excel xlsx 格式文件

需要引用NPOI库 https://github.com/dotnetcore/NPOI 调用Read 函数将excel读取到dataexcel控件 调用Save 函数将dataexcel控件文件保存为excel文件 using NPOI.HSSF.UserModel; using NPOI.HSSF.Util; using NPOI.SS.UserModel; using NPOI.SS.Util; using System; using …

canvas-绘图库fabric.js简介

一般情况下简单的绘制&#xff0c;其实canvas原生方法也可以满足&#xff0c;比如画个线&#xff0c;绘制个圆形、正方形、加个文案。 let canvas document.getElementById(canvas);canvas.width 1200;canvas.height 600;canvas.style.width 1200px;canvas.style.height 6…

【算法基础】栈和队列及常见变种与使用,双栈、动态栈、栈的迭代器,双端队列、优先队列、并发队列、延迟队列的使用

目录 一、栈&#xff08;Stack&#xff09; 二、 队列&#xff08;Queue&#xff09; 三、栈和队列的常见变种与使用 3.1 栈的常见的变种与使用 3.1.1 最小栈&#xff08;Min Stack&#xff09; 3.1.2 双栈&#xff08;Two Stacks&#xff09; 3.1.3 固定大小栈&#xf…

eclipse svn插件安装

1.进入eclipse的help->Eclipse Marketplace,如下图所示&#xff1a; 2.输入“svn”,再按回车&#xff0c;如下图&#xff1a; 3.这我选择的是 Subversive,点击后面的“install”按钮&#xff0c;如下图 Eclipse 下连接 SVN 库有两种插件 —— Subclipse 与 Subversive &…

开源C# Winform Scada 上位机系统

开源Winform Scada系统 功能展示C#源码程序说明下载程序源码获取 功能展示 本软件目前包含: 常用PLC通讯控件, 常用IO读写控件, 权限过滤, 用户管理, 日志记录, 报警记录. 使用方式: 在VS2022里面拖放控件, 填写控件属性,完成组态.即可成为一个完整的上位机. C#源码 程序说明…

弱信号的采样与频谱分析(修订中...)

1.频谱混叠效应 - 波形数据抽样 这是一组经过抽样的数据的频谱&#xff0c;红圈圈出的两条谱线&#xff0c;是我们需要关注的特征谱线。这个信号与右侧的临近信号比较&#xff0c;求频率比值&#xff0c;比值恒定与理论推导相符。再5取1降低采样率后&#xff0c;大致相同的频率…

宝塔nginx搭建Ftp文件服务器

一&#xff1a;创建FTP 填入账号密码后&#xff0c;选择根目录&#xff0c;这个根目录就是nginx要代理的目录 二&#xff1a;配置nginx root的地址就是上面填的FTP根目录 三&#xff1a;http访问 服务器ip端口号加图片 例如我放了一个320.jp 我服务器ip是110.120.120.120 那…

使用MySQL聚合函数来聚合数据,结果发现有刺客...

问题&#xff1a; 使用MySQL聚合函数 group_concat 的坑&#xff01; 现象&#xff1a; 我有个业务&#xff0c;需要将表中符合条件的数据行的id聚合成一个字符串&#xff0c;以供另外一张表的查询过滤。 SELECTx FROMt_A WHEREFIND_IN_SET(guan_lian,(SELECTgroup_concat( i…

iOS自动化测试方案(一):MacOS虚拟机保姆级安装Xcode教程

文章目录 一、环境准备二、基础软件三、扩展&#xff1a;usb拓展插件 一、环境准备 1、下载VMware虚拟机的壳子&#xff0c;安装并注册软件(可以百度注册码)&#xff0c;最新版本&#xff1a;v17 2、下MacOS系统iOS镜像文件&#xff0c;用于vmware虚拟机安装&#xff0c;当前镜…

Unity WebSocket-Server

&#x1f33c;WebSocket-Server &#x1f96a;效果展示&#x1f32d;启动Server&#x1f371;连接Server &#x1f96a;效果展示 在Unity中创建WebSocket服务器&#xff0c;从网页连接到该服务器进行消息通信&#xff0c;在Unity中接收到的消息都在主线程中 &#x1f32d;启…

RK3588 VDD_NPU电源PCB设计注意事项

1、VDD_NPU的覆铜宽度需满足芯片的电流需求&#xff0c;连接到芯片电源管脚的覆铜足够宽&#xff0c;路径不能被过孔分割太严重&#xff0c;必须计算有效线宽&#xff0c;确认连接到CPU每个电源PIN脚的路径都足够。 2、VDD_NPU的电源在外围换层时&#xff0c;要尽可能的多打电…

计算机,软件工程,网络工程,大数据专业毕业设计选题有哪些(附源码获取)

计算机&#xff0c;软件工程&#xff0c;网络工程&#xff0c;大数据专业毕业设计选题有哪些?&#xff08;附源码获取&#xff09; ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于J…

纽禄美卡Neuromeka亮相美国FABTECH,展示用于焊接的3D视觉协作机器人

原创 | 文 BFT机器人 纽禄美卡Neuromeka公司在由美国精密成型协会、美国焊接协会、化工涂料协会等5大协会举办的美国金属加工及焊接展览会FABTECH上精彩亮相。这家总部位于韩国首尔的公司成立于2013年&#xff0c;是机器人解决方案领域的领先供应商&#xff0c;致力于提高各种…

超大表格组件滚动渲染优化

引用自 摸鱼wiki 背景 业务中需要渲染一个最多有100列的表格&#xff0c;由于表格使用原生dom实现&#xff0c;因此会出现同屏有近1000个单元格同时绘制&#xff0c;在快速滑动时页面会产生卡顿&#xff0c;影响用户体验。 方案 如下图所示&#xff0c;由于用户显示屏区域有…

现代数据中心发明人Luiz André Barroso去世,享年59岁,Jeff Dean、劈柴发推悼念

Luiz Andr Barroso因故去世&#xff0c;享年59岁。他作为现代云计算行业的奠基人&#xff0c;为谷歌的发展做出了不可磨灭的贡献。 数据中心发明人&#xff0c;云计算的奠基人&#xff0c;谷歌22年老兵Luiz Andr Barroso于9月16日意外去世&#xff0c;享年59岁。 谷歌CEO 劈柴…

Docker——认识并安装Docker(上篇)

Docker 一、Docker认识二、Docker功能1、更快速的交付和部署2、更高效的虚拟化3、更轻松的迁移和扩展4、更简单的管理Docker 和 VM 三、学习Docker前的必备知识1、环境配置2、虚拟化部署方式3、虚拟化优点4、虚拟化局限性5、容器与虚拟机的区别6、Docker为什么比VM快&#xff1…

TikTok营销成功秘籍:ROI指标的黄金法则

在当今数字营销领域&#xff0c;TikTok已经崭露头角&#xff0c;成为了品牌和营销者们争相追逐的热门平台。 然而&#xff0c;要在TikTok上取得成功&#xff0c;不仅需要创意和内容&#xff0c;还需要精确的ROI&#xff08;投资回报率&#xff09;指标来衡量和优化你的营销策略…