<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Material 3</title>
	<atom:link href="https://bps-e.com/dev/category/android/material3/feed/" rel="self" type="application/rss+xml" />
	<link>https://bps-e.com/dev</link>
	<description>android アプリ開発 kotlin + jetpack compose + material 3</description>
	<lastBuildDate>Thu, 02 Oct 2025 22:49:48 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>

<image>
	<url>https://bps-e.com/dev/wp-content/uploads/2022/10/cropped-logo3-32x32.png</url>
	<title>Material 3</title>
	<link>https://bps-e.com/dev</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>[Android] 006. TopAppBarのshadow</title>
		<link>https://bps-e.com/dev/android-006-topappbar%e3%81%aeshadow/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Thu, 02 Oct 2025 22:48:54 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<category><![CDATA[Narwhal]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1712</guid>

					<description><![CDATA[Material 3のTopAppBarに境界線の影がなくて、古いアプリのデザインと合わない；；みたいな人少ないかもでググってもサクッと出てこなかったので需要ないっぽいけど、こんなコードを見かけたので&#8230; 昔は [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Material 3のTopAppBarに境界線の影がなくて、古いアプリのデザインと合わない；；みたいな人少ないかもでググってもサクッと出てこなかったので需要ないっぽいけど、こんなコードを見かけたので&#8230;</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>Scaffold(
    topBar = {
        Surface(shadowElevation = 4.dp) {
            CenterAlignedTopAppBar(
                title = { Text(&quot;hoge&quot;) },
            )
        }
    },
)</code></pre></div>



<p>昔はModifierにshadowなかった？<br>単純にSurfaceにshadowElevationがあったから使っただけかもだけど</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>CenterAlignedTopAppBar(
    title = { Text(&quot;hoge&quot;) },
    Modifier.shadow(4.dp)
)</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Narwhal 3 Feature Drop 2025.1.3, built on August 28, 2025</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 005. ネストしてるScaffold</title>
		<link>https://bps-e.com/dev/android-005-%e3%83%8d%e3%82%b9%e3%83%88%e3%81%97%e3%81%a6%e3%82%8bscaffold/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Thu, 02 Oct 2025 22:05:45 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<category><![CDATA[Narwhal]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1702</guid>

					<description><![CDATA[よくありそうなBottomBarにNavigationBarをおいてTopBarは各View側で制御したい場合のView配置で適当にpadding設定すると隙間があいてしまう場合の対応コピペサンプル 正解 ダメな実装 内 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>よくありそうなBottomBarにNavigationBarをおいてTopBarは各View側で制御したい場合のView配置で適当にpadding設定すると隙間があいてしまう場合の対応コピペサンプル</p>



<h2 class="wp-block-heading"><span id="toc1">正解</span></h2>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>Scaffold(
    Modifier.fillMaxSize(),
    bottomBar = {
        Row(Modifier.fillMaxWidth().height(80.dp).background(Color.Cyan)) {}
    },
    containerColor = Color.Red
) { innerPadding -&gt;
    val title = &quot;test&quot;
    val layoutDirection = LocalLayoutDirection.current
    Scaffold(
        Modifier.padding(
            start = innerPadding.calculateLeftPadding(layoutDirection),
            end = innerPadding.calculateRightPadding(layoutDirection),
            bottom = innerPadding.calculateBottomPadding()
        ),
        topBar = {
            CenterAlignedTopAppBar(
                title = { Text(title) },
                colors = TopAppBarDefaults.topAppBarColors().copy(containerColor = Color.Magenta)
            )
        },
        containerColor = Color.Green
    ) { innerPadding -&gt;
        val topPadding = innerPadding.calculateTopPadding()
        Column(
            Modifier.fillMaxSize().padding(top = topPadding),
        ) {
            Text(title)
            Column(
                Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Bottom
            ) {
                Text(title)
            }
        }
    }
}</code></pre></div>



<figure class="wp-block-image size-large is-resized"><img fetchpriority="high" decoding="async" width="576" height="1024" src="https://bps-e.com/dev/wp-content/uploads/2025/10/image-2-576x1024.png" alt="" class="wp-image-1705" style="width:250px" srcset="https://bps-e.com/dev/wp-content/uploads/2025/10/image-2-576x1024.png 576w, https://bps-e.com/dev/wp-content/uploads/2025/10/image-2-169x300.png 169w, https://bps-e.com/dev/wp-content/uploads/2025/10/image-2.png 720w" sizes="(max-width: 576px) 100vw, 576px" /></figure>



<h2 class="wp-block-heading"><span id="toc2">ダメな実装</span></h2>



<p>内部のScaffoldのbottomに余計なpaddingが入ってしまう</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>Scaffold(
    Modifier.fillMaxSize(),
    bottomBar = {
        Row(Modifier.fillMaxWidth().height(80.dp).background(Color.Cyan)) {}
    },
    containerColor = Color.Red
) { innerPadding -&gt;
    val title = &quot;test&quot;
    Scaffold(
        Modifier.padding(innerPadding),
        topBar = {
            CenterAlignedTopAppBar(
                title = { Text(title) },
                colors = TopAppBarDefaults.topAppBarColors().copy(containerColor = Color.Magenta)
            )
        },
        containerColor = Color.Green
    ) { innerPadding -&gt;
        Column(
            Modifier.fillMaxSize().padding(innerPadding),
        ) {
            Text(title)
            Column(
                Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Bottom
            ) {
                Text(title)
            }
        }
    }
}</code></pre></div>



<figure class="wp-block-image size-full is-resized"><img decoding="async" width="720" height="1280" src="https://bps-e.com/dev/wp-content/uploads/2025/10/image.png" alt="" class="wp-image-1703" style="width:250px" srcset="https://bps-e.com/dev/wp-content/uploads/2025/10/image.png 720w, https://bps-e.com/dev/wp-content/uploads/2025/10/image-169x300.png 169w" sizes="(max-width: 720px) 100vw, 720px" /></figure>



<h2 class="wp-block-heading"><span id="toc3">おしい実装</span></h2>



<p>statusbar分がズレている</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>Scaffold(
    Modifier.fillMaxSize(),
    bottomBar = {
        Row(Modifier.fillMaxWidth().height(80.dp).background(Color.Cyan)) {}
    },
    containerColor = Color.Red
) { innerPadding -&gt;
    val title = &quot;test&quot;
    Scaffold(
        Modifier.padding(innerPadding),
        topBar = {
            CenterAlignedTopAppBar(
                title = { Text(title) },
                colors = TopAppBarDefaults.topAppBarColors().copy(containerColor = Color.Magenta)
            )
        },
        containerColor = Color.Green
    ) { innerPadding -&gt;
        val topPadding = innerPadding.calculateTopPadding()
        Column(
            Modifier.fillMaxSize().padding(top = topPadding),
        ) {
            Text(title)
            Column(
                Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Bottom
            ) {
                Text(title)
            }
        }
    }
}</code></pre></div>



<figure class="wp-block-image size-large is-resized"><img decoding="async" width="576" height="1024" src="https://bps-e.com/dev/wp-content/uploads/2025/10/image-1-576x1024.png" alt="" class="wp-image-1704" style="width:250px" srcset="https://bps-e.com/dev/wp-content/uploads/2025/10/image-1-576x1024.png 576w, https://bps-e.com/dev/wp-content/uploads/2025/10/image-1-169x300.png 169w, https://bps-e.com/dev/wp-content/uploads/2025/10/image-1.png 720w" sizes="(max-width: 576px) 100vw, 576px" /></figure>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Narwhal 3 Feature Drop 2025.1.3, built on August 28, 2025</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 004. @Preview(デバイスサイズごと)</title>
		<link>https://bps-e.com/dev/android-008-004/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Wed, 25 Jun 2025 19:04:39 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<category><![CDATA[Narwhal]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1691</guid>

					<description><![CDATA[最新のNow in Androidを眺めていたら&#8230; ん？@DevicePreviews？？？ たしかにいちいち個別にデバイスごとにPreview実装する必要なかった&#8230;もっとはやく知りたかったorz]]></description>
										<content:encoded><![CDATA[
<p>最新のNow in Androidを眺めていたら&#8230;</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-kt" data-lang="Kotlin"><code>@DevicePreviews
@Composable
fun ForYouScreenPopulatedAndLoading(...)</code></pre></div>



<p>ん？@DevicePreviews？？？</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-kt" data-lang="Kotlin"><code>/**
 * Multipreview annotation that represents various device sizes. Add this annotation to a composable
 * to render various devices.
 */
@Preview(name = &quot;phone&quot;, device = &quot;spec:shape=Normal,width=360,height=640,unit=dp,dpi=480&quot;)
@Preview(name = &quot;landscape&quot;, device = &quot;spec:shape=Normal,width=640,height=360,unit=dp,dpi=480&quot;)
@Preview(name = &quot;foldable&quot;, device = &quot;spec:shape=Normal,width=673,height=841,unit=dp,dpi=480&quot;)
@Preview(name = &quot;tablet&quot;, device = &quot;spec:shape=Normal,width=1280,height=800,unit=dp,dpi=480&quot;)
annotation class DevicePreviews</code></pre></div>



<p>たしかにいちいち個別にデバイスごとにPreview実装する必要なかった&#8230;<br>もっとはやく知りたかったorz</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 021. Modifier.animateContentSizeとElevated</title>
		<link>https://bps-e.com/dev/android-005-021/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Tue, 17 Sep 2024 15:40:56 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1572</guid>

					<description><![CDATA[コンポーネントのサイズ変更時のアニメーションですが、Modifierのセット順でElevatedCardなどの影が表示されない罠があったので覚書き paddingつけないと表示されないとか説明してるサイトもあったけどそん [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>コンポーネントのサイズ変更時のアニメーションですが、Modifierのセット順でElevatedCardなどの影が表示されない罠があったので覚書き</p>



<p>paddingつけないと表示されないとか説明してるサイトもあったけどそんなことはない<br>size指定前にanimateContentSizeを指定すればok</p>



<p><a rel="noopener" href="https://developer.android.com/develop/ui/compose/animation/quick-guid" target="_blank">https://developer.android.com/develop/ui/compose/animation/quick-guid</a><br><a rel="noopener" href="https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#(androidx.compose.ui.Modifier).animateContentSize(androidx.compose.animation.core.FiniteAnimationSpec,kotlin.Function2)" target="_blank">https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#(androidx.compose.ui.Modifier).animateContentSize(androidx.compose.animation.core.FiniteAnimationSpec,kotlin.Function2)</a><br><a rel="noopener" href="https://developer.android.com/develop/ui/compose/components/card?elevated#elevated" target="_blank">https://developer.android.com/develop/ui/compose/components/card?elevated#elevated</a></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>@Composable
fun Screen(modifier: Modifier = Modifier) {
    var expanded by remember { mutableStateOf(false) }

    Column(
        modifier.padding(44.dp),
    ) {
        ElevatedCard(
            onClick = { expanded = !expanded },
            elevation = CardDefaults.cardElevation(
                defaultElevation = 8.dp
            ),
        ) {
            Column(
                Modifier
                    .animateContentSize(
                        spring(
                            stiffness = Spring.StiffnessHigh,
                            visibilityThreshold = IntSize.VisibilityThreshold
                        )
                    )
                    .fillMaxWidth()
                    .padding(horizontal = 12.dp, vertical = 8.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.spacedBy(8.dp)
            ) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Text(&quot;ElevatedCard&quot;)
                    Icon(
                        Icons.Default.ArrowDropDown,
                        &quot;&quot;,
                        Modifier
                            .then(
                                if (expanded) Modifier.rotate(180f)
                                else Modifier
                            )
                    )
                }
                if (expanded) Text(&quot;あ&quot;.repeat(100))
            }
        }
    }
}</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala Feature Drop 2024.1.2 built on August 23, 2024</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 019. プルダウンリフレッシュ(PullToRefreshBox)</title>
		<link>https://bps-e.com/dev/android-005-019/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Mon, 09 Sep 2024 17:41:27 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1549</guid>

					<description><![CDATA[https://bps-e.com/dev/android-003-039/ で試したプルダウンリフレッシュを使うタイミングが来そうだったので、今の環境で確認してたらCompose BOM 2024.09でMateria [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p><a href="https://bps-e.com/dev/android-003-039/">https://bps-e.com/dev/android-003-039/</a> で試したプルダウンリフレッシュを使うタイミングが来そうだったので、今の環境で確認してたらCompose BOM 2024.09でMaterial3向けのPullToRefreshBoxが追加されていることに気が付きましたので試しました<br><a rel="noopener" href="https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/package-summary" target="_blank">https://developer.android.com/reference/kotlin/androidx/compose/material3/pulltorefresh/package-summary</a></p>



<p>thresholdを指定したい場合などIndicatorをカスタマイズする必要がなければ、もっと簡略化したコードで実装できます</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
fun Screen(modifier: Modifier = Modifier) {
    var isRefreshing by remember { mutableStateOf(false) }
    val state = rememberPullToRefreshState()
    val scope = rememberCoroutineScope()

    PullToRefreshBox(
        isRefreshing,
        onRefresh = {
            isRefreshing = true
            scope.launch {
                delay(2_000)
                isRefreshing = false
            }
        },
        modifier = modifier,
        state = state,
        indicator = {
            Indicator(
                modifier = Modifier.align(Alignment.TopCenter),
                isRefreshing = isRefreshing,
                state = state,
                threshold = 100.dp
            )
        }
    ) {
        Column(
            Modifier
                .fillMaxSize()
                .verticalScroll(rememberScrollState()),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(&quot;PullToRefreshBox&quot;)
        }
    }
}</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala Feature Drop 2024.1.2 built on August 23, 2024</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 017. Typography(Text Style)</title>
		<link>https://bps-e.com/dev/android-005-017/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Sat, 31 Aug 2024 04:01:43 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1507</guid>

					<description><![CDATA[compose-bomを2024.08にしたら？なんかTextが中央に表示されてないような？みたいな違和感があったので調べてみました テキストスタイルまわりは以下https://developer.android.com [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>compose-bomを2024.08にしたら？なんかTextが中央に表示されてないような？みたいな違和感があったので調べてみました</p>



<p>テキストスタイルまわりは以下<br><a rel="noopener" href="https://developer.android.com/develop/ui/compose/text/style-paragraph?hl=ja" target="_blank">https://developer.android.com/develop/ui/compose/text/style-paragraph</a></p>



<p>PlatformTextStyleが微妙に高さに影響してたようです<br>ProvideTextStyleとかで全体的に指定をしておいた方がいいかもしれません</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>@Composable
fun Screen(modifier: Modifier = Modifier) {
    @Composable
    fun TestText(content: @Composable (RowScope.() -&gt; Unit)) {
        Row(
            Modifier
                .background(Color(0.95f, 0.5f, 0.5f, 1.0f))
                .drawWithCache {
                    onDrawBehind {
                        val center = this.size.height / 2
                        drawLine(Color.LightGray, Offset(0.0f, center), Offset(this.size.width, center))
                    }
                },
            verticalAlignment = Alignment.CenterVertically
        ) {
            Box(Modifier.size(9.dp).background(Color.Black))
            content()
            Box(Modifier.size(9.dp).background(Color.Black))
        }
    }

    Column(
        modifier,
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
            TestText { Text(&quot;あいうabc&quot;) }
            TestText { Text(&quot;あいうabc&quot;, style = LocalTextStyle.current.copy(platformStyle = PlatformTextStyle(includeFontPadding = true))) }
            TestText { Text(&quot;あいうabc&quot;, style = LocalTextStyle.current.copy(
                lineHeight = 32.sp,
                platformStyle = PlatformTextStyle(includeFontPadding = false),
                lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None)
            )) }
        }
    }
}</code></pre></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="720" height="80" src="https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_124705-1.png" alt="" class="wp-image-1510" srcset="https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_124705-1.png 720w, https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_124705-1-300x33.png 300w" sizes="(max-width: 720px) 100vw, 720px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"></div>
</div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala 2024.1.1 Patch 1 built on July 11, 2024</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 016. グラフ(Canvas)</title>
		<link>https://bps-e.com/dev/android-005-016/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Fri, 30 Aug 2024 16:30:04 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1499</guid>

					<description><![CDATA[急に要件が出てきたら困るかもなので、グラフの表示試してみます Vicoなどサードベンダーのライブラリを使うのもありだけど、保守性を考えるとあまり依存したくないのでCanvasに描画＆アニメーションで独自に作ってみましたh [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>急に要件が出てきたら困るかもなので、グラフの表示試してみます</p>



<p>Vicoなどサードベンダーのライブラリを使うのもありだけど、保守性を考えるとあまり依存したくないのでCanvasに描画＆アニメーションで独自に作ってみました<br><a rel="noopener" href="https://developer.android.com/develop/ui/compose/graphics/draw/overview" target="_blank">https://developer.android.com/develop/ui/compose/graphics/draw/overview</a><br><a href="https://developer.android.com/develop/ui/compose/animation/quick-guide">https://developer.android.com/develop/ui/compose/animation/quick-guide</a></p>



<h2 class="wp-block-heading"><span id="toc1">折れ線グラフ</span></h2>



<p>ただの直線じゃ面白くないので、よくある？滑らかな曲線で作ってみようと思います<br>こちらが参考になりました<br><a rel="noopener" href="https://medium.com/@kezhang404/either-compose-is-elegant-or-if-you-want-to-draw-something-with-an-android-view-you-have-to-7ce00dc7cc1" target="_blank">https://medium.com/@kezhang404/either-compose-is-elegant-or-if-you-want-to-draw-something-with-an-android-view-you-have-to-7ce00dc7cc1</a></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>// ↑の実装を流用
fun buildCurveLine(path: Path, startPoint: Offset, endPoint: Offset) {
    val firstControlPoint = Offset(
        x = startPoint.x + (endPoint.x - startPoint.x) / 2F,
        y = startPoint.y,
    )
    val secondControlPoint = Offset(
        x = startPoint.x + (endPoint.x - startPoint.x) / 2F,
        y = endPoint.y,
    )
    path.cubicTo(
        x1 = firstControlPoint.x,
        y1 = firstControlPoint.y,
        x2 = secondControlPoint.x,
        y2 = secondControlPoint.y,
        x3 = endPoint.x,
        y3 = endPoint.y,
    )
}</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>data class Item (
    val title: String,
    val amount: Float
)

@Composable
fun LineChart(chart: List&lt;Item&gt;, modifier: Modifier = Modifier) {
    val max = remember {
        chart.map { it.amount }.max()
    }

    Canvas(modifier) {
        val moveX = this.size.width / chart.size
        val drawH = this.size.height

        var current = Offset(0.0f, 0.0f)
        val path = Path().apply {
            chart.forEachIndexed { i, it -&gt;
                val x = moveX * i
                val y = drawH - drawH * (it.amount / max)

                if (i == 0) moveTo(x, y)
                else buildCurveLine(this, current, Offset(x, y))
                current = Offset(x, y)
            }
        }

        repeat((max / 500).toInt()) {
            if (it != 0) {
                drawLine(
                    Color.Gray,
                    start = Offset(0.0f, drawH - drawH * (it * 500 / max)),
                    end = Offset(this.size.width, drawH - drawH * (it * 500 / max))
                )
            }
        }

        drawPath(
            path,
            Color.Black,
            1.0f,
            style = Stroke(width = 4.0f)
        )

        path.lineTo(current.x, drawH)
        path.lineTo(0.0f, drawH)
        path.close()

        drawPath(
            path,
            Brush.verticalGradient(listOf(Color(1.0f, 0.2f, 0.2f, 1.0f), Color(1.0f, 0.4f, 0.4f, 1.0f), Color(1.0f, 0.95f, 0.95f, 1.0f))),
            0.75f
        )
    }
}

@Composable
fun Screen(modifier: Modifier = Modifier) {
    val chart = remember {
        listOf(
            Item(&quot;2010&quot;, 1000f),
            Item(&quot;2015&quot;, 700f),
            Item(&quot;2020&quot;, 0f),
            Item(&quot;2025&quot;, 100f),
            Item(&quot;2030&quot;, 500f),
            Item(&quot;2030&quot;, 1500f),
            Item(&quot;2030&quot;, 1200f),
            Item(&quot;2030&quot;, 900f),
        )
    }

    Column(
        modifier
            .fillMaxWidth()
            .padding(top = 44.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        LineChart(chart, Modifier.fillMaxSize().padding(44.dp))
    }
}</code></pre></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_011728-576x1024.png" alt="" class="wp-image-1501" srcset="https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_011728-576x1024.png 576w, https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_011728-169x300.png 169w, https://bps-e.com/dev/wp-content/uploads/2024/08/Screenshot_20240831_011728.png 720w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"></div>
</div>



<h2 class="wp-block-heading"><span id="toc2">ドーナツグラフ</span></h2>



<p>表示にアニメーションするドーナツグラフを作ってみました</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>@Composable
fun DonutChart(
    chart: List&lt;Item&gt;,
    startColor: Color = Color(0, 79, 70),
    endColor: Color = Color(178, 255, 255)
) {
    val sum = remember {
        chart.map { it.amount }.sum()
    }
    val color = remember {
        var i = 0
        chart.map {
            val r = startColor.red - ((startColor.red - endColor.red) / chart.size * i)
            val g = startColor.green - ((startColor.green - endColor.green) / chart.size * i)
            val b = startColor.blue - ((startColor.blue - endColor.blue) / chart.size * i)
            i++
            Color(r, g, b, 1.0f)
        }
    }

    val startAngle = remember {
        var angle = 270f
        chart.map {
            val value = angle
            angle += 360f * (it.amount / sum)
            if (angle &gt; 360f) angle -= 360f
            value
        }
    }
    val animationAngle = remember {
        chart.map { Animatable(0f) }
    }

    LaunchedEffect(Unit) {
        animationAngle.forEachIndexed { i, it -&gt;
            it.animateTo(360f * (chart[i].amount / sum) - 1f, tween(1000 / chart.size, easing = LinearEasing))
        }
    }

    Column {
        Canvas(Modifier.size(200.dp, 200.dp)) {
            chart.forEachIndexed { i, value -&gt;
                drawArc(
                    color = color[i],
                    startAngle = startAngle[i],
                    sweepAngle = animationAngle[i].value,
                    useCenter = false,
                    style = Stroke(width = 64f),
                )
            }
        }
        Spacer(Modifier.height(44.dp))

        Column(Modifier.width(200.dp)) {
            Text(&quot;資産合計 ${sum.toInt()} 万円&quot;, style = MaterialTheme.typography.labelSmall)
            HorizontalDivider()
            chart.forEachIndexed { i, it -&gt;
                Row(
                    Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.spacedBy(4.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Box(Modifier.size(8.dp).background(color[i]))
                    Row(
                        Modifier.fillMaxWidth(),
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text(it.title, style = MaterialTheme.typography.labelSmall)
                        Text(&quot;${it.amount.toInt()} 万円&quot;, style = MaterialTheme.typography.labelSmall)
                    }
                }
            }
        }
    }
}

@Composable
fun Screen(modifier: Modifier = Modifier) {
    val chart = remember {
        listOf(
            Item(&quot;現金&quot;, 1000f),
            Item(&quot;株式&quot;, 500f),
            Item(&quot;債権&quot;, 0f),
            Item(&quot;保険&quot;, 50f),
            Item(&quot;不動産&quot;, 2500f),
        )
        .filter { it.amount != 0f }
        .sortedByDescending { it.amount }
    }

    Column(modifier.fillMaxWidth().padding(top = 44.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        DonutChart(chart)
    }
}</code></pre></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-video"><video controls src="https://bps-e.com/dev/wp-content/uploads/2024/08/Screen_recording_20240830_204521.webm"></video></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow"></div>
</div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala 2024.1.1 Patch 1 built on July 11, 2024</p>
]]></content:encoded>
					
		
		<enclosure url="https://bps-e.com/dev/wp-content/uploads/2024/08/Screen_recording_20240830_204521.webm" length="0" type="video/webm" />

			</item>
		<item>
		<title>[Android] 013. AudioRecord</title>
		<link>https://bps-e.com/dev/android-005-013/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 09:30:51 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1442</guid>

					<description><![CDATA[Jetpack Compose向けにリアルタイムバッファ処理用途での録音実装したことなかったので実装してみましたcallback頻度を100ms程度としてます ぱーみっしょんちぇっく こぴぺこーど※ Manifest.p [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>Jetpack Compose向けにリアルタイムバッファ処理用途での録音実装したことなかったので実装してみました<br>callback頻度を100ms程度としてます</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>&lt;uses-permission android:name=&quot;android.permission.RECORD_AUDIO&quot; /&gt;</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>class AudioRecorder {
    companion object {
        const val AUDIO_SOURCE = MediaRecorder.AudioSource.MIC
        const val SAMPLE_RATE = 16000
        const val CHANNEL = AudioFormat.CHANNEL_IN_MONO
        const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
    }

    lateinit var audioRecord: AudioRecord
        private set
    val _isRecording = MutableStateFlow(false)
    val isRecording = _isRecording.asStateFlow()

    private val bufferSize = AudioRecord.getMinBufferSize(
        SAMPLE_RATE,
        CHANNEL,
        AUDIO_FORMAT
    )
    // 100ms/1frameとする
    private val frameSize = SAMPLE_RATE * Short.SIZE_BYTES / 10
    private var frame = ByteArray(0)
    private var index = 0L

    fun start(context: Context, callback: (buffer: ByteArray, size: Int, index: Long) -&gt; Unit): Boolean {
        stop()

        if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
            audioRecord = AudioRecord(
                AUDIO_SOURCE,
                SAMPLE_RATE,
                CHANNEL,
                AUDIO_FORMAT,
                bufferSize
            )

            CoroutineScope(Job() + Dispatchers.IO).launch {
                capture(callback)
            }
            return true
        }
        return false
    }

    fun stop() {
        if (isRecording.value) {
            audioRecord.stop()
            audioRecord.release()
        }
    }

    private suspend fun capture(callback: (buffer: ByteArray, size: Int, index: Long) -&gt; Unit) = coroutineScope {
        audioRecord.startRecording()
        _isRecording.update { true }
        index = 0L
        frame = ByteArray(0)

        while (audioRecord.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
            val buffer = ByteArray(bufferSize)
            val size = audioRecord.read(buffer, 0, bufferSize)
            if (size == 0) continue

            val copy = if (frameSize - frame.size &gt; size) size else frameSize - frame.size
            frame = frame + buffer.copyOfRange(0, copy)
            if (frame.size &lt; frameSize) continue

            callback(frame, frame.size, index++)
            frame = if (copy &gt;= size) ByteArray(0) else buffer.copyOfRange(copy, size)
        }

        if (frame.isNotEmpty()) callback(frame, frame.size, index)
        _isRecording.update { false }
    }
}</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>@Composable
fun Screen(modifier: Modifier = Modifier) {
    val recorder = AudioRecorder()
    val context = LocalContext.current
    val isRecording by recorder.isRecording.collectAsState()

    Row(modifier, horizontalArrangement = Arrangement.spacedBy(16.dp)) {
        Button(onClick = {
            if (!recorder.isRecording.value) {
                recorder.start(context) { buffer, size, index -&gt;
                    Log.d(&quot;AudioRecorder&quot;, &quot;$index $size&quot;)
                }
            }
            else {
                recorder.stop()
            }
        }) {
            Text(if (isRecording) &quot;stop&quot; else &quot;start&quot;)
        }
    }
}</code></pre></div>



<p>ぱーみっしょんちぇっく こぴぺこーど<br>※ Manifest.permission.RECORD_AUDIOではなくandroid.Manifest.permission.RECORD_AUDIOを参照</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted -&gt;
    Log.d(&quot;RequestPermission&quot;, &quot;$isGranted&quot;)
}

if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
    Log.d(&quot;checkSelfPermission&quot;, &quot;ok!&quot;)
} else {
    requestPermissionLauncher.launch(android.Manifest.permission.RECORD_AUDIO)
}</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala 2024.1.1 Patch 1 built on July 11, 2024</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 012. Google Map Compose</title>
		<link>https://bps-e.com/dev/android-005-012/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 04:58:26 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1438</guid>

					<description><![CDATA[使いすぎると課金されてしまいますが、Google MapのJetpack Compose版があったのでためしてみましたhttps://developers.google.com/maps/documentation/an [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>使いすぎると課金されてしまいますが、Google MapのJetpack Compose版があったのでためしてみました<br><a rel="noopener" href="https://developers.google.com/maps/documentation/android-sdk/maps-compose" target="_blank">https://developers.google.com/maps/documentation/android-sdk/maps-compose</a></p>



<p>まずはGCPでAPIキーを取得してください</p>



<p>APIキーはlocal.propertiesじゃなくて以下に設定</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="AndroidManifest.xml"><code>&lt;manifest
  &lt;application
    &lt;meta-data android:name=&quot;com.google.android.geo.API_KEY&quot; android:value=&quot;...&quot;&gt;</code></pre></div>



<p>サンプル通りでちゃんとGoogleMapが表示されました(当たり前ですが&#8230;</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>val singapore = LatLng(1.35, 103.87)
val cameraPositionState = rememberCameraPositionState {
  position = CameraPosition.fromLatLngZoom(singapore, 10f)
}
GoogleMap(
  modifier = Modifier.fillMaxSize(),
  cameraPositionState = cameraPositionState
) {
  Marker(
    state = MarkerState(position = singapore),
    title = &quot;Singapore&quot;,
    snippet = &quot;Marker in Singapore&quot;
  )
}</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala 2024.1.1 Patch 1 built on July 11, 2024</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>[Android] 011. FusedLocationProviderClient(位置情報)</title>
		<link>https://bps-e.com/dev/android-005-011/</link>
		
		<dc:creator><![CDATA[bps-e]]></dc:creator>
		<pubDate>Thu, 22 Aug 2024 03:32:58 +0000</pubDate>
				<category><![CDATA[Jetpack Compose]]></category>
		<category><![CDATA[Koala]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Material 3]]></category>
		<guid isPermaLink="false">https://bps-e.com/dev/?p=1433</guid>

					<description><![CDATA[位置情報取得はLocationManagerでもできますが、こちらを使うのが推奨っぽいのでためしてみましたhttps://developer.android.com/develop/sensors-and-locatio [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>位置情報取得はLocationManagerでもできますが、こちらを使うのが推奨っぽいのでためしてみました<br><a rel="noopener" href="https://developer.android.com/develop/sensors-and-location/location" target="_blank">https://developer.android.com/develop/sensors-and-location/location</a></p>



<p>以下の依存関係追加<br>com.google.android.gms:play-services-location</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="AndroidManifest.xml"><code>&lt;manifest
    &lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&gt;
    &lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>import android.os.Looper
import androidx.core.content.ContextCompat

class MainActivity : ComponentActivity() {
    lateinit var fusedLocationClient: FusedLocationProviderClient
    val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 500)
    val locationCallback: LocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            super.onLocationResult(locationResult)
            locationResult.lastLocation?.apply {
                location.update { &quot;$latitude, $longitude&quot; }
            }
        }
    }
    val location = MutableStateFlow(&quot;&quot;)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        val locationPermissionRequest = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { permissions -&gt;
            when {
                permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -&gt; {
                    Log.d(&quot;MainActivity&quot;, &quot;ACCESS_FINE_LOCATION false&quot;)
                }
                permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -&gt; {
                    Log.d(&quot;MainActivity&quot;, &quot;ACCESS_COARSE_LOCATION false&quot;)
                }
                else -&gt; {}
            }
        }
        locationPermissionRequest.launch(
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
        )
        ...
    }

    override fun onResume() {
        super.onResume()

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            fusedLocationClient.requestLocationUpdates(
                locationRequest.build(),
                locationCallback,
                Looper.getMainLooper()
            )
        }
    }

    override fun onStop() {
        super.onStop()
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }
}

@Composable
fun Screen(text: String, modifier: Modifier = Modifier) {
    Text(text = text, modifier = modifier)
}
</code></pre></div>



<p>エミュレータで試しているのであれば位置情報変更も簡単にできます</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="822" height="652" src="https://bps-e.com/dev/wp-content/uploads/2024/08/image-8.png" alt="" class="wp-image-1434" style="width:538px;height:auto" srcset="https://bps-e.com/dev/wp-content/uploads/2024/08/image-8.png 822w, https://bps-e.com/dev/wp-content/uploads/2024/08/image-8-300x238.png 300w, https://bps-e.com/dev/wp-content/uploads/2024/08/image-8-768x609.png 768w" sizes="(max-width: 822px) 100vw, 822px" /></figure>



<div style="height:var(--wp--preset--spacing--20)" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">LocationManager</h2>



<p>せっかく？なのでLocationManagerの場合は以下</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>class MainActivity : ComponentActivity() {
    lateinit var locationManager: LocationManager
    val locationListener: LocationListener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
            locate.update { &quot;${location.latitude}, ${location.longitude}&quot; }
        }
    }
    val locate = MutableStateFlow(&quot;&quot;)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        ...
    }

    override fun onResume() {
        super.onResume()

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
        }
        else {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000,
                50f,
                locationListener)
            locationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER,
                1000,
                50f,
                locationListener)
        }
    }

    override fun onPause() {
        super.onPause()
        locationManager.removeUpdates(locationListener)
    }
}</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="has-text-align-right">Android Studio Koala 2024.1.1 Patch 1 built on July 11, 2024</p>



<p></p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
