<bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>

    Android 桌面小組件使用

    原文: Android 桌面小組件使用-Stars-One的雜貨小窩

    借助公司上的幾個項目,算是學習了Android桌面小組件的用法,記下踩坑記錄

    基本步驟

    1.創建小組件布局

    這里需要注意的事,小組件布局里不能使用自定義View,只能使用原生的組件,比如說LinearLayout,TextView,連約束布局都不能使用

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:orientation="vertical"
        android:padding="16dp">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/tvDate"
                style="@style/textStyle14"
                android:textColor="#313131"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="2023-12-10" />
    
            <ImageView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1" />
    
            <TextView
                android:id="@+id/tvTime"
                android:textColor="#313131"
                style="@style/textStyle14"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="12:10" />
    
        </LinearLayout>
    
        <LinearLayout
            android:layout_marginTop="16dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/result_clean"/>
    
            <LinearLayout
                android:orientation="vertical"
                android:layout_width="0dp"
                android:layout_marginStart="9dp"
                android:gravity="center_vertical"
                android:layout_height="match_parent"
                android:layout_weight="1" >
                <TextView
                    style="@style/textStyle14"
                    android:textColor="#313131"
                    android:textStyle="bold"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="125.4MB"/>
                <TextView
                    style="@style/textStyle14"
                    android:textColor="#313131"
                    android:textStyle="bold"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Junk"/>
            </LinearLayout>
    
            <TextView
                android:layout_gravity="center_vertical"
                android:id="@+id/tvClean"
                android:textColor="#313131"
                style="@style/textStyle14"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Clean" />
    
        </LinearLayout>
    
    </LinearLayout>
    

    2.創建provider

    import android.appwidget.AppWidgetManager
    import android.appwidget.AppWidgetProvider
    import android.content.Context
    import android.widget.RemoteViews
    import android.widget.RemoteViews.RemoteView
    import ten.jou.recover.R
    
    class CleaningWidget : AppWidgetProvider() {
        override fun onUpdate(
            context: Context,
            appWidgetManager: AppWidgetManager,
            appWidgetIds: IntArray
        ) {
            appWidgetIds.forEach {
    			//如果小組件布局中使用不支持的組件,這里創建RemoteViews時候,IDE會報紅提示!
                val remoteView = RemoteViews(context.packageName, R.layout.widget_layout)
    			//綁定數據
    			remoteView.setTextViewText(R.id.tv1,"hello world")
                appWidgetManager.updateAppWidget(it, remoteView)
            }
    
        }
    }
    

    AppWidgetProvider本質就是一個廣播接收器,所以在清單文件需要聲明(見步驟4)

    這里先補充下,RemoteViews對于TextView,ImageView等View,有設置文本,字體顏色,圖片等相關方法,但并不是所有方法都支持,綁定數據的時候需要注意下小組件是否支持!

    3.創建xml屬性聲明

    在xml文件夾里新建widget_info.xml文件:

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:targetCellWidth="4"
        android:targetCellHeight="2"
        android:minWidth="250dp"
        android:minHeight="110dp"
        android:updatePeriodMillis="0"
        android:initialLayout="@layout/widget_layout"
        tools:targetApi="s">
    </appwidget-provider>
    

    Android12版本以上新增的2個屬性,聲明組件是4*2大小

    • targetCellWidth
    • targetCellHeight

    4.清單文件聲明

    <receiver
    	android:name=".view.CleaningWidget"
    	android:enabled="true"
    	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/widget_info" />
    </receiver>
    

    5.代碼添加小組件

    官方說Android12不允許直接通過代碼添加小組件,只能讓用戶手動去桌面拖動添加,但是我手頭的三星系統卻是支持的(也是Android12),具體還沒有細究...

    而官方文檔上的寫的例子如下:

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    	val context = this@DesktopWidgetActivity
    
    	val appWidgetManager: AppWidgetManager =
    		context.getSystemService(AppWidgetManager::class.java)
    	val myProvider = ComponentName(context, CleaningWidget::class.java)
    
    	//判斷啟動器是否支持小組件pin
    	val successCallback = if (appWidgetManager.isRequestPinAppWidgetSupported) {
    		// Create the PendingIntent object only if your app needs to be notified
    		// that the user allowed the widget to be pinned. Note that, if the pinning
    		// operation fails, your app isn't notified.
    		Intent(context, CleaningWidget::class.java).let { intent ->
    			// Configure the intent so that your app's broadcast receiver gets
    			// the callback successfully. This callback receives the ID of the
    			// newly-pinned widget (EXTRA_APPWIDGET_ID).
    			//適配android12的
    			val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    				PendingIntent.FLAG_MUTABLE
    			} else {
    				PendingIntent.FLAG_UPDATE_CURRENT
    			}
    			PendingIntent.getBroadcast(
    				context,
    				0,
    				intent,
    				flags
    			)
    		}
    	} else {
    		null
    	}
    
    	appWidgetManager.requestPinAppWidget(myProvider, null, successCallback)
    }
    

    這里提下,上面的設置flags方法

    val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    	PendingIntent.FLAG_MUTABLE
    } else {
    	PendingIntent.FLAG_UPDATE_CURRENT
    }
    

    有個新項目的targetSdk為34(即Android14),如果使用上面的代碼會出現下面崩潰錯誤提示

    Targeting U+ (version 34 and above) disallows creating or retrieving a PendingIntent with FLAG_MUTABLE, an implicit Intent within and without FLAG_NO_CREATE and FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT for security reasons. To retrieve an already existing PendingIntent, use FLAG_NO_CREATE, however, to create a new PendingIntent with an implicit Intent use FLAG_IMMUTABLE.
    

    實際上提示已經告訴我們怎么去改代碼了,我這里把PendingIntent.FLAG_MUTABLE改為FLAG_IMMUTABLE就不會出現了上述的崩潰問題

    應該是Android14添加的限制:

    • 如果Intent不傳數據,必須使用PendingIntent.FLAG_IMMUTABLE
    • 如果是需要傳遞數據,則還是需要使用PendingIntent.FLAG_MUTABLE

    定時刷新小組件UI

    首先,我們得知道,如何主動去更新數據:

    val context = it.context
    val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
    val myProvider = ComponentName(context, CleaningWidget::class.java)
    val remoview = CleaningWidget.getRemoteViewTest(context)
    
    //更新某類組件
    appWidgetManager.updateAppWidget(myProvider,remoview)
    //更新具體某個組件id
    appWidgetManager.updateAppWidget(widgetId,remoview)
    

    getRemoteViewTest方法就是創建一個remoteview,然后調用remoteview相關方法設置文本之類的進行數據填充,代碼就略過不寫了,詳見上述基本步驟2

    上面的方法我們注意到updateAppWidget可以傳不同的參數,一般我們用的第二個方法,指定更新某個組件

    但這里又是需要我們傳一個組件id,所以就是在步驟2的時候,我們根據需要需要存儲下widgetId比較好,一般存入數據庫,或者使用SharePreference也可

    然后,就是對于定時的情況和對應方案:

    1. 如果是間隔多長更新一次,可以使用開一個服務,在服務中開啟協程進行
    2. 如果是單純的時間文本更新,可以使用TextClock組件,比如說 12:21這種
    3. 小組件的xml中默認可以設置定時更新時長,不過最短只能需要15分鐘
    4. 可以使用鬧鐘服務AlarmManager來實現定時,不過此用法需要結合pendingintent和廣播接收器使用,最終要在廣播接收器里調用更新數據方法
    5. JobScheduler來實現定時更新,似乎受系統省電策略影響,適用于不太精確的定時事件(官方文檔上推薦這個)
    6. WorkManager來實現定時更新(實際上算是JobScheduler升級版),似乎受系統省電策略影響,適用于不太精確的定時事件

    應該是除了第一種方法,其他都是可以在應用被殺死的情況進行更新小組件UI

    小組件播放動畫

    progressbar實現

    幀動畫不手動調用anim.start()方法是不會播放的,然后在網上看到一篇文章,使用了progressbar來實現,步驟如下:

    在drawable文件夾準備幀動畫文件

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" android:visible="true">
        <item android:drawable="@drawable/cat_1" android:duration="100" />
        <item android:drawable="@drawable/cat_2" android:duration="100" />
        <item android:drawable="@drawable/cat_3" android:duration="100" />
        <item android:drawable="@drawable/cat_4" android:duration="100" />
    </animation-list>
    
    <ProgressBar
    	android:indeterminateDrawable="@drawable/cat_animdrawable"
    	android:layout_width="wrap_content"
    	android:layout_height="wrap_content"/>
    

    indeterminateDrawable設置為上面的幀動畫文件即可

    layoutanim實現

    主要是利用viewgroup的初次顯示的時候,會展示當前view的添加動畫效果,從而實現比較簡單的動畫效果,如平移,縮放等

    可以看實現的敲木魚一文Android-桌面小組件RemoteViews播放木魚動畫 - 掘金

    使用ViewFlipper

    ViewFlipper主要是輪播使用的

    里面可放幾個元素,之后通過設置autoStart為true,則保證自動輪播

    flipInterval屬性則是每個元素的間隔時間(幀動畫的時間),單位為ms

    不過在remoteview中使用的話,缺點就是里面的元素數目只能固定死

    否則只能通過定義不同layout文件(如3個元素則是某個layout,4個元素則是某個layout,然后根據選擇來創建remoteview)

    <ViewFlipper
            android:id="@+id/viewFlipper"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:layout_margin="4dp"
            android:autoStart="true"
            android:flipInterval="800">
    
            <ImageView
                android:id="@+id/vf_img_1"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/peace_talisman_1" />
    
            <ImageView
                android:id="@+id/vf_img_2"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:src="@drawable/peace_talisman_2" />
        </ViewFlipper>
    

    補充

    獲取當前桌面的組件id列表

    //獲得當前桌面已添加的組件的id列表(可能用戶添加了多個)
    val context = it.context
    val appWidgetManager: AppWidgetManager = context.getSystemService(AppWidgetManager::class.java)
    val myProvider = ComponentName(context, CleaningWidget::class.java)
    
    val info =appWidgetManager.getAppWidgetIds(myProvider)
    toast(info.size.toString())
    

    參考

    posted @ 2024-03-14 16:02  Stars-one  閱讀(422)  評論(0編輯  收藏  舉報
    免费视频精品一区二区_日韩一区二区三区精品_aaa在线观看免费完整版_世界一级真人片
    <bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>