Jetpack Compose入門到精通完整版_第1頁
Jetpack Compose入門到精通完整版_第2頁
Jetpack Compose入門到精通完整版_第3頁
Jetpack Compose入門到精通完整版_第4頁
Jetpack Compose入門到精通完整版_第5頁
已閱讀5頁,還剩115頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

JetpackCompose是用于構(gòu)建原生AndroidUIJetpackCompose使用更少的代碼,強(qiáng)大的工具和直觀的KotlinAPI,簡化并加速了Android上的UI開發(fā)。這是AndroidDevelopers官網(wǎng)對它的描述。本文不是教你JetpackCompose的一些基本使用方法,而是為啥我們需要JetpackCompose們對JetpackCompose有更深層次的了解。如果你想看JetpackCompose章\hAndroidJetpackCompose在Andoid中,UI工具包的歷史可追溯到至少10備,用戶的期望,以及開發(fā)人員對他們所使用的開發(fā)工具和語言的期望。以上只是我們需要新UI工具的一個(gè)原因,另外一個(gè)重要的原因是View.java這個(gè)類實(shí)在是太大了,有太多的代碼,它大到你甚至無法在Githubs上查看該文件,因?yàn)樗鼘?shí)際上包含了30000乎每一個(gè)AndoidUI組件都需要繼承于Viw。GogleAndoid團(tuán)隊(duì)的Anna-Chiaa表示,他們對已經(jīng)實(shí)現(xiàn)的一些API情況下收回、修復(fù)或擴(kuò)展這些API,因此現(xiàn)在是一個(gè)嶄新起點(diǎn)的好時(shí)機(jī)。這就是為什么JetpackCompose強(qiáng)大的UI直觀的Kotlin化等事情上,看看下面這個(gè)Viw:這個(gè)MaterialEdit-txt管理等等。而JetpackCompose為我們提供了很多開箱即用的Material組件,如果的APP是使用的material設(shè)計(jì)的話,那么使用JetpackCompose能讓你節(jié)省不少精力。沒有正確工具的UI工具包是無用的,因此從過去10年的經(jīng)驗(yàn)中能學(xué)到不少,JetpackComposeJetBrains合作,以提供開發(fā)者強(qiáng)大的工具包,在AndroidStudio上大規(guī)模的支持Compose看一看在AndroidStudio上圖是使用JetpackCompose開發(fā)UI時(shí),在AndoidStudio同時(shí)展現(xiàn)UI即時(shí)預(yù)覽,比如在明/暗模式下的狀態(tài)切換,都能在右邊及時(shí)展示出來。它與我們現(xiàn)在使用的AndroidStudio中的text/DesignAndroidStudio4.0以上預(yù)覽版,開發(fā)compose直觀的Kotlin(\h/platform/fr…)由于Compose十多年來,Andoid團(tuán)隊(duì)在創(chuàng)建API和審查API方面擁有豐富的經(jīng)驗(yàn),但有一個(gè)收獲-他們使用Java但有一個(gè)問題-他們使用的是Java作為開發(fā)語言。JetpackCompose是第一個(gè)使用Kotlin正在開發(fā)中的大型項(xiàng)目,因此Android團(tuán)隊(duì)正在探索KotlinAPI指南的新世界,以創(chuàng)建一組特定于ComposeAPI的指南,該工作仍在進(jìn)行中,仍然有很長的路要走。正如我前面的文章所說,Compose是一個(gè)聲明式UI系統(tǒng),其中,我們用一組函數(shù)來聲明UI,并且一個(gè)函數(shù)可以嵌套另一個(gè)Compose函數(shù),并以樹的結(jié)構(gòu)來構(gòu)造所需要的UI。在Compose中,我們稱該樹為UI圖,當(dāng)UI需要改變的時(shí)候會刷新此UI圖,比如Compose函數(shù)中有if么Kotlin編譯器就需要注意了。頂層函數(shù)(Top-level在Composefuncheckbox(...funTextView(...funEdittext(...funImage(...在此過程中,Compose函數(shù)始終根據(jù)接收到的輸入生成相同的UI,因此,放棄類結(jié)構(gòu)不會有任何害處。從類結(jié)構(gòu)構(gòu)建UI過渡到頂層函數(shù)構(gòu)建UI對開發(fā)者和Andoidelease版。JetpackCompose首選組合而不是繼承。Compose會基于其他部分構(gòu)建UI如果你經(jīng)常關(guān)注Android或者對Android有所了解,你就會知道,Android中的幾乎所有組件都繼承于View(直接或間接繼承)。比如EidtText繼承于TextView,而同時(shí)TextView又繼承于其他一些View,這樣的繼承機(jī)構(gòu)最終會指向跟View即View.java。并且View.java又非常多的功能。而Compose團(tuán)隊(duì)則將整個(gè)系統(tǒng)從繼承轉(zhuǎn)移到了頂層函數(shù)。Textview,EditText,復(fù)選框和所有UI組件都是信任單一來源是構(gòu)建整個(gè)JetpackCompose一項(xiàng)非常重要的特性。如果您習(xí)慣了現(xiàn)有的UI工具包,那么您可能會finalbooleanhandled=首先,它改變viw的狀態(tài),然后執(zhí)行動作,這會導(dǎo)致許多bug,例如復(fù)選框,因?yàn)樗紫葟囊堰x中狀態(tài)變?yōu)橹?,反之亦然,然后由于某種原因,如果操作失敗,開發(fā)人員必須手動分配先前的狀態(tài)。而在Compose中呢,功能正好相反。在此,復(fù)選框等功能具有兩個(gè)參數(shù)。一個(gè)是在UIlambda函數(shù),用于觀察UIfunCheckbox(checked:onCheckedChange:((Boolean)->深入了解如上圖所示,Compose顧名思義,這是Compose基本上,核心包含四個(gè)構(gòu)建模塊:繪制(Draw)布局(Layout)輸入(Input)語義(Semantics)2、Layout—通過布局,我們可以測量事物并相應(yīng)地放置視圖。3、Input—4、Semantics—1.5.2.Foundation的核心是收集上面提到的所有內(nèi)容,并共同創(chuàng)建一個(gè)抽象層1.5.3在這一層,所有的Material組件將會被提供,并且我們可以通過提供的這些組件來構(gòu)建復(fù)雜的UI這是Compose團(tuán)隊(duì)所做的出色工作中最精彩的部分,在這里,所有提供的View都有MaterialCompose來構(gòu)建APP,默認(rèn)就Material插槽插槽API的出現(xiàn)是為了給開發(fā)人員留出了很多空間,以便他們可以執(zhí)行所需的任何自定義操作,Andoid圖猜測開發(fā)人員可能會想到的許多自定義設(shè)置,但他們無法一直想象開發(fā)人員的想法,例如使用帶drawable的xtViw。因此,Compose團(tuán)隊(duì)為組件留出了空間,以便開發(fā)人員可以執(zhí)行所需的任何操作,例如使用按鈕。你可以文本或帶有圖標(biāo)的文本或所需的任何內(nèi)容,如下所示AndroidJetpackComposeJetpackCompose環(huán)境準(zhǔn)備和Hello每當(dāng)我們學(xué)習(xí)一門新的語言,我們都是從一個(gè)helloworld開始,今天我們也從一個(gè)helloworld來開始JetpackCompose吧!要想獲得JetpackCompose的最佳體驗(yàn),我們需要下載最新版本的AndroidStudio預(yù)覽版本(即AndroidStudio4.0)。因?yàn)锳ndroidStudio4.0添加了對JetpackCompose的支持,如新的Compose模版和Compose及時(shí)預(yù)覽。將JetpackCompose添加到現(xiàn)有項(xiàng)目創(chuàng)建一個(gè)支持JetpackCompose的新應(yīng)用將JetpackCompose如果你想在現(xiàn)有的項(xiàng)目中使用JetpackCompose(1)gradle在app目錄下的build.gradle中將app支持的最低API版本設(shè)置為21或更高,同時(shí)開啟JetpackenableandroidandroiddefaultConfigminSdkVersionbuildFeatures//EnablesJetpackComposeforthismodulecomposetrue//SetboththeJavaandKotlincompilerstotargetJavacompileOptionssourceCompatibilityJavaVersion.VERSION_1_8targetCompatibilitykotlinOptions{jvmTarget="1.8"使用試驗(yàn)版Kotlin-GradleJetpackCompose需要試驗(yàn)版的Kotlin-Gradle插件,在根目錄下的build.gradlebuildscriptbuildscript//TodownloadtherequiredversionoftheKotlin-Gradle//addthefollowingmaven{url'/kotlin/kotlin-eap'dependenciesclasspath'com.android.tools.build:gradle:4.0.0-classpath'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.60-eap-allprojectsallprojectsmaven{url'/kotlin/kotlin-eap'添加JetpackCompose在app目錄下的build.gradle添加JetpackComposedependenciesdependencies//YoualsoneedtoincludethefollowingComposetoolkitdependencies.implementation'androidx.ui:ui-tooling:0.1.0-dev02'implementation'androidx.ui:ui-layout:0.1.0-dev02'implementation'androidx.ui:ui-material:0.1.0-dev02'ok,到這兒準(zhǔn)備工作就完畢,就可以開始寫代碼了,但是前面說了,還有一種方式接入JetpackCompose,來一起看看。創(chuàng)建一個(gè)支持JetpackCompose比起在現(xiàn)有應(yīng)用中接入JetpackCompose,創(chuàng)建一個(gè)支持JetpackCompose的新項(xiàng)目則簡單了許多,因?yàn)锳ndoidStudio4.0提供了一個(gè)新的Compose幫我們完成了。創(chuàng)建一個(gè)支持JetpackCompose1如果你在AndroidStudio的歡迎窗口,點(diǎn)擊StartanewAndroidStudioproject,如果你已經(jīng)打開了AndroidStudio項(xiàng)目,則在頂部菜單欄選擇File>New>NewProject1.在SelectaProjectTemplate窗口,選擇EmptyComposeActivity1.在Configureyourproject設(shè)置項(xiàng)目名稱,包名和Kotlin是唯一一個(gè)可選項(xiàng),因?yàn)镴etpackCompose只能用Kotlin寫的才能運(yùn)行。MinimumAPIlevel下拉菜單中,選擇211點(diǎn)擊現(xiàn)在,你就可以使用JetpackComposeHello在MainActivity.ktclassclassMainActivity:AppCompatActivity()overridefunonCreate(savedInstanceState:Bundle?){setContentText("Hello,Android技術(shù)雜貨鋪JetpackCompose是圍繞composable函數(shù)來構(gòu)建的。這些函數(shù)使你可以通過描述應(yīng)用程序的形狀和數(shù)據(jù)依賴,以編程方式定義應(yīng)用程序的UI,而不是著眼于UI的構(gòu)建過程。要創(chuàng)建composable函數(shù),只需要在函數(shù)名前面加上一個(gè)@composable注解即可上面的Text就是一個(gè)composable函數(shù)。定義一個(gè)composable一個(gè)composable函數(shù)只能在另一個(gè)composable函數(shù)的作用域里北調(diào)用,要使一個(gè)函數(shù)變?yōu)閏omposable函數(shù),只需在函數(shù)名前加上@composable注解,我們把上面的代碼中,setContentcomposable函數(shù)中,然后傳遞一個(gè)參數(shù)name給text元素。classclassMainActivity:AppCompatActivity()overridefunonCreate(savedInstanceState:Bundle?){setContentGreeting("Android技術(shù)雜貨鋪funGreeting(name:String){Text(text="Hello$name!")UI元素是分層級的,元素包含在其他元素中。在JetpackCompose中,你可以通過從其他composablecomposable函數(shù)來構(gòu)建UI在Android的xml布局中,如果要顯示一個(gè)垂直結(jié)構(gòu)的布局,最長用的就是LinearLayout,值為vertical,子元素就會垂直排列,那么,在JetpackCompose中,如何來實(shí)現(xiàn)垂直布局呢?先添加幾個(gè)Text來看一下。添加多個(gè)在上面的例子中,我們添加了一個(gè)TextclassclassMainActivity:AppCompatActivity()overridefunonCreate(savedInstanceState:Bundle?){setContentfunNewsStory()Text("我超?JetPackCompose的!")Text("依然范特西從上圖可以看到,我們添加了3本元素相互重疊繪制,使得文本不可讀。使用要使重疊繪制的Text文本能夠垂直排列,我們就需要使用到Column函數(shù),寫過flutter眼熟?是的,跟flutter里面的ColumnWidget名字和功能完全一樣,甚至連他們的屬性都一摸一樣。funNewsStory()Column添加Column,使布局垂直排列Text("我超?JetPackCompose的!")Text("依然范特西有任何間距。接下來,我們給Column設(shè)置一些樣式。給Column在調(diào)用Column()時(shí),可以傳遞參數(shù)給Column()來配置ColumnfunNewsStory(){Column(crossAxisSize=modifier=){//添加Column,使布局垂直排列Text("我超?JetPackCompose的!")Text("依然范特西如上圖所示,我們填充了padding crossAxisSize:指定Column組件(注:Compose中,所有的組件都是composable函數(shù),文中的組件都是指代composable函數(shù))在水平方向的大小,設(shè)置 crossAxisSize為LayoutSize.Expand即表示Column寬度應(yīng)為其父組件允許的最大寬度,相當(dāng)于傳統(tǒng)布局中的match_parant,還有一個(gè)值為LayoutSize.Wrap,看名字就知道,包裹內(nèi)容,相當(dāng)于傳統(tǒng)布局中的wrap_content。 modifier:使你可以進(jìn)行其他格式更改。在這種情況下,我們將應(yīng)用一個(gè)SpacingCloumn在原來的安卓原生布局中,顯示圖片有相應(yīng)的控件ImageView,設(shè)置本地圖片地址或者BitmapJetpackCompose我們先下載這張圖片到本地,添加到資源管理器中,命名為header.png,我們更改一下上面的NewsStory方法,先從資源文件夾獲取圖片image,然后通過DrawImage()funNewsStory()Column(crossAxisSize=modifier=添加ColumnText("我超?JetPackCompose的!")Text("依然范特西將其放入Container(又一個(gè)和flutter中一樣的控件) Container:一個(gè)通用的內(nèi)容對象,用于保存和安排其他UI元素。然后,你可以將大小和位置的設(shè)置應(yīng)用funNewsStory()Column(crossAxisSize=modifier=添加Column//Container(expanded=true,height=180.dp)Text("我超?JetPackCompose的!")Text("依然范特西expanded指定Container的大小,默認(rèn)是false(Containerexpandedmatch_parentwrap_content),如果將它設(shè)置為true,就指定Container的大小為父控件所允許的最大size,match_parent設(shè)置Container容器的高度,height屬性的優(yōu)先級高于expanded,因此會覆蓋expanded,如上面的例子,設(shè)置height為180dp,也就是容器寬為父控件寬度,高為180dpheight添加間距height我們看到,圖片和文本之間沒有間距,傳統(tǒng)布局中,我們可以添加Margin屬性,設(shè)置間距,在JetpackCompose中,我們可以使用HeightSpacer()和WidthSpacer()來設(shè)置垂直和水平間距WidthSpacer(width=20.dp)//設(shè)置水平間距20dp在上面的例子中,我們來為圖片和文本之間添加20dpfunNewsStory()Column(crossAxisSize=modifier=添加Column//Container(expanded=true,height=180.dp)HeightSpacer(height20.dp)添加垂直方向間距20dpText("我超?JetPackCompose的!")Text("Android技術(shù)雜貨鋪Text("依然范特西使用MaterialdesignCompose旨在支持MaterialDesign設(shè)計(jì)原則,許多組件都實(shí)現(xiàn)了MaterialDesign設(shè)計(jì),可以開箱即用,在這添加ShapeShape是MaterialDesign系統(tǒng)中的支柱之一,我們來用clipfunNewsStory()Column(crossAxisSize=modifier=添加Column//Container(expanded=true,height=180.dp)Clip(shape=RoundedCornerShape(10.dp))HeightSpacer(height20.dp)添加垂直方向間距20dpText("我超?JetPackCompose的!")Text("Android技術(shù)雜貨鋪Text("依然范特西通過Compose,可以輕松利用MaterialDesign原則。將MaterialTheme()funNewsStory()valimage=使用MaterialDesignColumn(crossAxisSize=modifiermodifier=){//添加Column//Container(expanded=true,height=180.dp)Clip(shape=RoundedCornerShape(10.dp))HeightSpacer(height20.dp)添加垂直方向間距20dpText("我超?JetPackCompose的!")Text("Android技術(shù)雜貨鋪Text("依然范特西如上面的代碼,添加了MaterialTheme后,重新運(yùn)行,效果沒有任何變化,文本現(xiàn)在使用了MaterialTheme默認(rèn)文本樣式。接下來,我們將特定的段落樣式應(yīng)用于每個(gè)文本元素。funNewsStory()valimage=使用MaterialDesignColumn(crossAxisSize=modifier=添加Column//Container(expanded=true,height=180.dp)Clip(shape=RoundedCornerShape(10.dp))HeightSpacer(height20.dp)添加垂直方向間距Text("我超?JetPackCompose的!style+themeTextStyleh5Text("Android技術(shù)雜貨鋪style+themeTextStylebody1注意添加了styleText("依然范特西",style=+themeTextStyle{body2})//注意添加了style現(xiàn)在看看,我們的文本樣式已經(jīng)有變化了,標(biāo)題有6中樣式h1-h6,其實(shí)HTMLbody1和body22Material調(diào)色版使用了一些基本顏色,如果要強(qiáng)調(diào)文本,可以調(diào)整文本的不透明度Text("Text("我超?JetPackCompose的!style(+themeTextStyleh5}).withOpacity(0.87f))Text("Android技術(shù)雜貨鋪",style=(+themeTextStyle{body1}).withOpacity(0.87f))Text("依然范特西",style=(+themeTextStyle{body2}).withOpacity(0.6f))有些時(shí)候,標(biāo)題很長,但是我們又不想長標(biāo)題換行從而影響我們的appUI,行數(shù),如本例所示,我們設(shè)置顯示最大行數(shù)為2Text("Text("我超?JetPackCompose更!",maxLines=2,overflow=style=(+themeTextStyle{h5Compose從AndroidStudio4.0開始,提供了在IDE中預(yù)覽composable函數(shù)的功能,不用像以前那樣,要先下載一個(gè)模函數(shù)沒有參數(shù)在函數(shù)前面添加@Preview注解當(dāng)布局改變了之后,頂部會出現(xiàn)一個(gè)導(dǎo)航條,顯示預(yù)覽已經(jīng)過期,點(diǎn)擊build&Refresh這真的是一個(gè)非常棒的功能,像其他聲明式布局,如React、flutter行才能看到效果,雖然可以熱啟動,但是還是沒有這個(gè)預(yù)覽來得直接。還有一個(gè)非常牛逼的地方是,compose的預(yù)覽可以同時(shí)預(yù)覽多個(gè)composable函數(shù)。JetpackCompse目前還是試驗(yàn)版,礙我們學(xué)習(xí)和體驗(yàn)它,聲明式UI框架近年來飛速發(fā)展,React為聲明式UI奠定了堅(jiān)實(shí)基礎(chǔ)并。Flutter的發(fā)布將聲明式UI的思想成功帶到移動端開發(fā)領(lǐng)域,Apple和Google分別先后發(fā)布了自己的聲明式UI框架SwiftUI和JetpackCompose,以后,原生UI布局,聲明式可能將會是主流。人們對于UI開發(fā)的預(yù)期已經(jīng)不同往昔。現(xiàn)如今,為了滿足用戶的需求,我們構(gòu)建的應(yīng)用必須包含完善的用戶界面,其中必然包括動畫(animation)和動效(motion),這些訴求在UI工具包創(chuàng)建之初時(shí)并不存在。為了解決如何快速而高效地創(chuàng)建完善的UI這一技術(shù)難題,我們引入了JetpackCompose——這是一個(gè)現(xiàn)代的UI開發(fā)者們在新的趨勢下取得成功。在本系列的兩篇文章中,我們將闡述Compose分享Compose所解決的問題、一些設(shè)計(jì)決策背后的原因,以及這些決策如何幫助開發(fā)者。此外,我還會分享Compose的思維模型,您應(yīng)如何考慮在Compose中編寫代碼,以及如何創(chuàng)建您自己的API。Compose關(guān)注點(diǎn)分離(Sepaationofconcerns,SOC)是一個(gè)眾所周知的軟件設(shè)計(jì)原則,這是我們作為開發(fā)者所要學(xué)習(xí)的基礎(chǔ)知識之一。然而,盡管其廣為人知,但在實(shí)踐中卻常常難以把握是否應(yīng)當(dāng)遵循該原則。面對這樣的問題,從"合"和"內(nèi)聚"的角度去考慮這一原則可能會有所幫助。編寫代碼時(shí),我們會創(chuàng)建包含多個(gè)單元的模塊。"耦合"中的各部分是如何影響另一個(gè)模塊的各個(gè)部分的。"內(nèi)聚"中各個(gè)單元相互組合的合理程度。當(dāng)我們處理緊耦合合常常是隱式的,以至于看起來毫無關(guān)聯(lián)的修改,卻會造成了意料之外的錯誤發(fā)生。長而擴(kuò)展我們的代碼。讓我們在當(dāng)前Android開發(fā)的上下文中進(jìn)行更為實(shí)際的操作,并以視圖模型(viewmodel)和XML布局為例視圖模型會向布局提供數(shù)據(jù)。事實(shí)證明,這里隱藏了很多依賴關(guān)系:視圖模型與布局間存在許多耦合。一個(gè)更為熟悉的可以讓您查看這一清單的方式是通過一些API,例如findViwByID。使用這些API需要對XML內(nèi)容有一定了解。使用這些API需要了解XML布局是如何定義并與視圖模型產(chǎn)生耦合的。由于應(yīng)用規(guī)模會隨著時(shí)間增長,我們還大多數(shù)現(xiàn)代應(yīng)用會動態(tài)展示UI,并且會在執(zhí)行過程中不斷演變。結(jié)果導(dǎo)致應(yīng)用不僅要驗(yàn)證布局XML是否靜態(tài)地一些依賴關(guān)系可能會被破壞,并導(dǎo)致諸如NullRefeenceEceptions一類的問題。通常,視圖模型會使用像Kotlin這樣的編程語言進(jìn)行定義,而布局則使用XML。由于這兩種語言的差異,使得它們之間存在一條強(qiáng)制的分隔線。然而即使存在這種情況,視圖模型與布局XML說,它們二者緊密耦合。這就引出了一個(gè)問題:如果我們開始用相同的語言定義布局與UI結(jié)構(gòu)會怎樣?如果我們選用Kotlin來做這件事至那些可以使它們減少耦合和增加內(nèi)聚的位置?,F(xiàn)在,您可能會以為這是建議您將邏輯與UI混合起來。不過現(xiàn)實(shí)的情況是,無論您如何組織架構(gòu),您的應(yīng)用中都將出現(xiàn)與UI相關(guān)聯(lián)的邏輯??蚣鼙旧聿⒉粫淖冞@一點(diǎn)。不過框架可以為您提供一些工具,從而幫您更加簡單地實(shí)現(xiàn)關(guān)注點(diǎn)分離:這一工具便是Composable函數(shù),長久獲得的技巧,都可以應(yīng)用在Composable函數(shù)上。Composable這是一個(gè)Composable函數(shù)的示例funApp(appData:AppData)valderivedData=compute(appData)if(appData.isOwner){Bodyfor(iteminderivedData.items){在示例中,函數(shù)從AppData類接收數(shù)據(jù)作為參數(shù)。理想情況下,這一數(shù)據(jù)是不可變數(shù)據(jù),而且Composable數(shù)也不會改變:Composable函數(shù)應(yīng)當(dāng)成為這一數(shù)據(jù)的轉(zhuǎn)換函數(shù)。這樣一來,我們便可以使用任何Kotlin代碼來獲取這一數(shù)據(jù),并利用它來描述的我們的層級結(jié)構(gòu),例如Header()與Body()調(diào)用。這意味著我們調(diào)用了其他Composable函數(shù),并且這些調(diào)用代表了我們層次結(jié)構(gòu)中的UI。我們可以使用中語言級別的原語來動態(tài)執(zhí)行各種操作。我們也可以使用if語句與for循環(huán)來實(shí)現(xiàn)控制流,來處理更為復(fù)雜的UI邏輯。Composable函數(shù)通常利用Kotlin的尾隨lambda語法,所以Body()是一個(gè)含有ComposablelambdaComposable函數(shù)。這種關(guān)系意味著層級或結(jié)構(gòu),所以這里Body()"聲明式"程方式。讓我們來看一個(gè)例子:會在信封中繪制一些紙張;而如果有100條消息,我們就把圖標(biāo)繪制成好像在著火的樣子funfunupdateCount(count:Int)if(count>0&&!hasBadge()){}elseif(count==0&&hasBadge()){if(count>99&&!hasFire()){}elseif(count<=99&&hasFire()){if(count>0&&!hasPaper()){}elseif(count==0&&hasPaper()){if(count<=99){在這段代碼中,我們接收新的數(shù)量并且必須搞清楚如何更新當(dāng)前的UI來反映對應(yīng)的狀態(tài)。盡管是一個(gè)相對簡單作為替代,使用聲明式接口編寫這一邏輯則會看起來像下面這樣funBadgedEnvelope(count:Int){Envelope(fire=count>99,paper=count>0){if(count>0){這里我們定義當(dāng)數(shù)量大于99時(shí),顯示火焰;當(dāng)數(shù)量大于0時(shí),顯示紙張;當(dāng)數(shù)量大于0時(shí),繪制數(shù)量氣泡。這便是聲明式API的含義。我們編寫代碼來按我們的想法描述UI,而不是如何轉(zhuǎn)換到對應(yīng)的狀態(tài)。這里的關(guān)鍵是,編寫像這樣的聲明式代碼時(shí),您不需要關(guān)注您的UI架控制著如何從一個(gè)狀態(tài)轉(zhuǎn)到其他狀態(tài),所以我們不再需要考慮它。vs在軟件開發(fā)領(lǐng)域,Composition(組合)元。在面向?qū)ο缶幊棠P椭校畛R姷慕M合形式之一便是基于類的繼承。在JetpackCompose的世界中,由于我們使用函數(shù)替代了類型,因此實(shí)現(xiàn)組合的方法頗為不同,但相比于繼承也擁有許多優(yōu)點(diǎn),讓我們來看一個(gè)例子:假設(shè)我們有一個(gè)視圖,并且我們想要添加一個(gè)輸入。在繼承模型中,我們的代碼可能會像下面這樣classclassInput:View(){/*...*/classValidatedInput:Input(){/*...*/}classDateInput:ValidatedInput(){/*...*/}classDateRangeInput:???{/*...*/}Viw是基類,alidatedInput使用了Input的子類。為了驗(yàn)證日期,DateInput使用了alidatedInput的子類。但是接下來挑戰(zhàn)來了:我們要創(chuàng)建一個(gè)日期范圍的輸入,這意味著需要驗(yàn)證兩個(gè)日期——以繼承DateInput,但是您無法執(zhí)行兩次,這便是繼承的限制:我們只能繼承自一個(gè)父類。在Compose中,這個(gè)問題變得很簡單。假設(shè)我們從一個(gè)基礎(chǔ)的InputComposable函數(shù)開始fun<T>Input(value:T,onChange:(T)->Unit)/*...當(dāng)我們創(chuàng)建ValidatedInput時(shí),只需要在方法體中調(diào)用Input輯funValidatedInput(value:T,onChange:(T)->Unit,isValid:Boolean){InputDecoration(color=if(isValid)blueelsered){Input(value,接下來,對于DataInput,我們可以直接調(diào)用funDateInput(value:DateTime,onChange:(DateTime)->Unit){onChange={...onChange(...)},isValid=isValidDate(value)現(xiàn)在,當(dāng)我們實(shí)現(xiàn)日期范圍輸入時(shí),這里不再會有任何挑戰(zhàn):只需要調(diào)用兩次即可。示例如下funDateRangeInput(value:DateRange,onChange:(DateRange)->Unit){DateInput(value=value.start,...)DateInput(value=value.end,在Compose另一種類型的組合問題是對裝飾類型的抽象。為了能夠說明這一情況,請您考慮接下來的繼承示例:classclassFancyBox:View(){/*...*/}classStory:View(){/*...*/}classEditForm:FormView(){/*...*/}classFancyStory:???{/*...*/}classFancyEditForm:???{/*...*/}FancyBx是一個(gè)用于裝飾其他視圖的視圖,本例中將用來裝飾Story和EditForm。我們想要編寫FancyStory與FancyEditForm,但是如何做到呢?我們要繼承自FancyBx還是Story里變得十分含糊。相反,Compose可以很好地處理這一問題funFancyBox(children:@Composable()->Unit){Box(fancy){children()}@ComposablefunStory(…){/*...*/}@ComposablefunEditForm(...){/*...*/}@ComposablefunFancyStory(...){FancyBox{Story(…)@ComposablefunFancyEditForm(...){FancyBox{EditForm(...)}我們將Composablelambda創(chuàng)建FancyStory時(shí),可以在FancyBx的子級中調(diào)用Story,并且可以使用FancyEditForm進(jìn)行同樣的操作。這便是Compose的組合模型。Compose做的很好的另一個(gè)方面是"封裝"。這是您在創(chuàng)建公共Composable函數(shù)API時(shí)需要考慮的問題:的ComposableAPI只是一組其接收的參數(shù)而已,所以Compose無法控制它們。另一方面,Composable函數(shù)可以管理和創(chuàng)建狀態(tài),然后將該狀態(tài)及它接收到的任何數(shù)據(jù)作為參數(shù)傳遞給其他的Composable函數(shù)?,F(xiàn)在,由于它正管理該狀態(tài),如果您想要改變狀態(tài),您可以啟用您的子級Composable的改變。"重組ComposableComposable層級結(jié)構(gòu),當(dāng)您的層級中的某一部分發(fā)生改變時(shí),您不會希望重新計(jì)算整個(gè)層級結(jié)構(gòu)。所以Composable函數(shù)是可重啟動(restartable)的,您可以利用這一特性來實(shí)現(xiàn)一些強(qiáng)大的功能。舉個(gè)例子,這里有一個(gè)Bind函數(shù),里面是一些Android開發(fā)的常見代碼liveMsgs.observe(this){msgs->并在接下來傳入lambda。lambda會在每次LiveData更新被調(diào)用,并且發(fā)生這種情況時(shí),我們會想要更新視圖。使用ComposevalmsgsbyliveMsgs.observeAsState()for(msginmsgs){這里有一個(gè)相似的Composable函數(shù)——Messages。它接收了LiveData作為參數(shù)并調(diào)用了Compose的observeAsState方法。observeAsState方法會把LiveData映射為Stat,這意味著您可以在函數(shù)體的范圍使用其值。State實(shí)例訂閱了LiveData實(shí)例,這意味著State會在LiveData何處讀取State實(shí)例,包裹它的、已被讀取的Composable函數(shù)將會自動訂閱這些改變。結(jié)果就是,這里不再需要指定LifecycleOwner或者更新回調(diào),Composable可以隱式地實(shí)現(xiàn)這兩者的功能。Compose提供了一種現(xiàn)代的方法來定義您的UI,這使您可以有效地實(shí)現(xiàn)關(guān)注點(diǎn)分離。由于Composable函數(shù)與普通Kotlin函數(shù)很相似,因此您使用Compose編寫和重構(gòu)UI所使用的工具與您進(jìn)行Andoid所使用的工具將會無縫銜接。本節(jié)是Compose深入詳解\h上一節(jié)中,我已經(jīng)闡述了Compose的優(yōu)點(diǎn)、Compose決的問題、一些設(shè)計(jì)決策背后的原因,以及這些內(nèi)容是如何幫助開發(fā)者的。此外,我還討論了Compose的思維模型、您應(yīng)如何考慮使用Compose編寫代碼,以及如何創(chuàng)建您自己的API。在本文中,我將著眼于Compose背后的工作原理。但在開始之前,我想要強(qiáng)調(diào)的是,使用Compose并不一定@Composable如果您已經(jīng)了解過Compose,您大概已經(jīng)在一些代碼示例中看到過@Composable情需要注意——Compose并不是一個(gè)注解處理器。Compose在Kotlin編譯器的類型檢測與代碼生成階段依賴Kotlin編譯器插件工作,所以無需注解處理器即可使用Compose。這一注解更接近于一個(gè)語言關(guān)鍵字。作為類比,可以參考Kotlin的\hsuspend關(guān)鍵字suspendfunMyFun(){…lambdavalmyLambda=suspend{…funMyFun(myParam:suspend()->Unit){…Kotlin的\hsuspend關(guān)鍵字適用于處理函數(shù)類型:您可以將函數(shù)、lambda或者函數(shù)類型聲明為suspend。Compose與其工作方式相同:它可以改變函數(shù)類型。@ComposablefunMyFun(){…lambdavalmyLambda=@Composable{…funMyFun(myParam:@Composable()->Unit){…這里的重點(diǎn)是,當(dāng)您使用@Composable:與注解后的類型互不兼容。同樣的,掛起(suspend)中調(diào)用掛起函數(shù):funfunExample(a:Unit,b:suspendUnit)a()//允許b()funExample(a:Unit,b:suspendUnit)a()//允許b()ComposablefunfunExample(a:Unit,b:@ComposableUnit)a()//允許b()funExample(a:Unit,b:@ComposableUnit)a()//允許b()我們將其稱之為"Composer"。Composer的實(shí)現(xiàn)包含了一個(gè)與GapBuffer(間隙緩沖區(qū))密切相關(guān)的數(shù)據(jù)結(jié)GapBuffer\h間隙緩沖區(qū)是一個(gè)含有當(dāng)前索引或游標(biāo)的集合,它在內(nèi)存中使用扁平數(shù)組(flatarray)實(shí)現(xiàn)。這一扁平數(shù)組比它一個(gè)正在執(zhí)行的Composable頂部并再次遍歷執(zhí)行。在我們執(zhí)行時(shí),可以選擇僅僅查看數(shù)據(jù)并且什么都不做,或是更新數(shù)據(jù)的值。我們也許會決定改變UI在了解此數(shù)據(jù)結(jié)構(gòu)時(shí),很重要的一點(diǎn)是除了移動間隙,它的所有其他操作包括獲取(get)、移動(mve)、插入(insert)、刪除(delete)都是常數(shù)時(shí)間操作。移動間隙的時(shí)間復(fù)雜度為O(n)。我們選擇這一數(shù)據(jù)結(jié)構(gòu)是因?yàn)閁I通常不會頻繁地改變。當(dāng)我們處理動態(tài)UI時(shí),它們的值雖然發(fā)生了改變,卻通常不會頻繁地改變結(jié)構(gòu)。當(dāng)它們確實(shí)需要改變結(jié)構(gòu)時(shí),則很可能需要做出大塊的改動,此時(shí)進(jìn)行O(n)的間隙移動操作便是一個(gè)很合理的權(quán)衡。讓我們來看一個(gè)計(jì)數(shù)器示例funCounter()varcountbyremember{mutableStateOf(0)}text="Count:$count",onPress={count+=1當(dāng)編譯器看到Composable首先,編譯器會添加一個(gè)composer.start方法的調(diào)用,并向其傳遞一個(gè)編譯時(shí)生成的整數(shù)keyfunfunCounter($composer:Composer)varcountbyremember{mutableStateOf(0)}text="Count:$count",onPress={count+=1}編譯器也會將composer對象傳遞到函數(shù)體里的所有composablefunfunCounter($composer:Composer)varcountbyremember($composer){mutableStateOf(0)}onPress={count+=1當(dāng)此composer執(zhí)行時(shí),它會進(jìn)行以下操作Composer.start被調(diào)用并存儲了一個(gè)組對象(groupobject)remember插入了一個(gè)組對象mutableStateOf的值被返回,而state實(shí)例會被存儲起來Button基于它的每個(gè)參數(shù)存儲了一個(gè)分組最后,當(dāng)我們到達(dá)composer.end時(shí)現(xiàn)在,所有這些組對象已經(jīng)占據(jù)了很多的空間,它們?yōu)槭裁匆紦?jù)這些空間呢?這些組對象是用來管理動態(tài)UI可能發(fā)生的移動和插入的。編譯器知道哪些代碼會改變UI下,編譯器不需要它們,所以它不會向插槽表(slottable)中插入過多的分組。為了說明一這點(diǎn),請您查看以下條件邏輯:@Composable@ComposablefunApp(){valresult=getData()if(result==null){}else{在這個(gè)Composable函數(shù)中,getData函數(shù)返回了一些結(jié)果并在某個(gè)情況下繪制了一個(gè)Loading函數(shù);而在另一個(gè)情況下,它繪制了Header和Body函數(shù)。編譯器會在iffunfunApp($composer:Composer){valresult=getData()if(result==null)}else讓我們假設(shè)這段代碼第一次執(zhí)行的結(jié)果是null函數(shù)第二次執(zhí)行時(shí),讓我們假設(shè)它的結(jié)果不再是null對composer.start的調(diào)用有一個(gè)key為456的分組。編譯器會看到插槽表中key為123所以此時(shí)它知道UI于是編譯器將縫隙移動至當(dāng)前游標(biāo)位置并使其在以前UI的位置進(jìn)行擴(kuò)展,從而有效地消除了舊的UI此時(shí),代碼已經(jīng)會像一般的情況一樣執(zhí)行,而且新的UI——header和body——在這種情況下,if語句的開銷為插槽表中的單個(gè)條目。通過插入單個(gè)組,我們可以在UI中任意實(shí)現(xiàn)控制流,同UIUI時(shí)利用這種類緩存的數(shù)據(jù)結(jié)構(gòu)。PositionalMemoizationComposePositionalMemoization(位置記憶化我們用它作為位置記憶化的示例:funApp(items:List<String>,query:String)valresults=items.filter{it.matches(query)//至對emember函數(shù)的調(diào)用中——emember函數(shù)知道如何利用插槽列表。emember函數(shù)會查看列表中的字符串,同時(shí)也會存儲列表并在插槽表中對其進(jìn)行查詢。過濾計(jì)算會在之后運(yùn)行,并且emember函數(shù)會在結(jié)果傳回之前對其進(jìn)行存儲。函數(shù)第二次執(zhí)行時(shí),emember變,過濾操作就會在跳過的同時(shí)將之前的結(jié)果返回。這便是位置記憶化。有趣的是,這一操作的開銷十分低廉:編譯器必須存儲一個(gè)先前的調(diào)用。這一計(jì)算可以發(fā)生在您的UI的各個(gè)地下面是remember的函數(shù)簽名,它可以接收任意多的輸入與一個(gè)calculationfun<T>remember(vararginputs:Any?,calculation:()->T):不過,這里沒有輸入時(shí)會產(chǎn)生一個(gè)有趣的退化情況。我們可以故意誤用這一API,比如記憶一個(gè)像Math.random這樣不輸出穩(wěn)定結(jié)果的計(jì)算:@Composable@ComposablefunApp()valx=remember{Math.random()//語義。每當(dāng)我們在Composable層級中使用App函數(shù)時(shí),都將會返回一個(gè)新的Math.andom值。不過,每次Composable被重新組合時(shí),它將會返回相同的Math.andom狀態(tài)成為可能。下面,讓我們用GoogleComposable函數(shù)來說明Composable數(shù)字作為參數(shù),并且通過調(diào)用AddessComposable函數(shù)來繪制地址。@Composable@ComposablefunGoogle(number:Int){city="MountainView",@ComposablefunAddress(number:Int,city:String,state:String,zip:String)Text(",")Text("")Compose將Composable函數(shù)的參數(shù)存儲在插槽表中。在本例中,我們可以看到一些冗余:Addess加的"MountainViw"與"C"會在下面的文本調(diào)用被再次存儲,所以這些字符串會被存儲兩次。我們可以在編譯器級為Composable函數(shù)添加staticfunfun$composer:number:Int)0b11110or($staticand0b1),street="AmphitheatrePkwy",city="MountainView",本例中,static需存儲該參數(shù)。所以這一Google函數(shù)示例中,編譯器傳遞了一個(gè)位字段來表示所有參數(shù)都不會發(fā)生改變。接下來,在Address函數(shù)中,編譯器可以執(zhí)行相同的操作并將參數(shù)傳遞給textfunfun$composer:$static:number:Int,street:city:String,state:String,zip:)Text($composer,($staticand0b11)and(($staticand0b10)shr1),"$number$street")Text($composer,($staticand0b100)shr2,city)Text($composer,0b1,",Text($composer,($staticand0b1000)shr3,state)Text($composer,0b1,"")Text($composer,($staticand0b10000)shr4,這些位操作邏輯難以閱讀且令人困惑,但我們也沒有必要理解它們:在Google樣一來,number參數(shù)便可以決定整個(gè)層級,它也是唯一一個(gè)需要編譯器進(jìn)行存儲的值。有賴于此,我們可以更進(jìn)一步,生成可以理解number是唯一一個(gè)會發(fā)生改變的值的代碼。接下來這段代碼可以在number沒有發(fā)生改變時(shí)直接跳過整個(gè)函數(shù)體,而我們也可以指導(dǎo)Composer到的位置。funfunnumber:Int)if(number==$composer.next()){city="MountainView",}elseComposer為了解釋重組是如何工作的,我們需要回到計(jì)數(shù)器的例子funfunCounter($composer:Composer)varcount=remember($composer){mutableStateOf(0)}onPress={count.value+=1編譯器為Counter函數(shù)生成的代碼含有一個(gè)composer.start和一個(gè)compose.end。每當(dāng)Counter行時(shí)就會理解:當(dāng)它調(diào)用count.value時(shí),它會讀取一個(gè)appmodel實(shí)例的屬性。在運(yùn)行時(shí),每當(dāng)我們調(diào)用compose.end,我們都可以選擇返回一個(gè)值。接下來,我們可以在該返回值上使用lambda來調(diào)用updateScope當(dāng)前的Composable。這一方法等同于LiveData接收的lambda參數(shù)。在這里使用問號的原因——可空的原因是因?yàn)槿绻覀冊趫?zhí)行Counter的過程中不讀取任何模型對象,則沒有理由告訴運(yùn)行時(shí)如何更新它,因?yàn)槲覀冎浪肋h(yuǎn)不會更新。您一定要記得的重要一點(diǎn)是,這些細(xì)節(jié)中的絕大部分只是實(shí)現(xiàn)細(xì)節(jié)。與標(biāo)準(zhǔn)的Kotlin函數(shù)相比,Composable函數(shù)具有不同的行為和功能。有時(shí)候理解如何實(shí)現(xiàn)十分有用,但是未來Composable而實(shí)現(xiàn)則有可能發(fā)生變化。同樣的,Compose說一千道一萬,不如自己動手試一下,使用之前需要重新配置一下新的AndroidStudio,必須使用新版本金絲雀版本的AndroidStudio,必須區(qū)分開必須使用kotlin語言,不支持Java創(chuàng)建New\h必須選擇EmptyCompose\h項(xiàng)目配置看到語言只支持Kotlin,而且最低版本要在5.0布局部分左側(cè)的Layout是,現(xiàn)在JetpackCompose還并不完善,預(yù)覽的話還得添加一個(gè)@Pviw才行。依賴的變化包體大小的變化這么多依賴下來,包體整體增加了6m左右,不過隨著手機(jī)的更新?lián)Q代,app完全能夠蓋過包體大小的影響。代碼編寫的變化可以看到setContentViw沒有了,取而代之的是使用代碼生成布局。以前使用xml是方便我們觀察布局的排列變化,如果使用代碼編寫也能達(dá)到這種效果,使用代碼性能肯定會比加載xml文件的要高。唯一麻煩的是我們需要一定事件去適應(yīng)這種方式,畢竟老的xml布局都寫了那么些年了,突然的改變肯定會有不適應(yīng),而且還需要去學(xué)習(xí)一下Kotlin,如果之前沒開發(fā)過。EditText數(shù)據(jù)不會變化我之前是這么寫的,發(fā)現(xiàn)輸入時(shí)日志有打印,但是EditTextpackagepackageimportandroid.util.Logimportandroidx.activity.ComponentActivityimportpose.setContentpose.foundation.layout.Boximportpose.foundation.layout.sizeimportpose.foundation.text.KeyboardOptionsimportpose.material.MaterialThemeimportpose.material.Surfaceimportpose.material.Textimportpose.material.TextFieldimportpose.runtime.Composableimportpose.runtime.getValueimportpose.runtime.mutableStateOfimportpose.runtime.setValueimportpose.ui.Alignmentimportimportpose.ui.text.input.KeyboardTypeimportpose.ui.tooling.preview.Previewimportpose.ui.unit.dpimportclassMainActivity:ComponentActivity()overridefunonCreate(savedInstanceState:Bundle?){setContentMyApplicationTheme//Asurfacecontainerusingthe'background'colorfromthethemeSurface(color=MaterialTheme.colors.background){funGreeting(name:String){Text(text="Hello$name!")funDefaultPreview(){privatefunEditText()vartext11:StringbymutableStateOf("")modifier=.size(300.dp,120.dp),)Log.e("lbs","EditText222")

modifier=onValueChange={Log.e("lbs",Log.e("lbs","EditText222$it")text11=itvalue=label={Text("CountdownSeconds")},maxLines=1,keyboardOptions=KeyboardOptions(keyboardType=后來發(fā)現(xiàn)不太對,需要將vartext11:StringbymutableStateOf("")提到全局才行,否則應(yīng)該是每次進(jìn)來都是新導(dǎo)入其他人寫的一個(gè)項(xiàng)目,在mumu模擬器報(bào)錯"Snapshotisnotopen"不知道為啥回去驗(yàn)證這個(gè)功03-2219:48:39.7511714-1714/com.example.androiddevchallengeE/AndroidRuntime:FATALEXCEPTION:mainProcess:com.example.androiddevchallenge,PID:1714java.lang.IllegalStateException:Snapshotisnotopenatatpose.runtime.snapshots.SnapshotKt.access$validateOpen(Snapshot.kt:1)pose.runtime.snapshots.MutableSnapshot.apply(Snapshot.kt:584)atatpose.runtime.Recomposer.access$applyAndCheck(Recomposer.kt:100)pose.runtime.Recomposer.performRecompose(Recomposer.kt:1003)atpose.runtime.Recomposer.access$performRecompose(Recomposer.kt:100)atandroid.view.Choreographer$CallbackRecord.run(Choreographer.java:856)atandroid.view.Choreographer.doCallbacks(Choreographer.java:670)atatandroid.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)atandroid.os.Handler.handleCallback(Handler.java:739)atandroid.os.Handler.dispatchMessage(Handler.java:95)atandroid.os.Handler.dispatchMessage(Handler.java:95)atandroid.os.Looper.loop(Looper.java:148)atandroid.app.ActivityThread.main(ActivityThread.java:5539)atjava.lang.reflect.Method.invoke(NativeMethod)aternal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)aternal.os.ZygoteInit.main(ZygoteInit.java:635)剛才已經(jīng)說了,項(xiàng)目要求非常簡單,只要包含一個(gè)小狗列表界面,以及每只小狗的詳細(xì)信息界面即可。首周的挑戰(zhàn)項(xiàng)目必須在3月3日下午3:59前完成并提交,因此如果你也有興趣的話,現(xiàn)在動手可能還來得及(時(shí)間完成的)。不管是使用JetpackCompose,還是使用傳統(tǒng)的寫法去實(shí)現(xiàn),首先你必須要擁有用于展示的數(shù)據(jù)才行。于是我在\h另外,你是不可以自己隨便創(chuàng)建一個(gè)項(xiàng)目就開始寫代碼的,Google編寫代碼才行。模板地址是:\h\h/android/android-dev-challenge-\h至于具體的代碼我就不貼出來了,因?yàn)榛径际荍etpackCompose相關(guān)的代碼,而我在本篇文章中是不準(zhǔn)備講解JetpackCompose的。最基本的項(xiàng)目要求都滿足了,如下圖所示:雖然代碼已經(jīng)寫完了,但是我在提交代碼時(shí)才意識到,Google的挑戰(zhàn)賽項(xiàng)目并沒有那么容易。因?yàn)镚oogle我自認(rèn)為自己平時(shí)的編程風(fēng)格是非常規(guī)范的,并且微軟也有這種類似的代碼審核機(jī)制,但完全沒有Google的這套嚴(yán)格。在Google的這套規(guī)則中,每個(gè)類的頭部都要按照固定的格式聲明版權(quán)。代碼中import的包不能使用*通配寫一個(gè)空格,少加一個(gè)換行都會導(dǎo)致編譯失敗。然沒有一次代碼是可以編譯通過的,簡直快把我搞崩潰了。后來重新打開了這個(gè)挑戰(zhàn)項(xiàng)目的GitHub原來項(xiàng)目中會使用Spotless來檢查我們的代碼是否規(guī)范。雖然我并不知道Spotless聽起來就感覺很變態(tài),因?yàn)樗峭昝罒o暇的意思。也就是說,Google會用這個(gè)工具來檢查我們的代碼是否完美無不過好在它還是給了我們一個(gè)簡單的解決方案,運(yùn)行g(shù)radlewapp:spotlessApply命令可以自動將所有代碼按照議大家使用Ctrl+Shift+L來格式化代碼一樣。因?yàn)橛眠@種快捷的方式可能會導(dǎo)致許多并非你自己編寫的代碼發(fā)生變動,從而對以后追查代碼的變動歷史造成很大的困擾。最后,我們還需要按照Google給出的模板修改README.md填內(nèi)容就行了。隨便寫點(diǎn)項(xiàng)目描述,以及這個(gè)項(xiàng)目有什么優(yōu)勢即可。注意,這個(gè)主頁的頂部有個(gè)Checkpassing可以迅速看出你提交的代碼有沒有通過Google的檢查。\hhttp://goo.gle/dev-challenge-week-1-令我沒想到的是,在提交作品時(shí),Google于是我又去找回了多年沒有使用過的Twitter有3周的比賽,并且后面獎品只會更加豐富。我可能就做不出來了。所以走一步看一步吧。如果是想要借助這個(gè)項(xiàng)目來學(xué)習(xí)JetpackCompose的朋友,也可以參考一下我的實(shí)現(xiàn),源碼地址是:\h雖然是個(gè)小項(xiàng)目,但Compose來,效果如下:TimerViewModel////Maxinputlengthlimit,it'susedtopreventnumbergrowstoobig.constvalMAX_INPUT_LENGTH=5classTimerViewModel:ViewModel()*TotaltimeusersetinvartotalTime:LongbyTimeleftduringcountdowninvartimeLeft:LongbyUpdatevaluewhenEditTextcontent@paramtextnewcontentinfunupdateValue(text:String)//Justincasethenumberistooif(text.length>MAX_INPUT_LENGTH)//Removenon-numericvarvalue=text.replace("\\D".toRegex(),//Zerocannotappearinthefirstif(value.startsWith("0"))value=//SetadefaultvaluetopreventNumberFormatExceptionif(value.isBlank())value="0"totalTime=value.toLong()timeLeft=value.toLong()其中,updateValueTimerViewModeltotalTime為了防止數(shù)字過大,我們只允許用戶輸入5位數(shù),并且用正則表達(dá)式過濾掉用戶輸入的小數(shù)點(diǎn)、負(fù)號、逗號分隔符等非數(shù)值。并且數(shù)字首位不允許出現(xiàn)0。經(jīng)過層層處理,將安全的數(shù)值賦給totalTime和timeLeft。實(shí)現(xiàn)倒計(jì)時(shí)有很多種方式,比如我們熟悉的handler.postDelayed的方式在協(xié)程中repeat+delay的方式使用ValueAnimator的方式我采用的是第三種方式,因?yàn)閯赢嬒鄬碚f較容易控制,pause、esume、cancel便的實(shí)現(xiàn)暫停、繼續(xù)、停止等功能。AnimatorControllerclassclassAnimatorController(privatevalviewModel:TimerViewModel)privatevarvalueAnimator:ValueAnimator?=funstart()if(viewModel.totalTime==0L)returnif(valueAnimator==null){//Animator:totalTime->valueAnimator=ValueAnimator.ofInt(viewModel.totalTime.toInt(),0)valueAnimator?.interpolator=LinearInterpolator()//UpdatetimeLeftinViewModelvalueAnimator?.addUpdateListener{viewModel.timeLeftviewModel.timeLeft=(it.animatedValueasvalueAnimator?.addListener(object:AnimatorListenerAdapter(){overridefunonAnimationEnd(animation:Animator?){}elsevalueAnimator?.setIntValues(viewModel.totalTime.toInt(),//(LinearInterpolator+duration)aimtosettheintervalas1second.valueAnimator?.duration=viewModel.totalTime*1000Lfunpause()funresume(){funstop()viewModel.timeLeft=0funcomplete(){viewModel.totalTime=0在這個(gè)類中,我們處理了動畫的啟動、暫停、恢復(fù)、停止和完成五個(gè)功能。通過將ValueAnimatortotalTime秒內(nèi)從totalTime線性變化到0的方式設(shè)置出動畫的間隔時(shí)間為1s為了方便使用,我們將創(chuàng)建的AnimatorController對象放到TimerViewModelclassclassTimerViewModel:ViewModel()varanimatorController=分析可知,倒計(jì)時(shí)App可分為四個(gè)狀態(tài):尚未開始已經(jīng)開始暫停完成由此,我們很容易想到用狀態(tài)模式來設(shè)計(jì)此App,只要為每個(gè)狀態(tài)創(chuàng)建一個(gè)狀態(tài)類,就可以減少大量的if-when狀態(tài)模式(StatePattern):在不同狀態(tài)下,App的表現(xiàn)和行為是不同的,我們先將這些不同的部分提取到接口中,大致有如下幾個(gè)方法:interfaceinterfaceIStatusThecontentstringdisplayedinStartinclude:Start,Pause,funstartButtonDisplayString():ThebehaviourwhenclickStartfunStopButtonenablefunstopButtonEnabled():ThebehaviourwhenclickStopfunShoworhidefunshowEditText():接口中抽出了五個(gè)函數(shù),對應(yīng)AppfunstartButtonDisplayString():StringStart按鈕上的文字顯示,在尚未開始/完成狀態(tài)下,按鈕“Start”“Pause”,在暫停狀態(tài)下,按鈕顯示文字為funclickStartButton()用于控制Start按鈕的點(diǎn)擊事件,在尚未開始/完成狀態(tài)下,點(diǎn)擊Start按鈕啟動ValueAnimatorStartValueAnimatorStart按鈕恢復(fù)ValueAnimator。funstopButtonEnabled():BooleanStop按鈕是否可點(diǎn)擊,在尚未開始/完成狀態(tài)下,Stop按鈕不可點(diǎn)擊,在已經(jīng)開始/暫停狀態(tài)下,Stop按鈕可點(diǎn)擊。funclickStopButton()Stop按鈕的點(diǎn)擊事件,在尚未開始/完成狀態(tài)下,Stop按鈕不可點(diǎn)擊,點(diǎn)擊事件為空,在已經(jīng)開始/暫停狀態(tài)下,點(diǎn)擊Stop按鈕停止ValueAnimator。funshowEditText():Boolean用于控制EditText的顯示和隱藏,在尚未開始/完成狀態(tài)下,EditText顯示,在已經(jīng)開始/暫停狀態(tài)下,EditText隱藏。classclassNotStartedStatus(privatevalviewModel:TimerViewModel):IStatusoverridefunstartButtonDisplayString()=overridefunclickStartButton()=overridefunstopButtonEnabled()=overridefunclickStopButton()overridefunshowEditText()=classclassStartedStatus(privatevalviewModel:TimerViewModel):IStatusoverridefunstartButtonDisplayString()=overridefunclickStartButton()=overridefunstopButtonEnabled()=overridefunclickStopButton()=overridefunshowEditText()=classclassPausedStatus(privatevalviewModel:TimerViewModel):IStatusoverridefunstartButtonDisplayString()=overridefunclickStartButton()=overridefunstopButtonEnabled()=overridefunclickStopButton()=overridefunshowEditText()=classclassCompletedStatus

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論