8. lecke Szótár (1)

Online Android szakkör (DKRMG)


Egy kis bevezetés

Az első hét lecke elkészítése után nem túlzás azt mondani, hogy sikeresen vetted az Android programozás talán legnagyobb akadályait. Feltelepítetted az Android Studiot, megírtad az első önálló alkalmazásaidat, saját Activityket hoztál létre, használatba vetted a rezgőmotort és három különböző érzékelőt is. Ehhez meg kellett tanulnod a layout szerkesztő használatát és a Java nyelv alapjait. Gratulálunk!

Az új év (eredetileg januári lecke) viszont most új kihívásokat tartogat. Ahhoz, hogy tényleg otthonosan tudjatok mozogni az androidos alkalmazás fejlesztés világában, kicsit komolyabb programok felé fordítjuk a figyelmünket.

Az egyik fontos változás, hogy nem fogunk minden leckében új programot készíteni; például a mostani leckével egy három leckén átívelő projektbe kezdünk. Ami viszont reményeink szerint nem fog változni: minden lecke végén egy használható alkalmazás lesz a kezetekben, legfeljebb nem lesz teljesen készen.

Tehát akkor a tervezett program

Az első nagyobb projektben szeretnénk valami olyan programot alkotni, ami hasznosnak bizonyulhat akár a mindennapokban is. Az angolórák/nyelvórák pedig elég fontos részét képezik a mindennapjainknak; ezen belül pedig mi egészen pontosan a szavak megtanulására fogunk összpontosítani.

A mostani leckénk célja nem lesz más, mint egy egyszerű szótárfüzet program megírása. Az applikáció készítése közben megismerkedünk majd a listákkal és azok megjelenítésével, négy Activityvel zsonglőrködünk egyszerre, ezáltal tényleg megtapasztalva az igazi Android fejlesztés nehézségeit. A végeredmény viszont egy teljes, használható applikáció lesz (puskázó appal kombinálva kéretik tanóra keretein belül nem használni!)

A program részei

Az általunk tervezett program négy Activityből fog állni. Ennek a gyorsan rajzolt (wireframe) terveit itt láthatjátok. Javasoljuk, hogy ismerkedj meg az ábrán szereplő ablakokkal, és vagy másold le, vagy térj vissza ide minden alkalommal, mielőtt munkához látsz. A kép kattintással nagyítható

Ebben a leckében a menü és az About ablakok elkészítése mellett szeretnénk letenni a SzavakActivity alapjait a szavak kilistázásával. A következő két leckében ezt fogjuk tovább finomítani, illetve megírjuk a KvizActivity ablakot is.

Honnan jönnek a szavak?

Egy szótárfüzet programnál jogosan merül fel a kérdés: honnan jönnek a szavak? Ebben a leckében a szavakat a program készítésekor kell majd beírnunk (például egy tömbbe), részleteket lásd később.

A folytatásban viszont már fájlokat fogunk használni, amiket akár számítógépen is szerkeszthettek, illetve egymással vagy akár osztálytársakkal is megoszthattok. Egy minta fájl különböző nehézségű szavakkal. Ennek kapcsán javasolnánk, hogy mindenki olyan szavakat írjon be (mind a programba, mind a fájlokba), amelyek az órákon szükséges nyelvi tudását tükrözik. Így hasznosabb lesz a program, mint ha a mi fantáziátlan példáinkat használjátok.

Új projekt létrehozása

Az előző leckéhez hasonlóan most sem készítettünk elő projektet, így azt Nektek kell létrehozni. Kicsit lusták is vagyunk, és nem szeretjük ismételni magunkat, ezért nem írnánk le megint az új projekt létrehozásának menetét, kövessétek az előző leckében leírtakat! Az "Importálás helyett…" rész mind a nyolc pontját figyelmesen csináljátok végig! Amire figyelj:

Alkalmazás adatai

Ne felejtsd el beállítani a megfelelő SDK verziót, majd futtasd a programot a telefonodon!

Ismétlés: AboutActivity

Ismétlés a tudás anyja, mondták már sokan, és milyen igazuk van! Ismételjünk most mi is egy kicsit. A 6. lecke (Iránytű) végén létrehoztunk egy második Activityt, ami a készítő nevét tartalmazta. Csináljunk egy ilyen About Activityt ehhez az alkalmazáshoz is. Annak örülnénk a legjobban, ha minél több eszetekbe jutna, de persze ha elakadtok szabad puskázni a 6. lecke leírásából.

A lépések nagy vonalaiban:

Ha nem emlékszel arra, hogy miként tudod megnyitni az új Activityt, akkor puskázhatsz a gyorssegély lapról.

Ehhez a feladathoz tartozik egy szorgalmi is, amiben a gombot felváltja egy felbukkanó menü. Az érdekesség kedvéért javasoljuk, hogy a következő lecke előtt nézd majd meg!

Még egy activity: SzavakActivity

Első megközelítés

Szuper, most már van egy "névjegy" activitynk (AboutActivity) és egy kiinduló menü activitynk, ahonnan az alkalmazás funkciói lesznek elérhetőek. Ideje elkészítenünk az első funkciót, a szavak kilistázását! A cél tehát: egy Activity, ami megjeleníti a program által ismert szavak listáját.

Üres Activity létrehozása, megnyitása

A fentiek mintájára adjunk hozzá a projekthez még egy activityt (puska itt) SzavakActivity néven, aminek a típusa legyen továbbra is Empty Activity! Figyelj rá, hogy itt is írd át az ActionBarActivityt sima Activityre!

Most, hogy már többször is beírtátok/bemásoltátok az új activityt megnyitó kódot, nézzük meg kicsit közelebbről ezt a két sort!

public void aboutOnClick(View v) {
    Intent intent = new Intent(this, ValamiActivity.class);
    startActivity(intent);
}

Az első sorban létrehozott Intent típusú változónk tárolja, hogy melyik Activityt szeretnénk megnyitni. Írd át úgy a parancsot, hogy SzavakActvityt nyissa meg!


Intent intent = new Intent(this, SzavakActvity.class);
startActivity(intent);

Szavak tárolása

Futólagos ismeretséget köthettünk már az előző leckék során a String típussal. Emlékeztetésként: ez egy olyan típus, amely szövegeket tárol. Például nézzük meg az alábbi változót:

String szoveg;
szoveg = "kutya";

A String típus azonban ennél sokkal többre is képes. A szöveg tartalmazhat szóközöket, illetve akár (végre valahára) ékezetes karaktereket is! Például:

String szoveg2;
szoveg2 = "Ez egy szép kis árvíztűrő tükörfúrógép";

Innen jön az ötlet: egy magyar-angol szópárt könnyedén eltárolhatunk egy String típusú változóban:

szo1 = "kutya = dog";
szo2 = "macska = cat";

Azonban a mi programunk remélhetőleg rengeteg szót tárol majd, és mi nem igazán szeretnénk minden szóhoz új változót létrehozni. A megoldás, hogy az előző leckében tanult tömböket alkalmazzuk. Tehát:

A szavainkat egyelőre egy String típusú tömbben fogjuk tárolni. Emlékeztetésként, a String tömb egy olyan adatszerkezet, amelynek minden eleme egy String típusú változó.

A fentiek alapján akárhány Stringet (szöveget) megadhatunk kettősidézőjelek (") között, vesszővel elválasztva. Például:


szavak = new String[] { "kutya = dog", "macska = cat", "ló = horse", "tehén = cow", "állat = animal",
                        "iskola = school", "osztály = class", "osztályterem = classroom", "ceruza = pencil",
                        "vonalzó = ruler", "gondol = think", "érez = feel", "szeret = like", "ad = give",
                        "kap = get", "ékesszólás = eloquence", "kétkedő = incredulous", "egyszerű = trivial",
                        "elkeseredés = exasperation", "győzedelmes = triumphant" };

Vedd elő a saját szótárfüzetedet, és írj is be néhány olyan szót, amit Neked kell megtanulnod!

Egy String tömb megjelenítése

Az eddigi programjaink során több layout komponenssel is találkoztunk már. Rengeteg gombot és feliratot hoztunk létre, és a képeket tároló ImageView sem teljesen ismeretlen számunkra. Ezúttal azonban egy olyan összetett komponensre lesz szükségünk, ami szavak/szövegek egy egész listáját tudja megjeleníteni. Ehhez fogjuk használni a ListView vezérlőt.

A most bemutatásra kerülő ListView egy elég különös, univerzális elem, ami kicsit másképp viselkedik, mint a korábbi komponenseink. Ügyelnünk kell arra, hogy most nem elég csak a layout szerkesztőben kattintgatni ahhoz, hogy String tömbünk elemei megjelenjenek a listában. A Java kódba is kicsit jobban bele kell másznunk.

Layout kialakítása

Kezdésként helyezzünk el egy ListView komponenst a layout szerkesztőben (Containers kategória alatt). Az Activity legfontosabb feladata a szavak megjelenítése, tehát méretezzük úgy a listánkat, hogy a képernyő nagy részét elfoglalja.

Láthatjuk, hogy a layout szerkesztőnknek fogalma sincs, hogy mit szeretnénk a listánkban megjeleníteni, így jobb híján az "item 1", "item 2", … szavakat használja. Ezeket fogjuk lecserélni a saját szavainkra.

Mielőtt megnézzük a String tömb megjelenítéséhez szükséges mágikus parancsot, hozzunk létre egy ListView típusú változót a SzavakActivityben, mondjuk szolista néven, és rendeljük hozzá a ListView komponensünkhöz (ilyet már sokszor csináltunk a findViewById paranccsal)! Ugye adtál id-t a ListView komponensnek?

Tömb megjelenítése a ListView komponensen (Adapter)

Tehát jelenleg a szavaink egy String tömbben laknak, és ezeket szeretnénk megjeleníteni a ListView komponensünkön. Nézzük meg az alábbi két sort, majd írjuk be az onCreate függvényünk végére.

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, szavak);
szolista.setAdapter(adapter);

Nem kell teljességgel értenünk a fentieket; így is láthatjuk, hogy létrehoztunk egy átalakítót (adaptert), ami segít a tömbünket a szolista (ListView) számára megemészthető formába hozni. Egészen pontosan létrehoztunk egy úgynevezett ArrayAdapter objektumot, ami szól a ListView változónknak: itt vannak az új adatok a szavak tömbben, jelenítsd meg őket!

Ha esetleg kicsit megkavarodtál, most még gyorsan ellenőrizheted, hogy hogyan kéne az onCreate függvénynek kinéznie:


            

Futtassuk le a programot. Ha minden igaz, akkor a Menüből a megfelelő gombra kattintva eljutunk a Szavak activityre, ahol ott sorakoznak a kódban megadott szavak.

Szavak tárolása

Kicsit jobb megközelítés

A programunk ugyan szépen működik, gyönyörűen kilistázza a String értékeket. Ha csak ennyit szerettünk volna, akkor most kényelmesen hátradőlhetnénk, elfogyasztva a karácsonyról maradt bejglit (ha még nem száradt ki teljesen).

Ahhoz azonban, hogy továbbhaladjunk, egy kicsit sajnos át kell alakítanunk, hogy miként tároljuk a szavakat.

Emlékeztetésként: egy szópárt (magyar-angol) egy String-ben került tárolásra "magyar = angol" formában. Tehát például:

"küklopsz = cyclops", "szem = eye", stb.

Ez egy igen rugalmatlan módszer. Néhány példa, hogy miért nem szeretnénk így tárolni a szópárokat:

A fenti problémára több megoldás is létezik. Korábbi programozási nyelveknél előfordult, hogy például az angol szavakat és a magyar szavakat külön-külön String tömbben tárolták. Mi ehelyett egy Java programozók által kedveltebb megoldást választunk.

A lecke folytatásában betekintés szintjén megismerkedünk a Java nyelvben méltán népszerű osztály fogalmával. Itt előre szeretnénk leszögezni, hogy ez a téma túl nagy egy ilyen leckéhez (vagy akár szakkörhöz); a lecke végén a célunk az lenne, hogy mindenki megértse magát az alap ötletet, és lássa, hogy miként alkalmazzuk mindezt a szavaink tárolásához. Ne aggódj, ha nem értesz minden részletet. Emellett szeretnénk megjegezni, hogy Lazarus nyelvből a class vagy record adatszerkezetek ismerete óriási előnyt jelenthet.

Osztály

Az osztály egy igen érdekes adatszerkezet, ami segítségével létrehozhatjuk a saját változó típusainkat a Java nyelvben. Egészen pontosan amikor megírunk egy osztályt, akkor azzal felvázoljuk

Példaként megnézhetjük a Haromszog osztályt, ami tartalmazza egy háromszög adatait, és felvázolja, hogy milyen "cselekvéseket" végezhetünk egy háromszöggel.

Fontos, hogy mi ezzel egy tervrajzot készítünk csak, azt mondjuk el a Java nyelvnek, hogy egy háromszög típusú változó milyen adatokat tároljon. Nem egy bizonyos háromszögről van szó! Viszont innentől kezdve akárhány Haromszog típusú változót létre tudnánk hozni, amelyek mind a külön-külön a megfelelő adatokat tárolnák, és ki tudnák számolni például a saját kerületüket.

A háromszög osztályt itt és most nem írjuk meg (bár javasoljuk, hogy az érdeklődő kedvűek olvassanak utána a Java osztályoknak és írják meg az osztály kódját). Ehelyett inkább nézzük meg, hogy miként festene egy osztály, ami egy szót tartalmaz, különböző nyelveken!

Tehát a két kérdés, amit fel kell tennünk magunknak:

A válaszokat most mi adjuk meg:

Vágjunk is bele. Hozz létre egy új osztály Szo néven!

Most pedig másold be az alábbi kódot a package... sor alá, felülírva az Android Studio által írt sorokat:

public class Szo {
    public String magyar;
    public String angol;

    @Override
    public String toString() {
        String sor = magyar + " = " + angol;
        return sor;
    }

    public Szo(String magyar, String angol) {
        this.magyar = magyar;
        this.angol = angol;
    }

    public Szo() {}
}

                    

Kattints rá balra a zöld keretes részekre, hogy megtudd, mire valók!

Ez az új osztályunk neve: Szo. Figyeld meg, hogy az osztály neve megyegyezik a Java fájléval (Szo.java)

A Szo típusunknak két adatot kell tárolnia: a szó angol és magyar formáját. Ezek a Szo osztály belső változói. De ilyesmit mintha már láttunk volna! Igen, ez pontosan olyan, mint amit az Activity elején szoktunk csinálni amikor létrehozunk egy változót valamelyik komponensnek vagy mint a 3. leckében a tippelt számnak. Tehát az angol és a magyar egy-egy változó, mégpedig String típusú. És mivel az osztályunkon belül találhatóak, ezért belső változónak fogjuk hívni őket.
A public szócska felelős azért, hogy írhassunk ilyet: szo1.magyar

Ez a belső függvény való arra, hogy egy Szo típusú változót majd szöveggé tudjunk alakítani. Nézd meg, hogy ez a belső függvény hogyan használja a belső változókat! Ez is ismerős lehet egyébként, egy függvény, mégpedig egy belső függvény, hiszen ez is az osztályon belül található. Látszik rajta, hogy a neve toString. Java nyelven ez a toString függvény használható arra, hogy egy osztályt szöveggé alakítsunk.

Ez is egy függvény, ami egészen pontosan abban segít, hogy létre tudjunk hozni új Szo típusú változókat. Tehát amikor azt írod, hogy new Szo("macska", "cat"), akkor ez a függvény fut le. Az ilyen belső függvényeket konstruktornak hívjuk. Egyelőre ne fájjon miatta a fejed túlzottan.

Ez is egy konstruktor, vele sem kell egyelőre foglalkozni. Azért hagytuk itt, hogy segítsen később a munkában.

Fontos!

Amint már utaltunk rá, az osztály, amit a fenti kóddal adunk meg, az csak egy tervrajz! Megmondja, hogy hogyan kell kinéznie egy Szo-nak. Ezzel csak egy új típust hoztunk létre. Ahhoz, hogy igazán használni tudjuk, el kell kezdenünk Szo típusú változókat gyártani. Ez a következő kóddal tehetjük meg:

Szo szo1;      // változó deklarálása
szo1 = new Szo();   // új, üres szó létrehozása

Ilyenkor létrejön az új változónk. Ha be szeretnénk írni a szo1 változó magyar és angol megfelelőit, akkor azt könnyedén megtehetjük:

szo1.magyar = "kutya";
szo1.angol = "dog";

Láthatjuk, hogy a szo1 mögé helyezett ponttal a szo1 változónk belső változóinak adhatunk értéket (vagy olvashatjuk ki őket).

Mi most még ezen felül csalhatunk is egy kicsit, hogy ne kelljen három sort gépelni, beírhatjuk az alábbit is:

Szo szo1;                  // változó deklarálása
szo1 = new Szo("kutya", "dog"); // új szó létrehozása

Vagy ha még messzebb akarunk menni, akkor mindez egy sorba is leírható (a konstruktor miatt)!

Szo szo1 = new Szo("kutya", "dog"); // változó deklarálása ÉS új szó létrehozása

Belső függvény

Láthattuk, hogy a szo1 változó mögé helyezett ponttal a szo1 változóban lévő Szo típusú objektum belső változóihoz nyertünk hozzáférést (pl. szo1.angol, szo1.magyar). Ehhez hasonlatosan nem nehéz kitalálni, hogy a szo1.toString() parancs meghívja a szo1 változóban lévő Szo típusú objektum toString belső függvényét. Ha megnézed a Szo osztály kódját, akkor láthatod, hogy az az alábbi Stringet fogja nekünk visszaadni: "kutya = dog". Ha létrehozunk egy macska változót is (macska =new Szo("macska", "cat");), akkor a macska.toString() parancs pedig nem túl meglepő módon a "macska = cat" Stringet fogja visszaadni. Eddig nem foglalkoztunk mélyrehatóan a függvények működésével, így nem probléma, ha ezt nem látod át teljesen.

Tömb átalakítása saját objektumossá

A szólistánkat eddig egy tömbben tároltuk. Egy olyan tömbben, ami egyszerű Stringekből állt. Talán nem túl meglepő lépés, hogy most szeretnénk ezt a tömbünket kicsit átalakítani, és egy olyan tömböt használni inkább, amelynek minden eleme Szo típusú.

Ha visszaemlékeztek, a lecke elején csináltunk egy tömböt, ami String-eket tudott tárolni, és ezeket jelenítettük meg a ListView-ban. Ezt a String tömböt így deklaráltuk az Activity kódjának elején.

String[] szavak;

Figyeld meg, hogy mi mit jelent/jelez! Először leírjuk, hogy milyen típusú objektumokat szeretnénk belerakni, majd a [] karakterekkel megmondjuk, hogy nem egyet, hanem többet szeretnénk tárolni a változóban. Utána már csak a változó nevét kell megadni (itt pl. "szavak"). Eddig egy változóba csak egy objektum fért, de ha a []ket is használjuk, akkor már több is belefér egymás mellé. Ilyenkor tömb-nek hívjuk.

De mi azt szeretnénk, hogy ne String-eket, hanem Szo objektumokat tároljon a tömbünk. Ezt hogyan tudnád elérni?

Szo[] szavak;

Ha a tömbünk típusát átírtuk, akkor a benne lakó adatokat is meg kell változtatni egy csöppet. Az Android Studio most aláhúzza azt a sort, ahol a "macska = cat", "kutya = dog", "molylepke = moth" szópárokat bepakoltuk a tömbbe. Mindezt teljesen jogosan teszi, hiszen a szavak tömbbe most már nem Stringeket, hanem Szo típusú változókat kéne tenni.

Már csak annyi feladatunk van, hogy az adaptert is átállítsuk, hogy ne String-eket használjon, hanem Szo típusú változókat. Ehhez keresd meg azt a részt, ahova ezt beírtad (onCreate):

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, szavak);

És ahol String-et látsz, írd át Szo-ra (2 helyen van, ezeket kiemeltük)!

Ezzel készen is vagyunk, ki lehet próbálni a programot! Ha mindent jól csináltál, akkor ugyan azt kell látnod, mint amikor String-ek voltak a tömbben! Mindez annak köszönhető, hogy a ListView (egészen pontosan az adapter) a Szo típusunk kiírásához rögtön a toString() függvény segítéségét kéri. Tehát a ListView minden egyes Szo-nak meghívja a saját toString-jét. És mivel mi úgy írtuk meg a Szo toString függvényét, hogy "angol = magyar" formába alakítsa szöveggé a Szo-t pontosan ezt is fogjuk látni a listánkban.

És hogy mégis miért szenvedtünk ennyit az osztályunk megírásával?

1. Feladat

Változtasd meg úgy a Szo osztály toString() belső függvényét, hogy a szavak közé egyenlőségjel helyett kötőjelet tegyen inkább!

Futtasd a programot! Minden szónál kötőjel jelenik most már meg?

String sor =  magyar + " = " + angol;
Láthatod, hogy a magyar szót, az = jelet és az angol szót hogyan fűzzük egymás után a + jelekkel. Nincs más dolgod, mint lecserélni itt az egyenlőségjelet egy kötőjelre!

2. Feladat

Változtasd meg úgy a Szo osztály toString() belső függvényét, hogy a nyelveket angol-magyar sorrendben jelenítse meg!

String sor = angol + " - " + magyar;

Amikor végeztél, töltsd fel a projektedet a szakkör feltöltő oldalán keresztül. Ha elakadtál, vagy kérdésed támad a fentiekkel kapcsolatban, nyugodtan írd meg emailben dkrmg.android@gmail.com

Szorgalmi

1*. Harmadik nyelv hozzáadása

Fentebb említettük, hogy például azért jó, hogy bevezettük az új osztályt, mert könnyebb új nyelvet hozzáadni. Csináljuk is meg! Válassz ki egy tetszőleges nyelvet (mi a németet választottuk), és gondolkozz el, hogy miket kéne csinálni ahhoz, hogy egy Szo objektum a harmadik nyelven is tudja tárolni a szó jelentését! Érdemes lehet visszamenni az interaktív részhez. Ha végiggondoltad, , hogy megjelenjen a megoldás!