精华内容
下载资源
问答
  • 如何取消添加小组件
    千次阅读
    2020-05-19 16:46:42

    背景:

    不想要2020/xx/xx/xxx/xx这样比较长的说明,想要简短一点的。

    解决:

    安装 hexo-abbrlink 插件

    npm install hexo-abbrlink
    

    编辑 站点的 _config.yml文件,找到 permalink , 改成 permalink: posts/:abbrlink/

    具体位置,可以参考下图:


    最后,在你写的文章 front部分,添加自定义 abbrlink 即可。

    地址栏变化,成功 ✿✿ヽ(°▽°)ノ✿

    更多相关内容
  • Android 12 小部件详解

    千次阅读 2021-12-03 10:52:31
    2020年九月苹果的 iOS 14 正式版本发布,其中的一项重大更新就是苹果也支持小部件了!不容易啊,安卓好多年前拥有的功能现如今苹果终于用上了,虽然苹果的小部件挺好看的,但是安卓也不差,快来看看吧!

    来龙去脉

    小部件兴起

    2020年九月苹果的 iOS 14 正式版本发布,其中的一项重大更新就是苹果也支持小部件了!不容易啊,安卓好多年前拥有的功能现如今苹果终于用上了,先来看看苹果中的小部件样式吧!

    天气地图等时钟提醒等
    image.pngimage.png

    苹果的小部件的确不错,还挺好看,但是安卓的其实也不差,前段时间写了一个完全用 Compose 写的天气应用:
    从零到一写一个完整的 Compose 版本的天气,想着苹果的天气小部件挺好用,给安卓也整一个吧!就有了今天的文章,来看看今天实现的最终效果吧:

    今天实现的样式可以上下滑动来查看一周的天气哦
    image.pngimage.png
    添加小部件.gif小部件效果.gif

    是不是也很炫酷,哈哈哈!这就是基于之前编写的天气应用写的(文末也有Github地址)。

    虽然安卓在很多年前就有了小部件,但小部件在安卓手机里的使用并不多,甚至可能说很少,最多也就是手机出厂的时候自带的时间小部件。。。其实很多咱们常用应用都有很多小部件,由于使用的确实不多,所以存在感很低(顺带吐槽下,常用的软件都太流氓了,每个应用都有一堆功能一样的小部件,比如:抖音有好几个、头条也有好几个、爱奇艺、优酷等就不说了。。。)

    为什么安卓中的小部件很少人使用呢?主要还是样式太丑,还有就是像上面说的那样太流氓就不想用。Google 其实都快把小部件给忘记了,但去年让苹果给提了下醒,想起了安卓中还有小部件这个东西呢,于是痛定思痛,将小部件做了一些大的更新及升级。

    安卓小部件之痛

    其实不光使用者不喜欢用安卓的小部件,开发者也不想开发小部件,这是为什么呢?由于小部件是依附在桌面上的,所以并不属于原本应用的进程,而如果想要跨进程修改布局的话就需要使用到 RemoteViews ,但 RemoteViews 不能说是难用,那是相当难用,不仅不能使用自定义 View,连咱们常用的 RecyclerView 等控件都不能使用,只能使用官方固定的几种控件,

    可以支持以下布局类:

    FrameLayoutLinearLayoutRelativeLayoutGridLayout

    以及以下控件:AnalogClock(模拟时钟)、ButtonChronometerImageButtonImageViewProgressBarTextViewViewFlipperListViewGridViewStackViewAdapterViewFlipper

    注:这块的控件指的是 Android 12之前的,Android 12中新增了一些新的控件,在下面的部分中会有介绍。

    扯皮就先扯到这里吧,开始干活吧!

    Android 12 中小部件的更新

    刚才也说过,Google 这次在 Android 12中对小部件更新很大,这块来说下吧!

    用户可重新设置原有小部件

    在之前,用户如果想要重新设置小部件的话只能删除了再重新添加,但是在 Android 12 中,用户将无需通过删除和重新添加 widget 来调整这些原有设定。

    image.png

    设置方法其实很简单,只需要添加一行配置:

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:configure="com.zj.weather.common.widget.WeatherWidgetConfigureActivity"
        android:widgetFeatures="reconfigurable" 
        ... />
    

    上面配置有两个,widgetFeatures 就是 Android 12中新增的可重新设置小部件的配置项,另外一个是配置小部件的 Activity,想要使 widgetFeatures 起作用的话必须要配置 Activity,这很好理解,如果都不知道去哪配置小部件何谈重新设置呢!

    小部件的尺寸限制

    在 Android 12之前,Android 中的小部件大小其实特别混乱,每个应用在小部件中标柱的大小基本都是错的,比如应用写的大小是 4 * 1 ,当你将页面布局调整之后应用大小就有可能发生变化,就不再是 4 * 1 的大小了。

    Google 有可能也知道这种情况,所以在 Android 12 中增加了小部件的尺寸限制,除了现有的 minWidthminHeighminResizeWidth 以及 minResizeHeight 以外,还新增了新的 maxResizeWidthmaxResizeHeighttargetCellWidthtargetCellHeight 属性,下面来具体说下新增的几个属性的含义。

    • maxResizeWidth:定义用户所能够调整的小部件尺寸的最大宽度
    • maxResizeHeight:定义用户所能够调整的小部件尺寸的最大高度
    • targetCellWidth:定义设备主屏幕上的小部件默认宽度所占格数(即使不同型号的手机中也会占定义好的格数,但手机系统版本必须在 Android 12 及以上)
    • targetCellHeight:定义设备主屏幕上的小部件默认高度所占格数

    如果之前有 targetCellWidthtargetCellHeight 属性的话,小部件也不至于像现在这么乱而导致用户不想使用。

    新的小部件控件

    Android 12 使用以下现有控件新增了对有状态行为的支持:

    上面这几个控件大家应该非常熟悉了,但在 Android 12 之前在小部件中想要使用的话也是不可能的。

    小部件UI更新

    这块其实大家应该都看过了,就一带而过吧,就是为小部件默认添加了一个圆角,可以通过 system_app_widget_background_radiussystem_app_widget_inner_radius 系统参数来设置微件圆角的半径。

    这里来放一张官方文档中的图吧。

    image.png

    干活了干活了

    上面叨叨了这么多,先是介绍了下小部件的前世今生,然后又说了下 Android 12中的更新内容,终于要准备干活了。

    编写配置文件

    在清单中声明小部件

    如果想要在 Android 中添加一个小部件的话首先应该在 AndroidManifest.xml 中进行声明,因为小部件实际上也是一个 BroadcastReceiver,大家都知道四大组件想要使用的话都需要在 AndroidManifest.xml 中进行声明,所以咱们先来在清单中声明小部件。

    <receiver
        android:name=".common.widget.WeatherWidget"
        android:exported="true" >
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
    ​
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/weather_widget_info" />
    </receiver>
    

    <receiver> 元素需要 android:name 属性,该属性指定小部件使用的 AppWidgetProvider(AppWidgetProvider的父类就是BroadcastReceiver)。

    <intent-filter> 中的 <action> 元素指定小部件接受 ACTION_APPWIDGET_UPDATE 广播。这是必须明确声明的唯一一项广播,用以接收小部件的增删改等信息。

    <meta-data> 元素指定小部件的资源,并且需要以下属性:

    • android:name - 指定元数据名称。必须使用 android.appwidget.provider 将数据标识为 AppWidgetProviderInfo 描述符。
    • android:resource - 指定 AppWidgetProviderInfo 资源位置。

    编写小部件的配置文件

    上面在清单文件中声明了小部件,下面来编写下小部件的配置文件,根据上面的代码可以看到这个配置文件放在了 xml 文件下,具体路径为:res -> xml,如果本地没有这个文件夹的话创建一个就好。

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:configure="com.zj.weather.common.widget.WeatherWidgetConfigureActivity"
        android:initialKeyguardLayout="@layout/weather_widget"
        android:initialLayout="@layout/weather_widget"
        android:minWidth="170dp"
        android:minHeight="90dp"
        android:previewImage="@mipmap/weather_widget"
        android:resizeMode="horizontal|vertical"
        android:targetCellWidth="3"
        android:targetCellHeight="2"
        android:updatePeriodMillis="86400000"
        android:widgetCategory="home_screen"
        android:widgetFeatures="reconfigurable" />
    

    可以看到这里已经使用到了上面讲的 Android 12中的新的配置,并且设置了最小的宽高,还有预览图片等等,下面来详细看下每一项配置都是干啥的吧。

    • minWidth 和 minHeight :指定小部件默认情况下占用的最小空间。

      注意:为使小部件能够在设备间移植,小部件的最小大小不得超过 4 x 4 单元格。

    • minResizeWidth和minResizeHeight:指定小部件的绝对最小大小。

    • updatePeriodMillis:定义小部件框架通过调用 onUpdate() 回调方法来从 AppWidgetProvider 请求更新的频率应该是多大。

    • initialLayout: 指向用于定义小部件布局的布局资源。

    • configure: 定义要在用户添加小部件时启动以便用户配置小部件属性的 Activity。。

    • previewImage: 指定预览来描绘小部件经过配置后是什么样子的,用户在选择小部件时会看到该预览。

    • autoAdvanceViewId :指定应由小部件的托管应用自动跳转的小部件子视图的视图 ID。

    • resizeMode :指定可以按什么规则来调整微件的大小,可选值为“horizontal|vertical”,一般默认设置横竖都可以进行调整。

    • minResizeHeight :指定可将微件大小调整到的最小高度。

    • minResizeWidth: 指定可将微件大小调整到的最小宽度。

    • widgetCategory:声明小部件是否可以显示在主屏幕 (home_screen) 或锁定屏幕 (keyguard) 上。只有低于 5.0 的 Android 版本才支持锁定屏幕微件。对于 Android 5.0 及更高版本,只有 home_screen 有效,所以现在将这个值写为home_screen即可。

    编写布局

    根布局

    配置文件写好了来编写下布局吧,来考虑下布局应该怎么写,通过文章开头的图可以知道这是一个 StackView ,那就先来写下根布局吧。

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#00000000"
        android:theme="@style/Theme.Design.NoActionBar">
    ​
        <StackView
            android:id="@+id/stack_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:loopViews="true" />
    ​
    </FrameLayout>
    

    子布局

    可以看到布局很简单,只放了一个 StackView,它继承自 AdapterViewAnimator ,同 ListViewGridView 一样,StackView 也需要子布局,那就来吧。

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/widget_ll_item">
    ​
        <ImageView
            android:id="@+id/widget_iv_bg"/>
    ​
        <LinearLayout>
    ​
            <TextView
                android:id="@+id/widget_tv_city" />
    ​
            <TextView
                android:id="@+id/widget_tv_date"/>
    ​
            <ImageView
                android:id="@+id/widget_iv_icon" />
    ​
            <ImageView
                android:id="@+id/widget_iv_small_icon" />
    ​
            <TextView
                android:id="@+id/widget_tv_temp" />
    ​
        </LinearLayout>
    ​
    </FrameLayout>
    

    由于篇幅原因将布局给简化了下,详细布局可以看文末提供的项目源码。

    包含集合小部件的清单

    由于咱们的布局中有 StackView ,包含集合的小部件除了上面中列出的要求之外,要使包含集合的小部件能够绑定到 RemoteViewsService,还必须在清单文件中使用 BIND_REMOTEVIEWS 权限来声明该服务。这样可防止其他应用自由访问小部件的数据。

    <service
        android:name=".common.widget.WeatherWidgetService"
        android:exported="false"
        android:permission="android.permission.BIND_REMOTEVIEWS" />
    

    包含集合小部件的 AppWidgetProvider 类

    与常规小部件一样,AppWidgetProvider 子类中的大部分代码通常都在 onUpdate() 中。在创建包含集合的小部件时,必须调用 setRemoteAdapter() 来设置适配器,这样将告知集合视图要从何处获取其数据。然后,RemoteViewsService 可以返回 RemoteViewsFactory 实现,并且微件可以提供适当的数据。当调用此方法时,必须传递指向 RemoteViewsService 实现的 Intent,以及指定要更新的小部件的小部件 ID,来看看具体实现吧。

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        appWidgetIds.forEach { appWidgetId->
            updateAppWidget(context, appWidgetManager, appWidgetId)
            val cityInfo = loadTitlePref(context, appWidgetId)
            // 设置布局
            val views = RemoteViews(context.packageName, R.layout.weather_widget)
            val intent = Intent(context, WeatherWidgetService::class.java).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            }
            views.apply {
                // 设置 StackView 适配器
                setRemoteAdapter(R.id.stack_view, intent)
                setEmptyView(R.id.stack_view, R.id.empty_view)
            }
            val toastPendingIntent: PendingIntent = Intent(
                context,
                WeatherWidget::class.java
            ).run {
                action = CLICK_ITEM_ACTION
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
                PendingIntent.getBroadcast(
                    context,
                    0,
                    this,
                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                )
            }
            // 设置点击事件的模版
            views.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent)
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
    }
    

    RemoteViewsService实现

    上面说过,想要创建包含集合的小部件的话必须设置适配器,这里咱们就来实现下。

    class WeatherWidgetService : RemoteViewsService() {
        override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
            return WeatherRemoteViewsFactory(this.applicationContext, intent)
        }
    }
    

    可以看到 WeatherWidgetService 继承自 RemoteViewsService ,并自己实现了 WeatherRemoteViewsFactory

    class WeatherRemoteViewsFactory(private val context: Context, intent: Intent) :
        RemoteViewsService.RemoteViewsFactory, CoroutineScope by MainScope() {
          
        private var cityInfo: CityInfo? = null
    ​
        init {
            intent.getStringExtra(CITY_INFO)?.apply {
                cityInfo = Gson().fromJson(this, CityInfo::class.java)
            }
        }
    ​
        override fun getViewAt(position: Int): RemoteViews {
            if (widgetItems.size != WEEK_COUNT) {
                return RemoteViews(context.packageName, R.layout.weather_widget_loading)
            }
            return RemoteViews(context.packageName, R.layout.widget_item).apply {
                val weather = widgetItems[position]
                setTextViewText(R.id.widget_tv_temp, "${weather.min}-${weather.max}℃")
                setTextViewText(
                    R.id.widget_tv_city,
                    "${cityInfo?.city ?: ""} ${cityInfo?.name ?: "北京"}"
                )
                setImageViewBitmap(
                    R.id.widget_iv_bg,
                    fillet(context = context, bitmap = zoomImg(context, weather.icon), roundDp = 10)
                )
                layoutAdapter(weather.icon)
                setTextViewText(R.id.widget_tv_date, weather.time)
                setImageViewResource(
                    R.id.widget_iv_icon,
                    IconUtils.getWeatherIcon(weather.icon)
                )
                // 设置点击事件
                val fillInIntent = Intent().apply {
                    putExtra(EXTRA_ITEM, weather.time)
                }
                setOnClickFillInIntent(R.id.widget_ll_item, fillInIntent)
            }
        }
    ​
        override fun getLoadingView(): RemoteViews {
            // 加载数据时的布局
            return RemoteViews(context.packageName, R.layout.weather_widget_loading)
        }
    ​
    }
    

    上面编写了 RemoteViewsFactory 的实现,省略了一些不重要的方法,大家可以去源码中进行查看。

    设置配置Activity

    配置 Activity 在上面咱们已经说过如何添加到小部件的配置文件中,剩下的就和普通的 Activity 一样了。

    由于小部件不支持 Compose ,所以上面咱们都是编写的 Layout ,但是在 Activity 中就可以使用 Compose 了!

    @AndroidEntryPoint
    class WeatherWidgetConfigureActivity : BaseActivity() {
    ​
        private val viewModel by viewModels<CityListViewModel>()
    ​
        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            // 刷新城市数据
            viewModel.refreshCityList()
            setContent {
                PlayWeatherTheme {
                    Surface(color = MaterialTheme.colors.background) {
                        ConfigureWidget(
                            viewModel,
                            onCancelListener = {
                                setResult(RESULT_CANCELED)
                                finish()
                            }) { cityInfo ->
                            onConfirm(cityInfo)
                        }
                    }
                }
            }
        }
    

    这样 Layout 布局咱们就不需要编写了,下面来看下 ConfigureWidget的实现吧。

    @OptIn(ExperimentalPagerApi::class)
    @Composable
    private fun ConfigureWidget(
        viewModel: CityListViewModel,
        onCancelListener: () -> Unit,
        onConfirmListener: (CityInfo) -> Unit
    ) {
        val cityList by viewModel.cityInfoList.observeAsState(arrayListOf())
        val buttonHeight = 45.dp
        val pagerState = rememberPagerState()
        Column(modifier = Modifier.fillMaxSize()) {
            Spacer(modifier = Modifier.height(80.dp))
            Text(
                text = "小部件城市选择",
                modifier = Modifier.fillMaxWidth(),
                textAlign = TextAlign.Center,
                fontSize = 26.sp,
                color = Color(red = 53, green = 128, blue = 186)
            )
            Box(modifier = Modifier.weight(1f)) {
                HorizontalPager(
                    state = pagerState,
                    count = cityList.size,
                    modifier = Modifier.fillMaxSize()
                ) { page ->
                    Card(
                        shape = RoundedCornerShape(10.dp),
                        backgroundColor = MaterialTheme.colors.onSecondary,
                        modifier = Modifier.size(300.dp)
                    ) {
                        val cityInfo = cityList[page]
                        Column(
                            verticalArrangement = Arrangement.Center,
                            horizontalAlignment = Alignment.CenterHorizontally,
                        ) {
                            Text(text = cityInfo.name, fontSize = 30.sp)
                        }
                    }
                }
                DrawIndicator(pagerState = pagerState)
            }
            Spacer(modifier = Modifier.height(50.dp))
            Divider(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(1.dp)
            )
            Row {
                TextButton(
                    modifier = Modifier
                        .weight(1f)
                        .height(buttonHeight),
                    onClick = {
                        onCancelListener()
                    }
                ) {
                    Text(
                        text = stringResource(id = R.string.city_dialog_cancel),
                        fontSize = 16.sp,
                        color = Color(red = 53, green = 128, blue = 186)
                    )
                }
                Divider(
                    modifier = Modifier
                        .width(1.dp)
                        .height(buttonHeight)
                )
                TextButton(
                    modifier = Modifier
                        .weight(1f)
                        .height(buttonHeight),
                    onClick = {
                        onConfirmListener(cityList[pagerState.currentPage])
                    }
                ) {
                    Text(
                        text = stringResource(id = R.string.city_dialog_confirm),
                        fontSize = 16.sp,
                        color = Color(red = 53, green = 128, blue = 186)
                    )
                }
            }
        }
    }
    

    看着代码多,其实布局很简单,一个线性布局包裹着标题、城市ViewPager、确定和取消按钮,然后通过高阶函数的方式将确定按钮的点击事件回调出去。

    遇到的坑

    OK,到这里本篇文章基本就算结束了,上面的这些一般在别的博客中都能搜到,但是重点来了,有很多东西网上是搜不到的,包括在官方文档中写的也是很笼统,并没有实际的应用案例,下面就来详细说一说吧。

    布局适配问题

    在苹果中小部件的布局在添加的时候就固定好了,后面是不可以进行修改的,想要修改的话只能是删除掉然后重新进行添加,但是在安卓中小部件的大小是可以进行拉伸的,长按即可进行宽高的调整,所以就难免出现布局适配的问题。

    Android 12 之前的解决方案

    在 Android 12 之前如果想适配不同宽高下显示不同布局的话需要重写下 onAppWidgetOptionsChanged() 方法,然后从中获取到当前小部件的最小宽高,根据宽高的不同就可以进行布局适配了。

    override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int,
        newOptions: Bundle
    ) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
        // See the dimensions and
        val options = appWidgetManager.getAppWidgetOptions(appWidgetId)
        // 获取小部件最小的宽高
        val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
        val minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
        // 计算小部件的占的格数
        val rows: Int = getCellsForSize(minHeight)
        val columns: Int = getCellsForSize(minWidth)
        XLog.e("rows:$rows   columns:$columns")
        updateAppWidget(context, appWidgetManager, appWidgetId, rows, columns)
    }
    

    上面代码中提到了一个 getCellsForSize() 方法,这个方法是根据官方文档中写的计算小部件格数的方法进行定义的,来看下吧:

    /**
     * 返回给定大小的小部件所需的单元格数。
     *
     * @param size 以 dp 为单位的小部件大小。
     * @return 单元格数量的大小。
     */
    fun getCellsForSize(size: Int): Int {
        var n = 2
        while (70 * n - 30 < size) {
            ++n
        }
        return n - 1
    }
    

    注意!!! 这里所计算出的单元格数量不一定是正确的,在有的手机上可能没问题,但一些手机上就有可能出问题,大家一定要注意,这也是没办法的事,手机厂商太多了,每个桌面的实现方式也略有不同,这事是正常的。

    Android 12 之后的解决方案

    在 Android 12 之后,可以通过响应式布局来进行适配,首先需要创建一组不同尺寸的布局,然后调用 updateAppWidget() 函数,并传入一组布局,当小部件尺寸发生变化时,系统会自动更改布局。

    val viewMapping = mapOf(
        SizeF(150f, 110f) to RemoteViews(
            context.packageName,
            布局
        ),
        SizeF(250f, 110f) to RemoteViews(
            context.packageName,
            布局
        ),
    )
    ​
    // 指示小部件管理器更新小部件
    appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(viewMapping))
    

    这样确实会简单一些,相当于是 RemoteViews 内部为我们做了处理,无需再重写 onAppWidgetOptionsChanged() 方法了,但这样的话只能在 Android 12 及之后的版本中进行使用,大家根据需求来使用吧。

    StackView 数据刷新问题

    这个问题是真的挺恶心,也有可能是我水平有限,官方给出的刷新是 notifyAppWidgetViewDataChanged() 方法,这块搞的时候差点给我搞疯。。。

    也是我自己的问题,人家都告诉刷新的流程了还写的有问题。

    我之前是将天气的数据请求放在 onCreate 方法中,然后通过 runBlocking() 方法将异步转为同步,获取到数据再执行下一步,但这样的话就会 anr。。

    然后我又写了一个高阶函数:

    /**
     * 获取之后一周的天气
     *
     * @param context /
     * @param cityInfo 需要获取天气的城市
     * @param onSuccessListener 获取成功的回调
     */
    fun getWeather7Day(
        context: Context,
        cityInfo: CityInfo?,
        onSuccessListener: (MutableList<WeekWeather>) -> kotlin.Unit
    ) {
        QWeather.getWeather7D(context, getLocation(cityInfo = cityInfo),
            getDefaultLocale(context), Unit.METRIC,
            object : QWeather.OnResultWeatherDailyListener {
                override fun onError(e: Throwable) {
                    XLog.e("getWeather7Day1 onError: $e")
                    showToast(context, e.message)
                }
    ​
                override fun onSuccess(weatherDailyBean: WeatherDailyBean?) {
                    onSuccessListener(weatherDailyBean.daily)
                }
            })
    }
    

    获取到数据的时候进行回调,然后将数据进行赋值,但数据就是不刷新。。。

    也是太傻了,数据赋值完刷新下不就好了。。。

    private fun notifyWeatherWidget(
        context: Context,
        appWidgetId: Int
    ) {
        WeatherWidgetUtils.getWeather7Day(context = context, cityInfo = cityInfo) { items ->
            // 赋值
            widgetItems = items
            val mgr = AppWidgetManager.getInstance(context)
            // 刷新 
            mgr.notifyAppWidgetViewDataChanged(
                appWidgetId,
                R.id.stack_view
            )
            XLog.e(TAG, "init: $widgetItems")
        }
    }
    

    这就可以了,再来放下官方的流程图吧。

    image.png

    桌面图片显示圆角

    这块是为了展示天气背景而出的问题,小部件中不支持自定义 View,所以就只能通过图片本身了,需要将图片加上圆角,这很简单,网上一搜一大堆,但我设置完了之后并不是我想要的效果,我想要的是宽高一样,这也简单,加一行配置就行:

    android:scaleType="centerCrop"
    

    再次运行发现设置的圆角没了。。。好吧,被切了,那只能先自己切成想要的大小,然后再添加圆角了。。。

    /**
     * 将普通Bitmap按照centerCrop的方式进行截取
     */
    fun zoomImg(bm: Bitmap): Bitmap {
        val w = bm.width // 得到图片的宽,高
        val h = bm.height
        val retX: Int
        val retY: Int
        val wh = w.toDouble() / h.toDouble()
        val nwh = w.toDouble() / w.toDouble()
        if (wh > nwh) {
            retX = h * w / w
            retY = h
        } else {
            retX = w
            retY = w * w / w
        }
        val startX = if (w > retX) (w - retX) / 2 else 0 //基于原图,取正方形左上角x坐标
        val startY = if (h > retY) (h - retY) / 2 else 0
        val bit = Bitmap.createBitmap(bm, startX, startY, retX, retY, null, false)
        bm.recycle()
        return bit
    }
    

    这样设置完再切圆角就没问题了,最后再将图片设置到 ImageView 中。

    setImageViewBitmap(
        R.id.widget_iv_bg,
        fillet(context = context, bitmap = zoomImg(context, weather.icon), roundDp = 10)
    )
    

    打完收工

    大家可以购买我的新书《Jetpack Compose:Android全新UI编程》进行阅读,里面有完整的 Compose 框架供大家学习。

    京东购买地址

    当当购买地址

    呸呸呸,太不要脸了,又在推荐自己的新书。。。

    说了这么多还没放 Github 地址呢:https://github.com/zhujiang521/PlayWeather

    如果你在学习或者想要学习关于 Compose 的话,亦或是想学习安卓的小部件,这个项目应该或多或少会对你有点帮助,如果对你有帮助的话,别忘记点个 Star,感激不尽。

    其实还有一些细节的东西我没有说到,大家如果有疑问的话可以在评论区提出来。

    先写到这里吧,再会!

    展开全文
  • Qt添加组件

    千次阅读 2021-12-15 14:56:39
    Qt添加组件,使用MaintenanceTool.exe,如添加QtCharts组件,也可以移除组件,

    很多人在安装Qt的时候,为了节省空间而只选择几个必要的组件,比如MSVC系列的编译器。安装完成后,暗自高兴,心想:“又节省了不少空间,舒服的很”。然而,后面做项目的时候,或者跑别人的程序时,发现IDE找不到头文件,自己去翻源目录也找不到。苦逼。。。无奈地面对事实,缺少组件。

    1. 开发环境

    Win10+Qt5.14.2,缺少QtCharts组件

    2. 安装步骤

    2.1 找到安装目录,我的在D盘,D:\Qt\Qt5.14.2,里面有个MaintenanceTool.exe

     2.2 双击运行,可以先断网进入到  维护  页面,在左下角有个设置,可以添加临时存储库,返回维护界面,选择添加或移除组件,报错。。。选择更新组件,即更新MaintenanceTool.exe。老版的界面就是大家之前看到的,下面是新的。挺坑的,必须先登录才能继续。

    更新组件后,现在变成中文界面了(2022.09.21)

     

    2.3 勾选

    2.4 在自己安装的Qt版本下勾选需要增加的组件,点击Next。(本来我是已经安装msvc2017 64-bit的,然后瞎玩全选,全取消勾选,玩了几次,最后只勾选QtCharts,然后竟然在我不知情的情况下把我的编译器卸载了,吐了。。。随后系统识别不到了这个编译器了,找了一下发现被删除了,第二次只能勾选上了,重新装了)(仔细看说明,勾选的将被安装,不被勾选的将被卸载,真的吐了)

     2.5 点击Update

     2.6 等待下载完成即可

     3. 请大家谨慎操作,看清说明再采取行动。

    展开全文
  • 只需单击浏览器窗口右上角的“选项”并取消选中您不需要的小部件。 哦,对于速度迷(像我们一样),所有数据都将通过瞬态存储和提供。 这样插件就不会影响您的仪表板加载时间! 工具 #1一般 主机名 服务器IP ...
  • 提供给flutter滚动组件小部件可下拉刷新并拉起load.support android和ios。 如果您是中国人,请单击此处() : 特征 拉负载并拉刷新 它几乎适合所有Scroll组件,例如GridView,ListView ... 提供默认指标和属性...
  • 微信程序picker组件设置默认(单个picker情况) 需求描述:默认显示"请选择",当点击picker后弹出的才是选项内容 看图1和图2: 实现思路描述: 首先data{}中添加一个初始变量,在用户选择这个组件的时候,通过...

    微信小程序picker组件设置默认(单个picker情况)

    需求描述:默认显示"请选择",当点击picker后弹出的才是选项内容

    看图1和图2:
    图1
    图2

    实现思路描述:

    首先data{}中添加一个初始变量,在用户选择这个组件的时候,通过一个三目运算,给picker组件中的value属性赋值,去判断这个变量的值,为null时,就显示请选择;为其他时,显示选项。

    实现方式:

    <!--wxml代码-->
    <picker bindchange="bindYearPickerChange" value="{{yearIndex}}" range="{{yearArr}}">
    	<view>
    		{{seleNull?yearArr[yearIndex]:'请选择年份'}}
    	</view>
    </picker>
    
    //js代码
    page({
    	data: {
    	   seleNull:null,//设置的变量
    	   yearArr: ['2020', '2019', '2018'],//选项的值
    	   yearIndex: 0   //选项的索引
    	},
    	bindYearPickerChange: function(e) {
    	    console.log(this.data.yearArr[e.detail.value])
    	    this.setData({
    		    seleNull:'0',
    		    yearIndex: e.detail.value
    	    })
      	}
    })
    

    本篇文章为个人见解,如果各位大佬有更好的解决方式欢迎评论指出,谢谢~~~~~~~~

    以上是在页面中只有一个picker组件时,默认显示请选择的解决方法,下一篇我会发出在页面中有多个picker组件时,要显示不同的请选择(请选择省份、请选择市、请选择区)的解决方法


    好了,结束了。
    在这里插入图片描述

    展开全文
  • action-sheet组件是从底部弹出可选菜单项,估计也是借鉴IOS的设计添加的,action-sheet有两个子组件, action-sheet-item为每个选项,action-sheet-cancel取消选项,与action-sheet-item中间会有间隔,并且点击会...
  • QT组件添加、更新、移除

    千次阅读 2020-09-03 16:45:57
    添加组件的过程如下: 进入QT安装目录,打开MaintenanceTool 点击设置; 点击自来哦档案库,添加临时资料档案库。 由于国内网络环境原因,这里使用清华镜像。根据自己安装的版本添加,可以在镜像站里找到自己安装的...
  • 一、legend 取消点击事件 legend:{ selectedMode:false,//取消图例上的点击事件 ...图例组件用户切换图例开关会触发该事件。 myChart.on('legendselectchanged', function(params) { var option = this....
  • Übersicht 的最小天气小部件什么是新的该小部件现在使用新的 Geolocation API 来自动查找您的位置。 需要 Übersicht 0.5 或更高版本才能工作。安装在index.coffee添加您自己的 forecast.io api 密钥。 你可以在...
  • JAVAFX在线程中动态添加或删除组件

    千次阅读 2018-10-26 14:33:36
    JAVAFX在自定义线程中动态添加或删除组件会报错,需要在JAVAFX的线程中来操作这些组件,具体方式就是把操作组件的代码放入如下所示代码中 Platform.runLater(new Runnable() { public void run() { //操作...
  • Android使用addView动态添加组件

    万次阅读 多人点赞 2016-05-08 22:51:36
    在项目开发中,我们经常需要进行动态添加组件,其中可添加的部分有两项:布局和组件  其中,添加的布局主要有RelativeLayout型(相对布局)的和LinearLayout(线性布局)  添加的组件主要有文本显示框,编辑框,...
  • 【微信程序|组件库】自定义加载动画(1)

    千次阅读 热门讨论 2021-04-30 15:56:12
    在编写微信程序的代码时,我们会发现:原生加载动画样式不多,可供我们设置的参数也不多。         通过查询可以得知,若是需要自己定义加载动画,一般的做法就是:在...
  • Android App Widget桌面小部件开发实践

    千次阅读 2021-08-16 11:05:55
    App Widget桌面小部件已经被很多APP所使用,在日常的生活中也有很多人在使用,最常见的就是时钟、天气、日历、记事本这样的工具,但是不少主流APP也提供了一些快捷功能的小部件,例如音乐快捷播放、代办事项、课程...
  • 首先是不管是添加还是编辑,都需要将子组件需要的对象属性一一写出来,传给子组件, 然后是主要用到了el-form表单有一个清空重置表单数据的事件方法resetField(),在子组件表单弹窗打开的时候清空一下,在关闭子组件...
  • 微信程序弹窗组件

    千次阅读 2021-01-27 10:26:30
    最近在开发程序应用, 发现程序当中有关于组件的介绍非常的少, 当前自己做的项目当中,有出现过这种情况, 所以自己就封装了一个程序的弹窗组件, 现在把自己的心得分享给大家, 大家一起来学习吧~~ 效果图 需求...
  • 『可组合的Vue』别样的“小组件”设计

    千次阅读 多人点赞 2022-01-08 23:48:42
    最近在设计笔者所在组自己的组件库。从设计上看,一个组件库是否“成功”取决于前期的设计 —— 我决定用上ITCSS模型。为这个组件的团队维护、扩展打下坚实的基础。 但这还不够,组件库最重要的组件往往是非大型库...
  • vue中动态添加和删除组件缓存 keep-alive

    千次阅读 热门讨论 2019-10-30 16:09:07
    Vue的抽象组件,自身不会渲染一个DOM元素,也不会出现在父组件链中,能将组件在切换过程中将状态保存在内存中,防止重复渲染DOM;包裹动态组建时,会缓存不活动的组件实例,而不是销毁它们;当组件在 内被切换时,它...
  • 设置networking.widget Übersicht 小部件可在静态 IP 和 DHCP 之间切换,... 使用小部件更改设置时,系统会要求您取消密码,我认为没有办法解决这个问题。 要与小部件交互,请按住您在 Ubersicht 首选项中指定的热键。
  • Whig.js Whig.js是用于HTML textarea元素的纯JavaScript所见即所得文本编辑器小部件。 Whig编辑器提供以下功能: 大胆的。 强调。 斜体。 无序列表。 有序列表。 缩进。 非缩进(Outdent)。 对齐文本中心。 左对齐...
  • 一.... const app = getApp() 引入全局app.js <button bindtap="addCart">添加</button> data:{ id:'', image:'', title:'' } addCart(){ const obj = {} goods.id = this.id goods.image =this.image...
  • 有人会问:既然要取消选中为什么不用checkbox呢? 举个栗子,比如选中性别时,用户可以选男或者女(二选一),然后也可以取消选中(二者都不选) 这时这个demo就派上用场了 <!-- 如果直接@click会触发两次 ...
  • 使用uni-app官方搜索组件可快速搭建 输入框 通过官方组件提供的搜索组件以及自定义结构如下 效果: 设置背景方法二(在官方组件内设置) 在找到官方组件 效果(一样) 二、 input事件处理 在input组件中,输入的值...
  • 如何在微信程序使用组件插槽slot slot:用于在不破坏组件的情况下,给组件嵌入结构的一种方式 slot在程序中使用时分为两种情况: 1.当一个组件只有一个slot插槽的时候 //使用<slot></slot>的标签放到...
  • 主要难点:出现报错,最终只显示安装的组件,无法添加组件 最终解决过程 01、运行MaintenanceTool(Qt的安装路径下) 02、选择设置 03、选择资料档案库→临时资料档案库→添加→填写地址(正对资料档案库...
  • PickerView添加确定,取消按钮

    千次阅读 2016-05-03 13:39:59
    PickerView添加确定,取消按钮 txtfield点击出现pickerview 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ...
  • Widget是iOS 14的新特性,要创建小组件,需要使用到SwiftUI和WidgetKit。要在项目中创建Widget,首先点击此处的加号图标,添加一个target。 点击右侧的垂直滚动条,查看Widget扩展所在的位置。 选择此处的...
  • 程序——picker组件

    千次阅读 2021-01-15 22:08:07
    程序自带的picker组件,picker一共分为三类 普通选择器:mode = selector range:下拉列表的数据,为一个obj或array。只有当mode=selector或者 mode= multiSelector时有效。 range-key:当range为对象时,range-...
  • 在安装完Qt后,面对不同的需求可能会发现需要一些组件但没有安装,或者有些组件没有太大用处,这时候,我们就可以将组件添加或者移除。具体步骤如下: 打开安装的qt的文件夹,找到MaintenanceTool,双击打开。 ...
  • 不知道是不是我理解有问题,看了vant的组件库,他的文档是这样说...补充知识:修改 vant 弹窗Dialog 组件调用是确认按钮与取消按钮的文字 Props 有关props 两个属性 confirm-button-text 与 cancel-button-text 。 co

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 132,980
精华内容 53,192
热门标签
关键字:

如何取消添加小组件