Jetpack Compose學習(6)——關於Modifier的妙用

one發表於2021-10-03

原文: Jetpack Compose學習(6)——關於Modifier的妙用 | Stars-One的雜貨小窩

之前學習記錄中也是陸陸續續地將常用的Modifier的方法穿插進去了,本期就來詳細的講解下關於modifier的使用

限於篇幅,我是以常用的屬性來講解,漏講了一些請見諒,畢竟方法真的太多了,之後可能有用的新的效果,會穿插地講些

本系列以往文章請檢視此分類連結Jetpack compose學習

基本使用

我們直接以個簡單的例子講解下使用,設定Box佈局的寬高各100dp,且內邊距為16dp,背景色為綠色,程式碼如下

Column {
    Box(
        Modifier
            .size(100.dp)
            .background(Color.Green)
            .padding(16.dp)
    )
    Box(
        Modifier
            .size(100.dp)
            .padding(16.dp)
            .background(Color.Green)
    )
}

由上面程式碼和效果可以看到,modifier中的順序不同會導致效果不同,這是因為Modifier的設計如此

如果我們先設定背景色,之後再設定padding,那麼padding也是在綠色背景的基礎上進行的,所以,就是圖中全是綠色的效果

如果是先設定padding,那麼我們設定背景色是針對裡面的佈局進行設定,而不會講padding也算到裡面去

寬高類和邊距

首先,先是講解常用的屬性,設定寬高和邊距

size

同時設定寬高

  • size(size: Dp)
  • size(height: Dp,width: Dp)

width

單獨設定寬度

  • width(intrinsicSize: IntrinsicSize) 這個引數是自定義佈局測量裡的,本篇暫時不講解
  • width(width: Dp)

hegiht

單獨設定高度

  • hegiht(intrinsicSize: IntrinsicSize) 這個引數是自定義佈局測量裡的,本篇暫時不講解
  • hegiht(hegiht: Dp)

defaultMinSize

設定寬高的預設最小值

defaultMinSize(
    minWidth: Dp = Dp.Unspecified,
    minHeight: Dp = Dp.Unspecified
):

sizeIn

設定寬高的最小值和最大值,寬度在minWidth~maxWidth之間,高度在minHeight~maxHeight之間

sizeIn(
    minWidth: Dp = Dp.Unspecified,
    minHeight: Dp = Dp.Unspecified,
    maxWidth: Dp = Dp.Unspecified,
    maxHeight: Dp = Dp.Unspecified
):

同理,也有單獨給寬度或高度設定的方法

  • widthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)
  • heightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)

fillMaxSize

寬高都填充滿父佈局(相當於原生xml中的match_parent)

fillMaxSize(fraction: Float = 1f)

預設是1f,代表填充滿父佈局,如果設定為0.5f,則是填滿父佈局的0.5(即一半)

除此之外,也有單獨給寬度或高度方法
= fillMaxWidth(fraction: Float = 1f)
= fillMaxHegiht(fraction: Float = 1f)

wrapContentSize

元件的控制元件寬高若是小與定義的最小寬高,會將元件進行排列的設定

wrapContentSize(
    align: Alignment = Alignment.Center,
    unbounded: Boolean = false
) 

上面說的可能不是太好懂,以一個例子來說明吧

Box(
    Modifier.sizeIn(minWidth = 40.dp, minHeight = 40.dp)
        .wrapContentSize(Alignment.TopCenter)
        .size(20.dp)
        .background(Color.Blue)
)

我們設定了Box的元件的最小寬高為40dp,但此Box的寬高實際設定成了20dp,如果沒有加上這個方法wrapContentSize,那麼最終渲染出的Box寬高其實是40dp x 40dp,且是藍色背景

而由於我們加上了這個方法,最終渲染出的Box寬高就為20dp x 20dp,且排列對齊方式會按照wrapContentSize中的align引數進行

同理,也有單獨設定寬度或高度的方法

wrapContentHeight(
    align: Alignment.Vertical = Alignment.CenterVertically,
    unbounded: Boolean = false
)

//使用例子
Box(
    Modifier.size(50.dp)
        .wrapContentHeight(Alignment.CenterVertically)
        .height(20.dp)
        .background(Color.Blue)
)
wrapContentWidth(
    align: Alignment.Horizontal = Alignment.CenterHorizontally,
    unbounded: Boolean = false
)

//使用例子
Box(
    Modifier.size(50.dp)
        .wrapContentWidth(Alignment.CenterHorizontally)
        .width(20.dp)
        .background(Color.Blue)
)

padding

有四種不同的引數,各位看著用就行,之前文章中也是有詳細講解,這裡不再贅述

padding(all: Dp)

點選事件類(點選 雙擊 長按)

clickable

給任意元件(包括佈局)設定點選事件,且自帶點選水波紋效果

clickable(
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onClick: () -> Unit
)

onClickLabelrole主要是為殘疾人(應該是盲人)設定的屬性,可以不用設定

此外,還有另外的引數列表

clickable(
    interactionSource: MutableInteractionSource,
    indication: Indication?,
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onClick: () -> Unit
)

interactionSource之前也有講過,是用來判斷按鈕的點選狀態,具體可以看之前講解關於Button的使用的文章

indication看文件說明是說用來繪製水波紋或者點選高亮的效果,具體使用沒有深究,下面給個例子:

val interactionSource = remember { MutableInteractionSource() }
Column {
    Text(
        text = "Click me and my neighbour will indicate as well!",
        modifier = Modifier
            // clickable will dispatch events using MutableInteractionSource and show ripple
            .clickable(
                interactionSource = interactionSource,
                indication = rememberRipple()
            ) {
                /**do something */
            }
            .padding(10.dp)
    )
    Spacer(Modifier.requiredHeight(10.dp))
    Text(
        text = "I'm neighbour and I indicate when you click the other one",
        modifier = Modifier
            // this element doesn't have a click, but will show default indication from the
            // CompositionLocal as it accepts the same MutableInteractionSource
            .indication(interactionSource, LocalIndication.current)
            .padding(10.dp)
    )
}

效果如下圖所示

combinedClickable

組合點選事件,可以給元件點選,雙擊,長按監聽操作

不過,需要注意的是,此方法是實驗性方法,也不知道後面版本更新會有所改變

combinedClickable(
    enabled: Boolean = true,
    onClickLabel: String? = null,
    role: Role? = null,
    onLongClickLabel: String? = null,
    onLongClick: () -> Unit = null,
    onDoubleClick: () -> Unit = null,
    onClick: () -> Unit
)

看方法很好理解,onLongClick是長按操作,onDoubleClick是雙擊操作,onClick是點選操作

val context = LocalContext.current
Box(
    Modifier
        .size(50.dp)
        .background(Color.Blue)
        .combinedClickable(onLongClick = {
            Toast.makeText(context, "長按操作", Toast.LENGTH_SHORT).show()
        }, onDoubleClick = {
            Toast.makeText(context, "雙擊操作", Toast.LENGTH_SHORT).show()
        }, onClick = {
            Toast.makeText(context, "點選操作", Toast.LENGTH_SHORT).show()
        })
)

形狀(shape) 邊框(border) 背景(background)

border

設定邊框寬度和形狀

border(border: BorderStroke, shape: Shape = RectangleShape)
border(width: Dp, color: Color, shape: Shape = RectangleShape))
border(width: Dp, brush: Brush, shape: Shape)

Brush是設定漸變,如下面的例子

//紅藍綠三色水平漸變
val gradientBrush = Brush.horizontalGradient(
    colors = listOf(Color.Red, Color.Blue, Color.Green),
    startX = 0.0f,
    endX = 500.0f,
    tileMode = TileMode.Repeated
)
Text(
    "Text with gradient border",
    modifier = Modifier.padding(10.dp).border(width = 2.dp, brush = gradientBrush, shape = CircleShape)
        .padding(10.dp)
)

效果如下:

後期再出一篇講解Brush的使用

background

設定背景及背景形狀

background(color: Color, shape: Shape = RectangleShape)
background(brush: Brush, shape: Shape = RectangleShape,alpha: Float = 1.0f)

alpha是設定透明度

background有兩種引數列表,具體使用也是與上面的類似,這裡不過多贅述

陰影

shadow(elevation: Dp, shape: Shape = RectangleShape, clip: Boolean)

滾動效果

之前在講解佈局的時候有提及,Row和Column佈局裡面的子元件,寬高若是大於父元件就是導致子元件被隱藏,我們可以將其設定為滾動效果

但Compose沒有Scrollview,要想Row或Column實現滾動效果,就得使用modifier來實現

verticalScroll

horizontalScroll(
    state: ScrollState,
    enabled: Boolean = true,
    flingBehavior: FlingBehavior? = null,
    reverseScrolling: Boolean = false
)

flingBehavior這個引數不是很理解做什麼用的..

Column(Modifier.verticalScroll(rememberScrollState())) {
    repeat(10){
        Box(
            Modifier
                .fillMaxWidth()
                .height(200.dp)
                ){
            Text(text = "測試$it")
        }
    }
}

效果如下:

如果不加上verticalScroll,Column是無法向下滾動的

reverseScrolling設定為true的話,預設自動滾動到底部,效果如下所示

Column一般和verticalScroll連用實現垂直方向的滾動效果,而Row則與horizontalScroll連用

horizontalScroll

horizontalScroll(
    state: ScrollState,
    enabled: Boolean = true,
    flingBehavior: FlingBehavior? = null,
    reverseScrolling: Boolean = false
)

使用與上面的類似,這裡不再贅述

scrollable

scrollable(
    state: ScrollableState,
    orientation: Orientation,
    enabled: Boolean = true,
    reverseDirection: Boolean = false,
    flingBehavior: FlingBehavior? = null,
    interactionSource: MutableInteractionSource? = null
)

沒太搞懂這個主要是實現什麼效果的..

文件的示例程式碼:

// actual composable state that we will show on UI and update in `Scrollable`
val offset = remember { mutableStateOf(0f) }
Box(
    Modifier
        .size(150.dp)
        .scrollable(
            orientation = Orientation.Vertical,
            // state for Scrollable, describes how consume scroll amount
            state = rememberScrollableState { delta ->
                offset.value = offset.value + delta // update the state
                delta // indicate that we consumed all the pixels available
            }
        )
        .background(Color.LightGray),
    contentAlignment = Alignment.Center
) {
    Text(offset.value.roundToInt().toString(), style = TextStyle(fontSize = 32.sp))
}

效果

選擇

selectable

可用來實現單選功能

val option1 = Color.Red
val option2 = Color.Blue
var selectedOption by remember { mutableStateOf(option1) }
Column {
    Text("Selected: $selectedOption")
    Row {
        listOf(option1, option2).forEach { color ->
            val selected = selectedOption == color
            Box(
                Modifier
                    .size(100.dp)
                    .background(color = color)
                    .selectable(
                        selected = selected,
                        onClick = { selectedOption = color }
                    )
            ){
                if(selected) Text(text = "已選",color = Color.White)
            }
        }
    }
}

效果:

toggleable

類似核取方塊的勾選及不勾選

var checked by remember { mutableStateOf(false) }
// content that you want to make toggleable
Text(
    modifier = Modifier.toggleable(value = checked, onValueChange = { checked = it }),
    text = checked.toString()
)

效果如下:

變化類(旋轉 縮放 放大)

aspectRatio

Box(Modifier.width(100.dp).aspectRatio(2f).background(Color.Green))

rotate

元件沿中心順時針旋轉,最高支援360°

//順時針旋轉45°
Box(
    Modifier.rotate(45f)
        .size(100.dp, 100.dp)
)

效果如下:

scale

縮放或放大

Box(
    Modifier.scale(scaleX = 0.2f, scaleY = 0.5f)
        .background(Color.Black)
        .size(100.dp, 100.dp)
)

不過看實際效果,感覺是將寬和高都往中間縮放了

參考

相關文章