Als ik mijn laatste artikel over Jetpack Compose schreef, stelde ik erin dat Jetpack Compose een aantal (in mijn mening) basiscomponenten ontbreekt en een daarvan is de tooltip.

Toen was er geen ingebouwde compositie om tooltips weer te geven en waren er verschillende alternatieve oplossingen online die de ronde deden. Het probleem met die oplossingen was dat zodra Jetpack Compose nieuwere versies uitbracht, deze oplossingen mogelijk kapot zouden raken. Dus was dat niet ideaal en de community moest hopen dat er later steun zou worden toegevoegd voor tooltips.

Ik ben er trots op dat ik kunnen melden dat sinds versie 1.1.0 van Compose Material 3 we nu gebruikelijke tooltipondersteuning hebben. 👏

Hoewel dit alleen al een wonder is, is meer dan een jaar gegaan sinds die versie werd uitgebracht. En met latere versies zijn de API’s die met tooltips te maken hebben ook drastisch veranderd.

Als je de wijzigingslogboeken doorneemt, zie je hoe de openbare en interne API’s zijn veranderd. Dus denk eraan, dat als je dit artikel leest, er misschien nog steeds veranderingen zijn gebeurd, aangezien alles wat met Tooltips te maken heeft nog steeds gemarkeerd is met de annotatie ExperimentalMaterial3Api::class.

❗️ De versie van Material 3 die in dit artikel wordt gebruikt is 1.2.1, die op 6 maart 2024 werd uitgebracht

Soorten tooltips

We hebben nu ondersteuning voor twee verschillende typen tooltips:

  1. Eenvoudige tooltip

  2. Platte tooltip

    Rich media tooltip

    Rich media tooltip

Platte Tooltip

U kunt het eerste type gebruiken om informatie over een pictogramknop te verschaffen die anders niet duidelijk zou zijn. Bijvoorbeeld, u kunt een platte tooltip gebruiken om aan een gebruiker aan te geven waarvoor de pictogramknop staat.

Om een tooltip aan uw applicatie toe te voegen, gebruikt u de TooltipBox composable. Deze composable heeft verschillende argumenten:

fun TooltipBox(
    positionProvider: PopupPositionProvider,
    tooltip: @Composable TooltipScope.() -> Unit,
    state: TooltipState,
    modifier: Modifier = Modifier,
    focusable: Boolean = true,
    enableUserInput: Boolean = true,
    content: @Composable () -> Unit,
)

Sommige daarvan zouden u bekend moeten zijn als u al Composables hebt gebruikt. Ik zal diegene benadrukken die een specifiek gebruik geven:

  • positionProvider – Van het type PopupPositionProvider en wordt gebruikt om de positie van de tooltip te berekenen.

  • tooltip – Hier kunt u het uiterlijk van de tooltip ontwerpen.

  • state – Deze bevat de status die is gekoppeld aan een specifieke Tooltip-instantie. Het biedt methoden zoals het weergeven/verwijderen van de tooltip en wanneer u een instantie van eenmaal maakt, kunt u aangeven of de tooltip persistente moet zijn of niet (dat wil zeggen of hij op het scherm moet blijven worden weergegeven totdat een gebruiker buiten de tooltip een klikactie uitvoert).

  • content – Dit is de UI die de tooltip boven of onder weergeeft.

Hier is een voorbeeld van het instantiëren van een BasicTooltipBox met alle relevante argumenten ingevuld:

@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun BasicTooltip() {
    val tooltipPosition = TooltipDefaults.rememberPlainTooltipPositionProvider()
    val tooltipState = rememberBasicTooltipState(isPersistent = false)

    BasicTooltipBox(positionProvider = tooltipPosition,
        tooltip =  { Text("Hello World") } ,
        state = tooltipState) {
        IconButton(onClick = { }) {
            Icon(imageVector = Icons.Filled.Favorite, 
                 contentDescription = "Your icon's description")
        }
    }
}

Jetpack Compose heeft een ingebouwde klasse genaamd TooltipDefaults. U kunt deze klasse gebruiken om argumenten te helpen instantiëren die een TooltipBox uitmaken. Bijvoorbeeld, u kunt TooltipDefaults.rememberPlainTooltipPositionProvider gebruiken om de tooltip correct in relatie tot het anker-element te positioneren.

Rich Tooltip

Een rich media tooltip neemt meer ruimte in dan een eenvoudige tooltip en kan gebruikt worden om meer context te bieden over de functionaliteit van een pictogrammabutton. Wanneer de tooltip getoond wordt, kunt u er knopjes en koppelingen aan toevoegen om een verdere uitleg of definities aan te bieden.

Hij wordt op gelijke wijze geïnstantieerd als een eenvoudige tooltip, binnen een TooltipBox, maar u gebruikt de RichTooltip composable.

TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text("RichTooltip") },
                      caretSize = caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()
                              }
                          }) {
                              Text("Dismiss")
                          }
                      }
                  ) {
                        Text("This is where a description would go.")
                  }
        },
        state = tooltipState) {
        IconButton(onClick = {
            /* Click event van pictogrammabutton */
        }) {
            Icon(imageVector = tooltipIcon,
                contentDescription = "Your icon's description",
                tint = iconColor)
        }
    }

Enkele dingen die u moet opmerken over een Rich tooltip:

  1. Een Rich tooltip heeft ondersteuning voor een caret.

  2. U kunt een actie (dus een knop) aan de tooltip toevoegen om gebruikers de optie te geven om meer informatie te vinden.

  3. U kunt logica toevoegen om de tooltip af te sluiten.

Edge Cases

Als u kiest om uw tooltip-status persistent te markeren, betekent dat als de gebruiker interactie heeft met de UI die uw tooltip weergeeft, zal de tooltip zichtbaar blijven tot de gebruiker op de schermrand drukt.

Als u naar het voorbeeld van een rijke tooltip boven kijkt, zou u misschien hebben gemerkt dat we een knop hebben toegevoegd om de tooltip af te sluiten nadat hij is geklikt.

Er is een probleem dat optreedt als de gebruiker op deze knop drukt. Omdat de afsluitactie wordt uitgevoerd op de tooltip, zal de tooltip niet opnieuw verschijnen als de gebruiker opnieuw een lang drukken op het UI-item dat deze tooltip oproept. Dit betekend dat de status van de tooltip persistent is nadat hij is afgesloten. Maar hoe komen we erachter en vergelijkbaar maken?

Om de status van de tooltip “terug te zetten”, moeten we de onDispose methode aanroepen die wordt getoond via de tooltip-status. Zodra we dat doen, wordt de tooltip-status opnieuw ingesteld en zal de tooltip weer verschijnen als de gebruiker een lang drukken uitvoert op het UI-item.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RichTooltip() {
    val tooltipPosition = TooltipDefaults.rememberRichTooltipPositionProvider()
    val tooltipState = rememberTooltipState(isPersistent = true)
    val scope = rememberCoroutineScope()

    TooltipBox(positionProvider = tooltipPosition,
        tooltip = {
                  RichTooltip(
                      title = { Text("RichTooltip") },
                      caretSize = TooltipDefaults.caretSize,
                      action = {
                          TextButton(onClick = {
                              scope.launch {
                                  tooltipState.dismiss()
                                  tooltipState.onDispose()  /// <---- HIER
                              }
                          }) {
                              Text("Dismiss")
                          }
                      }
                  ) {

                  }
        },
        state = tooltipState) {
        IconButton(onClick = {  }) {
            Icon(imageVector = Icons.Filled.Call, contentDescription = "Your icon's description")
        }
    }
}

Een ander scenario waarin de tooltip-status niet wordt opnieuw ingesteld, is als u zichzelf niet oproept voor de afsluitmethode per actie van de gebruiker, maar de gebruiker buiten de tooltip klikt, waardoor de tooltip wordt afgesloten. Dit roept de afsluitmethode achter de schermen op en de tooltip-status wordt ingesteld op afgesloten. Een langdruk op het UI-element om de tooltip opnieuw te zien zal niets resulteren.

Ons logica dat de onDispose-methode van de tooltip aanroep, wordt niet geactiveerd, dus hoe kunnen we de status van de tooltip resetten?

Momenteel heb ik dit niet kunnen uitfigureren. Het zou misschien te maken kunnen hebben met de MutatorMutex van de tooltip. Misschien zal er in de komende uitgaves een API voor zijn. Ik heb gemerkt dat als andere tooltips op het scherm zijn en ze worden gedrukt, dit de eerder geklikte tooltip reset.

Als u de code hieronder wilt zien, kunt u naar dit GitHub-repository

gaan. Als u tooltips in een applicatie wilt zien, kunt u het hier bekijken.

Referenties