Obraz zawierający tekst, Czcionka, Grafika

Opis wygenerowany automatycznie 

Kierunek Informatyka

 

Instrukcja do ćwiczeń laboratoryjnych nr:

4

Nazwa przedmiotu:
Programowanie w języku Kotlin

Temat: Jetpack Cpmpose – Kotlin – Android Studio

Tryb studiów: stacjonarne

Czas trwanie ćw.

2x45 min

Autor materiałów: dr Marcin Skuba

1. Treści programowe: 

Programowanie deklaratywne, tworzenie interfejsu UI – Jetpack Compose, Kotlin

 

2. Cel zajęć:

Celem zajęć jest zrozumienie zasad oraz poznanie składni programowania deklaratywnego na przykładzie biblioteki JetPpack Compose do tworzenia interfejsów użytkownika w języku Kotlin.

 

3. Materiały dydaktyczne

Jetpack Compose to największa rewolucja w tworzeniu UI na Androida od czasu powstania tego systemu. Przejście z XML na Compose to nie tylko zmiana języka, to całkowita zmiana filozofii myślenia o kodzie.

Oto najważniejsze punkty, które musisz znać:

 

1. Programowanie deklaratywne vs imperatywne

To klucz do zrozumienia Compose.

·        W starym stylu (imperatywnym) pisałeś instrukcje krok po kroku: "Znajdź przycisk, zmień mu kolor na czerwony, ustaw tekst na 'Kliknięto'". Musiałeś sam zarządzać stanem i dbać, by UI nadążało za logiką.

·        W Compose (deklaratywnym) po prostu opisujesz, jak UI ma wyglądać w danym stanie: "Jeśli przycisk jest kliknięty, ma być czerwony i mieć napis 'Kliknięto'". Ty dostarczasz dane, a Compose zajmuje się resztą.

 

2. UI jako funkcja stanu

W Compose interfejs użytkownika jest wynikiem funkcji. Można to zapisać wzorem:

Gdy zmienia się State (stan), Compose automatycznie wywołuje funkcję ponownie z nowymi danymi. Ten proces nazywamy Rekmpozycją (Recomposition).

 

3. Kompozycja zamiast dziedziczenia

W starym systemie każdy widok (View) był gigantycznym obiektem dziedziczącym po tysiącach linii kodu. W Compose budujesz UI z małych, niezależnych klocków – funkcji @Composable.

Możesz stworzyć własny przycisk MyGreenButton i używać go wielokrotnie, łącząc go z innymi komponentami jak klocki LEGO.

 

4. Koniec z plikami XML

Cały interfejs piszesz w Kotlinie. Oznacza to:

·        Pełne wsparcie programistyczne (podpowiadanie składni, sprawdzanie typów).

·        Możliwość używania pętli if czy for bezpośrednio wewnątrz definicji UI (np. "jeśli lista jest pusta, wyświetl komunikat, w przeciwnym razie wyświetl listę").

·        Brak konieczności przełączania się między plikami .kt a .xml.

 

5. Recomposition (Inteligentne odświeżanie)

Compose jest bardzo sprytny. Kiedy zmienia się jedna mała dana (np. liczba polubień pod postem), nie rysuje całego ekranu od nowa. Odświeża tylko te konkretne funkcje @Composable, które używają tej zmienionej danej. Dzięki temu aplikacje są płynne i wydajne.

 

W programowaniu deklaratywnym nie mówisz programowi "jak" ma zmienić widok, ale "co" ma być wyświetlone w oparciu o aktualne dane.

 

Klasa aktywności:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.mpje_jpc3.ui.theme.Mpje_JPC3Theme

class MainActivity : ComponentActivity() {

   
override fun onCreate(savedInstanceState: Bundle?) {
       
super.onCreate(savedInstanceState)
       
enableEdgeToEdge()
       
setContent {
           
Mpje_JPC3Theme {
               
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                   
Greeting(
                       
name = "Android",
                       
modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
   
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
   
Text(
       
text = "Hello $name!",
       
modifier = modifier
    )
}

@Preview(showBackground = true, showSystemUi = true)
@Composable
fun GreetingPreview() {
    Mpje_JPC3Theme {
       
Greeting("Android")
    }
}

 

1. enableEdgeToEdge()

Ta funkcja sprawia, że Twoja aplikacja zajmuje cały ekran, łącznie z obszarami pod paskiem stanu (tam, gdzie masz godzinę i baterię) oraz paskiem nawigacji na dole.

 

2. setContent { ... }

To jest most łączący "stary" świat Activity z "nowym" światem Compose.

 

3. Moje_JPC3Theme { ... }

To jest tzw. Wrapper tematu (nazwa pochodzi od Twojego projektu, np. Moje JetPack Compose 3).

 

4. Scaffold

Po angielsku to "rusztowanie". Jest to niesamowicie pomocny komponent, który daje Ci gotową strukturę ekranu zgodną z Material Design.

 

5. @Composable

To najważniejsza adnotacja w Compose. Mówi ona kompilatorowi: "Ta funkcja nie jest zwykłą funkcją – ona służy do generowania interfejsu użytkownika".

 

6. @Preview(showBackground = true)

To narzędzie dla programisty, które pozwala zobaczyć wygląd komponentu bez uruchamiania aplikacji na telefonie czy emulatorze.

 

7. fun GreetingPreview() { ... }

To po prostu zwykła funkcja, która służy wyłącznie do wyświetlenia podglądu.

 

8. Nawiasy okrągłe () – Parametry (Właściwości)

Wewnątrz () przekazujemy argumenty funkcji. To tutaj decydujesz, jak dany komponent ma wyglądać lub jak ma się zachowywać "technicznie".

Np.: Tekst do wyświetlenia, kolory, kształty, style czcionek oraz najważniejszy element w Compose: Modifier (modyfikator).

 

9. Nawiasy klamrowe {} – Treść (Sloty / Trailing Lambda)

Wewnątrz {} umieszczamy to, co ma się znaleźć w środku danego komponentu. W Kotlinie, jeśli ostatnim parametrem funkcji jest inna funkcja (tzw. lambda), możemy ją wyciągnąć poza nawiasy okrągłe.

Wpisujemy tam Inne komponenty @Composable.

 

Podstawowe elementy do budowy interfejsu:

 

·       Proste pole tekstowe:

Text(
   
text = "Tekst przycisku",
   
color = Color(0,0,255)
)

 

 

·       Prosty przycisk z etykieta tekstową

  

Button(onClick = {
   
Log.d("Click","Kliknięcie")
}) {
   
Text(text = "Przycisk")
}

 

·       Modifier (modyfikator)

 

Modifier to absolutnie najważniejsze narzędzie w Jetpack Compose. Jeśli Composable (np. Text lub Button) jest "rzeczą", to Modifier jest opisem tego, jak ta rzecz ma wyglądać, gdzie ma stać i jak ma reagować na dotyk.

Możesz o nim myśleć jak o liście instrukcji, którą dołączasz do elementu.

Modyfikatory pozwalają na:

 

Kluczowa zasada: Kolejność ma znaczenie!

To najczęstszy błąd początkujących. Modifiers są przetwarzane od góry do dołu (jeden po drugim).

Zmiana kolejności całkowicie zmienia wynik.

Przykład:

  1. .padding(16.dp).background(Color.Red) – najpierw robisz margines, a potem malujesz tło (tło będzie tylko pod tekstem).
  2. .background(Color.Red).padding(16.dp) – najpierw malujesz tło, a potem dodajesz margines wewnątrz tego tła (cały czerwony prostokąt będzie większy).

 

Text(
   
text = "Cześć!",
   
modifier = Modifier
        .
fillMaxWidth()           // 1. Rozciągnij na całą szerokość
       
.background(Color.Yellow)  // 2. Daj żółte tło
       
.padding(20.dp)           // 3. Dodaj odstęp wewnątrz
       
.clickable { Log.d("Click", "Kliknięcie") } // 4. Spraw, by tekst był klikalny
)

 

 

Wybrane Atrybuty Modifier:

1. Rozmiar i Wymiary (Size)

Te modyfikatory decydują o tym, ile miejsca na ekranie zajmie Twój komponent.

Atrybut

Opis

Przykład

fillMaxSize()

Zajmuje całą dostępną przestrzeń (szerokość i wysokość).

Modifier.fillMaxSize()

fillMaxWidth()

Rozciąga element na całą dostępną szerokość.

Modifier.fillMaxWidth()

fillMaxHeight()

Rozciąga element na całą dostępną wysokość.

Modifier.fillMaxHeight()

size(dp)

Ustawia stałą szerokość i wysokość (np. 100.dp na 100.dp).

Modifier.size(100.dp)

width(dp) / height(dp)

Ustawia konkretną szerokość lub wysokość.

Modifier.width(50.dp)

wrapContentSize()

Pozwala elementowi mieć własny rozmiar, ignorując limity rodzica.

Modifier.wrapContentSize()

 

 

2. Odstępy i Pozycjonowanie (Layout)

Decydują o "oddechu" wokół elementu i jego miejscu w kontenerze.

Atrybut

Opis

Przykład

padding(dp)

Dodaje margines wewnętrzny wokół elementu.

Modifier.padding(16.dp)

offset(x, y)

Przesuwa element o daną wartość bez zmiany układu innych.

Modifier.offset(x = 10.dp)

weight(float)

(Tylko w Row/Column) Rozdziela wolną przestrzeń proporcjonalnie.

Modifier.weight(1f)

align(alignment)

Ustawia wyrównanie elementu wewnątrz Box, Row lub Column.

Modifier.align(Alignment.Center)

 

3. Wygląd i Stylizacja (Graphics)

Wszystko, co sprawia, że komponent wygląda ładnie.

Atrybut

Opis

Przykład

background(color, shape)

Ustawia kolor tła lub gradient oraz opcjonalnie kształt.

Modifier.background(Color.Red)

border(width, color, shape)

Dodaje obramowanie wokół elementu.

Modifier.border(2.dp, Color.Black)

clip(shape)

Przycina element do danego kształtu (np. koła).

Modifier.clip(CircleShape)

alpha(float)

Ustawia przezroczystość (od 0.0 do 1.0).

Modifier.alpha(0.5f)

shadow(elevation, shape)

Dodaje cień pod elementem (efekt uniesienia).

Modifier.shadow(4.dp)

 

4. Interakcje i Zdarzenia (Interactions)

Sprawiają, że UI reaguje na działania użytkownika.

Atrybut

Opis

Przykład

clickable { }

Reaguje na kliknięcie i dodaje efekt "fali" (ripple).

Modifier.clickable { doSomething() }

combinedClickable

Obsługuje kliknięcie, podwójne kliknięcie i długie przytrzymanie.

Modifier.combinedClickable { ... }

scrollable(...)

Pozwala na przewijanie elementu gestem.

Modifier.scrollable(...)

selectable(...)

Pozwala na zaznaczenie (np. w RadioButton).

Modifier.selectable(...)

 

5. Bezpieczne obszary (Insets)

Modyfikatory, o które pytałeś wcześniej, dbające o widoczność UI.

Atrybut

Opis

Przykład

safeDrawingPadding()

Omija paski systemowe, wycięcia i klawiaturę.

Modifier.safeDrawingPadding()

statusBarsPadding()

Odsuwa tylko od górnego paska stanu.

Modifier.statusBarsPadding()

navigationBarsPadding()

Odsuwa tylko od dolnego paska nawigacji.

Modifier.navigationBarsPadding()

 

 

Obsługa zdarzeń Modifier.combinedClickable { ... }

 

Modifier.combinedClickable to "starszy brat" zwykłego .clickable. Pozwala on Twojemu komponentowi reagować na trzy różne rodzaje interakcji: zwykłe kliknięcie, podwójne kliknięcie oraz długie przytrzymanie.

Jest to idealne rozwiązanie do elementów listy, gdzie np. kliknięcie otwiera szczegóły, a długie przytrzymanie otwiera menu usuwania.

 

Modyfikator ten jest oznaczony jako Experimental. Aby go użyć, musisz dodać nad swoją funkcją adnotację @OptIn(ExperimentalFoundationApi::class).

Oto jak stworzyć element, który reaguje na różne gesty:

 

@OptIn(ExperimentalFoundationApi::class) // Wymagane dla combinedClickable
@Composable
fun GestureBox() {
   
val context = LocalContext.current

    Box
(
       
modifier = Modifier
            .
size(200.dp)
            .
background(Color.LightGray, RoundedCornerShape(16.dp))
            .
combinedClickable(
               
onClick = {
                   
Toast.makeText(context, "Kliknięto!", Toast.LENGTH_SHORT).show()
                },
               
onDoubleClick = {
                   
Toast.makeText(context, "Podwójne kliknięcie!", Toast.LENGTH_SHORT).show()
                },
               
onLongClick = {
                   
Toast.makeText(context, "Długie przytrzymanie!", Toast.LENGTH_SHORT).show()
                }
           
),
       
contentAlignment = Alignment.Center
   
) {
       
Text("Testuj Gesty")
    }
}

 

·       Column

Column to jeden z trzech podstawowych układów (layoutów) w Jetpack Compose. Jej zadaniem jest układanie elementów pionowo – jeden pod drugim.

Wszystko, co wrzucisz do środka klamer {} w Column, pojawi się na ekranie w kolejności od góry do dołu. Jest to odpowiednik pionowego LinearLayout ze starego systemu XML.

 

Pozycjonowanie (Alignment i Arrangement)

Column pozwala Ci kontrolować, jak elementy mają się zachowywać wewnątrz niej:

 

Column(
   
modifier = Modifier.fillMaxSize(),
   
verticalArrangement = Arrangement.Center,           // Wyśrodkuj w pionie
   
horizontalAlignment = Alignment.CenterHorizontally // Wyśrodkuj w poziomie
) {
   
Text("Element 1")
   
Text("Element 2")
   
Button(onClick = { /* ... */ }) {
       
Text("Przycisk pod tekstami")
    }
}

 

 

 

                    

Domyślnie Column nie jest przewijalna. Jeśli dodasz do niej 50 przycisków, te, które nie zmieszczą się na ekranie, po prostu zostaną ucięte. Aby lista była przewijalna, musisz dodać modyfikator: Modifier.verticalScroll(rememberScrollState()) lub użyć komponentu LazyColumn (odpowiednik RecyclerView).

 

·       Row

Row to poziomy odpowiednik Column. Jest to kontener, który układa wszystkie elementy wewnątrz klamer {} jeden obok drugiego (w poziomie).

Możesz o nim myśleć jak o rzędzie w tabeli lub poziomym LinearLayout ze starego systemu XML.

 

Wszystko, co dodasz do Row, pojawi się od lewej do prawej strony ekranu. Jest to idealny układ, gdy chcesz umieścić np. ikonę obok tekstu lub dwa przyciski obok siebie.

Główne parametry (wewnątrz nawiasów okrągłych)

Podobnie jak w Column, tutaj też mamy dwa kluczowe ustawienia, które decydują o tym, jak elementy "pływają" wewnątrz rzędu:

 

Row(
   
modifier = Modifier
        .
fillMaxWidth()
        .
padding(16.dp)
        .
background(Color.LightGray),
   
verticalAlignment = Alignment.CenterVertically, // Wyśrodkuj ikonę i tekst w pionie
   
horizontalArrangement = Arrangement.spacedBy(8.dp) // Dodaj 8dp odstępu między elementami
) {
   
Icon(Icons.Default.Info, contentDescription = null)
   
Text(text = "Masz nową wiadomość!")
}

 

 

·       Spacer

Spacer to najprostszy, a jednocześnie jeden z najczęściej używanych komponentów w Jetpack Compose. Jak sama nazwa wskazuje, służy do tworzenia pustej przestrzeni (odstępu) między innymi elementami.

Możesz o nim myśleć jak o „niewidzialnym klocku”, który wypycha inne komponenty.

Column {
   
Text("Górny tekst")
   
Spacer(modifier = Modifier.height(20.dp)) // 20dp odstępu w pionie
   
Text("Dolny tekst")
}

 

 

·       Button

Button(
   
onClick = { Log.d("Log", "Kliknięcie") },
   
colors = ButtonDefaults.buttonColors(
       
containerColor = Color(0xFFFF5722), // Kolor tła przycisku
       
contentColor = Color.White          // Kolor tekstu i ikon wewnątrz
       
)
    ) {
   
Text(text = "Przycisk")
   
Spacer(modifier = Modifier.width(5.dp))
   
Icon(Icons.Default.Info, contentDescription = null)
}

 

Wstrzykiwanie parametrów do funkcji:

 

Zamiast pisać ten sam kod przycisku wiele razy, tworzysz jedną funkcję i "wstrzykujesz" do niej to, co ma się zmieniać.

Oto przykład profesjonalnie napisanej funkcji @Composable, która przyjmuje tekst, ikonę, akcję kliknięcia oraz modyfikator.

 

@Composable
fun CustomIconButton(
    text: String,                
// Wstrzykujemy tekst
   
icon: ImageVector,           // Wstrzykujemy ikonę
   
onClick: () -> Unit,         // Wstrzykujemy funkcję (akcję)
   
modifier: Modifier = Modifier // Wstrzykujemy modyfikator (z domyślną wartością)
) {
   
Button(
       
onClick = onClick,
       
modifier = modifier // Przekazujemy otrzymany modyfikator dalej
   
) {
       
Icon(
           
imageVector = icon,
           
contentDescription = null // null, bo tekst obok już opisuje przycisk
       
)
       
Spacer(modifier = Modifier.width(8.dp)) // Odstęp między ikoną a tekstem
       
Text(text = text)
    }
}

 

Dzięki temu, że "wstrzyknęliśmy" te parametry, możesz użyć tego samego przycisku w zupełnie różnych konfiguracjach:

 

@Composable
fun MyScreen() {
   
Spacer(modifier = Modifier.height(45.dp))
   
Column(modifier = Modifier.padding(16.dp)) {

       
// 1. Standardowy przycisk "Lubię to"
       
CustomIconButton(
           
text = "Lubię to",
           
icon = Icons.Default.Favorite,
           
onClick = { Log.d("Click", "Kliknięto 'Lubię to'") }
       
)

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

       
// 2. Ten sam przycisk, ale z dodatkowym modyfikatorem (cała szerokość)
       
CustomIconButton(
           
text = "Wyślij wiadomość",
           
icon = Icons.Default.Build,
           
onClick = { Log.d("Click", "Wysłano wiadomość") },
           
modifier = Modifier.fillMaxWidth() // Nadpisujemy domyślny modyfikator
       
)
    }
}

 

 

Zapamiętywanie stanu:

 

Przykład kodu, który inkrementuje wartość zmiennej oraz próbuje ją wyświetlić w polu tekstowym oraz wyświetla ją jako Log.

var count= 0
@Composable
fun MyButton(){
   
Button(onClick = {
       
count++
        Log.d(
"Click", "$count") //Diała
   
}) {
       
Text(text = "Count $count")         //Nie działa
   
}
}

 

Dlaczego ten przykład nie działa?

Twój kod nie działa (licznik w Log.d rośnie, ale na ekranie stoi w miejscu), ponieważ funkcja MyButton nie wie, że musi się narysować od nowa

ü  Brak "Recompozycji" (Odświeżania)

- W Compose interfejs użytkownika jest odświeżany tylko wtedy, gdy zmieni się coś, co Compose obserwuje. Twoja zmienna var count to zwykła zmienna procesora. Kiedy ją zmieniasz, system operacyjny o tym wie, ale silnik Compose nie ma pojęcia, że ta zmiana powinna wpłynąć na wygląd ekranu.

Aby Compose wiedział, że ma odświeżyć funkcję (czyli wykonać tzw. Recompozycję), musisz użyć specjalnego typu opakowania: MutableState.

- Gdybyś nawet użył stanu, ale nie użył funkcji remember, to przy każdej próbie odświeżenia ekranu Twoja zmienna count byłaby tworzona od zera i ustawiana na 0. Funkcje w Compose są wykonywane wielokrotnie – muszą więc mieć "pamięć".

 

@Composable
fun MyButton(){
   
var count by remember {mutableStateOf(0)}
   
Button(onClick = {
       
count++
        Log.d(
"Click", "$count") //Diała
   
}) {
       
Text(text = "Count $count")         //Nie działa
   
}
}

 

Dlaczego to teraz działa?

  1. mutableStateOf(0): Tworzy specjalny obiekt "obserwowalny". Gdy jego wartość się zmienia, Compose automatycznie odnajduje wszystkie miejsca, gdzie ta zmienna jest używana (u Ciebie w Text) i tylko te miejsca rysuje od nowa.
  2. remember { ... }: Mówi Compose: "Przy pierwszym uruchomieniu stwórz tę zmienną, ale przy każdym kolejnym (recompozycji) nie twórz jej na nowo, tylko weź tę, którą już znasz". Bez tego licznik zawsze wracałby do zera.
  3. by (Delegat): To ułatwienie w Kotlinie. Dzięki niemu możesz używać count jak zwykłej liczby (count++), zamiast pisać count.value++.

 

W powyższym przykładzie var count było poza funkcją @Composable. To bardzo zła praktyka w Compose, ponieważ:

 

UI w Compose to lustrzane odbicie stanu. Jeśli chcesz, żeby UI się zmieniło, musisz zmienić State, a nie zwykłą zmienną.

 

Kolejne elementy do budowy interfejsu:

 

·       Box

Box to trzeci z fundamentów układów w Jetpack Compose (obok Column i Row). Jeśli Column to pion, a Row to poziom, to Box służy do układania elementów jeden na drugim (w głąb ekranu). Działa bardzo podobnie do FrameLayout ze starego systemu XML.

 

1. Nakładanie elementów (Warstwy)

Głównym zadaniem Box jest wyświetlanie komponentów tak, aby mogły na siebie nachodzić. Elementy dopisane na końcu listy wewnątrz {} będą "na wierzchu".

Przykład: Zdjęcie profilowe, a na nim mała zielona kropka statusu "online".

2. Precyzyjne pozycjonowanie (Alignment)

Box pozwala na bardzo łatwe "przypięcie" elementu do dowolnego rogu lub środka kontenera za pomocą parametru contentAlignment.

3. Tworzenie tła lub filtrów

Często używa się Box, aby nałożyć półprzezroczysty filtr na obrazek lub stworzyć niestandardowe tło pod grupą elementów.

 

Przykład kodu: Ikona powiadomień z kropką

Box(
   
modifier = Modifier.size(64.dp).background(Color.Green),
   
contentAlignment = Alignment.Center // Domyślnie wszystko na środku
) {
   
// 1. Warstwa dolna: Ikona
   
Icon(
        Icons.
Default.Notifications,
       
contentDescription = null,
       
modifier = Modifier.size(48.dp)
    )

   
// 2. Warstwa górna: Czerwona kropka przypięta do prawego górnego rogu
   
Box(
       
modifier = Modifier
            .
size(15.dp)
            .
background(Color.Red, shape = CircleShape)
            .
align(Alignment.TopEnd) // Specjalny modyfikator dostępny tylko w Box
   
)
}

 

Kluczowe parametry Box

  1. contentAlignment: Ustawiasz go w nawiasach (). Decyduje, gdzie domyślnie lądują elementy (np. Alignment.BottomCenter).
  2. propagateMinConstraints: Jeśli ustawisz na true, Box wymusi na swoich dzieciach, aby miały co najmniej taki sam rozmiar jak on sam.

3.     Modyfikator .align()

Wewnątrz klamer {} możesz każdemu elementowi z osobna nadać modyfikator .align(), aby ustawić go w konkretnym miejscu:

·        Alignment.TopStart (Lewy górny)

·        Alignment.Center (Środek)

·        Alignment.BottomEnd (Prawy dolny)

·        ...i wszystkie inne kombinacje (łącznie 9 pozycji).

 

·       Pole tekstowe edycyjne (OutlinedTextField oraz TextField)

Różnica między TextField a OutlinedTextField sprowadza się niemal wyłącznie do stylistyki wizualnej i sposobu, w jaki prezentują się na ekranie. Oba komponenty działają identycznie pod względem kodu (mają te same parametry, jak value czy onValueChange).

1. Wygląd wizualny

2. Zachowanie etykiety (Label)

 

@Composable
fun LoggingTextField() {
   
// Stan przechowujący tekst
   
var textState by remember { mutableStateOf("") }

   
OutlinedTextField(
       
value = textState,
       
singleLine = true, // To sprawia, że pole ma tylko jedną linię
       
maxLines = 1,      // Dodatkowe zabezpieczenie (limit linii)
        //typ klawiatury
       
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
       
onValueChange = { newText ->
           
// 1. Aktualizujemy stan, aby tekst pojawił się w polu
           
textState = newText
           
// 2. Wysyłamy wiadomość do Logcat
           
Log.d("Tag", "Użytkownik wpisał: $newText")
        },
       
label = { Text("Wpisz coś...") },
       
modifier = Modifier
            .
fillMaxWidth()
            .
padding(16.dp)
    )
}

 

Przykład konwersji tekstu do wartości całkowitej:

@Composable
fun SimplestIntExample() {
   
var textValue by remember { mutableStateOf("") }

   
Column(modifier = Modifier.padding(16.dp)) {
       
// 1. Pole tekstowe pobierające znaki
       
OutlinedTextField(
           
value = textValue,
           
onValueChange = { textValue = it },
           
label = { Text("Wpisz liczbę") },
           
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
        )

       
// 2. Próba zamiany na Int
        // toIntOrNull() spróbuje zrobić liczbę, jeśli się nie uda (np. puste pole) - poda 0
       
val number = textValue.toIntOrNull() ?: 0

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

       
// 3. Wykorzystanie liczby do obliczeń
       
Text(text = "Liczba jako Int: $number")
       
Text(text = "Liczba pomnożona przez 2: ${number * 2}")
    }
}

 

Zadania

Zadanie 1: Personalizowany Witacz

Stwórz aplikację, która posiada pole tekstowe do wpisania imienia oraz przycisk "Przywitaj mnie".

 

Zadanie 2: Kalkulator

Napisz aplikację, która pozwoli wprowadzić dwie wartości do dwóch edycyjnych pól tekstowych umieszczonych obok siebie oraz cztery przyciski (poniżej pól tekstowych, wszystkie obok siebie), które kolejno będą dodawać, odejmować, mnożyć oraz dzielić te liczby.

Ustaw odpowiednie etykiety dla przycisków. Wynik powinien być wyświetlany poniżej przycisków w polu tekstowym.   

 

Zadanie 3: Konwerter Jednostek (Poziom: Średni)

Zaprojektuj prosty konwerter walut (np. PLN na EUR) lub jednostek miary (np. Kilometry na Mile).

 

Zadanie 4: Karta Kontrolna Smart Home

Stwórz panel sterowania jasnością żarówki.

s