公眾號「稀有猿訴」 ?????? 原文鏈接 降Compose十八掌之『見龍在田』| Modifier
通過前面的文章我們學會了如何使用元素來構建和填充我們的UI頁面,但這只完成了一半,元素還需要裝飾,以及進行動畫和事件響應,這才能生成完整的UI。這就要用到Modifier,Jetpack Compose中的靈魂,它被用來裝飾和增強Composables,讓一個個平凡的元素變成鮮活的,好看的,可交互UI。我們來具體的看一下Modifier的使用方法。
對于Compose來說,每一個元素叫做Composable,它是一個函數,比如前面學過的布局(如Row)和小部件(如Text)等都是一個Composable,可以把它理解成為一個元素。
概念和基本使用方法
Modifier之于元素,猶如CSS之于HTML,但能做的更多,因為除了樣式裝飾以外,Modifier還能做很多事情,比如響應用戶輸入。
每一個Composable都可以接收一個Modifier參數,準確地說第一個參數都是Modifier,可以把一個Modifier對象作為第一個參數傳給元素;也可以用命名參數如modifier = Modifier.padding(8.dp)。Modifier支持鏈式調用,它的每個方法都會把當前對象返回:
@Composable
private fun Greeting(name: String) {Column(modifier = Modifier.padding(24.dp).fillMaxWidth()) {Text(text = "Hello,")Text(text = name)}
}
裝飾元素的樣式
重點來看一下如何用Modifier裝飾元素的樣式。需要注意的是Modifier是裝飾元素的共性樣式如尺寸,背景,邊框和位移等等,而像具體元素特性,如Text中的文本樣式,是無法用Modifier來修改的。
尺寸Size
尺寸對于一個UI元素來說是必要的,就是渲染之后的視覺上的寬度和高度。可以通過Modifier的下列函數來進行尺寸約束:
- width/height - 指定固定的偏好寬度和偏好高度,參數傳入具體的數值如Modifier.width(320.dp).height(480.dp),就是指定某個元素的寬度是320dp,高度是480dp。偏好(preferred)的意思是可能會被其他約束條件覆蓋,而最終值可能會不一樣。
- size - 同時指定寬度和高度為某一偏好的數值,只傳一個參數就是一個正方形,如Modifier.size(100.dp),等同于Modifier.width(100.dp).height(100.dp);傳兩個參數時分別指定寬和高,如Modifier.size(320.dp, 480.dp)等同于Modifier.width(320.dp).height(480.dp)。
- wrapContentWidth/wrapContentHeight/wrapContentSize - 讓元素自己決定尺寸,無視最小尺寸限制(minimum_wdith/minimum_height),類似于XML中的wrap_content,這是比較嚴格的限制。
- requiredWidth/requiredHeight - 指定寬度和高度必須為某一數值,不可以被其他限制約束覆蓋。帶有In的函數可以指定一個范圍。
- requiredSize - 指定寬度和高度必須為某一數值,帶有In的函數可以指定范圍。
- widthIn/heightIn/sizeIn - 帶有in的函數,可以指定一個范圍而非具體數值,比如Modifier.widthIn(10.dp, 100.dp),就是說限制此元素的寬度為10dp到100dp之間。
- fillMaxWidth/fillMaxHeight - 不固定具體的數值,按比例填充最大可用空間,比例為1.0時填滿,類似于XML中的match_parent。
- fillMaxSize - 按比例填充滿可用空間。
- weight - 權重比例,僅在父布局是Row或者Column時,且尺寸與父布局約束一致時有效,比如在Row中,對width生效,在Column中對height生效。最終的占比是『權重比例 x 可用空間』,不指定weight則weight是0,比如一個Row中,有三個元素,其中兩個元素A和B指定了weight為1和2,另一個沒指定,那么A將占Row中剩余可用寬度的1/3,B將占2/3。與View中的LinearLayout中的權重是差不多的。
Modifier修飾尺寸的函數比較多,容易學雜了,需要梳理一下:size相當于快捷方式,可以同時約束寬和高;帶有In的函數可以為某個約束指定范圍。寬高是一種限制性約束,不同的函數的限制嚴格性是不一樣的,帶有required是最嚴格的限制約束,優先級最高,如有沖突,以它為準;wrapContentWidth/wrapContentHeight/wrapContentSize是較嚴格的限制,僅次于required;width/height/size是中等嚴格,較wrapContent再次之;weight再次之;fillMax則是最弱的限制。
間隔
間隔(padding)是在元素與其邊界之間添加的額外的空白空間,Modifier.padding不影響元素的尺寸,它是在測量之前就應用生效。
對于有XML經驗的同學來說,以往的間隔有兩個,一個是margin控制著元素邊界之外的間隔,padding控制著邊界與元素本身內容間隔。對于Modifier來說,只有一個函數,在不同的位置調用padding函數會有不同的效果,如果在尺寸之后調用padding,則是調整邊界與內容之間的間隔,如果是在尺寸之前調用,則是調整邊界與外部的間隔。另外,就是padding不可以傳負值。
@Composable
fun PaddingDemo(modifier: Modifier = Modifier.fillMaxSize()) {Box(modifier = Modifier.fillMaxSize().background(Color.LightGray)) {Text(text = "降龍十八掌",style = MaterialTheme.typography.headlineLarge,modifier = Modifier.padding(16.dp) // As margin: outside space beyond border.background(Color.Cyan).size(360.dp, 120.dp).padding(10.dp) // As padding: space between border and content)}
}
位移Offset
函數Modifier.offset用來給水平和垂直方向加一個位移,數值可正可負,注意僅是增加位移,并不會改變元素的尺寸。與View中的translateX和translateY是類似的。它有兩個函數,只有一個參數時是水平和垂直方向都加上相同的位移;兩個參數時是分別指定水平方向和垂直方向。
@Composable
fun ModifierDemo(modifier: Modifier = Modifier.fillMaxSize()) {var offset by remember { mutableStateOf(0) }Column(modifier = modifier.padding(8.dp).clickable { offset += 8 }) {Text(text = "降龍十八掌",style = MaterialTheme.typography.headlineLarge,color = MaterialTheme.colorScheme.primary,modifier = Modifier.offset(offset.dp, offset.dp).background(Color.Cyan).padding(16.dp))}
}
因為offset并不改變元素的尺寸,僅是在原位置上進行偏移,所以多用于點擊效果,或者點擊動畫。
背景Background
使用Modifier.background函數來修改元素的背景顏色,唯一需要注意的是padding的影響,background是給尺寸所指定的區域加背景色,所以padding調用的位置會有影響。
邊框Border
用Modifier.bodrer函數可以指定邊框的樣式,如形狀,粗細,線條和顏色。需要注意的是它也是跟尺寸一樣的,受padding的影響:
@Composable
fun BorderWithShape(modifier: Modifier = Modifier.fillMaxSize()) {Box {Text(text = "降龍十八掌",style = MaterialTheme.typography.headlineLarge,modifier = Modifier.padding(10.dp).border(2.dp, SolidColor(Color.Green), RoundedCornerShape(20.dp)).padding(10.dp))}
}
變幻
Modifier還能對元素進行一些變幻,如透明度(Alpha),旋轉(Rotate)和縮放(Scale)。通常用來實現一些非動畫的靜態特效。
Modifier.alpha指定透明度,0是透明,1是完全不透明,默認值是1.0。
Modifier.rotate實現旋轉,參數是一個角度,順時針旋轉為正值,逆時針旋轉為負值,默認值是0度。
Modifier.scale是以元素的幾何中心為中心點進行縮放負值會進行水平和垂直方向翻轉:
@Composable
fun TransformationDemo(modifier: Modifier = Modifier.fillMaxSize()) {Box(modifier = Modifier.fillMaxSize().background(Color.LightGray)) {Text(text = "降龍十八掌",style = MaterialTheme.typography.headlineLarge,color = MaterialTheme.colorScheme.primary,modifier = Modifier.padding(16.dp).alpha(0.618f).rotate(11.8f).scale(0.618f).background(Color.Cyan).size(360.dp, 120.dp).offset(10.dp, 10.dp))}
}
事件監聽
Modifier.clickable函數用來指定點擊事件響應。此外,還可以用于指定元素是否可以點擊。
另外,還可以通過Modifier.scrollable來指定元素是否可以滑動。
最佳實踐
Modifier是非常強大的,也是非常復雜的,前面列出的都是最為常用的一些函數。接下再來學習一下使用Modifier時需要注意的事項。
順序很重要
Modifier有很多很多函數,修改著同一個對象實例,有些函數會相互影響,因此這些函數的調用順序就變得相當重要,特別是涉及尺寸強相關的裝飾特性時,如background和border,它們會受到padding以及變幻的影響。比如前面變幻小節的例子,可以試著修改函數的調用順序,就會發現結果會不一樣。在實例使用時,如果出現與預期不一致的結果時,就嘗試調整一下Modifiier函數的順序,看是否是順序 導致的。
留意上下文
在Compose中,有一些Modifier函數只能在特定的元素中使用,這就涉及了Compose上下文(Scope)。比如說像Modifier.align只能在BoxScope中使用,也就是說只能在Box的子元素中使用。對其他任何布局來說都是不能用的,所以在使用的時候也要注意元素所在的父布局。
盡可能的復用
每個元素在渲染的時候都需要一個Modifier對象,通常情況下都是通過Modifier的函數進行對象創建。但對于一些循環的場景,且Modifier對象沒啥變化 時,這時就應該復用對象,而非每次都創建。比如說動畫,以及像集合性布局的子布局,這時都應該在其父布局緩存Modifier對象,直接傳給子元素,而不是讓其每次都創建新對象:
val modifier = Modifier.padding(12.dp).background(Color.Gray)
@Composable
fun LoadingWheelAnimation() {val animatedState = animateFloatAsState(/*...*/)LoadingWheel(// 把Modifier對象緩存到上一級的父布局中,以免每幀動畫都創建一個Modifier對象modifier = reusableModifier,animatedState = animatedState)
}
對于集合性布局也最好是能復用Modifier對象:
val reusableItemModifier = Modifier.padding(bottom = 12.dp).size(216.dp).clip(CircleShape)@Composable
private fun AuthorList(authors: List<Author>) {LazyColumn {items(authors) {AsyncImage(// 提升到上一級中時進行緩存,而不是每次都創建modifier = reusableItemModifier,)}}
}
保持一致性
一致性對于代碼的可維護性和可擴展性是非常重要的,因為每個元素都需要Modifier對象,在Compose中到處都可以看到Modifier,在實際使用中保持一致性就非常重要。比如說Modifier要作為Composable的第一個參數,參數的命名應該是modifier,并且最好要有默認值,可以查閱Compose本身的代碼,可以發現其所有的元素都遵循此約定。另外,就是對于共性的裝飾要提升到父布局中進行統一設定,比如說根布局統一設定padding,而不是每個子布局進行分別設定,等等。
參考資料
- Compose modifiers
- 6. Jetpack Compose Modifiers
- 6. Using Compose Modifiers
歡迎搜索并關注 公眾號「稀有猿訴」 獲取更多的優質文章!
保護原創,請勿轉載!