@Composable
fun Label() {
Text(
text = "Hello World",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.wrapContentHeight()
.wrapContentWidth(),
style = TextStyle(background = Color(0xFF4a8fff))
)
}
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FF4a8fff"
android:ellipsize="end"
android:text = "YourText"
android:maxLines="1" />
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun Password() {
var text by remember { mutableStateOf(TextFieldValue("")) }
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
value = text,
onValueChange = {
text = it
},
modifier = Modifier
.requiredWidth(250.dp)
.wrapContentHeight(),
maxLines = 1,
label = {
Text(text = "Your Label")
},
placeholder = {
Text(text = "Your Placeholder/Hint")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() }),
visualTransformation = PasswordVisualTransformation()
)
}
<com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Your Label"
app:placeholderText="Your Placeholder">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="250dp"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun NumberPassword() {
var text by remember { mutableStateOf(TextFieldValue("")) }
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
value = text,
onValueChange = {
text = it
},
maxLines = 1,
label = {
Text(text = "Your Label")
},
placeholder = {
Text(text = "Your Placeholder/Hint")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.NumberPassword,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() }),
visualTransformation = PasswordVisualTransformation()
)
}
<com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Your Label"
app:placeholderText="Your Placeholder">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="250dp"
android:layout_height="wrap_content"
android:inputType="numberPassword"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun PasswordVisibilityToggle() {
var text by remember { mutableStateOf(TextFieldValue("")) }
var passwordVisibility by remember { mutableStateOf(false) }
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
value = text,
onValueChange = {
text = it
},
maxLines = 1,
modifier = Modifier
.requiredWidth(250.dp)
.wrapContentHeight(),
label = { Text(text = "Your Label") },
placeholder = { Text(text = "Your Placeholder/Hint") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() }),
visualTransformation = if (passwordVisibility) {
VisualTransformation.None
} else {
PasswordVisualTransformation()
},
trailingIcon = {
val image = if (passwordVisibility) {
Icons.Filled.Visibility
} else {
Icons.Filled.VisibilityOff
}
IconButton(onClick = {
passwordVisibility = !passwordVisibility
}) {
Icon(imageVector = image, "")
}
}
)
}
<com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true"
android:hint="Your Hint"
app:placeholderText="Your Placeholder">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="250dp"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Email() {
var text by remember { mutableStateOf(TextFieldValue("")) }
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
value = text,
onValueChange = {
text = it
},
modifier = Modifier
.requiredWidth(250.dp)
.wrapContentHeight(),
maxLines = 1,
label = {
Text(text = "Your Label")
},
placeholder = {
Text(text = "Your Placeholder/Hint")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {keyboardController?.hide()} )
)
}
<com.google.android.material.textfield.TextInputLayout
app:placeholderText="Your Placeholder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Your Label">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="250dp"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun PhoneNumber() {
var text by remember { mutableStateOf(TextFieldValue("012345678900")) }
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
value = text,
onValueChange = {
text = it
},
maxLines = 1,
modifier = Modifier
.wrapContentWidth()
.wrapContentHeight(),
label = { Text(text = "Your Label") },
placeholder = { Text(text = "Your Placeholder/Hint") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Phone,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = { keyboardController?.hide() })
)
}
<com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Your Label"
app:placeholderText="Your Placeholder">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="250dp"
android:layout_height="wrap_content"
android:inputType="phone"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
@Composable
fun PostalAddressExample() {
var address by remember { mutableStateOf("") }
TextField(
value = address,
onValueChange = { address = it },
)
}
<EditText
android:id="@+id/editTextTextPostalAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPostalAddress" />
@Composable
fun MultilineText() {
val text = remember { mutableStateOf("") }
TextField(
value = text.value,
onValueChange = {
text.value = it
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text
),
label = {
Text(text = "Label")
},
placeholder = {
Text(text = "Your Placeholder/Hint")
},
maxLines = 3,
modifier = Modifier.width(250.dp)
)
}
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/editTextTextMultiLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:gravity="start|top"
android:hint="@string/your_label"
android:inputType="textMultiLine"
app:placeholderText="Your Placeholder">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="250dp"
android:layout_height="wrap_content"
android:maxLines="3" />
</com.google.android.material.textfield.TextInputLayout>
@Composable
fun NumberExample() {
var number by remember { mutableStateOf("") }
TextField(
value = number,
onValueChange = { input ->
number = input
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
<EditText
android:id="@+id/editTextNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number" />
@Composable
fun NumberSignedExample() {
var number by remember { mutableStateOf("") }
TextField(
value = number,
onValueChange = { input ->
number = validateInput(input)
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
private fun validateInput(input: String): String {
val filteredChars = input.filterIndexed { index, char ->
char in "0123456789" ||
(char == '-' && 0 == index)
}
return filteredChars
}
<EditText
android:id="@+id/editTextNumberSigned"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="numberSigned" />
@Composable
fun NumberDecimalExample() {
var number by remember { mutableStateOf("") }
TextField(
value = number,
onValueChange = { input ->
number = validateInput(input)
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
private fun validateInput(input: String): String {
val filteredChars = input.filterIndexed { index, char ->
char in "0123456789" ||
(char == '.' && input.indexOf('.') == index)
}
return filteredChars
}
<EditText
android:id="@+id/editTextNumberDecimal"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:inputType="numberDecimal" />
/* This is NOT an official implementation!
* Look if there is an official implementaion already*/
@Composable
private fun AutoCompleteExample() {
val countriesList = listOf(
"Germany",
"Spain",
"France",
)
val dropDownOptions = remember { mutableStateOf(listOf("")) }
val textFieldValue = remember { mutableStateOf(TextFieldValue()) }
val dropDownExpanded = remember { mutableStateOf(false) }
fun onDropdownDismissRequest() {
dropDownExpanded.value = false
}
fun onValueChanged(value: TextFieldValue) {
dropDownExpanded.value = true
textFieldValue.value = value
dropDownOptions.value =
countriesList.filter { it.startsWith(value.text) && it != value.text }.take(3)
}
TextFieldWithDropdown(
value = textFieldValue.value,
setValue = ::onValueChanged,
onDismissRequest = ::onDropdownDismissRequest,
dropDownExpanded = dropDownExpanded.value,
list = dropDownOptions.value,
)
}
@Composable
private fun TextFieldWithDropdown(
value: TextFieldValue,
setValue: (TextFieldValue) -> Unit,
onDismissRequest: () -> Unit,
dropDownExpanded: Boolean,
list: List
) {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
.onFocusChanged { focusState ->
if (!focusState.isFocused)
onDismissRequest()
},
value = value,
onValueChange = setValue,
label = { Text("Enter a country of Europe") },
colors = TextFieldDefaults.outlinedTextFieldColors()
)
DropdownMenu(
expanded = dropDownExpanded,
properties = PopupProperties(
focusable = false,
dismissOnBackPress = true,
dismissOnClickOutside = true
),
onDismissRequest = onDismissRequest,
) {
list.forEach { text ->
DropdownMenuItem(
modifier = Modifier.clickable(
enabled = true,
onClickLabel = text,
null
) {
setValue(
TextFieldValue(
text,
TextRange(text.length)
)
)
},
onClick = {
setValue(
TextFieldValue(
text,
TextRange(text.length)
)
)
}
) {
Text(text = text)
}
}
}
}
<AutoCompleteTextView
android:id="@+id/auto_complete_text_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Enter a country in Europe" />
<!-- Code implementation -->
private lateinit var binding: AutoCompleteBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = AutoCompleteBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title = "Auto Complete"
val countriesList = resources.getStringArray(R.array.auto_complete_array)
val arrayAdapter = ArrayAdapter(this, R.layout.support_simple_spinner_dropdown_item, countriesList)
binding.autoCompleteTextView.setAdapter(arrayAdapter)
}
<!-- Data -->
<resources>
<string-array name="auto_complete_array">
<item>Germany</item>
<item>Great Britain</item>
<item>France</item>
<item>Spain</item>
</string-array>
</resources>
@Composable
fun TextInputExample() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = {
text = it
},
label = { Text("hint") }
)
}
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="hint" />
</com.google.android.material.textfield.TextInputLayout>
@Composable
fun ImageButton(image: Any?) {
Button(
onClick = {},
modifier = Modifier.size(100.dp),
shape = CircleShape,
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.White
),
elevation = ButtonDefaults.elevation(0.dp, 0.dp)
) {
GlideImage(
imageModel = image,
contentScale = ContentScale.FillWidth,
contentDescription = "Image of Button",
modifier = Modifier.fillMaxSize()
)
}
}
<ImageButton
android:id="@+id/imageButton"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/example_image_button"
android:onClick="onImageButtonClick"/>
// In activity
val imageView = findViewById<android.widget.ImageButton>(R.id.imageButton)
Glide
.with(this)
.load(R.drawable.c24logo)
.fitCenter()
.into(imageView)
@Composable
fun ChipGroupSingleSelectionExample(list: List = listOf("I'm a list")) {
var selectedChip by remember { mutableStateOf("") }
LazyRow(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
) {
items(list) {
ActionChip(it)
}
}
}
<HorizontalScrollView
android:id="@+id/horizontal_scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chip_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chip One"
android:textAlignment="center" />
[...]
<com.google.android.material.chip.Chip
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chip Ten"
android:textAlignment="center" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
/* At the time of the implementation there were no official implementation!
* Look if there is an official implementaion already*/
@Composable
fun ActionChip(
name: String = "Action Chip",
icon: Painter? = null,
onToggle: ((String) -> Unit)? = null
) {
var isSelected by remember { mutableStateOf(false) }
val modifier = if (icon == null) {
Modifier.padding(horizontal = 12.dp)
} else Modifier.padding(start = 4.dp, end = 12.dp)
Surface(
modifier = Modifier.padding(4.dp),
shape = CircleShape,
color = if (isSelected) Purple100 else Gray300,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(32.dp)
.toggleable(
value = isSelected,
onValueChange = {
isSelected = !isSelected
onToggle?.invoke(name)
}
)
) {
if (icon != null) {
Icon(
icon,
"Icon",
tint = if (isSelected) Green else Red,
modifier = Modifier
.padding(horizontal = 4.dp)
.width(24.dp)
)
}
Text(
text = name,
color = if (isSelected) Purple500 else Gray700,
style = MaterialTheme.typography.body2,
modifier = modifier
)
}
}
}
<com.google.android.material.chip.Chip
android:id="@+id/chip_with_icon"
style="@style/Widget.MaterialComponents.Chip.Choice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chip With Icon"
app:chipIconEnabled="true"
app:chipIcon="@drawable/ic_your_icon" />
@Composable
fun CheckBox() {
var checked by remember { mutableStateOf(true) }
Checkbox(
checked = checked,
onCheckedChange = {
checked = it
}
)
}
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onCheckBoxClicked"
android:checked="true" />
@Composable
fun RadioGroup() {
val stringList = listOf(
"First Radio Button",
"Second Radio Button",
"Third Radio Button"
)
val rememberObserver = remember {
mutableStateOf("")
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
stringList.forEach { item ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
RadioButton(selected = rememberObserver.value == item,
onClick = {
rememberObserver.value = item
}
)
Text(
text = item,
modifier = Modifier.clickable { rememberObserver.value = item }
)
}
}
}
}
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:id="@+id/first_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="First Radio Button"/>
<RadioButton
android:id="@+id/second_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Second Radio Button"/>
<RadioButton
android:id="@+id/third_radio_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Third Radio Button"/>
</RadioGroup>
@Composable
fun RadioButtonExample() {
val observer = remember { mutableStateOf(false) }
RadioButton(
selected = observer.value,
onClick = { observer.value = !observer.value }
)
Text(
text = "I am a Radio Button",
modifier = Modifier
.clickable {
observer.value = !observer.value
}
)
}
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am a Radio Button"/>
/* this is a custom build toggle button because*/
/* there is no toggle button in compose right now */
@Composable
fun CustomToggleButton() {
val buttonClicked: MutableState = remember {
mutableStateOf(false)
}
val buttonText: String
val buttonTextColor: Color
val buttonBackgroundColor: Color
if (buttonClicked.value) {
buttonText = "On"
buttonTextColor = Color.Green
buttonBackgroundColor = Color.LightGray
} else {
buttonText = "Off"
buttonTextColor = Color.Red
buttonBackgroundColor = Color.Gray
}
Button(
onClick = { buttonClicked.value = !buttonClicked.value },
colors = ButtonDefaults.buttonColors(backgroundColor = buttonBackgroundColor)
) {
Text(text = buttonText, color = buttonTextColor)
}
}
<ToggleButton
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@Composable
fun Switch() {
val checkedState = remember { mutableStateOf(false) }
Switch(
checked = checkedState.value,
onCheckedChange = { checkedState.value = it }
)
}
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@Composable
private fun FloatingActionButton() {
FloatingActionButton()
onClick = { /*your code*/ }
) {
Icon(
Icons.Rounded.Add,
contentDescription = "Floating ActionButton"
)
}
/* Or you can use an Extended Floating Action Button */
FloatingActionButton(
modifier = Modifier.constrainAs(RectangleFAB) { },
onClick = { /*your code*/ },
shape = RectangleShape
) {
Icon(
Icons.Rounded.Add,
contentDescription = "Floating ActionButton"
)
}
}
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_baseline_date_range"
android:contentDescription="FloatingActionButton" />
/* Or you can use an Extended Floating Action Button */
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/extended_floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Extended FAB" />
sealed class ExampleListItems {
data class TitleItem(
val title: String
) : ExampleListItems()
data class ContentItem(
val title: String,
val body: String
) : ExampleListItems()
}
@Composable
fun ExampleItemsListView(listOfExampleItems: List<ExampleListItems>) {
LazyColumn {
items(listOfExampleItems) { item ->
when (item) {
is ExampleListItems.TitleItem -> TitleItemView(item = item)
is ExampleListItems.ContentItem -> ContentItemView(item = item)
}
}
}
}
@Composable
fun TitleItemView(item: ExampleListItems.TitleItem) {
Row(
modifier = Modifier
.background(color = Color.LightGray)
.padding(all = 20.dp)
) {
Text(text = item.title, fontWeight = FontWeight.Bold)
}
}
@Composable
fun ContentItemView(item: ExampleListItems.ContentItem) {
Row(
modifier = Modifier
.background(color = Color.Cyan)
.padding(all = 20.dp)
) {
Column {
Text(text = item.title, fontWeight = FontWeight.SemiBold)
Spacer(modifier = Modifier.padding(vertical = 8.dp))
Text(text = item.body, fontWeight = FontWeight.ExtraLight)
}
}
}
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/shared_colorBackground"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
// Define a layout file for a singular item, one for each viewType
// Define two constants similar to this
val CONTENT_TYPE = 0
val TITLE_TYPE = 1
// Write Custom List Adapter
class DifferentViewAdapter :
ListAdapter<ExampleListItems, RecyclerView.ViewHolder>(DifferentViewDiffCallback) {
// Handle the Animations of deleted, changed items
object DifferentViewDiffCallback : DiffUtil.ItemCallback<ExampleListItems>() {
override fun areItemsTheSame(
oldItem: ExampleListItems,
newItem: ExampleListItems
): Boolean =
oldItem.hashCode() == newItem.hashCode()
override fun areContentsTheSame(
oldItem: ExampleListItems,
newItem: ExampleListItems
): Boolean =
oldItem == newItem
}
// Create the matching ViewHolder for the current viewType
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TITLE_TYPE)
TitleViewHolder(
TitleItemBinding.inflate(
LayoutInflater
.from(parent.context), parent, false
).root
)
else ContentViewHolder(
ContentItemBinding.inflate(
LayoutInflater
.from(parent.context), parent, false
).root
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is TitleViewHolder) {
val titleItem = getItem(position) as ExampleListItems.TitleItem
val binding = TitleItemBinding.bind(holder.itemView)
binding.title.text = titleItem.title
} else if (holder is ContentViewHolder) {
val contentItem = getItem(position) as ExampleListItems.ContentItem
val binding = ContentItemBinding.bind(holder.itemView)
with(binding) {
contentTitle.text = contentItem.title
contentBody.text = contentItem.body
}
}
}
// Specify the viewType
override fun getItemViewType(position: Int): Int {
return if (getItem(position) is ExampleListItems.ContentItem) CONTENT_TYPE
else TITLE_TYPE
}
class ContentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class TitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
// Set Adapter in Activity/Fragment and submit your data
class AndroidUIListWithDifferentViewsActivity : AppCompatActivity() {
var binding: RecyclerViewListBinding? = null
var adapter: DifferentViewAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.title = "Different View List"
binding = RecyclerViewListBinding.inflate(LayoutInflater.from(this), null, false)
setContentView(binding?.root)
adapter = DifferentViewAdapter()
binding?.recyclerViewList?.adapter = adapter
val exampleList = mutableListOf(
ExampleListItems.TitleItem("I am a title"),
ExampleListItems.TitleItem("I am a title"),
ExampleListItems.ContentItem("I am a title", "I am body"),
ExampleListItems.TitleItem("I am a title")
)
adapter?.submitList(exampleList)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GridExample() {
val colorList = listOf(
Color.Red,
Color.Black,
Color(0xFFBB86FC),
Color(0xFF3700B3)
)
LazyVerticalGrid(
cells = GridCells.Fixed(2),
modifier = Modifier.size(200.dp)
) {
(0..3).forEach {
item {
Box(
modifier = Modifier
.size(100.dp)
.background(colorList[it])
)
}
}
}
}
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="2"
android:rowCount="2">
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/red" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/black" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/purple_200" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/purple_700" />
</GridLayout>
@Composable
fun FixedGridExample() {
val colorList = listOf(
Color.Red,
Color.Black,
Purple200,
Purple700
)
FixedGrid(2) {
(0..3).forEach {
Box(
modifier = Modifier
.size(100.dp)
.background(colorList[it % 4])
)
}
}
}
@Composable
fun FixedGrid(
columnCount: Int,
content: @Composable () -> Unit
) {
Layout(content = content) { measurables, constraints ->
val rowCount: Int =
ceil(measurables.size.toDouble() / columnCount.toDouble()).toInt()
val placeables = measurables.map { measurable ->
measurable.measure(constraints = constraints)
}
val placeableWidths = placeables.map { it.width }
val placeableHeights = placeables.map { it.height }
// get maximum widths of elements in each column
val widths = getWidths(columnCount, placeableWidths)
// get maximum heights of elements in each row
val heights = getHeights(columnCount, placeableHeights)
val layoutWidth = widths.sum()
val layoutHeight = heights.sum()
val xPositions = List(columnCount) {
var result = 0
for (i in 0 until it) {
result += widths[i]
}
result
}
val yPositions = List(rowCount) {
var result = 0
for (i in 0 until it) {
result += heights[i]
}
result
}
layout(
width = layoutWidth,
height = layoutHeight
) {
placeables.forEachIndexed { index, placeable ->
placeable.place(
x = xPositions[index % columnCount],
y = yPositions[index / columnCount]
)
}
}
}
}
fun getWidths(
columnCount: Int,
widths: List,
): List {
val result = mutableListOf()
for (i in 0 until columnCount) {
// specifies the width of the column by the widest element
var max = 0
for (j in i until widths.size step columnCount) {
if (max < widths[j]) max = widths[j]
}
result.add(max)
}
return result
}
fun getHeights(
columnCount: Int,
heights: List,
): List {
val result = mutableListOf()
for (i in heights.indices step columnCount) {
// specifies the height of the row by the highest element
var max = 0
for (j in i until min(i + columnCount, heights.size)) {
if (max < heights[j]) max = heights[j]
}
result.add(max)
}
return result
}
@Composable
private fun StaggeredGrid() {
LazyColumn {
item {
StaggeredVerticalGrid(
maxColumnWidth = 155.dp,
modifier = Modifier.padding(4.dp)
) {
(1..100).forEach {
GridItem()
}
}
}
}
}
@Composable
private fun GridItem() {
Card(
modifier = Modifier
.height((100..250).random().dp)
.padding(5.dp),
shape = RoundedCornerShape(15.dp),
elevation = 5.dp
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256)))
)
}
}
@Composable
private fun StaggeredVerticalGrid(
modifier: Modifier = Modifier,
maxColumnWidth: Dp,
content: @Composable () -> Unit
) {
Layout(
content = content,
modifier = modifier
) { measurables, constraints ->
check(constraints.hasBoundedWidth) {
"Unbounded width not supported"
}
// get how many columns fit on screen
val columns = ceil(constraints.maxWidth / maxColumnWidth.toPx()).toInt()
// get how wide every column can be
val columnWidth = constraints.maxWidth / columns
val itemConstraints = constraints.copy(maxWidth = columnWidth)
// track each column's height
val colHeights = MutableList(columns) { 0 }
val placeables = measurables.map { measurable ->
val column = shortestColumn(colHeights)
val placeable = measurable.measure(itemConstraints)
colHeights[column] += placeable.height
placeable
}
val height = colHeights.maxOrNull()
?.coerceIn(constraints.minHeight, constraints.maxHeight)
?: constraints.minHeight
layout(
width = constraints.maxWidth,
height = height
) {
// save the y coordinate for each column
val colY = MutableList(columns) { 0 }
placeables.forEach { placeable ->
val column = shortestColumn(colY)
placeable.place(
// calculate the x coordinate of each element
x = columnWidth * column,
y = colY[column]
)
colY[column] += placeable.height
}
}
}
}
private fun shortestColumn(colHeights: MutableList): Int {
var minHeight = Int.MAX_VALUE
var column = 0
colHeights.forEachIndexed { index, height ->
if (height < minHeight) {
minHeight = height
column = index
}
}
return column
}
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/staggered_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/shared_colorBackground"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="3"/>
// Define Layout for single item
<androidx.cardview.widget.CardView
android:id="@+id/color_card"
android:layout_width="120dp"
android:layout_height="wrap_content"
app:cardCornerRadius="15dp"
app:cardElevation="5dp"
app:cardUseCompatPadding="true"/>
// Write Custom Adapter
class StaggeredAdapter :
ListAdapter<Int, StaggeredAdapter.ColorViewHolder>(StaggeredDiffUtilCallback) {
object StaggeredDiffUtilCallback : DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean =
oldItem.hashCode() == newItem.hashCode()
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean =
oldItem == newItem
}
class ColorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder =
ColorViewHolder(
ColorItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
).root
)
override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
val color = getItem(position)
val binding = ColorItemBinding.bind(holder.itemView)
with(binding) {
colorCard.setCardBackgroundColor(color)
colorCard.updateLayoutParams {
width = 120.dp
height = Random.nextInt(100, 250).dp
}
}
}
// Extension function to get dp from Int
val Int.dp get() = (this * Resources.getSystem().displayMetrics.density).toInt()
}
// Set adapter in Activity or Fragment
class AndroidUIStaggeredListActivity : AppCompatActivity() {
private var binding: StaggeredRecyclerViewBinding? = null
private var adapter : StaggeredAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = StaggeredRecyclerViewBinding.inflate(layoutInflater)
setContentView(binding?.root)
adapter = StaggeredAdapter()
binding?.staggeredRecyclerView?.adapter = adapter
adapter?.submitList(createColorList())
supportActionBar?.title = "Staggered List"
}
private fun createColorList(): MutableList<Int> {
val colorList = mutableListOf<Int>()
(1..100).forEach {
colorList.add(Color.argb(255, Random.nextInt(256), Random.nextInt(256), Random.nextInt(256)))
}
return colorList
}
}
@Composable
fun ConstraintLayoutContent() {
ConstraintLayout {
// Create references for the composables to constrain
val (button, text) = createRefs()
Button(
onClick = { },
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
start.linkTo(parent.start)
}
) {
Text("Button")
}
Text("Text", Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
start.linkTo(parent.start)
})
}
}
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text"
app:layout_constraintTop_toBottomOf="@id/button"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@Composable
fun ConstraintLayoutGuidelineVertical() {
ConstraintLayout {
val (button) = createRefs()
val guideline = createGuidelineFromStart(100.dp)
Button(
onClick = { },
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
start.linkTo(guideline)
}
) {
Text("Button")
}
}
}
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="100dp" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintStart_toStartOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@Composable
fun ConstraintLayoutGuidelineHorizontal() {
ConstraintLayout {
val (button) = createRefs()
val guideline = createGuidelineFromTop(100.dp)
Button(
onClick = { },
modifier = Modifier.constrainAs(button) {
top.linkTo(guideline)
start.linkTo(parent.start)
}
) {
Text("Button")
}
}
}
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="100dp" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline" />
</androidx.constraintlayout.widget.ConstraintLayout>
@Composable
fun ConstraintLayoutBarrier() {
ConstraintLayout {
// Create references for the composables to constrain
val (text1, text2, button) = createRefs()
Text(
text = "text1",
modifier = Modifier
.constrainAs(text1) {
top.linkTo(parent.top)
start.linkTo(parent.start)
}
.height(200.dp)
.wrapContentWidth()
)
Text(
text = "text2",
modifier = Modifier
.constrainAs(text2) {
top.linkTo(parent.top)
end.linkTo(parent.end)
}
.wrapContentHeight()
.wrapContentWidth()
)
val barrier = createBottomBarrier(text1, text2)
Button(
onClick = { },
modifier = Modifier.constrainAs(button) {
top.linkTo(barrier)
start.linkTo(parent.start)
}
) {
Text("Button")
}
}
}
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="text1"/>
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="text2"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:barrierDirection="bottom"
app:constraint_referenced_ids="text1, text2" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintTop_toBottomOf="@+id/barrier" />
</androidx.constraintlayout.widget.ConstraintLayout>
// Vertical FlowLayout
// the crossAxisAlignment doesn't work properly
FlowColumn(
modifier = Modifier
.height(300.dp)
.wrapContentWidth(),
mainAxisAlignment = FlowMainAxisAlignment.Center,
mainAxisSpacing = 16.dp,
crossAxisSpacing = 50.dp,
crossAxisAlignment = FlowCrossAxisAlignment.Center,
) {
// add here your view elements
}
// Horizontal FlowLayout
// the crossAxisAlignment doesn't work properly
FlowRow(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(20.dp),
mainAxisAlignment = FlowMainAxisAlignment.Center,
crossAxisSpacing = 16.dp,
mainAxisSpacing = 16.dp
) {
// add here your code
}
<!-- Vertical FlowLayout -->
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow_vertical_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="vertical"
app:constraint_referenced_ids="id's of your elements"
app:flow_maxElementsWrap="5"
app:flow_verticalGap="16dp"
app:flow_wrapMode="aligned" />
<!-- Horizontal FlowLayout -->
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow_horizontal_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal" <--
app:constraint_referenced_ids="id's of your elements"
app:flow_horizontalGap="16dp"
app:flow_maxElementsWrap="5"
app:flow_verticalGap="16dp"
app:flow_wrapMode="chain"
app:layout_constraintHorizontal_chainStyle="spread" />
// At the time of the implementation there was no official compose equivalent
// This is an attempt to create a helperlayer
@Composable
private fun HelperLayerExample() {
var rotationRGB: Float by remember { mutableStateOf(0f) }
val angleRGB: Float by animateFloatAsState(
targetValue = rotationRGB,
animationSpec = tween(durationMillis = 2000, easing = LinearEasing)
)
var rotationRG: Float by remember { mutableStateOf(0f) }
val angleRG: Float by animateFloatAsState(
targetValue = rotationRG,
animationSpec = tween(durationMillis = 2000, easing = LinearEasing)
)
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
// first layer
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.graphicsLayer {
rotationZ = angleRGB
}
) {
// second layer within the first layer
Column(
modifier = Modifier.graphicsLayer {
rotationZ = angleRG
}
) {
ColorBox(color = Red200)
ColorBox(color = Green200)
}
ColorBox(color = Blue200)
}
Button(
onClick = { rotationRGB += 360f }
) {
Text(text = "Rotate 360°")
}
Button(
onClick = { rotationRG += 360f }
) {
Text(text = "Rotate Red and Green°")
}
}
}
<!-- XML Code -->
<View
android:id="@+id/view_red"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/red_200" />
[...]
<Button
android:id="@+id/rotate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Rotate 360ª" />
<androidx.constraintlayout.helper.widget.Layer
android:id="@+id/all_views"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="view_red,view_green,view_blue"
tools:ignore="MissingConstraints"/>
<!-- Code -->
binding?.rotateButton?.setOnClickListener {
ValueAnimator.ofFloat(0F, 360F)
.apply {
addUpdateListener { animator ->
binding?.allViews?.rotation = animator.animatedValue as Float
}
duration = 2000
start()
}
}
binding?.rotateRedAndGreenButton?.setOnClickListener {
ValueAnimator.ofFloat(0F, 360F)
.apply {
addUpdateListener { animator ->
binding?.viewRedAndGreen?.rotation = animator.animatedValue as Float
}
duration = 2000
start()
}
}
@Composable
fun FrameLayoutExample() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.c24logo),
contentDescription = "Check24 Logo",
modifier = Modifier.size(250.dp)
)
}
}
<:FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">:
<:ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/check24logo"
android:src="@drawable/c24logo" />:
<:/FrameLayout>:
@Composable
fun SpinnerExample() {
val itemList = listOf(
"Berlin",
"Hamburg",
"Stuttgart",
"München",
"Düsseldorf",
"Osnabrück"
)
var text by remember { mutableStateOf("Select a city!") }
var expanded by remember { mutableStateOf(false) }
Spinner(
text = text,
expanded = expanded,
list = itemList,
onItemClick = {
text = it
expanded = false
},
onClick = {
expanded = !expanded
}
)
}
@Composable
fun Spinner(
text: String = "Select something!",
expanded: Boolean = false,
list: List,
onItemClick: ((String) -> Unit)? = null,
onClick: () -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable(onClick = onClick)
) {
Text(text = text, )
Icon(
imageVector = Icons.Filled.ArrowDropDown,
contentDescription = "ArrowDropDown",
)
DropdownMenu(
expanded = expanded,
onDismissRequest = onClick
) {
list.forEach { item ->
DropdownMenuItem(
onClick = {
onItemClick?.invoke(item)
}
) {
Text(text = item)
}
}
}
}
}
<Spinner
android:id="@+id/spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- Spinner Data -->
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="spinner_array">
<item>Berlin</item>
<item>Hamburg</item>
<item>Stuttgart</item>
<item>München</item>
<item>Düsseldorf</item>
<item>Osnabrück</item>
</string-array>
</resources>
<!-- implementation in the activity-->
val spinner = binding.spinner
ArrayAdapter.createFromResource(
this,
R.array.spinner_array,
R.layout.support_simple_spinner_dropdown_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item)
spinner.adapter = adapter
}
@Composable
fun HorizontalScrollBox() {
val scrollState = rememberScrollState()
LazyRow(
Modifier
.fillMaxSize()
.horizontalScroll(scrollState)
) {
[...]
}
}
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
[...]
</HorizontalScrollView>
@Composable
fun VerticalScrollBox() {
val scrollState = rememberScrollState()
LazyColumn(
Modifier
.fillMaxSize()
.verticalScroll(scrollState)
) {
[...]
}
}
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
[...]
</ScrollView>
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ViewPagerExample() {
HorizontalPager(count = 5) { page ->
Card(
modifier = Modifier
.fillMaxSize()
.padding(20.dp),
elevation = 10.dp,
backgroundColor = Color.LightGray
) {
Box(
contentAlignment = Alignment.Center
) {
Text(text = "This is page ${page + 1}")
}
}
}
}
<-- View Pager -->
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<-- Fragment(s) in View Pager -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="25dp"
android:elevation="10dp"
app:cardBackgroundColor="#FFCCCCCC">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/view_pager_fragment_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/example_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textColor="@color/black" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<-- View Pager Adapter -->
class ViewPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int = 5
override fun createFragment(position: Int): Fragment = ViewPagerFragment(position)
}
<-- View Pager Fragment Implementation -->
class ViewPagerFragment(val position: Int) : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = ViewPagerFragmentBinding.inflate(inflater, container, false)
binding.viewPagerFragmentText.text = "This is page ${position + 1}"
return binding.root
}
}
@Composable
fun CardExample() {
Card(
elevation = 10.dp,
shape = CircleShape
) {
Text(
text = "Card with elevation and circleShape",
modifier = Modifier.padding(15.dp)
)
}
Card(
shape = RoundedCornerShape(5.dp)
) {
Text(
text = "Card with rounded corners and no elevation",
modifier = Modifier.padding(15.dp)
)
}
Card(
elevation = 10.dp,
backgroundColor = Color.LightGray
) {
Text(
text = "Card with color and elevation",
modifier = Modifier.padding(15.dp)
)
}
Card(
elevation = 10.dp,
border = BorderStroke(1.dp, Color.Green)
) {
Text(
text = "Card with a border",
modifier = Modifier.padding(15.dp)
)
}
Card(
shape = RoundedCornerShape(
topEnd = 15.dp,
topStart = 0.dp,
bottomEnd = 0.dp,
bottomStart = 15.dp
)
) {
Text(
text = "Card with two rounded corners",
modifier = Modifier.padding(15.dp)
)
}
val shapes = Shapes(
large = CutCornerShape(
topStart = 16.dp,
topEnd = 0.dp,
bottomEnd = 16.dp,
bottomStart = 0.dp
)
)
MaterialTheme(shapes = shapes){
Card(
elevation = 10.dp,
shape = MaterialTheme.shapes.large
) {
Text(
text = "Card with cut corners",
modifier = Modifier.padding(15.dp)
)
}
}
}
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:cardCornerRadius="50dp"
app:cardElevation="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card with elevation and rounded corners" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:cardCornerRadius="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card with no elevation and rounded corners" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:cardBackgroundColor="@color/gray"
app:cardCornerRadius="10dp"
app:cardElevation="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card with color and elevation" />
</androidx.cardview.widget.CardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:strokeColor="@color/green"
app:strokeWidth="2dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card with a border" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:shapeAppearance="@style/ShapeAppearance.MyApp.MediumComponent.Rounded">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card with two rounded corners" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
app:shapeAppearance="@style/ShapeAppearance.MyApp.MediumComponent.Cut">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Card with two cut corners" />
</com.google.android.material.card.MaterialCardView>
<!-- the styles-->
<style name="ShapeAppearance.MyApp.MediumComponent.Cut" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamilyTopLeft">cut</item>
<item name="cornerFamilyBottomRight">cut</item>
<item name="cornerSizeTopLeft">20dp</item>
<item name="cornerSizeBottomRight">20dp</item>
</style>
<style name="ShapeAppearance.MyApp.MediumComponent.Rounded" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamilyTopRight">rounded</item>
<item name="cornerFamilyBottomLeft">rounded</item>
<item name="cornerSizeTopLeft">15dp</item>
<item name="cornerSizeBottomRight">15dp</item>
</style>
@Composable
fun AppBarExample() {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
text = "App Bar Layout",
color = Color.White
)
},
navigationIcon = {
IconButton(onClick = { /* your code */ }) {
Icon(
imageVector = Icons.Rounded.Menu,
contentDescription = "Menu"
)
}
}
)
},
content = { [...] }
)
}
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:title="@string/app_bar_layout"
app:navigationIcon="@drawable/ic_baseline_menu_24"
app:titleTextColor="@color/white" />
</com.google.android.material.appbar.AppBarLayout>
@Composable
fun BottomAppBarExample() {
val screens = listOf(
BottomBarScreen.Favorite,
BottomBarScreen.Download
)
var currentScreen by remember { mutableStateOf(BottomBarScreen.Favorite) }
Scaffold(
topBar = {
TopAppBar(
title = {
Text(text = "BottomAppBar")
}
)
},
bottomBar = {
BottomAppBarDetails(screens, currentScreen) {
screen ->
currentScreen = screen
}
},
floatingActionButtonPosition = FabPosition.Center,
isFloatingActionButtonDocked = true,
floatingActionButton = {
FloatingActionButtonDetails()
},
) {
when (currentScreen) {
BottomBarScreen.Favorite -> FavoriteScreen()
BottomBarScreen.Download -> DownloadScreen()
}
}
}
@Composable
private fun BottomAppBarDetails(
screens: List,
currentScreen: BottomBarScreen,
onClick : (BottomBarScreen) -> Unit
) {
BottomAppBar(cutoutShape = CircleShape) {
screens.forEach { screen ->
BottomNavigationItem(
label = { Text(text = screen.title) },
icon = {
Icon(
imageVector = screen.icon,
contentDescription = "Navigation Icon"
)
},
selected = screen == currentScreen,
onClick = {
onClick(screen)
},
alwaysShowLabel = false
)
}
}
}
@Composable
private fun FloatingActionButtonDetails() {
FloatingActionButton(
shape = CircleShape,
onClick = { /* your code */ }
) {
Icon(
Icons.Filled.Add,
"Floating Action Button"
)
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".features.bar.AndroidUIBottomAppBarActivity">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floating_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="BottomAppBarWithFloatingActionButton"
android:src="@drawable/ic_baseline_add_24"
app:fabSize="auto"
app:layout_anchor="@id/bottom_app_bar" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/bottom_app_bar"
style="@style/DemoMaterial"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:backgroundTint="@color/purple_500"
app:fabAlignmentMode="center">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:background="@drawable/transparent_background"
app:itemIconTint="@color/white"
app:itemTextColor="@color/white"
app:labelVisibilityMode="selected"
app:menu="@menu/bottom_app_bar_menu" />
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- and the menu items -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/favorite"
android:icon="@drawable/ic_baseline_favorite_24"
android:title="Favorite" />
<item
android:enabled="false" />
<item
android:id="@+id/download"
android:icon="@drawable/ic_baseline_cloud_download_24"
android:title="Download" />
</menu>
@Composable
fun ToolbarExample() {
Scaffold(
topBar = {
TopAppBarDetails()
}
) { }
}
@Composable
private fun TopAppBarDetails() {
val context = LocalContext.current
var expanded by remember { mutableStateOf(false) }
var clickedItemText by remember { mutableStateOf("") }
TopAppBar(
title = { Text(text = "Toolbar") },
navigationIcon = {
IconButton(onClick = { context.findActivity()?.onBackPressed() }) {
Icon(Icons.Default.ArrowBack, "Back Button")
}
},
actions = {
IconButton(onClick = { }) {
Icon(Icons.Default.Search, "Search")
}
IconButton(onClick = { expanded = !expanded }) {
Icon(Icons.Default.MoreVert, "Options")
}
OptionDropdownMenu(expanded, onDismissMenu = { onDismissMenu: Boolean ->
expanded = onDismissMenu
}) {
}
}
)
}
@Composable
private fun OptionDropdownMenu(
expanded: Boolean,
list: List = listOf("First Option", "Second Option", "Third Option"),
onDismissMenu: ((Boolean) -> Unit)? = null,
onClickItem: ((String) -> Unit)? = null
) {
DropdownMenu(
expanded = expanded,
onDismissRequest = { onDismissMenu?.invoke(false) },
) {
list.forEach { item ->
DropdownMenuItem(
onClick = {
onClickItem?.invoke(item)
onDismissMenu?.invoke(false)
}
) {
Text(text = item)
}
}
}
}
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/top_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolbarThemeExample"
app:menu="@menu/tool_bar_menu"
app:navigationIcon="@drawable/ic_baseline_arrow_back_24"
app:title="Toolbar"
app:titleTextColor="@color/white">
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<!-- menu data -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/toolbar_search" android:icon="@drawable/ic_baseline_search_24"
android:iconTint="@color/white" android:title="Search" app:showAsAction="ifRoom" />
<item
android:id="@+id/first_option"
android:title="First Option" />
<item android:id="@+id/second_options"
android:title="Second Option" />
<item android:id="@+id/third_options"
android:title="Third Option" />
</menu>
<!-- code -->
binding?.toolbar?.setNavigationOnClickListener {
onBackPressed()
}
@Composable
fun TabLayoutExample() {
var selectedTabIndex by remember { mutableStateOf(0) }
val tabTexts = listOf("A", "B", "C")
TabRow(
selectedTabIndex = selectedTabIndex,
backgroundColor = Color.White
) {
tabTexts.forEachIndexed { index, text ->
Tab(
selected = index == selectedTabIndex,
modifier = Modifier.size(50.dp),
onClick = {
selectedTabIndex = index
}
) {
Text(text = text)
}
}
}
}
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="0dp"
android:layout_height="wrap_content" />
// in activity
var binding: TabLayoutBinding? = TabLayoutBinding.inflate(layoutInflater)
binding?.tabLayout?.apply {
addTab(newTab().setText("A"))
addTab(newTab().setText("B"))
addTab(newTab().setText("C"))
}
@Composable
fun IconTabs(tabData: List) {
var tabIndex by remember { mutableStateOf(0) }
TabRow(
selectedTabIndex = tabIndex,
backgroundColor = Color.White
) {
tabData.forEachIndexed { index, icon ->
Tab(selected = tabIndex == index, onClick = {
tabIndex = index
}, icon = {
Icon(imageVector = icon, contentDescription = null)
})
}
}
}
@Composable
fun TextTabs(tabData: List) {
var tabIndex by remember { mutableStateOf(0) }
TabRow(
selectedTabIndex = tabIndex,
backgroundColor = Color.White
) {
tabData.forEachIndexed { index, text ->
Tab(selected = tabIndex == index, onClick = {
tabIndex = index
}, text = {
Text(text = text)
})
}
}
}
@Composable
fun IconAndTextTabs(tabData: List>) {
var tabIndex by remember { mutableStateOf(0) }
TabRow(
selectedTabIndex = tabIndex,
backgroundColor = Color.White,
) {
tabData.forEachIndexed { index, pair ->
Tab(selected = tabIndex == index, onClick = {
tabIndex = index
}, text = {
Text(text = pair.first)
}, icon = {
Icon(imageVector = pair.second, contentDescription = null)
})
}
}
}
<!-- XML Code -->
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_item_icons"
android:layout_width="0dp"
android:layout_height="wrap_content" />
<!-- Code -->
binding.tabItemText.apply {
addTab(newTab().setText("MUSIC"))
addTab(newTab().setText("MARKET"))
addTab(newTab().setText("FILMS"))
addTab(newTab().setText("BOOKS"))
}
binding.tabItemIcons.apply {
addTab(newTab().setIcon(R.drawable.ic_baseline_save_24))
addTab(newTab().setIcon(R.drawable.ic_baseline_date_range))
addTab(newTab().setIcon(R.drawable.ic_baseline_favorite_24))
addTab(newTab().setIcon(R.drawable.ic_baseline_cloud_download_24))
}
binding.tabItemTextAndIcons.apply {
addTab(newTab().setText("MUSIC").setIcon(R.drawable.ic_baseline_save_24))
addTab(newTab().setText("MARKET").setIcon(R.drawable.ic_baseline_date_range))
addTab(newTab().setText("FILMS").setIcon(R.drawable.ic_baseline_favorite_24))
addTab(newTab().setText("BOOKS").setIcon(R.drawable.ic_baseline_cloud_download_24))
}
@Composable
fun NavHostDemo() {
val navController = rememberNavController()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
val abc = listOf("A", "B", "C")
NavHost(navController = navController, startDestination = "A") {
abc.forEach { letter ->
composable(letter) {
Text(text = letter)
}
}
}
Row {
abc.forEach { letter ->
Button(onClick = { navController.navigate(letter) }) {
Text(text = letter)
}
}
}
}
}
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/navigation_button_row"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:navGraph="@navigation/nav_host_navigation" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/navigation_button_row"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<Button
android:id="@+id/button_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/a"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_b"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/b"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_c"
app:layout_constraintStart_toEndOf="@+id/button_a"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/c"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/button_b"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// navigation Graph
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_host_navigation"
app:startDestination="@id/fragment_a">
<fragment
android:id="@+id/fragment_a"
android:name="de.check24.compose.demo.features.navigation.fragments.FragmentA"
tools:layout="@layout/navigation_plain_text" />
<fragment
android:id="@+id/fragment_b"
android:name="de.check24.compose.demo.features.navigation.fragments.FragmentB"
tools:layout="@layout/navigation_plain_text" />
<fragment
android:id="@+id/fragment_c"
android:name="de.check24.compose.demo.features.navigation.fragments.FragmentC"
tools:layout="@layout/navigation_plain_text" />
</navigation>
// in activity
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
val buttonA = findViewById<Button>(R.id.button_a)
val buttonB = findViewById<Button>(R.id.button_b)
val buttonC = findViewById<Button>(R.id.button_c)
buttonA.setOnClickListener {
navController.navigate(R.id.fragment_a)
}
buttonB.setOnClickListener {
navController.navigate(R.id.fragment_b)
}
buttonC.setOnClickListener {
navController.navigate(R.id.fragment_c)
}
@Composable
private fun GoogleMapExample() {
val c24Berlin = LatLng(52.51119997328961, 13.404559796099809)
val zoom = 17F
val cameraPositionState = rememberCameraPositionState {
position = CameraPosition(c24Berlin, zoom, 0F, 0F)
}
GoogleMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState
)
}
<fragment xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".features.custom.AndroidUIGoogleMapActivity" />
// In activity
val mapFragment: SupportMapFragment? = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync {
val c24Berlin = LatLng(52.51119997328961, 13.404559796099809)
val zoom = 17F
it.moveCamera(CameraUpdateFactory.newLatLngZoom(c24Berlin, zoom))
}
@Composable
fun MarginDemo() {
Card(backgroundColor = Color.Blue) {
Text(
text = "padding (red) and margin (blue)",
modifier = Modifier
.padding(
horizontal = 50.dp,
vertical = 20.dp
) // Margin
.background(Color.Red)
.padding(20.dp) // Padding
)
}
}
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/blue"
app:cardCornerRadius="5dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="50dp"
android:layout_marginVertical="20dp"
android:background="@color/red"
android:padding="20dp"
android:text="@string/modifier1text"
android:textAlignment="center"
app:layout_constraintTop_toTopOf="parent" />
</androidx.cardview.widget.CardView>
@Composable
fun SizeDemo() {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxWidth()
) {
Card(
backgroundColor = Color.LightGray,
modifier = Modifier.size(70.dp),
) {
Text(
text = "size: big"
)
}
Spacer(modifier = Modifier.size(20.dp))
Card(
backgroundColor = Color.LightGray,
modifier = Modifier.size(50.dp)
) {
Text(
text = "size: small"
)
}
}
}
<androidx.cardview.widget.CardView
android:layout_width="70dp"
android:layout_height="70dp"
app:cardBackgroundColor="@color/light_gray"
app:cardCornerRadius="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/modifier2text1" />
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="50dp"
android:layout_height="50dp"
app:cardBackgroundColor="@color/light_gray"
app:cardCornerRadius="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/modifier2text2" />
</androidx.cardview.widget.CardView>
@Composable
fun ClickableDemo() {
var count by remember { mutableStateOf(0) }
Text(
text = "Clicked ${count}x",
modifier = Modifier
.clickable {
count++
}
)
}
<TextView
android:id="@+id/modifier_clickable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:onClick="onTextClick"
android:text="@string/sample_text_clickable" />
// in activity
fun onTextClick(view: View) {
if (view.id != R.id.modifier_clickable) return
count++
(view as TextView).text = "Clicked ${count}x"
}
@Composable
fun ShadowDemo() {
Card(
modifier = Modifier
.shadow(elevation = 20.dp)
) {
Text(
text = "shadow",
modifier = Modifier.padding(10.dp)
)
}
}
<androidx.cardview.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardElevation="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/shadow" />
</androidx.cardview.widget.CardView>
@Composable
fun BackgroundDemo() {
Text(
text = "red background",
modifier = Modifier.background(Color.Red)
)
}
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/red"
android:text="@string/modifier_background" />
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val (purple, orange, blue, green, leftView, middleView, rightView) = createRefs()
val chainRefOne = createHorizontalChain(green, blue, chainStyle = ChainStyle.Spread)
val chainRefTwo =
createHorizontalChain(leftView, middleView, rightView, chainStyle = ChainStyle.Spread)
Text(
textAlign = TextAlign.Center,
text = "constraint to start, top and end of parent with a horizontal bias of 30/70",
modifier = Modifier
.padding(top = 80.dp)
.width(150.dp)
.background(Purple200)
.padding(10.dp)
.constrainAs(purple) {
linkTo(
parent.start,
parent.end,
bias = 0.3F
)
}
)
Text(
textAlign = TextAlign.Center,
text = "constraint via circle radius(170dp) and angle(135) to purple",
modifier = Modifier
.width(180.dp)
.background(Orange200)
.padding(10.dp)
.constrainAs(orange) {
circular(purple, angle = 135F, distance = 170.dp)
}
)
Text(
textAlign = TextAlign.Center,
text = "Chain A, both are bi-directional connected",
modifier = Modifier
.width(120.dp)
.background(Green200)
.padding(10.dp)
.constrainAs(green) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}
)
Text(
text = "Chain B, and chained with chainStyle \"Spread\"",
modifier = Modifier
.width(120.dp)
.background(Blue200)
.padding(10.dp)
.constrainAs(blue) {
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
},
textAlign = TextAlign.Center
)
Text(
text = "chained with 30 percent",
modifier = Modifier
.background(Red200)
.height(60.dp)
.padding(10.dp)
.constrainAs(leftView) {
bottom.linkTo(parent.bottom)
width = Dimension.percent(0.3F)
},
textAlign = TextAlign.Center
)
Text(
text = "chained with 40 percent",
modifier = Modifier
.background(Color.Gray)
.height(60.dp)
.padding(10.dp)
.constrainAs(middleView) {
bottom.linkTo(parent.bottom)
width = Dimension.percent(0.4F)
},
textAlign = TextAlign.Center
)
Text(
textAlign = TextAlign.Center,
text = "chained with 30 percent",
modifier = Modifier
.background(Color.Cyan)
.height(60.dp)
.padding(10.dp)
.constrainAs(rightView) {
bottom.linkTo(parent.bottom)
width = Dimension.percent(0.3F)
}
)
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/purple"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:background="@color/purple_200"
android:gravity="center"
android:padding="10dp"
android:text="constraint to start, top and end of parent with a horizontal bias of 30/70"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.1"/>
<TextView
android:id="@+id/orange"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:background="@color/orange_200"
android:gravity="center"
android:padding="10dp"
android:text="constraint via circle radius(110dp) and angle(135) to purple"
app:layout_constraintCircle="@id/purple"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="110dp" />
<TextView
android:id="@+id/blue"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:background="@color/blue_200"
android:gravity="center"
android:padding="10dp"
android:text="Chain B, and chained with chainStyle "spread" together "
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toEndOf="@+id/green"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/green"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:background="@color/green_200"
android:gravity="center"
android:padding="10dp"
android:text="Chain A, both are bi-directional connected"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/blue"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/left_textview"
android:layout_width="0dp"
android:layout_height="60dp"
android:background="@color/red_200"
android:gravity="center"
android:padding="10dp"
android:text="chained with 30 percent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/middle_textview"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/middle_textview"
android:layout_width="0dp"
android:layout_height="60dp"
android:background="@color/gray"
android:gravity="center"
android:padding="10dp"
android:text="chained with 40 percent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/right_textview"
app:layout_constraintHorizontal_weight="0.4"
app:layout_constraintStart_toEndOf="@+id/left_textview" />
<TextView
android:id="@+id/right_textview"
android:layout_width="0dp"
android:layout_height="60dp"
android:background="@color/teal_200"
android:gravity="center"
android:padding="10dp"
android:text="chained with 30 percent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="0.3"
app:layout_constraintStart_toEndOf="@+id/middle_textview" />
</androidx.constraintlayout.widget.ConstraintLayout>
// Vertical FlowLayout
FlowColumn(
modifier = Modifier
.height(300.dp)
.wrapContentWidth(),
mainAxisAlignment = FlowMainAxisAlignment.Center,
mainAxisSpacing = 16.dp,
crossAxisSpacing = 50.dp,
crossAxisAlignment = FlowCrossAxisAlignment.Center,
) {
// add here your view elements
}
// Horizontal FlowLayout
FlowRow(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(20.dp),
mainAxisAlignment = FlowMainAxisAlignment.Center,
crossAxisSpacing = 16.dp,
mainAxisSpacing = 16.dp
) {
// add here your code
}
<!-- Vertical FlowLayout -->
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow_vertical_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="vertical"
app:constraint_referenced_ids="id's of your elements"
app:flow_maxElementsWrap="5"
app:flow_verticalGap="16dp"
app:flow_wrapMode="aligned" />
<!-- Horizontal FlowLayout -->
<androidx.constraintlayout.helper.widget.Flow
android:id="@+id/flow_horizontal_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:orientation="horizontal"
app:constraint_referenced_ids="id's of your elements"
app:flow_horizontalGap="16dp"
app:flow_maxElementsWrap="5"
app:flow_verticalGap="16dp"
app:flow_wrapMode="chain"
app:layout_constraintHorizontal_chainStyle="spread" />
Column(
modifier = Modifier
.fillMaxSize()
.padding(50.dp)
) {
Text(
text = "TextView One",
textAlign = TextAlign.Center,
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.padding(vertical = 20.dp)
.background(Orange200)
.padding(10.dp)
)
[...]
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="50dp"
android:paddingVertical="50dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="20dp"
android:background="@color/orange_200"
android:padding="10dp"
android:text="TextView One"
android:textAlignment="center" />
[...]
</LinearLayout>
Row(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 50.dp, horizontal = 20.dp)
) {
Box(
modifier = Modifier
.padding(horizontal = 20.dp)
.width(50.dp)
.fillMaxHeight()
.background(Orange200)
.padding(10.dp)
)
[...]
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:paddingHorizontal="20dp"
android:paddingVertical="50dp">
<View
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="20dp"
android:background="@color/orange_200"
android:padding="10dp"
android:textAlignment="center" />
[...]
</LinearLayout>
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GridExample() {
val colorList = listOf(
Color.Red,
Color.Black,
Color(0xFFBB86FC),
Color(0xFF3700B3)
)
LazyVerticalGrid(
cells = GridCells.Fixed(2),
modifier = Modifier.size(200.dp)
) {
(0..3).forEach {
item {
Box(
modifier = Modifier
.size(100.dp)
.background(colorList[it])
)
}
}
}
}
<GridLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="2"
android:rowCount="2">
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/red" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/black" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/purple_200" />
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/purple_700" />
</GridLayout>
@Composable
fun FrameLayoutExample() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(R.drawable.c24logo),
contentDescription = "Check24 Logo",
modifier = Modifier.size(250.dp)
)
}
}
<:FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">:
<:ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:contentDescription="@string/check24logo"
android:src="@drawable/c24logo" />:
<:/FrameLayout>:
class MyViewModel : ViewModel() {
var isClicked = MutableStateFlow(false)
var text = MutableLiveData()
fun onToggle() {
isClicked.value = !isClicked.value
}
fun onValueChanged(input: String) {
text.value = input
}
}
@Composable
private fun ObservableTypeExample(model: MyViewModel) {
val text: String by model.text.observeAsState("")
val isClicked: Boolean by model.isClicked.collectAsState()
MyScreen(
text = text,
isClicked = isClicked,
onValueChanged = { model.onValueChanged(it) },
onToggle = { model.onToggle() }
)
}
@Composable
fun MyScreen(
text: String,
isClicked: Boolean,
onValueChanged: (String) -> Unit,
onToggle: () -> Unit
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
TextField(
value = text,
onValueChange = { onValueChanged(it) },
)
Switch(
checked = isClicked,
onCheckedChange = { onToggle() }
)
Box(
modifier = Modifier
.size(100.dp)
.background(
if (isClicked) Green200 else Blue200
)
)
}
}
<!-- XML Code -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_layout"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Switch
android:id="@+id/observable_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:id="@+id/view_box"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/blue_200" />
<!-- Code -->
private var binding: ObservableTypesBinding? = null
private var viewModel = ObservableViewModel()
viewModel.isClicked.observe(this) {
setBackgroundColor(binding?.viewBox, viewModel.isClicked)
}
binding?.observableSwitch?.setOnClickListener {
viewModel.onClick()
}
private fun setBackgroundColor(viewBox: View?, clicked: MutableLiveData<Boolean>) {
if (clicked.value == true) {
viewBox?.setBackgroundResource(R.color.green_200)
} else {
viewBox?.setBackgroundResource(R.color.blue_200)
}
}
class ObservableViewModel: ViewModel() {
val isClicked: MutableLiveData<Boolean> = MutableLiveData(false)
fun onClick() {
isClicked.value = !(isClicked.value ?: false)
}
}
// stateful composables tend to be less reusable and harder to test
@Composable
private fun StatefulExample() {
// stateful means that the composable manages its own state
// no states are handled in the parent object.
MySwitch()
}
@Composable
private fun MySwitch() {
// the state
var isClicked by remember { mutableStateOf(false) }
Switch(
checked = isClicked,
// the child manages its own state
onCheckedChange = { isClicked = !isClicked }
)
Box(
modifier = Modifier
.size(150.dp)
.background(
if (isClicked) Green200 else Blue200
)
)
}
<!-- XML Code -->
<Switch
android:id="@+id/switch_example"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<View
android:id="@+id/box"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@color/blue_200"/>
<!-- Code-->
private var isClicked = false
binding?.switchExample?.setOnClickListener {
if (isClicked) {
binding?.box?.setBackgroundColor(resources.getColor(R.color.blue_200))
} else {
binding?.box?.setBackgroundColor(resources.getColor(R.color.green_200))
}
isClicked = !isClicked
}
@Composable
fun StatelessExample() {
// the parent manages the state of the child
var isClicked by remember { mutableStateOf(false) }
MySwitch(isClicked = isClicked) {
// the parent, in this case th StatelessExample manages the state of MySwitch through an event of the child
isClicked = !isClicked
}
}
@Composable
fun MySwitch(isClicked: Boolean, onToggle: () -> Unit) {
// the child gives back an event to parent via lambda expression,
// so it can handle the state properly
Switch(
checked = isClicked,
onCheckedChange = { onToggle() }
)
Box(
modifier = Modifier
.size(150.dp)
.background(
if (isClicked) Green200 else Blue200
)
)
}
<!-- XML Code -->
<Switch
android:id="@+id/switch_example"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<View
android:id="@+id/box"
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@color/blue_200"/>
<!-- Code-->
private var isClicked = false
binding?.switchExample?.setOnClickListener {
if (isClicked) {
binding?.box?.setBackgroundColor(resources.getColor(R.color.blue_200))
} else {
binding?.box?.setBackgroundColor(resources.getColor(R.color.green_200))
}
isClicked = !isClicked
}
class MyStateHolder() {
// states
var text by mutableStateOf("")
var isClicked by mutableStateOf(false)
// events
fun onTextChanged(input: String) {
text = input
}
fun onToggle(clicked: Boolean) {
isClicked = clicked
}
}
// Through this implementation, the lifecycle corresponds to the composable that implemented it
@Composable
fun rememberStateHolder(): MyStateHolder =
remember { MyStateHolder() }
@Composable
fun MyScreen(state: MyStateHolder = rememberStateHolder()) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
TextField(
value = state.text,
onValueChange = { state.onTextChanged(it) },
)
Switch(
checked = state.isClicked,
onCheckedChange = { state.onToggle(it) }
)
Box(
modifier = Modifier
.size(100.dp)
.background(
if (state.isClicked) Green200 else Blue200
)
)
}
}
<!-- XML Code -->
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_layout"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<Switch
android:id="@+id/state_holder_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<View
android:id="@+id/view_box"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:background="@color/blue_200" />
<!-- Code -->
binding?.stateHolderSwitch?.setOnClickListener {
setBackgroundColor(
binding?.viewBox,
requireNotNull(binding?.stateHolderSwitch?.isChecked)
)
}
private fun setBackgroundColor(viewBox: View?, clicked: Boolean) {
if (clicked) {
viewBox?.setBackgroundResource(R.color.green_200)
} else {
viewBox?.setBackgroundResource(R.color.blue_200)
}
}
// the ViewModel manages the states and the events
// it also has access to the business logic and has a longer lifecycle than the composable
class MyViewModel : MyViewModelInterface {
// states
override var number = mutableStateOf(0)
override var isClicked = mutableStateOf(false)
// events
fun addNumber() {
number.value = number.value + 1
}
fun toggle(clicked: Boolean) {
isClicked.value = clicked
}
}
interface MyViewModelInterface {
val number: MutableState
val isClicked : MutableState
fun addNumber()
fun toggle(clicked: Boolean)
}
@Composable
fun MyScreen(myViewModel: MyViewModelInterface) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = myViewModel.number.value.toString(),
modifier = Modifier.clickable { myViewModel.addNumber() }
)
Switch(
checked = myViewModel.isClicked.value,
onCheckedChange = { myViewModel.toggle(it) }
)
Box(
modifier = Modifier
.size(100.dp)
.background(
if (myViewModel.isClicked.value) Green200 else Blue200
)
)
}
}
<!-- XML Code -->
<TextView
android:id="@+id/textView"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@color/blue_200"
android:foreground="?attr/selectableItemBackground"
android:gravity="center"
android:clickable="true"
android:focusable="true" />
<Switch
android:id="@+id/viewModel_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/box_view"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/blue_200" />
<!-- onCreate -->
binding?.apply {
setBackgroundColor(viewModelUI.isClicked, boxView)
textView.text = viewModelUI.number.toString()
viewModelSwitch.setOnClickListener {
viewModelUI.onToggle()
setBackgroundColor(viewModelUI.isClicked, boxView)
}
textView.setOnClickListener {
viewModelUI.onClick()
textView.text = viewModelUI.number.toString()
}
}
<!-- Code -->
private fun setBackgroundColor(clicked: Boolean, boxView: View) {
if (clicked) {
boxView.setBackgroundResource(R.color.green_200)
} else {
boxView.setBackgroundResource(R.color.blue_200)
}
}
class MyAndroidUIViewModel : ViewModel() {
var isClicked: Boolean = false
var number: Int = 0
fun onToggle() {
isClicked = !isClicked
}
fun onClick() {
number++
}
}
// brief explanation, states are given by the parents to the children.
// The children, on the other hand, only give events to the parents.
// Thus we have a unidirectional flow.
@Composable
fun EventsExample() {
// this is the parent class
// it contains the child and gives down the states
// ideally only the parent changes the states
var number by remember { mutableStateOf(0) }
Column(
Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
// here it gives the states to the child
NumberText(number = number.toString())
// between the curly brackets, the parent can decide what should happen when an event occurs.
AddButton {
number++
}
SubtractButton {
number--
}
}
}
@Composable
fun NumberText(number: String) {
// the child can't change the state, in this case number.
Text(
// the number displayed depends on the state
text = number,
fontSize = 24.sp,
color = White
)
}
@Composable
fun AddButton(onClick: () -> Unit) {
// if the child is clicked, an event is sent to the parents.
Button(
onClick = onClick,
modifier = Modifier.padding(10.dp)
) {
Text(text = "ADD +1")
}
}
@Composable
fun SubtractButton(onClick: () -> Unit) {
Button(
onClick = onClick
) {
Text(text = "ADD -1")
}
}
<!--
brief explanation, the viewModel holds the data and modifies it.
It gives the states/data to the fragment.
The fragment modifies the UI.
The UI sends events to the ViewModel for example, that it got clicked.
The event flow starts all over again
-->
<!-- Fragment -->
class Activity: AppCompatActivity() {
lateinit var binding: EventLayoutBinding
private val viewModel: EventViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = EventLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title = "Events"
binding.apply {
addButton.setOnClickListener {
viewModel.plusOne()
textView.text = viewModel.number.toString()
}
subtractButton.setOnClickListener {
viewModel.minusOne()
textView.text = viewModel.number.toString()
}
}
}
}
<!-- ViewModel -->
class EventViewModel : ViewModel() {
var number = 0
fun plusOne() {
number++
}
fun minusOne(){
number--
}
}
<!-- XML Code -->
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/green_200"
android:textColor="@color/white"
android:textSize="24sp"
android:gravity="center"/>
<Button
android:id="@+id/add_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Add +1" />
<Button
android:id="@+id/subtract_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Add -1" />
// colour
val exampleColors: Colors
get() = lightColors(
primary = Color(0xFF862A8A),
primaryVariant = Color(0xFFC280D5),
onPrimary = Color(0xFFFFFFFF),
secondary = Color(0xFFFF30FC),
secondaryVariant = Color(0xFAEAF7FB),
onSecondary = Color(0xFFFFFFFF),
error = Color(0xFFC82D70),
onError = Color(0xFFFFFFFF),
surface = Color(0xFFFFFFFF),
onSurface = Color(0xFF333333),
background = Color(0xFCEFEFF3),
onBackground = Color(0xFFFFFFFF)
)
// font style
val exampleTypography: Typography
get() = Typography(
h1 = TextStyle(
fontSize = 24.sp,
lineHeight = 28.sp,
color = Grey3,
letterSpacing = 0.sp
),
h2 = TextStyle(
fontSize = 20.sp,
lineHeight = 24.sp,
color = Grey6,
letterSpacing = 0.sp
),
h3 = TextStyle(
fontSize = 18.sp,
lineHeight = 22.sp,
color = Grey3,
letterSpacing = 0.sp
),
h4 = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
color = Grey6,
letterSpacing = 0.sp
),
h5 = TextStyle(
fontSize = 14.sp,
lineHeight = 18.sp,
color = Grey9,
letterSpacing = 0.sp
),
h6 = TextStyle(
fontSize = 12.sp,
lineHeight = 16.sp,
color = Grey3,
letterSpacing = 0.sp
),
subtitle1 = TextStyle(
fontSize = 18.sp,
lineHeight = 22.sp,
color = Grey3,
letterSpacing = 0.sp
),
subtitle2 = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
color = Grey6,
letterSpacing = 0.sp
),
body1 = TextStyle(
fontSize = 16.sp,
lineHeight = 20.sp,
color = Grey6,
letterSpacing = 0.sp
),
body2 = TextStyle(
fontSize = 14.sp,
lineHeight = 18.sp,
color = Grey9,
letterSpacing = 0.sp
),
button = TextStyle(
fontSize = 16.sp,
lineHeight = 18.sp,
color = Color.White,
letterSpacing = 0.sp
),
caption = TextStyle(
fontSize = 12.sp,
lineHeight = 16.sp,
color = Grey3,
letterSpacing = 0.sp
),
overline = TextStyle(
fontSize = 12.sp,
lineHeight = 16.sp,
color = Grey6,
letterSpacing = 0.sp
)
)
// shapes
val exampleShapes: Shapes
get() = Shapes(
small = RoundedCornerShape(0.dp),
medium = RoundedCornerShape(8.dp),
large = RoundedCornerShape(16.dp)
)
@Composable
fun ExampleTheme(content: @Composable () -> Unit) {
MaterialTheme(
colors = exampleColors,
typography = exampleTypography,
shapes = exampleShapes,
content = content
)
}
// usable like this
@Composable
fun ExampleThemeDemo() {
ExampleTheme {
[...]
}
}
// also addable to manifest
<activity
android:name=".features.theme.ComposableThemeActivity"
android:exported="false"
android:theme="@style/ExampleTheme" />
<!-- allocation of colour, style and shape -->
<style name="ExampleTheme" parent="Theme.AppCompat">
<item name="colorPrimary">#862A8A</item>
<item name="colorPrimaryVariant">#C280D5</item>
<item name="colorOnPrimary">@color/white</item>
<item name="colorSecondary">#FF30FC</item>
<item name="colorSecondaryVariant">#EAF7FB</item>
<item name="colorOnSecondary">@color/white</item>
<item name="colorError">#C82D70</item>
<item name="colorOnError">@color/white</item>
<item name="colorSurface">@color/white</item>
<item name="colorOnSurface">#333333</item>
<item name="android:colorBackground">#EFEFF3</item>
<item name="android:windowBackground">@color/white</item>
<item name="textAppearanceHeadline1">@style/h1</item>
<item name="textAppearanceHeadline2">@style/h2</item>
<item name="textAppearanceHeadline3">@style/h3</item>
<item name="textAppearanceHeadline4">@style/h4</item>
<item name="textAppearanceHeadline5">@style/h5</item>
<item name="textAppearanceHeadline6">@style/h6</item>
<item name="textAppearanceSubtitle1">@style/s1</item>
<item name="textAppearanceSubtitle2">@style/s2</item>
<item name="textAppearanceBody1">@style/b1</item>
<item name="textAppearanceBody2">@style/b2</item>
<item name="textAppearanceButton">@style/button</item>
<item name="textAppearanceCaption">@style/caption</item>
<item name="textAppearanceOverline">@style/overline</item>
<item name="shapeAppearanceSmallComponent">@style/small</item>
<item name="shapeAppearanceMediumComponent">@style/medium</item>
<item name="shapeAppearanceLargeComponent">@style/large</item>
</style>
<!-- font style -->
<resources>
<style name="h1" parent="TextAppearance.MaterialComponents.Headline1">
<item name="android:textSize">24sp</item>
<item name="lineHeight">28sp</item>
<item name="android:textColor">@color/shared_cool_grey_3</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="h2" parent="TextAppearance.MaterialComponents.Headline2">
<item name="android:textSize">20sp</item>
<item name="lineHeight">24sp</item>
<item name="android:textColor">@color/shared_cool_grey_6</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="h3" parent="TextAppearance.MaterialComponents.Headline3">
<item name="android:textSize">18sp</item>
<item name="lineHeight">22sp</item>
<item name="android:textColor">@color/shared_cool_grey_3</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="h4" parent="TextAppearance.MaterialComponents.Headline4">
<item name="android:textSize">16sp</item>
<item name="lineHeight">20sp</item>
<item name="android:textColor">@color/shared_cool_grey_6</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="h5" parent="TextAppearance.MaterialComponents.Headline5">
<item name="android:textSize">14sp</item>
<item name="lineHeight">18sp</item>
<item name="android:textColor">@color/shared_cool_grey_9</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="h6" parent="TextAppearance.MaterialComponents.Headline6">
<item name="android:textSize">12sp</item>
<item name="lineHeight">16sp</item>
<item name="android:textColor">@color/shared_cool_grey_3</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="s1" parent="TextAppearance.MaterialComponents.Subtitle1">
<item name="android:textSize">18sp</item>
<item name="lineHeight">22sp</item>
<item name="android:textColor">@color/shared_cool_grey_3</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="s2" parent="TextAppearance.MaterialComponents.Subtitle2">
<item name="android:textSize">16sp</item>
<item name="lineHeight">20sp</item>
<item name="android:textColor">@color/shared_cool_grey_6</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="b1" parent="TextAppearance.MaterialComponents.Body1">
<item name="android:textSize">16sp</item>
<item name="lineHeight">20sp</item>
<item name="android:textColor">@color/shared_cool_grey_6</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="b2" parent="TextAppearance.MaterialComponents.Body2">
<item name="android:textSize">14sp</item>
<item name="lineHeight">18sp</item>
<item name="android:textColor">@color/shared_cool_grey_9</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="button" parent="TextAppearance.MaterialComponents.Button">
<item name="android:textSize">16sp</item>
<item name="lineHeight">18sp</item>
<item name="android:textColor">@color/shared_white</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="caption" parent="TextAppearance.MaterialComponents.Caption">
<item name="android:textSize">12sp</item>
<item name="lineHeight">16sp</item>
<item name="android:textColor">@color/shared_cool_grey_3</item>
<item name="android:letterSpacing">0</item>
</style>
<style name="overline" parent="TextAppearance.MaterialComponents.Overline">
<item name="android:textSize">12sp</item>
<item name="lineHeight">16sp</item>
<item name="android:textColor">@color/shared_cool_grey_6</item>
<item name="android:letterSpacing">0</item>
</style>
<color name="shared_cool_grey_6">#666666</color>
<color name="shared_cool_grey_9">#999999</color>
<color name="shared_white">#FFFFFF</color>
<color name="shared_cool_grey_3">#333333</color>
</resources>
<!-- shape styles -->
<resources>
<style name="small" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">0%</item>
</style>
<style name="medium" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5%</item>
</style>
<style name="large" parent="ShapeAppearance.MaterialComponents.LargeComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">10%</item>
</style>
</resources>
<!-- in manifest -->
<activity
android:name=".features.theme.AndroidUIThemeActivity"
android:exported="false"
android:theme="@style/ExampleTheme" />
@Composable
fun LaunchedEffectDemo() {
var isSnackbarVisible by remember { mutableStateOf(false) }
val scaffoldState = rememberScaffoldState()
if (isSnackbarVisible) {
//LaunchedEffect Safely executes suspends funs
LaunchedEffect(scaffoldState.snackbarHostState) {
//showSnackbar is a suspend fun
scaffoldState.snackbarHostState.showSnackbar(
message = "this is a snackbar",
actionLabel = "hide"
)
isSnackbarVisible = scaffoldState.snackbarHostState.currentSnackbarData != null
}
}
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text(text = "Launched Effect") }
)
}
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(onClick = { isSnackbarVisible = !isSnackbarVisible }) {
Text(text =
if (isSnackbarVisible) "Hide Snackbar"
else "Show Snackbar"
)
}
}
}
}
@Composable
fun DisposableEffectDemo() {
var isEffectActive by remember { mutableStateOf(true) }
var count by remember { mutableStateOf(0) }
var disposableEffectKey by remember { mutableStateOf(true) }
if (isEffectActive) {
DisposableEffect(disposableEffectKey) {
//is called when DisposableEffect leaves composition or key changed
onDispose { count++ }
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Disposed $count times")
Button(onClick = { isEffectActive = !isEffectActive }) {
Text(
text = if (isEffectActive) "Deactivate"
else "Activate"
)
}
Button(onClick = { disposableEffectKey = !disposableEffectKey }) {
Text(text = "Change key")
}
}
}
/*
the SideEffect is used when you only want to run code if the composable is successfully recomposed.
After the recomposition the codeblock within the SideEffect gets executed.
*/
@Composable
fun WithoutSideEffect() {
var timer by remember {
mutableStateOf(0)
}
Box(
modifier = Modifier
.size(100.dp)
.padding(20.dp)
.background(Green200),
contentAlignment = Alignment.Center
) {
Text(
text = "$timer",
)
}
/*
through the Sleep, the timer gets changed when the composables recomposes itself.
Therefore it does not call the recomposition.
*/
Thread.sleep(1000)
timer++
}
@Composable
fun WithSideEffect() {
var timer by remember {
mutableStateOf(0)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(100.dp)
.padding(20.dp)
.background(Green200),
) {
Text(
text = "$timer",
)
}
/*
Through SideEffect timer gets called after the composable got recomposed.
So the composable gets recomposed and afterwards it increases timer.
In this case we created a loop
*/
SideEffect {
Thread.sleep(1000)
timer++
}
}
@Composable
fun TimeExample() {
var timePicked: String by remember {
mutableStateOf("Pick a Time")
}
val activity = LocalContext.current as AppCompatActivity
Text(
text = timePicked,
modifier = Modifier
.clickable {
showTimePicker(
{ time: String ->
timePicked = time
},
activity
)
}
)
}
private fun showTimePicker(
time: (String) -> Unit,
activity: AppCompatActivity
) {
val picker = MaterialTimePicker.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H)
.build()
// be careful with the timepicker, because you need to manage the dispose!
picker.show(activity.supportFragmentManager, picker.toString())
picker.addOnPositiveButtonClickListener {
time(
formatTime(
picker.hour,
picker.minute
)
)
}
}
private fun formatTime(hour: Int, minute: Int): String {
var stringHour = hour.toString()
var stringMinute = minute.toString()
if (hour < 10) stringHour = "0$stringHour"
if (minute < 10) stringMinute = "0$stringMinute"
return "Time: $stringHour:$stringMinute"
}
<TextView
android:id="@+id/time_picker_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Pick a time" />
<!-- code implementation -->
private fun showTimePicker() {
val materialTimePicker = MaterialTimePicker
.Builder()
.setTimeFormat(TimeFormat.CLOCK_24H)
.build()
materialTimePicker.addOnPositiveButtonClickListener {
binding.timePickerTextView.text =
formatTime(materialTimePicker.hour, materialTimePicker.minute)
}
materialTimePicker.show(supportFragmentManager, "TAG")
}
private fun formatTime(hour : Int, minute: Int) : String {
var stringHour = hour.toString()
var stringMinute = minute.toString()
if (hour < 10) stringHour = "0$stringHour"
if (minute < 10) stringMinute = "0$stringMinute"
return "Time: $stringHour:$stringMinute"
}
@Composable
fun Date() {
var datePicked: String by remember {
mutableStateOf("Date")}
val updatedDate = { date: String ->
datePicked = date
}
val activity = LocalContext.current as AppCompatActivity
Text(
text = datePicked,
color = MaterialTheme.colors.onSurface,
modifier = Modifier
.wrapContentSize()
.clickable { showDatePicker(activity, updatedDate) }
)
}
private fun showDatePicker(
activity: AppCompatActivity,
updatedDate: (String) -> Unit
) {
val picker = MaterialDatePicker.Builder.datePicker().build()
// be careful with the datepicker, because you need to manage the dispose!
picker.show(activity.supportFragmentManager, picker.toString())
picker.addOnPositiveButtonClickListener {
updatedDate(picker.headerText)
}
}
<TextView
android:id="@+id/TextViewDate"
android:text="Date"
app:drawableEndCompat="@drawable/ic_baseline_date_range" />
//In Activity or Fragment
textViewDate = findViewById(R.id.TextViewDate)
textViewDate.setOnClickListener {
showDatePicker()
}
private fun showDatePicker() {
val materialDatePicker = MaterialDatePicker
.Builder
.datePicker()
.build()
materialDatePicker.addOnPositiveButtonClickListener {
textViewDate.text = materialDatePicker.headerText
}
materialDatePicker.show(supportFragmentManager, "TAG")
}
}
@Composable
fun AlertDialogExample() {
var isVisible by remember { mutableStateOf(false) }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(onClick = { isVisible = true }
) {
Text(text = "SHOW DIALOG")
}
}
ShowDialog(isVisible) {
isVisible = !isVisible
}
}
@Composable
fun ShowDialog(isVisible: Boolean, onClick: () -> Unit) {
if (isVisible) {
AlertDialog(
onDismissRequest = { onClick() },
title = {
Text(
text = "Title",
style = MaterialTheme.typography.h6
)
},
confirmButton = {
Text(
text = "ACCEPT",
style = MaterialTheme.typography.button,
color = MaterialTheme.colors.primary,
modifier = Modifier
.padding(8.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onClick()
}
)
},
dismissButton = {
Text(
text = "DECLINE",
style = MaterialTheme.typography.button,
color = MaterialTheme.colors.primary,
modifier = Modifier
.padding(8.dp)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onClick()
}
)
},
text = {
Text(
text = "Scias me hoc mane canem meum mulsisse. Numquam satus dies sine me calidum pug lac",
style = MaterialTheme.typography.body1
)
}
)
}
}
<!-- XML Code -->
<Button
android:id="@+id/showDialogButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="show Dialog"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Code -->
lateinit var binding: AlertDialogLayoutBinding
private var dialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = AlertDialogLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title = "AlertDialog"
dialog = AlertDialog.Builder(this)
.setTitle("Title")
.setMessage("Scias me hoc mane canem meum mulsisse. Numquam satus dies sine me calidum pug lac")
.setCancelable(true)
.setPositiveButton("ACCEPT") { _, _ ->
finishActivity()
}
.setNegativeButton("DECLINE") { _, _ ->
finishActivity()
}
.create()
binding.showDialogButton.setOnClickListener {
dialog?.show()
}
}
private fun finishActivity() {
lifecycleScope.launch {
finish()
}
}
override fun onDestroy() {
dialog?.dismiss()
dialog = null
super.onDestroy()
}