<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

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

    共 15031字,需瀏覽 31分鐘

     ·

    2022-06-08 02:24


    ?安卓進(jìn)階漲薪訓(xùn)練營,讓一部分人先進(jìn)大廠

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


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


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

    來龍去脈

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


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


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

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


    安卓小部件之痛


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

    由于小部件是依附在桌面上的,所以并不屬于原本應(yīng)用的進(jìn)程,而如果想要跨進(jìn)程修改布局的話就需要使用到 RemoteViews ,但 RemoteViews 不能說是難用,那是相當(dāng)難用,不僅不能使用自定義 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)用在小部件中標(biāo)柱的大小基本都是錯的,比如應(yīng)用寫的大小是 4 * 1 ,當(dāng)你將頁面布局調(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è)備主屏幕上的小部件默認(rèn)寬度所占格數(shù)(即使不同型號的手機(jī)中也會占定義好的格數(shù),但手機(jī)系統(tǒng)版本必須在 Android 12 及以上)
    • targetCellHeight:定義設(shè)備主屏幕上的小部件默認(rèn)高度所占格數(shù)



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


    新的小部件控件


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

    • CheckBox
    • Switch
    • RadioButton



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


    小部件UI更新


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

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

    干活了干活了

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


    編寫配置文件


    • 在清單中聲明小部件


    如果想要在 Android 中添加一個小部件的話首先應(yīng)該在 AndroidManifest.xml 中進(jìn)行聲明,因為小部件實際上也是一個 BroadcastReceiver,大家都知道四大組件想要使用的話都需要在 AndroidManifest.xml 中進(jìn)行聲明,所以咱們先來在清單中聲明小部件。

    <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ù)標(biāo)識為 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è)置了最小的寬高,還有預(yù)覽圖片等等,下面來詳細(xì)看下每一項配置都是干啥的吧。


    • minWidth minHeight :指定小部件默認(rèn)情況下占用的最小空間。
      注意:為使小部件能夠在設(shè)備間移植,小部件的最小大小不得超過 4 x 4 單元格。
    • minResizeWidthminResizeHeight:指定小部件的絕對最小大小。
    • updatePeriodMillis:定義小部件框架通過調(diào)用 onUpdate() 回調(diào)方法來從 AppWidgetProvider 請求更新的頻率應(yīng)該是多大。
    • initialLayout:指向用于定義小部件布局的布局資源。
    • configure:定義要在用戶添加小部件時啟動以便用戶配置小部件屬性的 Activity。
    • previewImage:指定預(yù)覽來描繪小部件經(jīng)過配置后是什么樣子的,用戶在選擇小部件時會看到該預(yù)覽。
    • autoAdvanceViewId :指定應(yīng)由小部件的托管應(yīng)用自動跳轉(zhuǎn)的小部件子視圖的視圖 ID。
    • resizeMode :指定可以按什么規(guī)則來調(diào)整微件的大小,可選值為“horizontal|vertical”,一般默認(rèn)設(shè)置橫豎都可以進(jìn)行調(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簡化了下,詳細(xì)布局可以看文末提供的項目源碼。


    包含集合小部件的清單


    由于咱們的布局中有 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),并且微件可以提供適當(dāng)?shù)臄?shù)據(jù)。當(dāng)調(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è)置點(diǎn)擊事件的模版
    ????????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è)置點(diǎn)擊事件
    ????????????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),省略了一些不重要的方法,大家可以去源碼中進(jì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)
    ????????????????)
    ????????????}
    ????????}
    ????}
    }

    看著代碼多,其實布局很簡單,一個線性布局包裹著標(biāo)題、城市ViewPager、確定和取消按鈕,然后通過高階函數(shù)的方式將確定按鈕的點(diǎn)擊事件回調(diào)出去。

    遇到的坑

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


    布局適配問題


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


    • Android 12 之前的解決方案


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

    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ù)的方法進(jìn)行定義的,來看下吧:

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

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

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


    • Android 12 之后的解決方案



    在 Android 12 之后,可以通過響應(yīng)式布局來進(jìn)行適配,首先需要創(chuàng)建一組不同尺寸的布局,然后調(diào)用 updateAppWidget()?函數(shù),并傳入一組布局,當(dāng)小部件尺寸發(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))

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


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


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

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

    我之前是將天氣的數(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ù)的時候進(jìn)行回調(diào),然后將數(shù)據(jù)進(jìn)行賦值,但數(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"

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

    /**
    ?*?將普通Bitmap按照centerCrop的方式進(jìn)行截取
    ?*/

    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坐標(biāo)
    ????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)注我防備的小號



    ??微信改了推送機(jī)制,真愛請星標(biāo)本公號??
    瀏覽 44
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報
    評論
    圖片
    表情
    推薦
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

    分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    高清无码影音 | 囯产精品久久久久久 | 大香蕉97| 中文天堂在线观看 | 国产无码精彩视频 |