Niestandardowe okno dialogowe
Niestandardowy układ widoku okna dialogowego przygotowuje się na tych samych zasadach co dowolny układ widoku zapisany w pliku XML. Aby podpiąć taki widok do okna modalnego klasy AlerDialog korzysta się z metody tej klasy- setView(). Parametrem tej metody jest przygotowany układ widoku (layout).
Celem jest utworzenie dwóch niestandardowych okien komunikatów. Okna mają mieć inny kształt ramki oraz inny kształt klawiszy. Patrz poniższa ilustracja.
Treścią komunikatu jest licznik kliknięć.
Przygotowanie projektu rozwiązania
Utwórz pusty projekt. Wybierz język Kotlin. W głównym widoku aplikacji osadź jedną kontrolkę typu TextView i cztery kontrolki typu Button. Wprowadź odpowiedni tekst.
Zawartość pliku XML proponowanego głównego układu widoku podaję poniżej.
Wskazówka:
<?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:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="92dp"
android:text="Licznik kliknięć"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="162dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="162dp"
android:text="Klikaj"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Pokaż komunikat (wersja 1)"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button1" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:text="Pokaż komunikat (wersja 2)"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button2" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="52dp"
android:text="Zamknij"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button3" />
</androidx.constraintlayout.widget.ConstraintLayout>
Aby zmiany kształtu klawiszy były widoczne w tworzonej aplikacji musimy zmienić główny typ motywu (theme) aplikacji. Przejdź do pliku res/ values/ themes/ themes.xml i zmień rodzica (parent) motywu na przykład na Theme.AppCompat.Light.NoActionBar
Wskazówka:
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<!--<style name="Base.Theme.OknoDialogoweNiestandardowe"
parent="Theme.Material3.DayNight.NoActionBar">-->
<style name="Base.Theme.OknoDialogoweNiestandardowe"
parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.OknoDialogoweNiestandardowe"
parent="Base.Theme.OknoDialogoweNiestandardowe" />
</resources>
W podanym przykładzie nazwa tworzonego motywu to Theme.OknoDialogoweNiestandardowe. Można przypisać swoją inną. Sprawdź czy w pliku manifestu ta nazwa się pojawia.
Wskazówka:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:theme="@style/Theme.OknoDialogoweNiestandardowe"
</application>
</manifest>
Funkcja licznika kliknięć i zamknięcia aplikacji
Tworzony projekt bazuje na projekcie opisanym pod tym linkiem zobacz. Musimy utworzyć zmienną do przechowywania licznika kliknięć i dwie funkcje. Jedna zwiększająca licznik, a druga zamykająca aplikację.
W głównym pliku tworzonego rozwiązania zapiszemy obie funkcje oraz zmienną. Kliknięcia podpinamy pod obsługę pierwszego klawisza. Patrz kod poniżej.
Wskazówka:
class MainActivity : AppCompatActivity() {
var licznik=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//zliczaj klikniecia
val bt1=findViewById<Button>(R.id.button1)
bt1.setOnClickListener{
licznik=Licznik(licznik)
val tv=findViewById<TextView>(R.id.textView)
tv.setText("Licznik kliknięć: "+licznik)
}
}
private fun Zamknij(){
this.finish()
System.exit(0)
}
private fun Licznik(i:Int):Int{
return i+1
}
}
Skompiluj projekt i sprawdź zliczanie klikania.
Niestandardowy kształt ramki okna dialogowego
Do projektu dodamy plik XML, w którym zdefiniujemy niestandardowy kształt ramki okna. Ramkę okna przygotujemy z zaokrąglonymi rogami, gradientowym tłem i cienka obwódką wokół ramki. W lokalizacji res/ drawable tworzymy plik zasobów
Nazwę pliku podajemy jako ksztalt_okno_dialogowe.xml, Root element ustawiamy na shape. Definiujemy właściwości ramki.
Wskazówka:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<!--rogi, prawy dolny mniejszy-->
<corners
android:radius="40dp"
android:bottomRightRadius="10dp"
/>
<!-- gradientowe tło-->
<gradient
android:angle="45"
android:centerX="200"
android:centerY="200"
android:startColor="#0189ff"
android:endColor="#01f1fa"
android:gradientRadius="100"
android:type="linear"
/>
<!--biała obwódka-->
<stroke
android:width="3dp"
android:color="#FFFFFF"
/>
</shape>
Układ widoku okna komunikatu pierwszego
Do folderu res/ layout dodajemy plik XML układu widoku okna komunikatu. W widoku osadzamy jedną kontrolkę TextView (dla treści komunikatu) i dwie kontroli typu Button.
Dla prezentowanego widoku przypisano poniższe właściwości
Wskazówka:
<?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/textView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="2dp"
android:background="@color/czerwony"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="Treść komunikatu"
android:textColor="@color/white"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="48dp"
android:layout_marginBottom="48dp"
android:text="Zamknij okno"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintVertical_bias="0.494" />
<Button
android:id="@+id/button6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="48dp"
android:text="Zakończ aplikację"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
Na tym etapie tworzenia aplikacji wszystko jest już przygotowane aby wywołać komunikat. W momencie wywoływania okna komunikatu podmieniamy w kodzie standardowy układ okna na przygotowany przez nas.
Funkcja tworzenia niestandardowego okna komunikatu
W tworzonym rozwiązaniu napiszemy jedną funkcję tworzącą niestandardowe okno komunikatu. Ta jedną funkcją obsłużymy dwie różne wersje okna komunikatu. Funkcja będzie posiadać parametr widoku układu okna komunikatu. Tworzenie okna jest dwuetapowe.
Zmienna BudowaOknaAlertu=AlertDialog.Builder() służy do ustawiania parametrów ramki i umożliwia dostęp do osadzonych kontrolek w widoku okna.
Zmienna oknoAlertu=BudowaOknaAlertu.create() pozwala skorzystać z metod pokazania okna (show) czy też programowego zamknięcia okna (cancel) jak i kilkunastu innych.
Patrz poniższy kod
Wskazówka:
private fun Komunikat(idUklad:Int){
val BudowaOknaAlertu=AlertDialog.Builder(this,R.style.UstawieniaAlertDialog)
BudowaOknaAlertu.setTitle("TYTUŁ OKNA")
val widok=layoutInflater.inflate(idUklad,null)
//ustaw niestandardowy styl układu
BudowaOknaAlertu.setView(widok)
//wyłącz zamknięcie okna przez klikniecie w dowolne miejsce ekranu
BudowaOknaAlertu.setCancelable(false)
//wyslij tekst komunikatu
val tv=widok.findViewById<TextView>(R.id.textView2)
tv.setText("Licznik kliknięć: "+licznik)
//utwórz zmodyfikowane okno dialogowe alertu
val oknoAlertu=BudowaOknaAlertu.create()
//obsłuz klawisz zamykania okna dialogowego
val btZamknijOkno=widok.findViewById<Button>(R.id.button5)
btZamknijOkno.setOnClickListener{
//zamknij tylko okno dialogowe
oknoAlertu.cancel()
}
//obsłuz klawisz zamykania całej aplikacji
val btZakonczAplikacje=widok.findViewById<Button>(R.id.button6)
btZakonczAplikacje.setOnClickListener{
Zamknij()
}
oknoAlertu.show();
}
W głównym pliku aplikacji wywołamy obsługę klawisza pokazującego okno komunikatu dla wersji pierwszej. Patrz poniższy kod
Wskazówka:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//zliczaj klikniecia
val bt1=findViewById<Button>(R.id.button1)
bt1.setOnClickListener{
licznik=Licznik(licznik)
val tv=findViewById<TextView>(R.id.textView)
tv.setText("Licznik kliknięć: "+licznik)
}
//obsługa klawisza pokaz komunikat wersja 1
val bt2=findViewById<Button>(R.id.button2)
bt2.setOnClickListener{
Komunikat(R.layout.okno_dialogowe_wersja_1)
}
}
Skompiluj program i sprawdź działanie. Aplikacja powinna wyświetlać stan licznika i reagować na wybór klawisza okna dialogowego.
Zmiana kształtu klawiszy niestandardowego okna dialogowego
Na podstawie tematu opisanego pod linkiem zobacz zmienimy kształty klawiszy okna dialogowego. Do projektu dodajemy plik XML w lokalizacji res/ drawable/ kształt_przycik_okragly.xml. Właściwości kształtu przypiszemy na owalne z gradientowym tłem i białą obwódką. Dodamy dwa stany aktywności klawisza: stan normalny i stan wciśnięty. Wciśnięcie klawisza zaakcentujemy zmianą koloru tła gradientu. Poniżej zawartość utworzonego pliku
Wskazówka:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
>
<!--okrag o promieniu 34dp-->
<corners android:radius="34dp"/>
<!-- gradientowe tło-->
<gradient
android:angle="45"
android:centerX="0"
android:centerY="0"
android:startColor="#B4EC74"
android:endColor="#15AF1B"
android:gradientRadius="100"
android:type="linear"
/>
<!--biała obwódka-->
<stroke
android:width="5dp"
android:color="#FFFFFF"
/>
</shape>
</item>
<!-- stan normalny-->
<item>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
>
<!--okrag oi promieniu 40dp-->
<corners android:radius="40dp"/>
<!-- gradientowe tło-->
<gradient
android:angle="45"
android:centerX="0"
android:centerY="0"
android:startColor="#BA0600"
android:endColor="#F6673A"
android:gradientRadius="100"
android:type="linear"
/>
<!--biała obwódka-->
<stroke
android:width="3dp"
android:color="#FFFFFF"
/>
</shape>
</item>
</selector>
Zmiana stylu klawiszy niestandardowego okna dialogowego
W wersji drugiej okna komunikatu oprócz kształtu klawisza zmienimy jego styl. Dodamy czcionkę pogrubioną z cieniowaniem. Styl możemy zdefiniować w osobnym pliku XML lub dopisać jako kolejny w pliku już istniejącym. Wybieramy drugą możliwość.
Otwieramy plik res/ values/ themes/ themes.xml i dopisujemy nowy styl o nazwie styl klawiszy. Patrz poniżej
Wskazówka:
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<!--<style name="Base.Theme.OknoDialogoweNiestandardowe"
parent="Theme.Material3.DayNight.NoActionBar">-->
<style name="Base.Theme.OknoDialogoweNiestandardowe"
parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.OknoDialogoweNiestandardowe"
parent="Base.Theme.OknoDialogoweNiestandardowe" />
<!--USTAWIENIE KSZTAŁTU OKNA DIALOGOWEGO-->
<style name="UstawieniaAlertDialog"
parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:windowBackground">
@drawable/ksztalt_okno_dialogowe
</item>
</style>
<style name="stylKlawiszy">
<item name="android:textColor">#FFFFFF</item>
<item name="android:gravity">center</item>
<item name="android:layout_margin">10dp</item>
<item name="android:textSize">25sp</item>
<item name="android:textStyle">bold</item>
<item name="android:shadowColor">#AD0A0A0A</item>
<item name="android:shadowDx">5</item>
<item name="android:shadowDy">5</item>
<item name="android:shadowRadius">2</item>
</style>
</resources>
Układ widoku drugiego okna komunikatu
Dodajemy nowy plik XML przeznaczony na układ widoku drugiego okna komunikatu. W lokalizacji tworzymy plik res/ layout/ okno_dialogowe_wersja_2.xml. Do pliku kopiujemy zawartość poprzedniego pliku widoku okna komunikatu wersji pierwszej. Przy kopiowaniu zostawiamy takie same identyfikatory jak w oknie komunikatu w wersji pierwszej. Pozostawienie tych samych identyfikatorów umożliwi wykorzystanie tej samej funkcji wywołującej okno komunikatu.
Po skopiowaniu dodaj do parametrów kontrolek Button te właściwości
Wskazówka:
android:background="@drawable/ksztalt_przycisk_okragly"
style="@style/stylKlawiszy"
Zawartość pliku okno_dialogowe_wersja_2.xml
Wskazówka:
<?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/textView2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="2dp"
android:background="@color/czerwony"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:text="Treść komunikatu"
android:textColor="@color/white"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button5"
style="@style/stylKlawiszy"
android:layout_width="164dp"
android:layout_height="162dp"
android:layout_marginStart="24dp"
android:layout_marginTop="48dp"
android:layout_marginBottom="48dp"
android:background="@drawable/ksztalt_przycisk_okragly"
android:text="Zamknij okno"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<Button
android:id="@+id/button6"
style="@style/stylKlawiszy"
android:layout_width="167dp"
android:layout_height="158dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="48dp"
android:background="@drawable/ksztalt_przycisk_okragly"
android:text="Zakończ aplikację"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
Na podglądzie widoku układu okna komunikatu w wersji drugiej pokaże się zmieniony kształt klawiszy
Dodajemy obsługę klawisza wywołującego komunikat w wersji drugiej. W głównym pliku aplikacji w metodzie onCreate() wprowadzamy kod obsługi kliknięcia w kolejny przycisk (tu button3). Zwróć uwagę, że zmieniono identyfikator okna układu komunikatu w funkcji Komunikat()
Wskazówka:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//zliczaj klikniecia
val bt1=findViewById<Button>(R.id.button1)
bt1.setOnClickListener{
licznik=Licznik(licznik)
val tv=findViewById<TextView>(R.id.textView)
tv.setText("Licznik kliknięć: "+licznik)
}
//obsługa klawisza pokaz komunikat wersja 1
val bt2=findViewById<Button>(R.id.button2)
bt2.setOnClickListener{
Komunikat(R.layout.okno_dialogowe_wersja_1)
}
//obsługa klawisza pokaz komunikat wersja 2
val bt3=findViewById<Button>(R.id.button3)
bt3.setOnClickListener{
Komunikat(R.layout.okno_dialogowe_wersja_2)
}
}
Uruchom aplikację i sprawdź zachowanie klawiszy okna komunikatu dla wersji drugiej.
Pełny kod pliku MainActivity.kt
Wskazówka:
import androidx.appcompat.app.AlertDialog
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
var licznik=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//zliczaj klikniecia
val bt1=findViewById<Button>(R.id.button1)
bt1.setOnClickListener{
licznik=Licznik(licznik)
val tv=findViewById<TextView>(R.id.textView)
tv.setText("Licznik kliknięć: "+licznik)
}
//obsługa klawisza pokaz komunikat wersja 1
val bt2=findViewById<Button>(R.id.button2)
bt2.setOnClickListener{
Komunikat(R.layout.okno_dialogowe_wersja_1)
}
//obsługa klawisza pokaz komunikat wersja 2
val bt3=findViewById<Button>(R.id.button3)
bt3.setOnClickListener{
Komunikat(R.layout.okno_dialogowe_wersja_2)
}
}
private fun Zamknij(){
this.finish()
System.exit(0)
}
private fun Licznik(i:Int):Int{
return i+1
}
private fun Komunikat(idUklad:Int){
val BudowaOknaAlertu=AlertDialog.Builder(this,R.style.UstawieniaAlertDialog)
BudowaOknaAlertu.setTitle("TYTUŁ OKNA")
val widok=layoutInflater.inflate(idUklad,null)
//ustaw niestandardowy styl układu
BudowaOknaAlertu.setView(widok)
//wyłącz zamknięcie okna przez klikniecie w dowolne miejsce ekranu
BudowaOknaAlertu.setCancelable(false)
//wyslij tekst komunikatu
val tv=widok.findViewById<TextView>(R.id.textView2)
tv.setText("Licznik kliknięć: "+licznik)
//utwórz zmodyfikowane okno dialogowe alertu
val oknoAlertu=BudowaOknaAlertu.create()
//obsłuz klawisz zamykania okna dialogowego
val btZamknijOkno=widok.findViewById<Button>(R.id.button5)
btZamknijOkno.setOnClickListener{
//zamknij tylko okno dialogowe
oknoAlertu.cancel()
}
//obsłuz klawisz zamykania całej aplikacji
val btZakonczAplikacje=widok.findViewById<Button>(R.id.button6)
btZakonczAplikacje.setOnClickListener{
Zamknij()
}
oknoAlertu.show();
}
}
Pełny kod manifestu aplikacji
Wskazówka:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OknoDialogoweNiestandardowe"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>