Development record of developer who study hard everyday.

레이블이 컴포즈인 게시물을 표시합니다. 모든 게시물 표시
레이블이 컴포즈인 게시물을 표시합니다. 모든 게시물 표시
, , , , ,

How to use UnityPlayer in Android Compose

How to use UnityPlayer in Android Compose

Android Development blog

In my company, I have to use 3D avatar animation using UnityPlayer derived from Unity module.

The base code I will use is consist of Android Compose.

So, I want to use UnityPlayer with Android Compose.

However, There were no information about using UnityPlayer with Compose.

This is why I write this post.


☝I introduce this post on the condition that you add UnityModule.

If you not, read this post first!!

https://antwhale94.blogspot.com/2024/03/AddingUnityModuleToAndroid.html


1. Add dependency

android {
...
buildFeatures {
viewBinding true
}     ...
}

dependencies {

implementation 'androidx.compose.ui:ui-viewbinding:1.6.0'

}

You should add viewBinding dependency.

And you should add "androidx.compose.ui:ui-viewbinding" to take xml into Compose.


2.  MainActivity.kt

class MainActivity : ComponentActivity() {
val TAG = MainActivity::class.java.simpleName
lateinit var mUnityPlayer: UnityPlayer

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

mUnityPlayer = UnityPlayer(this)
val glesMode = mUnityPlayer.getSettings().getInt("gles_mode", 1)
val trueColor8888 = false
mUnityPlayer.init(glesMode, trueColor8888)

setContent {
Surface(
modifier = Modifier
.fillMaxSize()
) {
MLCChatTheme {
NavView()
}
}
}


}

override fun onDestroy() {
Log.d(TAG, "onDestroy: ")
super.onDestroy()

mUnityPlayer.quit()
}

override fun onPause() {
Log.d(TAG, "onPause: ")
super.onPause()
mUnityPlayer.pause()
}

override fun onResume() {
Log.d(TAG, "onResume: ")
super.onResume()
mUnityPlayer.resume()
}

override fun onLowMemory() {
Log.d(TAG, "onLowMemory: ")
super.onLowMemory()
mUnityPlayer.lowMemory()
}

override fun onTrimMemory(level: Int) {
Log.d(TAG, "onTrimMemory: ")
super.onTrimMemory(level)
if(level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL)
mUnityPlayer.lowMemory()
}

override fun onConfigurationChanged(newConfig: Configuration) {
Log.d(TAG, "onConfigurationChanged: ")
super.onConfigurationChanged(newConfig)
mUnityPlayer.configurationChanged(newConfig)
}

override fun onWindowFocusChanged(hasFocus: Boolean) {
Log.d(TAG, "onWindowFocusChanged: $hasFocus")
super.onWindowFocusChanged(hasFocus)
mUnityPlayer.windowFocusChanged(hasFocus)
}

}

fun Context.getActivity(): ComponentActivity? = when (this) {
is ComponentActivity -> this
is ContextWrapper -> baseContext.getActivity()
else -> null
}

In my experience, The UnityPlayer derived from activity only can play animation.

I don't know why, so I make UnityPlayer object in MainActivity using MainActivity context.

The lowest function getActivity is used to get MainActivity from Composable function.


3. xml layout to put UnityPlayer

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/frameLayout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

</FrameLayout>

This layout file's name is "avatar_view_layout.xml"

Obviously, you can change this file name if you want.


4. Composable function to show UnityPlayer

@Composable
fun AvatarView() {
val TAG = "AvatarView"
val localContext = LocalContext.current

Log.d(TAG, "AvatarView composition")

val unityPlayer = (localContext.getActivity() as MainActivity).mUnityPlayer    //1
AndroidViewBinding(AvatarViewLayoutBinding::inflate) {     //2
if(unityPlayer.parent == null) {
Log.d(TAG,
"AvatarView added")
frameLayout.addView(unityPlayer.view)        //3
unityPlayer.resume()
}
}

DisposableEffect(key1 = localContext) {
onDispose {        //4
Log.d(TAG, "remove AvatarView")
(unityPlayer.
parent as FrameLayout).removeView(unityPlayer.view)

unityPlayer.pause()

}
}
}

1 => Get UnityPlayer object using local context

2 => AndroidViewBinding to use xml in Compose

3 => Add UnityPlayer to FrameLayout and resume UnityPlayer

4 => DisposableEffect to notice when AvatarView composable leave.

You should remove UnityPlayer from framelayout and pause it to restart when recomposing.


I don't know this is good way to use UnityPlayer with Compose, but This is only way I got this.




Share:
Read More
, , ,

Basic of Android Jetpack Compose

 Basic of Android Jetpack Compose


Android Develop blog

Jetpack Compose is a modern toolkit for building native Android UI.

Jetpack Compose is built around composable functions.

These functions let you define your app's UI programmatically by describing how it should look and providing data dependencies, rather than focusing on the process of the UI's construction.

To create a composable function, just add the @Composable annotation to the function name.


1.Composable functions

Add a text element

Compose "Hellow World!"

Define a content block and call the Text composable function.

The setContent block defines the activity's layout where composable functions are called.

Composable functions can only be called from other composable functions.


Define a composable function

To make a function composable, add the @Composable annotation.

Define a Composable function

Define a MessageCard function which is passed a name, and uses it to configure the text element.


Preview your function in Android Studio

The @Preview annotation lets you preview your composable functions within Android Studio without having to build and install the app to an Android device or emulator.

The annotation must be used on a composable function that does not take in parameters.

Fore this reason, you can't preview the MessageCard function directly.

Instead, make a second function named PreviewMessageCard, which calls MessageCard with an appropriate parameter.


The app itself doesn't change, since the new PreviewMessageCard function isn't called anywhere,

but Android Studio adds a preview window which you can expand by clicking on the split view.

This window shows a preview of the UI elements created by composable functions marked with the @Preview annotation.

My Android Studio is in dark theme mode. So, background of preview is a little bit black.


2. Layouts

Add multiple texts

UI elements are hierarchical, with elements contained in other elements.

Message Layout

Above code creates two text elements inside the content view.

However, since you haven't provided any information about how to arrange them, the text elements are drawn on top of each other, making text unreadable.

 

Using a Column

The Column function lets you arrange elements vertically.

You can use Row to arrange items horizontally and Box to stack elements.


Using a Column


Add an image element

Add a Row composable to have a well structured design and an Image composable inside it.

Add Image composable


Configure your layout

To decorate or configure a composable, Compose uses modifiers.

They allow you to change the composable's size, layout, appearance or add high-level interactions, such as making an element clickable.

Decorate layout


3. Material Design

Use Material Design

Compose is built to support Material Design principles. Many of its UI elements implement Material Design out of the box.

Jetpack Compose provides an implementation of Material Design 3 and its UI elements out of the box.

Jetpack Compose with Material Design

To start, wrap the MessageCard function with the Material theme created in your project, ComposeCodelabTheme, as well as a Surface.

Doing so will allow your composables to inherit styles as defined in your app's theme ensuring consistency across your app.

Material Design is built around three pillars: Color, Typography, and Shape

✋ Empty Compose Activity template generates a default theme for your project that allows you to customize MaterialTheme.

You can find your custom theme in the Theme.kt file.


Color

Use MaterialTheme.colorScheme to style with colors from the wrapped theme.

You can use these values from the theme anywhere a color is needed.


Typography

Material Typography styles are available in the MaterialTheme, just add them to the Text composables.


Shape

Wrap the message body text around a Surface composable.

Doing so allows customizing the message body's shape and elevation.

Padding is also added to the message for a better layout.

Apply Material typography and shape


Enable dark theme

Dark theme can be enabled to avoid a bright display especially at night, or simply to save the device battery.

Enable dark theme

You can create multiple previews in your file as separate functions, or add multiple annotations to the same function.


4. Lists and animations

Create a list of messages

We use LazyColumn and LayRow to display several messages.

These composables render only the elements that are visible on screen, so they are designed to be very efficient for long lists.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
}
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "launcher foreground",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)

Spacer(modifier = Modifier.width(8.dp))

Column {
Text(text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(4.dp))

Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
Text(text = msg.body,
modifier = Modifier.padding(all = 4.dp),
style = MaterialTheme.typography.bodyMedium)
}
}
}
}

@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeCodeLabTheme {
Surface {
MessageCard(Message("Lexi", "Hey, take a look at Jetpack Compose"))
}
}
}

@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) {message ->
MessageCard(message)
}
}
}

@Preview
@Composable
fun PreviewConversation() {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}

You can see that LazyColumn has an items child.

It takes a List as parameters and its lambda receives a parameter we've named message which is an instance of Message.

In short, this lambda is called for each item of the provided List.

👇 SampleData.kt

object SampleData {
// Sample conversation data
val conversationSample = listOf(
Message(
"Lexi",
"Test...Test...Test..."
),
Message(
"Lexi",
"""List of Android versions:
|Android KitKat (API 19)
|Android Lollipop (API 21)
|Android Marshmallow (API 23)
|Android Nougat (API 24)
|Android Oreo (API 26)
|Android Pie (API 28)
|Android 10 (API 29)
|Android 11 (API 30)
|Android 12 (API 31)""".trim()
),
Message(
"Lexi",
"""I think Kotlin is my favorite programming language.
|It's so much fun!""".trim()
),
Message(
"Lexi",
"Searching for alternatives to XML layouts..."
),
Message(
"Lexi",
"""Hey, take a look at Jetpack Compose, it's great!
|It's the Android's modern toolkit for building native UI.
|It simplifies and accelerates UI development on Android.
|Less code, powerful tools, and intuitive Kotlin APIs :)""".trim()
),
Message(
"Lexi",
"It's available from API 21+ :)"
),
Message(
"Lexi",
"Writing Kotlin for UI seems so natural, Compose where have you been all my life?"
),
Message(
"Lexi",
"Android Studio next version's name is Arctic Fox"
),
Message(
"Lexi",
"Android Studio Arctic Fox tooling for Compose is top notch ^_^"
),
Message(
"Lexi",
"I didn't know you can now run the emulator directly from Android Studio"
),
Message(
"Lexi",
"Compose Previews are great to check quickly how a composable layout looks like"
),
Message(
"Lexi",
"Previews are also interactive after enabling the experimental setting"
),
Message(
"Lexi",
"Have you tried writing build.gradle with KTS?"
),
)
}


Result of LazyColumn


Animate messages while expanding

It's time to play with animations.

We need to keep track of local UI state whether a message has been expanded or not.

We have to use the functions remember and mutableStateOf.

Composable functions can store local state in memory by using remember, and track changes to the value passed to mutableStateOf.

Composables using this state will get redrawn automatically when the value is updated.

This is called recomposition.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
}
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "launcher foreground",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)

Spacer(modifier = Modifier.width(8.dp))

var isExpanded by remember { mutableStateOf(false) }

Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {

Text(text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall)

Spacer(modifier = Modifier.height(4.dp))

Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
Text(text = msg.body,
modifier = Modifier.padding(all = 4.dp),
maxLines = if(isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.bodyMedium)
}
}
}
}

@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeCodeLabTheme {
Surface {
MessageCard(Message("Lexi", "Hey, take a look at Jetpack Compose"))
}
}
}

@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) {message ->
MessageCard(message)
}
}
}

@Preview
@Composable
fun PreviewConversation() {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}


You will use the clickable modifier to handle click events on the composable.

Instead of just toggling the background color of the Surface, you will animate the background color by gradually modifying its value from MaterialTheme.colorScheme.surface to MaterialTheme.colorScheme.primary and vice versa.

Lastly, you will use the animateContentSize modifier to animate the message container size smoothly.

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}
}
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "launcher foreground",
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
)

Spacer(modifier = Modifier.width(8.dp))

var isExpanded by remember { mutableStateOf(false) }

val surfaceColor by animateColorAsState(
if (isExpanded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surface,
label = "",
)

Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {

Text(text = msg.author,
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.titleSmall)

Spacer(modifier = Modifier.height(4.dp))

Surface(shape = MaterialTheme.shapes.medium,
shadowElevation = 1.dp,
color = surfaceColor,
modifier = Modifier.animateContentSize().padding(1.dp)
) {
Text(text = msg.body,
modifier = Modifier.padding(all = 4.dp),
maxLines = if(isExpanded) Int.MAX_VALUE else 1,
style = MaterialTheme.typography.bodyMedium)
}
}
}
}

@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeCodeLabTheme {
Surface {
MessageCard(Message("Lexi", "Hey, take a look at Jetpack Compose"))
}
}
}

@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) {message ->
MessageCard(message)
}
}
}

@Preview
@Composable
fun PreviewConversation() {
ComposeCodeLabTheme {
Conversation(SampleData.conversationSample)
}
}


https://github.com/antwhale/ComposeCodeLab

👆You can find full code through above link

Share:
Read More