A GridLayout és a TableLayout két népszerű elrendezés az Android alkalmazásokban, amelyeket hasonló célokra használunk, mégis különböző módon működnek és eltérő előnyökkel rendelkeznek. Az alábbiakban összehasonlítjuk őket, és bemutatjuk, hogy mikor célszerű használni egyiket vagy másikat.

A GridLayout és a TableLayout között az egyik legnagyobb különbség az, hogy miként definiáljuk a sorokat és oszlopokat. Míg a TableLayout esetében a sorokat konkrétan kell megadnunk TableRow elemek segítségével, a GridLayout esetén az oszlopok és sorok száma a layout definíciójánál van meghatározva. A TableLayout minden egyes sort egy-egy TableRow-val hoz létre, és az Android automatikusan kiszámítja a legnagyobb oszlopot, amely alapján az összes oszlopot beállítja. A GridLayout esetében ezzel szemben megadjuk a columnCount és rowCount attribútumokat, hogy az elrendezés rendelkezzen egy előre meghatározott struktúrával.

A megjelenítési viselkedés is különbözik a két elrendezés között. Míg a TableLayout-ban az oszlopok bővíthetők, hogy kitöltsék a képernyőt, addig a GridLayout-ban a cellák közötti elosztás alapértelmezés szerint rugalmas, és az Android automatikusan elosztja a nézeteket a sorok és oszlopok között. Ez különösen fontos lehet dinamikus elrendezések kialakításakor, mivel a GridLayout lehetővé teszi, hogy az alkalmazás a nézetek számától függően automatikusan rendezze őket.

A GridLayout esetében azt is megadhatjuk, hogy mely cellában helyezkedjen el egy-egy nézet, és ezeket a nézeteket explicit módon is pozicionálhatjuk. Ehhez használhatjuk az android:layout_row és android:layout_column attribútumokat, amelyeket a nézetek elhelyezésekor kell beállítani. Ez lehetővé teszi, hogy precízen meghatározzuk a cella pozícióját, míg a TableLayout automatikusan a sorokban helyezi el a nézeteket.

Mindkét elrendezésnek van lehetősége az oszlopok nyújtására, hogy kitöltsék a rendelkezésre álló képernyőterületet. A TableLayout esetében a android:stretchColumns attribútumot kell használni, hogy meghatározzuk, mely oszlopokat nyújtja az elrendezés, míg a GridLayout-ban a android:layout_columnWeight attribútumot kell hozzáadni a megfelelő nézetekhez, hogy elérjük ugyanezt a hatást. Fontos, hogy minden egyes oszlop celláinál meg kell határoznunk a súlyt, különben nem fog megfelelően nyújtódni.

A két elrendezés közötti választás nem csupán a kód különbségein múlik, hanem azon is, hogy milyen típusú feladatot szeretnénk megoldani. Ha fix oszlopok és sorok elhelyezésére van szükség, akkor a TableLayout megfelelőbb lehet, mivel könnyen kezelhetők benne a sorok, és az oszlopok automatikusan alkalmazkodnak. A GridLayout ezzel szemben jobban alkalmas dinamikusabb elrendezésekhez, ahol a pozíciók manuális beállítása vagy az automatikus elosztás hasznos lehet.

Egy másik jelentős különbség, amelyet figyelembe kell venni, az a szempont, hogy a TableLayout-ban a nézetek soronként kerülnek elhelyezésre, míg a GridLayout-ban a nézetek a meghatározott oszlopok és sorok mentén helyezkednek el. Ez különösen fontos, ha az alkalmazásunkban olyan elrendezésre van szükség, ahol az egyes elemeket konkrét helyeken szeretnénk megjeleníteni.

Amennyiben a felületünket dinamikusan akarjuk építeni, a ListView vagy GridView lehet a legjobb választás, mivel ezek az elemek nem statikusak, hanem az adatokat dinamikusan töltik be. A ListView és a GridView adatvezérelt elrendezések, tehát nem kell előre meghatározni az összes nézetet, amelyek majd megjelennek. Ezzel szemben a TableLayout és GridLayout esetében minden egyes nézetet manuálisan kell hozzáadnunk.

A helyes layout kiválasztása az adott alkalmazás céljától és a kívánt megjelenítéstől függ. Fontos megérteni, hogy a különböző layout típusok más-más előnyökkel és hátrányokkal rendelkeznek, és azokat az igényekhez kell igazítani. Az Android fejlesztésben a választás nemcsak a kód komplexitását, hanem a felhasználói élményt is befolyásolja, ezért érdemes alaposan mérlegelni, hogy melyik layout a legalkalmasabb egy adott feladat végrehajtására.

Hogyan készíthetünk egy egyéni zoom animációt a képernyőn

A korábbi példa, a "Kártyaflip animáció fragmentumokkal" bemutatta, hogyan lehet átmeneti animációkat használni animációs erőforrás fájlok segítségével. Ebben a részben egy olyan zoom effektust készítünk, amelyet kódban létrehozott animációs erőforrások segítségével valósítunk meg. Az alkalmazás egy előnézeti képet mutat, majd, amikor megnyomjuk, az nagyított képpé alakul.

A projekt elkészítéséhez kövessük az alábbi lépéseket.

Először is készítsünk egy új Android projektet az Android Studio-ban, és nevezzük el a projektet a kívánt módon. Az alapértelmezett telefon és tablet beállításokat használjuk, és válasszuk az "Üres tevékenység" típust, amikor a tevékenység típust kérdezi. Az alkalmazásunkhoz szükséges képet a www.Pixabay.com oldalról töltöttük le, de bármilyen más képet is használhatunk.

Miután elkészítettük a képet, kövessük az alábbi lépéseket:

  1. Másoljuk át a képet a "res/drawable" mappába, és nevezzük el "image.jpg"-nak (ha nem JPEG formátumú, hagyjuk meg az eredeti kiterjesztést).

  2. Most nyissuk meg az activity_main.xml fájlt, és cseréljük le az ott található XML-t a következőre:

  3. Nyissuk meg a MainActivity.java fájlt, és deklaráljuk az alábbi globális változókat:

    java
    private Animator mCurrentAnimator; private ImageView mImageViewExpanded;
  4. Adjuk hozzá a loadSampledResource() metódust, amely a képek lekicsinyítésére szolgál, hogy elkerüljük a memória túlfogyasztást:

    java
    public Bitmap loadSampledResource(int imageID, int targetHeight, int targetWidth) {
    final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), imageID, options); final int originalHeight = options.outHeight;
    final int originalWidth = options.outWidth;
    int inSampleSize = 1; while ((originalHeight / (inSampleSize * 2)) > targetHeight && (originalWidth / (inSampleSize * 2)) > targetWidth) { inSampleSize *= 2; } options.inSampleSize = inSampleSize; options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(getResources(), imageID, options); }
  5. Adjuk hozzá a következő kódot az onCreate() metódushoz:

    java
    final ImageView imageViewThumbnail = findViewById(R.id.imageViewThumbnail);
    imageViewThumbnail.setImageBitmap(loadSampledResource(R.drawable.image, 100, 100)); imageViewThumbnail.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { zoomFromThumbnail((ImageView) view); } }); mImageViewExpanded = findViewById(R.id.imageViewExpanded); mImageViewExpanded.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mImageViewExpanded.setVisibility(View.GONE); mImageViewExpanded.setImageBitmap(null); imageViewThumbnail.setVisibility(View.VISIBLE); } });
  6. Adjuk hozzá a zoomFromThumbnail() metódust, amely kezeli a valódi animációt:

    java
    private void zoomFromThumbnail(final ImageView imageViewThumb) { if (mCurrentAnimator != null) { mCurrentAnimator.cancel(); }
    final Rect startBounds = new Rect();
    final Rect finalBounds = new Rect();
    final Point globalOffset = new Point();
    imageViewThumb.getGlobalVisibleRect(startBounds); findViewById(R.id.frameLayout).getGlobalVisibleRect(finalBounds, globalOffset); mImageViewExpanded.setImageBitmap(loadSampledResource(R.drawable.image, finalBounds.height(), finalBounds.width())); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y);
    float startScale; if ((float) finalBounds.width() / finalBounds.height() > (float) startBounds.width() / startBounds.height()) { startScale = (float) startBounds.height() / finalBounds.height(); float startWidth = startScale * finalBounds.width();
    float deltaWidth = (startWidth - startBounds.width()) / 2;
    startBounds.left -= deltaWidth; startBounds.right += deltaWidth; }
    else { startScale = (float) startBounds.width() / finalBounds.width(); float startHeight = startScale * finalBounds.height(); float deltaHeight = (startHeight - startBounds.height()) / 2; startBounds.top -= deltaHeight; startBounds.bottom += deltaHeight; } imageViewThumb.setVisibility(View.GONE); mImageViewExpanded.setVisibility(View.VISIBLE); mImageViewExpanded.setPivotX(0f); mImageViewExpanded.setPivotY(0f);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(ObjectAnimator.ofFloat(mImageViewExpanded, View.X, startBounds.left, finalBounds.left)) .with(ObjectAnimator.ofFloat(mImageViewExpanded, View.Y, startBounds.top, finalBounds.top)) .with(ObjectAnimator.ofFloat(mImageViewExpanded, View.SCALE_X, startScale,
    1f)) .with(ObjectAnimator.ofFloat(mImageViewExpanded, View.SCALE_Y, startScale, 1f)); animatorSet.setDuration(1000); animatorSet.setInterpolator(new DecelerateInterpolator()); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mCurrentAnimator = null; } @Override
    public void onAnimationCancel(Animator animation) {
    mCurrentAnimator =
    null; } }); animatorSet.start(); mCurrentAnimator = animatorSet; }
  7. Futtassuk az alkalmazást egy eszközön vagy emulátoron.

A működés megértéséhez először nézzük meg a használt elrendezést. Két fő részből áll: a LinearLayout-ban található előnézeti képből és a kibővített képből. A két nézet láthatóságát az alapján kezeljük, hogy a képekre kattintunk. A kiinduló előnézeti képet a loadSampledResource() metódussal állítjuk be, ahogy azt a memória kezeléséről szóló példában is láthattuk.

A zoomFromThumbnail() metódus végzi el a tényleges animációt. A kód többi része arról szól, hogy megőrizzük az animációt a mCurrentAnimator változóban, hogy szükség esetén megszakíthassuk. Az animáció előkészítése során meghatározzuk a kezdő és a végpozíciót, a képek kiinduló és végső méreteit. A méretezés a képernyőhöz igazítva történik, így biztosítva a folytonos vizuális élményt a felhasználó számára.

Az ilyen típusú animációk alapvető fontosságúak lehetnek a felhasználói élmény szempontjából, mivel a látványos, de sima vizuális átmenetek erőteljesen hozzájárulnak az alkalmazások élményszerű, interaktív jellegeihez. A megfelelő animációk segítenek abban, hogy az alkalmazás intuitív és élvezetes legyen a felhasználók számára.

Hogyan rajzoljunk formákat az OpenGL ES segítségével?

Az OpenGL ES (Open Graphics Library for Embedded Systems) egy rendkívül hatékony grafikai API, amely lehetővé teszi a fejlesztők számára, hogy 2D és 3D grafikákat rendereljenek mobileszközökön. Az OpenGL használata során a legfontosabb feladatok közé tartozik a megfelelő renderelő környezet előkészítése, a megfelelő árnyékolók (shaderek) definiálása és azok alkalmazása a különböző formák megjelenítésére. Az alábbiakban részletesen bemutatjuk, hogyan hozhatunk létre egy egyszerű háromszöget OpenGL segítségével Android alkalmazásban.

A folyamat kezdetén, a GLSurfaceView osztály kiterjesztésével egyedi OpenGL SurfaceView-t hozunk létre, amely biztosítja a felületet a grafikai műveletekhez. Az OpenGL rajzolási műveletek elvégzése egy renderelő osztály (Renderer) segítségével történik. Az OpenGL használatakor a legfontosabb dolog, amit figyelembe kell venni, az a koordinátarendszer és a háromdimenziós objektumok megjelenítésének alapvető szabályai.

Az OpenGL koordinátarendszere eltér az Android canvas objektumának koordinátarendszerétől. Az OpenGL-ben a képernyő középpontja a (0, 0, 0) koordinátán helyezkedik el, míg a négy sarkon a következő pontok találhatóak: bal felső: (-1.0, 1.0, 0), jobb felső: (1.0, 1.0, 0), bal alsó: (-1.0, -1.0, 0), jobb alsó: (1.0, -1.0, 0). A Z-tengely az irányban helyezkedik el, amely a képernyőre merőleges, és befelé illetve kifelé irányul.

Mivel a háromszög az egyik legegyszerűbb geometriai alakzat, amelyet könnyedén definiálhatunk OpenGL-ben, ezért mi is ezt használjuk kiindulási alapként. Az OpenGL-ben az objektumok gyakran háromszögekből épülnek fel, így a háromszög szálai által meghatározott síkok adják az alapot bonyolultabb formák megjelenítéséhez.

A háromszög kirajzolásához szükség van a következő elemekre: egy vertex árnyékolóra (vertex shader), amely meghatározza a háromszög alakját és pozícióját, egy fragment árnyékolóra (fragment shader), amely színezi a formát, és egy program objektumra, amely összekapcsolja a vertex és fragment árnyékolókat. Az árnyékolók az OpenGL Shading Language (GLSL) nyelven vannak megírva, és miután megírtuk őket, a kódot le kell fordítani és hozzá kell rendelni az OpenGL programhoz.

A következő lépésben a háromszöget a megfelelő vertex- és fragment-árnyékolók kódjaival és koordinátáival készítjük el. A háromszög három csúcspontját a következő koordináták határozzák meg: (0.0, 0.66, 0), (-0.5, -0.33, 0), és (0.5, -0.33, 0). A háromszög színét az OpenGL-ben a fragment shader segítségével adhatjuk meg. A kiválasztott szín az RGB színmodell alapján kerül meghatározásra, ahol a háromszög színének kódja: {0.63, 0.76, 0.22, 1.0}.

A háromszög renderelésekor az OpenGL először betölti az árnyékolókat és programot, majd a vertex adatokat ByteBuffer segítségével átadja a grafikus kártyának. Az onSurfaceCreated() és onDrawFrame() callback-ek segítségével biztosítjuk, hogy a háromszög folyamatosan megjelenjen a kijelzőn.

A következő kódok bemutatják a vertex- és fragment-árnyékolók és a háromszög definícióját:

java
private final String vertexShaderCode = "attribute vec4 vPosition;" +
"void main() {" + " gl_Position = vPosition;" + "}"; private final String fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}";

Ezek után a GLSurfaceView.Renderer osztályban hívjuk meg a draw() metódust, amely végrehajtja a háromszög megjelenítését. Az onDrawFrame() callback-nek folyamatosan frissítenie kell a képet, hogy a háromszög mindig látható legyen.

A következő lépés a háromszög megjelenítése a MainActivity.java fájlban található GLRenderer osztályban történik. A háromszög példányosítása az onSurfaceCreated() metódusban történik, majd a kirajzolása az onDrawFrame() metódusban.

Fontos megérteni, hogy az OpenGL ES-ben minden grafikai műveletet renderelő osztályokon keresztül végeznek. A shader-ek és a program objektumok alkalmazásával lehetőséget kapunk a hardveres gyorsításhoz és az optimális grafikai teljesítményhez.

A megfelelő grafikai környezet és shader-ek definiálása után a további lehetőségek széles spektruma áll előttünk. A háromszög alapformától kezdve akár bonyolultabb 3D modellek létrehozásáig is képesek vagyunk OpenGL segítségével különböző típusú geometriai objektumokat megjeleníteni. A gyakorlatban az OpenGL ES egy rendkívül rugalmas eszköz, amellyel sokféle vizuális hatás hozható létre, legyen szó interaktív alkalmazásokról, játékok fejlesztéséről vagy akár dinamikus, valós idejű vizualizációkról.

Az OpenGL ES használata során egyre inkább fontossá válik a színkezelés, az animációk és a textúrák alkalmazása. A fejlesztőnek ismernie kell a GPU (grafikus feldolgozó egység) működését és az optimalizálási technikákat, mivel ezek alapvetően befolyásolják a renderelés sebességét és minőségét. Ahhoz, hogy a felhasználói élmény tökéletes legyen, a teljes alkalmazás grafikai környezetének folyamatos finomhangolása szükséges.

Hogyan jeleníthetünk meg weboldalt alkalmazásunkban és ellenőrizhetjük a hálózati kapcsolatot Androidos környezetben?

Az Android fejlesztés során elengedhetetlen, hogy az alkalmazások megfelelően kezeljék a weboldalak megjelenítését és a hálózati kapcsolatokat. Két olyan funkcióról lesz szó, amelyek szorosan összefonódnak: a weboldalak beágyazott megjelenítése az alkalmazásban és a hálózati státusz ellenőrzése, amely segít az internetkapcsolat típusának meghatározásában (WIFI vagy mobilhálózat).

Amikor az alkalmazásban HTML tartalmat szeretnénk megjeleníteni, két választásunk van: az alapértelmezett böngészőt használhatjuk, vagy az alkalmazásunkon belül jeleníthetjük meg a weboldalt. Ha csupán a böngészőt szeretnénk elindítani, akkor egy Intent használatával egyszerűen elérhetjük a kívánt URL-t:

java
Uri uri = Uri.parse("https://www.example.com/");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);

Ha azonban szeretnénk, hogy az alkalmazásunkban jelenjen meg a weboldal, akkor a WebView komponens használata a megfelelő választás. Az alábbi lépések segítenek abban, hogy alkalmazásunkon belül, kódból jelenítsük meg a kívánt weboldalt.

Első lépésként hozzunk létre egy új Android Studio projektet, amelyet WebView névre keresztelünk. Az alapértelmezett telefon és táblagép beállítást válasszuk, és az üres aktivitást válasszuk az aktivitás típusaként.

A következő lépés az Android Manifest fájl módosítása, hogy hozzáadjuk a megfelelő engedélyeket a webes tartalom megjelenítéséhez. Ezt követően az onCreate() metódusban a következő kódot adjuk hozzá:

java
WebView webview = new WebView(this); setContentView(webview); webview.loadUrl("https://www.example.com/");

A fenti kód egyszerűen betölti az adott URL-t a WebView komponensbe, és megjeleníti a tartalmat. Azonban fontos megjegyezni, hogy ez egy alapvető megoldás, amely csak az első oldalt jeleníti meg. Ha a felhasználó linkeket kattint, az alapértelmezett böngésző fogja kezelni a kérést.

A következő lépés az, hogy az alkalmazásunkon belül maradjunk, és minden kattintott linket a WebView kezeljen. Ehhez hozzunk létre egy WebViewClient osztályt:

java
webview.setWebViewClient(new WebViewClient());

Ezáltal a felhasználó minden kattintására a WebView fog reagálni, és nem fogja elhagyni az alkalmazást.

A navigáció további finomítása érdekében, ha csak az alkalmazásunk weboldalain szeretnénk belső linkekre kattintani, akkor a következő kódot használhatjuk, amely megakadályozza a kívülről érkező URL-ek megnyitását:

java
private class mWebViewClient extends WebViewClient {
@Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (Uri.parse(url).getHost().equals("www.example.com")) { return false; // Ne változtassunk a navigáción, mivel ugyanaz a host } else { return true; // Megakadályozzuk a navigációt, mivel másik oldalra mutat } } }

Ha szeretnénk JavaScript-et engedélyezni a WebView számára, akkor azt a következő módon tehetjük meg:

java
WebSettings webSettings = webview.getSettings();
webSettings.setJavaScriptEnabled(true);

Ezen kívül a beépített nagyítást is engedélyezhetjük, hogy a felhasználók kényelmesebben böngészhessenek:

java
webSettings.setBuiltInZoomControls(true);

A WebSettings további lehetőségeit is konfigurálhatjuk, hogy még kényelmesebbé tegyük a webes élményt. A teljes dokumentációt az Android fejlesztői oldalán találhatjuk.

A webes tartalom kezelésén túl fontos, hogy alkalmazásunk képes legyen ellenőrizni az eszköz hálózati kapcsolatát. Az internetkapcsolat ellenőrzése egy gyakori feladat, amely szinte minden internetes alkalmazásban szerepel. Ezzel nemcsak a kapcsolat állapotát, hanem annak típusát is meghatározhatjuk: WIFI vagy mobil adatkapcsolat.

Ehhez először hozzá kell adnunk a szükséges engedélyeket a AndroidManifest.xml fájlban. Ezt követően egy egyszerű elrendezést készítünk, amely tartalmaz egy gombot és egy szövegdobozt a státusz megjelenítéséhez. A következő kódot alkalmazzuk az online állapot ellenőrzésére:

java
private boolean isOnline() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); }

Ez a metódus ellenőrzi, hogy az eszköz csatlakozik-e az internethez. Ha csatlakozunk, akkor a kapcsolat típusát a következő módon jeleníthetjük meg:

java
public void checkStatus(View view) {
TextView textView = (TextView) findViewById(R.id.textView); if (isOnline()) { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); textView.setText(networkInfo.getTypeName()); } else { textView.setText("Offline"); } }

Ez a kód segít megjeleníteni a kapcsolat típusát, például WIFI vagy mobil adatkapcsolat. A ConnectivityManager osztály segítségével további típusokat is lekérdezhetünk, mint például Ethernet, WIMAX vagy Bluetooth kapcsolatokat.

Ha az alkalmazásunknak reagálnia kell a hálózati állapotváltozásokra, akkor a ConnectivityManager segítségével regisztrálhatunk egy eseményfigyelőt, amely a hálózati állapot változásakor értesíti az alkalmazást. Ehhez azonban figyelni kell arra, hogy ne pazaroljunk túl sok erőforrást, mivel a folyamatos figyelés jelentősen megnövelheti az akkumulátorhasználatot.