[Android][Compose]Groovy DSL升级Gradle版本

在Android开发领域,Groovy DSL (Domain Specific Language) 主要指的是使用Groovy语言编写的特定领域语言,特别指用于构建配置的Gradle脚本。自从Android Studio 3.0及更高版本以来,Google推荐使用Groovy DSL来编写Android项目的Gradle构建脚本,现在也引入了Kotlin DSL作为替代选项。

本文使用Groovy DSL来进行Gradle版本的升级,并且对其进行compose的环境搭建

更改Gradle分发包的下载URL

distributionUrl属性是这个文件中最关键的一行配置,它的作用是指定Gradle分发包的下载URL。这个URL指向一个特定版本的Gradle二进制包,当你的项目需要使用Gradle进行构建时,如果本地没有对应的Gradle版本,Android Studio或命令行工具会根据distributionUrl指定的地址自动下载该版本的Gradle。

下面使用官方的gradle下载链接,个人推荐还是使用国内镜像源的链接:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip

更新插件

在Project根目录下build.gradle中更改对应上面下载Gradle的Gradle插件,和添加跟下面要添加compose ui版本对应的kotlin版本,当然要注意Gradle版本7.0后url为http的要添加allowInsecureProtocol = true:

...............

buildscript{
	repositories {
		..........
		maven {
            url "http://xxxxxxxx.xxx"
            allowInsecureProtocol = true
        }
		..........
	}
	dependencies {
		........
		classpath 'com.android.tools.build:gradle:7.1.3' // 使用支持Compose的Android Gradle插件版本
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10" // 使用支持Compose的Kotlin版本
		........
	}
}

添加kotlin环境

在Module目录下的build.gradle中添加kotlin插件,并且添加kotlin配置和compose支持。kotlinCompilerExtensionVersion版本跟上面添加的kotlin版本对应。因为要支持compose的环境,所以compileSdk也要升级到33以上:

apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'org.jetbrains.kotlin.plugin.parcelize'

android {
	....................
	compileSdk 34
	buildFeatures {
        compose true
    }

    kotlinOptions {
		//这里使用jdk17版本
        jvmTarget = "17"
    }

    composeOptions {
        kotlinCompilerExtensionVersion = "1.4.3"
    }
	....................
}

要注意Gradle版本8.0后需要添加namespace,namespace跟模块名称一致:

android {
    namespace = "com.alibaba.genie.chat.ai"
    compileSdk rootProject.ext.compileSdkVersion
    defaultConfig {
        .....................
    }
	................
}

添加compose依赖

在Module目录下的build.gradle添加compose的依赖,下面进行的是最简单的compose框架依赖添加,在其中的

'androidx.compose:compose-bom:2023.08.00'是Jetpack Compose 使用物料清单:

dependencies {
	//compose
    def composeBom = platform('androidx.compose:compose-bom:2023.08.00')
    implementation composeBom
    androidTestImplementation composeBom
    implementation 'androidx.compose.material3:material3:1.2.1'
}

不过也要注意material3里面已经包含了部分lifecycle的依赖引用,所以会跟例如下面之类的有冲突:

implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'

这是官方依赖清单:

dependencies {

	def composeBom = platform('androidx.compose:compose-bom:2024.06.00')
	implementation composeBom
	androidTestImplementation composeBom

	// Choose one of the following:
	// Material Design 3
	implementation 'androidx.compose.material3:material3'
	// or Material Design 2
	implementation 'androidx.compose.material:material'
	// or skip Material Design and build directly on top of foundational components
	implementation 'androidx.compose.foundation:foundation'
	// or only import the main APIs for the underlying toolkit systems,
	// such as input and measurement/layout
	implementation 'androidx.compose.ui:ui'

	// Android Studio Preview support
	implementation 'androidx.compose.ui:ui-tooling-preview'
	debugImplementation 'androidx.compose.ui:ui-tooling'

	// UI Tests
	androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
	debugImplementation 'androidx.compose.ui:ui-test-manifest'

	// Optional - Included automatically by material, only add when you need
	// the icons but not the material library (e.g. when using Material3 or a
	// custom design system based on Foundation)
	implementation 'androidx.compose.material:material-icons-core'
	// Optional - Add full set of material icons
	implementation 'androidx.compose.material:material-icons-extended'
	// Optional - Add window size utils
	implementation 'androidx.compose.material3:material3-window-size-class'

	// Optional - Integration with activities
	implementation 'androidx.activity:activity-compose:1.9.0'
	// Optional - Integration with ViewModels
	implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
	// Optional - Integration with LiveData
	implementation 'androidx.compose.runtime:runtime-livedata'
	// Optional - Integration with RxJava
	implementation 'androidx.compose.runtime:runtime-rxjava2'

}

附件

package com.alibaba.genie.chat.ai.ui.widget
import android.content.Context
import android.nfc.Tag
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.widget.FrameLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TileMode
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.AnnotatedString.Range
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.sp
import androidx.lifecycle.MutableLiveData
import com.alibaba.genie.chat.ai.R
import kotlinx.coroutines.delay

class ComposePainterTextView: FrameLayout {
    companion object {
        const val TAG = "AiChatScreen"
    }
    private val text = MutableLiveData<String>("")

    private val displayText = MutableLiveData<AnnotatedString>(AnnotatedString(""))

    private val textList = mutableListOf<String>()

    private var mHighlightStr = MutableLiveData<String>("")

    private var isRealEnd = MutableLiveData<Boolean>(false)

    private var mRange: Range<SpanStyle>? = null

    private var textStyle: TextStyle = TextStyle(
        fontSize = 30.sp
    )

    private var needPrinting: Boolean = true

    private var needPausePrinting = MutableLiveData<Boolean>(false)

    private val mIsPainting = MutableLiveData<Boolean>(false)

    private val brush: Brush = Brush.linearGradient(
        listOf(
            Color.Blue,
            Color.Red,
            Color.Green
        ),
        start = Offset(0f,0f),
        end = Offset(1050f,0f),
        TileMode.Clamp
    )

    private var listener: TextAnimationListener? = null

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PrinterTextView)
        val textColor = typedArray.getColor(R.styleable.PrinterTextView_textColor, resources.getColor(R.color.normalTextColor))
        val textSize = typedArray.getDimension(R.styleable.PrinterTextView_textSize, 60f)
        Log.i(TAG, "textColor: $textColor, textSize=$textSize")
        textStyle = TextStyle(
            color = Color(textColor),
            fontSize = (textSize / 3).sp
        )
        typedArray.recycle()
        initView(context)
    }

    private fun initView(context: Context) {
        addView(
            ComposeView(context).apply {
                setContent {
                    val string = text.observeAsState()
                    val highlightStr = mHighlightStr.observeAsState()
                    val needPausePrinting by this@ComposePainterTextView.needPausePrinting.observeAsState()
                    TextBrushScreen(string.value ?:"", highlightStr.value ?:"", needPausePrinting ?:false)
                }
            }
        )
    }

    fun setHighlightStr(highlightStr: String): Boolean {
        if (mHighlightStr.value == highlightStr) {
            Log.w(TAG, "setHighlightStr: return $highlightStr")
            return false
        }
        mHighlightStr.value = highlightStr
        updateHighlightSpan()
        return true
    }

    fun setTextAnimationListener(listener: TextAnimationListener?) {
        this.listener = listener
    }

    fun setNeedPrinting(needPrinting: Boolean) {
        this.needPrinting = needPrinting
    }

    fun setPausePrinting(needPausePrinting: Boolean) {
        Log.w(TAG, "setPausePrinting:${needPausePrinting}, text=${text.value}", )
        this.needPausePrinting.value = needPausePrinting
    }

    fun setText(text: String, tag: String = "") {
        Log.w(TAG, "setText: tag=$tag, text=${text}", )
        this.textList.clear()
        this.textList.add(text)
        this.text.value = text
    }

    fun setText(textId: Int) {
        try {
            val text = resources.getText(textId)
            Log.w(TAG, "setText: text=${text.toString()}", )
            this.textList.clear()
            this.textList.add(text.toString())
            this.text.value = text.toString()
            this.displayText.value = AnnotatedString(text.toString())
        } catch (e: Exception) {
            println()
            return
        }
    }

    fun setTextStyle(textStyle: TextStyle) {
        this.textStyle = textStyle
    }

    fun setIsEnd(value : Boolean) {
        Log.w(TAG, "setIsEnd: $value", )
        isRealEnd.value = value
    }

    fun isPrintTaskEmpty(): Boolean {
        return mIsPainting.value != true
    }

    fun clearText(): ComposePainterTextView {
        textList.clear()
        text.value = ""
        displayText.value = AnnotatedString("")
        mHighlightStr.value = ""
        return this
    }

    private fun updateHighlightSpan() {
        val highlightStr = mHighlightStr.value ?:return
        var start = text.value?.indexOf(highlightStr) ?: return
        Log.w(TAG, "updateHighlightSpan: start=${start},TextUtils.isEmpty(text.value)=${TextUtils.isEmpty(text.value)}", )
        if (start < 0) {
            if (!TextUtils.isEmpty(text.value) && mHighlightStr.value?.startsWith(text.value!!) == true) {
                start = 0
            } else {
                appendTaskToQueue(highlightStr)
                updateHighlightSpan()
                return
            }
        }
        Log.w(TAG, "setHighlightStr: highlightStr $highlightStr, start=${start}, end=${start + highlightStr.length},highlightStr.length=${highlightStr.length}")
        mRange = Range(
            SpanStyle(brush = brush),
            start = start,
            end = start + highlightStr.length
        )
    }

    fun appendTaskToQueue(text: String) {
        Log.w(TAG, "appendTaskToQueue: text=${text}", )
        if (textList.contains(text)) {
            return
        } else {
            textList.add(text)
            this.text.value += text
            needPrinting = true
            needPausePrinting.value = false
        }
    }

    @Composable
    fun TextBrushScreen(text: String, highlightStr: String, needPausePrinting: Boolean) {
        Box(modifier = Modifier.fillMaxSize()) {
//            var printedText: AnnotatedString by remember { mutableStateOf(
//                AnnotatedString("")
//            ) }
            val printedText by displayText.observeAsState(AnnotatedString(""))
            var cursorText : AnnotatedString by remember { mutableStateOf(
                AnnotatedString("")
            ) }

            val isPainting by mIsPainting.observeAsState(initial = false)
            LaunchedEffect(key1 = UInt) {
                while (true) {
                    delay(200)
                    val needUpdateHighlight = if (mRange == null) {
                        false
                    } else if (printedText.text.length < mRange!!.start || printedText.text.length > mRange!!.end) {
                        false
                    } else {
                        true
                    }

                    val string = if ((isPainting || (isRealEnd.value != true && needPrinting)) && cursorText.text == "  ") {
                        "__"
                    } else {
                        "  "
                    }
                    cursorText = buildAnnotatedString {
                        if (needUpdateHighlight) {
                            withStyle(
                                SpanStyle(brush)
                            ) {
                                append(string)
                            }
                        } else {
                            append(string)
                        }
                    }
                }
            }
            LaunchedEffect(key1 = text, highlightStr, needPausePrinting) {
                if (TextUtils.isEmpty(text)) {
                    displayText.value = AnnotatedString("")
                    return@LaunchedEffect
                }
                mIsPainting.value = true
                text.forEachIndexed { index, char ->
                    val needUpdateText = printedText.length < index + 1 && !needPausePrinting
                    val needUpdateHighlight = (printedText.spanStyles.isNotEmpty() && !printedText.spanStyles.contains(mRange)) || (printedText.spanStyles.isEmpty() && mRange !=null )
                    val string = if (needUpdateText) {
                        printedText.text + char
                    } else {
                        printedText.text
                    }
                    if (needUpdateHighlight || needUpdateText) {
                        displayText.value = buildAnnotatedString{
                            if (mRange == null || mRange!!.start > index){
//                                    Log.w(TAG, "mRange == null || mRange!!.start > index, index=$index, mRange!!.start=${mRange?.start}")
                                append(string)
                            }else if (mRange!!.start <= index) {
                                val start = mRange!!.start
                                val end = if (mRange!!.end > string.length) {
                                    string.length
                                } else {
                                    mRange!!.end
                                }
//                                Log.w(TAG, "TextBrushScreen: index=${index}, string.length=${string.length}, start=${start}, mRange?.end=${mRange?.end}, end=${end}, needPausePrinting=${needPausePrinting}")

                                if ( start <= string.length && end <= string.length) {
                                    if (start > 0) {
                                        append(string.substring(0, start))
                                    }

                                    withStyle(
                                        SpanStyle(brush)
                                    ){
                                        append(string.substring(start, end))
                                    }

                                    append(string.substring(end, string.length))
                                }
                            } else {
//                                Log.w(TAG, "TextBrushScreen: index=${index}, mRange?.start=${mRange?.start}, mRange?.end=${mRange?.end}")
                            }
                        }
                        if (needUpdateText && needPrinting) {
                            delay(100)
                        }

                    }
                }
                mIsPainting.value = false
                listener?.animationFinish()
            }
//            Log.w(TAG, "TextBrushScreen: text=${text}", )
            Text(
                modifier = Modifier
                    .fillMaxWidth()
                    .align(Alignment.Center),
                text = printedText + cursorText,
                style = textStyle
            )
        }
    }

    interface TextAnimationListener {
        fun animationFinish()
    }
}

给旧Activity添加生命周期

添加依赖

//compose
    def composeBom = platform('androidx.compose:compose-bom:2023.08.00')
    implementation composeBom
    androidTestImplementation composeBom
    implementation 'androidx.compose.material3:material3:1.2.1'
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"

def lifecycle_version = "2.7.0"
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

代码:

package com.cocos.game.base

import android.app.Activity
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.cocos.lib.CocosActivity

class BaseCocosActivity: Activity(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner{
    private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
    private val savedStateRegistryController = SavedStateRegistryController.create(this)
    private val store = ViewModelStore()

    override val lifecycle: Lifecycle
        get() = lifecycleRegistry
    override val savedStateRegistry: SavedStateRegistry
        get() = savedStateRegistryController.savedStateRegistry
    override val viewModelStore: ViewModelStore
        get() = store


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        savedStateRegistryController.performRestore(null)
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
    }

    override fun onStart() {
        super.onStart()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
    }

    override fun onResume() {
        super.onResume()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
    }

    override fun onPause() {
        super.onPause()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    }

    override fun onStop() {
        super.onStop()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        store.clear()
    }

    /**
     * Compose uses the Window's decor view to locate the
     * Lifecycle/ViewModel/SavedStateRegistry owners.
     * Therefore, we need to set this class as the "owner" for the decor view.
     */
    fun attachToDecorView(decorView: View?) {
        decorView?.let {
            it.setViewTreeViewModelStoreOwner(this)
            it.setViewTreeLifecycleOwner(this)
            it.setViewTreeSavedStateRegistryOwner(this)
        } ?: return
    }
}

完毕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值