Fragment ile Dinamik Kullanıcı Arayüzü (UI) Oluşturmak

Fragment ile Dinamik Kullanıcı Arayüzü (UI) Oluşturmak
Android'de çok bölmeli (multi-pane) dinamik arayüzler oluşturmak istediğinizde, Activity davranışlarını ve arayüz bileşenlerini (liste, düğme vs), Activity'nize girip çıkabilen modüller halinde tutmalısınız. Bu modülleri Fragment sınıfı ile oluşturabilirsiniz. Fragment'lar, yaşam döngülerini yönetebileceğiniz, özel layout'ları tanımlanabilen iç içe geçmiş Activity'ler gibi davranır.

Fragment'lar ile ilgili bu bölümleri daha kolay öğrenebilmek için Activity yaşam döngüsü belgesini okumak yararlı olabilir.

Bir Fragment kendi layout'unu tanımladığında öteki Fragment'lar ile birlikte farklı kombinasyonlar oluşturacak şekilde bir Activity'nin içinde yapılandırılabilir. Böylece layout yapılandırmanızı farklı boyuttaki ekranlar için değiştirebilirsiniz. Örneğin küçük ekranlarda tek fragment'ın gösterilmesini veya geniş ekranlarda iki veya daha fazla fragment'ın gösterilmesini sağlayabilirsiniz.

Bu eğitim içeriğinde Fragment'lar ile nasıl dinamik bir kullanıcı deneyimi oluşturacağınızı ve farklı ekran boyutları olan cihazlarda uygulamanızın kullanıcı deneyimini nasıl iyileştireceğinizi bulacaksınız. Bu sırada Android 1.6'ya kadar eski sürümlerle çalışan cihazları da desteklemeye devam edebileceksiniz.

Fragment Oluşturmak

Fragment'ı şöyle düşünebilirsiniz: Activity'nin modüler bir bölümüdür. Kendi yaşam döngüsü vardır, kendi giriş olaylarını (input events) kendisi alır ve Activity çalışmaya devam ederken eklenip çıkarılabilir. Tıpkı farklı Activity'lerde tekrar tekrar kullanabileceğiniz bir “alt-Activity” gibi de düşünebilirsiniz.

Bu eğitim içeriğinde Fragment sınıfının nasıl genişletileceğini öğrenebilirsiniz. Bunu yaparken Android Destekleme Kütüphanesi kullanacağız ve böylece uygulamanız Android 1.6'ya kadar alt sürüm sistemlerle bile uyumlu kalacaktır.

NOT: Eğer en düşük API seviyesini uygulamanız için 11 ve yukarısı yapmayı düşünüyorsanız, Android Destekleme Kütüphanesi'ni kullanmanıza gerek kalmayacaktır. Bunun yerine o platform sürümünün kendi Fragment sınıfını ve ilişkili API'lerini kullanabilirsiniz. Sadece bu eğitim içeriğinin Destekleme Kütüphanesi'nden gelen API'leri kullanmayı esas aldığını ve bundan dolayı platformun içindeki sınıflardan farklı bazı sınıf ya da API isimleriyle karşılaşabileceğinizi unutmayın.

Bu içerikte anlatılanları yapmaya başlamadan önce projenizi Destekleme Kütüphanesi'ni kullanacak şekilde yapılandırmanız gerekiyor. Eğer daha önce Android Destekleme Kütüphanesi kullanmadıysanız v4 kütüphanesini Destekleme Kütüphanesi'nin Kurulumu belgesine bakarak projenize eklemelisiniz. Aynı yolu kullanarak Activity'lerinizde Action Bar'ı (eylem çubuğu) kullanmak için v7 appcompat kütüphanesini de ekleyebilirsiniz. Böylelikle hem Android 2.1 ve üstüyle uyumlu bir uygulamanız olur hem de Fragment API'lerini o sürümlerde bile kullanabilirsiniz.

Fragment sınıfı oluşturmak
Bir Fragment sınıfı oluşturmak için Fragment sınıfını türetin (extend) ve temel yaşam döngüsü metotlarını -tıpkı Activity sınıfı için yaptığınız gibi- uygulama gereksinimlerinize göre yeniden yazın (override).

Bir Fragment oluştururmanın önemli bir farkı, layout tanımını yapmak için onCreateView() isimli callback metodunu kullanmanız gerekmesidir. Aslında bu metot, bir Fragment'ı ayakta tutmak için gereken tek callback metodudur. Aşağıdaki kod bloğunda, kendi layout'unu tanımlayan basit bir Fragment örneği görüyorsunuz:

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.ViewGroup;

public class ArticleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
        // bu fragment'in layout'unu hazır hale getirelim
        return inflater.inflate(R.layout.article_view, container, false);
    }
}
Tıpkı bir Activity'de olduğu gibi bir Fragment da diğer yaşam döngüsü callback'lerini (Activity'ye eklenirken ya da Activity'den çıkarılırken ve Activity'nin yaşam döngüsü durumları arasında geçişler olurken gerçekleşen) gerçeklemelidir. Örneğin Activity'nin onPause() metodu çağrıldığında o Activity'deki her Fragment onPause() için bir çağrı alır.

XML kullanarak bir Fragment'i bir Activity'ye eklemek
Fragment'ların tekrar kullanılabilir yapılar olduğuna ve modüler bir UI bileşeni olduğuna daha önce değişmiştik. Bununla beraber Fragment sınıfının her örneğinin (instance) ebeveyn bir FragmentActivity ile ilişkilendirilmesi gerektiğini de belirtmemiz gerek. Bu ilişkiyi sağlamak için her Fragment'ı Activity layout'unu barındıran XML dosyası içinde tanımlayabilirsiniz.

NOT: Fragment'lar tek başına kullanılamazlar. Mutlaka onu yönetebilecek, onun durumundan anlayabilecek bir Activity'ye ihtiyaç var ki bu tür Activity'ler de API 11'den sonra SDK'ya entegre edildi ve ek olarak Android Destekleme Kütüphanesi'nin de içine konuldu.

NOT: FragmentActivity sınıfı Android Destekleme Kütüphanesi içinde sunulan özel bir Activity'dir. API 11 ve altındaki Android sürümlerde Fragment'ın yönetilebilirliğini sağlar. Eğer projenizin en düşük API (minSdkVersion) değeri 11 ve yukarısıysa, bu iş için normal Activity sınıfını kullanabilirsiniz.

Aşağıdaki kod bloğunda geniş ekranlar için düşünülmüş bir Activity layout'u var. Bu layout içinde de iki tane fragment bulunuyor. Geniş ekranlarda kullanılsın diye “large” eleyicisinin dizin adında olduğuna dikkatinizi çekeriz.

res/layout-large/news_articles.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <fragment android:name="com.example.android.fragments.HeadlinesFragment"
              android:id="@+id/headlines_fragment"
              android:layout_weight="1"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

    <fragment android:name="com.example.android.fragments.ArticleFragment"
              android:id="@+id/article_fragment"
              android:layout_weight="2"
              android:layout_width="0dp"
              android:layout_height="match_parent" />

</LinearLayout>
 

TÜYO: Farklı ekran boyutları için layout oluşturma konusunda daha fazla bilgi almak isterseniz Farklı Ekran Boyutlarını Desteklemek belgesine bakabilirsiniz.

Şimdi yukarıda tanımladığımız layout'u Activity'mize uygulayalım:

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_articles);
    }
}
Eğer v7 appcompat kütüphanesini kullanıyorsanız, Activiy'niz FragmentActivity'nin bir alt sınıfı olan ActionBarActivity'den türemelidir. (Daha fazla bilgi için Action Bar Eklemek içeriğine bakabilirsiniz)

NOT: Bir Fragment'ı Activity layout'una XML dosyası üzerinden eklediğinizde bu Fragment'ı çalışma zamanında kaldıramazsınız. Eğer Fragment'ları kullanıcı etkileşimi sırasında çıkarıp eklemeyi planlıyorsanız, bundan sonraki içerikte gösterildiği gibi Activity içinde ve Activity başladığında eklemelisiniz.
Esnek Bir Kullanıcı Arayüzü (UI) Oluşturmak
Farklı farklı ekran boyutlarını destekleyen bir uygulama tasarlarken, birçok esnekliği beraberinde getiren Fragment'ları kullanabilirsiniz. Farklı layout yapılandırmaları sayesinde kullanılabilecek ekran alanını en verimli şekilde kullanabilir ve kullanıcı deneyimini artırabilirsiniz.

Örneğin avuca sığan bir telefonda tek bölmeli bir tasarımla sadece bir Fragment göstermek isteyebilirsiniz. Aynı şekilde daha geniş ekranlı bir tablette kullanıcıya daha fazla bilgi göstermek için Fragment'ları yan yana kullanabilirsiniz.


Resim 1: Farklı boyuttaki ekranlardaki iki Fragment, aynı Activity'nin içinde farklı ayarlamalarla gösteriliyor. Geniş ekranda Fragment'lar yan yana gelebiliyorken bir avuç büyüklüğündeki telefon gibi cihazlarda tek seferde tek Fragment gösteriliyor. Böyle ufak ekranlarda olası bir kullanıcı etkileşiminde gösterilen Fragment'ı diğer Fragment ile yer değiştirmek kullanıcı deneyimi açısından iyi olacaktır.

Gelelim Fragment kullanırken olmazsa olmaz FragmentManager sınıfına. FragmentManager sınıfı, dinamik bir kullanıcı deneyimini sağlayabilesiniz diye çalışma zamanı (runtime) sırasında bir Activity'ye Fragment ekleme, çıkarma, yer değiştirme gibi işlemleri yapmanıza olanak sağlar. Şimdi ayrıntılarına bakalım.

Çalışma zamanında bir Fragment'ı Activity'ye Eklemek
Fragment'ları doğrudan <fragment> elementi şeklinde XML layout dosyaları içerisinden Activity'ye eklemenin dışında, çalışma zamanı sırasında da Activity'ye ekleyebilirsiniz. Böyle bir işleme, Fragment'ları istediğiniz zaman ekleyip çıkaracağınız zaman ihtiyaç duyarsınız.

Fragment eklemek ya da çıkarmak gibi bir işlemi (transaction) gerçekleştirmek için mutlaka FragmentManager'ı kullanarak bir FragmentTransaction oluşturmalısınız. FragmentTransaction size fragment ekleme, çıkarma, yer değiştirme ve diğer fragment işlemlerini gerçekleştirmenizi sağlayan API'ler sunar. Adı üstünde, Fragment işlemi yapmanızı sağlar.

Activity'nizin Fragment'ları çıkarabilmesini ya da yer değiştirebilmesini sağlamak istiyorsanız, Activity'nin onCreate() metodu sırasında ilk olarak istediğiniz Fragment(ları) ekleyebilirsiniz.

Fragment'lar ile çalışırken dikkat etmeniz gereken bir kural da (özellikle çalışma zamanında ekleyerek kullandığınız Fragment'lar için) Fragment'ın yer alacağı layout'un mutlaka bir taşıyıcı (container) View üzerinde olması gerektiğidir. Bir başka deyişle şöyle açıklayabiliriz: Ekleme-çıkarma yapacağınız Fragment'ları doğrudan Activity'ye bir yere eklemek istiyorsanız bunun için bir yer açmalısınız ve o yer bir View nesnesi olmalı. Aşağıdaki örneklerde bu yer bir FrameLayout olarak karşınıza çıkacak.

Aşağıdaki layout, bir önceki içerikte sözünü ettiğimiz "tek seferde tek fragment gösteren" layout'a alternatif bir layout'tur. Bir Fragment'ı diğeriyle yer değiştirmek için Activity'nin layout'unda Fragment taşıyıcısı gibi davranan boş bir FrameLayout olması gerekir.

Örneği incelemeye başlamadan önce gözünüzden kaçmasını istemediğimiz bir şey var: Bu örnekteki layout dosyalarının ismi bir önceki derstekiyle aynı ancak layout dizini "large" son ekini içermiyor. Bu layout, tek seferde iki fragment sığmayan, geniş ekrandan daha ufak ekranı olan cihazlar için tasarlanmıştır.

res/layout/news_articles.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
 

Activity'nizin içindeyken getSupportFragmentManager() metodunu kullanarak bir FragmentManager nesnesi elde etmelisiniz. Tüm bunlar Anroid Destekleme Kütüphanesi API'leri ile gerçekleştireceğimiz işlemler. Ardından beginTransaction() metodunu kullanarak bir FragmentTransaction nesnesi oluşturuyoruz ve add() metoduyla da fragment'ı FrameLayout'un olduğu yere ekliyoruz.

NOT: Eğer uygulamanız API 11 ve sonrasını hedef alıyorsa getSupportFragmentManager() metodu yerine getFragmentManager() metodunu kullanmanız yeterli.

Bu arada Activity'de birden fazla Fragment üzerinde işlem gerçekleştirmek için aynı FragmentTransaction nesnesini kullanabilirsiniz. Buradaki işleriniz bittiğinde tüm değişiklikleri kullanıcıya göstermek için commit() metodunu çağırmanız gerekir.

Şimdi yukarıda anlattıklarımızı kod bloğunda görelim. Yukarıdaki FrameLayout içine (adı fragment_container idi) bir fragment ekleyelim:

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {

   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.news_articles);
       
       // Activity'nin fragment_container isimli FrameLayout'u olan
       // bir layout'u kullandığını doğrulayalım; o an başka bir 
       // kaynak (resource) dizinindeki içinde fragment_container
       // olmayan başka bir layout kullanıyor olabilir       
       if (findViewById(R.id.fragment_container) != null) {
       
       // Eğer Activity'yi önceki durumundan (state) geri 
       // dönüştürüyorsak bir şey yapmamıza gerek yok ve 
       // doğrudan return yapabiliriz. Veyahut üst üste gelen 
       //Fragment'ların icabına bakabiliriz
           if (savedInstanceState != null) {
               return;
           }
           
        // Activity layout'unun içinde yer alacak yeni bir Fragment oluşturalım
        HeadlinesFragment firstFragment = new HeadlinesFragment();
        
        // Activity'miz Intent'ten gelen özel verilerle başlatılmış
        // olabilir. Bu nedenle (örnek olması açısından) Intent'in 
        // ekstra verilerini Fragment'a argüman olarak geçiriyoruz
        firstFragment.setArguments(getIntent().getExtras());
        
        //Fragment'ı ‘fragment_container' isimli FrameLayout'a ekliyoruz
        getSupportFragmentManager().beginTransaction()
                .add(R.id.fragment_container, firstFragment).commit();
       }
   }
}
Fragment, çalışma zamanı sırasında FrameLayout taşıyıcısına eklenebildiğinden dolayı, Activity, o Fragment'ı aynı zamanda kaldırabilir veya başka biriyle yer değiştirebilir. Şimdi bunu nasıl yapabileceğimize bakalım.

Bir Fragment'ı diğeriyle yer değiştirmek
Bir Fragment yer değiştirme işlemi aynı Fragment eklemeye benziyor. Sadece add() metoduyla değil, replace() metoduyla yapılıyor.

Şu detaya dikkatimizi yoğunlaştıralım: Fragment'lar üzerinde gerçekleştirilen ekleme-çıkarma gibi işlemleri, kullanıcının geriye dönme veya yaptığı işlemi geri alma gibi işlemlerde sıkça kullanabilirsiniz. Kullanıcının Fragment işlemleri arasında geriye dönebilmesini sağlamak için FragmentTransaction'ı commit() metoduyla çalıştırmadan önce mutlaka addToBackStack() metodunu çağırmalısınız.

NOT: Bir Fragment'ı yer değiştirdiğinizde veya kaldırdığınızda ve bu işlemi "back stack" denilen yere eklediğinizde fragment kaldırılır ve durur (yok edilmez). Kullanıcı Fragment'ı geri dönüştürerek geriye dönerse o Fragment yeniden başlatılır. Eğer işlemi "back stack"e eklemezseniz, Fragment kaldırıldığında veya yer değiştirildiğinde yok edilir.

Aşağıda bir Fragment'ı diğeriyle yer değiştirme işlemini örnekle görebilirsiniz:

// Fragment'ı oluşturalım ve göstereceği makaleyi
// (article) ona argüman olarak verelim
ArticleFragment newFragment = new ArticleFragment();

Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// fragment_container layout'u içinde o anki 
// Fragment ile istediğiniz bir fragment'ı yer 
// değiştirin ve bu işlemi back stack'e de ekleyin
// ki kullanıcı geri döndüğünde önceki Fragment'ı görebilsin
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// yapacak bir şey kalmadı. işlemi FragmentManager'a teslim ediyoruz
transaction.commit();
addToBackStack() metodu yapılan işlemi ifade eden eşsiz bir string parametresini isim olarak alır. Eğer FragmentManager.BackStackEntry API'leriyle ileri seviye Fragment işleri yapmayacaksanız işleme bir isim vermenize gerek olmayacaktır. Yukarıdaki kodda bu yüzden null parametresi geçilmiştir.
Diğer Fragmet'lar ile İletişime Geçmek
Fragment arayüz bileşenlerini tekrar tekrar kullanabilmek istiyorsanız her birini, kendi kendini taşıyabilen, kendi layout'unu ve davranışını tanımlayabilen modüler bir yapıda inşa etmelisiniz. Tekrar kullanılabilir Fragment'ları bir kere tanımladıktan sonra, onları bir Activity ile ilişkilendirmeli ve genel arayüz mantığında yerine koymak için uygulama mantığına oturtmalısınız.

Genellikle bir Fragment'ı diğeriyle iletişime sokmak isteyeceksiniz. Örneğin bir kullanıcı olayına göre diğerinin içeriğini değiştirmek isteyeceksiniz. Fragment'tan Fragment'a yapılacak tüm iletişim, ilişkili oldukları Activity üzerinden gerçekleşir. Activity, burada trafik polisi gibidir. İki Fragment onsuz asla doğrudan iletişime geçmez.

Fragment'tan mesaj göndermek
Arayüz sınıfının tanımlaması
Bir Fragment'ın bağlı olduğu Activity ile iletişime geçmesi için Fragment sınıfınızın içinde bir arayüz sınıfı tanımlayabilir ve onu da Activity içinde gerçekleyebilirsiniz. Fragment, onAttach8) yaşam döngüsü olayı boyunca bu arayüz (interface) sınıfının gerçeklemesini (implementation) yakalar ve bu arayüz sınıfının metotlarını Activity ile haberleşmek için çağırır.

Aşağıda Fragment'tan Activity'ye doğru bir iletişim örneği var:

public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // Taşıyıcı durumdaki Activity bu interface'i mutlaka implemente etmeli
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        // bununla taşıyıcı activity'nin bu callback interface'ini
        // gerçeklediğinden emin oluruz. Etmemişse hata fırlatırız
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " mutlaka OnHeadlineSelectedListener");
        }
    }
    
    ...
}
Bu kodla birlikte artık Fragment, mCallback'in onArticleSelected() metodu yardımıyla Activity'ye mesaj teslim edebilir. mCallBack, OnHeadlineSelectedListener interface'inin bir örneği (instance) oluyor.

Örneğin aşağıdaki metod, kullanıcı bir liste öğesine tıklayınca çağırılıyor. Burada Fragment, callback interface'ini (mCallback) kullanarak olayı üstteki Activity'ye iletiyor.

@Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // olayı üstteki activity'ye iletelim
        mCallback.onArticleSelected(position);
    } 

Arayüz sınıfının gerçeklemesi
Fragment'tan olaylarla ilişkili callback'leri alabilmek için ev sahibi Activity'nin Fragment sınıfı içinde tanımlanmış arayüz (interface) sınıfını gerçeklemesi gerekiyor.

Aşağıdaki örnekte gördüğünüz Activity, yukarıdaki örnekte yer alan interface'i gerçekliyor.

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(int position) {
        // kullanıcı HeadLinesFragment'tan bir yazının başlığını seçer
        // burada da yazıyı gösterecek işlemleri başlatırız
    }
}
 

Fragment'a mesaj göndermek
Ev sahibi Activity, bir örneğine (instance) findFragmentById() metoduyla eriştiği Fragment'a isterse mesaj da gönderebilir. Ardından o Fragment'ın public metotlarını doğrudan çalıştırabilir.

Örneğin, yukarıdaki örneklerde de geçtiği gibi bir Activity hayal edin. İçinde yazı başlıklarının listelendiği Fragment'tan (HeadLinesFragment) başka bir Fragment daha olsun ve seçilen başlığa göre bu Fragment'ta ilgili içerik gösterilsin. Bu Activity yukarıdaki callback metodunun döndürdüğü veriden yararlanarak ilgili içeriği diğer Fragment'ta gösterebilir. Aşağıdaki örnekte de bunu gerçekleştiriyoruz.

Activity, callback metoduyla gelen bilgiden yararlanarak bu bilgileri diğer Fragment'ta göstertiyor:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // kullanıcı HeadlinesFragment'tan bir yazının başlığını seçti
        // şimdi seçtiği başlığa göre bir gösterme işlemi yapalım

        // önce detaylı bilgiyi göstereceğimiz ArticleFragment'a erişelim
        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // eğer articleFrag kullanılabilirse iki layout'u da 
            // ekranda görebiliyoruz demektir

            // ArticleFragment'ın içideyken içeriğini güncelleyecek
            // metodu çağıralım
            articleFrag.updateArticleView(position);
        } else {
            // eğer articleFrag kullanılabilir değilse
            // tek parçalı bir layout'tayız demektir ve fragment'ları yer
            // değiştirmemiz gerekir

            // bir Fragment oluşturalım ve seçilen başlığa göre ona belli
            // argümanlar verelim
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // fragment_container view'ını bu yeni fragment ile değiştirelim
            // ve back stack'e bir işlem ekleyelim ki kullanıcı
            // geri döndüğünde daha önce gördüğü Fragment'ı görebilsin
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // işlerimizi bitirelim
            transaction.commit();
        }
    }
}
 

Yorumlar

Bu blogdaki popüler yayınlar

İç İçe Döngüler

CSS Bir Elemanın Genişliği ve Yüksekliği

JavaScript Dilinde Fonksiyon Çağırma Teknikleri