AndroidStudioの物置
このスクラップについて
何度も書くことになりそうな書き方を個人的なメモ代わりに置いておくための場所です
個人用ではありますが、自由にコピペとかして使ってください
ただ動作保証とかはできません
marerial3です
clickableなUIコンポーネント
JetpackComposeのコンポーネントのクリック周りをカスタムしたもの
Box
クリックやロングクリックに対応したBox
// クリック可能
@Suppress("unused")
@Composable
inline fun Box(
noinline onClick: () -> Unit,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {
androidx.compose.foundation.layout.Box(
modifier = modifier
.clickable(
onClick = onClick
),
contentAlignment = contentAlignment,
propagateMinConstraints = propagateMinConstraints,
content = content
)
}
// クリック+ロングクリック可能
@Suppress("unused")
@OptIn(ExperimentalFoundationApi::class)
@Composable
inline fun Box(
modifier: Modifier = Modifier,
noinline onClick: (() -> Unit)? = null,
noinline onLongClick: () -> Unit,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {
androidx.compose.foundation.layout.Box(
modifier = modifier
.combinedClickable(
onClick = onClick?: { },
onLongClick = onLongClick
),
contentAlignment = contentAlignment,
propagateMinConstraints = propagateMinConstraints,
content = content
)
}
// 中身なし
// クリック可能
@Suppress("unused")
@Composable
fun Box(
onClick: () -> Unit,
modifier: Modifier
) {
androidx.compose.foundation.layout.Box(
modifier = modifier
.clickable (
onClick = onClick
)
)
}
// 中身なし
// クリック+ロングクリック可能
@Suppress("unused")
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Box(
onClick: (() -> Unit)? = null,
onLongClick: () -> Unit,
modifier: Modifier
) {
androidx.compose.foundation.layout.Box(
modifier = modifier
.combinedClickable(
onClick = onClick?: { },
onLongClick = onLongClick
),
)
}
Column
クリックやロングクリックに対応したColumn
// クリック可能
@Suppress("unused")
@Composable
inline fun Column(
noinline onClick: () -> Unit,
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {
androidx.compose.foundation.layout.Column(
modifier = modifier
.clickable(
onClick = onClick
),
verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment,
content = content
)
}
// クリック+ロングクリック可能
@Suppress("unused")
@OptIn(ExperimentalFoundationApi::class)
@Composable
inline fun Column(
modifier: Modifier = Modifier,
noinline onClick: (() -> Unit)? = null,
noinline onLongClick: () -> Unit,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable ColumnScope.() -> Unit
) {
androidx.compose.foundation.layout.Column(
modifier = modifier
.combinedClickable(
onClick = onClick?: { },
onLongClick = onLongClick
),
verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment,
content = content
)
}
Row
クリックやロングクリックに対応したRow
// クリック可能
@Suppress("unused")
@Composable
inline fun Row(
noinline onClick: () -> Unit,
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
) {
androidx.compose.foundation.layout.Row(
modifier = modifier
.clickable(
onClick = onClick
),
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
content = content
)
}
// クリック+ロングクリック可能
@Suppress("unused")
@OptIn(ExperimentalFoundationApi::class)
@Composable
inline fun Row(
modifier: Modifier = Modifier,
noinline onClick: (() -> Unit)? = null,
noinline onLongClick: () -> Unit,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
) {
androidx.compose.foundation.layout.Row(
modifier = modifier
.combinedClickable(
onClick = onClick?: { },
onLongClick = onLongClick
),
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
content = content
)
}
Button
クリックやロングクリックに対応したButton
// ロングクリック可能
@Suppress("unused")
@Composable
fun Button(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
onLongClick: () -> Unit,
enabled: Boolean = true,
shape: Shape = ButtonDefaults.shape,
colors: ButtonColors = ButtonDefaults.buttonColors(),
// elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
) {
val containerColor = if (enabled) colors.containerColor else colors.disabledContainerColor
val contentColor = if (enabled) colors.contentColor else colors.disabledContentColor
val shadowElevation = 0.0.dp // elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp
val tonalElevation = 0.0.dp //elevation?.tonalElevation(enabled) ?: 0.dp
Surface(
onClick = onClick,
onLongClick = onLongClick,
modifier = modifier.semantics { role = Role.Button },
enabled = enabled,
shape = shape,
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
interactionSource = interactionSource,
border = border
) {
ProvideContentColorTextStyle(
contentColor = contentColor,
textStyle = MaterialTheme.typography.labelLarge) {
androidx.compose.foundation.layout.Row(
Modifier
.defaultMinSize(
minWidth = ButtonDefaults.MinWidth,
minHeight = ButtonDefaults.MinHeight
)
.padding(contentPadding),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content
)
}
}
}
// ロングクリック可能
@Suppress("unused")
@Composable
fun FloatingActionButton(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
onLongClick: () -> Unit,
shape: Shape = FloatingActionButtonDefaults.shape,
containerColor: Color = FloatingActionButtonDefaults.containerColor,
contentColor: Color = contentColorFor(containerColor),
// elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable () -> Unit,
) {
val tonalElevation = 0.0.dp // elevation.tonalElevation() ?: 0.dp
val shadowElevation = 0.0.dp //elevation.shadowElevation(interactionSource = interactionSource).value 0.dp
Surface(
onClick = onClick,
onLongClick = onLongClick,
modifier = modifier.semantics { role = Role.Button },
shape = shape,
color = containerColor,
contentColor = contentColor,
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
interactionSource = interactionSource,
) {
ProvideContentColorTextStyle(
contentColor = contentColor,
textStyle = MaterialTheme.typography.labelLarge
) {
androidx.compose.foundation.layout.Box(
modifier = Modifier
.defaultMinSize(
minWidth = 56.0.dp,
minHeight = 56.0.dp,
),
contentAlignment = Alignment.Center,
) { content() }
}
}
}
/*
* ロングクリック可能なボタン用のSurface
* internalな関連関数もコピー
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
@NonRestartableComposable
private fun Surface(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
onLongClick: () -> Unit,
enabled: Boolean = true,
shape: Shape = RectangleShape,
color: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = contentColorFor(color),
tonalElevation: Dp = 0.0.dp,
shadowElevation: Dp = 0.0.dp,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
indication: Indication? = rememberRipple(),
border: BorderStroke? = null,
content: @Composable () -> Unit
) {
val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalAbsoluteTonalElevation provides absoluteElevation
) {
@Suppress("DEPRECATION_ERROR")
(androidx.compose.foundation.layout.Box(
modifier = modifier
.minimumInteractiveComponentSize()
.surface(
shape = shape,
backgroundColor = surfaceColorAtElevation(
color = color,
elevation = absoluteElevation
),
border = border,
shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }
)
.combinedClickable(
enabled = enabled,
onClick = onClick?: { },
onLongClick = onLongClick,
indication = indication,
interactionSource = interactionSource,
),
propagateMinConstraints = true
) {
content()
})
}
}
@Stable
private fun Modifier.surface(
shape: Shape,
backgroundColor: Color,
border: BorderStroke?,
shadowElevation: Float,
) = this
.graphicsLayer(shadowElevation = shadowElevation, shape = shape, clip = false)
.then(if (border != null) Modifier.border(border, shape) else Modifier)
.background(color = backgroundColor, shape = shape)
.clip(shape)
@Composable
private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color =
MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
@Composable
@ReadOnlyComposable
private fun ColorScheme.applyTonalElevation(backgroundColor: Color, elevation: Dp): Color {
val tonalElevationEnabled = LocalTonalElevationEnabled.current
return if (backgroundColor == surface && tonalElevationEnabled) {
surfaceColorAtElevation(elevation)
} else {
backgroundColor
}
}
@Composable
private fun ProvideContentColorTextStyle(
contentColor: Color,
textStyle: TextStyle,
content: @Composable () -> Unit
) {
val mergedStyle = LocalTextStyle.current.merge(textStyle)
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides mergedStyle,
content = content
)
}
拡張関数
JSON
org.json
の拡張関数
// JsonArrayをforeachで扱う
@Suppress("unused")
inline fun JSONArray.forEachIndexOnly(action: (Int) -> Unit) {
for (index in 0 until length()) action(index)
}
// JsonArrayをmapで扱う
@Suppress("unused")
inline fun <R> JSONArray.mapIndexOnly(action: (Int) -> R): List<R> {
return (0 until length()).map { index -> action(index) }
}
Collection
Collection
,Iterable
の拡張関数
// ArrayListに変換
@Suppress("unused")
fun <T> Iterable<T>.toArrayList(): ArrayList<T> {
if (this is Collection<T>)
return this.toArrayList()
return toCollection(ArrayList<T>())
}
// ArrayListに変換
@Suppress("unused")
fun <T> Collection<T>.toArrayList(): ArrayList<T> {
return ArrayList(this)
}
ActivityResult関連
rememberLauncherForActivityResult
を拡張したもの
Permission
ActivityResultContracts.RequestPermission
を拡張したもの
権限要求を行い、許可された,されているかを確認する
input
-
permission
要求する権限 -
onGrant
権限が許可された,されていた際に行う処理
output
-
launch()
権限要求を実行する -
isGranted()
権限が有効かどうかを取得
@Stable
data class PermissionResultLauncher(
private val context: Context,
private val permission: String,
private val launcher: ManagedActivityResultLauncher<String, Boolean>,
private val onAlreadyGranted: () -> Unit
) {
fun launch() {
if (!isGranted()) {
launcher.launch(permission)
} else {
onAlreadyGranted()
}
}
fun isGranted(): Boolean {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
}
@Composable
fun rememberParmissionResult(
permission: String,
onGrant: () -> Unit
): PermissionResultLauncher {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGrant ->
if (isGrant) {
onGrant()
}
}
return remember {
PermissionResultLauncher(
context = context,
permission = permission,
launcher = launcher,
onAlreadyGranted = onGrant
)
}
}
WiFi SettingPanel
ActivityResultContracts.StartActivityForResult()
を拡張したもの
WiFiのSettingPanelを呼び出し、有効化された,されていたかを確認する
Api29未満ではWi-Fiを有効化する
input
-
onEnable
WiFiが有効化された,されていた場合に行う処理
output
-
launch()
SettingPanelを呼び出す -
isEnabled()
WiFiが有効化されているかを取得
@Stable
data class WifiResultLauncher(
private val wifiManager: WifiManager,
private val launcher: ManagedActivityResultLauncher<Intent, ActivityResult>,
private val onAlreadyEnabled: () -> Unit
) {
@RequiresApi(Build.VERSION_CODES.Q)
private val intent = Intent(Settings.Panel.ACTION_WIFI)
fun launch() {
if (!isEnabled()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
launcher.launch(intent)
}
else {
@Suppress("DEPRECATION")
wifiManager.isWifiEnabled = true
}
} else {
onAlreadyEnabled()
}
}
fun isEnabled(): Boolean {
return wifiManager.isWifiEnabled()
}
}
@Composable
fun rememberWifiResult(
onEnable: () -> Unit
): WifiResultLauncher {
val wifiManager =
LocalContext.current.applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
if (wifiManager.isWifiEnabled()) {
onEnable()
}
}
return remember {
WifiResultLauncher(
wifiManager = wifiManager,
launcher = launcher,
onAlreadyEnabled = onEnable
)
}
}
Animation関連
AnimatedVisibility
アニメーションが設定された状態で呼び出されるAnimatedVisibility
このコードではfadeIn()
,fadeOut()
@Suppress("unused")
@Composable
fun FadeInAnimated(
visible: Boolean,
modifier: Modifier = Modifier,
label: String = "AnimatedVisibility",
content: @Composable() (AnimatedVisibilityScope.() -> Unit)
) {
val enter: EnterTransition = fadeIn()
val exit: ExitTransition = fadeOut()
AnimatedVisibility(
visible = visible,
enter = enter,
exit = exit,
modifier = modifier,
label = label,
content = content
)
}
AnimatedContent
AnimatedContent
を拡張したもので、基本的にAnimatedVisibility
のように動作する
visible
がfalse
の際、space
分のスペースが確保される
また、アニメーションが設定された状態で呼び出せされる
このコードではfadeIn()
,fadeOut()
@Composable
fun FadeInSpaceableAnimated(
visible: Boolean,
space: Dp,
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
label: String = "AnimatedContent",
contentKey: (targetState: Boolean) -> Any? = { it },
content: @Composable() AnimatedContentScope.() -> Unit
) {
val transitionSpec: AnimatedContentTransitionScope<Boolean>.() -> ContentTransform = {
(fadeIn(animationSpec = tween(300, delayMillis = 90)))
.togetherWith(fadeOut(animationSpec = tween(90)))
}
AnimatedContent(
targetState = visible,
modifier = modifier,
transitionSpec = transitionSpec,
contentAlignment = contentAlignment,
label = label,
contentKey = contentKey
) { isVisible ->
if (isVisible) {
content()
} else {
Spacer(modifier = Modifier.size(space))
}
}
}
LazyList関連
JetpackComposeのLazyList
,LazyColumn
,LazyRow
,などをカスタムしたもの
animateItem()
animateItem()
が各アイテムに設定された状態で呼び出せるLazyList
のitems
拡張
@Suppress("unused")
inline fun <T> LazyListScope.animatedItems(
items: List<T>,
noinline key: ((item: T) -> Any)? = null,
noinline contentType: (item: T) -> Any? = { null },
crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
) = items(
count = items.size,
key = if (key != null) { index: Int -> key(items[index]) } else null,
contentType = { index: Int -> contentType(items[index]) }
) { index ->
Box(modifier = Modifier.animateItem()) {
itemContent(items[index])
}
}
LazyPager
Pager
っぽく動作させられるLazyList
List
を直接渡せる
このコードではHorizontalPager
に近い感じ
@Suppress("unused")
@Composable
fun LazyHorizontalPager(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(all = 0.dp),
reverseLayout: Boolean = false,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: LazyListScope.() -> Unit
) {
val flingBehavior = rememberLazyPagerFlingBehavior(state = state)
androidx.compose.foundation.lazy.LazyRow(
modifier = modifier,
state = state,
contentPadding = contentPadding,
reverseLayout = reverseLayout,
verticalAlignment = verticalAlignment,
horizontalArrangement = Arrangement.spacedBy(10.dp), // 画面外に余白を作って一つだけcompositionされるように
flingBehavior = flingBehavior,
userScrollEnabled = true,
content = content
)
}
// PagerのっぽくスクロールさせるためのFlingBehavior
@Composable
fun rememberLazyPagerFlingBehavior(
state: LazyListState,
decayAnimationSpec: DecayAnimationSpec<Float> = remember {
exponentialDecay(frictionMultiplier = 20.0f) // スワイプのブレーキ
},
snapAnimationSpec: AnimationSpec<Float> = spring(
stiffness = Spring.StiffnessLow,
visibilityThreshold = Int.VisibilityThreshold.toFloat()
),
): FlingBehavior {
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
return remember(state, decayAnimationSpec, snapAnimationSpec, density, layoutDirection) {
val snapLayoutInfoProvider = SnapLayoutInfoProvider(
lazyListState = state,
snapPosition = SnapPosition.Start
)
snapFlingBehavior(
snapLayoutInfoProvider = snapLayoutInfoProvider,
decayAnimationSpec = decayAnimationSpec,
snapAnimationSpec = snapAnimationSpec
)
}
}
// Pagerっぽく表示させるためのitems
@Suppress("unused")
inline fun <T> LazyListScope.animatedPagerItems(
items: List<T>,
noinline key: ((item: T) -> Any)? = null,
noinline contentType: (item: T) -> Any? = { null },
crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
) = items(
count = items.size,
key = if (key != null) { index: Int -> key(items[index]) } else null,
contentType = { index: Int -> contentType(items[index]) }
) { index ->
androidx.compose.foundation.layout.Box(
modifier = Modifier
.animateItem()
.fillParentMaxSize() // 一つずつ表示
) {
itemContent(items[index])
}
}