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

原文: 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)
)

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

參考