Jetpack Compose是Google I/O 2019 發佈的Andorid UI框架,它不一樣於Andorid常見的Xml+命令式Coding的UI開發範式,而是基於Kotlin的DSL實現了一套相似React的聲明式UI框架。Jetpack Compose目前仍然處於Alpha版本,目標是2020年可以發佈穩定的Beta版本。伴隨React Native、Flutter等大前端框架的興起以及Jetpack Compose、SwiftUI等native框架的出現,聲明式UI正逐漸成爲客戶端UI開發的新趨勢。前端
特色:java
因爲Jetpack Compose還未正式發佈,須要下載最新Canary版的Android Studio 預覽版node
如下三種方式可初步體驗:python
基於現狀,我主要介紹第三種方式:android
配置Kotlingit
plugins {
id 'org.jetbrains.kotlin.android'
version '1.4.0'
}
複製代碼
配置Gradlegithub
android {
defaultConfig {
...
minSdkVersion 21
}
buildFeatures {
// Enables Jetpack Compose for this module
compose true
} ...
// Set both the Java and Kotlin compilers to target Java 8.
compileOptions {
sourceCompatibility
JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
useIR = true
}
composeOptions {
kotlinCompilerVersion '1.4.0'
kotlinCompilerExtensionVersion '1.0.0-alpha05'
}
}
複製代碼
添加工具包依賴項編程
dependencies {
implementation 'androidx.compose.ui:ui:1.0.0-alpha05'
// Tooling support (Previews, etc.)
implementation 'androidx.ui:ui-tooling:1.0.0-alpha05'
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation 'androidx.compose.foundation:foundation:1.0.0-alpha05'
// Material Design
implementation 'androidx.compose.material:material:1.0.0-alpha05'
// Material design icons
implementation 'androidx.compose.material:material-icons-core:1.0.0-alpha05'
implementation 'androidx.compose.material:material-icons-extended:1.0.0-alpha05'
// Integration with observables
implementation 'androidx.compose.runtime:runtime-livedata:1.0.0-alpha05'
implementation 'androidx.compose.runtime:runtime-rxjava2:1.0.0-alpha05'
// UI Tests androidTestImplementation 'androidx.ui:ui-test:1.0.0-alpha05' }
複製代碼
Jetpack Compose包含了基本組件(compose.ui)、Material Design 組件(compose.material)、動畫組件(compose.animation)等衆多UI組件,在此我就不贅述了,在對應的文檔中你們均可以參閱,此處我重點講解一下關於Compose的關鍵點緩存
全部關於構建View的方法都必須添加@Compose的註解才能夠。而且@Compose跟協程的Suspend的使用方法比較相似,被@Compose的註解的方法只能在一樣被@Comopse註解的方法中才能被調用。前端框架
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
複製代碼
經常使用用參數以下:
name: String
: 爲該Preview命名,該名字會在佈局預覽中顯示。showBackground: Boolean
: 是否顯示背景,true爲顯示。backgroundColor: Long
: 設置背景的顏色。showDecoration: Boolean
: 是否顯示Statusbar和Toolbar,true爲顯示。group: String
: 爲該Preview設置group名字,能夠在UI中以group爲單位顯示。fontScale: Float
: 能夠在預覽中對字體放大,範圍是從0.01。widthDp: Int
: 在Compose中渲染的最大寬度,單位爲dp。heightDp: Int
: 在Compose中渲染的最大高度,單位爲dp。上面的參數都是可選參數,還有像背景設置等的參數並非對實際的App進行設置,只是對Preview中的背景進行設置,爲了更容易看清佈局。
@Preview(showBackground = true)
@Composable fun DefaultPreview() {
ComposeDemoTheme {
Greeting("Android")
}
}
複製代碼
當更改跟UI相關的代碼時,會顯示以下圖的一個橫條通知,點擊Build&Refresh便可更新顯示所更改代碼的UI。
該方法做用是和zaiLayout/View中的setContentView是同樣的。setContent的方法也是有@Compose註解的方法。因此,在setContent中寫入關於UI的@Compopse方法,便可在Activity中顯示。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello world!")
}
}
複製代碼
該類
是各個Compose
的UI組件必定會用到的一個類。它是被用於設置UI的擺放位置,padding等修飾信息的類。關於Modifier
相關的設置較多,在這裏只介紹會常常用到的。
Modifier.padding(10.dp) // 給上下左右設置成同一個值
Modifier.padding(10.dp, 11.dp, 12.dp, 13.dp) // 分別爲上下左右設值
Modifier.padding(10.dp, 11.dp) // 分別爲上下和左右設值
Modifier.padding(InnerPadding(10.dp, 11.dp, 12.dp, 13.dp))// 分別爲上下左右設值
Modifier.fillMaxHeight() // 填充整個寬度
Modifier.fillMaxHeight() // 填充整個高度
Modifier.fillMaxSize() // 填充整個寬度和高度
Modifier.width(2.dp) // 設置寬度
Modifier.height(3.dp) // 設置高度
Modifier.size(4.dp, 5.dp) // 設置高度和寬度
Modifier.widthIn(2.dp) // 設置最大寬度
Modifier.heightIn(3.dp) // 設置最大高度
Modifier.sizeIn(4.dp, 5.dp, 6.dp, 7.dp) // 設置最大最小的寬度和高度
Modifier.gravity(Alignment.CenterHorizontally) // 橫向居中
Modifier.gravity(Alignment.Start) // 橫向居左
Modifier.gravity(Alignment.End) // 橫向居右
Modifier.rtl // 從右到左 Modifier.ltr // 從左到右
Modifier.plus(otherModifier) // 把otherModifier的信息加入到現有的modifier中
// Modifier的方法都返回Modifier的實例的鏈式調用,因此只要連續調用想要使用的方法便可。
@Composable fun Greeting(name: String) {
Text(text = "Hello $name!", modifier = Modifier.padding(20.dp).fillMaxWidth())
}
複製代碼
Column和Row能夠理解爲在View/Layout體系中的縱向和橫向的ViewGroup
Column(
verticalArrangement:Arrangement // 控制縱向佈局關係
horizontalAlignment:Alignment // 控制橫向對齊關係
)
Row(
horizontalArrangement:Alignment // 控制橫向佈局關係
verticalAlignment:Arrangement // 控制縱向對齊關係
)
@Composable
fun TestColumnRow() {
Column(
modifier = Modifier.fillMaxHeight().background(Color.Yellow),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.Start
) {
Text(text = "java")
Text(text = "android")
Text(text = "python")
}
Row(
modifier = Modifier.fillMaxWidth().background(Color.LightGray),
verticalAlignment = Alignment.Top,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = "java")
Text(text = "android")
Text(text = "python")
}
}
複製代碼
全部 Android 應用都有核心界面更新循環,以下所示:
Compose 專爲單向數據流而打造。這是一種狀態向下流動而事件向上流動的設計。
使用單向數據流的應用的界面更新循環以下所示:
事件:事件由界面的一部分生成而且向上傳遞。
更新狀態:事件處理腳本能夠更改狀態。
顯示狀態:狀態會向下傳遞,界面會觀察新狀態並顯示該狀態。
舉兩個例子展現:
//內部狀態管理
@Composable
fun CounterInner() {
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value += 1 })
{
Text(text = "Count: ${count.value}")
}
}
複製代碼
解釋一下上圖的數據流狀況
事件:當點擊發生時候,會觸發count.value
更新狀態:mutableStateOf會進行處理,而後設置count的狀態
顯示狀態:系統會調用count的觀察器,而且界面會顯示新狀態
//支持其餘可觀察類型的狀態管理
class CountViewModel : ViewModel() {
// LiveData holds state which is observed by the UI
// (state flows down from ViewModel)
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
// onNameChanged is an event we're defining that the UI can invoke
// (events flow up from UI)
fun onCountChanged(newCount: Int) {
_count.value = newCount
}
}
@Composable
fun Counter(countViewModel: CountViewModel = viewModel()) {
val observeAsState = countViewModel.count.observeAsState(0)
val count = observeAsState.value
Button(
colors = ButtonConstants.defaultButtonColors(backgroundColor = if (count > 5) Color.Green else Color.White),
onClick = { countViewModel.onCountChanged(count + 1) },
) {
Text(text = "I've been clicked $count times")
}
}
複製代碼
解釋一下上圖的數據流狀況
事件:當點擊發生時候,會觸發onCountChanged
更新狀態:onCountChanged會進行處理,而後設置_count的狀態
顯示狀態:系統會調用count的觀察器,而且界面會顯示新狀態
@Composable
fun Counter(countViewModel: CountViewModel = viewModel()) {
val observeAsState = countViewModel.count.observeAsState(0)
val count = observeAsState.value
ButtonCount(count = count, onCountChanged = { countViewModel.onCountChanged(it) })
}
@Composable
fun ButtonCount( /* state */ count: Int, /* event */ onCountChanged: (Int) -> Unit ) {
Button(
colors = ButtonConstants.defaultButtonColors(backgroundColor = if (count > 5) Color.Green else Color.White),
onClick = { onCountChanged(count + 1) },
) {
Text(text = "I've been clicked $count times")
}
}
複製代碼
若是想使用Compose的狀況下,又不想遷移整個應用,能夠在xml裏面增長ComposeView,相似於佔位符,而後在Actviity/fragment中尋找該控件並調用setContent方法便可,在該方法中便可使用compose相關屬性
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".AndroidViewComposeActivity">
<TextView android:id="@+id/hello_world" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Hello Android!" app:layout_constraintTop_toTopOf="parent" />
<androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view_text" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/hello_world" />
<androidx.compose.ui.platform.ComposeView android:id="@+id/compose_view_img" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/compose_view_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
複製代碼
class AndroidViewComposeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_android_view_compose)
findViewById<ComposeView>(R.id.compose_view_text).setContent {
MaterialTheme {
Text("Hello Compose!")
}
}
findViewById<ComposeView>(R.id.compose_view_img).setContent {
val imageResource = imageResource(id = R.drawable.header)
val imageModifier = Modifier.preferredHeight(180.dp)
.fillMaxWidth()
.padding(16.dp)
.clip(RoundedCornerShape(4.dp))
MaterialTheme {
Image(
bitmap = imageResource,
modifier = imageModifier,
contentScale = ContentScale.Crop
)
}
}
}
}
複製代碼
若是碰到在Compose環境中,想要使用Android的View視圖的狀況,只須要使用AndroidView函數便可
@Composable
fun CustomView() {
val selectedItem = remember { mutableStateOf(0) }
val context = AmbientContext.current
val customView = remember {
// Creates custom view
Button(context).apply {
// Sets up listeners for View -> Compose communication
setOnClickListener {
selectedItem.value += 1
}
}
}
// Adds view to Compose
AndroidView({ customView }) {
view ->
// View's been inflated - add logic here if necessary
// As selectedItem is read here, AndroidView will recompose
// whenever the state changes
// Example of Compose -> View communication
view.text = selectedItem.value.toString()
}
}
複製代碼
若是是須要使用xml的配置狀況,也使用AndroidView函數便可
@Composable
fun CustomView2() {
val context = AmbientContext.current
val customView = remember {
// Creates custom view
View.inflate(context, R.layout.layout_custom_view, null)
}
AndroidView({ customView })
}
複製代碼
從源碼可看出,viewmodel函數底層也是經過ViewModelProvider進行獲取的
@Composable
fun <VM : ViewModel> viewModel( modelClass: Class<VM>, key: String? = null, factory: ViewModelProvider.Factory? = null )
: VM = AmbientViewModelStoreOwner.current.get(modelClass, key, factory)
複製代碼
Compose也是適配Android主流的基於流的方案,如
在Compose中,LiveData.observeAsState()獲取的State對象賦值給Text
@Composable
fun HelloScreen(helloViewModel: HelloViewModel = viewModel()) {
// by default, viewModel() follows the Lifecycle as the Activity or Fragment
// that calls HelloScreen(). This lifecycle can be modified by callers of HelloScreen.
// name is the _current_ value of [helloViewModel.name]
// with an initial value of ""
val observeAsState = helloViewModel.name.observeAsState("")
Column {
Text(text = observeAsState.value)
TextField(
value = observeAsState.value,
onValueChange = { helloViewModel.onNameChanged(it) },
label = { Text("Name") }
)
}
}
複製代碼
此處須要補充說明的是Compose的生命週期
Compose經過一系列Effect方法,實現生命週期函數
Compose生命週期 | 說明 | 對應React |
---|---|---|
onActive | compose函數第一次被渲染到畫面 | componentWillMount componentDidMount |
onDispose | compose函數從畫面上移除 | componentWillUnmount |
onCommit | compose函數每次執行,畫面從新渲染 | componentDidUpdate |
因此onCommit函數的使用相似於React的useEffect,支持可觀察函數
@Suppress("ComposableNaming")
@Composable
/*inline*/
fun </*reified*/ V1> onCommit( v1: V1, /*noinline*/ callback: CommitScope.() -> Unit ) {
remember(v1) { PreCommitScopeImpl(callback) }
}
複製代碼
僅當v1發生變化時onCommit纔會執行
舉個例子使用異步操做
@Composable fun fetchImage(url: String): ImageAsset? {
// Holds our current image, and will be updated by the onCommit lambda below
var image by remember(url) { mutableStateOf<ImageAsset?>(null) }
onCommit(url) {
// This onCommit lambda will be invoked every time url changes
val listener = object : ExampleImageLoader.Listener() {
override fun onSuccess(bitmap: Bitmap) {
// When the image successfully loads, update our image state
image = bitmap.asImageAsset()
}
}
// Now execute the image loader
val imageLoader = ExampleImageLoader.get()
imageLoader.load(url).into(listener)
onDispose {
// If we leave composition, cancel any pending requests
imageLoader.cancel(listener)
}
}
// Return the state-backed image property. Any callers of this function
// will be recomposed once the image finishes loading
return image
}
複製代碼
由於代碼是基於Kotlin註解動態生成的,查看方法能夠先build一個apk,而後查看其中的classess.dex文件,使用dex2jar轉爲jar包,而後使用jd-gui進行查看,下圖是反編譯獲得的源碼
//CountActivityKt.class->CountActivity->CounterInner(Composer,int):void
public static final void CounterInner(Composer<?> paramComposer, int paramInt) {
paramComposer.startRestartGroup(-908461591,"C(CounterInner)47@2322L30,48@2374L20,48@2357L91:CountActivity.kt#ffoge4");
if (paramInt != 0 || !paramComposer.getSkipping()) {
paramComposer.startReplaceableGroup(-3687207, "C(remember):Remember.kt#9igjgp");
Object object = paramComposer.nextSlot();
if (object == SlotTableKt.getEMPTY()) {
object = MutableStateKt.mutableStateOf$default(Integer.valueOf(LiveLiterals$CountActivityKt.INSTANCE.Int$arg-0$call-mutableStateOf$fun-$anonymous$$arg-0$call-remember$val-count$fun-CounterInner()), null, 2, null); paramComposer.updateValue(object);
}
paramComposer.endReplaceableGroup();
MutableState<Integer> mutableState = (MutableState)object; paramComposer.startReplaceableGroup(-3686846, "C(remember)P(1):Remember.kt#9igjgp");
boolean bool = paramComposer.changed(mutableState);
object = paramComposer.nextSlot();
if (object == SlotTableKt.getEMPTY() || bool) {
object = new CountActivityKt$CounterInner$1$1(mutableState);
paramComposer.updateValue(object);
}
paramComposer.endReplaceableGroup();
ButtonKt
.Button((Function0)object, null, false, null, null, null, null, null, null,(Function3)ComposableLambdaKt.composableLambda(paramComposer, -819892270, true, "C49@2406L36:CountActivity.kt#ffoge4", new CountActivityKt$CounterInner$2(mutableState)), paramComposer, 805306368, 510);
} else {
paramComposer.skipToGroupEnd();
}
ScopeUpdateScope scopeUpdateScope = paramComposer.endRestartGroup();
if (scopeUpdateScope == null)
return;
scopeUpdateScope.updateScope(new CountActivityKt$CounterInner$3(paramInt));
}
複製代碼
仔細查看源碼可知