<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          別羨慕蘋果的小部件了,安卓也有!

          共 15031字,需瀏覽 31分鐘

           ·

          2022-06-08 02:24


          ?安卓進階漲薪訓練營,讓一部分人先進大廠

          大家好,我是皇叔,最近開了一個安卓進階漲薪訓練營,可以幫助大家突破技術(shù)&職場瓶頸,從而度過難關(guān),進入心儀的公司。


          詳情見文章:皇叔的最新作來啦!


          作者:Zhujiang
          https://juejin.cn/user/3913917127985240

          來龍去脈

          2020年九月蘋果的 iOS 14 正式版本發(fā)布,其中的一項重大更新就是蘋果也支持小部件了!不容易啊,安卓好多年前擁有的功能現(xiàn)如今蘋果終于用上了,先來看看蘋果中的小部件樣式吧!


          蘋果的小部件的確不錯,還挺好看,但是安卓的其實也不差,前段時間寫了一個完全用 Compose 寫的天氣應(yīng)用:?從零到一寫一個完整的 Compose 版本的天氣(https://juejin.cn/post/7030986229512404999),想著蘋果的天氣小部件挺好用,給安卓也整一個吧!就有了今天的文章,來看看今天實現(xiàn)的最終效果吧:


          雖然安卓在很多年前就有了小部件,但小部件在安卓手機里的使用并不多,甚至可能說很少,最多也就是手機出廠的時候自帶的時間小部件。。。其實很多咱們常用應(yīng)用都有很多小部件,由于使用的確實不多,所以存在感很低(順帶吐槽下,常用的軟件都太流氓了,每個應(yīng)用都有一堆功能一樣的小部件,比如:抖音有好幾個、頭條也有好幾個、愛奇藝、優(yōu)酷等就不說了。。。)

          為什么安卓中的小部件很少人使用呢?主要還是樣式太丑,還有就是像上面說的那樣太流氓就不想用。Google 其實都快把小部件給忘記了,但去年讓蘋果給提了下醒,想起了安卓中還有小部件這個東西呢,于是痛定思痛,將小部件做了一些大的更新及升級。


          安卓小部件之痛


          其實不光使用者不喜歡用安卓的小部件,開發(fā)者也不想開發(fā)小部件,這是為什么呢?

          由于小部件是依附在桌面上的,所以并不屬于原本應(yīng)用的進程,而如果想要跨進程修改布局的話就需要使用到 RemoteViews ,但 RemoteViews 不能說是難用,那是相當難用,不僅不能使用自定義 View,連咱們常用的 RecyclerView 等控件都不能使用,只能使用官方固定的幾種控件,可以支持以下布局類:

          FrameLayout 、 LinearLayout 、 RelativeLayout 、GridLayout

          以及以下控件:AnalogClock(模擬時鐘)、Button 、Chronometer 、ImageButton 、ImageView 、ProgressBar 、TextView 、ViewFlipper 、ListView 、GridView 、StackView 、AdapterViewFlipper

          注:這塊的控件指的是 Android 12之前的,Android 12中新增了一些新的控件,在下面的部分中會有介紹。

          扯皮就先扯到這里吧,開始干活吧!

          Android 12小部件

          剛才也說過,Google 這次在 Android 12中對小部件更新很大,這塊來說下吧!


          用戶可重新設(shè)置原有小部件


          在之前,用戶如果想要重新設(shè)置小部件的話只能刪除了再重新添加,但是在 Android 12 中,用戶將無需通過刪除和重新添加 widget 來調(diào)整這些原有設(shè)定。


          設(shè)置方法其實很簡單,只需要添加一行配置:


          <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中新增的可重新設(shè)置小部件的配置項,另外一個是配置小部件的 Activity,想要使 widgetFeatures 起作用的話必須要配置 Activity,這很好理解,如果都不知道去哪配置小部件何談重新設(shè)置呢!


          小部件的尺寸限制


          在 Android 12之前,Android 中的小部件大小其實特別混亂,每個應(yīng)用在小部件中標柱的大小基本都是錯的,比如應(yīng)用寫的大小是 4 * 1 ,當你將頁面布局調(diào)整之后應(yīng)用大小就有可能發(fā)生變化,就不再是 4 * 1 的大小了。

          Google 有可能也知道這種情況,所以在 Android 12 中增加了小部件的尺寸限制,除了現(xiàn)有的 minWidth、minHeigh、minResizeWidth 以及 minResizeHeight 以外,還新增了新的 maxResizeWidth 、 maxResizeHeight 、 targetCellWidth 和 targetCellHeight 屬性,下面來具體說下新增的幾個屬性的含義。


          • maxResizeWidth:定義用戶所能夠調(diào)整的小部件尺寸的最大寬度
          • maxResizeHeight:定義用戶所能夠調(diào)整的小部件尺寸的最大高度
          • targetCellWidth:定義設(shè)備主屏幕上的小部件默認寬度所占格數(shù)(即使不同型號的手機中也會占定義好的格數(shù),但手機系統(tǒng)版本必須在 Android 12 及以上)
          • targetCellHeight:定義設(shè)備主屏幕上的小部件默認高度所占格數(shù)



          如果之前有 targetCellWidth 和 targetCellHeight 屬性的話,小部件也不至于像現(xiàn)在這么亂而導致用戶不想使用。


          新的小部件控件


          Android 12 使用以下現(xiàn)有控件新增了對有狀態(tài)行為的支持:

          • CheckBox
          • Switch
          • RadioButton



          上面這幾個控件大家應(yīng)該非常熟悉了,但在 Android 12 之前在小部件中想要使用的話也是不可能的。


          小部件UI更新


          這塊其實大家應(yīng)該都看過了,就一帶而過吧,就是為小部件默認添加了一個圓角,可以通過 system_app_widget_background_radius 和 system_app_widget_inner_radius 系統(tǒng)參數(shù)來設(shè)置微件圓角的半徑。

          這里來放一張官方文檔中的圖吧。

          干活了干活了

          上面叨叨了這么多,先是介紹了下小部件的前世今生,然后又說了下 Android 12中的更新內(nèi)容,終于要準備干活了。


          編寫配置文件


          • 在清單中聲明小部件


          如果想要在 Android 中添加一個小部件的話首先應(yīng)該在 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>

          元素需要 android:name 屬性,該屬性指定小部件使用的 AppWidgetProvider(AppWidgetProvider的父類就是BroadcastReceiver)。

          中的 元素指定小部件接受 ACTION_APPWIDGET_UPDATE 廣播。這是必須明確聲明的唯一一項廣播,用以接收小部件的增刪改等信息。

          元素指定小部件的資源,并且需要以下屬性:


          • android:name - 指定元數(shù)據(jù)名稱。必須使用 android.appwidget.provider 將數(shù)據(jù)標識為 AppWidgetProviderInfo 描述符。
          • android:resource - 指定 AppWidgetProviderInfo 資源位置。



          • 編寫小部件的配置文件


          上面在清單文件中聲明了小部件,下面來編寫下小部件的配置文件,根據(jù)上面的代碼可以看到這個配置文件放在了 xml 文件下,具體路徑為:res -> xml,如果本地沒有這個文件夾的話創(chuàng)建一個就好。


          <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"?/>


          可以看到這里已經(jīng)使用到了上面講的 Android 12中的新的配置,并且設(shè)置了最小的寬高,還有預覽圖片等等,下面來詳細看下每一項配置都是干啥的吧。


          • minWidth minHeight :指定小部件默認情況下占用的最小空間。
            注意:為使小部件能夠在設(shè)備間移植,小部件的最小大小不得超過 4 x 4 單元格。
          • minResizeWidthminResizeHeight:指定小部件的絕對最小大小。
          • updatePeriodMillis:定義小部件框架通過調(diào)用 onUpdate() 回調(diào)方法來從 AppWidgetProvider 請求更新的頻率應(yīng)該是多大。
          • initialLayout:指向用于定義小部件布局的布局資源。
          • configure:定義要在用戶添加小部件時啟動以便用戶配置小部件屬性的 Activity。
          • previewImage:指定預覽來描繪小部件經(jīng)過配置后是什么樣子的,用戶在選擇小部件時會看到該預覽。
          • autoAdvanceViewId :指定應(yīng)由小部件的托管應(yīng)用自動跳轉(zhuǎn)的小部件子視圖的視圖 ID。
          • resizeMode :指定可以按什么規(guī)則來調(diào)整微件的大小,可選值為“horizontal|vertical”,一般默認設(shè)置橫豎都可以進行調(diào)整。
          • minResizeHeight :指定可將微件大小調(diào)整到的最小高度。
          • minResizeWidth:指定可將微件大小調(diào)整到的最小寬度。
          • widgetCategory:聲明小部件是否可以顯示在主屏幕 (home_screen) 或鎖定屏幕 (keyguard) 上。只有低于 5.0 的 Android 版本才支持鎖定屏幕微件。對于 Android 5.0 及更高版本,只有 home_screen 有效,所以現(xiàn)在將這個值寫為home_screen即可。




          編寫布局


          • 根布局


          配置文件寫好了來編寫下布局吧,來考慮下布局應(yīng)該怎么寫,通過文章開頭的圖可以知道這是一個 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 ,同 ListView 和 GridView 一樣,StackView 也需要子布局,那就來吧。


          <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>

          由于篇幅原因?qū)⒉季纸o簡化了下,詳細布局可以看文末提供的項目源碼。


          包含集合小部件的清單


          由于咱們的布局中有 StackView ,包含集合的小部件除了上面中列出的要求之外,要使包含集合的小部件能夠綁定到 RemoteViewsService,還必須在清單文件中使用 BIND_REMOTEVIEWS 權(quán)限來聲明該服務(wù)。這樣可防止其他應(yīng)用自由訪問小部件的數(shù)據(jù)。

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


          包含集合小部件的 AppWidgetProvider 類


          與常規(guī)小部件一樣,AppWidgetProvider 子類中的大部分代碼通常都在 onUpdate() 中。在創(chuàng)建包含集合的小部件時,必須調(diào)用 setRemoteAdapter() 來設(shè)置適配器,這樣將告知集合視圖要從何處獲取其數(shù)據(jù)。

          然后,RemoteViewsService 可以返回 RemoteViewsFactory 實現(xiàn),并且微件可以提供適當?shù)臄?shù)據(jù)。當調(diào)用此方法時,必須傳遞指向 RemoteViewsService 實現(xiàn)的 Intent,以及指定要更新的小部件的小部件 ID,來看看具體實現(xiàn)吧。

          override?fun?onUpdate(
          ????context:?Context,
          ????appWidgetManager:?AppWidgetManager,
          ????appWidgetIds:?IntArray
          )
          ?{
          ????appWidgetIds.forEach?{?appWidgetId->
          ????????updateAppWidget(context,?appWidgetManager,?appWidgetId)
          ????????val?cityInfo?=?loadTitlePref(context,?appWidgetId)
          ????????//?設(shè)置布局
          ????????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?{
          ????????????//?設(shè)置?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
          ????????????)
          ????????}
          ????????//?設(shè)置點擊事件的模版
          ????????views.setPendingIntentTemplate(R.id.stack_view,?toastPendingIntent)
          ????????appWidgetManager.updateAppWidget(appWidgetId,?views)
          ????}
          }


          • RemoteViewsService實現(xiàn)


          上面說過,想要創(chuàng)建包含集合的小部件的話必須設(shè)置適配器,這里咱們就來實現(xiàn)下。

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

          可以看到 WeatherWidgetService 繼承自 RemoteViewsService ,并自己實現(xiàn)了 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)
          ????????????)
          ????????????//?設(shè)置點擊事件
          ????????????val?fillInIntent?=?Intent().apply?{
          ????????????????putExtra(EXTRA_ITEM,?weather.time)
          ????????????}
          ????????????setOnClickFillInIntent(R.id.widget_ll_item,?fillInIntent)
          ????????}
          ????}

          ????override?fun?getLoadingView():?RemoteViews?{
          ????????//?加載數(shù)據(jù)時的布局
          ????????return?RemoteViews(context.packageName,?R.layout.weather_widget_loading)
          ????}

          }

          上面編寫了 RemoteViewsFactory 的實現(xiàn),省略了一些不重要的方法,大家可以去源碼中進行查看。


          設(shè)置配置Activity


          配置 Activity 在上面咱們已經(jīng)說過如何添加到小部件的配置文件中,剩下的就和普通的 Activity 一樣了。

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

          @AndroidEntryPoint
          class?WeatherWidgetConfigureActivity?:?BaseActivity()?{

          ????private?val?viewModel?by?viewModels()

          ????public?override?fun?onCreate(savedInstanceState:?Bundle?)?{
          ????????super.onCreate(savedInstanceState)
          ????????//?刷新城市數(shù)據(jù)
          ????????viewModel.refreshCityList()
          ????????setContent?{
          ????????????PlayWeatherTheme?{
          ????????????????Surface(color?=?MaterialTheme.colors.background)?{
          ????????????????????ConfigureWidget(
          ????????????????????????viewModel,
          ????????????????????????onCancelListener?=?{
          ????????????????????????????setResult(RESULT_CANCELED)
          ????????????????????????????finish()
          ????????????????????????})?{?cityInfo?->
          ????????????????????????onConfirm(cityInfo)
          ????????????????????}
          ????????????????}
          ????????????}
          ????????}
          ????}

          這樣 Layout 布局咱們就不需要編寫了,下面來看下 ConfigureWidget的實現(xiàn)吧。

          @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、確定和取消按鈕,然后通過高階函數(shù)的方式將確定按鈕的點擊事件回調(diào)出去。

          遇到的坑

          OK,到這里本篇文章基本就算結(jié)束了,上面的這些一般在別的博客中都能搜到,但是重點來了,有很多東西網(wǎng)上是搜不到的,包括在官方文檔中寫的也是很籠統(tǒng),并沒有實際的應(yīng)用案例,下面就來詳細說一說吧。


          布局適配問題


          在蘋果中小部件的布局在添加的時候就固定好了,后面是不可以進行修改的,想要修改的話只能是刪除掉然后重新進行添加,但是在安卓中小部件的大小是可以進行拉伸的,長按即可進行寬高的調(diào)整,所以就難免出現(xiàn)布局適配的問題。


          • Android 12 之前的解決方案


          在 Android 12 之前如果想適配不同寬高下顯示不同布局的話需要重寫下 onAppWidgetOptionsChanged()?方法,然后從中獲取到當前小部件的最小寬高,根據(jù)寬高的不同就可以進行布局適配了。

          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)
          ????//?計算小部件的占的格數(shù)
          ????val?rows:?Int?=?getCellsForSize(minHeight)
          ????val?columns:?Int?=?getCellsForSize(minWidth)
          ????XLog.e("rows:$rows???columns:$columns")
          ????updateAppWidget(context,?appWidgetManager,?appWidgetId,?rows,?columns)
          }

          上面代碼中提到了一個 getCellsForSize()?方法,這個方法是根據(jù)官方文檔中寫的計算小部件格數(shù)的方法進行定義的,來看下吧:

          /**
          ?*?返回給定大小的小部件所需的單元格數(shù)。
          ?*
          ?*?@param?size 以 dp 為單位的小部件大小。
          ?*?@return?單元格數(shù)量的大小。
          ?*/

          fun?getCellsForSize(size:?Int):?Int?{
          ????var?n?=?2
          ????while?(70?*?n?-?30?????????++n
          ????}
          ????return?n?-?1
          }

          注意?。?!?這里所計算出的單元格數(shù)量不一定是正確的,在有的手機上可能沒問題,但一些手機上就有可能出問題,大家一定要注意,這也是沒辦法的事,手機廠商太多了,每個桌面的實現(xiàn)方式也略有不同,這事是正常的。


          • Android 12 之后的解決方案



          在 Android 12 之后,可以通過響應(yīng)式布局來進行適配,首先需要創(chuàng)建一組不同尺寸的布局,然后調(diào)用 updateAppWidget()?函數(shù),并傳入一組布局,當小部件尺寸發(fā)生變化時,系統(tǒng)會自動更改布局。

          val?viewMapping?=?mapOf(
          ????SizeF(150f,?110f)?to?RemoteViews(
          ????????context.packageName,
          ????????布局
          ????),
          ????SizeF(250f,?110f)?to?RemoteViews(
          ????????context.packageName,
          ????????布局
          ????),
          )

          //?指示小部件管理器更新小部件
          appWidgetManager.updateAppWidget(appWidgetId,?RemoteViews(viewMapping))

          這樣確實會簡單一些,相當于是 RemoteViews 內(nèi)部為我們做了處理,無需再重寫 onAppWidgetOptionsChanged()?方法了,但這樣的話只能在 Android 12 及之后的版本中進行使用,大家根據(jù)需求來使用吧。


          StackView 數(shù)據(jù)刷新問題


          這個問題是真的挺惡心,也有可能是我水平有限,官方給出的刷新是 notifyAppWidgetViewDataChanged() 方法,這塊搞的時候差點給我搞瘋。。。

          也是我自己的問題,人家都告訴刷新的流程了還寫的有問題。

          我之前是將天氣的數(shù)據(jù)請求放在 onCreate 方法中,然后通過 runBlocking() 方法將異步轉(zhuǎn)為同步,獲取到數(shù)據(jù)再執(zhí)行下一步,但這樣的話就會 anr。。

          然后我又寫了一個高階函數(shù):

          /**
          ?*?獲取之后一周的天氣
          ?*
          ?*?@param?context?/
          ?*?@param?cityInfo?需要獲取天氣的城市
          ?*?@param?onSuccessListener?獲取成功的回調(diào)
          ?*/

          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)
          ????????????}
          ????????})
          }

          獲取到數(shù)據(jù)的時候進行回調(diào),然后將數(shù)據(jù)進行賦值,但數(shù)據(jù)就是不刷新。。。

          也是太傻了,數(shù)據(jù)賦值完刷新下不就好了。。。

          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")
          ????}
          }

          這就可以了,再來放下官方的流程圖吧。



          桌面圖片顯示圓角



          這塊是為了展示天氣背景而出的問題,小部件中不支持自定義 View,所以就只能通過圖片本身了,需要將圖片加上圓角,這很簡單,網(wǎng)上一搜一大堆,但我設(shè)置完了之后并不是我想要的效果,我想要的是寬高一樣,這也簡單,加一行配置就行:

          android:scaleType="centerCrop"

          再次運行發(fā)現(xiàn)設(shè)置的圓角沒了。。。好吧,被切了,那只能先自己切成想要的大小,然后再添加圓角了。。。

          /**
          ?*?將普通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
          }

          這樣設(shè)置完再切圓角就沒問題了,最后再將圖片設(shè)置到 ImageView 中。

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

          github地址:https://github.com/zhujiang521/PlayWeather






          為了失聯(lián),歡迎關(guān)注我防備的小號



          ??微信改了推送機制,真愛請星標本公號??
          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  蜜桃传媒-熊猫成人网 | 少妇后入在线观看 | 欧美天堂在线观看 | 精品免费久久久久 | 午夜曝乳 |