| title | More Controls |
|---|
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
In the following steps of the tutorial we will add some basic controls for the end users which will allow them to:
- Enable/Disable the count-in
- Enable/Disable the metronome
- Enable/Disable the looping
- Change the zoom level
- Change the layout of the music sheet
The implementation will follow the same principle for all controls:
- We add the UI to the popup
- We add new properties to the viewmodel.
- We add event listeners to the UI and interact with the viewmodel.
- In the activity we listen to the viewmodel values and map them to the alphaTab API object
For the toggle controls we will add a small helper which will change the colors of the icon and text according to the check state. This code should not act as a reference how to properly style controls in android but is rather a simplistic approach for this tutorial.
<Tabs defaultValue="colors" values={[ { label: 'res/values/colors.xml', value: 'colors', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', } ] }>
In this file we add some new colors for our buttons which will toggle some features in an on/off fashion.
<color name="checkedIconColor">#FF436d9d</color>
<color name="uncheckedIconColor">#7F436d9d</color>
<color name="checkedTextColor">#FF000000</color>
<color name="uncheckedTextColor">#7F000000</color>This helper takes care of handling the toggle button initializations and coloring.
private fun initToggle(
button: MaterialButton,
initialState: Boolean,
onChange: (newValue: Boolean) -> Unit
) {
updateToggleColors(button, initialState)
button.addOnCheckedChangeListener { _, isChecked ->
updateToggleColors(button, isChecked)
onChange(isChecked)
}
}
private fun updateToggleColors(buttonView: MaterialButton, isChecked: Boolean) {
val textColor = if (isChecked) R.color.checkedTextColor else R.color.uncheckedTextColor
val iconColor = if (isChecked) R.color.checkedIconColor else R.color.uncheckedIconColor
val textColorList = ColorStateList.valueOf(ContextCompat.getColor(context, textColor))
val iconColorList = ColorStateList.valueOf(ContextCompat.getColor(context, iconColor))
buttonView.iconTint = iconColorList
buttonView.setTextColor(textColorList)
}The count-in will become relevant when we activate the player in the next step, but we can already create the control which will set the count-in volume based on the toggle button state.
<Tabs defaultValue="popup_controls" values={[ { label: 'popup_controls.xml', value: 'popup_controls', }, { label: 'MainViewModel.kt', value: 'MainViewModel', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', }, { label: 'MainActivity.kt', value: 'MainActivity', }, ] }>
<!-- After openFile button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/countIn"
style="@style/PopupButton"
android:checkable="true"
app:icon="@drawable/baseline_hourglass_empty_24"
android:text="Count-In" />init {
...
// highlight-start
initToggle(view.findViewById(R.id.countIn), mainViewModel.countIn.value ?: false) {
mainViewModel.countIn.value = it
}
// highlight-end
contentView = view
}override fun onCreate(savedInstanceState: Bundle?) {
...
// highlight-start
mViewModel.countIn.observe(this) {
mAlphaTabView.api.countInVolume = if (it) 1.0 else 0.0
}
// highlight-end
}The metronome will become relevant when we activate the player in the next step, but we can already create the control which will set the metronome volume based on the toggle button state.
<Tabs defaultValue="popup_controls" values={[ { label: 'popup_controls.xml', value: 'popup_controls', }, { label: 'MainViewModel.kt', value: 'MainViewModel', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', }, { label: 'MainActivity.kt', value: 'MainActivity', }, ] }>
<com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:checkable="true"
android:id="@+id/metronome"
app:icon="@drawable/baseline_edit_square_24"
android:text="Metronome"/>init {
...
// highlight-start
initToggle(view.findViewById(R.id.metronome), mainViewModel.metronome.value ?: false) {
mainViewModel.metronome.value = it
}
// highlight-end
contentView = view
}override fun onCreate(savedInstanceState: Bundle?) {
...
// highlight-start
mViewModel.metronome.observe(this) {
mAlphaTabView.api.metronomeVolume = if (it) 1.0 else 0.0
}
// highlight-end
}Again nothing new here, rinse and repeat to extend the UI and map the UI state ultimately to the API object:
<Tabs defaultValue="popup_controls" values={[ { label: 'popup_controls.xml', value: 'popup_controls', }, { label: 'MainViewModel.kt', value: 'MainViewModel', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', }, { label: 'MainActivity.kt', value: 'MainActivity', }, ] }>
<com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:checkable="true"
android:id="@+id/looping"
app:icon="@drawable/baseline_repeat_24"
android:text="Looping" />init {
...
// highlight-start
initToggle(view.findViewById(R.id.looping), mainViewModel.looping.value ?: false) {
mainViewModel.looping.value = it
}
// highlight-end
contentView = view
}override fun onCreate(savedInstanceState: Bundle?) {
...
// highlight-start
mViewModel.looping.observe(this) {
mAlphaTabView.api.isLooping = it
}
// highlight-end
}As alphaTab can change the scale in which the music sheet is rendered, we offer the user a popup menu to change this scale.
<Tabs defaultValue="popup_controls" values={[ { label: 'res/menu/zoom.xml', value: 'zoom', }, { label: 'popup_controls.xml', value: 'popup_controls', }, { label: 'MainViewModel.kt', value: 'MainViewModel', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', }, { label: 'MainActivity.kt', value: 'MainActivity', }, ] }>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="HardcodedText" >
<item android:title="25%" />
<item android:title="50%" />
<item android:title="75%" />
<item android:title="90%" />
<item android:title="100%" />
<item android:title="110%" />
<item android:title="125%" />
<item android:title="150%" />
<item android:title="200%" />
</menu><com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:id="@+id/zoom"
app:icon="@drawable/baseline_zoom_in_24"
android:text="Zoom" />Here create a popup menu and inflate the pre-created menu items into it.
As we know we labeled all menu items with <percentage>% we can derive the zoom level from the label.
init {
...
// highlight-start
val zoom = view.findViewById<MaterialButton>(R.id.zoom)
@SuppressLint("SetTextI18n")
zoom.text = "${mainViewModel.zoomLevel.value}%"
zoom.setOnClickListener {
PopupMenu(context, zoom).apply {
setOnMenuItemClickListener {
val zoomLevel = it.title!!.trim('%').toString().toInt()
mainViewModel.zoomLevel.value = zoomLevel
this@ControlsPopupWindow.dismiss()
true
}
inflate(R.menu.zoom)
show()
}
}
// highlight-end
contentView = view
}Updating the zoom level is a bit more effort than the previous tasks but still not very complicated:
First we fill the settings with the new scale. Then we tell alphaTab to read this updated settings
and re-render the music sheet.
override fun onCreate(savedInstanceState: Bundle?) {
...
// highlight-start
mViewModel.zoomLevel.observe(this) {
mAlphaTabView.settings.display.scale = it / 100.0
mAlphaTabView.api.updateSettings()
mAlphaTabView.renderTracks()
}
// highlight-end
}alphaTab can either render the music sheet in a page-like fashion that grows from top to bottom along the available width. Or it can show the music sheet in a horizontal scrolling fashion.
These options are also offered to the user via another popup menu. To actually apply the user selection we will again just fill the user input into the settings object and trigger an update just like for the zoom level:
<Tabs defaultValue="popup_controls" values={[ { label: 'res/menu/layout.xml', value: 'layout', }, { label: 'popup_controls.xml', value: 'popup_controls', }, { label: 'MainViewModel.kt', value: 'MainViewModel', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', }, { label: 'MainActivity.kt', value: 'MainActivity', }, ] }>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="HardcodedText">
<item android:title="Page" />
<item android:title="Horizontal" />
</menu><com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:id="@+id/layout"
app:icon="@drawable/baseline_view_quilt_24"
android:text="Layout" />Similar to the zoom level we use the PopupMenu and inflate the menu from the resource into it. To get the right alphaTab layout mode we match again against the labels.
init {
...
// highlight-start
val layout = view.findViewById<MaterialButton>(R.id.layout)
@SuppressLint("SetTextI18n")
layout.text = "${mainViewModel.layout.value!!.name}"
layout.setOnClickListener {
PopupMenu(context, layout).apply {
setOnMenuItemClickListener {
mainViewModel.layout.value = when(it.title) {
"Page" -> LayoutMode.Page
"Horizontal" -> LayoutMode.Horizontal
else -> throw IllegalStateException("Unknown Layout")
}
this@ControlsPopupWindow.dismiss()
true
}
inflate(R.menu.layout)
show()
}
}
// highlight-end
contentView = view
}Changing the layout is again similar to the zoom level. Changing the setting, refreshing the settings state inside alphaTab and trigger a render.
override fun onCreate(savedInstanceState: Bundle?) {
...
// highlight-start
mViewModel.layout.observe(this) {
mAlphaTabView.settings.display.layoutMode = it
mAlphaTabView.api.updateSettings()
mAlphaTabView.renderTracks()
}
// highlight-end
}That was already it. As you can see now, it is typically the same task over and over to extend your app with new functionalities. The biggest complexity is to identify how you want to build your UI towards your user. Using your Android development expertise you give the user options to change the settings as desired. After that is "only" understanding how to tell alphaTab how to respect those settings. Often the idea for new features start in the Settings and API reference to see what functionalities are offered.
<Tabs defaultValue="activity_main" values={[ { label: 'res/layout/activity_main.xml', value: 'activity_main', }, { label: 'res/layout/popup_controls.xml', value: 'popup_controls', }, { label: 'res/menu/layout.xml', value: 'layout', }, { label: 'res/menu/zoom.xml', value: 'zoom', }, { label: 'res/values/colors.xml', value: 'colors', }, { label: 'res/values/themes/themes.xml', value: 'themes', }, { label: 'MainActivity.kt', value: 'MainActivity', }, { label: 'MainViewModel.kt', value: 'MainViewModel', }, { label: 'ControlsPopupWindow.kt', value: 'ControlsPopupWindow', } ] }>
<?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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="HardcodedText"
tools:context=".MainActivity">
<alphaTab.AlphaTabView
android:id="@+id/alphatab"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:padding="6dp"
android:background="#436d9d"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@+id/controls"
android:orientation="vertical"
>
<TextView
android:id="@+id/trackName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Track Name"
android:textColor="@color/white" />
<TextView
android:id="@+id/songName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Song Name - Artist Name"
android:textStyle="bold"
android:textColor="@color/white"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/controls"
android:layout_alignParentEnd="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageButton
android:id="@+id/playPause"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@null"
android:textColor="@color/white"
android:paddingHorizontal="7dp"
android:contentDescription="Play/Pause"
android:src="@drawable/baseline_play_arrow_24" />
</LinearLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout><HorizontalScrollView
android:id="@+id/buttons"
android:layout_width="match_parent"
android:background="@color/design_default_color_background"
android:layout_alignParentStart="true"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_vertical"
android:padding="4dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/back"
style="@style/PopupButton"
app:icon="@drawable/baseline_arrow_back_48"
app:iconSize="48dp"
app:iconGravity="textStart"
app:iconTint="@color/black"
android:layout_marginHorizontal="8dp"
android:padding="0dp"
app:backgroundTint="@android:color/transparent"
/>
<com.google.android.material.button.MaterialButton
android:id="@+id/openFile"
style="@style/PopupButton"
app:icon="@drawable/baseline_file_open_24"
android:text="Open File" />
<com.google.android.material.button.MaterialButton
android:id="@+id/countIn"
style="@style/PopupButton"
android:checkable="true"
app:icon="@drawable/baseline_hourglass_empty_24"
android:text="Count-In" />
<com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:checkable="true"
android:id="@+id/metronome"
app:icon="@drawable/baseline_edit_square_24"
android:text="Metronome"/>
<com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:checkable="true"
android:id="@+id/looping"
app:icon="@drawable/baseline_repeat_24"
android:text="Looping" />
<com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:id="@+id/zoom"
app:icon="@drawable/baseline_zoom_in_24"
android:text="Zoom" />
<com.google.android.material.button.MaterialButton
style="@style/PopupButton"
android:id="@+id/layout"
app:icon="@drawable/baseline_view_quilt_24"
android:text="Layout" />
</LinearLayout>
</HorizontalScrollView>
<ListView
android:id="@+id/trackList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/buttons"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="-12dp"
android:layout_marginTop="-4dp"
android:layout_marginBottom="3dp"
android:background="@color/design_default_color_background">
</ListView>
<style name="PopupButton" parent="Widget.Material3.Button">
<item name="iconPadding">0dp</item>
<item name="cornerRadius">0dp</item>
<item name="iconTint">#FF436d9d</item>
<item name="iconGravity">top</item>
<item name="iconSize">24dp</item>
<item name="backgroundTint">#dedede</item>
<item name="android:textSize">12sp</item>
<item name="android:textColor">#000</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center_vertical</item>
<item name="android:layout_marginEnd">4dp</item>
<item name="android:minWidth">0dp</item>
<item name="android:paddingHorizontal">8dp</item>
</style>
import alphaTab.AlphaTabView import alphaTab.core.ecmaScript.Uint8Array import alphaTab.importer.ScoreLoader import alphaTab.model.Score import android.annotation.SuppressLint import android.net.Uri import android.os.Bundle import android.util.Log import android.view.Gravity import android.view.View import android.view.ViewGroup import android.widget.TextView import android.widget.Toast import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.lifecycle.ViewModelProvider import java.io.ByteArrayOutputStream import kotlin.contracts.ExperimentalContracts
@OptIn(ExperimentalContracts::class, ExperimentalUnsignedTypes::class) @SuppressLint("SetTextI18n") class MainActivity : AppCompatActivity() {
private lateinit var mAlphaTabView: AlphaTabView
private lateinit var mTrackName: TextView
private lateinit var mSongName: TextView
private lateinit var mViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
mAlphaTabView = findViewById(R.id.alphatab)
mTrackName = findViewById(R.id.trackName)
mSongName = findViewById(R.id.songName)
findViewById<View>(R.id.info).setOnClickListener {
val popup = ControlsPopupWindow(
this, mViewModel,
) {
mOpenFile.launch(arrayOf("*/*"))
}
popup.width = ViewGroup.LayoutParams.MATCH_PARENT
popup.height = ViewGroup.LayoutParams.MATCH_PARENT
popup.showAtLocation(mAlphaTabView, Gravity.CENTER, 0, 0)
}
mViewModel = ViewModelProvider(this)[MainViewModel::class.java]
mViewModel.settings.observe(this) {
mAlphaTabView.settings = it
}
mViewModel.tracks.observe(this) {
mAlphaTabView.tracks = it
val first = it?.firstOrNull()
if (first != null) {
mTrackName.text = first.name
mSongName.text = "${first.score.title} - ${first.score.artist}"
}
}
mViewModel.countIn.observe(this) {
mAlphaTabView.api.countInVolume = if (it) 1.0 else 0.0
}
mViewModel.metronome.observe(this) {
mAlphaTabView.api.metronomeVolume = if (it) 1.0 else 0.0
}
mViewModel.looping.observe(this) {
mAlphaTabView.api.isLooping = it
}
mViewModel.zoomLevel.observe(this) {
mAlphaTabView.settings.display.scale = it / 100.0
mAlphaTabView.api.updateSettings()
mAlphaTabView.renderTracks()
}
mViewModel.layout.observe(this) {
mAlphaTabView.settings.display.layoutMode = it
mAlphaTabView.api.updateSettings()
mAlphaTabView.renderTracks()
}
}
private val mOpenFile = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
val uri = it ?: return@registerForActivityResult
val score: Score
try {
val fileData = readFileData(uri)
score = ScoreLoader.loadScoreFromBytes(fileData, mAlphaTabView.settings)
Log.i("AlphaTab", "File loaded: ${score.title}")
} catch (e: Exception) {
Log.e("AlphaTab", "Failed to load file: $e, ${e.stackTraceToString()}")
Toast.makeText(this, "Failed to load file: ${e.message}", Toast.LENGTH_LONG).show()
return@registerForActivityResult
}
try {
mViewModel.score.value = score
mViewModel.tracks.value = arrayListOf(score.tracks[0])
} catch (e: Exception) {
Log.e("AlphaTab", "Failed to render file: $e, ${e.stackTraceToString()}")
Toast.makeText(this, "Failed to render file: ${e.message}", Toast.LENGTH_LONG).show()
}
}
private fun readFileData(uri: Uri): Uint8Array {
val inputStream = contentResolver.openInputStream(uri)
inputStream.use {
ByteArrayOutputStream().use {
inputStream!!.copyTo(it)
return Uint8Array(it.toByteArray().asUByteArray())
}
}
}
}
</TabItem>
<TabItem value="MainViewModel">
```kotlin
package net.alphatab.tutorial.android
import alphaTab.LayoutMode
import alphaTab.Settings
import alphaTab.model.Score
import alphaTab.model.Track
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlin.contracts.ExperimentalContracts
@ExperimentalUnsignedTypes
@ExperimentalContracts
class MainViewModel : ViewModel() {
val score = MutableLiveData<Score?>()
val tracks = MutableLiveData<List<Track>?>()
val countIn = MutableLiveData(false)
val metronome = MutableLiveData(false)
val looping = MutableLiveData(false)
val zoomLevel = MutableLiveData(100)
val layout = MutableLiveData(LayoutMode.Page)
val settings = MutableLiveData<Settings>().apply {
value = Settings().apply {
this.player.enableCursor = true
this.player.enablePlayer = true
this.player.enableUserInteraction = true
this.display.barCountPerPartial = 4.0
this.display.resources.barNumberFont
}
}
}
import alphaTab.LayoutMode import alphaTab.model.Track import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ListView import android.widget.PopupMenu import android.widget.PopupWindow import android.widget.TextView import androidx.core.content.ContextCompat import com.google.android.material.button.MaterialButton import kotlin.contracts.ExperimentalContracts
@OptIn(ExperimentalContracts::class, ExperimentalUnsignedTypes::class) @SuppressLint("InflateParams") class ControlsPopupWindow( private val context: Context, private val mainViewModel: MainViewModel, onOpenFile: () -> Unit ) : PopupWindow(context) { private val mOpenButton: MaterialButton private val mTrackList: ListView
init {
val view = LayoutInflater.from(context).inflate(R.layout.popup_controls, null)
mOpenButton = view.findViewById(R.id.openFile)
mOpenButton.setOnClickListener {
onOpenFile()
dismiss()
}
mTrackList = view.findViewById(R.id.trackList)
mTrackList.adapter =
TrackListAdapter(context, mainViewModel.score.value?.tracks?.toList() ?: emptyList())
mTrackList.setOnItemClickListener { _, _, position, _ ->
mainViewModel.tracks.value =
mutableListOf((mTrackList.adapter as TrackListAdapter).getItem(position)!!)
dismiss()
}
view.findViewById<MaterialButton>(R.id.back).setOnClickListener {
dismiss()
}
initToggle(view.findViewById(R.id.countIn), mainViewModel.countIn.value ?: false) {
mainViewModel.countIn.value = it
}
initToggle(view.findViewById(R.id.metronome), mainViewModel.metronome.value ?: false) {
mainViewModel.metronome.value = it
}
initToggle(view.findViewById(R.id.looping), mainViewModel.looping.value ?: false) {
mainViewModel.looping.value = it
}
val zoom = view.findViewById<MaterialButton>(R.id.zoom)
@SuppressLint("SetTextI18n")
zoom.text = "${mainViewModel.zoomLevel.value}%"
zoom.setOnClickListener {
PopupMenu(context, zoom).apply {
setOnMenuItemClickListener {
val zoomLevel = it.title!!.trim('%').toString().toInt()
mainViewModel.zoomLevel.value = zoomLevel
this@ControlsPopupWindow.dismiss()
true
}
inflate(R.menu.zoom)
show()
}
}
val layout = view.findViewById<MaterialButton>(R.id.layout)
@SuppressLint("SetTextI18n")
layout.text = "${mainViewModel.layout.value!!.name}"
layout.setOnClickListener {
PopupMenu(context, layout).apply {
setOnMenuItemClickListener {
mainViewModel.layout.value = when(it.title) {
"Page" -> LayoutMode.Page
"Horizontal" -> LayoutMode.Horizontal
else -> throw IllegalStateException("Unknown Layout")
}
this@ControlsPopupWindow.dismiss()
true
}
inflate(R.menu.layout)
show()
}
}
contentView = view
}
private fun initToggle(
button: MaterialButton,
initialState: Boolean,
onChange: (newValue: Boolean) -> Unit
) {
updateToggleColors(button, initialState)
button.addOnCheckedChangeListener { _, isChecked ->
updateToggleColors(button, isChecked)
onChange(isChecked)
}
}
private fun updateToggleColors(buttonView: MaterialButton, isChecked: Boolean) {
val textColor = if (isChecked) R.color.checkedTextColor else R.color.uncheckedTextColor
val iconColor = if (isChecked) R.color.checkedIconColor else R.color.uncheckedIconColor
val textColorList = ColorStateList.valueOf(ContextCompat.getColor(context, textColor))
val iconColorList = ColorStateList.valueOf(ContextCompat.getColor(context, iconColor))
buttonView.iconTint = iconColorList
buttonView.setTextColor(textColorList)
}
class TrackListAdapter(context: Context, tracks: List<Track>) :
ArrayAdapter<Track>(context, android.R.layout.simple_list_item_1, tracks) {
private val mInflater: LayoutInflater = LayoutInflater.from(context)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view =
convertView ?: mInflater.inflate(android.R.layout.simple_list_item_1, parent, false)
if (view !is TextView) {
throw IllegalStateException("Expected simple_list_item_1 to be a TextView")
}
val item = getItem(position)
view.text = item!!.name
return view
}
}
}
</TabItem>
</Tabs>


