Jetpack 之Glance+Compose实现一个小组件

这篇具有很好参考价值的文章主要介绍了Jetpack 之Glance+Compose实现一个小组件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Glance,官方对其解释是使用 Jetpack Compose 样式的 API 构建远程 Surface 的布局,通俗的讲就是使用Compose风格的API来搭建小插件布局,其最新版本是2022年2月23日更新的1.0.0-alpha03。众所周知,Compose样式的API与原生差别不小,至于widget这块改动如何,接下来让我们来一探究竟。

声明依赖项

第一步肯定要添加对应依赖,相应的都是在build.gradle中添加,如果你的工程还没支持Compose,要先添加:

android {
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.1.0-beta03"
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

如果已经支持,上述依赖可以省略,但下述依赖不能省略,继续添加:

dependencies {
    implementation("androidx.glance:glance-appwidget:1.0.0-alpha03")
    implementation("androidx.glance:glance-wear-tiles:1.0.0-alpha03")
}

以上是官方的标准依赖方式,同样以下面这种方式依赖也可以:

implementation 'androidx.glance:glance-appwidget:+'
implementation 'androidx.glance:glance:+'
implementation "androidx.glance:glance-appwidget:1.0.0-alpha03"

创建对应 widget

首先编写对应布局,放在对应/layout/xml目录下:

widget_info.xml

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/app_name"
    android:minWidth="150dp"
    android:minHeight="66dp"
    android:resizeMode="horizontal|vertical"
    android:targetCellWidth="3"
    android:targetCellHeight="2"
    android:widgetCategory="home_screen"
    />

我在上一篇介绍widget的文章中说过,widget其实就是个广播,广播属于四大组件,而四大组件都要在AndroidManifest清单文件中注册:

<receiver
    android:name=".CounterWidgetReceiver"
    android:enabled="@bool/glance_appwidget_available"
    android:exported="false">
    <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>

对应CounterWidgetReceiver代码为:

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.ktfly.comapp.ui.theme.CounterWidget

class CounterWidgetReceiver : GlanceAppWidgetReceiver(){
    override val glanceAppWidget: GlanceAppWidget = CounterWidget()
}

可能看到这里你就迷惑了,widget对应广播类不是要继承AppWidgetProvider然后实现相应方法的吗,其实Glance提供的GlanceAppWidgetReceiver类就已经继承了AppWidgetProvider,我们使用Glance需要GlanceAppWidgetReceiver:

abstract class GlanceAppWidgetReceiver : AppWidgetProvider() {

    private companion object {
        private const val TAG = "GlanceAppWidgetReceiver"
    }

    /**
     * Instance of the [GlanceAppWidget] to use to generate the App Widget and send it to the
     * [AppWidgetManager]
     */
    abstract val glanceAppWidget: GlanceAppWidget

    @CallSuper
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.w(
                TAG,
                "Using Glance in devices with API<23 is untested and might behave unexpectedly."
            )
        }
        goAsync {
            updateManager(context)
            appWidgetIds.map { async { glanceAppWidget.update(context, appWidgetManager, it) } }
                .awaitAll()
        }
    }

    @CallSuper
    override fun onAppWidgetOptionsChanged(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int,
        newOptions: Bundle
    ) {
        goAsync {
            updateManager(context)
            glanceAppWidget.resize(context, appWidgetManager, appWidgetId, newOptions)
        }
    }

    @CallSuper
    override fun onDeleted(context: Context, appWidgetIds: IntArray) {
        goAsync {
            updateManager(context)
            appWidgetIds.forEach { glanceAppWidget.deleted(context, it) }
        }
    }

    private fun CoroutineScope.updateManager(context: Context) {
        launch {
            runAndLogExceptions {
                GlanceAppWidgetManager(context)
                    .updateReceiver(this@GlanceAppWidgetReceiver, glanceAppWidget)
            }
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        runAndLogExceptions {
            if (intent.action == Intent.ACTION_LOCALE_CHANGED) {
                val appWidgetManager = AppWidgetManager.getInstance(context)
                val componentName =
                    ComponentName(context.packageName, checkNotNull(javaClass.canonicalName))
                onUpdate(
                    context,
                    appWidgetManager,
                    appWidgetManager.getAppWidgetIds(componentName)
                )
                return
            }
            super.onReceive(context, intent)
        }
    }
}

private inline fun runAndLogExceptions(block: () -> Unit) {
    try {
        block()
    } catch (ex: CancellationException) {
        // Nothing to do
    } catch (throwable: Throwable) {
        logException(throwable)
    }
}

基本流程方法跟原生widget的差别不大,其含义也无差别,如果对原生Widget不太了解的同学可以翻阅我上一篇文章,这里还有官方注释:“Using Glance in devices with API<23 is untested and might behave unexpectedly.”。在6.0版本以下的Android系统上使用Glance的情况未经测试可能有出乎意料的情况发生。在开始编写widget代码之前,我们先来了解下其使用组件与Compose中的对应组件的些许差别。

差别

根据官方提示,可使用的Compose组合项如下:Box、Row、Column、Text、Button、LazyColumn、Image、Spacer。原生widget是不支持自定义View的,但Compose能通过自定义组件的方式来“自定义”出我们想要的视图,这一点来看相对更加灵活。

Compose中使用的修饰符是Modifier,这里修饰可组合项的修饰符是GlanceModifier,使用方式并无二致,其余组件也有些许差异,这个我们放到后面来说,

Action

以前使用widget跳转页面啥的,都离不开PendingIntent,但是Glance中则采取另一套方式:

actionStartActivity

看函数命名就得知,通过Action启动Activity。共有三种使用方式:

// 通过包名启动Activity
public fun actionStartActivity(
    componentName: ComponentName,
    parameters: ActionParameters = actionParametersOf()
): Action = StartActivityComponentAction(componentName, parameters)


// 直接启动Activity
public fun <T : Activity> actionStartActivity(
    activity: Class<T>,
    parameters: ActionParameters = actionParametersOf()
): Action = StartActivityClassAction(activity, parameters)


//调用actionStartActivity启动Activity,内联函数
public inline fun <reified T : Activity> actionStartActivity(
    parameters: ActionParameters = actionParametersOf()
): Action = actionStartActivity(T::class.java, parameters)\

其对应的使用方式也简单:

Button(text = "Jump", onClick = actionStartActivity(ComponentName("com.ktfly.comapp","com.ktfly.comapp.page.ShowActivity")))
Button(text = "Jump", onClick = actionStartActivity<ShowActivity>())
Button(text = "Jump", onClick = actionStartActivity(ShowActivity::class.java))

actionRunCallback

顾名思义,此函数是通过Action执行Callback,以下是官方提供的使用说明:\

fun <T : ActionCallback> actionRunCallback(
    callbackClass: Class<T>, 
    parameters: ActionParameters = actionParametersOf()
): Action

inline fun <reified T : ActionCallback> actionRunCallback(parameters: ActionParameters = actionParametersOf()): Action

使用方式:

先创建一个继承actionRunCallback的回调类:

class ActionDemoCallBack : ActionCallback {
    override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
        TODO("Not yet implemented")
    }
}

然后在控件中调用:

Button(text = "CallBack", onClick = actionRunCallback<ActionDemoCallBack>())

Button(text = "CallBack", onClick = actionRunCallback(ActionDemoCallBack::class.java))\

actionStartService

此函数是通过Action启动Service,有以下四个使用方式:

fun actionStartService(
    intent: Intent, 
    isForegroundService: Boolean = false
): Action


fun actionStartService(
    componentName: ComponentName, 
    isForegroundService: Boolean = false
): Action

fun <T : Service> actionStartService(
    service: Class<T>, 
    isForegroundService: Boolean = false
): Action
 

inline fun <reified T : Service> actionStartService(isForegroundService: Boolean = false): Action

这里的isForegroundService参数含义是此服务是前台服务。在调用之前也需要先创建对应Service:

class ActionDemoService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        TODO("Not yet implemented")
    }
}

其在控件中使用方式如下:

Button(text = "start", onClick = actionStartService<ActionDemoService>())

Button(text = "start", onClick = actionStartService(ActionDemoService::class.java))

actionStartBroadcastReceiver

此函数是通过Action启动BroadcastReceiver,有以下使用方式:

fun actionSendBroadcast(
    action: String, 
    componentName: ComponentName? = null
): Action

fun actionSendBroadcast(intent: Intent): Action

fun actionSendBroadcast(componentName: ComponentName): Action

fun <T : BroadcastReceiver> actionSendBroadcast(receiver: Class<T>): Action

inline fun <reified T : BroadcastReceiver> actionSendBroadcast(): Action

fun actionStartActivity(
    intent: Intent, 
    parameters: ActionParameters = actionParametersOf()
): Action

其各函数用法跟actionStartActivity函数差不多,这里不做赘述。你会发现以上函数中经常出现ActionParameters。其实ActionParameters就是给Action提供参数,这里不做赘述。

创建widget

创建对应的widget类,通过GlanceStateDefinition来保留GlanceAppWidget的状态,通过点击事件回调自定义的ActionCallBack达到更改widget中数字的目的:

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.glance.*
import androidx.glance.action.ActionParameters
import androidx.glance.action.actionParametersOf
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.action.ActionCallback
import androidx.glance.appwidget.action.actionRunCallback
import androidx.glance.appwidget.state.updateAppWidgetState
import androidx.glance.layout.*
import androidx.glance.state.GlanceStateDefinition
import androidx.glance.state.PreferencesGlanceStateDefinition
import androidx.glance.text.Text
import androidx.glance.text.TextAlign
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider

private val countPreferenceKey = intPreferencesKey("widget-key")
private val countParamKey = ActionParameters.Key<Int>("widget-key")

class CounterWidget : GlanceAppWidget(){

    override val stateDefinition: GlanceStateDefinition<*> =
        PreferencesGlanceStateDefinition

    @Composable
    override fun Content(){
        val prefs = currentState<Preferences>()
        val count = prefs[countPreferenceKey] ?: 1

        Column(
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalAlignment = Alignment.CenterVertically,
            modifier = GlanceModifier
                .background(Color.Yellow)
                .fillMaxSize()
        ) {
            Text(
                text = count.toString(),
                modifier = GlanceModifier.fillMaxWidth(),
                style = TextStyle(
                    textAlign = TextAlign.Center,
                    color = ColorProvider(Color.Blue),
                    fontSize = 50.sp
                )
            )

            Spacer(modifier = GlanceModifier.padding(8.dp))

            Button(
                text = "变两倍",
                modifier = GlanceModifier
                    .background(Color(0xFFB6C0C9))
                    .size(100.dp,50.dp),
                onClick = actionRunCallback<UpdateActionCallback>(
                    parameters = actionParametersOf(
                        countParamKey to (count + count)
                    )
                )
            )
        }
    }
}


class UpdateActionCallback : ActionCallback{
    override suspend fun onRun(context: Context, glanceId: GlanceId,
                               parameters: ActionParameters) {

        val count = requireNotNull(parameters[countParamKey])

        updateAppWidgetState(
            context = context,
            definition = PreferencesGlanceStateDefinition,
            glanceId = glanceId
        ){ preferences ->
            preferences.toMutablePreferences()
                .apply {
                    this[countPreferenceKey] = count
                }
        }

        CounterWidget().update(context,glanceId)
    }
}

运行后效果如下:

Jetpack 之Glance+Compose实现一个小组件,Android进阶,Jetpack Compose,android,android-jetpack,android jetpack

也许你会发现上述导包与平常Compose导包不一样:

Jetpack 之Glance+Compose实现一个小组件,Android进阶,Jetpack Compose,android,android-jetpack,android jetpack

控件导的包都是glance包下的,当然不仅是Column,还有Button、Image等参数都有变化,但变化不大,例如Image的差异:

原Compose中:
Image(
        modifier = modifier,
        painter = BitmapPainter(bitmap),
        contentDescription = "",
        contentScale = contentScale
    )

Image(
       modifier = modifier,
       painter = painterResource(资源id),
       contentDescription = "",
       contentScale = contentScale
    )

Glance中:
public fun Image(
    provider: ImageProvider,
    contentDescription: String?,
    modifier: GlanceModifier = GlanceModifier,
    contentScale: ContentScale = ContentScale.Fit
)

其余控件差异大同小异,这里不做赘述。文章来源地址https://www.toymoban.com/news/detail-831166.html

到了这里,关于Jetpack 之Glance+Compose实现一个小组件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android Jetpack Compose实现轮播图效果

    在最近思索如何使用Compose方式改进我的开源TMDB电影列表应用程序的主屏幕时,一个激动人心的概念浮现在我的脑海中——为什么不整合一个吸引人的轮播图来展示即将上映的电影呢?在本文中,我将分享我的开发和实现自定义轮播图的经历,提供涉及不同步骤的见解。 首先

    2024年02月08日
    浏览(106)
  • Android Jetpack Compose之底部导航栏的实现

    写过一段Android jetpack compose 界面的小伙伴应该都用过Compose的脚手架 Scaffold ,利用它我们可以很快的实现一个现代APP的主流界面架构,即一个带顶部导航栏和底部导航栏的界面架构,我们基于这个架构可以快速的搭建出我们想要的页面效果。而今天的文章就是要介绍如何实现

    2024年03月23日
    浏览(52)
  • 安卓学习笔记之五:Android Studio_骰子案例3(Kotlin搭配 Jetpack Compose实现)

    使用 Compose 创建一款交互式  Dice Roller  Android 应用。 完成: 定义可组合函数。 使用组合创建布局。 使用  Button  可组合项创建按钮。 导入  drawable  资源。 使用  Image  可组合项显示图片。 使用可组合项构建交互式界面。 使用  remember  可组合项将组合中的对象存储到内

    2024年02月20日
    浏览(49)
  • Android Jetpack Compose 别裁

    一、简介 二、compose优缺点 三、compose好学吗 四、Android Jetpack Compose 跟 fluter 哪个更好 五、Android Jetpack Compose 跟 fluter 技能学习选择 之所以叫Android Jetpack Compose别裁是希望能取舍网上的对compose的资料,推出别出心裁的文章,文章结束都会有一个案例,通过实践学习,让学习的

    2024年02月03日
    浏览(66)
  • Jetpack Compose: Hello Android

    Jetpack Compose 是一个现代化的工具包,用于使用声明式方法构建原生 Android UI。在本博文中,我们将深入了解一个基本的 “Hello Android” 示例,以帮助您开始使用 Jetpack Compose。我们将探讨所提供代码片段中使用的函数和注解。 在深入代码之前,请确保您已经准备好使用 Jetpac

    2024年03月10日
    浏览(60)
  • Android Jetpack Compose — Slider滑动条

            在Android Jetpack Compose中,Slider(滑动条)是一个常用的用户界面控件,它允许通过滑动条来选择一个范围或数值。Slider控件非常适用于调整音量、亮度、进度等需要连续调整的场景。 一、Slider的属性         Slider是Android Jetpack Compose中的一个控件,用于实现滑动条

    2024年02月11日
    浏览(52)
  • Android Jetpack Compose之RadioGroup的使用

    Android Jetpack Compose是一个现代化的UI工具包,帮助开发者以声明式的方式构建出美观且功能强大的Android应用。在本文中,我们将详细介绍其中的一个重要组件—— RadioGroup 。 一. RadioGroup简介 Jetpack Compose中并没有像传统View系统中那样直接提供 RadioGroup ,但我们可以很方便地通

    2024年02月06日
    浏览(56)
  • 对于Android开发,我们为何要学Jetpack Compose?

    Jetpack Compose 是用于构建原生 Android 界面的新工具包。它可简化并加快 Android 上的界面开发,使用更少的代码、强大的工具和直观的 Kotlin API,快速让应用生动而精彩。Compose 使用全新的组件——可组合项 (Composable) 来布局界面,使用修饰符 (Modifier) 来配置可组合项。 为何Jetp

    2024年02月10日
    浏览(54)
  • Android Jetpack Compose中使用字段验证的方法

    数据验证是创建健壮且用户友好的Android应用程序的关键部分。随着现代UI工具包Jetpack Compose的引入,处理字段验证变得更加高效和直观。在这篇文章中,我们将探讨如何在Android应用中使用Jetpack Compose进行字段验证。 字段验证是确保用户在各种输入字段中输入的数据符合特定

    2024年02月11日
    浏览(54)
  • 现代化 Android 开发:Jetpack Compose 最佳实践

    作者:古哥E下 如果一直关注 Compose 的发展的话,可以明显感受到 2022 年和 2023 年的 Compose 使用讨论的声音已经完全不一样了, 2022 年还多是观望,2023 年就有很多团队开始采纳 Compose 来进行开发了。不过也有很多同学接触了下 Compose,然后就放弃了。要么使用起来贼特么不顺手

    2024年02月17日
    浏览(71)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包