1. Android programlama öğrenimi
Belgenin PDF dosyası, |BURADA| adresinde mevcuttur.
Belgedeki örnekler |BURADA| adresinde mevcuttur.
1.1. Introduction
1.1.1. İçindekiler
Bu belge, mevcut birkaç belgenin yeniden yazılmış halidir:
- Örneklerle Android Tablet Programlamaya Giriş;
- Android Tablet ile Arduino'yu Kontrol Etme;
- Örneklerle Android Tablet Programlamaya Giriş - sürüm 2
ve aşağıdaki yenilikleri içermektedir:
- 1. belgede, bir Android uygulamasında asenkron programlamayı kolaylaştırmak için AVAT (Etkinlik-Görünümler-Eylemler-Görevler) adlı bir mimari sunulmuştu. Bu belgede, asenkron eylemleri yönetmek için RxJava standart kütüphanesi kullanılmaktadır;
- 2. belgede, bir Android eklentisiyle birlikte IDE Eclipse kullanılıyordu. Bu belgede ise Android Studio kullanılmaktadır;
- 3. belge aynen korunmuştur;
- 4. belgede, [Android Annotations] (AA) kütüphanesi, IDE IntelliJ IDEA Community Edition ile birlikte kullanılmıştı. Bu belge, 4. belgenin tamamını aşağıdaki farklılıklarla birlikte içermektedir:
- IDE artık Android Studio'dur;
- tüm istemci veya sunucu projeleri için derleme sistemi Gradle’dır (4 numaralı belgede bazen Maven kullanılıyordu);
- asenkron programlama, RxJava kütüphanesi ile gerçekleştirilmektedir (4 numaralı belgede AA kütüphanesi kullanılıyordu);
- bu belge, önceki belgelerde hiç veya çok az ele alınan alanları incelemektedir:
- parça komşuluğu kavramı;
- etkinliğin ve fragmanlarının yedeklenmesi / geri yüklenmesi;
- parçaların yaşam döngüsü;
Son olarak, bir web hizmeti / jSON ile iletişim kuran bir Android istemcisinin iskeletini sunar; bu iskelette, bu tür istemcilerde sıklıkla rastlanan çok sayıda öğe faktörlere ayrılmıştır. Bu iskelet, 2. bölümden itibaren tüm örneklerde kullanılmaktadır. Bu, belgenin gerçekten yenilikçi kısmıdır.
Aşağıdaki örnekler sunulmaktadır:
Tür | |
Mevcut bir Android projesinin içe aktarılması | |
Temel bir Android projesi | |
Temel bir [Android Annotations] projesi | |
Görünümler ve olaylar | |
Görünümler arasında gezinme | |
Sekmelerle gezinme | |
Gradle ile [Android Annotations] kütüphanesinin kullanımı | |
Android uygulamasında parçaların yönetimi | |
Görünümler Arasında Gezinme: Yeniden Ele Alınmış Yaklaşım | |
İki katmanlı mimari | |
İstemci/sunucu mimarisi | |
RxJava ile asenkronizasyonu yönetme | |
Veri Giriş Bileşenleri | |
Görünüm şablonunun kullanılması | |
ListView bileşeni | |
Menü kullanma | |
Parçalar için üst sınıf kullanma | |
Etkinlik ve parçaların durumunu kaydetme ve geri yükleme | |
Hava durumu istemcisi | |
Bir web hizmeti ile iletişim kuran bir Android istemcisinin iskeleti / jSON. Burada, bu tür Android istemcilerinde sıklıkla rastlanan çok sayıda öğe faktörlere ayrılmıştır. | |
Bir muayenehanenin randevu yönetimi | |
Uygulama alıştırması - Temel maaş yönetimi | |
Uygulama alıştırması - Arduino kartlarının kontrolü |
Bu belge, Angers Üniversitesi'ne bağlı IstiA mühendislik okulunun son sınıfında kullanılmıştır [istia.univ-angers.fr]. Bu durum, metnin yer yer biraz farklı bir üslup sergilemesini açıklamaktadır. İki uygulama alıştırması da TP'ten alınmış metinlerdir ve çözümün sadece ana hatları verilmiştir. Çözümün tamamı okuyucu tarafından oluşturulmalıdır.
Örneklerin kaynak kodu |BURADA| adresinde mevcuttur. Bu örnekleri çalıştırmak için 6.12. paragrafındaki prosedürü izlemeniz gerekir.
Bu belge, Android programlama konusunda temel bir eğitim belgesidir. Kapsamlı bir kaynak olması amaçlanmamıştır. Esas olarak yeni başlayanlara yöneliktir.
Android programlama için referans site URL [http://developer.android.com/guide/components/index.html] adresindedir. Android programlamaya genel bir bakış elde etmek için bu siteyi ziyaret etmelisiniz.
1.1.2. Ön Koşullar
Bu belgeden en iyi şekilde yararlanabilmek için Java diline hakim olmanız gerekmektedir.
1.1.3. Kullanılan araçlar
Aşağıdaki örnekler şu ortamda test edilmiştir:
- 64 bit Windows 10 Pro işletim sistemli bilgisayar;
- JDK 1.8;
- Android SDK API 23;
- Android Studio, sürüm 2.1;
- Genymotion Emülatörü, sürüm 2.6.0;
Bu belgeyi takip etmek için şunları yüklemeniz gerekir:
- bir JDK (bkz. paragraf 6.8);
- Genymotion Android emülatör yöneticisi (bkz. paragraf 6.9);
- Maven bağımlılık yöneticisi (bkz. paragraf 6.10);
- IDE ve [Android Studio] (bkz. paragraf 6.11);
1.2. Örnek-01: Bir Android örneğinin içe aktarılması
1.2.1. Proje oluşturma
Android Studio ile ilk Android projemizi oluşturalım. Öncelikle, tüm projelerimizin yerleştirileceği boş bir [exemples] klasörü oluşturalım:
![]() |
ardından Android Studio ile bir proje oluşturalım. Öncelikle, IDE [1-5] ile birlikte gelen örneklerden birini içe aktaracağız:
![]() |

![]() | ![]() |
Projenin içe aktarılması, projenin oluşturulduğu ortam ile burada yürütülmesi için kullanılan ortam arasındaki uyumsuzluktan kaynaklanan hatalara yol açabilir. Bu, bu tür hataların nasıl çözüleceğini görmek için bir fırsattır. Burada şu hata ile karşılaşıyoruz:
![]() | ![]() |
İçe aktarılan proje, aşağıdaki [build.gradle] [2] dosyasıyla yapılandırılmıştır:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
}
}
apply plugin: 'com.android.application'
repositories {
jcenter()
}
dependencies {
compile "com.android.support:support-v4:23.3.0"
compile "com.android.support:support-v13:23.3.0"
compile "com.android.support:cardview-v7:23.3.0"
}
// Örnek derlemede,
// klişe ve ortak kodları
// ana örnek koddan ayırmak için birden fazla dizin kullanır;
List<String> dirs = [
'main', // ilginç kısımlar için buraya bakın.
'common', // birden fazla örnek tarafından yeniden kullanılan bileşenler
'template'] // örnek şablon işlemi tarafından oluşturulan standart kod
android {
compileSdkVersion 21
buildToolsVersion "23.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 21
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
sourceSets {
main {
dirs.each { dir ->
java.srcDirs "src/${dir}/java"
res.srcDirs "src/${dir}/res"
}
}
androidTest.setRoot('tests')
androidTest.java.srcDirs = ['tests/src']
}
aaptOptions {
noCompress "pdf"
}
}
- Bildirilen hata 31, 34-35. satırlardan kaynaklanmaktadır: SDK 21 sürümü elimizde bulunmamaktadır. Bu sürümü, elimizde bulunan 23 sürümüyle değiştiriyoruz.
[build.gradle] dosyasında, Android Studio aşağıdaki gibi önerilerde bulunuyor:
![]() |
Önerileri kabul etmek için, öneri üzerinde [alt-entrée] komutunu uyguluyoruz:
![]() |
Ayrıca Gradle sürümüyle ilgili bir hata da alabilirsiniz:
![]() |
Bu hata, projenin [build.gradle] dosyasında istenen Gradle sürümüyle (aşağıdaki 6. satırda belirtilen 2.10) uyuşmamasından kaynaklanmaktadır:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
}
}
ve [<projet>/gradle/wrapper/gradle-wrapper.properties] dosyasında yer alan:
#10 Nisan Çarşamba 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
Yukarıdaki 6. satırda, 2.8 değerini 2.10 ile değiştirmelisiniz.
[<projet>/gradle/wrapper/gradle-wrapper.properties] dosyasına erişmek için projenin proje görünümünü kullanmanız gerekir:
![]() | ![]() | ![]() |
Bu düzeltme yapıldıktan sonra, [1] uygulamasını derleyebilir, Genymotion [2] emülatörünü başlatabilir ve ardından [3] projesini çalıştırabilirsiniz:
![]() | ![]() | ![]() |
![]() |

Uygulamayı durduralım:
![]() |
Artık projeyi kapatabiliriz. Yeni bir proje oluşturacağız.
![]() |
1.2.2. IDE hakkında birkaç nokta
1.2.2.1. Perspektifler
IDE Android Studio (AS), bir proje üzerinde çalışmak için çeşitli görünümler sunar. Biz esas olarak ikisini kullanacağız:
- [Android] [1] perspektifi:
- [Project] [4] görünümü;
![]() | ![]() |
![]() |
Çoğu zaman [Android] perspektifiyle çalışacağız. Bir projeyi başka bir projeye kopyalarken [Project] perspektifine ihtiyacımız olacak.
1.2.2.2. Çalıştırma Yönetimi
Bir AS projesini çalıştırmanın, durdurmanın veya yeniden çalıştırmanın birkaç yolu vardır. Öncelikle araç çubuğundaki düğmeler vardır:
![]() | ![]() | ![]() |
[Rerun] ve [3] düğmeleri, [2] projesinin yürütülmesini durdurur ve ardından [1] projesini yeniden başlatır.
1.2.2.3. Önbellek Yönetimi
Android Studio, IDE'i olabildiğince hızlı hale getirmek amacıyla yönettiği projelerin önbelleğini tutar. Android 2.1 sürümünde (Mayıs 2016), bu önbellek genellikle az önce yapılan kod değişikliklerini dikkate almıyordu. Bu durumda, önbelleği geçersiz kılmak gerekir:
![]() | ![]() |
Android 2.1 (Mayıs 2016) sürümünde, önceki işlem birçok kez tekrarlanması gerekiyordu ve bazen bu, tespit edilen hatayı gidermek için yeterli olmuyordu. Çözüm, [Instant Run] teknolojisini devre dışı bırakmaktı:
![]() | ![]() |
- [3-4]'te her şey devre dışı bırakıldı;
Bundan sonraki tüm işlemlerde bu önbellek yapılandırmasıyla çalışıldı ve herhangi bir sorunla karşılaşılmadı.
1.2.2.4. Günlük yönetimi
Bir proje çalıştırılırken, Android monitöründe günlükler görüntülenir:
![]() | ![]() |
[Android Monitor] [1] sekmesinde, günlükler [logcat] [2] sekmesinde görüntülenir. [3] düğmesi, günlükleri silmeye yarar. Bu düğme, belirli bir eylemin günlüklerini görmek istediğinizde kullanışlıdır:
- günlükleri silin;
- Android cihazında, günlüklerini görmek istediğiniz eylemi gerçekleştirin;
- bu sırada ortaya çıkan günlükler, gerçekleştirilen eylemle ilgili olanlardır;
[4] ile çeşitli günlük seviyeleri mevcuttur. Varsayılan olarak [Verbose] modu seçilidir. Bu, tüm seviyelerdeki günlüklerin görüntülendiği anlamına gelir. [4] ile belirli bir seviye seçilebilir.
Günlükler, bir projenin yürütülmesi sırasında belirli yöntemlerin hangi anlarda görüntülendiğini öğrenmek için çok yararlıdır. Bunlara sık sık başvuracağız. [Exemple-01] projesindeki [MainActivity] sınıfının kodunu ele alalım:
![]() |
package com.example.android.pdfrendererbasic;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
public static final String FRAGMENT_PDF_RENDERER_BASIC = "pdf_renderer_basic";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_real);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.container, new PdfRendererBasicFragment(),
FRAGMENT_PDF_RENDERER_BASIC)
.commit();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_info:
new AlertDialog.Builder(this)
.setMessage(R.string.intro_message)
.setPositiveButton(android.R.string.ok, null)
.show();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Yukarıda, [onCreate, ligne 14] ve [onCreateOptionsMenu, ligne 26] yöntemleri, üst sınıf olan [Activity]'e ait yöntemlerdir (9. satır). Bu yöntemler, uygulamanın yaşam döngüsünün farklı aşamalarında çağrılır. Bazen bu yöntemler birden fazla kez yürütülür. Dokümantasyonu okurken bile, yaşam döngüsüne ait bu tür bir yöntemin, kendi yazdığımız bir yöntemden önce mi yoksa sonra mı yürütüleceğini belirlemek bazen zor olabilir. Oysa bu bilgiyi bilmek genellikle önemlidir. Bu durumda, aşağıdaki gibi günlük kayıtları ekleyebiliriz:
public class MainActivity extends Activity {
public static final String FRAGMENT_PDF_RENDERER_BASIC = "pdf_renderer_basic";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity","onCreate");
super.onCreate(savedInstanceState);
...
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d("MainActivity","onCreateOptionsMenu");
getMenuInflater().inflate(R.menu.main, menu);
...
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d("MainActivity","onOptionsItemSelected");
switch (item.getItemId()) {
...
}
}
- 7., 14. ve 21. satırlarda [Log] sınıfı kullanılmaktadır. Bu sınıf, Android konsoluna [logcat] logları yazmaya olanak tanır. Loglar çeşitli seviyelere (info, warning, debug, verbose, error) göre sınıflandırılır. [Log.d], [debug] seviyesindeki günlükleri görüntüler. İlk argümanı, günlük mesajının kaynağıdır. Nitekim, çeşitli kaynaklar günlük konsoluna mesajlar gönderebilir. Bunları birbirinden ayırt edebilmek için bu ilk argüman kullanılır. İkinci argüman ise günlük konsoluna yazılacak mesajdır;
[Exemple-01] projesini tekrar çalıştırırsak, aşağıdaki günlükleri elde ederiz:
05-28 08:37:12.709 23881-23881/com.example.android.pdfrendererbasic D/MainActivity: onCreate
05-28 08:37:12.778 23881-23923/com.example.android.pdfrendererbasic D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
[ 05-28 08:37:12.781 23881:23881 D/ ]
HostConnection::get() New Host ...
05-28 08:37:12.967 23881-23881/com.example.android.pdfrendererbasic D/MainActivity: onCreateOptionsMenu
Böylece, Android etkinliğini oluşturan [onCreate] yönteminin, uygulama menüsünü oluşturan [onCreateOptionsMenu] yönteminden önce yürütüldüğünü görüyoruz.
Şimdi, Android emülatöründe [1] numaralı menü seçeneğine tıklarsak:
![]() |
aşağıdaki günlük kaydı günlük konsoluna eklenir:
05-28 08:41:22.881 23881-23881/com.example.android.pdfrendererbasic D/MainActivity: onOptionsItemSelected
Bundan sonra, Android koduna sık sık günlük kayıt komutları ekleyeceğiz. Çoğu zaman bunları açıklamayacağız. Bunlar, okuyucuyu günlük konsoluna bakmaya davet ederek bir Android uygulamasının yaşam döngüsünü aşamalı olarak anlamasını sağlamak amacıyla yer almaktadır.
1.2.2.5. Emülatör Yönetimi [Genymotion]
Bazen Genymotion emülatörü çökebilir ve yeniden başlatılamayabilir. Bunun nedeni, görev yöneticisinde VirtualBox işlemlerinin aktif kalmış olmasıdır. Görev yöneticisini açın [Ctrl-Alt-Supp] ve mevcut tüm VirtualBox görevlerini silin:
![]() | ![]() |
Bu işlemi tamamladıktan sonra, Android Studio'dan Genymotion emülatörünü yeniden başlatın.
1.2.2.6. Oluşturulan APK ikili dosyasının yönetimi
Projenin derlenmesi sonucunda .apk uzantılı bir ikili dosya oluşturulur:
![]() | ![]() | ![]() |
İki sürüm bulunmaktadır: biri [debug], diğeri ise [debug-unaligned] olarak adlandırılır. Kullanılması gereken ilk sürümdür; diğeri ise ara bir sürümdür. [4] ile oluşturulan .pak dosyası, doğrudan bir emülatöre veya Android cihazına aktarılabilir. Emülatöre aktarmak için, dosyayı fareyle emülatöre sürükleyip bırakmanız yeterlidir.
1.3. Örnek-02: Temel bir Android projesi
Android Studio ile [1-12] adlı yeni bir Android projesi oluşturalım:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() | ![]() | ![]() |
[13]'te uygulama çalıştırılır. Bunun üzerine Genymotion emülatöründe [14] ekranı görüntülenir.
1.3.1. Gradle Yapılandırması
Oluşturulan proje, aşağıdaki [build.gradle] dosyası ile yapılandırılır:
![]() |
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "exemples.android"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
Bu dosya, IDE tarafından yapılandırma öğeleriyle birlikte oluşturulmuştur. Bu, zamanla zenginleştireceğimiz minimal bir dosyadır.
- 3-12. satırlar: Android uygulamasının özellikleri;
- 22-25. satırlar: Uygulamanın bağımlılıkları. İncelediğimiz örneklere göre değişiklikleri esas olarak bu kısımda yapacağız;
1.3.2. Uygulamanın manifest dosyası
![]() |
[AndroidManifest.xml] [1] dosyası, Android uygulamasının ikili dosyasının özelliklerini belirler. İçeriği şu şekildedir:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- 3. satır: Android projesinin paketi;
- 10. satır: aktivitenin adı;
Bu iki bilgi, proje oluşturulurken girilen verilerden alınır:
![]() |
- manifestin (paket) 3. satırı, yukarıdaki [4] girdisinden alınmıştır. Bu pakette belirli sayıda sınıf otomatik olarak oluşturulur;
![]() |
- manifestin 10. satırı (etkinlik adı), yukarıdaki [1] girişinden alınmıştır;
Manifesta'ya geri dönelim:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
![]() | ![]() | ![]() |
- 10. satır: Uygulamanın ana etkinliği. Yukarıdaki [1] sınıfına atıfta bulunur;
- 6. satır: Uygulamanın [2] simgesi. Bu simge değiştirilebilir;
- 7. satır: Uygulamanın adı. Bu bilgi, [strings.xml] dosyasında bulunur. [3]:
<resources>
<string name="app_name">Exemple-02</string>
</resources>
[strings.xml] dosyası, uygulama tarafından kullanılan karakter dizelerini içerir. 2. satırda yer alan uygulama adı, [4] projesinin oluşturulması sırasında girilen bilgiden alınmıştır:
![]() |
- 10. satır: bir etkinlik etiketi. Bir Android uygulaması birden fazla etkinliğe sahip olabilir;
- 12. satır: Bu aktivite, ana aktivite olarak belirlenmiştir;
- 13. satır: ve bu aktivite, Android cihazda başlatılabilen uygulamalar listesinde görünmelidir.
1.3.3. Ana aktivite
![]() | ![]() |
Bir Android uygulaması, bir veya daha fazla aktiviteye dayanır. Burada [1] adlı bir aktivite oluşturulmuştur: [MainActivity]. Bir aktivite, türüne bağlı olarak bir veya daha fazla görünüm görüntüleyebilir. Oluşturulan [MainActivity] sınıfı şöyledir:
package exemples.android;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
- 6. satır: [MyActivity] sınıfı, Android [AppCompatActivity] sınıfını genişletir. Gelecekteki tüm etkinlikler için de durum böyle olacaktır;
- 9. satır: [onCreate] yöntemi, aktivite oluşturulduğunda yürütülür. Bu, aktiviteyle ilişkili görünümün görüntülenmesinden önce gerçekleşir;
- 10. satır: üst sınıfın [onCreate] yöntemi çağrılır. Bu her zaman yapılmalıdır;
- satır 11: [activity_main.xml] dosyası, etkinliğe ilişkin görünümdür. Bu görünümün XML tanımı şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="exemples.android.MainActivity">
<TextView
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
- b-k satırları: Biçimlendirme yöneticisi. Varsayılan olarak seçilen, [RelativeLayout] türüdür. Bu tür bir kapsayıcıda bileşenler birbirlerine göre konumlandırılır (sağında, solunda, altında, üstünde);
- m-p satırları: metin görüntülemek için kullanılan [TextView] tipi bir bileşen;
- n satırı: görüntülenen metin. Görünümlere sabit metin eklemeniz önerilmez. Bu metinleri [res/values/strings.xml] [3] dosyasına taşımanız daha uygun olur:
Böylece görüntülenen metin [Hello World!] olacaktır. Nerede görüntülenecek? [RelativeLayout] konteyneri ekranı dolduracaktır. Tek öğesi olan [TextView], bu konteynerin sol üst köşesinde, dolayısıyla ekranın sol üst köşesinde görüntülenecektir;
- satırdaki [R.layout.activity_main] ne anlama geliyor? Her Android kaynağına (görünümler, parçalar, bileşenler vb.) bir tanımlayıcı atanır. Dolayısıyla, [res / layout] klasöründe bulunan [V.xml] adlı bir görünüm, [R.layout.V] olarak tanımlanır. R, [app / build / generated] [1-3] klasöründe oluşturulan bir sınıftır:
![]() |
[R] sınıfı şöyledir:
...............
public static final class string {
public static final int abc_action_bar_home_description=0x7f060000;
public static final int abc_action_bar_home_description_format=0x7f060001;
public static final int abc_action_bar_home_subtitle_description_format=0x7f060002;
...
public static final int app_name=0x7f060014;
}
public static final class layout {
public static final int abc_action_bar_title_item=0x7f040000;
public static final int abc_action_bar_up_container=0x7f040001;
...
public static final int activity_main=0x7f040019;
...
}
public static final class mipmap {
public static final int ic_launcher=0x7f030000;
}
- 14. satır: [R.layout.activity_main] özniteliği, [res / layout / activity_main.xml] görünümünün tanımlayıcısıdır;
- 7. satır: [R.string.app_name] özniteliği, [res / values / string.xml] dosyasındaki [app_name] dizesinin tanımlayıcısıdır:
- 19. satır: [R.mipmap.ic_launcher] özniteliği, [res / mipmap / ic_launcher] resminin tanımlayıcısıdır;
Dolayısıyla, kodda [R.layout.activity_main]'e atıfta bulunulduğunda, [R] sınıfının bir özniteliğine atıfta bulunulduğunu unutmamak gerekir. IDE, bu sınıfın farklı öğelerini tanımamıza yardımcı olur:
![]() | ![]() |
1.3.4. Uygulamanın çalıştırılması
Bir Android uygulamasını çalıştırmak için bir çalıştırma yapılandırması oluşturmamız gerekir:
![]() | ![]() | ![]() |
- [1]'te, [Edit Configurations]'i seçin;
- proje, [app] yapılandırmasıyla oluşturulmuştur; bunu silip [2] olarak yeniden oluşturacağız;
- [3]'te yeni bir yürütme yapılandırması oluşturun;
![]() |
- [4]'te, [Android Application]'i seçin;

- [5]'te, açılır listeden [app] modülünü seçin;
- [6-8]'te, varsayılan değerleri koruyun;
- [7]'te, varsayılan etkinlik [AndroidManifest.xml] dosyasında (aşağıdaki 1. satır) tanımlanan etkinliktir:
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
- [8]'te, uygulamanın çalıştırılacağı aygıtı (emülatör, tablet) seçmenize olanak tanıyan [Show Chooser Dialog]'i seçin;
- [9]'te, bu seçimin kaydedilmesi gerektiğini belirtin;
- yapılandırmayı onaylayın;
![]() |
- [11]'te, [Genymotion] emülatör yöneticisini başlatın (bkz. paragraf 6.9);
![]() |
- [12]'te bir tablet emülatörü seçin ve [13]'i başlatın;
![]() | ![]() |
- [14]'e, [app] çalıştırma yapılandırmasını yürütün;
- [15]'te yürütme aygıtı seçim formu görüntülenir. Burada yalnızca bir seçenek mevcuttur: daha önce başlatılan [Genymotion] emülatörü;
Yazılım emülatörü bir süre sonra aşağıdaki ekranı görüntüler:

1.3.5. Bir etkinliğin yaşam döngüsü
[MainActivity] etkinliğinin koduna geri dönelim:
package exemples.android;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
8-12. satırlardaki [onCreate] yöntemi, bir etkinliğin yaşam döngüsü boyunca çağrılabilen yöntemlerden biridir. Android belgelerinde bu yöntemlerin listesi şu şekildedir:
![]() |
- [1]: [onCreate] yöntemi, aktivitenin başlatılması sırasında çağrılır. Aktiviteyi bir görünüme bağlamak ve bileşenlerinin referanslarını almak bu yöntem içinde gerçekleştirilir;
- [2-3]: Ardından [onStart, onResume] yöntemleri çağrılır. Görüldüğü gibi, [onResume] yöntemi, yürütülmekte olan aktivitenin [4] durumuna ulaşmadan önce yürütülen son yöntemdir;
1.4. Örnek-03: [Exemple-02] projesinin [Android Annotations] kütüphanesi ile yeniden yazılması
Şimdi, Android uygulamalarının yazılmasını kolaylaştıran [Android Annotations] kütüphanesini tanıtacağız. Bunun için, [1-16] prosedürünü izleyerek [Exemple-02] örneğini [Exemple-03] olarak kopyalayacağız.
![]() | ![]() |
- [1]'e kopyalayın; Android projesinin tamamını görmek için [Project] perspektifini seçin;
![]() | ![]() |
![]() | ![]() |
![]() | ![]() | ![]() | ![]() |
Not: [14] ile [15] arasında, [Android] perspektifinden [Project] perspektifine geçilmiştir (bkz. paragraf 1.2.2.1).
Ardından [res / values / strings.xml] dosyasını [17] olarak değiştiriyoruz:
![]() |
[strings.xml] dosyası şu şekilde değiştirilir:
<resources>
<string name="app_name">Exemple-03</string>
</resources>
Şimdi, [Exemple-02] dosyasının tüm yapılandırmasını devralan yeni uygulamayı çalıştıralım:
![]() | ![]() |
[19]'te, [Exemple-02] ile aynı sonucu elde ediyoruz, ancak yeni bir adla.
Şimdi, kolaylık olması açısından AA olarak adlandıracağımız [Android Annotations] kütüphanesini tanıtacağız. Bu kütüphane, Android kaynak kodlarını açıklamak için yeni sınıflar sunar. Bu açıklamalar, modül içinde yeni Java sınıfları oluşturacak bir işleyici tarafından kullanılacaktır; bu sınıflar, geliştirici tarafından yazılan sınıflarla aynı şekilde modülün derlenmesine katkıda bulunacaktır. Böylece şu derleme zinciri ortaya çıkar:
![]() |
Öncelikle, [build.gradle] dosyasına, AA anotasyon derleyicisine (yukarıdaki işlemci) olan bağımlılıkları ekleyeceğiz:
def AAVersion = '4.0.0'
dependencies {
apt "org.androidannotations:androidannotations:$AAVersion"
compile "org.androidannotations:androidannotations-api:$AAVersion"
compile 'com.android.support:appcompat-v7:23.4.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
}
- 4-5. satırlar, AA kütüphanesini oluşturan iki bağımlılığı ekler;
[build.gradle] dosyası, derleme sürecini iki aşamaya ayıran [android-apt] adlı bir eklentiyi kullanmak üzere yeniden değiştirilir:
- Android anotasyonlarının işlenmesi, bu da yeni sınıfların oluşturulmasına yol açar;
- projedeki tüm sınıfların derlenmesi;
buildscript {
repositories {
mavenCentral()
}
dependencies {
// Android'in Gradle eklentisi 0.11 sürümünden itibaren, android-apt >= 1.3 sürümünü kullanmanız gerekir
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
- 8. satır: Merkezi Maven deposunda (3. satır) aranacak olan [android-apt] eklentisinin sürümü;
- 13. satır: bu eklentinin etkinleştirilmesi;
Bu aşamada, [app] çalıştırma yapılandırmasının hâlâ çalıştığını kontrol edin.
Şimdi, AA kütüphanesinin ilk anotasyonunu [MainActivity] sınıfına ekleyeceğiz:
![]() |
[MainActivity] sınıfı şu anda şu şekildedir:
package exemples.android;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Bu kodu 1.3.3. paragrafında daha önce açıklamıştık. Kodu şu şekilde değiştiriyoruz:
package exemples.android;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.androidannotations.annotations.EActivity;
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
- 7. satır: [@EActivity] açıklaması, AA (3. satır) açıklamasına aittir. Parametresi, etkinlikle ilişkili görünümdür;
Bu anotasyon, [MainActivity] sınıfından türetilmiş bir [MainActivity_] sınıfı oluşturacak ve asıl etkinlik bu sınıf olacaktır. Bu nedenle, [AndroidManifest.xml] projesinin manifestosunu şu şekilde değiştirmeliyiz:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity_">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- 11. satır: yeni aktivite;
Bu işlem tamamlandıktan sonra, [1] projesini derleyebiliriz:
![]() | ![]() |
- [2] dosyasında, [app / build / generated / source / apt / debug] klasöründe oluşturulan [MainActivity_] sınıfını görebiliriz;
Oluşturulan [MainActivity_] sınıfı şöyledir:
//
// DO NOT EDIT THIS FILE.
// AndroidAnnotations 4.0.0 kullanılarak oluşturulmuştur.
//
// Bu dosyayı içeren daha büyük bir eser oluşturabilir ve bu eseri istediğiniz şartlar altında dağıtabilirsiniz.
//
package exemples.android;
import android.app.Activity;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import org.androidannotations.api.builder.ActivityIntentBuilder;
import org.androidannotations.api.builder.PostActivityStarter;
import org.androidannotations.api.view.HasViews;
import org.androidannotations.api.view.OnViewChangedNotifier;
public final class MainActivity_
extends MainActivity
implements HasViews
{
private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();
@Override
public void onCreate(Bundle savedInstanceState) {
OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
init_(savedInstanceState);
super.onCreate(savedInstanceState);
OnViewChangedNotifier.replaceNotifier(previousNotifier);
setContentView(R.layout.activity_main);
}
...
- 24-25. satırlar: [MainActivity_] sınıfı, [MainActivity] sınıfını genişletir;
AA tarafından oluşturulan sınıfların kodunu açıklamaya çalışmayacağız. Bu sınıflar, anotasyonların gizlemeye çalıştığı karmaşıklığı yönetir. Ancak, kullandığımız anotasyonların nasıl 'çevrildiğini' anlamak istediğimizde, bu kodu incelemek bazen yararlı olabilir.
Artık [app] yapılandırmasını yeniden çalıştırabiliriz. Daha önce elde ettiğimizle aynı sonucu alırız. Bundan sonra, Android programlamasının önemli kavramlarını tanıtmak için bu projeyi kopyalayarak devam edeceğiz.
1.5. Örnek-04: Görünümler ve olaylar
1.5.1. Projenin Oluşturulması
1.4. paragrafta [Exemple-02] projesini [Exemple-03] olarak çoğaltmak için açıklanan prosedürü izleyeceğiz:
Şöyle yapacağız:
- [Exemple-03] projesini [Exemple-04] olarak kopyalayacağız (önce [Exemple-03] içindeki [app / build] klasörünü sileceğiz);
- [Exemple-04] projesini yüklüyoruz;
- [app / res / values / strings.xml] dosyasında projenin adını değiştirelim (Android görünümü);
- [Exemple-04 / Exemple-04.iml] dosyasını silelim (Project perspektifi);
- projeyi derleyip çalıştıralım;
![]() | ![]() |
1.5.2. Bir görünüm oluşturma
Şimdi grafik düzenleyiciyi kullanarak [Exemple-04] projesinde görüntülenen görünümü değiştireceğiz:
![]() | ![]() |
- [1-4]'te, XML adlı yeni bir görünüm oluşturun;
- [5]'te, görünüme bir ad verin;
- [6]'te, görünümün kök etiketini belirtin. Burada, [RelativeLayout] adlı bir kapsayıcı seçiyoruz. Bu bileşen kapsayıcısında, bileşenler birbirlerine göre şu şekilde yerleştirilir: "sağında", "solunda", "altında", "üstünde";
![]() |
[vue1.xml] dosyası tarafından oluşturulan [7] dosyası şu şekildedir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
- 2. satır: Tabletin tüm genişliğini (3. satır) ve tüm yüksekliğini (4. satır) kaplayacak boş bir [RelativeLayout] konteyneri;
![]() | ![]() |
- [1]'te, görüntülenen [vue1.xml] görünümünde [Design] sekmesini seçin;
- [2-4]'te tablet moduna geçin;
![]() | ![]() | ![]() |
- [5]'te, tablet için ölçek 1'e geçin;
- [6]'te, tablet için 'yatay' modu seçin;
- [7] ekran görüntüsü, yapılan seçimleri özetlemektedir.
![]() | ![]() | ![]() |
- [1]'te, bir [Large Text] seçin ve bunu [2] görünümüne sürükleyin;
- [3]'te bileşene çift tıklayın;
- [4]'te, görüntülenen metni değiştirin. Metni XML görünümüne "sabit" olarak eklemek yerine, [res / values / string.xml] dosyasına taşıyacağız
![]() | ![]() | ![]() |
- dosyasına aktarıyoruz; [strings.xml] dosyasına yeni bir değer ekliyoruz;
- [8] dosyasında, dizeye bir tanımlayıcı verilir;
- [9] dosyasında, dizenin değeri verilir;
- [10]'te, önceki adımın onaylanmasından sonra ortaya çıkan yeni görünüm;
![]() | ![]() | ![]() |
- bileşene çift tıkladıktan sonra, kimlik numarasını [11] olarak değiştirin;
- [12] olarak değiştirilir; bileşenin özelliklerinde, yazı tipi boyutu [50sp];
- [13] olarak değiştirilir; yeni görünüm;
[vue1.xml] dosyası şu şekilde değişmiştir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/titre_vue1"
android:id="@+id/textViewTitreVue1"
android:layout_marginLeft="213dp" android:layout_marginStart="213dp"
android:layout_marginTop="50dp" android:layout_alignParentTop="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:textSize="50sp"/>
</RelativeLayout>
- grafik arayüzde yapılan değişiklikler 10, 11 ve 14. satırlarda yer almaktadır. [TextView] dosyasının diğer öznitelikleri varsayılan değerlerdir veya bileşenin görünümdeki konumundan kaynaklanmaktadır;
- 7-8. satırlar: Bileşenin boyutu, içerdiği metnin (wrap_content) yüksekliği ve genişliği kadardır;
- satır 13: bileşenin üst kenarı, görünümün üst kenarıyla (satır 13) hizalanmış ve 50 piksel aşağıda (satır 13) yer almaktadır;
- 12. satır: Bileşenin sol kenarı, görünümün sol kenarıyla hizalanır (13. satır), 213 piksel sağa kaydırılır (12. satır);
Genel olarak, sol, sağ, üst ve alt kenar boşluklarının kesin boyutları doğrudan XML dosyasında belirlenir.
Aynı şekilde ilerleyerek, aşağıdaki [1] görünümünü oluşturun:
![]() |
Bileşenler şunlardır:
Bileşenleri birbirine göre konumlandırmak, grafik düzenleyicinin tepkilerinin bazen şaşırtıcı olabileceği için sinir bozucu bir işlem olabilir. Bileşenlerin özelliklerini kullanmak daha uygun olabilir:
[textView1] bileşeni, başlığın 50 piksel altına ve kapsayıcının sol kenarından 50 piksel uzağa yerleştirilmelidir:
![]() | ![]() | ![]() |
- [1] bileşeninde, bileşenin üst kenarı (top), [textViewTitreVue1] bileşeninin alt kenarına (bottom) göre 50 piksel mesafede hizalanır;
- [2]'te, bileşenin sol kenarı (left), konteynerin sol kenarına 50 piksel mesafede hizalanır ([3] (left));
[editTextNom] bileşeni, [textView1] bileşeninin 60 piksel sağında yerleştirilmeli ve bu bileşenin alt kenarına hizalanmalıdır;
![]() | ![]() |
- [1] bileşeninde, bileşenin sol kenarı (left), [textView1] bileşeninin sağ kenarına (right) 60 piksel mesafede hizalanmıştır ([2] (left)). [textView1] bileşeninin alt kenarına (bottom:bottom) hizalanır;
[buttonValider] bileşeni, [editTextNom] bileşeninin 60 piksel sağında yerleştirilmeli ve bu bileşenin alt kenarına hizalanmalıdır;
![]() | ![]() |
- [1] bileşeninde, bileşenin sol kenarı (left), [editTextNom] bileşeninin sağ kenarına (right) 60 piksel mesafede hizalanmıştır ([2] (left)). [editTextNom] bileşeninin alt kenarına (bottom:bottom) hizalanır;
[buttonVue2] bileşeni, [textView1] bileşeninin 50 piksel altına yerleştirilmeli ve bu bileşenin sol kenarına hizalanmalıdır;
![]() | ![]() |
- [1] bileşeninde, bileşenin sol kenarı (left), [textView1] bileşeninin sol kenarına (left) hizalanır ve [2] (top) bileşeninin 50 piksel aşağısına (top:bottom) yerleştirilir;
Oluşturulan XML dosyası şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/titre_vue1"
android:id="@+id/textViewTitreVue1"
android:layout_marginTop="49dp"
android:textSize="50sp"
android:layout_gravity="center|left"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txt_nom"
android:id="@+id/textView1"
android:layout_below="@+id/textViewTitreVue1"
android:layout_alignParentLeft="true"
android:layout_marginLeft="50dp"
android:layout_marginTop="50dp"
android:textSize="30sp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editTextNom"
android:minWidth="200dp"
android:layout_toRightOf="@+id/textView1"
android:layout_marginLeft="60dp"
android:layout_alignBottom="@+id/textView1"
android:inputType="textCapCharacters"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_valider"
android:id="@+id/buttonValider"
android:layout_alignBottom="@+id/editTextNom"
android:layout_toRightOf="@+id/editTextNom"
android:textSize="30sp"
android:layout_marginLeft="60dp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_vue2"
android:id="@+id/buttonVue2"
android:layout_below="@+id/textView1"
android:layout_alignLeft="@+id/textView1"
android:layout_marginTop="50dp"
android:textSize="30sp"/>
</RelativeLayout>
Bu dosyada, grafik olarak yapılan tüm işlemler yer almaktadır. Bir görünümü oluşturmanın başka bir yolu da bu dosyayı doğrudan yazmaktır. Alıştığınızda, bu yöntem grafik düzenleyiciyi kullanmaktan daha hızlı olabilir.
- 38. satırda, daha önce göstermediğimiz bir bilgi yer almaktadır. Bu bilgi, [editTextNom] ve [1] bileşenlerinin özellikleri aracılığıyla verilmektedir:
![]() | ![]() |
Tüm metinler aşağıdaki [strings.xml] [2] dosyasından alınmıştır:
<resources>
<string name="app_name">Exemple-04</string>
<string name="titre_vue1">Vue n° 1</string>
<string name="txt_nom">Quel est votre nom ?</string>
<string name="btn_valider">Valider</string>
<string name="btn_vue2">Vue n° 2</string>
</resources>
Şimdi, uygulamanın başlatılmasıyla bu görünümün görüntülenmesi için [MainActivity] etkinliğini değiştirelim:
package exemples.android;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import org.androidannotations.annotations.EActivity;
@EActivity(R.layout.vue1)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
- 7. satır: Artık etkinlik tarafından [vue1.xml] görünümü görüntüleniyor;
[AndroidManifest.xml] dosyasını şu şekilde değiştirin:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity_"
android:windowSoftInputMode="stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- 12. satır: Bu yapılandırma satırı, [vue1] görünümünün görüntülenmesiyle birlikte klavyenin görünmesini engeller. Zira bu görünümde, görünüm görüntülendiğinde odağı üzerinde olan bir giriş alanı bulunmaktadır. Bu odak, varsayılan olarak sanal klavyenin görünmesine neden olur;
Uygulamayı çalıştırın ve [vue1.xml] görünümünün görüntülendiğini kontrol edin:

1.5.3. Olay Yönetimi
Şimdi [Vue1] görünümündeki [Valider] düğmesine yapılan tıklamayı yönetelim:

[MainActivity] kodunda şu değişiklikler yapılır:
package exemples.android;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
@EActivity(R.layout.vue1)
public class MainActivity extends AppCompatActivity {
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity","onCreate");
super.onCreate(savedInstanceState);
}
@AfterViews
protected void afterViews(){
Log.d("MainActivity","afterViews");
}
// olay yöneticisi
@Click(R.id.buttonValider)
protected void doValider() {
// girilen ad görüntülenir
Toast.makeText(this, String.format("Bonjour %s", editTextNom.getText().toString()), Toast.LENGTH_LONG).show();
}
}
- 17-18. satırlar: [protected EditText editTextNom] alanı, görsel arayüzün [R.id.editTextNom] tanımlayıcı bileşeniyle ilişkilendirilir. Bileşene eşleştirilen alan, türetilmiş sınıf [MainActivity_] içinde erişilebilir olmalıdır ve bu nedenle [private] kapsamında olamaz. [R.id.editTextNom] ile tanımlanan alan, [vue1.xml] görünümünden gelmektedir:
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/editTextNom"
android:minWidth="200dp"
android:layout_toRightOf="@+id/textView1"
android:layout_marginLeft="60dp"
android:layout_alignBottom="@+id/textView1"
android:inputType="textCapCharacters"/>
Not: [id] tanımlayıcılarına aksanlı karakterler eklemeyin. AA bunları doğru şekilde işleyemez.
- 32. satır: [@Click(R.id.buttonValider)] açıklaması, [R.id.buttonValider] tanımlayıcısındaki düğmenin 'Click' olayını işleyen yöntemi belirtir. Bu tanımlayıcı aynı zamanda [vue1.xml] görünümünden de gelir:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_valider"
android:id="@+id/buttonValider"
android:layout_alignBottom="@+id/editTextNom"
android:layout_toRightOf="@+id/editTextNom"
android:textSize="30sp"
android:layout_marginLeft="60dp"/>
- 35. satır: girilen adı görüntüler:
- Toast.makeText(...).show(): ekrana bir metin görüntüler,
- makeText'in ilk parametresi etkinliktir,
- ikinci parametre, makeText tarafından görüntülenecek kutuda gösterilecek metindir,
- üçüncü parametre, görüntülenen kutunun ömrüdür: Toast.LENGTH_LONG veya Toast.LENGTH_SHORT;
- 26. satırda, [@AfterViews] açıklaması, [@ViewById] ile açıklanan tüm alanlar başlatıldığında yürütülecek yöntemi belirtir. Bu alanların ne zaman başlatıldığını bilmek önemlidir. Örneğin, [onCreate] yönteminde 18. satırdaki referansı kullanabilir miyiz? Bu soruyu yanıtlamak için günlük kayıtları ekledik;
[Exemple-04] projesini çalıştırın ve [Valider] düğmesine tıkladığınızda bir işlem gerçekleşip gerçekleşmediğini kontrol edin. Aşağıdaki günlük kayıtlarını elde ediyoruz:
Buradan, [onCreate] yöntemi çalıştırıldığında, [@ViewById] ile işaretlenmiş alanların henüz başlatılmadığı sonucuna varıyoruz. Yine, yeni başlayan okuyucuların, uygulamanın yaşam döngüsünü yöneten yöntemlere bu tür günlük kayıtları eklemeleri önerilir.
1.6. Örnek-05: Görünümler arasında gezinme
Önceki projede, [Vue n° 2] düğmesi kullanılmamıştı. İkinci bir görünüm oluşturarak ve bir görünümden diğerine nasıl geçileceğini göstererek bu düğmeyi kullanmayı öneriyoruz. Bu sorunu çözmenin birkaç yolu vardır. Burada önerilen yöntem, her görünümü bir etkinliğe ilişkilendirmektir. Başka bir yöntem ise, [Fragment] türündeki görünümleri görüntüleyen tek bir [AppCompatActivity] türünde etkinlik kullanmaktır. Gelecekteki uygulamalarda bu yöntem kullanılacaktır.
1.6.1. Projenin Oluşturulması
[Exemple-04] projesini [Exemple-05] olarak kopyalayacağız. Bunun için, 1.4. paragrafta [Exemple-02] projesini [Exemple-03] olarak kopyalamak için açıklanan ve 1.5. paragrafta tekrarlanan prosedürü izleyeceğiz.
![]() | ![]() |
1.6.2. İkinci bir aktivitenin eklenmesi
İkinci bir görünümü yönetmek için ikinci bir etkinlik oluşturacağız. 2 numaralı görünümü bu etkinlik yönetecektir. Burada “bir görünüm = bir etkinlik” modelini kullanıyoruz. Başka modeller de mümkündür.
123

- [1-4]'te yeni bir aktivite oluşturulur;

- [5]'te, oluşturulacak sınıfın adı;
- [6]'te, yeni faaliyete bağlı görünümün adı (vue2.xml);
![]() |
- [7-8]'te, önceki yapılandırmadan etkilenen dosyalar;
[SecondActivity] etkinliği şöyledir:
package exemples.android;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.vue2);
}
}
- 11. satır: faaliyet, [vue2.xml] görünümüne atanmıştır;
[vue2.xml] görünümü şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="exemples.android.SecondActivity">
</RelativeLayout>
Bu, şimdilik boş olan ve [RelativeLayout] türünde bir düzen yöneticisine sahip bir görünümdür (2. satır). 11. satırda, bu görünümün yeni aktiviteyle ilişkilendirildiği görülmektedir.
Android modülü [AndroidManifest.xml]'in manifest dosyası şu şekilde değişmiştir:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity_"
android:windowSoftInputMode="stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".SecondActivity">
</activity>
</application>
</manifest>
- satırda, ikinci bir etkinlik kaydedilmiştir.
1.6.3. 1 numaralı görünümden 2 numaralı görünüme geçiş
1 numaralı görünümü görüntüleyen [MainActivity] sınıfının koduna geri dönelim. 2 numaralı görünüme geçiş şu anda desteklenmemektedir:
![]() |
Bunu şu şekilde yönetiyoruz:
// 2 numaralı görünüme geç
@Click(R.id.buttonVue2)
protected void navigateToView2() {
// 1 numaralı görünümde girilen adı 2 numaralı görünüme aktararak bu görünüme geçilir
// bir Intent oluşturulur
Intent intent = new Intent();
// bu Intent'i bir aktiviteye bağlanır
intent.setClass(this, SecondActivity.class);
// bu Intent'e bilgiler eklenir
intent.putExtra("NOM", editTextNom.getText().toString().trim());
// [SecondActivity] türündeki aktiviteyi, Intent'i aktararak başlatıyoruz
startActivity(intent);
}
- 2-3. satırlar: [navigateToView2] yöntemi, [vue1.xml] görünümünde tanımlanan ve [R.id.buttonVue2] ile belirtilen düğmeye yapılan 'tıklamayı' yönetir:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_vue2"
android:id="@+id/buttonVue2"
android:layout_below="@+id/textView1"
android:layout_alignLeft="@+id/textView1"
android:layout_marginTop="50dp"
android:textSize="30sp"/>
Yorumlar, görünüm değişikliği için gerçekleştirilmesi gereken adımları açıklamaktadır:
- 6. satır: [Intent] türünde bir nesne oluşturun. Bu nesne, başlatılacak etkinliği ve ona aktarılacak bilgileri belirlemenizi sağlayacaktır;
- 8. satır: Intent'i bir aktiviteye bağlayın; burada, 2 numaralı görünümü görüntülemekle görevli [SecondActivity] türünde bir aktivite kullanılır. [MainActivity] aktivitesinin ise 1 numaralı görünümü görüntülediğini unutmamak gerekir. Dolayısıyla, bir görünüm = bir aktivite vardır. [SecondActivity] türünü tanımlamamız gerekecek;
- 10. satır: İsteğe bağlı olarak, [Intent] nesnesine bilgiler girebiliriz. Bu bilgiler, başlatılacak olan [SecondActivity] etkinliği içindir. [Intent.putExtra]'in parametreleri (Anahtar nesne, Değer nesnesi) şeklindedir. Giriş alanına girilen metni döndüren [EditText.getText()] yöntemi, [String] türünü değil, [Editable] türünü döndürdüğüne dikkat edilmelidir. Girilen metni almak için [toString] yöntemini kullanmanız gerekir;
- 12. satır: [Intent] nesnesi tarafından tanımlanan etkinliği başlatın.
[Exemple-05] projesini çalıştırın ve 2 numaralı görünümün (şu anda boş) görüntülendiğini kontrol edin:
![]() | ![]() |
1.6.4. 2 numaralı görünümün oluşturulması
![]() | ![]() |
- [1-2] dosyasında, artık ihtiyacımız olmayan [main.xml] görünümünü siliyoruz, ardından [vue2.xml] görünümünü şu şekilde değiştiriyoruz:
![]() |
Bileşenler şunlardır:
XML [vue2.xml] dosyası şu şekildedir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="exemples.android.SecondActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/titre_vue2"
android:id="@+id/textViewTitreVue2"
android:layout_marginTop="50dp"
android:textSize="50sp"
android:layout_gravity="center|left"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textViewBonjour"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textViewTitreVue2"
android:layout_marginTop="50dp"
android:layout_marginLeft="50dp"
android:textSize="30sp"
android:text="Bonjour !"
android:textColor="#ffffb91b"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_vue1"
android:id="@+id/buttonVue1"
android:layout_marginTop="50dp"
android:textSize="30sp"
android:layout_alignLeft="@+id/textViewBonjour"
android:layout_below="@+id/textViewBonjour"/>
</RelativeLayout>
[Exemple-05] projesini çalıştırın ve [Vue n° 2] düğmesine tıklayarak yeni görünümün görüntülendiğini kontrol edin.
1.6.5. [SecondActivity] etkinliği
[MainActivity]'te şu kodu yazdık:
// 2 numaralı görünüme geçilir
protected void navigateToView2() {
// 1 numaralı görünümde girilen adı aktararak 2 numaralı görünüme geçilir
// bir Intent oluşturulur
Intent intent = new Intent();
// bu Intent'i bir aktiviteye bağlanır
intent.setClass(this, SecondActivity.class);
// bu Intent'e bilgiler eklenir
intent.putExtra("NOM", edtNom.getText().toString().trim());
// [SecondActivity] türündeki aktiviteyi, Intent'i aktararak başlatıyoruz
startActivity(intent);
}
- satırda, [SecondActivity] için kullanılmamış bilgiler ekledik. Şimdi bunları kullanıyoruz ve bu işlem [SecondActivity] kodunda gerçekleşiyor:
![]() |
[SecondActivity] kodunda şu değişiklikler yapıldı:
package exemples.android;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EActivity;
import org.androidannotations.annotations.ViewById;
@EActivity(R.layout.vue2)
public class SecondActivity extends AppCompatActivity {
// görsel arayüz bileşenleri
@ViewById
protected TextView textViewBonjour;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@AfterViews
protected void afterViews() {
// varsa niyet alınır
Intent intent = getIntent();
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
// adını alınır
String nom = extras.getString("NOM");
if (nom != null) {
// görüntülenir
textViewBonjour.setText(String.format("Bonjour %s !", nom));
}
}
}
}
}
- 11. satır: [@EActivity] açıklaması kullanılarak, [SecondActivity] sınıfının [vue2.xml] görünümüne bağlı bir etkinlik olduğu belirtilir;
- 15-16. satırlar: [R.id.textViewBonjour] ile tanımlanan [TextView] bileşenine bir referans alınır. Burada [@ViewById(R.id.textViewBonjour)] yazılmamıştır. Bu durumda, AA, bileşen tanımlayıcısının açıklamalı alanla aynı olduğunu varsayar; burada bu alan [textViewBonjour]'tir;
- 23. satır: [@AfterViews] açıklaması, [@ViewById] ile açıklanan alanlar başlatıldıktan sonra yürütülmesi gereken bir yöntemi açıklar. [OnCreate] yönteminde (satır 19), bu alanlar henüz başlatılmadıkları için kullanılamaz. [Exemple-05] projesinde bir etkinlikten diğerine geçiliyor ve [@AfterViews] ile anotlanmış yöntemin, etkinliğin ilk örneklenmesinde mi yoksa etkinlik her başlatıldığında mı yürütüleceği başlangıçta net değildi. Testler, ikinci varsayımın doğru olduğunu göstermiştir;
- 26. satır: [AppCompatActivity] sınıfında, faaliyete bağlı [Intent] nesnesini döndüren [getIntent] yöntemi bulunmaktadır;
- 28. satır: [Intent.getExtras] yöntemi, etkinliğin [Intent] nesnesiyle ilişkili bilgileri içeren bir tür sözlük olan [Bundle] türünü döndürür;
- 31. satır: Etkinliğin [Intent] nesnesinde yer alan ad alınır;
- 34. satır: Bu isim görüntülenir.
Hatırlatma: [@ViewById] etiketiyle işaretlenmiş alanlarda aksanlı karakterler bulunmamalıdır.
[SecondActivity] sınıfına geri dönelim. Çünkü şunu yazmıştık:
@EActivity(R.layout.vue2)
public class SecondActivity extends AppCompatActivity {
AA, [SecondActivity]'ten türetilen [SecondActivity_] sınıfını oluşturacak ve asıl aktivite bu sınıf olacaktır. Bu da bizi şu dosyada değişiklikler yapmaya yönlendiriyor:
[MainActivity]
// 2 numaralı görünüme geçilir
@Click(R.id.buttonVue2)
protected void navigateToView2() {
..
// bu Intent'i bir aktiviteye bağlanır
intent.setClass(this, SecondActivity_.class);
...
}
- 6. satırda, [SecondActivity]'i [SecondActivity_] ile değiştirmeliyiz;
[AndroidManifest.xml]
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity_"
android:windowSoftInputMode="stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".SecondActivity_">
</activity>
</application>
</manifest>
- 20. satırda, [SecondActivity] yerine [SecondActivity_] yazılmalıdır;
Bu yeni sürümü test edin. 1 numaralı görünümde bir ad yazın ve 2 numaralı görünümde bu adın doğru şekilde görüntülendiğini kontrol edin.
![]() | ![]() |
1.6.6. 2 numaralı görünümden 1 numaralı görünüme geçiş
2 numaralı görünümden 1 numaralı görünüme geçmek için daha önce gördüğümüz prosedürü izleyeceğiz:
- 2 numaralı görünümü görüntüleyen [SecondActivity] etkinliğine geçiş kodunu ekleyeceğiz;
- 1 numaralı görünümü görüntüleyen [MainActivity] etkinliğine [@AfterViews] yöntemini yazın;
[SecondActivity] kodunda aşağıdaki değişiklikler yapılır:
@Click(R.id.buttonVue1)
protected void navigateToView1() {
// [MainActivity] etkinliği için bir Intent oluşturulur
Intent intent1 = new Intent();
intent1.setClass(this, MainActivity_.class);
// geçerli etkinliğin Intent'ini alıyoruz [SecondActivity]
Intent intent2 = getIntent();
if (intent2 != null) {
Bundle extras2 = intent2.getExtras();
if (extras2 != null) {
// [MainActivity] Intent’ine adı eklenir
intent1.putExtra("NOM", extras2.getString("NOM"));
}
// [MainActivity]'i başlatıyoruz
startActivity(intent1);
}
}
- 1-2. satırlar: [navigateToView1] yöntemini, [btn_vue1] düğmesine tıklanmasına bağlayın;
- 4. satır: yeni bir [Intent] oluşturulur;
- 5. satır: [MainActivity_] etkinliğine atanır;
- 7. satır: [SecondActivity] ile ilişkilendirilmiş Intent alınır;
- 9. satır: Bu Intent'in bilgileri alınır;
- 12. satır: [NOM] anahtarı, [intent2]'ten alınarak aynı ilişkili değerle [intent1]'e eklenir;
- 15. satır: [MainActivity_] etkinliği başlatılır.
[MainActivity] koduna aşağıdaki [@AfterViews] yöntemi eklenir:
@AfterViews
protected void afterViews() {
// varsa Intent'i alıyoruz
Intent intent = getIntent();
if (intent != null) {
Bundle extras = intent.getExtras();
if (extras != null) {
// adı alınır
String nom = extras.getString("NOM");
if (nom != null) {
// görüntülenir
editTextNom.setText(nom);
}
}
}
}
Bu değişiklikleri yapın ve uygulamanızı test edin. Artık 2 numaralı görünümden 1 numaralı görünüme geri döndüğünüzde, başlangıçta girilen adı görmelisiniz; bu, şimdiye kadar böyle değildi.
![]() | ![]() |
1.6.7. Etkinliklerin yaşam döngüsü
1.3.5 numaralı paragrafta bir aktivitenin yaşam döngüsünü ele almıştık. Burada iki aktivite var ve yürütme sırasında birinden diğerine geçiş yapıyoruz. Bu aktiviteler, birbirlerine göre ne zaman çağrıldıkları tam olarak bilinmeyen iki yöntem içeriyor: [onCreate] ve [afterViews]. Bunu bilmek önemlidir. Bunun için her iki aktiviteye de günlük kayıtları ekliyoruz:
Böylece, [MainActivity] sınıfına şunu yazıyoruz:
// Üretici
public MainActivity() {
Log.d("MainActivity", "constructor");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity", "onCreate");
...
}
@AfterViews
protected void afterViews() {
Log.d("MainActivity", "afterViews");
...
}
}
- 2-4. satırlar: [MainActivity] sınıfının bir veya daha fazla kez örneklendirildiğini bilmek istiyoruz;
- 8. satır: [onCreate] yönteminin bir kez mi yoksa birden fazla kez mi çağrıldığını öğrenmek istiyoruz;
- 14. satır: [afterViews] yönteminin bir veya daha fazla kez çağrılıp çağrılmadığını öğrenmek istiyoruz;
Aynı işlemi [SecondActivity] sınıfında da aynen uyguluyoruz.
Uygulama başlatıldığında şu günlük kayıtları elde ediyoruz:
İlk aktivitedeki [onCreate, afterViews] yöntemleri bu sırayla yürütülmüştür. [Vue n° 2] düğmesine tıklandığında, yeni günlükler şu şekildedir:
İkinci aktivitedeki [onCreate, afterViews] yöntemleri bu sırayla yürütülmüştür. [Vue n° 1] düğmesine tıklandığında, yeni günlükler şu şekildedir:
Böylece [MainActivity] sınıfı yeniden örneklenmiştir. [Vue n° 2] düğmesine tıklandığında, yeni günlükler şu şekildedir:
Böylece [SecondActivity] sınıfı yeniden oluşturulur.
Dolayısıyla, aktivite değiştirildiğinde her iki aktivite de sistematik olarak yeniden oluşturulur.
Şimdi, fragmanlar olarak adlandırılan birden fazla görünümü yönetebilen tek bir aktiviteye sahip bir mimariyi inceleyeceğiz. Bir aktivitenin birden fazla kez örneklenebildiği önceki yöntemin aksine, bu mimaride aktivite ve görünümler yalnızca bir kez örneklenecektir.
1.7. Örnek-06: sekmeli gezinme
Burada sekmeli arayüzleri inceleyeceğiz. Örnek karmaşıktır ancak daha sonra kullanacağımız tüm unsurları içerir: tek aktivite, fragman (görünüm) yöneticisi, fragman konteyneri, fragmanlar arası gezinme. Sekme kavramı, fragman kavramından farklıdır ve bu örnekte göstermek istediğimiz husus açısından ikincil öneme sahiptir.
1.7.1. Proje Oluşturma
Yeni bir proje oluşturuyoruz:
![]() | ![]() |
![]() |
![]() |
- [7]'te, sekmeli etkinlik (Tabbed Activity) seçeneğini seçiyoruz;
![]() |
- [10-14]'te, varsayılan değerler korunur;
- [15]'te, başlık çubuğu olan sekmeler seçilir;
Oluşturulan proje şu şekildedir:
![]() | ![]() |
- [1]'te, aktivite;
- [2]'te, görünümler;
Modülün adını taşıyan [app] adlı bir yürütme yapılandırması otomatik olarak oluşturulmuştur [2b]:
![]() |
Bu yapı çalıştırılabilir. Ardından, üç sekme içeren bir pencere görüntülenir: [3-6]:

1.7.2. Gradle Yapılandırması
[Exemple-06] projesi, aşağıdaki [build.gradle] dosyası kullanılarak oluşturulmuştur:
![]() |
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "exemples.android"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
}
Daha önce karşılaşılanlara kıyasla bir yenilik var: 25. satır. Bu kütüphane, oluşturulan uygulama tarafından kullanılan yeni bileşenler için gereklidir.
1.7.3. [activity_main] görünümü
![]() |
[activity_main] görünümü, projenin [MainActivity] etkinliğiyle ilişkili görünümdür. [design] modunda görünüm şu şekildedir:

Aşağıdaki bileşenleri içerir:
![]() |
- [main_content], görünümün tamamını oluşturur;
- [appbar] (kırmızı çerçeve, 1) uygulama çubuğudur. İki bileşen içerir:
- [toolbar] (sarı kutucuk 4) araç çubuğudur;
- [tabs] (turuncu çerçeve 5) sekmelerin başlık çubuğudur;
- [container] (yeşil kutu, 2) çeşitli parçaları barındırabilir. Bir parça, bir görünümdür. Böylece, aynı etkinlik bu kapsayıcıda birden fazla görünüm (parça) görüntüleyebilir;
- [fab] (bileşen 3), yüzen bileşen olarak adlandırılır;
[text] modunda kod şu şekildedir:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="exemples.android.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"/>
</android.support.design.widget.CoordinatorLayout>
Daha önce açıklanan öğeleri burada da görüyoruz:
- 2-49. satırlar: Görünümün tamamını oluşturan [main_content] bileşeninin tanımı (5. satır). Bunun, [CoordinatorLayout] (satır 2) türünde bir layout (bileşen düzeni yöneticisi) olduğu görülmektedir;
- satır 11-33: [appbar] konteyneri (satır 12). Bu, [AppBarLayout] türünde (satır 11) bir layout'tir;
- satır 18-24: [Toolbar] (satır 18) tipindeki [toolbar] bileşeni (satır 19);
- satır 28-31: [tabs] konteyneri (satır 29). Bu, [TabLayout] türündeki (satır 28) bir layout'tir. Sekme başlıklarını görüntüleyecektir;
- satır 35-39: [container] bileşeni (satır 36). Etkinliğin farklı görünümlerini görüntüleyen bu konteynerdir;
- satır 41-47: [FloatingActionButton] (satır 41) türündeki [fab] bileşeni (satır 42). Bu, tıklanabilen bir düğmedir. Varsayılan olarak genel görünümün sağ alt köşesine yerleştirilir;
Bu bileşenlerin tüm özniteliklerinin anlamını anlamaya çalışmayacağız. Onları olduğu gibi kullanacağız. Bunların rolünü deneyim yoluyla ve genellikle [design] modunda keşfediyoruz. Bu modda, bileşenlerin onlarca özniteliği olduğunu görüyoruz. Genellikle sadece bazıları başlatılır, diğerleri ise varsayılan değerlerini korur.
Yine de birkaç noktayı açıklığa kavuşturalım. Farklı görünümleri yapılandıran değerlerin çoğu [res / values] klasöründe toplanmıştır:
![]() |
Bu değerler, [activity_main.xml] dosyasının 15-16, 23, 39 ve 46. satırlarında belirtilmiştir. Bir örnek verelim:
- 15. satır:
android:paddingTop="@dimen/appbar_padding_top"
[@dimen] açıklaması, [res / values / dimens.xml] dosyasına atıfta bulunur:
<resources>
<!-- Android Tasarım kılavuzuna göre varsayılan ekran kenar boşlukları. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="appbar_padding_top">8dp</dimen>
</resources>
[activity_main.xml] dosyasının 15. satırı, yukarıdaki (f) satırına atıfta bulunur;
Benzer şekilde, şu açıklama:
- [@string], [res / values / strings.xml] kaynak dosyasına atıfta bulunur;
- [@color], [res / values / colors.xml] kaynak dosyasına atıfta bulunur;
- [@style], [res / values / styles.xml] kaynak dosyasına atıfta bulunur;
1.7.4. Etkinlik
![]() |
Etkinlik için oluşturulan kod, daha önce açıklanan görünümle aynı düzeyde: karmaşıktır. Bunu birkaç adımda inceleyeceğiz.
1.7.4.1. Parça ve sekmelerin yönetimi
[MainActivity] dosyasındaki parçalar ve sekmelerle ilgili kod şöyledir:
package exemples.android;
import android.support.design.widget.TabLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// parça yöneticisi
private SectionsPagerAdapter mSectionsPagerAdapter;
// fragman konteyneri
private ViewPager mViewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
// üst öğe
super.onCreate(savedInstanceState);
// görünüm
setContentView(R.layout.activity_main);
// araç çubuğu
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// parça yöneticisi
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// parça konteyneri, parça yöneticisiyle ilişkilendirilmiştir
// yani, parça konteynerindeki i numaralı parça, parça yöneticisi tarafından sağlanan i numaralı parçadır
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
// sekme çubuğu da parça konteyneriyle ilişkilidir
// yani i numaralı sekme, kapsayıcıdaki i numaralı parçayı görüntüler
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
}
// bir parça
public static class PlaceholderFragment extends Fragment {
...
}
// parça yöneticisi
// ana görünümde görüntülenecek parçalar bu yöneticiye istenir
// [getItem] ve [getCount] yöntemlerini tanımlamalıdır - diğerleri isteğe bağlıdır
public class SectionsPagerAdapter extends FragmentPagerAdapter {
...
}
}
- 28. satır: Android, [android.support.v4.view.ViewPager] türünde bir görünüm konteyneri sağlar (12. satır). Bu konteynere bir görünüm veya parça yöneticisi sağlanmalıdır. Bunu geliştirici sağlar;
- 25. satır: Bu örnekte kullanılan fragman yöneticisi. Uygulaması 61-63. satırlarda yer almaktadır;
- satır 31: aktivitenin oluşturulmasında yürütülen yöntem;
- satır 35: [activity_main.xml] görünümü, etkinliğe atanır;
- satır 37: [toolbar] bileşeninin referansı, kimliği aracılığıyla görünümden alınır;
- satır 38: bu araç çubuğu, etkinliğin eylem çubuğu (bir Android kavramı) haline gelir;
- satır 40: Fragman yöneticisi örneklenir. Oluşturucunun parametresi, Android sınıfı [android.support.v4.app.FragmentManager]'tir (satır 10);
- 44. satır: [activity_main.xml] görünümünden, tanımlayıcısı aracılığıyla fragman konteynerinin referansı alınır;
- satır 45: Fragman yöneticisi, fragman konteynerine bağlanır. Bu, fragman konteynerinden i numaralı fragmanı görüntülemesi istendiğinde, bu isteğin fragman yöneticisine iletileceği anlamına gelir;
- satır 48: sekme çubuğuna ait referans, tanımlayıcısı aracılığıyla alınır;
- 49. satır: Sekme yöneticisi, parça konteyneriyle ilişkilendirilir. Bu, i numaralı sekmeye tıklandığında konteynerin i numaralı parçayı görüntüleyeceği anlamına gelir. Sekme yöneticisi ile parça konteyneri arasında kurulan bu ilişki, sekmelerin yönetilmesini gereksiz kılar. Böylece, bir sekmeye tıklama için olay yöneticisi tanımlamamız gerekmez. Parça konteyneriyle yapılan ilişkilendirme bunu varsayılan olarak sağlar. Sekmelerden daha fazla parça bulunan bir örnek göreceğiz. Bu durumda, bu ilişkilendirme yapılmaz.
[SectionsPagerAdapter] fragman işleyicisi şu şekildedir:
// parça yöneticisi
// ana görünümde görüntülenecek parçaları bu yöne sorulur
// [getItem] ve [getCount] yöntemlerini tanımlamalıdır - diğerleri isteğe bağlıdır
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
// parça numarası ve konumu
@Override
public Fragment getItem(int position) {
// bir [PlaceHolder] parçası oluşturulur ve işlenir
return PlaceholderFragment.newInstance(position + 1);
}
// yönetilen parça sayısını verir
@Override
public int getCount() {
return 3;
}
// isteğe bağlı - yönetilen parçalara bir başlık verir
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "SECTION 1";
case 1:
return "SECTION 2";
case 2:
return "SECTION 3";
}
return null;
}
}
}
- Bir uygulama tarafından görüntülenen parçalar, söz konusu uygulamaya bağlıdır. Parça yöneticisi, geliştirici tarafından tanımlanır;
- 5. satır: Parça yöneticisi, Android [android.support.v4.app.FragmentPagerAdapter] sınıfını genişletir. Oluşturucu bize zorunlu olarak verilir. En azından aşağıdaki iki yöntemi tanımlamamız gerekir:
- int getCount(): yönetilecek fragman sayısını döndürür;
- Fragment getItem(i): i numaralı parçayı döndürür;
i numaralı parçanın başlığını döndüren CharSequence getPageTitle(i) yöntemi isteğe bağlıdır. Sekme yöneticisi, parça yöneticisiyle ilişkilendirildiğinden, i numaralı sekmenin başlığı, i numaralı parçanın başlığı olacaktır. Böylece, 27-33. satırlardaki başlıklar sekme başlıkları olacaktır;
- 18-21. satırlar: getCount, yönetilen parça sayısını gösterir; burada üçtür;
- 11-15. satırlar: getItem(i), i numaralı parçayı döndürür. Burada tüm parçalar [PlaceholderFragment] türünde ve aynı olacaktır;
- 24-35. satırlar: getPageTitle(int i), i numaralı parçanın başlığını döndürür;
1.7.4.2. Görüntülenen parçalar
![]() |
Etkinliğin parçaları burada hepsi aynı türdedir ve hepsi aşağıdaki XML [fragment_main] görünümüne aittir:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="exemples.android.MainActivity$PlaceholderFragment">
<TextView
android:id="@+id/section_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
- 1-16. satırlar: [RelativeLayout] türünde bir layout;
- 11-14. satırlar: görünümün tek bileşeni (parça): [section_label] ile tanımlanan bir [TextView];
[MainActivity]'te yönetilen parçalar, aşağıdaki [PlaceholderFragment] türündedir:
// bir parça
public static class PlaceholderFragment extends Fragment {
// parçada görüntülenen bir metin
private static final String ARG_SECTION_NUMBER = "section_number";
public PlaceholderFragment() {
}
// bir parça döndürür ve parametre olarak geçirilen parça numarasını içerir
public static PlaceholderFragment newInstance(int sectionNumber) {
// parça
PlaceholderFragment fragment = new PlaceholderFragment();
// gömülü bilgi
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
// sonuç
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// [fragment_main] görünümü örneklenir
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
// [TextView] bulundu
TextView textView = (TextView) rootView.findViewById(R.id.section_label);
// içeriği değiştirildi
textView.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
// görünüm döndürülür
return rootView;
}
}
- 2. satır: [PlaceholderFragment] sınıfı, Android [Fragment] sınıfını genişletir. Bu genellikle her zaman böyledir;
- 2. satır: [PlaceholderFragment] sınıfı statiktir. Bu sınıfın [newInstance] yöntemi (10. satır), [PlaceholderFragment] türünde örnekler elde etmeyi sağlar;
- satır 10-19: [newInstance] yöntemi, [PlaceholderFragment] türünde bir nesne oluşturur ve döndürür;
- satır 14-16: parça bir argümanla oluşturulur;
Bir fragman, 22. satırdaki [onCreateView] yöntemini tanımlamalıdır. Bu yöntem, fragmana bağlı görünümü döndürmelidir.
- 25. satır: [fragment_main.xml] görünümü parçaya ilişkilendirilir;
- satır 27: bu görünüm, kimlik numarası aracılığıyla referansı alınan bir [TextView] bileşeni içerir;
- 29. satır: [TextView]'te bir metin görüntülenir;
- [getString], üst sınıf [AppCompatActivity]'in bir yöntemidir;
- 1. argüman bir bileşen numarasıdır. [R.string.section_format], [res / values / strings.xml] dosyasında (aşağıdaki 4. satır) [section_format] ile tanımlanan bileşenin numarasını belirtir:
<resources>
<string name="app_name">Exemple-06</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
</resources>
- (devamı)
- yukarıdaki (d) satırındaki %1$d, 1 numaralı argümanın (%1) bir tamsayı ($d) olarak biçimlendirilmesi gerektiğini belirtir;
- [getString]'in ikinci argümanı, yukarıdaki (d) satırındaki $1 argümanına verilecek değerdir;
- [getArguments], parçanın argüman paketinin referansını verir. Burada, her argümanın aşağıdaki paketle oluşturulduğunu unutmamak gerekir (f-h satırları):
// bir bilgi içeren bir parça döndürür: parametre olarak geçirilen parça numarası
public static PlaceholderFragment newInstance(int sectionNumber) {
// parça
PlaceholderFragment fragment = new PlaceholderFragment();
// gömülü bilgi
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
// sonuç
return fragment;
}
- (devam)
- getArguments().getInt(ARG_SECTION_NUMBER) bu nedenle yukarıdaki (g) ve (b) satırlarının [sectionNumber] değerini döndürür;
- 31. satır: Bu şekilde oluşturulan görünüm görüntülenir;
1.7.4.3. Menü yönetimi
Oluşturulan uygulamada bir menü bulunmaktadır:
![]() |
[menu_main.xml] dosyasının içeriği şöyledir:
<menu 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"
tools:context="exemples.android.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
- 1-9. satırlar: menü;
- 5-8. satırlar: [action_settings] (5. satır) ile tanımlanan bir menü öğesi;
- 6. satır: menü seçeneğinin etiketi. Bu, [res / values / strings.xml] dosyasında bulunur (aşağıdaki (c) satırı):
<resources>
<string name="app_name">Exemple-06</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
</resources>
Yukarıdaki kod, aşağıdaki görsele karşılık gelir (menü, Android çalıştırma penceresinin sağ üst köşesindedir):
![]() | ![]() |
Bu menü, [MainActivity] etkinliğinde şu şekilde yönetilir:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Menüyü şişirin; bu, varsa eylem çubuğuna öğeler ekler.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Eylem çubuğu öğelerine yapılan tıklamaları burada işleyin. Eylem çubuğu
// Ana Sayfa/Yukarı düğmesine yapılan tıklamaları otomatik olarak işleyecektir,
// AndroidManifest.xml'te bir üst etkinlik belirlediğiniz sürece.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
- 1-6. satırlar: Sistem, uygulama menüsünü oluşturmaya hazır olduğunda bu yöntem çağrılır. [Menu menu] giriş parametresi, henüz seçenekleri olmayan boş bir menüdür;
- 4. satır: [res / menu / menu_main.xml] dosyası kullanılır. Parametre olarak geçirilen [Menu menu] nesnesine, bu dosyada tanımlanan menü seçenekleri atanır;
- 5. satır: menünün oluşturulduğu belirtilir;
- satır 8-21: menüdeki bir seçeneğe tıklandığında [onOptionsItemSelected] yöntemi yürütülür;
- 13. satır: Tıklanan menü seçeneğinin referansı;
- satır 16-18: Tıklanan seçenek [action_settings] kimlik numaralı seçenek ise, hiçbir işlem yapılmaz ve olayın işlendiği belirtilir (satır 17);
- 20. satır: olay üst sınıfa aktarılır;
Bu menüde neler olduğunu daha iyi görebilmek için, önceki koda günlük kayıtları ekliyoruz:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d("menu", "création menu en cours");
// Menüyü şişirin; bu, eylem çubuğu varsa bu çubuğa öğeler ekler.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d("menu", "onOptionsItemSelected");
// Eylem çubuğu öğelerine yapılan tıklamaları burada işleyin. Eylem çubuğu
// AndroidManifest.xml'te bir üst etkinlik belirlediğiniz sürece
// AndroidManifest.xml'te bir üst etkinlik belirlediğiniz sürece.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
Log.d("menu", "action_settings selected");
return true;
}
// üst
return super.onOptionsItemSelected(item);
}
1.7.4.4. Yüzen düğme
Oluşturulan görünümde bir yüzen düğme bulunmaktadır:
![]() |
Bu bileşen, [activity-main.xml] ana görünümünde tanımlanmıştır:
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"/>
- satır, Android destek ekibi tarafından sağlanan bir resme, yani bir zarf resmine atıfta bulunur.
Bu bileşen, [MainActivity] sınıfında şu şekilde yönetilir:
// yüzen düğme
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
- 2. satır: aktiviteyle ilişkili görünümdeki (activity_main) yüzen düğmenin referansı alınır;
- 3-9. satırlar: Düğmeye yapılan tıklamayı yönetmek için bir işleyici atanır;
- 6. satır: [Snackbar] sınıfı, [Snackbar.make] yöntemiyle görünüm üzerinde geçici mesajlar görüntülenmesini sağlar. İlk argüman, [Snackbar]'in mesajı görüntüleyeceği üst görünümü arayacağı bir görünümdür. Burada [view], tıklanan zarfın görünümüdür (satır 5). Bulunacak üst görünüm, [activity_main] görünümü olacaktır. İkinci argüman, görüntülenecek mesajdır. Üçüncü argüman ise görüntüleme süresidir (SHORT veya LONG);
- 7. satır: Görüntülenen mesaja tıklanarak bir eylem tetiklenebilir. Burada mesaja tıklamaya herhangi bir eylem atanmamıştır. Son olarak, [show] yöntemi mesajı görüntüler;
Yüzen düğmeye tıklandığında şu görsel sonuç ortaya çıkar:
![]() |
1.7.5. Projenin çalıştırılması
Oluşturulan kodun ayrıntılarını açıkladığımıza göre, kodun yürütülüşünü daha iyi anlayabiliriz:

i numaralı sekmeye tıklandığında, i numaralı parça görünüm konteynerinde görüntülenir. Bu, [4]'te görüntülenen metinden anlaşılabilir. Ayrıca, fareyle görünümü sağa veya sola kaydırarak (swipe) bir sekmeden diğerine geçilebildiği de görülebilir. Bu davranışı kontrol edebileceğimizi göreceğiz.
[6]'teki menü seçeneğine tıklandığında, aşağıdaki günlükler görüntülenir:
![]() |
1.7.6. Parçaların yaşam döngüsü
![]() | ![]() |
- [1]'te, [onCreateView] yöntemi ve sonraki yöntemlerin, parçanın ilk görüntülenmesi sırasında ve etkinliğin onu yeniden görüntülemesi gerektiği her seferinde yürütüldüğü görülür;
Etkinliğin ve parçaların yaşam döngüsünü takip etmek için, [MainActivity] koduna aşağıdaki günlük kayıtlarını ekliyoruz:
// oluşturucu
public MainActivity(){
Log.d("MainActivity","constructor");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d("MainActivity","onCreate");
// üst öğe
super.onCreate(savedInstanceState);
...
}
// bir parça
public static class PlaceholderFragment extends Fragment {
// parçada görüntülenen bir metin
private static final String ARG_SECTION_NUMBER = "section_number";
public PlaceholderFragment() {
Log.d("PlaceholderFragment", "constructor");
}
// bir parça döndürür ve parametre olarak geçirilen parça numarasını içerir
public static PlaceholderFragment newInstance(int sectionNumber) {
Log.d("PlaceholderFragment", String.format("newInstance %s", sectionNumber));
// parça
PlaceholderFragment fragment = new PlaceholderFragment();
...
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("PlaceholderFragment", String.format("newInstance %s", getArguments().getInt(ARG_SECTION_NUMBER)));
...
}
}
}
Projeyi yeniden çalıştırıyoruz. İlk günlük kayıtları şunlardır:
- 1. satır: etkinliğin oluşturulması;
- 2. satır: [onCreate] yönteminin çalıştırılması;
- 3-4. satırlar: 1 numaralı parçanın örneklenmesi;
- satır 5-6: 2 numaralı parçanın örneklenmesi;
- 7. satır: 2 numaralı parçanın başlatılması;
- satır 8: 1 numaralı parçanın başlatılması;
- 9. satır: etkinliğin menüsünün oluşturulması;
Burada, parçaların oluşturulmasını yöneten kodu hatırlamak gerekir:
// parça yöneticisi
// ana görünümde görüntülenecek parçalar bu yöneticiye istenir
// [getItem] ve [getCount] yöntemlerini tanımlamalıdır - diğerleri isteğe bağlıdır
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
// parça numarası ve konumu
@Override
public Fragment getItem(int position) {
// [PlaceHolder] fragmanı örneklenir ve işlenir
return PlaceholderFragment.newInstance(position + 1);
}
...
- satır 11-15: Parça konteyneri her talep ettiğinde, [newInstance] tarafından bir parça örneklenir;
Yukarıdaki günlükler, ilk iki parçanın örneklendiğini ve başlatıldığını göstermektedir.
Şimdi 2 numaralı sekmeye tıklayalım. Yeni günlük kayıtları şu şekildedir:
- 1-3. satırlar: 3 numaralı parça oluşturulmuş ve başlatılmıştır. Hatırlatmak gerekirse, görüntülenen parça 2 numaralı parçadır;
Şimdi 3 numaralı sekmeye tıklayalım. Burada herhangi bir günlük kaydı yok. Muhtemelen görüntülenecek 3 numaralı parça zaten örneklenmiş olduğu içindir. Şimdi 1 numaralı sekmeye geri dönelim. Günlük kayıtları şu şekildedir:
1 numaralı parça yeniden örneklenmiyor, ancak [onCreateView] yöntemi yeniden yürütülüyor. Bu davranış, diğer iki parça için de tekrarlanıyor.
Bu günlüklerden şu sonuçlar çıkarılabilir:
- etkinlik bir kez örneklendirilip başlatılmıştır;
- her bir parçanın bir kez örneklendiği;
- her parçanın [onCreateView] yöntemi birkaç kez çalıştırılmıştır;
Bilmeniz gereken ve günlüklerin de doğruladığı şey, varsayılan olarak i numaralı bir parça görüntülendiğinde, i-1 ve i+1 numaralı parçaların henüz örneklenmemiş olmaları durumunda örneklendirilmesidir. Bu durum, örneğin başlangıçta i numaralı parçanın görüntülenmesi gerekirken, 1 ve 2 numaralı parçaların örneklendirilip başlatılmış olmasını açıklar. Günlük kayıtlarının da gösterdiği üzere, i numaralı parça birkaç kez görüntülenmiş olsa bile [getItem(i)] yöntemi yalnızca bir kez çağrılır. Dolayısıyla, i numaralı parçayı görüntülemesi gereken [ViewPager] parça konteyneri, bu parçayı [SectionsPagerAdapter] parça yöneticisinden bir kez talep ediyor gibi görünüyor. Daha sonra bunu tekrar talep etmiyor ve elde ettiği parçayı kullanmaya devam ediyor.
Son olarak, günlükler parçaların [onCreateView] yöntemiyle ilgili ipuçları vermektedir:
- başlangıçta, 1 ve 2 numaralı parçalar oluşturulmuş ve [onCreateView] yöntemleri yürütülmüştür;
- parça 1'den parça 2'ye geçildiğinde, parça 2'nin [onCreateView] yöntemi yeniden çalıştırılmaz. Dolayısıyla bu yöntem, parça 2'yi güncellemek için kullanılamaz. Oysa kullanıcı, parça 1 ile, sonucunun parça 2 tarafından görüntülenmesi gereken bir işlem yapmış olabilir. [onCreateView] yönteminin parça 2'yi güncellemek için kullanılamayacağı açıktır. Başka bir çözüm bulunması gerekecektir;
1.8. Örnek-07: [AA] kütüphanesi kullanılarak yeniden yazılmış Örnek-06
1.8.1. Projenin oluşturulması
Android açıklamalarını eklemek için [Exemple-06] projesini [Exemple-07] olarak kopyalayacağız. Bunun için 1.4. paragrafındaki prosedürü izleyin. Aşağıdaki sonucu elde ederiz:
![]() | ![]() |
1.8.2. Gradle Yapılandırması
![]() |
[build.gradle] dosyasını şu şekilde değiştiriyoruz:
buildscript {
repositories {
mavenCentral()
}
dependencies {
// Android'in Gradle eklentisi 0.11 sürümünden itibaren, android-apt >= 1.3 sürümünü kullanmanız gerekir
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "exemples.android"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
def AAVersion = '4.0.0'
dependencies {
apt "org.androidannotations:androidannotations:$AAVersion"
compile "org.androidannotations:androidannotations-api:$AAVersion"
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
}
[Android Annotations] kütüphanesini kullanmak için gerekli yapılandırmayı ekledik (bkz. paragraf 1.4).
1.8.3. İlk açıklamaların eklenmesi AA
[MainActivity] dosyasında AA açıklamalarını oluşturacağız:
![]() |
[MainActivity] sınıfı şu şekilde değişmektedir:
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
// fragman yöneticisi
private SectionsPagerAdapter mSectionsPagerAdapter;
// fragman konteyneri
@ViewById(R.id.container)
protected MyPager mViewPager;
// sekme yöneticisi
@ViewById(R.id.tabs)
protected TabLayout tabLayout;
// yüzen düğme
@ViewById(R.id.fab)
protected FloatingActionButton fab;
// oluşturucu
public MainActivity() {
Log.d("MainActivity", "constructor");
}
@AfterViews
protected void afterViews() {
Log.d("MainActivity", "afterViews");
// araç çubuğu
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// parça yöneticisi
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// parça konteyneri, parça yöneticisiyle ilişkilidir
// yani, parça konteynerindeki i numaralı parça, parça yöneticisi tarafından sağlanan i numaralı parçadır
mViewPager.setAdapter(mSectionsPagerAdapter);
// sekme çubuğu da parça konteyneriyle ilişkilidir
// yani i numaralı sekme, kapsayıcıdaki i numaralı parçayı görüntüler
tabLayout.setupWithViewPager(mViewPager);
// yüzen düğme
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
- 1. satır: [@EActivity] açıklaması, [MainActivity]'i AA tarafından yönetilen bir sınıf haline getirir. Bu sınıfın [R.layout.activity_main] parametresi, etkinliğe bağlı [activity_main.xml] görünümünün tanımlayıcısıdır;
- 11-12. satırlar: [R.id.tabs] ile tanımlanan bileşen, [tabLayout] alanına enjekte edilir. Bu, sekme yöneticisidir;
- satır 14-15: [R.id.fab] ile tanımlanan bileşen, [fab] alanına enjekte edilir. Bu, yüzen düğmedir;
- satır 23-50: Daha önce [onCreate] yönteminde bulunan kod, adı herhangi bir yöntem içine taşınır, ancak bu yöntem [@AfterViews] ile etiketlenir (satır 23). Bu şekilde etiketlenen yöntemde, [@ViewById] ile etiketlenmiş tüm görsel arayüz bileşenlerinin başlatılmış olduğundan emin olunur;
- ayrıca, aktivitenin yaşam döngüsünü görebilmek için günlük kayıtları eklenmiştir;
[@EActivity] etiketinin, projenin asıl etkinliği olacak [MainActivity_] sınıfını oluşturacağını hatırlayalım. Bu nedenle, [AndroidManifest.xml] dosyasını şu şekilde değiştirmemiz gerekir:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity_"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- 12. satır: yeni aktivite.
Bu noktada, projeyi yeniden çalıştırın ve sekmeli arayüzün hâlâ görüntülendiğini kontrol edin.
1.8.4. Parçaların yeniden yazılması
Projenin parçacık yönetimini gözden geçireceğiz. Şu anda [PlaceholderFragment] sınıfı, [MainActivity] etkinliğinin statik bir iç sınıfıdır. Daha yaygın bir kullanım durumuna, yani parçaların harici sınıflarda tanımlandığı duruma geri döneceğiz. Ayrıca, parçalar için AA anotasyonlarını ekliyoruz.
[Exemple-07] projesi şu şekilde gelişiyor:
![]() |
Yukarıda, [MainActivity] sınıfının dışına taşınan [PlaceholderFragment] sınıfı görülüyor. Bu sınıf şu şekilde yeniden yazılmıştır:
package exemples.android;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
// bir parça, bir parça konteyneri tarafından görüntülenen bir görünümdür
@EFragment(R.layout.fragment_main)
public class PlaceholderFragment extends Fragment {
// görsel arayüz bileşeni
@ViewById(R.id.section_label)
protected TextView textViewInfo;
// parça numarası
private static final String ARG_SECTION_NUMBER = "section_number";
// oluşturucu
public PlaceholderFragment() {
Log.d("PlaceholderFragment", "constructor");
}
@AfterViews
protected void afterViews() {
Log.d("PlaceholderFragment", String.format("afterViews %s", getArguments().getInt(ARG_SECTION_NUMBER)));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d("PlaceholderFragment", String.format("onCreateView %s", getArguments().getInt(ARG_SECTION_NUMBER)));
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onResume() {
Log.d("PlaceholderFragment", String.format("onResume %s", getArguments().getInt(ARG_SECTION_NUMBER)));
// üst öğe
super.onResume();
// görüntüleme
if (textViewInfo != null) {
Log.d("PlaceholderFragment", String.format("onResume setText %s", getArguments().getInt(ARG_SECTION_NUMBER)));
textViewInfo.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
}
}
}
- 15. satır: parça, [@EFragment] etiketiyle etiketlenmiştir; bu etiketin parametresi, parçayla ilişkili XML görünümünün kimliğidir; burada bu, [fragment_main.xml] görünümüdür;
- 19-20. satırlar: [textViewInfo] alanına, [R.id.section_label] ile tanımlanan ve [TextView] türünde olan [fragment_main.xml] bileşeninin referansı eklenir (aşağıdaki (l) satırı):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="exemples.android.MainActivity$PlaceholderFragment">
<TextView
android:id="@+id/section_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
- 42-52. satırlar: [onResume] yöntemi, parçaya ilişkin görünüm görüntülenmeden önce yürütülür. Bu yöntem, görüntülenecek görsel arayüzü güncellemek için kullanılabilir;
- 47. satır: üst sınıfın aynı adlı yöntemi çağrılmalıdır;
- satır 49: [onResume] yönteminin, 20. satırdaki alanın başlatılmasından önce çalıştırılıp çalıştırılamayacağı konusunda bir belirsizlik vardır. Parçanın yaşam döngüsünü takip etmek için yerleştirilen günlük kayıtları bize bunu gösterecektir. Şu an için ve tedbir amaçlı olarak, bir null testi yapıyoruz;
- 51. satır: [textViewInfo] alanındaki bilgileri, fragmanın oluşturulurken kendisine aktarılan tamsayı argümanıyla güncelliyoruz;
[MainActivity] sınıfı, iç sınıfı olan [PlaceholderFragment]'i kaybeder ve fragman yöneticisi şu şekilde değişir:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private Fragment[] fragments;
// parça sayısı
private static final int FRAGMENTS_COUNT = 3;
// parça numarası
private static final String ARG_SECTION_NUMBER = "section_number";
// üretici
public SectionsPagerAdapter(FragmentManager fm) {
// üst öğe
super(fm);
// parça tablosunun başlatılması
fragments = new Fragment[FRAGMENTS_COUNT];
for (int i = 0; i < fragments.length; i++) {
// bir parça oluşturulur
fragments[i] = new PlaceholderFragment_();
// parçaya argümanlar geçirilebilir
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, i + 1);
fragments[i].setArguments(args);
}
}
// parça numarası ve konumu
@Override
public Fragment getItem(int position) {
Log.d("MainActivity", String.format("getItem[%s]", position));
return fragments[position];
}
// yönetilen parça sayısını verir
@Override
public int getCount() {
return fragments.length;
}
// isteğe bağlıdır - yönetilen parçalara bir başlık verir
@Override
public CharSequence getPageTitle(int position) {
return String.format("Onglet n° %s", (position + 1));
}
}
- 4. satır: parçalar bir diziye yerleştirilir;
- 16-23. satırlar: Parça dizisinin başlatılması yapıcıda gerçekleştirilir. Parçalar, [PlaceholderFragment_] türündedir (18. satır), [PlaceholderFragment] değildir. [PlaceholderFragment] sınıfı, aslında bir AA anotasyonu ile anotlanmıştır ve [PlaceholderFragment]'ten türetilen bir [PlaceholderFragment_] sınıfı oluşturacaktır; faaliyetin kullanması gereken sınıf budur. Oluşturulan her parçaya, parça tarafından görüntülenecek bir tamsayı argümanı aktarılır;
- 42-45. satırlar: parçaların başlıklarını değiştirdik. Bunlar aynı zamanda sekme başlıkları olduğu için sekme çubuğunda bir değişiklik görmemiz gerekir;
[Make] ve [1] dosyalarını içeren bu projeyi derleyelim:
![]() | ![]() |
- [2] olarak derleyelim, AA kütüphanesi tarafından oluşturulan sınıfların [app / build / generated / source / apt / debug] klasöründe yer aldığını görüyoruz ([2]'i görmek için [Project] perspektifinde olmanız gerekir);
[Exemple-07] projesini çalıştırın ve hala çalıştığını kontrol edin.
1.8.5. Günlüklerin incelenmesi
Uygulama başlatıldığında günlükler şu şekildedir:
- 1. satır: tek etkinliğin oluşturulması;
- 2. satır: aktivitenin [afterViews] yöntemi: [@ViewById] ile işaretlenmiş alanları başlatılır;
- satır 3-5: üç parçanın oluşturulması;
- satır 6-7: [ViewPager] parça konteyneri ilk iki parçayı ister;
- satır 8-9: 2. parçanın yöntemleri;
- satır 10-11: parça 1'in yöntemleri;
- satır 12-13: 1. parçanın [onResume] yöntemi;
- satır 14-15: parça 2'nin [onResume] yöntemi;
- 16. satır: etkinlik menüsünün oluşturulması;
Daha önce sorulan bir sorunun cevabını burada görebiliriz: örneğin, parça 1'deki [onResume] yöntemi (satır 12), parça 1'deki [afterViews] yönteminden (satır 11) sonra yürütülür. Dolayısıyla, [onResume] yöntemi yürütüldüğünde, [@ViewById] tarafından işaretlenen alanları kullanabilir. Artık [onResume] yöntemini şu şekilde yazabiliriz:
@Override
public void onResume() {
Log.d("PlaceholderFragment", String.format("onResume %s", getArguments().getInt(ARG_SECTION_NUMBER)));
// üst öğe
super.onResume();
// görüntüleme
textViewInfo.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));
}
Şimdi sekme 1'den sekme 2'ye geçelim. Yeni günlük kayıtları şunlardır:
- 1. satır: [ViewPager] parça konteyneri 3 numaralı parçayı talep ediyor;
- 2-3. satırlar: 3 numaralı parçanın yöntemleri. Bu parçanın, uygulamanın başlatılmasıyla birlikte örneklendirildiğini hatırlatırız;
- satır 4-5: 3 numaralı parçanın [onResume] yöntemi yürütülür. Ekranda görüntülenenin 2 numaralı parça olduğunu hatırlatırız;
Şimdi sekme 2'den sekme 3'e geçelim. Herhangi bir günlük kaydı yok. Dolayısıyla, 3 numaralı parçanın [onCreateView, afterViews, onResume] yöntemlerinden hiçbiri yürütülmüyor. [Hello World from section:3] metni yalnızca, bu metin bir önceki adımda 2 numaralı parçanın görüntülenmesi sırasında zaten oluşturulmuş olduğu için doğru şekilde görüntülenmektedir. Hatırlanacağı üzere, bu adımda 3 numaralı parçanın [onResume] yöntemi yürütülmüştü. Burada, [onCreateView] yönteminin yanı sıra [onResume] yönteminin de 3 numaralı parçayı güncellemek için kullanılamayacağı anlaşılmaktadır. Parçanın görüntülediği metni değiştirmek gerekseydi, bu iki yöntemden hiçbiri bunu yapamazdı.
Şimdi, 3 numaralı sekmeden 1 numaralı sekmeye geri dönelim. Bu durumda günlük kayıtları şu şekildedir:
Görüldüğü gibi, 1 numaralı parçadaki tüm yöntemler yürütülmüştür. getItem yönteminin çağrılmadığı görülmektedir. Daha önce de belirtildiği gibi, bu yöntem her parça için yalnızca bir kez çağrılır;
Şimdi, sekme 1'den yanındaki sekme 2'ye geçelim. Aşağıdaki günlük kayıtları elde ediyoruz:
Şaşırtıcı, değil mi? 3 numaralı parçanın tüm yöntemleri yeniden yürütülüyor.
Bu durumu anlamak için, varsayılan olarak parça konteynerinin i numaralı parçayı görüntüleyeceği zaman i-1, i ve i+1 numaralı parçaları da başlattığını hatırlamamız gerekir. Bu bilgiyi göz önünde bulundurarak logları tekrar inceleyelim.
Öncelikle, uygulamanın başlatılması sırasındaki günlükler:
Parça konteyneri 1 numaralı parçayı görüntüleyeceği için, 1 ve 2 numaralı parçalar başlatılır (8-15. satırlar).
Şimdi sekme 1'den sekme 2'ye geçiyoruz:
Parça konteyneri 2. parçayı göstereceği için, 1., 2. ve 3. parçaların başlatılması gerekir. 1. ve 2. parçalar bir önceki adımda zaten başlatılmıştır. 3. parça 2-5. satırlarda başlatılır.
Sekme 2'den sekme 3'e geçiyoruz. Herhangi bir günlük kaydı yok. Parça konteyneri parça 3'ü görüntüleyeceği için, parça 2 ve 3'ün başlatılması gerekir. Ancak bir önceki adımdan beri bunlar zaten başlatılmış durumdadır. Burada görmediğimiz şey, parça 3'e bitişik olmayan parça 1'in, bellekte saklanmayan durumunu kaybetmesidir.
Sekme 3'ten sekme 1'e geçiyoruz. Günlükler şu şekildedir:
Parça konteyneri 1. parçayı göstereceği için, 2. parçanın da başlatılması gerekir. Bu, bir önceki adımdan beri yapılmıştır. Aynı adımda, parça 1'in durumu kaybolmuştu. Bu nedenle 1-4. satırlarda yeniden başlatılır. Burada görülmeyen şey, parça 1'e bitişik olmayan parça 3'ün durumunu kaybetmesi ve bu durumun bellekte saklanmamasıdır.
Sekme 1'den bitişik sekme 2'ye geçildiğinde, aşağıdaki günlük kayıtları elde edilir:
Parça konteyneri 2. parçayı görüntüleyeceği için 1., 2. ve 3. parçaların başlatılması gerekir. 1. ve 2. parçalar bir önceki adımda zaten başlatılmıştır. 3. parça ise 1-4. satırlara sıfırlanır.
Ne öğrendik?
- Fragmanların varsayılan yönetimi oldukça özeldir ve kafayı yormamak için bunu bilmek gerekir. Bu yönetim şeklini değiştirebiliriz ve bunu biraz ileride yapacağız;
- bu varsayılan yönetimle, görüntülenecek parçayı güncellemek için [onCreateView, onResume] yöntemlerinden hiçbiri kullanılamaz, çünkü bunların çalıştırılacağından emin olamayız;
1.8.6. onDestroyView
[onDestroyView] yöntemi, parçaların yaşam döngüsünün bir parçasıdır (bkz. paragraf 1.7.6):
![]() | ![]() |
Bir parçanın yaşam döngüsünde şunu görüyoruz:
- [onCreateView] yöntemi birden fazla kez çalıştırılabilir;
- daha sonra [onCreateView] yöntemine dönmeden önce, mutlaka [onDestroyView] ve [2] yöntemlerinden geçilmesi gerekir;
Bu yöntemleri, yaşam döngülerini daha iyi takip edebilmek için parçalara ekleyeceğiz. Parçanın kodu şu şekilde olur:
package exemples.android;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
// bir parça, bir parça kapsayıcısı tarafından görüntülenen bir görünümdür
@EFragment(R.layout.fragment_main)
public class PlaceholderFragment extends Fragment {
...
@Override
public void onDestroyView() {
// günlük
Log.d("PlaceholderFragment", String.format("onDestroyView %s", getArguments().getInt(ARG_SECTION_NUMBER)));
// üst öğe
super.onDestroyView();
}
}
Uygulamayı çalıştıralım. İlk günlük kayıtları şu şekildedir:
- 1. satır: tek etkinliğin oluşturulması;
- 2. satır: aktivitenin [afterViews] yöntemi: [@ViewById] ile işaretlenmiş alanları başlatıldı;
- satır 3-5: üç parçanın oluşturulması;
- satır 6-7: [ViewPager] parça konteyneri ilk iki parçayı ister;
- satır 8-9: 2. parçanın görünümü oluşturulur (mutlaka görünür hale getirilmez);
- satır 10-11: 1. parçanın görünümü oluşturulur (mutlaka görünür hale getirilmez);
- satır 12-13: 1. parçanın [onResume] yöntemi;
- satır 14-15: parça 2'nin [onResume] yöntemi;
- satır 16: etkinlik menüsünün oluşturulması;
- sekmeden 3. sekmeye geçelim:
06-03 02:50:02.685 2346-2346/exemples.android D/MainActivity: getItem[2]
06-03 02:50:02.685 2346-2346/exemples.android D/PlaceholderFragment: onCreateView 3
06-03 02:50:02.686 2346-2346/exemples.android D/PlaceholderFragment: afterViews 3
06-03 02:50:02.686 2346-2346/exemples.android D/PlaceholderFragment: onResume 3
06-03 02:50:02.686 2346-2346/exemples.android D/PlaceholderFragment: onResume setText 3
06-03 02:50:03.024 2346-2346/exemples.android D/PlaceholderFragment: onDestroyView 1
- 1. satır: parça konteyneri 3. parçayı ister;
- satır 2-3: 3. parçanın görünümü oluşturulur (mutlaka görüntülenmez);
- satır 4-5: 3. parçanın [onResume] yöntemi yürütülür;
- 6. satır: 1. parçanın [onDestroyView] yöntemi yürütülür. Bu, kullanıcının 1. parçaya veya bitişik bir parçaya geri döndüğünde, bu parçanın yaşam döngüsünün yeniden yürütüleceği anlamına gelir;
Sekme 3'ten sekme 1'e geri dönülür:
06-03 02:53:46.255 2346-2346/exemples.android D/PlaceholderFragment: onCreateView 1
06-03 02:53:46.256 2346-2346/exemples.android D/PlaceholderFragment: afterViews 1
06-03 02:53:46.256 2346-2346/exemples.android D/PlaceholderFragment: onResume 1
06-03 02:53:46.256 2346-2346/exemples.android D/PlaceholderFragment: onResume setText 1
06-03 02:53:46.604 2346-2346/exemples.android D/PlaceholderFragment: onDestroyView 3
- 1-4. satırlar: Parça 1'in yaşam döngüsü, [onDestroyView] işlemine tabi tutulduğu için yeniden yürütülür;
- 5. satır: Şimdi de parça 3'ün [onDestroyView] yöntemi yürütülür. Burada da, kullanıcı parça 3'e veya bitişik bir parçaya geri döndüğünde, bu parçanın yaşam döngüsü yeniden yürütülecektir;
1.8.7. setUserVisibleHint
Yaşam döngüsünün [onCreateView] yöntemi, parçaya ilişkili görünümü oluşturur ancak onu mutlaka görünür hale getirmez. Şimdi bunu inceleyeceğiz. [Fragment.setUserVisibleHint] yöntemi, parçanın görünürlüğü her değiştiğinde yürütülür. Bu yöntemi parça koduna ekliyoruz:
package exemples.android;
....
// bir fragman, bir fragman kapsayıcısı tarafından görüntülenen bir görünümdür
@EFragment(R.layout.fragment_main)
public class PlaceholderFragment extends Fragment {
// görsel arayüz bileşeni
@ViewById(R.id.section_label)
protected TextView textViewInfo;
...
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
// günlük
Log.d("PlaceholderFragment", String.format("setUserVisibleHint %s isVisibleToUser=%s", getArguments().getInt(ARG_SECTION_NUMBER), isVisibleToUser));
}
}
Başlangıçta günlükler şu şekildedir:
06-03 03:06:13.263 20586-20586/exemples.android D/MainActivity: constructor
06-03 03:06:13.291 20586-20586/exemples.android D/MainActivity: afterViews
06-03 03:06:13.324 20586-20586/exemples.android D/PlaceholderFragment: constructor
06-03 03:06:13.324 20586-20586/exemples.android D/PlaceholderFragment: constructor
06-03 03:06:13.329 20586-20586/exemples.android D/PlaceholderFragment: constructor
06-03 03:06:13.504 20586-20586/exemples.android D/MainActivity: getItem[0]
06-03 03:06:13.504 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=false
06-03 03:06:13.504 20586-20586/exemples.android D/MainActivity: getItem[1]
06-03 03:06:13.504 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 isVisibleToUser=false
06-03 03:06:13.504 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=true
06-03 03:06:13.511 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 1
06-03 03:06:13.519 20586-20586/exemples.android D/PlaceholderFragment: afterViews 1
06-03 03:06:13.519 20586-20586/exemples.android D/PlaceholderFragment: onResume 1
06-03 03:06:13.519 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 1
06-03 03:06:13.520 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 2
06-03 03:06:13.527 20586-20586/exemples.android D/PlaceholderFragment: afterViews 2
06-03 03:06:13.527 20586-20586/exemples.android D/PlaceholderFragment: onResume 2
06-03 03:06:13.527 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 2
06-03 03:06:15.075 20586-20586/exemples.android D/menu: création menu en cours
- 7. ve 9-10. satırlardaki günlük kayıtları, yalnızca 1 numaralı parçanın görünür hale geldiğini göstermektedir. Ayrıca, bu parçanın [onCreateView] yönteminin yürütülmesinden önce görünür hale geldiği de görülmektedir;
- sekmeden 2. sekmeye geçelim:
06-03 03:10:15.215 20586-20586/exemples.android D/MainActivity: getItem[2]
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 isVisibleToUser=false
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=false
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 isVisibleToUser=true
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 3
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: afterViews 3
06-03 03:10:15.216 20586-20586/exemples.android D/PlaceholderFragment: onResume 3
06-03 03:10:15.216 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 3
- 1. parça gizlenmiştir (3. satır), 2. parça görüntülenmektedir (4. satır);
Sekme 2'den sekme 3'e geçelim:
06-03 03:12:06.238 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 isVisibleToUser=false
06-03 03:12:06.238 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 isVisibleToUser=true
06-03 03:12:06.239 20586-20586/exemples.android D/PlaceholderFragment: onDestroyView 1
- 2. parça gizlenir (1. satır), 3. parça görüntülenir (2. satır);
Sekme 1'e geri dönelim:
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=false
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 isVisibleToUser=false
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=true
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 1
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: afterViews 1
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: onResume 1
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 1
06-03 03:13:10.789 20586-20586/exemples.android D/PlaceholderFragment: onDestroyView 3
- 3. parça gizlenmiştir (2. satır), 1. parça görüntülenmiştir (3. satır);
Ne öğrendik?
- [setUserVisibleHint] yöntemi, görüntülenecek parça için [isVisibleToUser]’ten true’e kadar olan özelliklerle bir kez yürütülür;
- bu yöntemin, parçanın yaşam döngüsü içinde ne zaman çalıştırılacağı belirlenemez. Dolayısıyla, 1 numaralı parça için [setUserVisibleHint, true] yöntemi, bu parçanın yaşam döngüsünün başlangıcındaki [onCreateView] yönteminden önce yürütülürken, 2 ve 3 numaralı parçalar için ise bunun tersi gerçekleşmiştir;
1.8.8. setOffscreenPageLimit
Yukarıdaki günlük kayıtları, [ViewPager] parça konteyneri i numaralı parçayı görüntülemeye hazırlanırken, henüz yapılmamışsa, i-1 ve i+1 numaralı bitişik parçaların yaşam döngüsünü yürüttüğünü göstermektedir. Bu işleyiş, [ViewPager].setOffscreenPageLimit yöntemi ile kontrol edilebilir:
Yukarıdaki komutla,
- [ViewPager] parça konteyneri i numaralı parçayı görüntülemeye hazırlanırken, henüz yapılmamışsa, [i-n, i+n] aralığındaki bitişik parçaların yaşam döngüsünü yürütür;
- ardından j numaralı parça görüntülenirse:
- aynı durum, [j-n, j+n] aralığındaki bitişik parçalar için de tekrarlanır;
- 1. adımda başlatılan ve yeni parçanın [j-n, j+n] komşuluğunda artık bulunmayan parçalar, bu durumda bir [onDestroyView] işlemi geçirebilir. Bununla birlikte, diğer uygulamalarda, özellikle de 3. bölümdeki uygulamada, bunun her zaman böyle olmadığını gözlemledim;
[MainActivity.afterViews] yöntemini şu şekilde değiştiriyoruz:
@AfterViews
protected void afterViews() {
Log.d("MainActivity", "afterViews");
// araç çubuğu
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// parça yöneticisi
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// parça konteyneri, parça yöneticisiyle ilişkilendirilmiştir
// yani, parça konteynerindeki i numaralı parça, parça yöneticisi tarafından sağlanan i numaralı parçadır
mViewPager.setAdapter(mSectionsPagerAdapter);
// parçalar arasında kaydırma engellenir
mViewPager.setSwipeEnabled(false);
// parçaların ofseti
mViewPager.setOffscreenPageLimit(mSectionsPagerAdapter.getCount() - 1);
// sekme çubuğu da parça konteyneriyle ilişkilendirilir
// yani i numaralı sekme, konteynerdeki i numaralı parçayı görüntüler
tabLayout.setupWithViewPager(mViewPager);
// yüzen düğme
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
- 20. satır: Başlatılacak bitişik parça sayısını, toplam parça sayısından 1 çıkararak belirliyoruz. Böylece, başlangıçta parça konteyneri 1 numaralı parçayı görüntüleyecekken, aynı anda 2, 3, ..., n numaralı parçaları da n=1+mSectionsPagerAdapter.getCount() - 1=mSectionsPagerAdapter.getCount() değerleriyle başlatacaktır. Dolayısıyla tüm parçalar başlatılacaktır. Görüntüleme penceresi başka bir parçaya geçtiğinde, parça konteyneri:
- yeni parçanın tüm komşu parçalarının zaten başlatılmış olduğunu tespit edecek ve bu nedenle bunları başlatmayacaktır;
- yeni parçanın bitişiği de tüm parçaları kapsadığından, parça konteyneri tarafından hiçbir parça "başlatma işlemi iptal edilmeyecektir";
Sonuç olarak, uygulamanın başlatılması sırasında tüm parçaların örneklendiğini ve başlatıldığını görmemiz gerekir; daha sonra ise bir daha asla görmemeliyiz. Şimdi logları inceleyerek bunu doğrulayacağız.
Başlangıçta şu günlük kayıtları görüyoruz:
- 4-6. satırlar: üç parçanın oluşturulması;
- 7, 9, 11. satırlar: parça konteyneri üç parçayı talep eder. Önceki sürümde iki parçayı talep ediyordu;
- satır 14-25: üç parçanın yaşam döngüsü yürütülür;
Şimdi sekme 1'den sekme 2'ye geçelim:
Sekme 2'den sekme 3'e geçelim:
Ardından sekme 3'ten sekme 1'e geçelim:
Günlükler bu teoriyi doğruluyor. Tüm parçalar başlatma sırasında örneklendirilmiş ve başlatılmıştır. Ardından, yaşam döngülerine ait yöntemler artık yürütülmemektedir. Burada, parçaların kullanımı büyük ölçüde kolaylaştıran, oldukça öngörülebilir bir işleyiş söz konusudur.
Bulmak istediğimiz şey, geliştirici tarafından seçilen fragmanların birbirine yakınlığı ne olursa olsun, görüntülenecek bir fragmanı güncellemenin bir yoludur. Günlükler bize iki şeyi gösterdi:
- [setUserVisibleHint, true] yöntemi her zaman görüntülenecek fragman için yürütülür, diğerleri için değil;
- bu olay, parçanın yaşam döngüsünden önce veya sonra gerçekleşebilir. Bu, geliştiricinin seçtiği parça bitişikliğine bağlıdır. Bu bir sorundur, çünkü yaşam döngüsü henüz gerçekleşmemişse, bu, parçanın [setUserVisibleHint, true] yöntemi ile güncellenemeyeceği anlamına gelir;
Fragmanların bitişikliği 1 olarak ayarlandığında, uygulamanın başlatılması sırasında kaydedilen günlükler şunlardı:
06-03 03:06:13.263 20586-20586/exemples.android D/MainActivity: constructor
06-03 03:06:13.291 20586-20586/exemples.android D/MainActivity: afterViews
06-03 03:06:13.324 20586-20586/exemples.android D/PlaceholderFragment: constructor
06-03 03:06:13.324 20586-20586/exemples.android D/PlaceholderFragment: constructor
06-03 03:06:13.329 20586-20586/exemples.android D/PlaceholderFragment: constructor
06-03 03:06:13.504 20586-20586/exemples.android D/MainActivity: getItem[0]
06-03 03:06:13.504 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=false
06-03 03:06:13.504 20586-20586/exemples.android D/MainActivity: getItem[1]
06-03 03:06:13.504 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 isVisibleToUser=false
06-03 03:06:13.504 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=true
06-03 03:06:13.511 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 1
06-03 03:06:13.519 20586-20586/exemples.android D/PlaceholderFragment: afterViews 1
06-03 03:06:13.519 20586-20586/exemples.android D/PlaceholderFragment: onResume 1
06-03 03:06:13.519 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 1
06-03 03:06:13.520 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 2
06-03 03:06:13.527 20586-20586/exemples.android D/PlaceholderFragment: afterViews 2
06-03 03:06:13.527 20586-20586/exemples.android D/PlaceholderFragment: onResume 2
06-03 03:06:13.527 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 2
06-03 03:06:15.075 20586-20586/exemples.android D/menu: création menu en cours
- Görüldüğü gibi, fragman 1 görünür hale geldiğinde, görünümü henüz oluşturulmamıştır. Dolayısıyla bu fragmana müdahale edilemez. Bu işlem, fragmanın yaşam döngüsü sırasında, örneğin [onCreateView] (11. satır) veya [onResume] (13-14. satırlar) yöntemlerinde gerçekleştirilebilir. AA anotasyonlarını kullandığımız için, normalde [onCreateView] yöntemini yazmamız gerekmez. Dolayısıyla, burada parça 1’i güncellemek için en uygun yöntem [onResume] gibi görünüyor;
Sekme 1'den sekme 2'ye geçtiğimizde, günlük kayıtları şu şekildeydi:
06-03 03:10:15.215 20586-20586/exemples.android D/MainActivity: getItem[2]
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 isVisibleToUser=false
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=false
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 isVisibleToUser=true
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 3
06-03 03:10:15.215 20586-20586/exemples.android D/PlaceholderFragment: afterViews 3
06-03 03:10:15.216 20586-20586/exemples.android D/PlaceholderFragment: onResume 3
06-03 03:10:15.216 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 3
Bu sefer, 2. parçayı güncellemek için sadece 4. satırdaki [setUserVisibleHint, true] yöntemi vardı;
Sekme 2'den sekme 3'e geçtiğimizde, günlük kayıtları şu şekildeydi:
06-03 03:12:06.238 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 isVisibleToUser=false
06-03 03:12:06.238 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 isVisibleToUser=true
06-03 03:12:06.239 20586-20586/exemples.android D/PlaceholderFragment: onDestroyView 1
Burada, 3. parçayı güncellemek için sadece 2. satırdaki [setUserVisibleHint, true] yöntemi var;
Sekme 3'ten sekme 1'e geçtiğimizde, günlük kayıtları şu şekildeydi:
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=false
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 isVisibleToUser=false
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 isVisibleToUser=true
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: onCreateView 1
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: afterViews 1
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: onResume 1
06-03 03:13:10.427 20586-20586/exemples.android D/PlaceholderFragment: onResume setText 1
06-03 03:13:10.789 20586-20586/exemples.android D/PlaceholderFragment: onDestroyView 3
Burada, parça 1'i güncellemek için parça 1'deki (6-7. satırlar) [onResume] yöntemini kullanmak gerekir.
Dolayısıyla bu örnekte, görüntülenecek bir parçayı güncellemek için iki yöntemimiz olduğunu görüyoruz: [setUserVisibleHint] ve [onResume].
Bu çözümü, her fragmanın kaç kez görüntülendiğini (buna “ziyaret” diyeceğiz) göstermesi gereken yeni bir projede uygulayacağız. Dolayısıyla, fragman her görüntülendiğinde ekranı güncellemek gerekecektir. İşte çözmeye çalıştığımız sorun da budur.
Bundan önce, bir aktivitenin veya parçanın yaşam döngüsünün son aşamasını, yani yok edildiği aşamayı inceleyeceğiz. Daha öncelikli diğer aktiviteler kullanılamayan kaynakları talep ederse, sistem bir aktiviteyi silme inisiyatifini alabilir. Bu kaynakları serbest bırakmak için sistem, belirli aktiviteleri silme inisiyatifini alacaktır. Bu durumda, aktivite ve fragmanların [onDestroy] yöntemi çağrılır.
1.8.9. OnDestroy
![]() | ![]() | ![]() |
Kullanıcının [5] menü seçeneği aracılığıyla etkinliği silmesine izin vereceğiz. Bunun için, [menu_main.xml] [1] dosyasına yeni bir menü seçeneği ekliyoruz:
<menu 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"
tools:context="exemples.android.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/action_terminate"
android:title="@string/action_terminate"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
İlk menü seçeneğini basitçe kopyalayıp yapıştırıyoruz ve sonucu uyarlıyoruz (9. ve 10. satırlar). Bu yeni seçeneğin adı, [strings.xml] [2] dosyasına eklenir:
<resources>
<string name="app_name">Exemple-07</string>
<string name="action_settings">Settings</string>
<string name="action_terminate">Terminate</string>
<string name="section_format">Hello World from section: %1$d</string>
</resources>
Son olarak, [MainActivity] sınıfında, [Terminate] seçeneğine yapılan tıklama işlenir:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d("menu", "onOptionsItemSelected");
// Eylem çubuğu öğelerine yapılan tıklamaları buradan işleyin. Eylem çubuğu
// Ana Sayfa/Yukarı düğmesine yapılan tıklamaları otomatik olarak işleyecektir,
// AndroidManifest.xml'te bir üst etkinlik belirlediğiniz sürece.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
Log.d("menu", "action_settings selected");
return true;
}
if (id == R.id.action_terminate) {
Log.d("menu", "action_terminate selected");
//ana etkinlik tamamlandığında
finish();
return true;
}
// ana etkinlik
return super.onOptionsItemSelected(item);
}
- 14-19. satırlar: 10-13. satırları kopyalayıp yapıştırıyoruz ve kodu yeni seçeneğe uyarlıyoruz;
- 17. satır: İşlem yazılım tarafından sonlandırıldı;
Şimdi bu yeni sürümü çalıştıralım, ardından ilk görünüm görüntülenir görüntülenmez [Terminate] menü seçeneğine tıklayalım. Günlük kayıtları şu şekildedir:
- 1-2. satırlar: [Terminate] seçeneğine tıklama;
- 4. satır: etkinliğin [onDestroy] yöntemi çağrıldı;
- 4-5. satırlar: 1. parçanın [onDestroyView] yöntemi çağrılır, ardından [onDestroy] yöntemi çağrılır;
- satır 6-9: bu işlem diğer iki parça için de tekrarlanır;
Dolayısıyla, etkinlik ve parçaların [onDestroy] yöntemi, etkinlik sistem, geliştirici veya kullanıcı tarafından silinecek olduğunda çağrılır. Bu yöntem, örneğin kullanıcının uygulamayı yeniden başlattığında bilgilere ulaşabilmek için bilgileri yerel olarak tablete kaydetmek amacıyla kullanılabilir.
1.9. Örnek-08: Değişken fragman komşuluğuna sahip bir fragmanın güncellenmesi
1.9.1. Projenin oluşturulması
[Exemple-07] projesini [Exemple-08] olarak çoğaltırız. Bunun için, 1.4. paragrafında [Exemple-02] projesini [Exemple-03] olarak çoğaltmak için açıklanan prosedürü izleriz.
![]() | ![]() |
1.9.2. [PlaceholderFragment] parçasının yeniden yazılması
[PlaceholderFragment] parçasının yeni kodu aşağıdaki gibidir. Bu kod, parçalara verilen bitişiklik türünden (1, kısmi, tam) bağımsız olarak çalışır:
package exemples.android;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.widget.TextView;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
// bir parça, bir parça kapsayıcısı tarafından görüntülenen bir görünümdür
@EFragment(R.layout.fragment_main)
public class PlaceholderFragment extends Fragment {
// görsel arayüz bileşeni
@ViewById(R.id.section_label)
protected TextView textViewInfo;
// veri
private boolean afterViewsDone = false;
private boolean initDone = false;
private String text;
private boolean isVisibleToUser = false;
private boolean updateDone = false;
private int numVisit = 0;
// parça numarası
private static final String ARG_SECTION_NUMBER = "section_number";
// oluşturucu
public PlaceholderFragment() {
Log.d("PlaceholderFragment", "constructor");
}
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
Log.d("PlaceholderFragment", String.format("afterViews %s %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
if (!initDone) {
// başlangıç metni
text = getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER));
// başlatma tamamlandı
initDone = true;
}
// geçerli metin gösterimi
textViewInfo.setText(text);
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
...
}
@Override
public void onDestroyView() {
...
}
@Override
public void onResume() {
...
}
// parça güncelleme
public void update() {
// yapılacak iş, ziyaret numarasına bağlıdır
if (numVisit > 1) {
// günlük
Log.d("PlaceholderFragment", String.format("update %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// metin değiştirildi
textViewInfo.setText(String.format("%s update(%s)", text, (numVisit - 1)));
}
}
// günlükler için yerel bilgiler
private String getInfos() {
return String.format("numVisit=%s, afterViewsDone=%s, isVisibleToUser=%s, initDone=%s, updateDone=%s", numVisit, afterViewsDone, isVisibleToUser, initDone, updateDone);
}
}
- 34-48. satırlar: [@AfterViews] yöntemi birden fazla kez çalıştırılabilir. Bu yöntem, fragmanın metnini başlatmak için kullanılıyordu (satır 42). Hala bunu yapıyoruz, ancak işlemin yalnızca bir kez gerçekleştirilmesi için, başlatmanın yapıldığını ve tekrar edilmesine gerek olmadığını belirtmek üzere bir boole değişkeni olan [initDone]'i (satır 44) kullanıyoruz;
- 56-59. satırlar: Parçanın bir sonraki sefer yeniden görüntüleneceği zaman yaşam döngüsünün yeniden yürütüleceğini belirtmek için [onDestroyView] yöntemini ekliyoruz;
- günlük kayıtları, [@AfterViews] yönteminden sonra iki yöntemin çalışabileceğini göstermiştir: [setUserVisibleHint] ve [onResume] yöntemleri. [onResume] yöntemi yalnızca parçanın yaşam döngüsü yürütüldüğünde çalıştırılır. [setUserVisibleHint] yöntemi ise [@AfterViews] yönteminden sonra her zaman çalıştırılmaz. Günlük kayıtları, [@AfterViews] yönteminden sonra bu ikisinden en az birinin yürütüldüğünü göstermiştir. Günlük kayıtları, [@AfterViews] yönteminden sonra her ikisinin birden yürütüldüğünü hiçbir zaman göstermemiştir. Ya biri ya da diğeri yürütülür. Önlem olarak, bir güncelleme yapıldığında [updateDone] adlı bir boole değeri ayarlanacaktır;
[setUserVisibleHint] ve [onResume] yöntemleri şunlardır:
// veriler
private boolean afterViewsDone = false;
private boolean initDone = false;
private String text;
private boolean isVisibleToUser = false;
private boolean updateDone = false;
private int numVisit = 0;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
// üst öğe
super.setUserVisibleHint(isVisibleToUser);
// bellek
this.isVisibleToUser = isVisibleToUser;
// günlük
Log.d("PlaceholderFragment", String.format("setUserVisibleHint %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// ziyaret sayısı
if (isVisibleToUser) {
// artış
numVisit++;
// güncelleme parçası
if (afterViewsDone && !updateDone) {
update();
updateDone = true;
}
} else {
// parça gizlenecek
updateDone = false;
}
}
@Override
public void onResume() {
// üst öğe
super.onResume();
// günlük
Log.d("PlaceholderFragment", String.format("onResume %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// güncelleme
if (isVisibleToUser && !updateDone) {
update();
updateDone = true;
}
}
- 14. satır: parçanın görünür olup olmadığı kaydedilir;
- 22-25. satırlar: parça görünür durumdaysa ve [@AfterViews] yöntemi yürütülmüşse, [update] yöntemi yürütülür ve [updateDone] boole değeri true'e aktarılır;
- 26-28. satırlar: Parça gizlenecekse, [updateDone] boole değeri false olarak sıfırlanır. Aslında, yeni güncellemelerin yapılabilmesi için, [update] yöntemi çağrılır çağrılmaz [updateDone] değerine ayarlanan boole değeri false'e sıfırlamak üzere bir olaya ihtiyacımız var. Bunu yapmak için, parçanın artık görünür olmaması durumunu kullanıyoruz. Parça tekrar görünür hale geldiğinde, parçanın güncellenmesi yeniden gerçekleştirilmelidir;
- 32-42. satırlar: Günlük kayıtları, parçalar için seçilen komşuluk ilişkisine bağlı olarak, parça görünür durumda olmasa bile [onResume] yönteminin çalışabileceğini göstermektedir. Eğer görünür değilse, güncelleme yapılmaz (satır 39) ve [setMenuVisibility] için yaptığımız gibi, [updateDone] boole değerini yönetiriz.
Son olarak, [onDestroyView] yöntemi şu şekildedir:
@Override
public void onDestroyView() {
// üst öğe
super.onDestroyView();
// güncelleme göstergesi
afterViewsDone = false;
// günlük
Log.d("PlaceholderFragment", String.format("onDestroyView %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
}
[onDestroyView] yöntemi, bir parça yaşam döngüsü sona erdiğinde yürütülür. Daha sonra başka bir döngü devam edebilir.
- 6. satır: [onDestroyView] yöntemi, parçaya bağlı görünümle olan tüm bağlantıları kaldırır. Bu bağlantı, parçanın bir sonraki yaşam döngüsünde yeniden oluşturulacaktır. Şu an için, görünümle olan bağlantının artık mevcut olmadığını belirtmek üzere [afterViews] boole değerini false olarak ayarlamamız gerekiyor;
Uygulamayı, komşuluk derecesi 2 olan 5 fragmanla çalıştıracağız. Değişiklikler [MainActivity]'te yapıldı:
// parça sayısı
private final int FRAGMENTS_COUNT = 5;
// parça komşuluğu
private final int OFF_SCREEN_PAGE_LIMIT=2;
// parça yöneticisi
private SectionsPagerAdapter mSectionsPagerAdapter;
@AfterViews
protected void afterViews() {
Log.d("MainActivity", "afterViews");
....
// parçaların ofseti
mViewPager.setOffscreenPageLimit(OFF_SCREEN_PAGE_LIMIT);
...
}
Başlangıçtaki günlükler şunlardır:
05-31 06:23:07.015 32551-32551/exemples.android D/MainActivity: constructor
05-31 06:23:07.041 32551-32551/exemples.android D/MainActivity: afterViews
05-31 06:23:07.050 32551-32551/exemples.android D/PlaceholderFragment: constructor
05-31 06:23:07.053 32551-32551/exemples.android D/PlaceholderFragment: constructor
05-31 06:23:07.053 32551-32551/exemples.android D/PlaceholderFragment: constructor
05-31 06:23:07.053 32551-32551/exemples.android D/PlaceholderFragment: constructor
05-31 06:23:07.053 32551-32551/exemples.android D/PlaceholderFragment: constructor
05-31 06:23:07.278 32551-32551/exemples.android D/MainActivity: getItem[0]
05-31 06:23:07.278 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:23:07.278 32551-32551/exemples.android D/MainActivity: getItem[1]
05-31 06:23:07.278 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:23:07.278 32551-32551/exemples.android D/MainActivity: getItem[2]
05-31 06:23:07.278 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:23:07.278 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=true, initDone=false, updateDone=false
05-31 06:23:07.280 32551-32551/exemples.android D/PlaceholderFragment: afterViews 2 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:23:07.291 32551-32551/exemples.android D/PlaceholderFragment: afterViews 3 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:23:07.294 32551-32551/exemples.android D/PlaceholderFragment: afterViews 1 numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=false, updateDone=false
05-31 06:23:07.295 32551-32551/exemples.android D/PlaceholderFragment: onResume 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 06:23:07.295 32551-32551/exemples.android D/PlaceholderFragment: onResume 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 06:23:07.295 32551-32551/exemples.android D/PlaceholderFragment: onResume 3 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 06:23:07.798 32551-32551/exemples.android D/menu: création menu en cours
- 8., 10. ve 12. satırlar: Parça konteyneri, 1 numaralı parçaya bitişik tüm parçaları talep eder;
- 9., 11. ve 13. satırlar: Bu parçaların [setUserVisibleHint] yöntemi, [visibleToUser] ile false arasında yürütülür;
- 14. satır: Parça 1’in [setUserVisibleHint] yöntemi, [visibleToUser]’ten true’e kadar yürütülür;
- satır 15-17: 3 bitişik segmentin [afterViews] yöntemi çağrılır. Dolayısıyla burada, bir parçanın görünür hale gelmesinden sonra (14. satırdaki 1. parça) bu yöntemin çağrıldığı bir durum görülmektedir;
- satır 18-20: 3 bitişik segmentin [onResume] yöntemi çağrılır;
- sekmeden 2. sekmeye geçiliyor:
05-31 06:52:36.132 32551-32551/exemples.android D/MainActivity: getItem[3]
05-31 06:52:36.132 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:52:36.132 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 06:52:36.132 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 06:52:36.134 32551-32551/exemples.android D/PlaceholderFragment: afterViews 4 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:52:36.134 32551-32551/exemples.android D/PlaceholderFragment: onResume 4 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
- parçaların bitişikliği bir konum sağa kaydırıldığından, parça 4 parça kapsayıcısı tarafından talep edilir;
- 2. satır: 4. parçanın [setUserVisibleHint] yöntemi, [visibleToUser]'ten false'e kadar çağrılır;
- satır 3: Parça 1'in [setUserVisibleHint] yöntemi, [visibleToUser]'ten false'e kadar çağrılır. Nitekim, parça 1 artık gizlenmiştir;
- 4. satır: Parça 2'nin [setUserVisibleHint] yöntemi, [visibleToUser]'ten true'e kadar çağrılır. Parça 2 artık görünür durumdadır;
- 5-6. satırlar: 4. parçanın yaşam döngüsü devam eder;
Sekme 2'den sekme 3'e geçiliyor:
05-31 06:58:16.228 32551-32551/exemples.android D/MainActivity: getItem[4]
05-31 06:58:16.228 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:58:16.228 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 06:58:16.228 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 06:58:16.229 32551-32551/exemples.android D/PlaceholderFragment: afterViews 5 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 06:58:16.229 32551-32551/exemples.android D/PlaceholderFragment: onResume 5 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
- parça sıralaması bir konum sağa kaydırıldığı için, parça 5 parça konteyneri tarafından talep edilir;
- 2. satır: 5. parçadaki [setUserVisibleHint] yöntemi, [visibleToUser] ile false arasında çağrılır;
- 3. satır: 2. parçadaki [setUserVisibleHint] yöntemi, [visibleToUser]'ten false'e kadar çağrılır. Nitekim, 2. parça artık gizlenmiştir;
- 4. satır: Parça 3'ün [setUserVisibleHint] yöntemi, [visibleToUser]'ten true'e kadar çağrılır. Parça 3 artık görünür durumdadır;
- 5-6. satırlar: 5. parçanın yaşam döngüsü devam eder;
Sekme 3'ten sekme 4'e geçiliyor:
05-31 07:00:17.762 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:00:17.762 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:00:17.762 32551-32551/exemples.android D/PlaceholderFragment: onDestroyView 1 : numVisit=1, afterViewsDone=false, isVisibleToUser=false, initDone=true, updateDone=false
- 1. satır: 3. parça artık gizlenmiştir;
- 2. satır: 4. parça artık görünür durumda. 4. parçanın yaşam döngüsünün yürütülmediğine dikkat edilmelidir. Bu işlem, iki adım önce zaten gerçekleştirilmiştir;
- 3. satır: 1. parça, görüntülenen 4. parçanın komşuluğundan çıkar. [onDestroyView] yöntemi yürütülür. Bir dahaki sefere görüntülendiğinde, [onCreateView, afterViews, onResume] görünüm döngüsü yeniden yürütülecektir;
- sekmeden 5. sekmeye geçiyoruz:
05-31 07:04:19.004 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:04:19.004 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:04:19.004 32551-32551/exemples.android D/PlaceholderFragment: onDestroyView 2 : numVisit=1, afterViewsDone=false, isVisibleToUser=false, initDone=true, updateDone=false
- 1. satır: 4. parça artık gizlenmiştir;
- 2. satır: 5. parça artık görünür durumdadır. 5. parçanın yaşam döngüsünün yürütülmediğine dikkat edilmelidir. Bu işlem, 2 adım önce zaten gerçekleştirilmiştir;
- 3. satır: 2. parça, görüntülenen 5. parçanın komşuluğundan çıkar. [onDestroyView] yöntemi yürütülür;
- sekmeden 1. sekmeye geçiyoruz:
05-31 07:06:17.246 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=1, afterViewsDone=false, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:06:17.246 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=1, afterViewsDone=false, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:06:17.246 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:06:17.246 32551-32551/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=1, afterViewsDone=false, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:06:17.246 32551-32551/exemples.android D/PlaceholderFragment: afterViews 1 numVisit=2, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:06:17.246 32551-32551/exemples.android D/PlaceholderFragment: onResume 1 : numVisit=2, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:06:17.247 32551-32551/exemples.android D/PlaceholderFragment: update 1 : numVisit=2, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:06:17.247 32551-32551/exemples.android D/PlaceholderFragment: afterViews 2 numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:06:17.247 32551-32551/exemples.android D/PlaceholderFragment: onResume 2 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:06:17.819 32551-32551/exemples.android D/PlaceholderFragment: onDestroyView 4 : numVisit=1, afterViewsDone=false, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:06:17.819 32551-32551/exemples.android D/PlaceholderFragment: onDestroyView 5 : numVisit=1, afterViewsDone=false, isVisibleToUser=false, initDone=true, updateDone=false
- 1., 4., 5. ve 6. satırlar: 1. parçanın yaşam döngüsü yeniden yürütülür. Zira, bu parça kendi görünümüyle bağlantısını kaybetmişti;
- 2., 5., 8. ve 9. satırlar: Aynı nedenden ötürü, parça 2'nin yaşam döngüsü yeniden yürütülür;
- satır 10-11: parçalar 4 ve 5, görüntülenen parçanın komşuluğundan çıkar;
- satır 7: 1 numaralı parça güncellenir;
![]() |
Günlüklerde, [setUserVisibleHint] ve [onResume] yöntemlerinin her ikisinin de parçayı güncellemeye çalıştığına dair hiçbir kayıt görülmemiştir. Ya biri ya da diğeri. Okuyucunun, parçaların komşuluk kavramını ve yaşam döngüsünü tam olarak anlamak için başka testler yapması ve günlükleri takip etmesi önerilir.
Şimdi, tam bir bitişiklik varsayalım ve aynı testleri yapalım.
[MainActivity]'te:
// parça sayısı
private final int FRAGMENTS_COUNT = 5;
// parçaların bitişikliği
private final int OFF_SCREEN_PAGE_LIMIT = FRAGMENTS_COUNT - 1;
Başlangıçtaki günlükler şunlardır:
05-31 07:34:44.717 28908-28908/exemples.android D/MainActivity: constructor
05-31 07:34:44.844 28908-28908/exemples.android D/MainActivity: afterViews
05-31 07:34:44.887 28908-28908/exemples.android D/PlaceholderFragment: constructor
05-31 07:34:44.887 28908-28908/exemples.android D/PlaceholderFragment: constructor
05-31 07:34:44.887 28908-28908/exemples.android D/PlaceholderFragment: constructor
05-31 07:34:44.887 28908-28908/exemples.android D/PlaceholderFragment: constructor
05-31 07:34:44.887 28908-28908/exemples.android D/PlaceholderFragment: constructor
05-31 07:34:45.201 28908-28908/exemples.android D/MainActivity: getItem[0]
05-31 07:34:45.201 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.201 28908-28908/exemples.android D/MainActivity: getItem[1]
05-31 07:34:45.204 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.204 28908-28908/exemples.android D/MainActivity: getItem[2]
05-31 07:34:45.204 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.204 28908-28908/exemples.android D/MainActivity: getItem[3]
05-31 07:34:45.204 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.205 28908-28908/exemples.android D/MainActivity: getItem[4]
05-31 07:34:45.205 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.205 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=true, initDone=false, updateDone=false
05-31 07:34:45.207 28908-28908/exemples.android D/PlaceholderFragment: afterViews 2 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.208 28908-28908/exemples.android D/PlaceholderFragment: afterViews 3 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.208 28908-28908/exemples.android D/PlaceholderFragment: afterViews 4 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.209 28908-28908/exemples.android D/PlaceholderFragment: afterViews 5 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
05-31 07:34:45.210 28908-28908/exemples.android D/PlaceholderFragment: afterViews 1 numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=false, updateDone=false
05-31 07:34:45.210 28908-28908/exemples.android D/PlaceholderFragment: onResume 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:34:45.210 28908-28908/exemples.android D/PlaceholderFragment: onResume 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:34:45.210 28908-28908/exemples.android D/PlaceholderFragment: onResume 3 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:34:45.210 28908-28908/exemples.android D/PlaceholderFragment: onResume 4 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:34:45.210 28908-28908/exemples.android D/PlaceholderFragment: onResume 5 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
05-31 07:34:46.548 28908-28908/exemples.android D/menu: création menu en cours
- Günlükler, 5 parçanın yaşam döngüsünün yürütüldüğünü gösteriyor;
- 1. parça 18. satırda görüntüleniyor;
- sekmeden 2. sekmeye geçiliyor:
05-31 07:38:27.780 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:38:27.780 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
- 1. satır: 1. parça gizlenir;
- 2. satır: 2. parça görüntülenir;
Sekme 2'den sekme 3'e geçildiğinde:
05-31 07:39:33.059 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:39:33.059 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
- satır 1: parça 2 gizlenir;
- 2. satır: 3. parça görüntüleniyor;
Sekme 3'ten sekme 4'e geçildiğinde:
05-31 07:40:30.362 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:40:30.362 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
- satır 1: parça 3 gizlenmiştir;
- 2. satır: 4. parça görüntüleniyor;
- sekmeden 5. sekmeye geçildiğinde:
05-31 07:41:23.479 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:41:23.479 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=0, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
- satır 1: parça 4 gizlenmiştir;
- 2. satır: 5. parça görüntüleniyor;
- sekmeden 1. sekmeye geçildiğinde:
05-31 07:42:22.549 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=1, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:42:22.549 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:42:22.549 28908-28908/exemples.android D/PlaceholderFragment: update 1 : numVisit=2, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
- satır 1: parça 5 gizlenmiştir;
- 2. satır: 1. parça görüntüleniyor;
- 3. satır: 1. parça güncellenir;
Sekme 1'den sekme 4'e geçiyoruz:
05-31 07:44:13.129 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=2, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=true
05-31 07:44:13.129 28908-28908/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
05-31 07:44:13.129 28908-28908/exemples.android D/PlaceholderFragment: update 4 : numVisit=2, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
- 1. satır: 1. parça gizlenmiştir;
- 2. satır: 4. parça görüntülenir;
- 3. satır: 4. parça güncellenir;
Görüldüğü gibi, tam komşuluk durumunda parçaların davranışı çok daha öngörülebilir hale geliyor.
Şimdi, komşuluk değerini sıfıra ayarlayalım ve ne olacağını görelim. [MainActivity] sınıfı şu şekilde değişir:
// parça sayısı
private final int FRAGMENTS_COUNT = 5;
// parçaların bitişikliği
private final int OFF_SCREEN_PAGE_LIMIT = 0;
Başlangıçtaki günlükler şunlardır:
06-01 03:11:52.068 5679-5679/exemples.android D/MainActivity: constructor
06-01 03:11:52.353 5679-5679/exemples.android D/MainActivity: afterViews
06-01 03:11:52.433 5679-5679/exemples.android D/PlaceholderFragment: constructor
06-01 03:11:52.433 5679-5679/exemples.android D/PlaceholderFragment: constructor
06-01 03:11:52.434 5679-5679/exemples.android D/PlaceholderFragment: constructor
06-01 03:11:52.434 5679-5679/exemples.android D/PlaceholderFragment: constructor
06-01 03:11:52.434 5679-5679/exemples.android D/PlaceholderFragment: constructor
06-01 03:11:52.566 5679-5679/exemples.android D/MainActivity: getItem[0]
06-01 03:11:52.566 5679-5679/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
06-01 03:11:52.566 5679-5679/exemples.android D/MainActivity: getItem[1]
06-01 03:11:52.566 5679-5679/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false
06-01 03:11:52.566 5679-5679/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=true, initDone=false, updateDone=false
06-01 03:11:52.571 5679-5679/exemples.android D/PlaceholderFragment: afterViews 2 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false
06-01 03:11:52.574 5679-5679/exemples.android D/PlaceholderFragment: afterViews 1 numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=false, updateDone=false
06-01 03:11:52.574 5679-5679/exemples.android D/PlaceholderFragment: onResume 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false
06-01 03:11:52.574 5679-5679/exemples.android D/PlaceholderFragment: onResume 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false
06-01 03:11:54.597 5679-5679/exemples.android D/menu: création menu en cours
- 8. ve 10. satırlarda, parça konteynerinin 1 ve 2 numaralı 2 parçayı talep ettiği görülüyor. Dolayısıyla her şey, komşuluk değeri 1miş gibi gerçekleşiyor. Komşuluk değeri 0 ise göz ardı edilmiştir.
1.9.3. Parçalar Arası İletişim
Önceki mimaride bir etkinlik ve n adet fragman bulunmaktadır. Kullanıcı, farklı fragmanlarla etkileşime girer. Bu etkileşimler, uygulamanın durumunu değiştirir. Burada uygulama durumu, uygulamanın ömrü boyunca depoladığı tüm bilgilerin toplamı olarak tanımlanır. Bu durumda şu sorun ortaya çıkar:
- kullanıcı i numaralı parçayla etkileşime girdiğinde, uygulama E1 durumundan E2 durumuna geçer;
- kullanıcının i parçası üzerinde yaptığı bir işlem, j parçasının görüntülenmesine neden olur;
- j parçasını, uygulamanın mevcut E2 durumuna göre nasıl güncelleyebiliriz;
Önceki örneklerden, j parçasını nasıl güncelleyeceğimizi biliyoruz. Peki, güncelleme için uygulamanın E2 durumunu nereden bulabiliriz?
Bu sorunun çeşitli çözümleri vardır. Bunlardan birini gördük: i parçası, argümanlar aracılığıyla uygulamanın E2 durumunu j parçasına aktarabilir. Bu yöntemi, parçaları oluştururken [MainActivity] sınıfında görmüştük:
for (int i = 0; i < fragments.length; i++) {
// bir parça oluşturulur
fragments[i] = new PlaceholderFragment_();
// parçaya argümanlar aktarılabilir
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, i + 1);
fragments[i].setArguments(args);
}
Bu çözüm burada hemen kullanılamaz. Zira kullanıcı, j parçasını görüntüleyecek olan j sekmesine tıkladığında kodumuz çağrılmaz. Yalnızca sistem kodu çalışır. Bir sonraki projede sekme tıklamasını nasıl yakalayacağımızı göreceğiz, ancak şimdilik başka bir yol izleyeceğiz.
Uygulamanın durumundan bahsettik: zaman içinde uygulama tarafından yönetilen verilerin bütünü. Burada uygulama, bir etkinlik ve n adet parçadan oluşur; bunların tümü uygulamanın başlatılması sırasında tek seferlik olarak örneklenir ve ömürleri uygulamanın ömrü kadardır. Dolayısıyla bu öğelerin her biri ya da birkaçı birlikte, uygulamanın durumunu depolamak için aday olabilir. Her fragman, [Fragment.getActivity()] yöntemi aracılığıyla kendisini oluşturan aktiviteye erişebilir. Tüm fragmanlar aktiviteye erişebildiğinden, uygulama durumunu bu aktivite içinde depolamak doğal bir çözüm gibi görünür.
Ancak [Fragment.getActivity()] yönteminin sonucu, yaşam döngüsünün hangi aşamasında çağrıldığına bağlıdır. Bu noktayı, [PlaceholderFragment] sınıfına birkaç günlük kaydı ekleyerek açıklayacağız:
// parçayı güncelle
public void update() {
Log.d("PlaceholderFragment", String.format("update %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// yapılacak iş, ziyaret numarasına bağlıdır
if (numVisit > 1) {
// günlük
Log.d("PlaceholderFragment", String.format("update %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// değiştirilen metin
textViewInfo.setText(String.format("%s update(%s)", text, (numVisit - 1)));
}
}
// günlükler için yerel bilgiler
private String getInfos() {
return String.format("numVisit=%s, afterViewsDone=%s, isVisibleToUser=%s, initDone=%s, updateDone=%s, getActivity()==null:%s",
numVisit, afterViewsDone, isVisibleToUser, initDone, updateDone, getActivity() == null);
}
- 14-16. satırlar: [getInfos] yöntemi, uygulama durumunun bir kısmını görüntüler;
Uygulamayı 2'lik parça komşuluğu ile başlatıyoruz. Uygulamanın başlatılması sırasında kaydedilen günlükler:
06-01 03:26:13.769 10931-10931/exemples.android D/MainActivity: constructor
06-01 03:26:13.856 10931-10931/exemples.android D/MainActivity: afterViews
06-01 03:26:13.864 10931-10931/exemples.android D/PlaceholderFragment: constructor
06-01 03:26:13.864 10931-10931/exemples.android D/PlaceholderFragment: constructor
06-01 03:26:13.864 10931-10931/exemples.android D/PlaceholderFragment: constructor
06-01 03:26:13.864 10931-10931/exemples.android D/PlaceholderFragment: constructor
06-01 03:26:13.864 10931-10931/exemples.android D/PlaceholderFragment: constructor
06-01 03:26:14.535 10931-10931/exemples.android D/MainActivity: getItem[0]
06-01 03:26:14.538 10931-10931/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:26:14.538 10931-10931/exemples.android D/MainActivity: getItem[1]
06-01 03:26:14.538 10931-10931/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:26:14.538 10931-10931/exemples.android D/MainActivity: getItem[2]
06-01 03:26:14.538 10931-10931/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:26:14.538 10931-10931/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=true, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:26:14.541 10931-10931/exemples.android D/PlaceholderFragment: afterViews 2 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:26:14.545 10931-10931/exemples.android D/PlaceholderFragment: afterViews 3 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:26:14.547 10931-10931/exemples.android D/PlaceholderFragment: afterViews 1 numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:26:14.547 10931-10931/exemples.android D/PlaceholderFragment: onResume 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:26:14.547 10931-10931/exemples.android D/PlaceholderFragment: update 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:26:14.547 10931-10931/exemples.android D/PlaceholderFragment: onResume 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:26:14.547 10931-10931/exemples.android D/PlaceholderFragment: onResume 3 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:26:15.967 10931-10931/exemples.android D/menu: création menu en cours
- 9., 10., 13. ve 14. satırlar: [setUserVisibleHint] yöntemlerinde, fragman henüz görünür değilse (isVisibleToUser==false) [getActivity()==null]'in çağrıldığını görüyoruz;
- 19. satır: Yürütme akışı, parça 1’in [update] yöntemine ulaştığında, [getActivity] yönteminin etkinliği doğru bir şekilde işlediği görülmektedir;
Parça komşuluğunu 4'e (tam komşuluk) ayarladığımızda, günlük kayıtları şu şekildedir:
06-01 03:35:23.553 2814-2814/exemples.android D/MainActivity: constructor
06-01 03:35:23.751 2814-2819/exemples.android I/art: Ignoring second debugger -- kabul etme ve reddetme
06-01 03:35:23.900 2814-2814/exemples.android D/MainActivity: afterViews
06-01 03:35:23.991 2814-2814/exemples.android D/PlaceholderFragment: constructor
06-01 03:35:23.991 2814-2814/exemples.android D/PlaceholderFragment: constructor
06-01 03:35:23.991 2814-2814/exemples.android D/PlaceholderFragment: constructor
06-01 03:35:23.991 2814-2814/exemples.android D/PlaceholderFragment: constructor
06-01 03:35:24.002 2814-2814/exemples.android D/PlaceholderFragment: constructor
06-01 03:35:24.207 2814-2814/exemples.android D/MainActivity: getItem[0]
06-01 03:35:24.207 2814-2814/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:35:24.207 2814-2814/exemples.android D/MainActivity: getItem[1]
06-01 03:35:24.207 2814-2814/exemples.android D/PlaceholderFragment: setUserVisibleHint 2 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:35:24.207 2814-2814/exemples.android D/MainActivity: getItem[2]
06-01 03:35:24.207 2814-2814/exemples.android D/PlaceholderFragment: setUserVisibleHint 3 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:35:24.207 2814-2814/exemples.android D/MainActivity: getItem[3]
06-01 03:35:24.207 2814-2814/exemples.android D/PlaceholderFragment: setUserVisibleHint 4 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:35:24.207 2814-2814/exemples.android D/MainActivity: getItem[4]
06-01 03:35:24.207 2814-2814/exemples.android D/PlaceholderFragment: setUserVisibleHint 5 : numVisit=0, afterViewsDone=false, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:true
06-01 03:35:24.207 2814-2814/exemples.android D/PlaceholderFragment: setUserVisibleHint 1 : numVisit=0, afterViewsDone=false, isVisibleToUser=true, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:35:24.210 2814-2814/exemples.android D/PlaceholderFragment: afterViews 2 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:35:24.211 2814-2814/exemples.android D/PlaceholderFragment: afterViews 3 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:35:24.214 2814-2814/exemples.android D/PlaceholderFragment: afterViews 4 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:35:24.215 2814-2814/exemples.android D/PlaceholderFragment: afterViews 5 numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:35:24.215 2814-2814/exemples.android D/PlaceholderFragment: afterViews 1 numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=false, updateDone=false, getActivity()==null:false
06-01 03:35:24.215 2814-2814/exemples.android D/PlaceholderFragment: onResume 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:35:24.215 2814-2814/exemples.android D/PlaceholderFragment: update 1 : numVisit=1, afterViewsDone=true, isVisibleToUser=true, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:35:24.216 2814-2814/exemples.android D/PlaceholderFragment: onResume 2 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:35:24.216 2814-2814/exemples.android D/PlaceholderFragment: onResume 3 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:35:24.216 2814-2814/exemples.android D/PlaceholderFragment: onResume 4 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:35:24.216 2814-2814/exemples.android D/PlaceholderFragment: onResume 5 : numVisit=0, afterViewsDone=true, isVisibleToUser=false, initDone=true, updateDone=false, getActivity()==null:false
06-01 03:35:26.602 2814-2814/exemples.android D/menu: création menu en cours
Aynı sonuçlar elde edilir. Buradan, parça görünür hale gelir gelmez [getActivity] yönteminin parçanın etkinliğini döndürdüğü sonucuna varıyoruz. Ayrıca, yürütme, görüntülenecek parçanın [update] yöntemine ulaştığında, [getActivity] yönteminin bir değer döndürdüğü de görülmektedir.
Parçalar arası iletişimi göstermek için yeni bir proje oluşturuyoruz.
1.10. Örnek-09: Parçalar arası iletişim, kaydırma ve kaydırma
1.10.1. Projenin oluşturulması
[Exemple-07] projesini [Exemple-08] olarak çoğaltıyoruz. Bunun için, 1.4. paragrafında [Exemple-02] projesini [Exemple-03] olarak çoğaltmak için açıklanan prosedürü izleyeceğiz.
![]() | ![]() |
1.10.2. Oturum
Bu yeni projede, parçaların kullanıcı tarafından görüntülenen toplam parça sayısını göstermesini istiyoruz. Burada, tüm parçaların erişebileceği bir sayaç tutmamız gerekiyor. Parçalar tarafından paylaşılan verileri kapsayan nesneye "oturum" adını vereceğiz. Bu terim, web geliştirme alanından gelmektedir; burada, aynı kullanıcı tarafından talep edilen farklı görünümler arasında paylaşılacak veriler bir oturuma yerleştirilir. Farklı parçalar tarafından paylaşılan bilgileri tek bir nesne içinde kapsüllemek, kodu daha okunabilir hale getirir.
[Session] sınıfı şu şekilde olacaktır:
![]() |
package exemples.android;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
// ziyaret edilen parça sayısı
private int numVisit;
// alıcı ve ayarlayıcılar
public int getNumVisit() {
return numVisit;
}
public void setNumVisit(int numVisit) {
this.numVisit = numVisit;
}
}
- 8. satır: oturum, ziyaret edilen parça sayısını kapsülleyecektir;
- 5. satır: [EBean] etiket, bir AA etiketidir. [scope] özniteliği, bu şekilde etiketlenen sınıfın kapsamını (veya ömrünü) belirtir. Burada [scope = EBean.Scope.Singleton] özniteliği, [Session] sınıfının bir singleton olmasını sağlar: bu sınıf, uygulamanın başlatılması sırasında yalnızca bir kez örneklenecektir. [EBean] ile etiketlenmiş bir sınıfın referansı daha sonra başka bir sınıfa enjekte edilebilir. Bu, bağımlılık enjeksiyonu kavramıdır;
1.10.3. [MainActivity] etkinliği
[MainActivity] etkinliği şu şekilde gelişir:
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
...
// oturum enjeksiyonu
@Bean(Session.class)
protected Session session;
// parça sayısı
private final int FRAGMENTS_COUNT = 5;
// parçaların birbirine yakınlığı
private final int OFF_SCREEN_PAGE_LIMIT = 2;
@AfterInject
protected void afterInject(){
Log.d("MainActivity", "afterInject");
// oturum başlatma
session.setNumVisit(0);
}
...
- 7-8. satırlar: [@Bean] anotasyonu aracılığıyla oturumun singleton'una referans enjeksiyonu. Anotasyonun parametresi, enjekte edilecek bean sınıfıdır. Bu şekilde anotasyon uygulanan alan, [private] kapsamına sahip olamaz;
- 15. satır: [@AfterInject] anotasyonu, sınıfın tüm enjeksiyonları tamamlandığında çağrılacak bir yöntemi belirtmek için kullanılır. Böylece, 16. satırdaki [afterInject] yöntemine girildiğinde, 8. satırdaki referans başlatılmış olur;
- 20. satır: ziyaret sayacı sıfırlanır;
1.10.4. [PlaceholderFragment] parçası
[PlaceholderFragment] parçası şu şekilde gelişir:
@EFragment(R.layout.fragment_main)
public class PlaceholderFragment extends Fragment {
....
// oturum
protected Session session;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
// üst öğe
super.setUserVisibleHint(isVisibleToUser);
// bellek
this.isVisibleToUser = isVisibleToUser;
// günlük
Log.d("PlaceholderFragment", String.format("setUserVisibleHint %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// ziyaret sayısı
if (isVisibleToUser) {
// güncelleme parçası
if (afterViewsDone && !updateDone) {
update();
updateDone = true;
}
} else {
// parça gizlenecek
updateDone = false;
}
}
// parça güncelleme
public void update() {
// günlük
Log.d("PlaceholderFragment", String.format("update %s : %s", getArguments().getInt(ARG_SECTION_NUMBER), getInfos()));
// oturum
if (session == null) {
session = ((MainActivity) getActivity()).getSession();
}
// ziyaret sayısını artır
numVisit = session.getNumVisit();
numVisit++;
session.setNumVisit(numVisit);
// değiştirilmiş metin
textViewInfo.setText(String.format("%s, visite %s", text, numVisit));
}
- 7. satır: oturum;
- 35-37. satırlar: [update] yöntemine geldiğimizde, [getActivity] yönteminin etkinliği doğru bir şekilde yansıttığını biliyoruz. Bu fırsatı değerlendirerek oturumu alıyoruz ve yerel olarak saklıyoruz (36. satır);
- satır 39-41: Ziyaret numarasını artırmak için bunu oturumdan alıyoruz. Bu kodu, 19. satırdan itibaren [setUserVisibleHint] yöntemine yerleştirebilirdik, çünkü o zaman [getActivity] yönteminin etkinliği döndürdüğünü biliyoruz. Burada bu yönteme özel bir rol vermemeye karar veriyoruz ve bir parçaya özgü kodu, bunun için tasarlanmış olan [update] yöntemine taşıyoruz;
- 43. satır: ziyaret numarasını görüntüler;
Bu uygulamayı 5 fragman ve 2 fragmanlık bir komşuluk ile çalıştırdığımızda, ilk günlük kayıtları şu şekildedir:
05-31 08:38:47.305 20114-20114/exemples.android D/MainActivity: constructor
05-31 08:38:47.307 20114-20114/exemples.android D/MainActivity: afterInject
05-31 08:38:47.351 20114-20114/exemples.android D/MainActivity: afterViews
05-31 08:38:47.354 20114-20114/exemples.android D/PlaceholderFragment: constructor
05-31 08:38:47.354 20114-20114/exemples.android D/PlaceholderFragment: constructor
05-31 08:38:47.354 20114-20114/exemples.android D/PlaceholderFragment: constructor
05-31 08:38:47.354 20114-20114/exemples.android D/PlaceholderFragment: constructor
05-31 08:38:47.354 20114-20114/exemples.android D/PlaceholderFragment: constructor
...
- 2-3. satırlar: Aktivitenin [afterInject] yönteminin, [afterViews] yönteminden önce yürütüldüğü görülmektedir;
Okuyucunun bu yeni uygulamayı denemesi önerilir.
1.10.5. Kaydırma (Swipe) Özelliğini Devre Dışı Bırakma
Önceki uygulamada, Android emülatörünü fareyle sola veya sağa kaydırdığımızda, mevcut görünüm duruma göre sağdaki veya soldaki görünüme yerini bırakır. Bu varsayılan davranış her zaman istenen bir durum değildir. Görünümlerdeki kaydırma (swipe) işlevini nasıl devre dışı bırakacağımızı öğreneceğiz.
Ana görünüm XML'e geri dönelim:
![]() |
Görünümün XML kodunda, parça kapsayıcısının kodu bulunur:
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
- satır, etkinliğin sayfalarını yöneten sınıfı belirtir. Bu sınıfa [MainActivity] etkinliğinde rastlanır:
import android.support.v4.view.ViewPager;
...
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
// parça yöneticisi
private SectionsPagerAdapter mSectionsPagerAdapter;
// parça konteyneri
@ViewById(R.id.container)
protected ViewPager mViewPager;
...
- satırda, parça konteyneri [android.support.v4.view.ViewPager] türündedir (1. satır). Taramayı devre dışı bırakmak için bu sınıfı şu şekilde türetmemiz gerekir:
![]() |
package exemples.android;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class MyPager extends ViewPager {
// kaydırma hareketini kontrol eder
private boolean isSwipeEnabled;
// oluşturucular
public MyPager(Context context) {
super(context);
}
public MyPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
// kaydırma hareketini yönetmek için yeniden tanımlanacak yöntemler
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// kaydırma izinli mi?
if (isSwipeEnabled) {
return super.onInterceptTouchEvent(event);
} else {
return false;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// kaydırma izinli mi?
if (isSwipeEnabled) {
return super.onTouchEvent(event);
} else {
return false;
}
}
// setter
public void setSwipeEnabled(boolean isSwipeEnabled) {
this.isSwipeEnabled = isSwipeEnabled;
}
}
- satır 8: [MyPager] sınıfı, Android [ViewPager] sınıfını genişletir (satır 4);
- el ile kaydırma yapıldığında, 24. ve 34. satırlardaki olay işleyicileri çağrılabilir. Her ikisi de bir boole değeri döndürür. Kaydırmayı engellemek için [false] boole değerini döndürmeleri yeterlidir;
- 11. satır: El hareketinin kabul edilip edilmediğini belirtmek için kullanılan boole değeri.
Bu işlem tamamlandıktan sonra, artık yeni sayfa yöneticimizi kullanmamız gerekiyor. Bu işlem, XML ve [activity_main.xml] görünümlerinde ve [MainActivity] ana etkinliğinde gerçekleştirilir. [activity_main.xml]'te şunlar yazılır:
![]() |
<exemples.android.MyPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
1. satırda yeni sınıf kullanılır. [MainActivity]'te kod şu şekilde değişir:
package exemples.android;
...
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
// parça yöneticisi
private SectionsPagerAdapter mSectionsPagerAdapter;
// parça konteyneri
@ViewById(R.id.container)
protected MyPager mViewPager;
@AfterViews
protected void afterViews() {
Log.d("MainActivity", "afterViews");
...
// parça konteyneri, parça yöneticisiyle ilişkilendirilmiştir
// yani, parça konteynerindeki i numaralı parça, parça yöneticisi tarafından sağlanan i numaralı parçadır
mViewPager.setAdapter(mSectionsPagerAdapter);
// parçalar arasında kaydırma engellenir
mViewPager.setSwipeEnabled(false);
// sekme çubuğu da fragman konteyneriyle ilişkilendirilmiştir
...
- 12. satır: Sayfa yöneticisinin türü artık [MyPager]'tir;
- 23. satır: El kaydırma işlevi devre dışı bırakılabilir veya bırakılmayabilir.
Bu yeni sürümü test edin. Kaydırmayı devre dışı bırakın ya da bırakmayın ve fareyle sağa veya sola sürüklediğinizde görünümlerin davranışındaki farkı gözlemleyin. Gelecekteki tüm uygulamalarda kaydırma devre dışı bırakılacaktır. Bu konuyu tekrar hatırlatmayacağız.
1.10.6. Parçalar Arasında Kaydırmayı Devre Dışı Bırakma
Sekme yöneticisindeki bir iyileştirmeyle devam edelim. Sekme 1'den sekme 4'e geçtiğinizde, aradaki 2 ve 3 numaralı sekmelerin kaydırıldığını görürsünüz. Buna Android jargonunda smoothScrolling denir. Sekme sayısı fazla olduğunda bu davranış rahatsız edici olabilir. Aşağıdaki kodu [MyPager] fragman yöneticisine ekleyerek bu davranışı devre dışı bırakabilirsiniz:
// kaydırma hareketini kontrol eder
private boolean isSwipeEnabled;
// kaydırma kontrolü
private boolean isScrollingEnabled;
...
// kaydırma
@Override
public void setCurrentItem(int position){
super.setCurrentItem(position,isScrollingEnabled);
}
// ayarlar
...
public void setScrollingEnabled(boolean scrollingEnabled) {
isScrollingEnabled = scrollingEnabled;
}
Sekme yöneticisi, [MyPager] fragman yöneticisiyle ilişkilendirildiğinden, i numaralı sekmeye tıklandığında, i numaralı fragman, yukarıdaki [setCurrentItem] yöntemi (9. satır) aracılığıyla fragman konteyneri tarafından görüntülenir. [position], görüntülenecek parçanın numarasıdır;
- 10. satır: üst sınıfın [setCurrentItem] yöntemini çağırır. [false] yöntemine verilen ikinci argüman, eski ve yeni fragman arasında anında geçiş yapılmasını (kaydırma olmadan) talep ederken, [true] yöntemine verilen ikinci argüman ise scrolling aracılığıyla geçiş yapılmasını talep eder. Burada ikinci argüman, 4. satırdaki alanın değeridir; geliştirici bu alanı 16-18. satırlardaki yöntemle ayarlayabilir;
Kaydırmayı devre dışı bırakmak istersek, [MainActivity] sınıfı şu şekilde olacaktır:
...
// parça ofseti
mViewPager.setOffscreenPageLimit(OFF_SCREEN_PAGE_LIMIT);
// fragmanlar arası kaydırma engellenir
mViewPager.setSwipeEnabled(false);
// kaydırma yok
mViewPager.setScrollingEnabled(false);
...
Projeyi yeniden çalıştırın ve örneğin 1 ile 4 numaralı sekmeler arasında artık scrolling bulunmadığını kontrol edin. Bundan sonra, kaydırmayı her zaman devre dışı bırakacağız. Bu konuya tekrar değinmeyeceğiz.
1.10.7. Yeni bir parça
Örneğimizde tüm parçalar aynı [PlaceHolderFragment] türündedir. Şimdi yeni bir parça oluşturmayı ve bunu görüntülemeyi öğreneceğiz.
Öncelikle, [Exemple-04] projesindeki [vue1.xml] görünümünü [Exemple-09] [1] projesine kopyalayalım:
![]() | ![]() |
- [1]'te, [vue1.xml] görünümü;
- [3] dosyasında, [res/values/strings.xml] dosyasında bulunmayan metinlerden kaynaklanan hatalar görülmektedir;
[2] dosyasında, [Exemple-04] projesinin [res/values/strings.xml] dosyasından alınarak eksik metinler eklenir
<resources>
<string name="app_name">Exemple-07</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
<!-- görünüm 1 -->
<string name="titre_vue1">Vue n° 1</string>
<string name="txt_nom">Quel est votre nom ?</string>
<string name="btn_valider">Valider</string>
<string name="btn_vue2">Vue n° 2</string>
</resources>
- dosyasına 6-9. satırlar eklendi;
Şimdi, [vue1.xml] görünümünü görüntülemekle görevli [Vue1Fragment] sınıfını oluşturuyoruz:
![]() |
[Vue1Fragment] sınıfı şu şekilde olacaktır:
package exemples.android;
import android.support.v4.app.Fragment;
import android.widget.EditText;
import android.widget.Toast;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends Fragment {
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
// olay yöneticisi
@Click(R.id.buttonValider)
protected void doValider() {
// girilen ad görüntülenir
Toast.makeText(getActivity(), String.format("Bonjour %s", editTextNom.getText().toString()), Toast.LENGTH_LONG).show();
}
}
- 10. satır: [@EFragment] açıklaması, aktivite tarafından kullanılan parçanın aslında [Vue1Fragment_] sınıfı olacağını belirtir. Bunu unutmamak gerekir. Parça, [vue1.xml] görünümüne ilişkilidir;
- 14-15. satırlar: [R.id.editTextNom] ile tanımlanan bileşen, 15. satırdaki [editTextNom] alanına enjekte edilir;
- satır 18-20: [doValider] yöntemi, [R.id.buttonValider] ile tanımlanan düğme üzerindeki 'click' olayını yönetir;
- 21. satır: [Toast.makeText]'in ilk parametresi [Activity] türündedir. [Fragment.getActivity()] yöntemi, parçanın bulunduğu etkinliği elde etmeyi sağlar. Bu mimaride farklı görünümleri veya parçaları görüntüleyen tek bir etkinlik olduğundan, bu [MainActivity]'tir;
[MainActivity] sınıfında, fragman yöneticisi şu şekilde gelişir:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private Fragment[] fragments;
// parça numarası
private static final String ARG_SECTION_NUMBER = "section_number";
// oluşturucu
public SectionsPagerAdapter(FragmentManager fm) {
// üst öğe
super(fm);
// parça tablosunun başlatılması
fragments = new Fragment[FRAGMENTS_COUNT];
for (int i = 0; i < fragments.length - 1; i++) {
// bir parça oluşturulur
fragments[i] = new PlaceholderFragment_();
// parçaya argümanlar geçirilebilir
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, i + 1);
fragments[i].setArguments(args);
}
// bir fragman daha
fragments[fragments.length - 1] = new Vue1Fragment_();
}
...
}
- 13. satır: [FRAGMENTS_COUNT] parçaları vardır: [FRAGMENTS_COUNT-1], [PlaceholderFragment] türünde parçalar (14-21. satırlar) ve [Vue1Fragment_] türünde bir parça, 23. satır (alt çizgiye dikkat edin);
[Exemple-09] projesini derleyin ve çalıştırın. 5 numaralı sekme farklı olmalıdır:
![]() |
1.10.8. Tüm parçaları aynı soyut sınıftan türetin
Yeni parça [Vue1Fragment] de görüntülendiğinde güncellenmesi gerekir. Bunu yapmak için, [PlaceholderFragment] parçası için oluşturulan koda benzer bir kod yazmamız gerekecek. Tekrarlardan kaçınmak için, mümkün olan her şeyi uygulamanın tüm parçalarının miras alacağı bir soyut sınıfa faktörlere ayıracağız.
Bunun için yeni bir proje oluşturacağız.
1.11. Örnek-10: Tüm parçaları soyut bir sınıftan miras almak
1.11.1. Projenin oluşturulması
[Exemple-09] projesini [Exemple-10] olarak kopyalayalım:
![]() | ![]() |
1.11.2. Hata ayıklama modunun yönetimi
Projeye, hata ayıklama modu günlüklerinin görüntülenip görüntülenmeyeceğini belirleme seçeneği ekliyoruz. Bunun için, [MainActivity] sınıfına statik bir sabit ekliyoruz:
// hata ayıklama modunda
public static final boolean IS_DEBUG_ENABLED = false;
1.11.3. Tüm parçaların üst sınıfı olan soyut sınıf
![]() |
[AbstractFragment] sınıfı şu şekildedir:
package exemples.android;
import android.app.Activity;
import android.support.v4.app.Fragment;
import android.util.Log;
public abstract class AbstractFragment extends Fragment {
// özel veriler
private boolean isVisibleToUser = false;
private boolean updateDone = false;
private String className;
// alt sınıflardan erişilebilen veriler
protected boolean afterViewsDone = false;
protected boolean isDebugEnabled = true;
// etkinlik
protected MainActivity activity;
// oturum
protected Session session;
// oluşturucu
public AbstractFragment() {
// başlatma
isDebugEnabled = MainActivity.IS_DEBUG_ENABLED;
className = getClass().getSimpleName();
// günlük
if (isDebugEnabled) {
Log.d("AbstractFragment", String.format("constructor %s", className));
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
// üst öğe
super.setUserVisibleHint(isVisibleToUser);
...
}
@Override
public void onDestroyView() {
// üst
super.onDestroyView();
...
}
@Override
public void onResume() {
// üst
super.onResume();
...
}
// yerel bilgiler
protected String getParentInfos() {
return String.format("className=%s, isVisibleToUser=%s, updateDone=%s, afterViewsDone=%s", className, isVisibleToUser, updateDone, afterViewsDone);
}
// güncelleme parçası
protected void update() {
...
// alt sınıftan kendini güncellemesi istenir
updateFragment();
}
protected abstract void updateFragment();
}
- 7. satır: [AbstractFragment] sınıfı, Android [Fragment] sınıfını genişletir;
- her fragman kendini güncelleyebilmelidir. Bu nedenle üst sınıf [AbstractFragment], alt sınıflarında [updateFragment] yönteminin (satır 68) bulunmasını zorunlu kılar ve bu yöntemi çağırır (satır 65);
- 19. satır: Sınıf, uygulamanın etkinliğine bir referans depolayacaktır;
- 22. satır: Sınıf, parçalar ve etkinlik tarafından paylaşılan verilerin toplandığı oturuma ilişkin bir referans depolayacaktır;
- 25-33. satırlar: soyut sınıfın oluşturucusu;
- 27. satır: [MainActivity.IS_DEBUG_ENABLED] sabitinin bir kopyası, 16. satırdaki alana eklenir;
- 28. satır: örneklenen sınıfın adı, yani bir alt sınıfın adı kaydedilir;
- satır 15-22: bu alanlar, alt sınıfların bunlara erişebilmesi için [protected] özniteliğine sahiptir. Alt sınıfların, [isVisibleToUser] ve [updateDone] boole değerlerinin varlığından habersiz oldukları dikkat çekmektedir (satır 10-11);
- 57. satır: [getParentInfos] yöntemi, alt sınıfların bu yöntemi çağırabilmesi için [protected] özniteliğine sahiptir;
[setUserVisibleHint, onDestroyView, onResume] yöntemleri, önceki projenin [PlaceholderFragment] sınıfındaki halleriyle aynı kalır:
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
// üst sınıf
super.setUserVisibleHint(isVisibleToUser);
// bellek
this.isVisibleToUser = isVisibleToUser;
// günlük
if (isDebugEnabled) {
Log.d("AbstractFragment", String.format("setUserVisibleHint : %s", getParentInfos()));
}
// parçanın görünür hale geleceği durum
if (isVisibleToUser) {
// parçayı güncelle
if (afterViewsDone && !updateDone) {
update();
updateDone = true;
}
} else {
// parçadan çıkma
updateDone = false;
}
}
@Override
public void onDestroyView() {
// üst öğe
super.onDestroyView();
// gösterge güncellemesi
afterViewsDone = false;
// günlük
if (isDebugEnabled) {
Log.d("AbstractFragment", String.format("onDestroyView : %s", getParentInfos()));
}
}
@Override
public void onResume() {
// üst öğe
super.onResume();
// günlük
if (isDebugEnabled) {
Log.d("AbstractFragment", String.format("onResume : %s", getParentInfos()));
}
if (isVisibleToUser) {
// güncelleme
if (!updateDone) {
update();
updateDone = true;
}
}
}
[update] yöntemi şu şekildedir:
// parça güncelleme
protected void update() {
// etkinlik ve oturum alınır
if (activity == null) {
Activity activity = getActivity();
if (activity != null) {
this.activity = (MainActivity) activity;
this.session = this.activity.getSession();
}
}
// alt sınıftan kendini güncellemesini istenir
updateFragment();
}
Yukarıdaki koda göre, bir parçanın [update] yöntemi yürütüldüğünde, bu parça görünür hale gelir. Bu önemlidir, çünkü bu durum [Fragment.getActivity] yönteminin uygulama etkinliğine bir referans döndürdüğü anlamına gelir (bkz. paragraf 1.10.8); bu da oturuma erişim sağlar.
- 4-10. satırlar: Henüz yapılmamışsa etkinlik ve oturum başlatılır;
- 12. satır: alt sınıfın [updateFragment] yöntemi çağrılır. Bu yöntem yürütüldüğünde, erişimi olan [activity] ve [session] alanları önceden başlatılmış olur;
1.11.4. [PlaceholderFragment] sınıfı
![]() |
[PlaceholderFragment] sınıfı şu şekilde gelişir:
package exemples.android;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.widget.TextView;
import org.androidannotations.annotations.*;
// bir parça, bir parça kapsayıcısı tarafından görüntülenen bir görünümdür
@EFragment(R.layout.fragment_main)
public class PlaceholderFragment extends AbstractFragment {
// görsel arayüzün bir bileşeni
@ViewById(R.id.section_label)
protected TextView textViewInfo;
// veri
private boolean initDone;
// veri
private String text;
private int numVisit;
// parça numarası
private static final String ARG_SECTION_NUMBER = "section_number";
// oluşturucu
public PlaceholderFragment() {
super();
// günlük
if (isDebugEnabled) {
Log.d("PlaceholderFragment", "constructor");
}
}
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
...
}
// güncelleme parçası
public void updateFragment() {
...
}
}
- 10. satır: [PlaceholderFragment] sınıfı, [AbstractFragment] sınıfını genişletir. Bu mimariyle, bir parçanın yazılması şunlardan oluşur:
- [@AfterViews] yöntemini yazmak; bu yöntem, parçacığın ilk yaşam döngüsü sırasında onu başlatmak veya daha önce bir [onDestroyView] işlemi olmuşsa onu sıfırlamak için kullanılır. 39. satır, parçacığın yaşam döngüsünü doğru bir şekilde yönetmek için zorunludur;
- fragmanı görüntülenmeden hemen önce güncelleyecek olan [updateFragment] yöntemini yazmak. Bu yöntem, üst sınıfının oturumunu kullanabilir;
- fragmanın olay işleyicilerini yazmak. Bunu gelecekteki projelerde yapacağız;
[@AfterViews] ve [updateFragment] yöntemleri, önceki projedeki halleriyle aynı kalır:
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("PlaceholderFragment", String.format("afterViews %s - %s - %s", getArguments().getInt(ARG_SECTION_NUMBER), getParentInfos(), getLocalInfos()));
}
if (!initDone) {
// başlangıç metni
text = getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER));
// başlatma tamamlandı
initDone = true;
}
// geçerli metin görüntüleme
textViewInfo.setText(text);
}
// parça güncelleme
public void updateFragment() {
// günlük
if (isDebugEnabled) {
Log.d("PlaceholderFragment", String.format("update %s - %s - %s", getArguments().getInt(ARG_SECTION_NUMBER), getParentInfos(), getLocalInfos()));
}
// ziyaret numarasını artır
numVisit = session.getNumVisit();
numVisit++;
session.setNumVisit(numVisit);
// metin değiştirildi
textViewInfo.setText(String.format("%s, visite %s", text, numVisit));
}
// günlükler için yerel bilgiler
protected String getLocalInfos() {
return String.format("numVisit=%s, initDone=%s, getActivity()==null:%s",
numVisit, initDone, getActivity() == null);
}
- 7. ve 23. satırlar: günlüklerde, miras alınan [getParentInfos] yöntemi ile üst sınıfın bilgileri görüntülenir;
1.11.5. [Vue1Fragment] sınıfı
![]() |
[Vue1Fragment] sınıfı, [PlaceholderFragment] sınıfıyla aynı yapıya sahiptir:
package exemples.android;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import org.androidannotations.annotations.*;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
// veri
private int numVisit;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("afterViews %s - %s", getParentInfos(), getLocalInfos()));
}
}
// olay yöneticisi
@Click(R.id.buttonValider)
protected void doValider() {
// girilen ad görüntülenir
Toast.makeText(getActivity(), String.format("Bonjour %s", editTextNom.getText().toString()), Toast.LENGTH_LONG).show();
}
// günlükler için yerel bilgiler
protected String getLocalInfos() {
return String.format("numVisit=%s", numVisit);
}
// parça güncellemesi
@Override
protected void updateFragment() {
// ziyaret numarasını artırma
numVisit = session.getNumVisit();
numVisit++;
session.setNumVisit(numVisit);
// ziyaret numarası görüntülenir
Toast.makeText(getActivity(), String.format("Visite n° %s", numVisit), Toast.LENGTH_SHORT).show();
}
}
- 9. satır: [Vue1Fragment] sınıfı, [AbstractFragment] sınıfını genişletir;
- 18-26. satırlar: [@AfterViews] yöntemi ilginç bir işlev yerine getirmez. Yine de, [afterViewsDone] boole değerini true olarak ayarlamak için bu yöntemi yazmak gerekir; çünkü bu bilgi üst sınıf tarafından kullanılır;
- 42-49. satırlar: [updateFragment] yöntemi, ziyaret numarasını gösteren kısa bir mesajı görüntülemek (48. satır) ve oturumda bu numarayı artırmaktan (44-46. satırlar) ibarettir;
Okuyucunun bu yeni projeyi test etmesi önerilir.
Gelecekteki tüm projelerde bu mimariyi kullanmaya devam edeceğiz:
- bir etkinlik ve n parça;
- tüm parçalar [AbstractFragment] sınıfını genişletir;
- parçalar arasında ve parçalar ile etkinlik arasında paylaşılacak veriler [Session] sınıfına yerleştirilir;
1.11.6. Sekmeler / parçalar ilişkisi
Sekmeleri yöneten [MainActivity] sınıfında şunlar yazmaktadır:
// sekme çubuğu da parça konteyneriyle ilişkilidir
// yani i numaralı sekme, kapsayıcının i numaralı parçasını görüntüler
tabLayout.setupWithViewPager(mViewPager);
3. satır, sekme yöneticisini parça konteyneriyle ilişkilendirir. Bu ilişkilendirmenin bir sonucunu gördük: kullanıcı i numaralı sekmeye tıkladığında, parça konteyneri i numaralı parçayı görüntüler. Bunun tersini görmedik: parça konteynerinden i numaralı parçayı görüntülemesi istendiğinde, i numaralı sekme otomatik olarak seçilir.
Bu davranışı örneklemek için mevcut menüye [Fragment 1, Fragment 2, ...] seçeneklerini ekleyeceğiz. Kullanıcı [Fragment i] seçeneğine tıkladığında, parça konteynerinden i numaralı parçayı görüntülemesi istenecektir. Ardından, i numaralı sekmenin seçilip seçilmediğini göreceğiz.
Bu adım, uygulama menüsünün değiştirilmesiyle başlar:
![]() | ![]() |
[res / menu / menu_main.xml] dosyasının içeriği şu şekilde değişir:
<menu 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"
tools:context="exemples.android.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment1"
android:title="@string/fragment1"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment2"
android:title="@string/fragment2"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment3"
android:title="@string/fragment3"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment4"
android:title="@string/fragment4"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment5"
android:title="@string/fragment5"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
- 9-28. satırlar: menüdeki beş yeni seçenek;
- seçeneklerin adları (10, 14, 18, 22, 26. satırlar) [res / values / strings.xml] dosyasında tanımlanmıştır [2]:
<resources>
<string name="app_name">Exemple-10</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
<!-- görünüm 1 -->
<string name="titre_vue1">Vue n° 1</string>
<string name="txt_nom">Quel est votre nom ?</string>
<string name="btn_valider">Valider</string>
<string name="btn_vue2">Vue n° 2</string>
<!-- menü -->
<string name="fragment1">Fragment 1</string>
<string name="fragment2">Fragment 2</string>
<string name="fragment3">Fragment 3</string>
<string name="fragment4">Fragment 4</string>
<string name="fragment5">Fragment 5</string>
</resources>
Görsel sonuç şöyledir:
![]() |
Bu menü seçeneklerine yapılan tıklamaların yönetimi, [MainActivity] sınıfında gerçekleştirilir:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("menu", "onOptionsItemSelected");
}
// menü seçeneklerinin işlenmesi
int id = item.getItemId();
switch (id) {
case R.id.action_settings: {
if (IS_DEBUG_ENABLED) {
Log.d("menu", "action_settings selected");
}
break;
}
case R.id.fragment1: {
showFragment(0);
break;
}
case R.id.fragment2: {
showFragment(1);
break;
}
case R.id.fragment3: {
showFragment(2);
break;
}
case R.id.fragment4: {
showFragment(3);
break;
}
case R.id.fragment5: {
showFragment(4);
break;
}
}
// işlenen öğe
return true;
}
private void showFragment(int i) {
if (i < FRAGMENTS_COUNT && mViewPager.getCurrentItem() != i) {
// görüntülenen parça değiştiriliyor
mViewPager.setCurrentItem(i);
}
}
- 2. satır: Menü seçeneklerinden birine tıklandığında [onOptionsItemSelected] yöntemi çağrılır;
- satır 8: tıklanan seçeneğin kimliği alınır;
- 9-36. satırlar: farklı durumlar switch ile işlenir;
- satır 16-36: [Fragment i] seçeneğine tıklama, satır 41-45'teki [showFragment(i-1)] yöntemine yönlendirir;
- satır 43: parça konteynerinden istenen parçayı görüntülemesi istenir;
- satır 42: öncelikle bunun yapılabileceğini (koşul 1) ve gerekli olduğunu (koşul 2) kontrol edilir;
Okuyucunun bu yeni sürümü denemesi önerilir. i numaralı parçanın görüntülenmesi istendiğinde, bu parçanın başarıyla görüntülendiği ve i numaralı sekmenin de seçili hale geldiği görülür.
Artık sekmeler ve parçaların nasıl eşleştirildiğini gördüğümüze göre, başka bir duruma odaklanacağız: sekmelerin yönetiminin parçaların yönetiminden ayrıldığı durum. Örneğin, sekme sayısı parça sayısından az olduğunda bu durum söz konusudur. Bu yeni kullanım durumunu örneklemek için yeni bir proje oluşturacağız.
1.12. Örnek-11: Parçalardan ayrılmış sekmeler
1.12.1. Projenin oluşturulması
[Exemple-10] projesini [Exemple-11] olarak kopyalıyoruz:
![]() | ![]() |
1.12.2. Hedefler
Yeni uygulama iki sekmeye sahip olacaktır:
- 1. sekme her zaman [Vue1] parçasını gösterecek;
- ikinci sekme ise menüden seçilen bir parçayı gösterecektir;

- [1]'te, [Vue1] parçası;
- [2]'e, kullanıcı tarafından seçilen [PlaceholderFragment] türündeki parça;
- [3]'te, ziyaret sayımı devam eder;
1.12.3. Oturum
![]() |
Yeni oturum şu şekilde olacaktır:
package exemples.android;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
// ziyaret edilen parça sayısı
private int numVisit;
// ikinci sekmede görüntülenen [PlaceholderFragment] tipi parça numarası
private int numFragment;
// getter ve setter'lar
...
}
- 10. satır: Sekmelere yapılan tıklamaları kendimiz yöneteceğiz. Bir sekmeye tıklandığında, o sekmenin en son seçildiğinde görüntülediği parçayı geri getirmeliyiz. [numFragment] alanı, 2 numaralı sekme için bu parçanın numarasını ([0, Fragments_COUNT-2]'teki sayı) kaydedecektir. 2 numaralı sekmeye tıklandığında, oturumdan görüntülenecek parçanın numarası alınacaktır;
1.12.4. Menü
![]() |
[res / menu / menu_main.xml] menüsü şu şekilde değişir:
<menu 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"
tools:context="exemples.android.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment1"
android:title="@string/fragment1"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment2"
android:title="@string/fragment2"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment3"
android:title="@string/fragment3"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment4"
android:title="@string/fragment4"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
2 numaralı sekme, 9-24. satırlardaki dört parçadan birini gösterecektir. 5. parça, her zaman 1 numaralı sekmede görüntülenecek olan [Vue1Fragment] parçasıdır.
1.12.5. [MainActivity] sınıfı
[MainActivity] sınıfı artık sekmeleri ve sekmeler arası gezinmeyi yönetmelidir; bu, şimdiye kadar yapmadığı bir işlevdir. Kodunda şu değişiklikler yapılır:
// sekme yöneticisi
@ViewById(R.id.tabs)
protected TabLayout tabLayout;
...
@AfterViews
protected void afterViews() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterViews");
}
...
// kaydırma yok
mViewPager.setScrollingEnabled(false);
// Vue1 ekranı
mViewPager.setCurrentItem(FRAGMENTS_COUNT - 1);
// başlangıçta tek bir sekme var
TabLayout.Tab tab = tabLayout.newTab();
tab.setText("Vue 1");
tabLayout.addTab(tab);
// olay yöneticisi
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
// bir sekme seçildi - parça konteyneri tarafından görüntülenen parça değiştiriliyor
...
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
...
}
- 17. satır: Parça konteyneri tarafından görüntülenen ilk parça, [Vue1Fragment] parçası olacaktır. Yapısı gereği, bu parça konteynerin son parçası olacaktır;
- 20-22. satırlar: Sekmeler ile parça konteyneri arasında bir ilişkilendirme yapmadığımız için sekmeleri kendimiz yönetmeliyiz. Başlangıçta, 3. satırdaki [tabLayout] sekme çubuğunda hiçbir sekme yoktur;
- 20. satır: ilk sekmeyi oluşturuyoruz;
- satır 21: sekmeye bir başlık veriyoruz. Önceki örneklerde sekmelerin başlığı, parçaların başlığıydı. Artık bu durum sona erdi. Dolayısıyla, parça yöneticisinden [getPageTitle] yöntemini kaldırıyoruz. Artık buna ihtiyacımız yok:
// isteğe bağlı - yönetilen parçalara başlık verilir
@Override
public CharSequence getPageTitle(int position) {
return String.format("Onglet n° %s", (position + 1));
}
- 22. satır: Oluşturulan sekme sekme çubuğuna eklenir. Artık sekme çubuğumuzda bir sekme var. Bu sekme neyi gösteriyor? Sekmelerin ve parçaların birbirinden bağımsız iki kavram olduğunu anlamak gerekir. Görüntülenen parça her zaman parça kapsayıcısı tarafından seçilen parçadır. Sekmeyi değiştirirsek ve parça kapsayıcısından görüntülenen parçayı değiştirmesini istemezsek, hiçbir şey olmaz: her zaman aynı parça görüntülenir, ancak seçili sekme değişmiştir. Dolayısıyla burada görüntülenen parça, 17. satırda seçilen parçadır: [Vue1Fragment] parçası;
- 26-30. satırlar: Kullanıcının sekme değişikliğini yönetmek için yazılması gereken yöntem;
26-30. satırlardaki [onTabSelected] yöntemi, sekme değişikliği olduğu anda tetiklenir (kullanıcı zaten seçili olan bir sekmeye tıklarsa hiçbir şey olmaz). Kod şu şekildedir:
@Override
public void onTabSelected(TabLayout.Tab tab) {
if (IS_DEBUG_ENABLED) {
Log.d("onglets", "onTabSelected");
}
// bir sekme seçildi - parça kapsayıcısı tarafından görüntülenen parça değiştiriliyor
// sekmenin konumu
int position = tab.getPosition();
// görüntülenecek parçanın numarası
int numFragment;
switch (position) {
case 0:
// parça numarası [Vue1Fragment]
numFragment = FRAGMENTS_COUNT - 1;
break;
default:
// parça numarası [PlaceholderFragment]
numFragment = session.getNumFragment();
}
// parça görüntüsü
mViewPager.setCurrentItem(numFragment);
}
- 8. satır: Tıklanan sekmenin konumu alınır. Burada 0 veya 1 sayısını alacağız;
- 12-15. satırlar: Tıklanan ilk sekme ise, [Vue1Fragment] parçacığını görüntülemeye hazırlanılır;
- satır 16-18: diğer durumlarda (2 numaralı sekme tıklandığında), 2 numaralı sekme en son seçildiğinde görüntülenen parçayı yeniden görüntülemek için hazırlık yapılır. Bu parçanın numarası o sırada uygulamanın oturumuna kaydedilmişti;
- 21. satır: Parça konteynerinden istenen parçayı görüntülemesi istenir;
Şimdi menü seçeneklerinin yönetimine bakalım (yine [MainActivity] içinde):
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("menu", "onOptionsItemSelected");
}
// menü seçeneklerinin işlenmesi
int id = item.getItemId();
switch (id) {
case R.id.action_settings: {
if (IS_DEBUG_ENABLED) {
Log.d("menu", "action_settings selected");
}
break;
}
case R.id.fragment1: {
showFragment(0);
break;
}
case R.id.fragment2: {
showFragment(1);
break;
}
case R.id.fragment3: {
showFragment(2);
break;
}
case R.id.fragment4: {
showFragment(3);
break;
}
}
// işlenen öğe
return true;
}
- 16-31. satırlar: menüdeki 4 seçeneğin yönetimi. Her yönetici, görüntülenecek parçanın numarasıyla birlikte [showFragment] yöntemini çağırır;
[showFragment] yöntemi şu şekildedir:
// 2 numaralı sekme
private TabLayout.Tab tab2 = null;
private void showFragment(int i) {
if (i < FRAGMENTS_COUNT && mViewPager.getCurrentItem() != i) {
// 2. sekme henüz yoksa, oluşturulur
if (tab2 == null) {
tab2 = tabLayout.newTab();
tabLayout.addTab(tab2);
}
// ikinci sekmenin başlığı belirlenir
tab2.setText(String.format("Fragment n° %s", (i + 1)));
// görüntülenen parça değiştirilir
mViewPager.setCurrentItem(i);
// görüntülenen parçanın numarası oturuma kaydedilir
session.setNumFragment(i);
// 2. sekme seçilir - sekme zaten seçiliyse hiçbir işlem yapılmaz
tab2.select();
}
}
- Uygulamanın başlangıcında sadece bir sekme olduğunu hatırlayalım;
- 2. satır: başlangıçta 2 numaralı sekmeye bir referans, null;
- 5. satır: Görüntüleme koşulları önceki sürüme göre değişmemiştir;
- 7-10. satırlar: 2 numaralı sekme henüz mevcut değilse, oluşturulur (8. satır) ve sekme çubuğuna eklenir (9. satır);
- 12. satır: İkinci sekmenin başlığına, 1'den başlayan numaralandırma ile görüntülenecek parçanın numarası yazılır;
- 14. satır: istenen parça görüntülenir;
- satır 16: Parçanın numarası oturuma kaydedilir;
- satır 18: 2 numaralı sekme seçilir. Eğer zaten seçiliyse, hiçbir şey olmaz: [onTabSelected] yöntemi çalıştırılmaz. Eğer henüz seçili değilse, [onTabSelected] yöntemi tetiklenir. Bu yöntem daha sonra parça konteynerinden 14. satırda zaten görüntülenmiş olan parçayı görüntülemesini ister. [onTabSelected] yönteminde yapılacak basit bir test bu durumu önler:
// parça yalnızca gerekliyse görüntülenir
if (numFragment != mViewPager.getCurrentItem()) {
mViewPager.setCurrentItem(numFragment);
}
Okuyucuların bu yeni sürümü test etmeleri önerilir.
1.12.6. İyileştirmeler
Artık parçalar, yaşam döngüleri, parça komşuluğu kavramı ve sekme çubuğuyla olan ilişkilerini iyi bir şekilde anlıyoruz. Ayrıca, Örnek 11'deki testi başarıyla geçen sağlam bir mimariye sahibiz:
- bir etkinlik ve n fragman;
- tüm parçalar [AbstractFragment] sınıfını genişletir;
- fragmanlar arasında ve fragmanlarla etkinlik arasında paylaşılacak veriler [Session] sınıfına yerleştirilir;
Yeni bir projede, bir arayüz ekleyerek aktivite ile parçalar arasındaki ilişkileri netleştireceğiz.
1.13. Örnek-12: Etkinlik ve parçalar arasındaki ilişkileri kodlamak
Bu örnekte, etkinlik ile parçalar arasındaki asgari ilişkileri tanımlamak istiyoruz. Bunun için şunu kullanacağız:
- parçaların etkinlikten ne talep edebileceğini tanımlayacak bir [IMainActivity] arayüzü;
- her parçanın sahip olması gereken durumu ve yöntemleri tanımlayacak [AbstractFragment] soyut sınıfı;
1.13.1. Projenin Oluşturulması
1.4. paragrafındaki prosedürü izleyerek [Exemple-11] projesini [Exemple-12] olarak kopyalıyoruz. Sonuç olarak şunu elde ediyoruz:
![]() | ![]() |
1.13.2. [IMainActivity] arayüzü
Yukarıdaki örneklerden anlaşıldığı üzere, parçaların etkinlik tarafından oluşturulan oturuma erişmesi gerekmektedir. Ayrıca, bu örneklerde görünmese de tahmin edilebileceği üzere: parçaların olay işleyicileri bazen bir görünüm değişikliğiyle sona erer. Bu değişikliği gerçekleştirmek için etkinlikten talepte bulunulacaktır. [IMainActivity] arayüzü bu durumda şu şekilde olabilir:
![]() |
package exemples.android;
public interface IMainActivity {
// oturuma erişim
Session getSession();
// Görünüm değiştirme
void navigateToView(int position);
// hata ayıklama modu
boolean IS_DEBUG_ENABLED = true;
}
- satırda, daha önce [MainActivity] sınıfında bulunan bir sabitin varlığına dikkat edilmelidir. Fragmanlar ile aktivite arasındaki bağımlılığı azaltmak ve bunu [AbstractFragment] ile [IMainActivity] arasındaki bir bağa indirgemek istiyoruz. Böylece aktivite, [MainActivity] dışında başka bir adla adlandırılabilir. [IS_DEBUG_ENABLED] sabiti parçalarda kullanıldığından, [IMainActivity] arayüzüne taşınır.
1.13.3. [AbstractFragment] soyut sınıfı
[AbstractFragment] soyut sınıfında çok az değişiklik yapılır:
// alt sınıflara açık veriler
protected boolean afterViewsDone = false;
final protected boolean isDebugEnabled = IMainActivity.IS_DEBUG_ENABLED;
// etkinlik
protected IMainActivity mainActivity;
protected Activity activity;
...
// parça güncelleme
protected void update() {
// etkinlik ve oturum alınır
if (mainActivity == null) {
this.activity = getActivity();
if (this.activity != null) {
this.mainActivity = (IMainActivity) activity;
this.session = this.mainActivity.getSession();
}
}
// alt sınıftan kendini güncellemesini istenir
updateFragment();
}
- 6. ve 7. satırlar: aktivite üzerinde iki tür referans tutulmaktadır:
- 6. satır: [IMainActivity] arayüzünü uygulayan aktiviteye bir referans;
- 7. satır: Android sınıfı [Activity]'ten miras alan aktiviteye bir referans. Bu, tüm aktiviteler için geçerlidir;
Bu iki referans elbette aynı nesneye işaret eder. Ancak bu nesne, iki farklı türde görülür. Bu, yürütme sırasında tür dönüştürme işlemlerinden kaçınmamızı sağlar;
- 14. satır: [getActivity] yöntemi kullanılarak aktiviteye bir referans alınır;
- 15. satır: Bu referans sıfırdan farklıysa, oturuma erişebiliriz;
- satır 16-17: Etkinliği, [IMainActivity] arayüzünü uygulayan bir nesne olarak ve oturumu saklıyoruz;
1.13.4. Parça yöneticisinin değiştirilmesi
[MainActivity] sınıfındaki [SectionsPagerAdapter] parça yöneticisi tek bir noktada değiştirilir: [Fragment] türündeki parçaları yönetmek yerine, artık [AbstractFragment] türündeki parçaları yönetir:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private AbstractFragment[] fragments;
// parça numarası
private static final String ARG_SECTION_NUMBER = "section_number";
// oluşturucu
public SectionsPagerAdapter(FragmentManager fm) {
// üst sınıf
super(fm);
// parça dizisinin başlatılması
fragments = new AbstractFragment[FRAGMENTS_COUNT];
for (int i = 0; i < fragments.length - 1; i++) {
...
}
// bir + parçası
fragments[fragments.length - 1] = new Vue1Fragment_();
}
// parça numarası ve konumu
@Override
public AbstractFragment getItem(int position) {
...
}
// yönetilen parça sayısını döndürür
@Override
public int getCount() {
...
}
}
1.13.5. [MainActivity] sınıfında yapılan değişiklik
[MainActivity] sınıfı, [IMainActivity] arayüzünü uygulamalıdır:
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity implements IMainActivity{
...
// oturum enjeksiyonu
@Bean(Session.class)
protected Session session;
...
// oturum getter'ı
public Session getSession() {
return session;
}
@Override
public void navigateToView(int position) {
// konum görünümünü görüntüler
if(mViewPager.getCurrentItem()!=position){
// parça görüntüleme
mViewPager.setCurrentItem(position);
}
}
- 10-12. satırlar: [getSession] yöntemi zaten mevcuttu;
- 15-22. satırlar: [navigateToView] yöntemi, [position] numaralı parçayı görüntülemektedir;
- 17. satır: Yapılması gereken bir şey olup olmadığına bakılır;
- 19. satır: [position] numaralı parça görüntülenir;
Bu aşamada uygulamayı çalıştırın. Çalışması gerekir.
1.13.6. [MainActivity]'te parçaların görüntülenmesinin değiştirilmesi
Şu anda, [MainActivity] sınıfı şu komutla bir parçayı görüntüler:
// Vue1 görüntüleme
mViewPager.setCurrentItem(FRAGMENTS_COUNT - 1);
[navigateToView] yöntemi de aynı işlevi gördüğünden, bu tür komutlar her yerde (2 yerde) şu şekilde değiştirilir:
Ardından uygulamayı çalıştırın. Uygulama hâlâ çalışmalıdır.
1.13.7. Sonuç
Bundan sonra her zaman önceki mimariyi kullanacağız:
- [IMainActivity] arayüzünü uygulayan bir aktivite;
- [AbstractFragment] sınıfını genişleten parçalar; bu da onların [updateFragment] yöntemini uygulamalarını gerektirir. Bunlar ayrıca, [afterViewsDone] boole değerini true olarak ayarladıkları bir [@AfterViews] yöntemine sahip olmalıdır;
- parçalar ve etkinlik arasında paylaşılacak verileri kapsayan bir oturum;
1.14. Örnek-13: Fragmanlar içeren Örnek-05
[Exemple-05] projesinde görünümler arası gezinme özelliğini ekledik. O zaman bu, etkinlikler arası gezinmeydi: 1 görünüm = 1 etkinlik. Burada ise [AbstractFragment] türünde birden fazla görünüme sahip tek bir etkinlik oluşturmayı hedefliyoruz.
1.14.1. Projenin oluşturulması
1.4. paragrafındaki prosedürü izleyerek önceki [Exemple-12] projesini [Exemple-13] olarak kopyalıyoruz. Sonuç olarak şunu elde ediyoruz:
![]() | ![]() |
1.14.2. Projenin Yapılandırılması
Kodu düzenlemek için paketler kullanmaya başlayacağız. Şu an için iki ayrı alan ayırt edebiliriz:
- faaliyet yönetimi;
- parça yönetimi;
Bunlar için [exemples.android.activity] ve [exemples.android.fragments] adlı iki paket oluşturuyoruz:
![]() |
![]() | ![]() |
Aynı şekilde [exemples.android.fragments] paketini oluşturmak için de:
![]() | ![]() |
[8]'te, [architecture] adlı üçüncü bir paket oluşturulur; bu pakete, uygulamamızın mimarisinin temel öğeleri olan [IMainActivity, AbstractFragment, Session, MyPager] varlıklarını yerleştireceğiz. Bu, belirli bir mimari seçimi yaptığımızı bize hatırlatmak içindir. Ardından, [9]'te belirtildiği gibi projedeki mevcut öğeleri taşıyın. Her taşıma işlemi, [Refactor] düğmesine tıklanarak onaylanmalıdır.
Bu aşamada, uygulamayı derleyin. [MainActivity] dosyasında şu hatalarla karşılaşıyoruz:
![]() |
Sınıflar paketlere taşınırken, Android Studio uygulama kodlarında gerekli değişiklikleri yapmıştır (örneğin, 18-21. satırlar). 15. ve 17. satırlarda bahsedilen sınıflar taşınmamıştır. Bu sınıflar, Android Annotations kütüphanesi tarafından oluşturulmuştur. Bu sınıflar için imports değerlerini manuel olarak değiştirmeniz gerekir. Dolayısıyla bu satırlar şu şekilde olur:
![]() |
Bu değişiklik yapıldıktan sonra derleme hatası kalmaz. Uygulamayı çalıştırın. Ardından şu hata mesajı görüntülenir:
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{exemples.android/exemples.android.MainActivity_}:
Bu hata, uygulamanın manifest dosyasından kaynaklanmaktadır:
![]() |
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity_"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- ve 12. satırlar, belirtilen etkinliğin [exemples.android.MainActivity_] olduğunu belirtir. Ancak etkinlik [activity] paketine taşındığı için, 12. satır artık şöyle olmalıdır:
android:name=".activity.MainActivity_"
[activity]'in başındaki . işaretine dikkat edin. Yine, Android Studio, manifest dosyasını güncelleyemedi çünkü bu dosya, taşınmamış bir Android Annotations sınıfına referans veriyor. Dolayısıyla, AA kütüphanesinin kullanılması bir takım sorunlara yol açmaktadır.
1.14.3. Projenin temizlenmesi
Yeni projede:
- artık sekmeler, yüzen düğmeler ve menüler yok;
- [PlaceholderFragment] parçaları ortadan kalkar. Uygulama, halihazırda mevcut olan iki [Vue1Fragment] parçasını ve oluşturulması gereken [Vue2Fragment] parçasını yönetecektir;
- oturum artık aynı değil;
1.14.3.1. Parçaların temizlenmesi
[PlaceHolderFragment] ve [1] sınıflarını silin:
![]() | ![]() |
Aynı şekilde, bu parçaya ([2]) bağlı olan [res / layout / fragment_main.xml] görünümünü de silin.
1.14.3.2. Oturumun temizlenmesi
Oturum şu anda şu şekildedir:
package exemples.android.architecture;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
// ziyaret edilen parça sayısı
private int numVisit;
// ikinci sekmede görüntülenen [PlaceholderFragment] türündeki parça numarası
private int numFragment;
// getter ve setter'lar
public int getNumVisit() {
return numVisit;
}
public void setNumVisit(int numVisit) {
this.numVisit = numVisit;
}
public int getNumFragment() {
return numFragment;
}
public void setNumFragment(int numFragment) {
this.numFragment = numFragment;
}
}
Bu oturumdan hiçbir şeyi saklamıyoruz.
Projeyi derleyin. Hata veren satırlar, oturum içeriğini kullanan satırlardır. Bunları silin. [Vue1Fragment] sınıfında da [numVisit] değişkenini koddan silin; kod şu şekilde olur:
package exemples.android.fragments;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("afterViews %s", getParentInfos()));
}
}
// olay yöneticisi
@Click(R.id.buttonValider)
protected void doValider() {
// girilen adı görüntüler
Toast.makeText(getActivity(), String.format("Bonjour %s", editTextNom.getText().toString()), Toast.LENGTH_LONG).show();
}
// parça güncelleme
@Override
protected void updateFragment() {
}
}
1.14.3.3. Sekmelerin, yüzen düğmenin ve menünün kaldırılması
Sekmelerin ve yüzen düğmenin kaldırılması iki yerde gerçekleştirilir:
- bu öğeleri ve görünümdeki konumlarını tanımlayan [res / layout / activity-main.xml] görünümünde;
- bu öğeleri ve görünümdeki konumlarını tanımlayan [res / layout / activity-main.xml] görünümünde;
Menünün kaldırılması da iki yerden yapılır:
- menü seçeneklerini tanımlayan [res / menu / menu-main.xml] görünümünde;
- [MainActivity] etkinliğinin kodunda;
[res / layout / activity-main.xml] görünümünün kodu şu anda şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activity.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>
<exemples.android.architecture.MyPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"/>
</android.support.design.widget.CoordinatorLayout>
- [28-31, 41-47] satırları silinir;
- ayrıca 18-24. satırlardaki araç çubuğunu da silin;
[res / menu / menu_main.xml] menüsünün kodu şu anda şöyledir:
<menu 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"
tools:context=".activity.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment1"
android:title="@string/fragment1"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment2"
android:title="@string/fragment2"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment3"
android:title="@string/fragment3"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment4"
android:title="@string/fragment4"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
- 9-24. satırları silinecektir. Böylece kullanılmayacak bir seçenek bırakılacaktır. Bu, sadece kopyala/yapıştır yoluyla çoğaltılabilecek bir menü seçeneği beyanı örneği elde etmek içindir;
[MainActivity] sınıfında sekmelere, yüzen düğmeye, araç çubuğuna ve menüye atıfta bulunan her şeyi silin. Bu atıfları bulmanın en kolay yolu, bunların tanımlarını silmektir:
// sekme yöneticisi
@ViewById(R.id.tabs)
protected TabLayout tabLayout;
// yüzen düğme
@ViewById(R.id.fab)
protected FloatingActionButton fab;
ve uygulamayı yeniden derleyin. Hatalı satırlar, artık mevcut olmayan öğelere atıfta bulunan satırlardır. Bu satırların tümünü silin. Ayrıca, fragman yöneticisini, sildiğimiz [PlaceholderFragment] fragmanına artık atıfta bulunmayacak şekilde değiştirin:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private AbstractFragment[] fragments;
// oluşturucu
public SectionsPagerAdapter(FragmentManager fm) {
// üst öğe
super(fm);
}
// parça numarası ve konumu
@Override
public AbstractFragment getItem(int position) {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("SectionsPagerAdapter", String.format("getItem[%s]", position));
}
return fragments[position];
}
// yönetilen parça sayısını döndürür
@Override
public int getCount() {
return fragments.length;
}
}
- 7-10. satırlar: tüm fragman oluşturma işlemleri kaldırılmıştır;
Bu aşamada artık derleme hatası olmamalıdır. [MainActivity] sınıfında, aşağıdaki ara koda ulaştık:
package exemples.android.activity;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import exemples.android.architecture.IMainActivity;
import exemples.android.architecture.MyPager;
import exemples.android.architecture.Session;
import exemples.android.fragments.Vue1Fragment_;
import org.androidannotations.annotations.*;
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity implements IMainActivity {
// parça konteyneri
@ViewById(R.id.container)
protected MyPager mViewPager;
// araç çubuğu
@ViewById(R.id.toolbar)
protected Toolbar toolbar;
// oturum enjeksiyonu
@Bean(Session.class)
protected Session session;
// parça sayısı
private final int FRAGMENTS_COUNT = 5;
// parçaların bitişikliği
private final int OFF_SCREEN_PAGE_LIMIT = 2;
// hata ayıklama modu
public static final boolean IS_DEBUG_ENABLED = true;
// parça yöneticisi
private SectionsPagerAdapter mSectionsPagerAdapter;
// oluşturucu
public MainActivity() {
// günlüğü
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "constructor");
}
}
@AfterViews
protected void afterViews() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterViews");
}
// araç çubuğu - uygulama adı burada görüntülenir
setSupportActionBar(toolbar);
// parça yöneticisi
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// parça konteyneri, parça yöneticisiyle ilişkilidir
// yani, parça konteynerindeki i numaralı parça, parça yöneticisi tarafından sağlanan i numaralı parçadır
mViewPager.setAdapter(mSectionsPagerAdapter);
// parça ofseti
mViewPager.setOffscreenPageLimit(OFF_SCREEN_PAGE_LIMIT);
// parçalar arasında kaydırma engellenir
mViewPager.setSwipeEnabled(false);
// kaydırma yok
mViewPager.setScrollingEnabled(false);
// Vue1 ekranı
navigateToView(FRAGMENTS_COUNT - 1);
}
@AfterInject
protected void afterInject() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterInject");
}
}
// oturum getter'ı
public Session getSession() {
return session;
}
@Override
public void navigateToView(int position) {
// konum görünümünü göster
if (mViewPager.getCurrentItem() != position) {
// parça görüntüleme
mViewPager.setCurrentItem(position);
}
}
// parça yöneticisi
// ana görünümde görüntülenecek parçalar için bu yöneticiye başvurulur
// [getItem] ve [getCount] yöntemlerini tanımlamalıdır - diğerleri isteğe bağlıdır
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private AbstractFragment[] fragments;
// oluşturucu
public SectionsPagerAdapter(FragmentManager fm) {
// üst öğe
super(fm);
}
// parça numarası ve konumu
@Override
public AbstractFragment getItem(int position) {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("SectionsPagerAdapter", String.format("getItem[%s]", position));
}
return fragments[position];
}
// yönetilen parça sayısını döndürür
@Override
public int getCount() {
return fragments.length;
}
}
}
Yapılması gereken birkaç değişiklik kaldı:
- artık gereksiz olan 31. satırı silin;
- 33. satır: parça komşuluğu olarak 1 değerini girin;
- 76. satır: 0 numaralı görünüme geçin. İlk olarak bu görünüm görüntülenecektir;
- 108. satır: diziyi [Vue1Fragment_] parçasıyla başlatın:
// parçalar
private AbstractFragment[] fragments = new AbstractFragment[]{new Vue1Fragment_()};
Yani tek bir fragmanımız var. Uygulamayı çalıştırın. Aşağıdaki sonucu almalısınız:

[Valider] düğmesi çalışmalıdır.
1.14.4. Parçaların ve ilgili görünümlerin oluşturulması
Uygulamanın, [Exemple-05] projesine ait iki görünümü olacaktır. Mevcut projede [vue1.xml] görünümü zaten mevcut. Şimdi [vue2.xml]'i [Exemple-05]'ten [Exemple-12]'e kopyalayıp yapıştırarak çoğaltacağız (iki projeyi de açın ve aralarında kopyala/yapıştır işlemi gerçekleştirin).
![]() | ![]() |
- [1]'te, yeni görünüm. Düzenlemeye çalıştığımızda, [2] dosyasında hatalar ortaya çıkıyor. [strings.xml] ve [3] dosyalarını değiştirerek bu yeni görünüm tarafından referans verilen dizeleri eklememiz gerekiyor:
<resources>
<string name="app_name">Exemple-13</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
<!-- görünüm 1 -->
<string name="titre_vue1">Vue n° 1</string>
<string name="txt_nom">Quel est votre nom ?</string>
<string name="btn_valider">Valider</string>
<!-- görünüm 2 -->
<string name="btn_vue2">Vue n° 2</string>
<string name="titre_vue2">Vue n° 2</string>
<string name="btn_vue1">Vue n° 1</string>
</resources>
[Vue1Fragment] sınıfını [Vue2Fragment] olarak kopyalıyoruz:
![]() |
ve kopyaladığımız kodu şu şekilde değiştiriyoruz:
package exemples.android.fragments;
import android.util.Log;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.EFragment;
@EFragment(R.layout.vue2)
public class Vue2Fragment extends AbstractFragment {
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue2Fragment", String.format("afterViews %s", getParentInfos()));
}
}
// parça güncellemesi
@Override
protected void updateFragment() {
}
}
- 9. satır: parça, [res / layout / vue2.xml] görünümüne atanır;
- 10. satır: sınıf, [AbstractFragment] soyut sınıfını genişletir;
- 12-20. satırlar: zorunlu [@AfterViews] yöntemi;
- satır 23-25: zorunlu [updateFragment] yöntemi;
1.14.5. Parçaların ve aralarındaki gezinmenin kurulması
Etkinlik artık iki parçayı yönetecektir. [SectionsPagerAdapter] sınıfı şu şekilde güncellenir:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private AbstractFragment[] fragments = new AbstractFragment[]{new Vue1Fragment_(), new Vue2Fragment_()};
...
}
[IMainActivity] arayüzü, [navigateToView] yöntemi ile görünümler arası gezinmeyi sağlar. [Vue1Fragment] parçacığındaki [Vue n° 2] düğmesine yapılan tıklamayı yöneteceğiz:
package exemples.android.fragments;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("afterViews %s", getParentInfos()));
}
}
// olay işleyicileri ----------------------------------
@Click(R.id.buttonValider)
protected void doValider() {
// girilen ad görüntülenir
Toast.makeText(activity, String.format("Bonjour %s", editTextNom.getText().toString()), Toast.LENGTH_LONG).show();
}
@Click(R.id.buttonVue2)
protected void showVue2() {
mainActivity.navigateToView(1);
}
// parça güncelleme
@Override
protected void updateFragment() {
}
}
- 37-40. satırlar: [showVue2] yöntemi, [Vue n° 2] düğmesine yapılan 'tıklama' olayını yönetir;
- 39. satır: Etkinliğin [navigateToView] yöntemi ile gezinme yapılır. Burada, etkinliğin üst sınıfta şu şekilde kaydedildiğini hatırlatmak isteriz:
// etkinlik
protected IMainActivity mainActivity;
şeklinde kaydedildiğini ve herhangi bir olay işleyicisine ulaşıldığında bu etkinliğin zaten başlatılmış olduğunu hatırlatmak isteriz.
- satır 34: komut, üst sınıftaki [activity] değişkenini kullanır; bu değişken, Android [Activity] türünde bir örnek olarak etkinliğe yapılan bir referanstır;
protected Activity activity;
[Vue2Fragment] parçası için de benzer bir kod bulunmaktadır:
package exemples.android.fragments;
import android.util.Log;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
@EFragment(R.layout.vue2)
public class Vue2Fragment extends AbstractFragment {
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue2Fragment", String.format("afterViews %s", getParentInfos()));
}
}
// olay yöneticileri ----------------------------------------------
@Click(R.id.buttonVue1)
protected void showVue1() {
mainActivity.navigateToView(0);
}
// parça güncellemesi
@Override
protected void updateFragment() {
}
}
- 24-27. satırlar: [showVue1] yöntemi, [Vue n° 1] düğmesindeki 'tıklama' olayını yönetir;
Projeyi çalıştırın ve görünümler arası gezinmenin çalıştığını kontrol edin.
1.14.6. Oturum Tanımı
Uygulamanın işleyişi şu şekildedir:
- 1 numaralı görünümde bir ad girilir;
- bu adın 2 numaralı görünümde görüntülenmesi;
1 numaralı görünümün girilen adı 2 numaralı görünüme iletebilmesi için aşağıdaki oturumu kullanacağız;
package exemples.android.architecture;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
// ad
private String nom;
// alıcı ve ayarlayıcılar
...
}
- 8. satır: girilen isim;
[MainActivity] sınıfı, oturumu şu şekilde başlatacaktır:
// oturum enjeksiyonu
@Bean(Session.class)
protected Session session;
...
@AfterInject
protected void afterInject() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterInject");
}
// oturum başlatma
session.setNom("");
}
1.14.7. Parçaların son hali
[Vue1Fragment] parçasında, [Valider] düğmesine tıklama işleyicisinin kodunu değiştiriyoruz:
package exemples.android.fragments;
import android.util.Log;
import android.widget.EditText;
import android.widget.Toast;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
...
// olay işleyicileri ----------------------------------
@Click(R.id.buttonValider)
protected void doValider() {
// girilen ad kaydediliyor
String nom = editTextNom.getText().toString();
// görüntülenir
Toast.makeText(activity, nom, Toast.LENGTH_LONG).show();
}
@Click(R.id.buttonVue2)
protected void showVue2() {
// girilen adı oturuma kaydedilir
session.setNom(editTextNom.getText().toString());
// 2 numaralı görünüme geçilir
mainActivity.navigateToView(1);
}
// parça güncellenir
@Override
protected void updateFragment() {
}
}
- satırlar: 31-37: [Vue n° 2] düğmesine yapılan tıklamayı yönetiyoruz;
- 34. satır: 2 numaralı görünüme geçmeden önce, yeni görünümün bu bilgiye erişebilmesi için girilen adı oturuma kaydediyoruz;
[Vue2Fragment] görünümü şu şekilde değişir:
package exemples.android.fragments;
import android.util.Log;
import android.widget.TextView;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.vue2)
public class Vue2Fragment extends AbstractFragment {
// görsel arayüz bileşenleri
@ViewById(R.id.textViewBonjour)
protected TextView textViewBonjour;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue2Fragment", String.format("afterViews %s", getParentInfos()));
}
}
// olay işleyicileri ----------------------------------------------
@Click(R.id.buttonVue1)
protected void showVue1() {
mainActivity.navigateToView(0);
}
// parça güncellemesi
@Override
protected void updateFragment() {
// oturumda girilen adı alır
String nom = session.getNom();
// görüntülenir
textViewBonjour.setText(String.format("Bonjour %s !", nom));
}
}
2 numaralı görünüm görüntülendiğinde, 1 numaralı görünümde girilen adı görüntülemek gerekir. Görüntülenmesinin hemen ardından [updateFragment] yönteminin çalıştırılacağı bilinmektedir. Dolayısıyla, adın görüntülenmesi için gerekli kodu bu yönteme (36-42. satırlar) ekleyebiliriz.
- 16-17. satırlar: görünümün tek görsel bileşeninin tanımlanması;
- 39. satır: 1 numaralı görünümde girilen ad, oturumdan alınır;
- 41. satır: [textViewBonjour] etiketi değiştirilir;
Projeyi çalıştırın ve çalıştığını kontrol edin.
1.14.8. Parça yaşam döngüsü yönetimi
[Vue1Fragment] parçasında, [@AfterViews] yöntemi şu şekildedir:
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("afterViews %s", getParentInfos()));
}
}
Bu yöntem eksiktir. Zira, bir [onDestroyView] işlemi sonrasında parçanın yeniden kullanılması durumunu her zaman hesaba katmak gerekir. Bu durumda, parça 1'in görünümü yeniden oluşturulur ve daha önce girilmiş olabilecek ad, görünümden kaybolur. Bunu istemiyoruz. Şu anda girilen ad görüntülenmeye devam ediyor, çünkü 1 numaralı parçaların bitişik olması nedeniyle [Vue1Fragment] parçasının yaşam döngüsü yalnızca bir kez yürütülüyor. Ancak, parçanın yeniden kullanılması durumunu da hesaba katmak daha uygun olacaktır.
Bu sorunu çözmenin birkaç yolu vardır:
- [update] yönteminin, parçanın her görüntülenişinde sistematik olarak çalıştırılmasından yararlanarak girilen adı güncelleyebiliriz;
- bu güncellemeyi yalnızca [@AfterViews] yöntemi yeniden çalıştırıldığında gerçekleştirebiliriz. Biz bu ikinci yolu tercih ediyoruz;
[Vue1Fragment] kodunu şu şekilde değiştiriyoruz:
// görsel arayüz öğeleri
@ViewById(R.id.editTextNom)
protected EditText editTextNom;
// veri
private String nom;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("afterViews %s", getParentInfos()));
}
// görüntülenen metni (yeniden) başlatıyoruz
editTextNom.setText(nom);
}
// olay işleyicileri ----------------------------------
...
@Click(R.id.buttonVue2)
protected void showVue2() {
// parça geri dönüştürülürse geri alınabilmesi için girilen adı not eder
nom = editTextNom.getText().toString();
// girilen ad oturuma kaydedilir
session.setNom(nom);
// 2 numaralı görünüme geçilir
activity.navigateToView(1);
}
- 27. satır: Görünüm 1'den Görünüm 2'ye geçmek üzereyken, girilen adı kaydediyoruz;
- 17. satır: Parça yaşam döngüsünün her yeni çalıştırılmasında, en son girilen ad yeniden görüntülenir;
[Vue2Fragment] parçası için mevcut kod yeterlidir:
// görsel arayüz bileşenleri
@ViewById(R.id.textViewBonjour)
protected TextView textViewBonjour;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue2Fragment", String.format("afterViews %s", getParentInfos()));
}
}
// parça güncelleme
@Override
protected void updateFragment() {
// oturumda girilen adı alıyoruz
String nom = session.getNom();
// görüntülenir
textViewBonjour.setText(String.format("Bonjour %s !", nom));
}
- Görünümün tek görsel bileşeni (satır 3), görünüm her görüntülendiğinde (satır 21) güncellenir. Dolayısıyla [@AfterViews] yöntemine eklenecek bir şey yoktur;
1.14.9. Sonuç
Bu noktada, mimarimizin uygunluğunu bir kez daha göstermiş olduk:
- [IMainActivity] arayüzünü uygulayan bir aktivite;
- [AbstractFragment] sınıfını genişleten parçalar; bu da onların [updateFragment] yöntemini uygulamalarını gerektirir. Bu parçaların ayrıca, [@AfterViews] yöntemine sahip olmaları ve bu yöntemde [afterViewsDone] boole değerini true olarak ayarlamaları gerekir;
- fragmanlar ve etkinlik arasında paylaşılacak verileri kapsayan bir oturum;
1.15. Örnek-14: İki katmanlı bir mimari
Aşağıdaki mimariye sahip tek görünümlü bir uygulama oluşturacağız:
![]() |
1.15.1. Projenin oluşturulması
1.4. paragrafındaki prosedürü izleyerek önceki proje [Exemple-12]’i [Exemple-13] olarak kopyalıyoruz. Aşağıdaki sonucu elde ediyoruz:
![]() | ![]() |
1.15.2. [vue1] görünümü
Uygulamada yalnızca [vue1.xml] adlı bir görünüm olacaktır. Bu nedenle, diğer [vue2.xml] görünümünü ve ona ait parçayı da siliyoruz:
![]() | ![]() |
Uygulamayı derleyin. [MainActivity]'te hatalar ortaya çıkıyor:
![]() |
[SectionsPagerAdapter] parça yöneticisinde aşağıdaki 4. satırı düzeltin
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private AbstractFragment[] fragments = new AbstractFragment[]{new Vue1Fragment_(), new Vue2Fragment_()};
...
Yukarıdaki 4. satır şu şekilde olur:
// parçalar
private AbstractFragment[] fragments = new AbstractFragment[]{new Vue1Fragment_()};
Artık gereksiz hale gelen [Ctrl-Shift-O] içe aktarmalarını silin. Artık derleme hatası olmamalıdır. Projeyi çalıştırın: 1 numaralı görünüm görünmelidir. Şimdi bunu değiştireceğiz.
Rastgele sayılar üretmeye olanak sağlayacak [vue1.xml] görünümünü oluşturacağız:
![]() |
Bileşenleri şunlardır:
XML kodu şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="20dp"
android:orientation="vertical" >
<TextView
android:id="@+id/txt_Titre2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/aleas"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="@+id/txt_nbaleas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_Titre2"
android:layout_marginTop="20dp"
android:text="@string/txt_nbaleas" />
<EditText
android:id="@+id/edt_nbaleas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_nbaleas"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_nbaleas"
android:inputType="number" />
<TextView
android:id="@+id/txt_errorNbAleas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/edt_nbaleas"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/edt_nbaleas"
android:text="@string/txt_errorNbAleas"
android:textColor="@color/red" />
<TextView
android:id="@+id/txt_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_nbaleas"
android:layout_marginTop="20dp"
android:text="@string/txt_a" />
<EditText
android:id="@+id/edt_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_a"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_a"
android:inputType="number" />
<TextView
android:id="@+id/txt_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_a"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/edt_a"
android:text="@string/txt_b" />
<EditText
android:id="@+id/edt_b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_a"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/txt_b"
android:inputType="number" />
<TextView
android:id="@+id/txt_errorIntervalle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/edt_b"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/edt_b"
android:text="@string/txt_errorIntervalle"
android:textColor="@color/red" />
<Button
android:id="@+id/btn_Executer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_a"
android:layout_marginTop="20dp"
android:text="@string/btn_executer" />
<TextView
android:id="@+id/txt_Reponses"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_Executer"
android:layout_marginTop="30dp"
android:text="@string/list_reponses"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/blue" />
<ListView
android:id="@+id/lst_reponses"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_below="@+id/txt_Reponses"
android:layout_marginTop="40dp"
android:background="@color/wheat"
android:clickable="true"
tools:listitem="@android:layout/simple_list_item_1" >
</ListView>
</RelativeLayout>
Önceki görünüm, [res / values / strings.xml] dosyasında tanımlanan etiketleri kullanır:
<resources>
<string name="app_name">Exemple-14</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
<!-- görünüm 1 -->
<string name="titre_vue1">Vue n° 1</string>
<string name="list_reponses">Liste des réponses</string>
<string name="btn_executer">Exécuter</string>
<string name="aleas">Génération de N nombres aléatoires</string>
<string name="txt_nbaleas">Valeur de N :</string>
<string name="txt_a">"Intervalle [a,b] de génération, a : "</string>
<string name="txt_b">"b : "</string>
<string name="txt_dummy">Dummy</string>
<string name="txt_errorNbAleas">Tapez un nombre entier >=1</string>
<string name="txt_errorIntervalle">Les bornes de l\'intervalle doivent être entières et b>=a</string>
</resources>
[vue1.xml] dosyasında kullanılan renkler, [res / values / colors.xml] dosyasında tanımlanmıştır:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<!-- uygulama renkleri -->
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="wheat">#FFEFD5</color>
<color name="floral_white">#FFFAF0</color>
</resources>
1.15.3. Oturum
![]() |
Burada sadece bir parça olduğu için parçalar arası iletişim öngörülmemelidir. Dolayısıyla oturum boş olacaktır:
package exemples.android.architecture;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
}
Bu aşamada uygulamayı derleyin. Artık boş olan oturum öğelerini kullanan satırlarda hatalar görünecektir. Bu satırları silin ve derleme işleminin artık hata vermediğini kontrol edin.
1.15.4. [Vue1Fragment] parçası
![]() |
Mevcut [Vue1Fragment] parçasını şu şekilde değiştiriyoruz:
package exemples.android.fragments;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
import java.util.ArrayList;
import java.util.List;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.lst_reponses)
protected ListView listReponses;
@ViewById(R.id.edt_nbaleas)
protected EditText edtNbAleas;
@ViewById(R.id.edt_a)
protected EditText edtA;
@ViewById(R.id.edt_b)
protected EditText edtB;
@ViewById(R.id.txt_errorNbAleas)
protected TextView txtErrorAleas;
@ViewById(R.id.txt_errorIntervalle)
protected TextView txtErrorIntervalle;
// bir komuta verilen yanıtların listesi
private List<String> reponses = new ArrayList<>();
// listview adaptörü
private ArrayAdapter<String> adapterReponses;
// girişler
private int nbAleas;
private int a;
private int b;
@AfterViews
protected void afterViews() {
// bellek
afterViewsDone = true;
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("afterViews %s", getParentInfos()));
}
// hata mesajları gizlenir
txtErrorAleas.setVisibility(View.INVISIBLE);
txtErrorIntervalle.setVisibility(View.INVISIBLE);
}
@Click(R.id.btn_Executer)
void doExecuter() {
// önceki olası hata mesajları gizlenir
txtErrorAleas.setVisibility(View.INVISIBLE);
txtErrorIntervalle.setVisibility(View.INVISIBLE);
// girdilerin geçerliliği kontrol edilir
if (!isPageValid()) {
return;
}
}
// girilen verilerin geçerliliği kontrol ediliyor
private boolean isPageValid() {
...
}
@Override
protected void updateFragment() {
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("updateFragment %s", getParentInfos()));
}
}
}
- Burada, yaşam döngüsü yalnızca uygulamanın başlatılması sırasında tek bir kez yürütülecek tek bir parça bulunmaktadır. Bu nedenle, [@AfterViews] (satır 46-57) ve [udateFragment] (satır 75-81) yöntemleri, uygulamanın başlatılması sırasında yalnızca bir kez çalıştırılacaktır;
- 55-56. satırlar: Görünümdeki iki hata mesajı (aşağıda gösterilen) [1-2] gizlenir;
![]() |
- 59-60. satırlar: [Exécuter] düğmesine tıklandığında yürütülen yöntem;
- 71-73. satırlar: Girişlerin geçerliliği kontrol edilir;
[isPageValid] yöntemi şöyledir:
// girdiler
private int nbAleas;
private int a;
private int b;
...
// girilen verilerin geçerliliği kontrol ediliyor
private boolean isPageValid() {
// rastgele sayıların sayısı giriliyor
nbAleas = 0;
Boolean erreur;
int nbErreurs = 0;
try {
nbAleas = Integer.parseInt(edtNbAleas.getText().toString());
erreur = (nbAleas < 1);
} catch (Exception ex) {
erreur = true;
}
// hata mı?
if (erreur) {
nbErreurs++;
txtErrorAleas.setVisibility(View.VISIBLE);
}
// a'nın girilmesi
a = 0;
erreur = false;
try {
a = Integer.parseInt(edtA.getText().toString());
} catch (Exception ex) {
erreur = true;
}
// hata mı?
if (erreur) {
nbErreurs++;
txtErrorIntervalle.setVisibility(View.VISIBLE);
}
// "b" girişi
b = 0;
erreur = false;
try {
b = Integer.parseInt(edtB.getText().toString());
erreur = b < a;
} catch (Exception ex) {
erreur = true;
}
// hata mı?
if (erreur) {
nbErreurs++;
txtErrorIntervalle.setVisibility(View.VISIBLE);
}
// geri
return (nbErreurs == 0);
}
- satır 2-4: Bu üç alan, [isPageValid] yöntemi tarafından başlatılır. Ayrıca, bu yöntem tüm girdiler geçerliyse true değerini, aksi takdirde false değerini döndürür. Girdiler geçersizse, ilgili hata mesajları görüntülenir;
Bu aşamada uygulama çalıştırılabilir durumdadır. Hatalı veriler girerek [isPageValid] yönteminin çalışmasını kontrol edin.
1.15.5. [métier] katmanı
![]() |
![]() |
[métier] katmanı, aşağıdaki [IMetier] arayüzünü sunar:
package exemples.android.metier;
import java.util.List;
public interface IMetier {
List<Object> getAleas(int a, int b, int n);
}
[getAleas(a,b,n)] yöntemi normalde [a,b] aralığında n adet rastgele tamsayı döndürür. Ayrıca, bu yöntemin her üç çağrıda bir istisna döndürmesi öngörülmüştür; bu istisna da yöntemin döndürdüğü yanıtlara eklenmiştir. Sonuç olarak, bu yöntem [Exception] veya [Integer] türünde nesnelerden oluşan bir liste döndürür.
Bu arayüzün [Metier] uygulaması şu şekildedir:
package exemples.android.metier;
import org.androidannotations.annotations.EBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@EBean(scope = EBean.Scope.Singleton)
public class Metier implements IMetier {
public List<Object> getAleas(int a, int b, int n) {
// nesnelerin listesi
List<Object> réponses = new ArrayList<Object>();
// bazı kontroller
if (n < 1) {
réponses.add(new AleaException("Le nombre d'entier aléatoires demandé doit être supérieur ou égal à 1"));
}
if (a < 0) {
réponses.add(new AleaException("Le nombre a de l'intervalle [a,b] doit être supérieur à 0"));
}
if (b < 0) {
réponses.add(new AleaException("Le nombre b de l'intervalle [a,b] doit être supérieur à 0"));
}
if (a >= b) {
réponses.add(new AleaException("Dans l'intervalle [a,b], on doit avoir a< b"));
}
// hata mı?
if (réponses.size() != 0) {
return réponses;
}
// rastgele sayılar üretiliyor
Random random = new Random();
for (int i = 0; i < n; i++) {
// her 3 denemede 1 kez rastgele bir istisna oluşturuluyor
int nombre = random.nextInt(3);
if (nombre == 0) {
réponses.add(new AleaException("Exception aléatoire"));
} else {
// aksi takdirde iki sınır arasında rastgele bir sayı döndürülüyor [a,b]
réponses.add(Integer.valueOf(a + random.nextInt(b - a + 1)));
}
}
// sonuç
return réponses;
}
}
- 9. satır: [Metier] sınıfına AA [@EBean] anotasyonu kullanılarak, bu sınıftaki referansların [Présentation] katmanına enjekte edilebilmesi sağlanır. (scope = EBean.Scope.Singleton) özniteliği, [Metier] sınıfının yalnızca tek bir örneği oluşturulmasını sağlar. Dolayısıyla, [Présentation] katmanına birkaç kez enjekte edilse bile her seferinde aynı referans enjekte edilir;
- kodun geri kalanı standarttır;
[Metier] sınıfı tarafından kullanılan [AleaException] türü şöyledir:
package exemples.android.metier;
public class AleaException extends RuntimeException {
private static final long serialVersionUID = 1L;
public AleaException() {
}
public AleaException(String detailMessage) {
super(detailMessage);
}
public AleaException(Throwable throwable) {
super(throwable);
}
public AleaException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
}
- 3. satır: [AleaException] sınıfı, [RuntimeException] sistem sınıfını genişletir; bu da onu denetlenmeyen bir istisna haline getirir: bunu bir try/catch bloğunda yönetmek veya yöntem imzalarına eklemek zorunlu değildir;
1.15.6. [MainActivity] etkinliği yeniden ele alındı
![]() |
Görünüm
Etkinlik
Katman
[metier]
Kullanıcı
Etkinlik, [métier] katmanının [IMetier] arayüzünü uygulayacaktır. Böylece, parça/görünümün tek iletişim ortağı bu etkinlik olacaktır.
[MainActivity] etkinliği, halihazırda [IMainActivity] arayüzünü uygulamaktadır. Bu etkinliğin [IMetier] arayüzünü de uygulamasını sağlamak için şunları yapabilirsiniz:
- etkinliğin uyguladığı arayüzlere [IMetier] arayüzünü ekleyebiliriz;
- [IMainActivity] arayüzünün, [IMetier] arayüzünü kendisi genişletmesini sağlamak. Biz bu yolu izliyoruz;
[IMainActivity] arayüzü şu şekilde olur:
![]() |
package exemples.android.architecture;
import exemples.android.metier.IMetier;
public interface IMainActivity extends IMetier {
// oturuma erişim
Session getSession();
// görünüm değişikliği
void navigateToView(int position);
// hata ayıklama modu
public static final boolean IS_DEBUG_ENABLED = true;
}
- 5. satır: [IMainActivity] arayüzü, [IMetier] arayüzünü genişletir
[MainActivity] sınıfı şu şekilde değişir:
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity implements IMainActivity {
...
// oturum enjeksiyonu
@Bean(Session.class)
protected Session session;
// iş mantığı enjeksiyonu
@Bean(Metier.class)
protected IMetier metier;
...
// uygulama IMetier --------------------------------------------------------------------
@Override
public List<Object> getAleas(int a, int b, int n) {
return metier.getAleas(a, b, n);
}
- 11-12. satırlar: [métier] katmanı faaliyete eklenir. Bunun için, parametresi AA [@EBean] anotasyonunu taşıyan sınıf olan AA [@Bean] anotasyonu kullanılır;
- 2. satır: Aktivite, [IMainActivity] arayüzünü ve dolayısıyla [métier] katmanındaki [IMetier] arayüzünü uygular;
- satır 16-19: [IMetier] arayüzünün tek yönteminin uygulanması. Çağrıyı [métier] katmanına devretmekle yetiniyoruz;
1.15.7. [Vue1Fragment] parçası yeniden ele alınmış hali
![]() |
[Vue1Fragment] sınıfının kodu şu şekilde değişir:
package exemples.android.fragments;
import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
import java.util.ArrayList;
import java.util.List;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.lst_reponses)
protected ListView listReponses;
@ViewById(R.id.edt_nbaleas)
protected EditText edtNbAleas;
@ViewById(R.id.edt_a)
protected EditText edtA;
@ViewById(R.id.edt_b)
protected EditText edtB;
@ViewById(R.id.txt_errorNbAleas)
protected TextView txtErrorAleas;
@ViewById(R.id.txt_errorIntervalle)
protected TextView txtErrorIntervalle;
// bir komuta verilen yanıtların listesi
private List<String> reponses = new ArrayList<>();
// listview adaptörü
private ArrayAdapter<String> adapterReponses;
// girişler
private int nbAleas;
private int a;
private int b;
@AfterViews
protected void afterViews() {
...
}
@Click(R.id.btn_Executer)
void doExecuter() {
...
}
// girdilen verilerin geçerliliği kontrol edilir
private boolean isPageValid() {
...
}
@Override
protected void updateFragment() {
// günlük
if (isDebugEnabled) {
Log.d("Vue1Fragment", String.format("updateFragment %s", getParentInfos()));
}
// uygulamanın başlatılması sırasında yalnızca bir kez çalıştırılacaktır
// ListView adaptörü oluşturulur - bunun için [activity] değişkeninin başlatılmış olması gerekir
adapterReponses=new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, reponses);
listReponses.setAdapter(adapterReponses);
}
}
- 69-70. satırlar: [ListView] türündeki bileşenin adaptörü belirlenir;
[ListView] bileşeni, bir öğe listesini görüntülemek için kullanılır. Bunu, [ListView]'e veri sağlaması gereken veri kaynağına bağlı olan [ListAdapter] türündeki bir adaptör aracılığıyla gerçekleştirir. Bir [ListView] adaptörünü tanımlamak için aşağıdaki [ListView.setAdapter] yöntemi kullanılabilir:
public void setAdapter (ListAdapter adapter)
[ListAdapter] bir arayüzdür. [ArrayAdapter] sınıfı, bu arayüzü uygulayan bir sınıftır. Yukarıdaki 69. satırda kullanılan oluşturucu şöyledir:
- [context], [ListView]'i görüntüleyen aktivitedir;
- [resource], [ListView]'teki bir öğeyi görüntülemek için kullanılan görünümü tanımlayan tamsayıdır. Bu görünüm herhangi bir karmaşıklıkta olabilir. Geliştirici, ihtiyaçlarına göre bu görünümü oluşturur;
- [textViewResourceId], [resource] görünümündeki bir [TextView] bileşenini tanımlayan tamsayıdır. Görüntülenen dize bu bileşen tarafından görüntülenecektir;
- [objects]: [ListView] tarafından görüntülenen nesnelerin listesi. Nesnelerin [toString] yöntemi, [textViewResourceId] ile tanımlanan nesneyi, [TextView] görünümünde, [resource] ile tanımlanan görünümde görüntülemek için kullanılır.
Geliştiricinin görevi, [ListView]'teki her bir öğeyi görüntüleyecek olan [resource] görünümünü oluşturmaktır. Burada olduğu gibi sadece basit bir karakter dizisini görüntülemek istediğimiz basit bir durumda, Android, [android.R.layout.simple_list_item_1] ile tanımlanan görünümü sağlar. Bu görünüm, [android.R.id.text1] ile tanımlanan [TextView] bileşenini içerir. 69. satırda [ListView] adaptörünü oluşturmak için kullanılan yöntem budur. Bu adaptörün yalnızca bir kez tanımlanması yeterlidir. Yeniden kullanılabilmesi için, sınıfın örnek değişkeni olarak tanımlanmıştır (39. satır). 69. satıra tekrar bakalım:
adapterReponses=new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, reponses);
[ArrayAdapter] yapıcı işlevinin ilk parametresi, [getActivity] tarafından bir parçadan elde edilen ve burada üst sınıfın [activity] değişkeninde saklanan aktivitedir. Bu alan her zaman bir değere sahip değildir. Dolayısıyla günlük kayıtları, [@AfterViews] yöntemine gelindiğinde bu alanın henüz başlatılmadığını ve bu nedenle 69-70. satırların bu yönteme eklenemeyeceğini göstermektedir. [updateFragment] yönteminde ise bu mümkündür, çünkü bu yöntemin çalıştırılması durumunda [activity!=null]'in mutlaka mevcut olduğu bilinmektedir. Adaptör burada, 37. satırda tanımlanan [reponses] veri kaynağına ilişkilendirilmiştir;
[doExecuter] yöntemi, [Exécuter] düğmesine yapılan tıklamayı işler. Kod şu şekildedir:
@Click(R.id.btn_Executer)
void doExecuter() {
// önceki hata mesajları varsa bunlar gizlenir
txtErrorAleas.setVisibility(View.INVISIBLE);
txtErrorIntervalle.setVisibility(View.INVISIBLE);
// önceki yanıtlar siliniyor
reponses.clear();
adapterReponses.notifyDataSetChanged();
// girişlerin geçerliliği kontrol edilir
if (!isPageValid()) {
return;
}
// aktiviteden rastgele sayılar isteniyor
List<Object> data = mainActivity.getAleas(a, b, nbAleas);
// bu verilerden bir String listesi oluşturulur
for (Object o : data) {
if (o instanceof Exception) {
reponses.add(((Exception) o).getMessage());
} else {
reponses.add(o.toString());
}
}
// liste görünümünü yenilenir
adapterReponses.notifyDataSetChanged();
}
- 7-8. satırlar: ListView'i boşaltmak istiyoruz. Bunun için [reponses] veri kaynağını boşaltıyoruz ve ListView ile ilişkilendirilmiş adaptörden yenilenmesini istiyoruz;
- satır 10-12: İstenen eylemi yürütmeden önce, girilen değerlerin doğru olup olmadığı kontrol edilir;
- 14. satır: Faaliyetten rastgele sayı listesi istenir. Her bir nesnenin türü [Integer] veya [AleaException] olan bir nesne listesi elde edilir;
- satır 16-22: elde edilen nesne listesinden, ListView'in görüntülediği [reponses] veri kaynağını güncelleriz;
- 24. satır: ListView adaptöründen yenilenmesi istenir;
1.15.8. Çalıştırma
Projeyi çalıştırın ve düzgün çalıştığını kontrol edin.
1.16. Örnek-15: İstemci/sunucu mimarisi
Android uygulamaları için yaygın bir mimariyi ele alacağız; bu mimaride Android uygulaması uzak web hizmetleriyle iletişim kurar. Şimdi şu mimariye sahip olacağız:
![]() |
Android uygulamasına, uzak sunucuyla iletişim kurmak için bir [DAO] katmanı eklendi. Bu katman, Android tablet tarafından görüntülenen rastgele sayıları üreten sunucuyla iletişim kuracak. Bu sunucu, aşağıdaki gibi iki katmanlı bir mimariye sahip olacak:
![]() |
İstemciler, [web / jSON] katmanındaki belirli URL'leri sorgular ve jSON (JavaScript Nesne Notasyonu) biçiminde bir metin yanıtı alırlar. Burada web hizmetimiz, [/a/b] türünde tek bir URL'i işleyecek ve [a,b] aralığında rastgele bir sayı döndürecektir. Uygulamayı şu sırayla açıklayacağız:
Sunucu
- [métier] katmanı;
- Spring ile uygulanan [web / jSON] hizmeti;
İstemci
- [DAO] katmanı. [métier] katmanı olmayacaktır;
1.16.1. Sunucu [web / jSON]
Aşağıdaki mimariyi oluşturmak istiyoruz:
![]() |
1.16.1.1. Projenin oluşturulması
Web hizmetini Spring ekosistemi [http://spring.io/] ile oluşturacağız. [http://start.spring.io/] (Haziran 2016) sitesine gidiyoruz; bu site, Android projesi olmayan ve Android Studio'nun oluşturulması için herhangi bir yardım sunmadığı projemiz için gerekli bağımlılıkları içeren bir Gradle projesi oluşturmamızı sağlayacak:
![]() |
- [1]'te: bir Gradle projesi seçin;
- [2-3]'te: proje tarafından oluşturulan jar bağımlılığının özellikleri (aşağıya bakın);
- [4]'te: web hizmetimiz için gerekli ikili dosyaların mevcut olması için [5] web bağımlılığını seçin;
- [6]'te: projeyi oluşturun. Ardından bir Gradle iskelet projesinin zip dosyası oluşturulur ve indirilmeye sunulur;
[2-3]'e ne yazmalı? Daha önce Gradle bağımlılıklarını kullanmıştık. Örneğin, önceki projeninki şöyleydi:
![]() |
buildscript {
repositories {
mavenCentral()
}
dependencies {
// Android'in Gradle eklentisi 0.11 sürümünden itibaren android-apt >= 1.3 sürümünü kullanmanız gerekir
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
android {
compileSdkVersion 23
...
}
def AAVersion = '4.0.0'
dependencies {
apt "org.androidannotations:androidannotations:$AAVersion"
compile "org.androidannotations:androidannotations-api:$AAVersion"
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
}
- 22. satır: Bir bağımlılık, [groupId:artifactId:version] biçiminde gösterilir. [http://start.spring.io/] sitesindeki formda istenen:
- [2]'te istenen, [groupId]'tir;
- [3]'te ise [artifactId];
Aşağıdaki zip dosyasını diğer projelerin bulunduğu klasöre açın:
![]() | ![]() ![]() | ![]() |
Android Studio ile [server-01] [1-2] adlı Gradle projesini açın. Açılan proje, [3]'tir (Project perspektifi).
1.16.1.2. Gradle Yapılandırması
![]() |
Oluşturulan Gradle dosyası (Haziran 2016) şöyledir:
buildscript {
ext {
springBootVersion = '1.3.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'spring-boot'
jar {
baseName = 'server-01'
version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
eclipse {
classpath {
containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
}
}
- 14. satır ile 34-38. satırlar IDE Eclipse içindir. Bunları siliyoruz;
- 1-11 ve 15. satırlar, Gradle projemize [spring-boot] adlı bir eklenti eklemek içindir. Spring Boot, Spring ekosistemine ait bir projedir. Bu eklenti, Spring ile en sık kullanılan bağımlılıkların sürümlerini tanımlar. Bu sayede, sürümlerini belirtmemize gerek kalmaz (30. ve 31. satırlar). Sürüm, kullanılan Spring Boot sürümü tarafından tanımlanan sürüm olur (3. satır);
- 22-23. satırlar: kullanılacak Java sürümü, burada 1.8 sürümü;
- 25-27. satırlar: bağımlılıkları indirmek için kullanılacak ikili dosya depoları;
- 26. satır: Merkezi Maven deposunu belirtir. Bu, şu anda mevcut en büyük açık kaynaklı ikili dosya deposudur;
- 29-32. satırlar: proje için gerekli bağımlılıklar:
- satır 30: bu bağımlılık, bir Spring web servisini oluşturmak için gerekli tüm ikili dosyaları beraberinde getirir;
- satır 31: Bu bağımlılık, testler için gerekli tüm ikili dosyaları, özellikle de JUnit testleri için gerekli olanları beraberinde getirir;
- bir [compile] bağımlılığı, projenin derlenmesi için bu bağımlılığa ihtiyaç duyulduğunu gösterir. Bir [testCompile] bağımlılığı ise, bu bağımlılığın yalnızca testlerin yürütülmesi için gerekli olduğunu gösterir. Bu durumda, bu bağımlılık projenin ikili dosyasına dahil edilmez;
Gradle dosyasını ilk olarak temizliyoruz:
// Spring Boot
buildscript {
ext {
springBootVersion = '1.3.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
// eklentiler
apply plugin: 'java'
apply plugin: 'spring-boot'
// proje ikili dosyası
jar {
baseName = 'server-01'
version = '0.0.1-SNAPSHOT'
}
// Java sürümleri
sourceCompatibility = 1.8
targetCompatibility = 1.8
// Maven depoları
repositories {
mavenLocal()
mavenCentral()
}
// bağımlılıklar
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
- 30. satır: Geliştirme ortamının yerel Maven deposunu ekledik. Bu depo, Maven kurulduğunda oluşturulur (bkz. paragraf 6.10). İstenen bağımlılık zaten yerel Maven deposunda mevcutsa, merkezi Maven deposundan talep edilmeyecektir;
- 19-22. satırlar: projenin ikili dosyasını oluşturmaya yarayan bir Gradle görevi. Ne yapıldığını görmek için bunu kullanacağız;
![]() | ![]() | ![]() |
- [1-4]'te, [build.gradle] dosyasında tanımlanan [jar] görevini çalıştırın dosyasında tanımlanan [jar] görevini çalıştırın ([1], IDE dosyasının sağ üst köşesinde ve yanında yer alır);
Önceki işlem, projenin jar arşivini oluşturur ve bunu [build / libs] [5] klasörüne yerleştirir:
![]() |
Arşivin adı, doğrudan [build.gradle] dosyasındaki [jar] görevine verilen bilgilerden (19-22. satırlar) alınır.
Projenin tüm bağımlılıkları şu şekilde görüntülenebilir:
![]() |
[1] dosyasında görüldüğü gibi, [compile('org.springframework.boot:spring-boot-starter-web')] projesinin tek bağımlılığı, beraberinde onlarca ikili dosyayı getirmiştir. Web için Spring Boot, bir Spring web uygulaması olan MVC'in muhtemelen ihtiyaç duyacağı bağımlılıkları dahil etmiştir. Bu, bazılarının gereksiz olabileceği anlamına gelir. Spring Boot, bir öğretici için idealdir:
- muhtemelen ihtiyaç duyacağımız bağımlılıkları beraberinde getirir;
- [1] adlı yerleşik bir Tomcat sunucusu içerir; bu sayede uygulamayı harici bir web sunucusuna dağıtmamıza gerek kalmaz;
Spring ekosisteminin web sitesinde Spring Boot'u kullanan çok sayıda örnek bulabilirsiniz [http://spring.io/guides].
Şimdi [build.gradle] dosyasını şu şekilde tamamlıyoruz:
// Spring Boot
...
// bağımlılıklar
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
// yerel Maven deposunda Maven standartlarına uygun bir ikili dosya oluşturmak için eklenti
apply plugin: 'maven-publish'
publishing {
publications {
maven(MavenPublication) {
groupId 'istia.st.exemples.android'
artifactId 'server-01'
version '0.0.1-SNAPSHOT'
from components.java
}
}
repositories {
maven {
// kendi deponuzu işaret edecek şekilde değiştirin: e.g. http://my.org/repo
url 'file://D:\\maven'
}
}
}
- 10. satır: Projenin ikili dosyasını Maven standartlarına uygun olarak bir Maven deposuna yayınlamayı sağlayan [maven-publish] adlı bir Gradle eklentisi içe aktarılır;
- 11. satır: [publishing] adlı bir Gradle görevi;
- 14-15. satırlar: Oluşturulacak Maven ikili dosyasının özellikleri;
- 23. satır: Yayınlanacağı Maven deposu; burada yerel bir Maven deposu kullanılıyor;
[maven-publish] eklentisinin eklenmesi, Gradle projesinde yeni görevler oluşturdu:
![]() | ![]() |
[2]'te [publish] görevini çalıştırırsak, projenin ikili dosyası oluşturulur ve [build.gradle] dosyasının 23. satırında belirtilen klasöre kurulur:
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() |
[jar] görevi, projenin ikili dosyasını oluşturmaya yarar. Bu ikili dosya, bağımlılıkları içermediğinden çalıştırılamaz. Tüm bağımlılıklarını içeren ve çalıştırılabilir bir ikili dosya oluşturmak mümkündür. Bunun için, [build.gradle] dosyasına aşağıdaki kodu ekliyoruz:
// tüm bağımlılıklarıyla birlikte bir ikili dosya oluşturun
version = '1.0'
task fatJar(type: Jar) {
manifest {
attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
attributes 'Main-Class': 'istia.st.exemples.android.Server01Application'
}
baseName = project.name + '-all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
- 6. satır: projenin çalıştırılabilir sınıfının tam adını girmelisiniz:
![]() |
Bu sınıfın kodu şu şekilde olacaktır:
package istia.st.exemples.android;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Server01Application {
public static void main(String[] args) {
System.out.println("Server01Application running");
//SpringApplication.run(Server01Application.class, args);
}
}
Gradle projesini yenileyin ve ardından [fatJar] görevini çalıştırın:
![]() | ![]() |
İkili dosya, [build / libs] klasöründe oluşturulur ve [1-7] komutuyla çalıştırılabilir:
![]() | ![]() |
1.16.1.3. Proje yapılandırması
Gradle yapılandırması yeterli değildir. Projeyi de yapılandırmamız gerekir. Bu, IDE tarafından oluşturulan bir Android projesi olmadığı için, şimdiye kadar yapmadığımız bu yapılandırmayı burada gerçekleştirmemiz gerekir.
![]() | ![]() |
- [3-4]'te: JDK 1.8 sürümünü kullanın;
Projeyi derlemek için Android projelerinde bulunan düğme artık mevcut değil. [1-2] menüsündeki bir seçeneği kullanacağız:
![]() | ![]() |
Devamında, okuyucudan aşağıdaki projeyi oluşturması istenir. [3] projesinin nihai kodunu açıklayacağız.
1.16.1.4. [métier] katmanı
![]() |
![]() |
[métier] katmanı, önceki örnekteki [métier] katmanının yapısını takip eder. Aşağıdaki [IMetier] arayüzüne sahip olacaktır:
package exemples.android.server.metier;
public interface IMetier {
// [a,b] içinde rastgele sayı
int getAlea(int a, int b);
}
- 5. satır: [a,b]'te 1 rastgele sayı üreten yöntem
Bu arayüzü uygulayan [Metier] sınıfının kodu şöyledir:
package exemples.android.server.metier;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.Random;
@Service
public class Metier implements IMetier {
@Override
public int getAlea(int a, int b) {
// bazı doğrulamalar
if (a < 0) {
throw new AleaException("Le nombre a de l'intervalle [a,b] doit être supérieur à 0", 2);
}
if (b < 0) {
throw new AleaException("Le nombre b de l'intervalle [a,b] doit être supérieur à 0", 3);
}
if (a >= b) {
throw new AleaException("Dans l'intervalle [a,b], on doit avoir a< b", 4);
}
// sonuç üretimi
Random random=new Random();
random.setSeed(new Date().getTime());
return a + random.nextInt(b - a + 1);
}
}
Sınıf hakkında yorum yapmayacağız: önceki örnekte gördüğümüz sınıfa benzerdir, ancak rastgele istisnalar oluşturmaz. Sadece 8. satırda, Spring'in sınıfı tek bir örnek (singleton) olarak oluşturmasını ve referansını diğer Spring bileşenleri için kullanılabilir hale getirmesini sağlayacak Spring anotasyonu [@Service]'e dikkat çekelim. Aynı etkiyi elde etmek için burada başka Spring anotasyonları da kullanılabilirdi. Spring bileşenlerinin varsayılan adları vardır ve bu adlar, kullanılan anotasyonun özniteliği olarak belirtilebilir. Bu öznitelik olmadan, buradaki gibi, Spring bileşeni sınıfın adını alır ve adın ilk harfi küçük harfle yazılır. Dolayısıyla burada, Spring bileşeni varsayılan olarak [metier] adını taşır;
[Metier] sınıfı, [AleaException] türünde istisnalar oluşturur:
package exemples.android.server.metier;
public class AleaException extends RuntimeException {
// hata kodu
private int code;
// yapıcılar
public AleaException() {
}
public AleaException(String detailMessage, int code) {
super(detailMessage);
this.code = code;
}
public AleaException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
public AleaException(String detailMessage, Throwable throwable, int code) {
super(detailMessage, throwable);
this.code = code;
}
// alıcı ve ayarlayıcılar
....
}
- 3. satır: [AleaException], [RuntimeException] sınıfını genişletir. Dolayısıyla bu, kontrol edilmeyen bir istisnadır (try / catch ile yönetilmesi zorunlu değildir);
- 6. satır: [RuntimeException] sınıfına bir hata kodu eklenir;
1.16.1.5. Web hizmeti / jSON
![]() |
![]() |
/ jSON web hizmeti, Spring MVC tarafından uygulanmaktadır. Spring MVC, MVC olarak adlandırılan mimari modelini (Model – Görünüm – Denetleyici) şu şekilde uygular:
![]() |
Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşir:
- istek - istenen URL'ler http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... biçimindedir. [Dispatcher Servlet], gelen URL'leri işleyen Spring sınıfıdır. Bu sınıf, URL'i onu işlemesi gereken eyleme "yönlendirir". Bu eylemler, [Contrôleurs] olarak adlandırılan belirli sınıfların yöntemleridir. Burada MVC'in C kısmı, [Dispatcher Servlet, Contrôleur, Action] dizesidir. Gelen URL'i işlemek için herhangi bir eylem yapılandırılmamışsa, [Dispatcher Servlet] servleti, istenen URL'in bulunamadığını bildirir (404 hatası NOT FOUND);
- işleme
- seçilen eylem, [Dispatcher Servlet] servletinin kendisine ilettiği parami parametrelerini kullanabilir. Bu parametreler çeşitli kaynaklardan gelebilir:
- [/param1/param2/...] yolundaki URL'ten,
- [p1=v1&p2=v2] parametreleri, URL,
- tarayıcı tarafından isteği ile birlikte gönderilen parametreler;
- kullanıcının isteğinin işlenmesi sırasında, eylem [metier] [2b] katmanına ihtiyaç duyabilir. Müşterinin isteği işlendikten sonra, bu eylem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
- istek düzgün bir şekilde işlenemediğinde bir hata sayfası
- aksi takdirde bir onay sayfası
- eylem, belirli bir görünümün ([3]) görüntülenmesini ister. Bu görünüm, görünüm modeli olarak adlandırılan verileri gösterecektir. Bu, MVC'teki M'dir. Eylem, bu M şablonunu ([2c]) oluşturacak ve bir V görünümünün ([3]) görüntülenmesini isteyecektir;
- yanıt - seçilen V görünümü, eylemin oluşturduğu M şablonunu kullanarak, müşteriye göndermesi gereken HTML yanıtının dinamik kısımlarını başlatır ve ardından bu yanıtı gönderir.
Bir web hizmeti / jSON için, önceki mimari biraz değiştirilmiştir:
![]() |
- [4a]'te, bir Java sınıfı olan şablon, bir kütüphane tarafından jSON dizesine dönüştürülür;
- [4b]'te, bu jSON dizesi tarayıcıya gönderilir;
Bir Java nesnesinin jSON dizesine serileştirilmesi ve bir jSON dizesinin Java nesnesine deserileştirilmesi örneği, eklerin 6.14. paragrafında sunulmuştur.
Uygulamamızın [web] katmanına geri dönelim:
![]() |
Uygulamamızda tek bir denetleyici bulunmaktadır:
![]() |
/ jSON web hizmeti, müşterilerine aşağıdaki gibi bir [Response] türünde yanıt gönderecektir:
package exemples.android.server.web;
import java.util.List;
public class Response<T> {
// ----------------- özellikler
// işlem durumu
private int status;
// olası hata mesajları
private List<String> messages;
// yanıt gövdesi
private T body;
// oluşturucular
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// getter ve setter'lar
...
}
- 13. satır: [T body] alanı, istemcinin beklediği yanıttır. Burada, beklenen rastgele sayının Integer türü yerine, T türünde genel bir yanıt kullanmaya karar verdik. Bu sınıfı başka durumlarda da yeniden kullanabilmek istiyoruz. Müşterinin talebini işlerken sunucu bir sorunla karşılaşabilir; bu durum diğer iki alanda özetlenir;
- 8. satır: bir durum kodu (hata yoksa 0);
- 9. satır: status!=0 ise, bir hata mesajları listesi (genellikle istisna yığınındaki mesajlar, eğer istisna oluştuysa); hata yoksa null;
[WebController] denetleyicisi şu şekildedir:
package exemples.android.server.web;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import exemples.android.server.metier.AleaException;
import exemples.android.server.metier.IMetier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.List;
@Controller
public class WebController {
// iş mantığı katmanı
@Autowired
private IMetier metier;
// eşleştirici JSON
@Autowired
private ObjectMapper mapper;
// rastgele sayılar
@RequestMapping(value = "/{a}/{b}", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAlea(@PathVariable("a") int a, @PathVariable("b") int b) throws JsonProcessingException {
// yanıt
Response<Integer> response = new Response<>();
// iş katmanı kullanılır
try {
response.setBody(metier.getAlea(a, b));
response.setStatus(0);
} catch (AleaException e) {
response.setStatus(e.getCode());
response.setMessages(getMessagesFromException(e));
}
// yanıt veriliyor
return mapper.writeValueAsString(response);
}
private List<String> getMessagesFromException(Throwable e) {
// mesaj listesi
List<String> messages = new ArrayList<String>();
// istisna yığını taranıyor
Throwable th = e;
while (th != null) {
messages.add(e.getMessage());
th = th.getCause();
}
// sonuç şu şekilde elde edilir
return messages;
}
}
- 17. satır: [@Controller] anotasyonu, sınıfın, web uygulamasının belirli URL'leri için istekleri işleyen yöntemlere sahip bir MVC denetleyicisi olduğunu belirtir;
- 21-22. satırlar: [@Autowired] anotasyonu, Spring'den alana [IMetier] türünde bir bileşen enjekte etmesini ister. Bu, önceki [Metier] sınıfı olacaktır. Bu sınıfa [@Service] anotasyonu eklediğimiz için, bir Spring bileşeni olarak yönetilmektedir;
- 24-25. satırlar: Aynı işlemi, daha sonra tanımlayacağımız jSON eşleştiricisiyle de yapıyoruz. Web hizmetimiz yanıtını jSON biçiminde bir dize olarak gönderecek. Yanıtın jSON biçimine serileştirilmesini bu eşleştirici gerçekleştirecektir;
- 30. satır: Rastgele sayı üreten yöntem. Adı önemli değildir. Yürütüldüğünde, parametreleri Spring tarafından MVC olarak başlatılmıştır. Bunun nasıl yapıldığını göreceğiz. Ayrıca, bu yöntemin çalıştırılmasının nedeni, web sunucusunun 28. satırdaki URL için bir istek HTTP GET almış olmasıdır;
- 28. satır: [@RequestMapping] açıklaması, açıklanan yöntemin belirli özelliklerini tanımlar:
- [value]: Yöntem tarafından kabul edilen URL;
- [method]: Yöntem tarafından kabul edilen HTTP yöntemi. Bunların başlıca ikisi, GET ve POST'tir. [POST] yöntemi, istemcinin HTTP isteğine bir belge eklemek istediğinde kullanılır;
- [produces]: müşteriye gönderilecek HTTP yanıtının başlıklarından birini belirler. Burada, müşteriye gönderilen yanıtla birlikte gönderilen HTTP başlıklarından biri, yanıtın jSON dizesi biçiminde gönderildiğini müşteriye bildirecektir. Bu başlık zorunlu değildir. Müşteri, çeşitli biçimlerde olabilecek yanıtlar bekliyorsa, bu başlık bilgilendirme amacıyla verilir;
- [consumes]: burada bulunmamaktadır. Bu başlık, müşterinin HTTP isteğinin kabul edilebilmesi için bu isteğe eşlik etmesi gereken HTTP başlıklarını belirtmeye yarar;
- 29. satır: [@ResponseBody] açıklaması, yöntemin ürettiği sonucun müşteriye gönderilmesi gerektiğini belirtir. Bu açıklama olmadan, yöntemin yanıtı, müşteriye gönderilecek HTML sayfasını seçmeye yarayan bir anahtar olarak kabul edilir. Bir web hizmetinde / jSON, HTML sayfaları yoktur;
- 28. satır: İşlenen URL, {x}'in bir değişkeni temsil ettiği /{a}/{b} biçimindedir. {a} ve {b} değişkenleri, 30. satırdaki yöntem parametrelerine atanır. Bu, @PathVariable("x") anotasyonu aracılığıyla yapılır. {a} ve {b}'nin bir URL'in bileşenleri olduğu ve dolayısıyla String türünde oldukları unutulmamalıdır. String'in parametre türüne dönüştürülmesi başarısız olabilir. Bu durumda Spring MVC bir istisna oluşturur. Özetleyelim: Bir tarayıcıyla URL /100/200'ü talep edersem, 30. satırdaki getAlea yöntemi a=100, b=200 tamsayı parametreleriyle çalışacaktır;
- 36. satır: [métier] katmanından [a,b] aralığında bir rastgele sayı istenir. [metier].getAlea yönteminin bir istisna oluşturabileceğini hatırlayalım;
- 37. satır: hata yok;
- 39. satır: hata kodu;
- satır 40: yanıt mesajları listesi, istisna yığınınınkiyle aynıdır (satır 46-57). Burada, yığının yalnızca bir istisna içerdiğini biliyoruz, ancak daha genel bir yöntem göstermek istedik;
- satır 43: [Response<Integer>] türündeki yanıt, jSON biçiminde bir dize olarak döndürülür;
1.16.1.6. Spring Projesinin Yapılandırılması
![]() |
Spring'i yapılandırmanın çeşitli yolları vardır:
- XML dosyaları kullanılarak;
- Java kodu kullanarak;
- ikisinin bir karışımıyla;
Web uygulamamızı Java kodu kullanarak yapılandırmayı tercih ediyoruz. Bu yapılandırmayı sağlayan sınıf şudur:
package exemples.android.server.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@ComponentScan(basePackages = { "exemples.android.server.metier", "exemples.android.server.web" })
@EnableWebMvc
public class Config {
// web yapılandırması ------------------------------------
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet();
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8080);
}
// jSON eşleştiricisi
@Bean
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
- 12. satır: Spring'e, yönetmesi gereken iki bileşeni hangi paketlerde bulacağını bildiriyoruz:
- [exemples.android.server.metier] paketinde [@Service] anotasyonuna sahip [Metier] bileşeni;
- [exemples.android.server.web] paketinde [@Controller] olarak etiketlenmiş [WebController] bileşeni;
- 13. satır: [@EnableWebMvc] anotasyonu, Spring Boot'un bir Spring uygulaması olan MVC için bir dizi standart yapılandırmayı kendi başına yapmasına olanak tanır. Bu da geliştiricinin yükünü hafifletir;
- 16., 22., 27. ve 33. satırlar: [@Bean] anotasyonu da daha önce karşılaştığımız iki anotasyon (@Service, @Controller) ile aynı şekilde Spring bileşenlerini (bean'leri) tanımlar. Burada [@Bean] anotasyonu bir sınıfı değil bir yöntemi anotlar ve Spring bileşeni, bu yöntemin sonucudur. [@Bean] anotasyonunda adlandırma özniteliği bulunmadığından, oluşturulan Spring bileşeni anotlanan yöntemin adını alır;
- 16-20. satırlar: [dispatcherServlet] bean'ini tanımlar. Bu, Spring'in önceden tanımlanmış bir adı olan MVC'tir ve uygulamanın ön denetleyicisini (front controller) MVC'i tanımlar; bu nesne, istemcilerden gelen tüm istekleri alır ve bunları (adından da anlaşılacağı üzere) Spring MVC uygulamasının çeşitli [@Controller] bileşenlerine dağıtır;
- 18. satır: [dispatcherServlet] bean'i, Spring MVC tarafından sağlanan [DispatcherServlet] sınıfının bir örneğidir;
- 22-25. satırlar: [servletRegistrationBean] bean'i, uygulama tarafından hangi URL'lerin kabul edileceğini tanımlamak için kullanılır. 24. satırda, tüm URL'ler kabul edilir;
- 27-30. satırlar: [embeddedServletContainerFactory] bean'i, web uygulamasını barındıracak olan ve proje bağımlılıkları içinde yer alan gömülü sunucuyu tanımlamak için kullanılır. 29. satır, bunun bir Tomcat sunucusu olduğunu ve bu sunucunun 8080 numaralı bağlantı noktasında çalışacağını belirtir. Varsayılan olarak, bu web sunucusunun ikili dosyaları Gradle dosyasındaki [org.springframework.boot:spring-boot-starter-web] bağımlılığı tarafından sağlanır;
1.16.1.7. Web hizmetinin çalıştırılması / jSON
![]() |
Proje, aşağıdaki [Boot] yürütülebilir sınıfından çalıştırılır:
package exemples.android.server.boot;
import exemples.android.server.config.Config;
import org.springframework.boot.SpringApplication;
public class Boot {
public static void main(String[] args) {
// uygulama çalıştırma
SpringApplication.run(Config.class, args);
}
}
- [Boot] sınıfı bir yürütülebilir sınıftır (7-10. satırlar);
- 9. satır: [SpringApplication.run] statik yöntemi, uygulamayı başlatacak olan [spring Boot] sınıfının (4. satır) bir yöntemidir. İlk parametresi, projeyi yapılandıran Java sınıfıdır. Burada, az önce tanımladığımız [Config] sınıfı yer almaktadır. İkinci parametre, [main] yöntemine (7. satır) aktarılan argüman dizisidir;
Web uygulamasını çeşitli şekillerde başlatabiliriz; bunlardan biri şöyledir:
![]() |
Konsolda, bir dizi günlük kaydı görüntülenir:
- 12-14. satırlar: Yerleşik Tomcat sunucusu başlatılır;
- 15-19. satırlar: Spring [DispatcherServlet] servleti yüklenir ve yapılandırılır;
- 20. satır: Web sunucusunun URL [/{a}/{b}]'i algılanır;
Şimdi bir tarayıcı açıp /jSON web hizmetinin URL'sini test edelim:
![]() |
![]() |
![]() |
![]() |
Her seferinde, jSON türündeki bir nesnenin [Response<Integer>] temsilini elde ediyoruz.
Standart bir tarayıcı yerine, şimdi Chrome tarayıcısının [Advanced Rest Client] uzantısını ele alalım (bkz. ekler, paragraf 6.13):

- [1]'e, istenen URL;
- [2]'e, bir GET aracılığıyla;
- [3]'e, istek gönderilir;

- [4]'e, sunucunun yanıtındaki HTTP başlıkları gönderilir. Burada, gönderilen belgenin bir jSON dizesi olduğu belirtilmektedir;
- [5]'te, alınan jSON dizisi;
1.16.1.8. Projenin çalıştırılabilir jar dosyasının oluşturulması
1.16.1.2 numaralı paragrafta, tüm bağımlılıklarıyla birlikte uygulamanın çalıştırılabilir dosyasını oluşturmak için Gradle dosyasının nasıl yapılandırılacağını göstermiştik. Mevcut uygulamaya uyarlanmış haliyle bu yapılandırma şu şekilde olur:
// tüm bağımlılıklarıyla birlikte bir ikili dosya oluştur
version = '1.0'
task fatJar(type: Jar) {
manifest {
attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
attributes 'Main-Class': 'exemples.android.server.boot.Boot'
}
baseName = project.name + '-all'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
Bu yürütülebilir dosyayı oluşturmak için şu adımları uygulayabilirsiniz: [1-5]:
![]() | ![]() |
Çalıştırmak için, web hizmeti çalışıyorsa durdurulur ([1]), ardından arşiv dosyası çalıştırılır ([2-4]):
![]() | ![]() |
Bir tarayıcı açın ve URL ile [localhost:8080/100/200] adreslerini ziyaret edin. Daha önce elde ettiğiniz sonuçların aynısını almanız gerekir.
1.16.1.9. Günlük dosyalarının yönetimi
Yürütülebilir arşivi çalıştırdığımızda, projeyi IDE dosyasından çalıştırdığımızda elde ettiğimiz günlük dosyalarıyla aynı günlük dosyalarının oluşmadığını görüyoruz. [DEBUG] modunda şu günlük dosyaları elde ediyoruz:
...
09:32:03.741 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Searching for key 'spring.liveBeansView.mbeanDomain' in [servletConfigInitParams]
09:32:03.742 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Searching for key 'spring.liveBeansView.mbeanDomain' in [servletContextInitParams]
09:32:03.742 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Searching for key 'spring.liveBeansView.mbeanDomain' in [systemProperties]
09:32:03.742 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Searching for key 'spring.liveBeansView.mbeanDomain' in [systemEnvironment]
09:32:03.742 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source. Returning [null]
juin 07, 2016 9:32:03 AM org.apache.coyote.AbstractProtocol init
INFOS: Initializing ProtocolHandler ["http-nio-8080"]
juin 07, 2016 9:32:03 AM org.apache.coyote.AbstractProtocol start
INFOS: Starting ProtocolHandler ["http-nio-8080"]
juin 07, 2016 9:32:03 AM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
INFOS: Using a shared selector for servlet write/read
09:32:03.810 [main] INFO org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)
09:32:03.813 [main] INFO exemples.android.server.boot.Boot - Started Boot in 1.984 seconds (JVM running for 2.206)
Projenin [resources] klasörüne bir [logback.xml] dosyası ekleyerek günlük düzeyini yönetebiliriz:
![]() |
Bu dosyanın içeriği şu şekilde olabilir:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- kodlayıcılara varsayılan olarak tür atanır
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- günlük düzeyini kontrol et -->
<root level="info"> <!-- bilgi, hata ayıklama, uyarı -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Günlük seviyesi 12. satırda kontrol edilir. Şimdi, yürütülebilir arşivi yeniden oluşturup çalıştırırsak, yalnızca [info] seviyesinde günlükler elde ederiz:
...
09:36:52.433 [main] INFO o.h.validator.internal.util.Version - HV000001: Hibernate Validator 5.2.4.Final
09:36:52.762 [main] INFO o.s.w.s.m.m.a.RequestMappingHandlerAdapter - Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7085bdee: startup date [Tue Jun 07 09:36:51 CEST 2016]; root of context hierarchy
09:36:52.811 [main] INFO o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/{a}/{b}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String exemples.android.server.web.WebController.getAlea(int,int) throws com.fasterxml.jackson.core.JsonProcessingException
juin 07, 2016 9:36:52 AM org.apache.coyote.AbstractProtocol init
INFOS: Initializing ProtocolHandler ["http-nio-8080"]
juin 07, 2016 9:36:52 AM org.apache.coyote.AbstractProtocol start
INFOS: Starting ProtocolHandler ["http-nio-8080"]
juin 07, 2016 9:36:52 AM org.apache.tomcat.util.net.NioSelectorPool getSharedSelector
INFOS: Using a shared selector for servlet write/read
09:36:52.923 [main] INFO o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)
09:36:52.926 [main] INFO exemples.android.server.boot.Boot - Started Boot in 1.865 seconds (JVM running for 2.203)
1.16.2. Web sunucusunun Android istemcisi / jSON
Android istemcisi şu yapıya sahip olacaktır:
![]() |
İstemci iki bileşene sahip olacaktır:
- [Présentation] katmanı (görünüm + etkinlik), daha önce incelediğimiz [Exemple-14] örneğindeki gibi;
- daha önce incelediğimiz [web / jSON] hizmetine hitap eden [DAO] katmanı.
1.16.2.1. Projenin Oluşturulması
1.4. paragrafındaki prosedürü izleyerek önceki [Exemple-14] projesini [Exemple-15] olarak kopyalıyoruz. Sonuç olarak şunu elde ediyoruz:
![]() | ![]() |
Devamında, okuyucunun aşağıdaki projeyi oluşturması istenmektedir.
1.16.2.2. Gradle Yapılandırması
![]() |
[build.gradle] dosyası şu şekildedir:
buildscript {
repositories {
mavenCentral()
}
dependencies {
// Android'in Gradle eklentisi 0.11 sürümünden itibaren android-apt >= 1.3 sürümünü kullanmanız gerekir
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.android.application'
apply plugin: 'android-apt'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "exemples.android"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// APK dosyasını oluşturabilmek için gerekli paketleme seçenekleri
packagingOptions {
exclude 'META-INF/ASL2.0'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/notice.txt'
exclude 'META-INF/license.txt'
}
}
def AAVersion = '4.0.0'
dependencies {
apt "org.androidannotations:androidannotations:$AAVersion"
compile "org.androidannotations:androidannotations-api:$AAVersion"
apt "org.androidannotations:rest-spring:$AAVersion"
compile "org.androidannotations:rest-spring-api:$AAVersion"
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:design:23.4.0'
compile 'org.springframework.android:spring-android-rest-template:2.0.0.M3'
compile 'com.fasterxml.jackson.core:jackson-databind:2.7.4'
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
}
repositories {
maven {
url 'https://repo.spring.io/libs-milestone'
}
}
Daha önce ele alınmamış olan kısımları açıklayacağız:
- 46-47. satırlar: AA eklentisinin eklenmesi. [rest-spring-api] eklentisi, istemci/sunucu iletişimini AA kütüphanesine devretmeyi sağlar;
- 50. satır: [spring-android-rest-template] kütüphanesi, AA tarafından istemci/sunucu iletişimini sağlamak için kullanılan kütüphanedir. [2.0.0.M3] sürümü, olağan Maven depolarında bulunmayan bir “milestone” sürümüdür. Bu nedenle, 56-59. satırlarda, kütüphaneyi bulmak için kullanılacak depoyu (58. satır) belirtmek gerekir;
- 51. satır: bir jSON kütüphanesi;
- 33-39. satırlar: Bu özellik belirtilmezse, projenin APK ikili dosyası oluşturulurken hatalar ortaya çıkar;
1.16.2.3. Android uygulamasının manifest dosyası
![]() |
[AndroidManifest.xml] dosyasında değişiklik yapılması gerekiyor. Zira varsayılan olarak İnternet erişimi devre dışıdır. Özel bir yönergeyle bu erişimi etkinleştirmek gerekir:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="exemples.android">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.MainActivity_"
android:label="@string/app_name"
android:windowSoftInputMode="stateHidden"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
- 5. satır: İnternet erişimi izin verilir;
1.16.2.4. [DAO] katmanı
![]() |
![]() |
1.16.2.4.1. [DAO] katmanının [IDao] arayüzü
[DAO] katmanının arayüzü şu şekilde olacaktır:
package exemples.android.dao;
public interface IDao {
// rastgele sayı
int getAlea(int a, int b);
// web hizmetinin URL'i
void setUrlServiceWebJson(String url);
// sunucu yanıtı için maksimum bekleme süresi (ms)
void setTimeout(int timeout);
// istek göndermeden önce istemcinin bekleme süresi (milisaniye)
void setDelay(int delay);
}
- 6. satır: Bu web hizmetinin [a,b] aralığında rastgele bir sayı elde etmek için kullanılan web hizmeti yöntemi / jSON;
- 9. satır: /jSON web hizmetinin rastgele sayı üretme yöntemi olan URL;
- 12. satır: Sunucudan yanıt gelene kadar beklemek üzere maksimum bir bekleme süresi belirlenir;
- 15. satır: Kullanıcıya isteğini iptal etmesi için zaman tanımak amacıyla, sunucuya isteğin gönderilmesinden önce bir bekleme süresi belirlenir;
1.16.2.4.2. [WebClient] arayüzü
![]() |
[WebClient] arayüzü, web hizmeti ile iletişimi sağlar. Kod şöyledir:
package exemples.android.dao;
import org.androidannotations.rest.spring.annotations.Get;
import org.androidannotations.rest.spring.annotations.Path;
import org.androidannotations.rest.spring.annotations.Rest;
import org.androidannotations.rest.spring.api.RestClientRootUrl;
import org.androidannotations.rest.spring.api.RestClientSupport;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Rest(converters = {MappingJackson2HttpMessageConverter.class})
public interface WebClient extends RestClientRootUrl, RestClientSupport {
// [a,b] aralığında 1 rastgele sayı
@Get("/{a}/{b}")
Response<Integer> getAlea(@Path("a") int a, @Path("b") int b);
}
- 12. satır: [WebClient], AA kütüphanesinin, içine ekleyeceğimiz açıklamalar sayesinde kendi başına uygulayacağı bir arayüzdür. Bu arayüz, web hizmeti / jSON tarafından sunulan URL çağrılarını uygulamalıdır:
// rastgele sayı
@RequestMapping(value = "/{a}/{b}", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAlea(@PathVariable("a") int a, @PathVariable("b") int b) throws JsonProcessingException {
- 11. satır: [@Rest] anotasyonu, bir AA anotasyonudur. [converters] özniteliğinin değeri bir dönüştürücü dizisidir. Burada [MappingJackson2HttpMessageConverter.class] dönüştürücüsü, sunucu bir jSON dizesi gönderdiğinde bu dizenin otomatik olarak serileştirilmesini sağlar. Böylece (d) satırında görüldüğü gibi, URL [/{a}/{b}], aslında bir jSON dizesi olan String türünü döndürür (b satırı). Bu bilgiler ve 16. satırdaki beklenen tür bilgisi ile, istemcinin [WebClient] örneği, alacağı dizeyi bir [Response<Integer>] türüne deserialize edecektir;
- 15. satır: AA adlı bir açıklama, URL'in HTTP GET yöntemiyle çağrılması gerektiğini belirtir. [@Get] açıklaması parametresi, web hizmeti tarafından beklenen URL biçimini ifade eder. Sunucudaki [WebController] denetleyicisinde çağrılan yöntemin [@RequestMapping] açıklaması (b satırı) içindeki [value] parametresini almanız yeterlidir. {} küme parantezleri, 16. satırdaki yöntemin parametrelerine aktarılması gereken URL parametrelerini çevrelemektedir. [@Path("a") int a] sözdizimi, yöntemin [a] parametresinin URL'in {a} değerine atanmasını sağlar. URL parametresi ile yöntemin parametresi burada olduğu gibi aynı adı taşıyorsa, daha basit bir şekilde [@Path int a] yazılabilir;
HTTP POST sorgusu durumunda, çağrı yönteminin imzası şu şekilde olur:
@Post("/{a}/{b}")
Response<Integer> getAlea(@Body T body, @Path("a") int a, @Path("b") int b);
Gönderilen değeri belirten, [@Body] notudur. Bu değer otomatik olarak jSON olarak serileştirilecektir. Sunucu tarafında ise şu imza olacaktır:
// rastgele sayılar
@RequestMapping(value = "/{a}/{b}", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAlea(@PathVariable("a") int a, @PathVariable("b") int b, @RequestBody T body) {
- 2. satır: HTTP POST isteğinin beklendiği ve bu isteğin gövdesinin (gönderilen nesne) bir dize olarak iletilmesi gerektiği belirtilir jSON (consumes özniteliği) şeklinde iletilmesi gerektiği belirtilmektedir;
- 4. satır: Gönderilen değer, yöntemin [@RequestBody T body] parametresinden alınacaktır;
[WebClient] sınıfının koduna geri dönelim:
@Rest(converters = {MappingJackson2HttpMessageConverter.class})
public interface WebClient extends RestClientRootUrl, RestClientSupport {
- İletişim kurulacak web hizmetinin URL değerini belirtebilmemiz gerekir. Bu, AA tarafından sağlanan [RestClientRootUrl] arayüzünü genişleterek elde edilir. Bu arayüz, bağlanılacak web hizmetinin URL değerini belirlemeye olanak tanıyan bir [setRootUrl(urlServiceWeb] yöntemi sunar;
- ayrıca, yanıt bekleme süresini sınırlamak istediğimiz için web hizmeti çağrısını kontrol etmek istiyoruz. Bunun için, [setRestTemplate] yöntemini sunan [RestClientSupport] arayüzünü genişletiyoruz; bu yöntem bize şunları yapma imkânı verecektir:
- istemci-sunucu iletişimini yönetmek için kullanılan [RestTemplate] nesnesini kendimiz oluşturmak;
- bu nesneyi yapılandırarak yanıt için maksimum bekleme süresini belirlemek;
1.16.2.4.3. [Response] sınıfı
[IDao] arayüzünün [getAlea] yöntemi, aşağıdaki gibi bir [Response] türü yanıt döndürür:
package exemples.android.dao;
import java.util.List;
public class Response<T> {
// ----------------- özellikler
// işlem durumu
private int status;
// olası hata mesajları
private List<String> messages;
// yanıt gövdesi
private T body;
// yapıcılar
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// alıcı ve ayarlayıcılar
...
}
Bu, sunucu tarafında (1.16.1.5 paragrafı) daha önce kullanılan [Response] sınıfıdır. Aslında, programlama açısından bakıldığında, her şey sanki istemcinin [DAO] katmanı, web hizmetinin [WebController] denetleyicisiyle doğrudan iletişim kuruyormuş gibi gerçekleşir:
![]() |
İstemci ile sunucu arasındaki ağ iletişimi ve istemci tarafındaki Java nesnelerinin serileştirilmesi/deserileştirilmesi, programcı için şeffaftır.
1.16.2.4.4. [DAO] katmanının uygulaması
![]() |
[IDao] arayüzü, aşağıdaki [Dao] sınıfı ile uygulanır:
package exemples.android.dao;
import com.fasterxml.jackson.databind.ObjectMapper;
import exemples.android.architecture.Utils;
import org.androidannotations.annotations.EBean;
import org.androidannotations.rest.spring.annotations.RestService;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
@EBean
public class Dao implements IDao {
// hizmet istemcisi REST
@RestService
protected WebClient webClient;
// eşleştirici jSON
private ObjectMapper mapper = new ObjectMapper();
// isteğin yürütülmesinden önceki bekleme süresi
private int delay;
// IDao arayüzü -------------------------------------------------------------------
@Override
public int getAlea(int a, int b) {
...
}
@Override
public void setUrlServiceWebJson(String urlServiceWebJson) {
...
}
@Override
public void setTimeout(int timeout) {
...
}
@Override
public void setDelay(int delay) {
this.delay = delay;
}
}
- 15. satır: [Dao] sınıfını, başka bir yere enjekte edebileceğimiz bir AA bean'i haline getirmek için [@EBean] anotasyonuyla anotasyonluyoruz;
- 19-20. satırlar: Daha önce tanımladığımız [WebClient] arayüzünün gerçekleştirilmesini enjekte ediyoruz. Bu enjeksiyonu sağlayan, [@RestService] anotasyonudur;
- diğer yöntemler [IDao] arayüzünü uygular (satır 27-46);
[setTimeout] Yöntemi
[setTimeout] yöntemi şu şekildedir:
@Override
public void setTimeout(int timeout) {
// istemci istekleri için zaman aşımı ayarlanır REST
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(timeout);
factory.setConnectTimeout(timeout);
// restTemplate oluşturuluyor
RestTemplate restTemplate = new RestTemplate(factory);
// jSON dönüştürücüsü ayarlanıyor
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
// web istemcisinin restTemplate'i ayarlanıyor
webClient.setRestTemplate(restTemplate);
}
- [WebClient] arayüzü, [org.springframework.android:spring-android-rest-template] Gradle bağımlılığını kullanan AA sınıfı tarafından uygulanacaktır. [spring-android-rest-template], [RestTemplate] türündeki bir sınıf aracılığıyla istemci ile web sunucusu / jSON arasındaki iletişimi sağlar;
- 4. satır: [SimpleClientHttpRequestFactory] sınıfı, [spring-android-rest-template] bağımlılığı tarafından sağlanır. Bu sınıf, sunucudan gelecek yanıt için maksimum bekleme süresini belirlememizi sağlayacaktır (5-6. satırlar);
- satır 8: Web hizmeti ile iletişimin temelini oluşturacak [RestTemplate] türündeki nesneyi oluşturuyoruz. Az önce oluşturulan [factory] nesnesini bu nesneye parametre olarak aktarıyoruz;
- 10. satır: İstemci-sunucu diyaloğu çeşitli şekillerde gerçekleşebilir. Veri alışverişi metin satırları üzerinden yapılır ve [RestTemplate] türündeki nesneye bu metin satırıyla ne yapması gerektiğini belirtmemiz gerekir. Bunun için, metin satırlarını işleyebilen dönüştürücüler ve sınıflar sağlarız. Dönüştürücünün seçimi genellikle metin satırına eşlik eden HTTP başlıkları aracılığıyla yapılır. Burada, yalnızca jSON biçiminde metin satırları aldığımızı biliyoruz. Ayrıca, 1.16.1.7 numaralı paragrafta, sunucunun HTTP başlığını gönderdiğini görmüştük:
Content-Type: application/json;charset=UTF-8
10. satırda, [RestTemplate]'in tek dönüştürücüsü, [Jackson] kütüphanesi ile uygulanan bir jSON dönüştürücüsü olacaktır. Bu dönüştürücülerle ilgili tuhaf bir durum var: AA, [WebClient] web istemcisinin açıklamasında da bunun bulunmasını zorunlu kılıyor:
@Rest(converters = {MappingJacksonHttpMessageConverter.class})
public interface WebClient extends RestClientRootUrl, RestClientSupport {
1. satırda, programlama yoluyla zaten belirtmiş olsak bile bir dönüştürücü belirtmek zorundayız.
- 12. satır: Bu şekilde oluşturulan [RestTemplate] nesnesi, [WebClient] arayüzünün uygulamasına eklenir ve istemci/sunucu iletişimini yürütecek olan da bu nesnedir;
[getAlea] Yöntemi
[getAlea] yöntemi şu şekildedir:
@Override
public int getAlea(int a, int b) {
// hizmetin çalıştırılması
Response<Integer> info;
DaoException ex;
try {
// bekleme
waitSomeTime(delay);
// hizmet yürütülüyor
info = webClient.getAlea(a, b);
int status = info.getStatus();
if (status == 0) {
// sonuç döndürülüyor
return info.getBody();
} else {
// istisna kaydediliyor
ex = new DaoException(mapper.writeValueAsString(info.getMessages()), status);
}
} catch (JsonProcessingException | RuntimeException e) {
// istisna kaydediliyor
ex = new DaoException(e, 100);
}
// istisna atılıyor
throw ex;
}
...
// özel yöntemler -------------------
private void waitSomeTime(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 8. satır: [delay] milisaniye beklenir;
- 10. satır: [WebClient] arayüzünü uygulayan sınıftaki aynı imzaya sahip yöntemi çağırmakla yetinilir;
- 11. satır: Sunucudan alınan yanıtın [status] değerine bakılarak analiz edilir;
- satır 12-14: sunucu tarafında hata yoksa (status=0), yöntemin sonucunu döndürürüz;
- 17. satır: sunucu tarafında hata varsa (status!=0), bir istisna hazırlanır ancak atılmaz. Sunucu bir hata mesajları listesi iletmiştir. Sunucunun mesaj listesindeki jSON dizesini tek mesaj olarak içeren bir istisna oluşturulur;
- 19-22. satırlar: diğer istisna durumları;
- 24. satır: buraya gelindiğinde, mutlaka bir istisna oluşmuştur. Bu durumda istisna tetiklenir;
Bu kodda kullanılan [DaoException] istisnası şöyledir:
package exemples.android.dao;
import java.util.ArrayList;
import java.util.List;
public class DaoException extends RuntimeException {
// hata kodu
private int code;
// yapıcılar
public DaoException() {
}
public DaoException(String detailMessage, int code) {
super(detailMessage);
this.code = code;
}
public DaoException(Throwable throwable, int code) {
super(throwable);
this.code = code;
}
// alıcı ve ayarlayıcılar
...
}
- 6. satır: [DaoException] istisnası, denetlenmeyen bir istisnadır;
[setUrlServiceWebJson] yöntemi
[setUrlServiceWebJson] yöntemi şöyledir:
@Override
public void setUrlServiceWebJson(String urlServiceWebJson) {
// REST hizmetinin URL'i ayarlanıyor
webClient.setRootUrl(urlServiceWebJson);
}
- 4. satır: [WebClient] arayüzünün [setRootUrl] yöntemi aracılığıyla web hizmetinin URL değeri belirlenir. Bu yöntem, söz konusu arayüzün [RestClientRootUrl] arayüzünü genişletmesi nedeniyle mevcuttur;
1.16.2.5. [architecture] paketi
[architecture] paketi, uygulamayı yapılandıran öğeleri bir araya getirir:
![]() |
![]() |
1.16.2.5.1. [IMainActivity] arayüzü
[IMainActivity] arayüzü, uygulama etkinliğinin uygulaması gereken yöntemleri listeler:
package exemples.android.architecture;
import exemples.android.dao.IDao;
public interface IMainActivity extends IDao {
// oturuma erişim
Session getSession();
// görünüm değişikliği
void navigateToView(int position);
// bekleme
void beginWaiting();
void cancelWaiting();
// hata ayıklama modu
boolean IS_DEBUG_ENABLED = true;
// yanıt bekleme süresi
int TIMEOUT = 1000;
// parçaların bitişikliği
int OFF_SCREEN_PAGE_LIMIT = 1;
}
- 5. satır: [IMainActivity] arayüzü, [IDao] arayüzünü genişletir;
- 13-16. satırlar: önceki örneklerde (7-11. satırlar) zaten bulunan yöntemlere, uygulamanın bekleme ekranını yönetmek için iki yöntem daha ekledik (14., 16. satırlar);
- 21. satır: Sunucudan yanıt gelmesi için maksimum bekleme süresini 1 saniye olarak belirledik;
1.16.2.5.2. [Utils] sınıfı
[Utils] sınıfında, uygulama mimarisinin farklı yerlerinden çağrılabilen statik yardımcı yöntemler bir araya getirilmiştir:
package exemples.android.architecture;
import java.util.ArrayList;
import java.util.List;
public class Utils {
// bir istisnaya ait mesaj listesi - sürüm 1
static public List<String> getMessagesFromException(Throwable ex) {
// istisna yığınının hata mesajlarından oluşan bir liste oluşturulur
List<String> messages = new ArrayList<>();
Throwable th = ex;
while (th != null) {
messages.add(th.getMessage());
th = th.getCause();
}
return messages;
}
// bir istisnaya ait mesaj listesi - sürüm 2
static public String getMessagesForAlert(Throwable th) {
// görüntülenecek metin oluşturuluyor
StringBuilder texte = new StringBuilder();
List<String> messages = getMessagesFromException(th);
int n = messages.size();
for (String message : messages) {
texte.append(String.format("%s : %s\n", n, message));
n--;
}
// sonuç
return texte.toString();
}
}
- 9-18. satırlar: Throwable içinde bulunan hata mesajlarının listesini oluşturur;
- 21-32. satırlar: önceki yöntemi kullanarak elde edilen mesaj listesinden bir Android uyarı mesajında görüntülenecek metni oluşturur;
- 27-28. satırlar: Mesajlar numaralandırılır. En küçük numara (1) ilk istisnaya, en büyük numara ise istisna yığınındaki en son istisnaya karşılık gelir;
1.16.2.5.3. [AbstractFragment] soyut sınıfı
[AbstractFragment] sınıfının iki işlevi vardır:
- fragman görüntülenirken alt sınıfların [updateFragments] yönteminin her zaman ve yalnızca bir kez çağrılmasını sağlamak;
- alt sınıfların durumunu ve yöntemlerini, mümkün olduğu ölçüde ayırmak;
Bekleme görüntüsünün yönetimi ile ilgili işlemleri bu sınıfa yerleştirmemizin nedeni ikinci işlevdir: Asenkron bir Android uygulamasındaki tüm parçalar bu tür sorunları yönetmek zorundadır:
// bekleme yönetimi
protected void beginWaiting() {
// kum saati gösteriliyor
mainActivity.beginWaiting();
}
protected void cancelWaiting() {
// kum saatini kaldırıyoruz
mainActivity.cancelWaiting();
}
1.16.2.6. Görünüm
![]() |
1.16.2.6.1. Görünüm [vue1.xml]
![]() |
Önceki örneğe kıyasla, [vue1.xml] görünümü şu şekilde değişir:
![]() |
![]() |
- [1]'te, kullanıcı her web hizmeti çağrısından önce web hizmetinin URL'sini ve [2] bekleme süresini belirtmelidir;
- [3]'te yanıtlar sayılır;
- [4]'te, kullanıcı isteğini iptal edebilir;
- [5]'te, sayılar istendiğinde bir bekleme göstergesi görüntülenir. Tüm sayılar alındığında veya işlem iptal edildiğinde bu gösterge kaybolur;

- [6]'te, girilen verilerin geçerliliği kontrol edilir;
Kullanıcıdan örneklerden [vue1.xml] dosyasını yüklemesi istenir. Devamında, yeni bileşenlerin kimlik numaralarını veriyoruz:

[10-11] düğmeleri fiziksel olarak üst üste bulunmaktadır. Belirli bir anda, bu ikisinden sadece biri görünür hale getirilecektir.
1.16.2.6.2. [Vue1Fragment] parçası
![]() |
[Vue1Fragment] parçasının iskeleti şöyledir:
package exemples.android.fragments;
import android.app.AlertDialog;
import android.support.annotation.*;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.*;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import exemples.android.architecture.Utils;
import org.androidannotations.annotations.*;
import org.androidannotations.annotations.UiThread;
import org.androidannotations.api.BackgroundExecutor;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// görsel arayüz öğeleri
@ViewById(R.id.editTextUrlServiceWeb)
EditText edtUrlServiceRest;
@ViewById(R.id.textViewErreurUrl)
TextView txtMsgErreurUrlServiceWeb;
@ViewById(R.id.editTextDelay)
EditText edtDelay;
@ViewById(R.id.textViewErreurDelay)
TextView textViewErreurDelay;
@ViewById(R.id.lst_reponses)
ListView listReponses;
@ViewById(R.id.txt_Reponses)
TextView infoReponses;
@ViewById(R.id.edt_nbaleas)
EditText edtNbAleas;
@ViewById(R.id.edt_a)
EditText edtA;
@ViewById(R.id.edt_b)
EditText edtB;
@ViewById(R.id.txt_errorNbAleas)
TextView txtErrorAleas;
@ViewById(R.id.txt_errorIntervalle)
TextView txtErrorIntervalle;
@ViewById(R.id.btn_Executer)
Button btnExecuter;
@ViewById(R.id.btn_Annuler)
Button btnAnnuler;
...
// yerel veriler
private List<String> reponses;
private ArrayAdapter<String> adapterReponses;
@AfterViews
void afterViews() {
// bellek
afterViewsDone=true;
// başlangıçta hata mesajı yok
txtErrorAleas.setVisibility(View.INVISIBLE);
txtErrorIntervalle.setVisibility(View.INVISIBLE);
txtMsgErreurUrlServiceWeb.setVisibility(View.INVISIBLE);
textViewErreurDelay.setVisibility(View.INVISIBLE);
// [Annuler] düğmesi gizlenir
btnAnnuler.setVisibility(View.INVISIBLE);
btnExecuter.setVisibility(View.VISIBLE);
// yanıt listesi
reponses = new ArrayList<>();
}
...
- 24-49. satırlar: [vue1.xml] görünümünün bileşenlerine ilişkin referanslar (20. satır);
- 55-69. satırlar: 24-49. satırlardaki referanslar başlatıldığında yürütülen [@AfterViews] yöntemi;
- satır 58: unutulmaması gereken - parçanın yaşam döngüsü için gereklidir;
- satır 60-63: hata mesajları gizlenir;
- satır 65-66: [Annuler] düğmesi gizlenir (satır 65) ve [Exécuter] düğmesi görüntülenir (satır 66). Bu düğmelerin fiziksel olarak birbirinin üzerinde yer aldığını hatırlatırız;
- 68. satır: 52. satırdaki alan, yanıtlardaki ListView tarafından görüntülenecek karakter dizelerinin listesini içerecektir;
[@AfterViews] yönteminden hemen sonra, aşağıdaki [updateFragment] yöntemi çalıştırılacaktır:
@Override
protected void updateFragment() {
// yanıt listesi adaptörü oluşturuluyor
adapterReponses = new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, android.R.id.text1, reponses);
listReponses.setAdapter(adapterReponses);
}
- 4-5. satırlar: Yanıtların ListView adaptörü oluşturulur. Sınıfın diğer yöntemleri tarafından kullanılabilmesi için bir örnek değişkeninde saklanır;
[Exécuter] düğmesine 'tıklandığında' aşağıdaki yöntem çalıştırılır:
// girdiler
private int nbAleas;
private int a;
private int b;
private String urlServiceWebJson;
private int delay;
// yerel veriler
private int nbInfos;
private List<String> reponses;
private ArrayAdapter<String> adapterReponses;
private boolean hasBeenCanceled;
@Click(R.id.btn_Executer)
protected void doExecuter() {
// önceki yanıtlar siliniyor
reponses.clear();
adapterReponses.notifyDataSetChanged();
hasBeenCanceled = false;
// yanıt sayacını 0'a sıfırlama
nbInfos = 0;
infoReponses.setText(String.format("Liste des réponses (%s)", nbInfos));
// girdilerin geçerliliği kontrol ediliyor
if (!isPageValid()) {
return;
}
// işlemin başlatılması
mainActivity.setUrlServiceWebJson(urlServiceWebJson);
mainActivity.setDelay(delay);
// rastgele sayılar istenir
for (int i = 0; i < nbAleas; i++) {
getAlea(a, b);
}
// bekleme süreci başlatılıyor
beginWaiting();
}
@Background(id = "alea")
void getAlea(int a, int b) {
// burada mümkün olduğunca az işlem yapılmalı
// her halükarda hiçbir şey görüntülenmemeli - bunlar UiThead içinde yapılmalıdır
try {
// sonuç UiThread dosyasında görüntülenir
showInfo(mainActivity.getAlea(a, b));
} catch (RuntimeException e) {
// istisna, UiThread içinde görüntüleniyor
showAlert(e);
}
}
- 17-18. satırlar: Sunucudaki önceki yanıt listesi silinir. Bunun için 17. satırda, ListView adaptörüne bağlı [reponses] veri kaynağı boşaltılır;
- 19. satır: Kullanıcının isteğini iptal edip etmediğini anlamamıza yardımcı olacak bir boole değeri;
- 21-22. satırlar: yanıt sayısı için sıfır değerinde bir sayaç görüntülenir;
- 24-26. satırlar: [2-6] satırlarındaki girdileri alır ve geçerliliğini kontrol ederiz. Bunlardan herhangi biri geçersizse, yöntem sonlandırılır (25. satır) ve kullanıcı görsel arayüze geri yönlendirilir;
- satır 28-29: Girilen verilerin tümü geçerliyse, web hizmetinin URL numarası (satır 28) ile her hizmet çağrısı öncesi bekleme süresi (satır 29) aktiviteye iletilir. Bu bilgiler [DAO] katmanı için gereklidir ve bu katmanla iletişim kuran faaliyetin kendisi olduğu hatırlatılmalıdır;
- satır 31-33: Rastgele sayılar, satır 39’daki [getAlea] yönteminden tek tek istenir;
- satır 38: [getAlea] yöntemi, AA [@Background] anotasyonuyla işaretlenmiştir; bu sayede, görsel arayüzün çalıştığı iş parçacığından farklı bir iş parçacığında (yürütme akışı, işlem) içinde çalıştırılacaktır. Aslında, tüm internet çağrılarının görsel arayüzün çalıştığı iş parçacığından farklı bir iş parçacığında çalıştırılması zorunludur. Böylece, belirli bir anda birkaç iş parçacığı olabilir:
- UI (Kullanıcı Arayüzü) arayüzünü görüntüleyen ve olaylarını yöneten iş parçacığı,
- her biri web hizmetinden rastgele bir sayı isteyen [nbAleas] iş parçacıkları. Bu iş parçacıkları eşzamansız olarak başlatılır: UI iş parçacığı, web hizmetinden rastgele bir sayı isteyen ve işlemin bitmesini beklemeyen bir [getAlea] iş parçacığını başlatır (satır 32). İşlemin tamamlandığı, bir olay aracılığıyla kendisine bildirilir. Böylece, [nbAleas] iş parçacıkları paralel olarak başlatılacaktır. Uygulamanın her seferinde yalnızca bir iş parçacığı başlatması için yapılandırılması mümkündür. Bu durumda, yürütülecek iş parçacıkları için bir kuyruk oluşur;
- satırda, [id] parametresi oluşturulan iş parçacığına bir ad verir. Burada [nbAleas] iş parçacıklarının hepsi aynı adı, yani [alea]'i taşır. Bu, hepsini aynı anda iptal etmemizi sağlayacaktır. İş parçacığının iptalini yönetmiyorsak bu parametre isteğe bağlıdır;
- 44. satır: Aktivitenin [getAlea] yöntemi çağrılır. Dolayısıyla bu yöntem, UI iş parçacığından ayrı bir iş parçacığında çağrılacaktır. Bu iş parçacığı web hizmetine çağrı yapacak ve yanıtı beklemeyecektir. Yanıtın hazır olduğu, daha sonra bir olay aracılığıyla kendisine bildirilecektir. İşte bu anda, 44. satırda, [showInfo] yöntemi, alınan yanıt parametresiyle çağrılacaktır;
- 45-47. satırlar: Web isteğinin yürütülmesi bir istisna oluşturabilir. Bu durumda, istisnaya ait hata mesajlarının bir uyarı mesajında görüntülenmesi istenir;
- 35. satır: Sonuçlar gelene kadar bekleme moduna geçilir:
- bir bekleme göstergesi görüntülenecektir;
- [Annuler] düğmesi, [Exécuter] düğmesinin yerini alacaktır. Başlatılan iş parçacıkları asenkron olduğundan, UI iş parçacığı bunları beklemez ve 35. satır, bu iş parçacıklarının tamamlanmasından önce yürütülür. [beginWaiting] yöntemi tamamlandığında, UI, [Annuler] düğmesine tıklama gibi kullanıcı isteklerine yeniden yanıt verebilir. Başlatılan iş parçacıkları senkron olsaydı, 35. satıra ancak tüm iş parçacıkları tamamlandıktan sonra ulaşılabilirdi. Bu durumda iş parçacıklarını iptal etmenin bir anlamı kalmazdı;
[showInfo] yöntemi şu şekildedir:
@UiThread
protected void showInfo(int alea) {
if (!hasBeenCanceled) {
// ek bilgi
nbInfos++;
infoReponses.setText(String.format("Liste des réponses (%s)", nbInfos));
// bitti mi?
if (nbInfos == nbAleas) {
// bekleme sona eriyor
cancelWaiting();
}
// bilgi yanıt listesine ekleniyor
reponses.add(0, String.valueOf(alea));
// yanıtlar görüntüleniyor
adapterReponses.notifyDataSetChanged();
}
}
- [showInfo] yöntemi, [@Background] ile işaretlenmiş [getAlea] iş parçacığı içinde çağrılır. Bu yöntem, UI görsel arayüzünü güncelleyecektir. Bunu ancak UI iş parçacığı içinde çalıştırıldığında yapabilir. 1. satırdaki [@UiThread] notunun anlamı budur;
- 2. satır: Yöntem rastgele bir sayı alır;
- 3. satır: Yöntemin gövdesi, kullanıcı isteğini iptal etmediği sürece yürütülür;
- 5-6. satırlar: yanıt sayacı artırılır ve görüntülenir;
- satır 8-11: Beklenen tüm yanıtlar alındığında, bekleme sonlandırılır (bekleme sinyalinin sonu; [Exécuter] düğmesi, [Annuler] düğmesinin yerini alır);
- satır 12-15: Alınan rastgele sayı, [ListView listReponses] bileşeni tarafından görüntülenen yanıt listesine eklenir ve liste yenilenir;
[showAlert] yöntemi şu şekildedir:
@UiThread
protected void showAlert(Throwable th) {
if (!hasBeenCanceled) {
// her şeyi iptal ediyoruz
doAnnuler();
// görüntüleniyor
new AlertDialog.Builder(activity).setTitle("Des erreurs se sont produites").setMessage(Utils.getMessagesForAlert(th)).setNeutralButton("Fermer", null).show();
}
}
[showInfo] yöntemine benzer bir mantık izlenir:
- 1. satır: [@UiThread] açıklaması zorunludur;
- 2. satır: Yöntem, meydana gelen istisnayı alır;
- 3. satır: Yöntem, kullanıcı isteğini iptal etmediği takdirde yürütülür;
- 5. satır: Kullanıcı, [Annuler] düğmesine kendisi tıklamış gibi isteği iptal edilir;
- 7. satır: Android [AlertDialog] sınıfı kullanılarak uyarı görüntülenir:
- [activity]: [Activity] türündeki etkinlik, üst sınıf [AbstractFragment]'te saklanır;
- [setTitle]: [1] uyarı penceresinin başlığını belirler;
- [setMessage]: [2] uyarı penceresinde görüntülenen mesajı belirler;
- [setNeutral]: [3] uyarı penceresini kapatacak düğmeyi belirler;
- [show]: uyarı penceresinin görüntülenmesini ister;
![]() |
[Annuler] düğmesine yapılan 'tıklama', aşağıdaki yöntemle yönetilir:
@Click(R.id.btn_Annuler)
protected void doAnnuler() {
// bellek
hasBeenCanceled=true;
// asenkron görevi iptal eder
BackgroundExecutor.cancelAll("alea", true);
// bekleme sona erdi
cancelWaiting();
}
- 4. satır: Kullanıcının isteğini iptal ettiği not edilir;
- 6. satır: [alea] dizesiyle tanımlanan tüm görevleri iptal eder. İkinci parametre olan [true], görevler daha önce başlatılmış olsa bile iptal edilmesi gerektiğini belirtir. [alea] kimliği, parçadaki (aşağıdaki 1. satır) [getAlea] yöntemini tanımlamak için kullanılır:
@Background(id = "alea")
void getAlea(int a, int b) {
...
}
Not: [doAnnuler] yönteminin kodundaki 6. satırın hatalı çalıştığı ortaya çıktı. Bu nedenle [hasBeenCanceled] boole değeri eklendi. Nitekim, bir istisna durumunda (sunucu yok), n rastgele sayı talep edilmişse, n kez uyarı penceresi görüntüleniyordu.
1.16.2.7. [MainActivity] etkinliği
![]() |
1.16.2.7.1. [activity-main.xml] görünümü
![]() |
Önceki örneğe kıyasla, [MainActivity] etkinliğiyle ilişkili görünüme bir bekleme resmi ekledik:
...
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways">
<!-- bekleme görüntüsü -->
<ProgressBar
android:id="@+id/loadingPanel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"/>
</android.support.v7.widget.Toolbar>
<!-- bekleme görüntüsü -->
</android.support.design.widget.AppBarLayout>
...
- 17-21. satırlar: bekleme görüntüsü;
1.16.2.7.2. [MainActivity] etkinliği
[MainActivity] etkinliği, [Exemple-14]'teki haline kıyasla çok az değişiklik göstermiştir. Öncelikle, bu etkinliğe [DAO] katmanı eklenmiştir:
// DAO enjeksiyonu
@Bean(Dao.class)
protected IDao dao;
...
@AfterInject
protected void afterInject() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterInject");
}
// [DAO] katmanını ayarlıyoruz
setTimeout(TIMEOUT);
}
- 2-3. satırlar: AA açıklaması yoluyla [DAO] katmanının eklenmesi;
- 5-13. satırlar: bu eklemeden sonra yürütülen kod;
- 12. satır: [DAO] katmanının timeout değeri belirlenir
Ayrıca, [MainActivity] etkinliği, [IMainActivity] arayüzünü uygulamalıdır; bu arayüz ise [IDao] arayüzünü genişletir:
// uygulama IMainActivity --------------------------------------------------------------------
@Override
public void navigateToView(int position) {
// konum görünümü görüntüleniyor
if (mViewPager.getCurrentItem() != position) {
// parça görüntüleniyor
mViewPager.setCurrentItem(position);
}
}
// bekleme görüntüsü yönetimi
public void cancelWaiting() {
loadingPanel.setVisibility(View.INVISIBLE);
}
public void beginWaiting() {
loadingPanel.setVisibility(View.VISIBLE);
}
// uygulama IDao --------------------------------------------------------------------
@Override
public int getAlea(int a, int b) {
// çalıştırma
return dao.getAlea(a, b);
}
@Override
public void setDelay(int delay) {
dao.setDelay(delay);
}
@Override
public void setUrlServiceWebJson(String url) {
dao.setUrlServiceWebJson(url);
}
@Override
public void setTimeout(int timeout) {
dao.setTimeout(timeout);
}
1.16.2.8. Projenin çalıştırılması
Web hizmetini başlatın (1.16.1.7. paragraf) ve ardından Android istemcisini başlatın:

[1]'e ne gireceğinizi öğrenmek için aşağıdaki adımları izleyin. Bir komut penceresi açın ve aşağıdaki komutu yazın:
C:\Program Files\Console2>ipconfig
Configuration IP de Windows
Carte réseau sans fil Connexion au réseau local* 3 :
Statut du média. . . . . . . . . . . . : Média déconnecté
Suffixe DNS propre à la connexion. . . :
Carte Ethernet VirtualBox Host-Only Network :
Suffixe DNS propre à la connexion. . . :
Adresse IPv6 de liaison locale. . . . .: fe80::e481:1583:cd2a:c47%27
Adresse IPv4. . . . . . . . . . . . . .: 192.168.82.2
Masque de sous-réseau. . . . . . . . . : 255.255.255.0
Passerelle par défaut. . . . . . . . . :
Carte Ethernet VirtualBox Host-Only Network #2 :
Suffixe DNS propre à la connexion. . . :
Adresse IPv6 de liaison locale. . . . .: fe80::8191:14ad:407d:b840%54
Adresse IPv4. . . . . . . . . . . . . .: 192.168.64.2
Masque de sous-réseau. . . . . . . . . : 255.255.255.0
Passerelle par défaut. . . . . . . . . :
Carte Ethernet Ethernet :
Suffixe DNS propre à la connexion. . . : ad.univ-angers.fr
Adresse IPv6 de liaison locale. . . . .: fe80::d972:ad53:3b8a:263f%28
Adresse IPv4. . . . . . . . . . . . . .: 172.19.81.34
Masque de sous-réseau. . . . . . . . . : 255.255.0.0
Passerelle par défaut. . . . . . . . . : 172.19.0.254
Carte réseau sans fil Wi-Fi :
Statut du média. . . . . . . . . . . . : Média déconnecté
Suffixe DNS propre à la connexion. . . : uang ad.univ-angers.fr univ-angers.fr
[GenyMotion]'i kurduysanız, VirtualBox sanal makinesi bilgisayarınıza IP adreslerini eklemiştir (10. ve 18. satırlar). Bu adresler, Windows güvenlik duvarı tarafından engellenmedikleri için özellikle kullanışlıdır. 30. satır, yerel ağdaki bilgisayarınızın IP adresini gösterir. Bu adresi kullanmak için genellikle Windows güvenlik duvarını devre dışı bırakmanız gerekir. Bir Wi-Fi ağına bağlıysanız, Wi-Fi adresini kullanın ve burada da varsa güvenlik duvarını devre dışı bırakın.
Uygulamayı aşağıdaki durumlarda test edin:
- [1000, 2000] aralığında bekleme süresi olmadan 100 rastgele sayı;
- [10000, 20000] aralığında 2000 rastgele sayı, bekleme süresi olmadan ve sayı üretimi tamamlanmadan beklemeyi iptal edin;
- [100, 200] aralığında 5 rastgele sayı, 5000 ms bekleme süresi ile ve sayı üretimi tamamlanmadan beklemeyi iptal edin;
1.16.2.9. İptal Yönetimi
Kullanıcı iptal talebinde bulunduğunda veya bir istisna oluştuğu için iptal talep edildiğinde neler olduğunu takip etmek için, [IDao] arayüzüne (bkz. paragraf 1.16.2.4.1) aşağıdaki yöntemi ekliyoruz:
package exemples.android.dao;
public interface IDao {
...
// hata ayıklama modu
void setDebugMode(boolean isDebugEnabled);
}
[Dao] sınıfına aşağıdaki kodu ekliyoruz:
// hata ayıklama modu
private boolean isDebugEnabled;
// sınıf adı
private String className;
..
// yapıcı
public Dao() {
// sınıf adı
className = getClass().getSimpleName();
}
...
// arayüz IDao -------------------------------------------------------------------
@Override
public int getAlea(int a, int b) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), String.format("getAlea [%s, %s] en cours", a, b));
}
// hizmet çalıştırma
Response<Integer> info;
...
@Override
public void setDebugMode(boolean isDebugEnabled) {
this.isDebugEnabled = isDebugEnabled;
}
- 9. satır: sınıfın adını yazıyoruz;
- 16-18. satırlar: [getAlea] yöntemi her çağrıldığında bir günlük kaydı yazıyoruz;
Ayrıca, [Vue1Fragment] parçacığına aşağıdaki günlük kayıtlarını ekliyoruz:
@UiThread
protected void showInfo(int alea) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), String.format("showInfo(%s)", alea));
}
....
}
@UiThread
protected void showAlert(Throwable th) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), "Exception reçue");
}
...
}
}
@Click(R.id.btn_Annuler)
protected void doAnnuler() {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), "Annulation demandée");
}
...
}
[Vue1Fragment] parçası, [DAO] katmanından her bilgi aldığında bir günlük kaydı oluşturulur. Ayrıca, [doAnnuler] yöntemi çağrıldığında olay günlüğe kaydedilir.
Test 1
Sunucu başlatılmamışken 5 sayı isteniyor. Aşağıdaki günlük kayıtları elde ediliyor:
- 1-5. satırlar: [Dao] sınıfının [getAlea] yöntemi beş kez çağrılmıştır. Bunların, [VueFragment] parçası tarafından yapılan asenkron çağrılar olduğunu ve bu parçanın çağrısının sonucunu beklemediğini hatırlatırız;
- 7. satır: İlk HTTP isteği gerçekleştirildi ve [VueFragment] parçası ilk istisnasını aldı;
- 8. satır: Ardından tüm isteklerin iptal edilmesini talep eder;
- 9-12. satırlar: ancak, aşağıdaki dört istisnayı aldığı görülüyor. Dolayısıyla, beklemede olan asenkron isteklerin tümü yürütülmüş;
Test 2
Şimdi sunucuyu başlatıp 5 saniyelik bir gecikmeyle 5 sayı isteyelim ve bu süre dolmadan [Annuler]'e tıklayalım. Günlük kayıtları şu şekildedir:
- 1-5. satırlar: [Dao] sınıfının [getAlea] yöntemi beş kez çağrılmıştır;
- 7. satır: kullanıcı isteklerin iptal edilmesini talep etmiştir;
- 8. satır: [Vue1_Fragment]'in 5 değer aldığı görülmektedir. Yine, beklemede olan asenkron isteklerin tümü yürütülmüştür;
Bu nedenle, iptal talebi varken herhangi bir şeyin görüntülenmesini önlemek için [hasBeenCanceled] adlı bir boole değişkeni kullanmamız gerekti. İptal kodunda:
@Click(R.id.btn_Annuler)
protected void doAnnuler() {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), "Annulation demandée");
}
// bellek
hasBeenCanceled = true;
// asenkron görev iptal ediliyor
BackgroundExecutor.cancelAll("alea",true);
// bekleme sona erdi
cancelWaiting();
}
- satırdaki kod beklenen şekilde çalışmıyor. Bunun nedeni, asenkron görevlerin aynı [@Background] anotlu yöntemi paylaşması olabilir:
@Background(id = "alea")
void getAlea(int a, int b) {
...
}
1.17. Örnek-16: RxAndroid ile asenkronluğu yönetme
Şimdi, Android uygulamaları için gerekli olan asenkronizasyonu, RxJava [http://reactivex.io/] adlı kütüphane ve Android ortamı için türetilmiş versiyonu olan [RxAndroid] ile yönetmeyi öneriyoruz. Bunun için [Introduction à RxJava. Application aux environnements Swing et Android] dersini kullanacağız.
1.17.1. Projenin Oluşturulması
[Exemple-1] projesini [Exemple-16] olarak kopyalıyoruz:
![]() | ![]() |
1.17.2. Gradle yapılandırması
![]() |
[build.gradle]'te, [RxAndroid] kütüphanesine bağımlılık ekliyoruz:
dependencies {
...
compile 'io.reactivex:rxandroid:1.2.0'
}
1.17.3. [DAO] katmanı
![]() |
1.17.4. [IDao] arayüzü
[IDao] arayüzü şu şekilde olur:
package exemples.android.dao;
import rx.Observable;
public interface IDao {
// rastgele sayı
Observable<Integer> getAlea(int a, int b);
// URL web hizmeti
void setUrlServiceWebJson(String url);
// sunucu yanıtı için maksimum bekleme süresi (ms)
void setTimeout(int timeout);
// istek öncesi istemci bekleme süresi (milisaniye)
void setDelay(int delay);
// hata ayıklama modu
void setDebugMode(boolean isDebugEnabled);
}
- 8. satır: [getAlea] yöntemi artık RxJava kütüphanesinden (3. satır) [Observable] türünde bir değer döndürür. Prensip şöyledir:
Observable<T> türündeki bir eleman akışı, Subscriber<T> türündeki bir veya daha fazla abone (gözlemci, tüketici) tarafından gözlemlenir. RxJava kütüphanesi, geliştiricininbu iş parçacıklarının yaşam döngüsünü yönetmekle veya iş parçacıkları arasında veri paylaşımı ve genel bir görevi yürütmek için iş parçacıklarının senkronizasyonu gibi doğal olarak zor sorunlarla uğraşmak zorunda kalmadan. Böylece asenkron programlamayı kolaylaştırır.
1.17.5. [AbstractDao] sınıfı
[Dao] sınıfını aşağıdaki [AbstractDao] sınıfından türeteceğiz:
package exemples.android.dao;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import rx.Observable;
import rx.Subscriber;
public abstract class AbstractDao {
// jSON eşleştiricisi
private ObjectMapper mapper = new ObjectMapper();
// korumalı yöntemler ----------------------------------------------------------
// genel arayüz
protected interface IRequest<T> {
Response<T> getResponse();
}
// genel istek
protected <T> Observable<T> getResponse(final IRequest<T> request) {
// hizmet yürütme
return rx.Observable.create(new rx.Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
DaoException ex = null;
// hizmet yürütme
try {
// senkron istek yapılır ve yanıt aboneye iletilir
Response<T> response = request.getResponse();
// hata mı?
int status = response.getStatus();
if (status != 0) {
// istisna kaydedilir
ex = new DaoException(mapper.writeValueAsString(response.getMessages()), status);
} else {
// yanıt gönderiliyor
subscriber.onNext(response.getBody());
// gözlemlenebilirin sonu bildirilir
subscriber.onCompleted();
}
} catch (JsonProcessingException | RuntimeException e) {
// istisna kaydediliyor
ex = new DaoException(e, 100);
}
// istisna mı?
if (ex != null) {
// istisna gönderilir
subscriber.onError(ex);
}
}
});
}
}
- [AbstractDao] sınıfının ana öğesi, sunucudan [Response<T>] türünde bir değer elde etmek için kullanılan [getResponse] jenerik yöntemidir; burada T, istemci tarafından istenen sonucun türü olan HTTP (burada Integer) türündedir;
- 20. satır: [getResponse] jenerik yönteminin tek parametresi, 15-17. satırlardaki [IRequest<T>] jenerik arayüzünün bir örneğidir. Bu arayüzün tek bir yöntemi vardır: [getResponse] ve istenen yanıtı [Response<T>] sağlayan da bu yöntemdir;
- önceki iki öğe sayesinde, [AbstractDao] sınıfı, [Response<T>] türünde yanıtlar gönderen bir sunucunun tüm [Dao] istemci katmanları için üst sınıf görevi görebilir;
- 20. satır: [getResponse] jenerik yöntemi, HTTP istemcisi tarafından gerçekten beklenen sonucu temsil eden bir [Observable<T>] türünü döndürür (burada bir Observable<Integer> türü);
- 22-51. satırlar: [rx.Observable.create] statik yöntemi, [Observable] türünü oluşturur;
- 22. satır: Bu yöntemin tek parametresi, aşağıdaki yöntemlere sahip bir arayüz olan [rx.Observable.OnSubscribe<T>] türünün bir örneğidir:
- [onNext(T element)]: Bir gözlemciye T türünde bir öğe gönderilmesini sağlar;
- [onError(Throwable th)]: bir gözlemciye bir istisna gönderilmesini sağlar;
- [onCompleted]: bir gözlemciye gönderimlerin sona erdiğini bildirir;
[Observable<T>] türü belirli kısıtlamalara tabidir:
- öğelerini [onNext(T element)] yöntemi ile gönderir;
- gözlemciye gönderilecek öğe kalmadığında [onCompleted] yöntemi yalnızca bir kez çağrılmalıdır;
- [onError(Throwable th)] yöntemi çağrılmışsa, [onCompleted] yöntemi çağrılmaz;
Örneğimizde:
- gözlemci, [Vue1Fragment] parçası olacaktır. [Observable<T>] tarafından gönderilen öğeleri (öğe veya istisna) işleyen bu parçadır;
- oluşturulan [Observable<T>] türü yalnızca tek bir öğe yayınlayacaktır (37. satır);
- 29. satır: sunucuya senkron bir HTTP isteği gönderir ve [Response<T>] türünü alır. Bu HTTP isteği, genel [getResponse] yöntemine parametre olarak geçirilen [IRequest] türü tarafından gerçekleştirilir;
- 31. satır: Yanıtta bulunan status alınır;
- 32-34. satırlar: Bu status bir hata koduysa, bir istisna hazırlanır;
- satır 36-39: Bu status bir hata kodu değilse, müşterinin gerçekten beklediği yanıt gönderilir (satır 37) ve gözlemciye başka yanıt gönderilmeyeceği bildirilir (satır 39);
- satır 41-44: HTTP isteği bir istisna ile sonuçlanırsa, bu istisna kaydedilir;
- satır 46-49: [ex] istisnası null'ten farklıysa, bu istisna gözlemciye gönderilir. Burada, gözlemciye artık öğe gönderilmeyeceğini belirtmek için [onCompleted] yöntemini çağırmaya gerek yoktur. Bu, zımni olarak anlaşılır;
Bu açıklamalardan şunu çıkarabiliriz:
- genel [<T> Observable<T> getResponse(final IRequest<T> request)] yöntemi, yalnızca T türünde bir öğe ya da bir istisna yayan [Observable<T>] türünü döndürür;
- bu yöntemin tek parametresi olarak [IRequest<T>] türünü kabul eder; bu türün tek yöntemi olan [getResponse()], HTTP erişimini gerçekleştirir ve bu erişim [Response<T>] türünü döndürür;
1.17.6. [Dao] sınıfı
[Dao] sınıfı şu şekilde gelişir:
@EBean
public class Dao extends AbstractDao implements IDao {
// hizmetin istemcisi REST
@RestService
protected WebClient webClient;
// isteğin yürütülmesinden önce bekleme süresi
private int delay;
// hata ayıklama modu
private boolean isDebugEnabled;
// sınıf adı
private String className;
// yapıcı
public Dao() {
// sınıf adı
className = getClass().getSimpleName();
}
// arayüz IDao -------------------------------------------------------------------
@Override
public Observable<Integer> getAlea(final int a, final int b) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), String.format("getAlea [%s, %s] en cours", a, b));
}
// web istemcisi çalıştırma
return getResponse(new IRequest<Integer>() {
@Override
public Response<Integer> getResponse() {
// bekleme
waitSomeTime(delay);
// senkron HTTP çağrısı
return webClient.getAlea(a, b);
}
});
}
...
- 2. satır: [Dao] sınıfı, [AbstractDao] sınıfını genişletir;
- 24. satır: [getAlea] yöntemi artık [Observable<Integer>] türünde bir değer döndürür;
- satır 30: üst sınıftaki [getResponse] jenerik yöntemi çağrılır. Bu yönteme [IRequest<Integer>] türünde bir parametre aktarılır;
- 32-37. satırlar: [IRequest<Integer>] arayüzünün uygulanması;
- satır 36: Daha önce yapıldığı gibi, AA ve [webClient] arayüzleri aracılığıyla HTTP sorgusu gerçekleştirilir. [Response<Integer>] türünde bir değer alacağımız biliniyor; bu, [IRequest<Integer>.getReponse()] yönteminin döndürmesi gereken türdür;
- 36. satır: Burada closure adlı bir özellik kullanılır: bu, bir nesne oluşturulurken kendisine ait olmayan değerleri (burada 24. satırdaki [a, b] değerleri) nesneye kapsülleme yeteneğidir. Bu sayede [IRequest<Integer>.getReponse()] yönteminin parametreye ihtiyacı kalmaz. Bu parametreler, yöntemin gövdesine sabitlenmiştir. Normalde yöntemin parametrelerini (a, b) -> (x, y) şeklinde değiştireceğimiz yerde, burada x ve y değerlerini kapsayan yeni bir [IRequest<Integer>] örneği oluşturulur;
1.17.7. [MainActivity] sınıfı
[IDao] arayüzünü uygulayan [MainActivity] sınıfı şu şekilde gelişir:
// IDao uygulaması --------------------------------------------------------------------
@Override
public Observable<Integer> getAlea(int a, int b) {
// çalıştırma
return dao.getAlea(a, b);
}
1.17.8. [Vue1Fragment] sınıfı
[Vue1Fragment] sınıfı şu şekilde gelişir:
@Click(R.id.btn_Executer)
protected void doExecuter() {
// önceki yanıtlar siliniyor
reponses.clear();
adapterReponses.notifyDataSetChanged();
hasBeenCanceled = false;
// yanıt sayacı sıfırlanıyor
nbInfos = 0;
infoReponses.setText(String.format("Liste des réponses (%s)", nbInfos));
// girişlerin geçerliliği kontrol ediliyor
if (!isPageValid()) {
return;
}
// işlemin başlatılması
mainActivity.setUrlServiceWebJson(urlServiceWebJson);
mainActivity.setDelay(delay);
// rastgele sayılar istenir
getAleasInBackground(a, b);
// bekleme süreci başlatılıyor
beginWaiting();
}
- 18. satır: Rastgele sayılar, [getAleasInBackground] yönteminden istenir; bu yöntemin adı, sayıların kullanıcı arayüzünden farklı bir iş parçacığında isteneceği için bu şekilde verilmiştir;
private int nbReponses = 0;
// gözlemlenebilir değişkenlere abonelikler
private List<Subscription> abonnements;
// [Background] açıklaması gereksiz
void getAleasInBackground(int a, int b) {
// başlangıçta yanıt yok ve abonelik yok
nbReponses = 0;
abonnements.clear();
// gözlemlenebilir hazırlığı yapılıyor
Observable<Integer> response = Observable.empty();
// farklı çağrıların sonuçları birleştiriliyor HTTP
// bunlar bir I/O iş parçacığı üzerinde yürütülüyor
for (int i = 0; i < nbAleas; i++) {
response = response.mergeWith(mainActivity.getAlea(a, b).subscribeOn(Schedulers.io()));
}
// birikmiş gözlemlenebilir, UI iş parçacığı üzerinde gözlemlenecek
response = response.observeOn(AndroidSchedulers.mainThread());
try {
// gözlemlenebilir çalıştırılır
abonnements.add(response.subscribe(new Action1<Integer>() {
@Override
public void call(Integer alea) {
// bilgi, yanıt listesine eklenir
showInfo(alea);
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable th) {
// hata mesajı
showAlert(th);
// bekleme sona erdi
doAnnuler();
}
}, new Action0() {
@Override
public void call() {
// bekleme sona erdi
cancelWaiting();
}
}));
} catch (RuntimeException e) {
// istisna, UiThread dosyasında görüntüleniyor
showAlert(e);
}
}
- 3. satır: Bir gözlemlenebilirin aboneleri vardır. Bir abone ile gözlemlediği süreç arasındaki bağlantıya abonelik (Subscription) denir. Burada sadece bir gözlemlenebilir süreç ve bir abone olacak. Dolayısıyla sadece bir abonelik olacak. Prensip olarak, farklı gözlemciler tarafından gözlemlenen birden fazla süreç olabileceğini varsayıyoruz; bu durumda birden fazla abonelik oluşur;
- satır 11-18: gözlemlenen süreci (observable) yapılandırıyoruz. Bunun sadece bir yapılandırma olduğunu anlamak gerekir: süreç çalıştırılmamaktadır;
- 11. satır: Boş bir gözlemlenebilirle başlıyoruz; bu gözlemlenebilir hiçbir sinyal yaymaz;
- 14-16. satırlar: bu boş gözlemlenene, [nbAleas] gözlemlenebilirleri eklenir; bunlar, [nbAleas] istekleri ve HTTP rastgele sayıları döndürecek olan [nbAleas] istekleri olacaktır;
- 15. satır: Daha önce olduğu gibi, i numaralı rastgele sayı [MainActivity] sınıfından istenir. Burada henüz hiçbir HTTP isteğinin yürütülmediğini iyice anlamak gerekir. [mainActivity.getAlea(a, b)] yöntemi yürütülür ve bir [Observable<Integer>] türü döndürür. Bu, başlatıldığında gözlemlenecek bir işlemdir;
- 15. satır: [subscribeOn(Schedulers.io())] yöntemi, işlemin (çalıştırıldığında) bir G/Ç iş parçacığı üzerinde yürütülmesini talep eder. RxJava kütüphanesi farklı iş parçacığı türleri sunar. G/Ç iş parçacığı, HTTP çağrıları için uygundur;
- 15. satır: i numaralı gözlemlenebilir, 11. satırdaki ilk gözlemlenebilirle birleştirilir: her biri bir eleman yayan [nbAleas] gözlemlenebilirlerinden, [nbAleas] eleman yayacak bir gözlemlenebilir oluşturulur. Gözlemlenecek olan budur. Bu gözlemlenebilir, onu oluşturan tüm gözlemlenebilirler kendi [onCompleted] bildirimlerini gönderdikten sonra [onCompleted] bildirimini gönderir. Bu, önceki sürümde yaptığımız gibi, beklenen tüm sayıların alınıp alınmadığını anlamak için yanıtları saymak zorunda kalmamamızı sağlar;
- 18. satır: Bu noktaya geldiğimizde, her biri bir I/O iş parçacığı üzerinde çalışan [nbAleas] gözlemlenebilirlerinin birleşiminden oluşan bir gözlemlenebilir yapılandırmış oluruz;
- 18. satır: [observeOn(AndroidSchedulers.mainThread())] yöntemi, gözlemlenebilir nesne tarafından gönderilen değerlerin hangi iş parçacığında gözlemleneceğini belirtmek için kullanılır. Burada [AndroidSchedulers.mainThread())] iş parçacığı, RxJava kütüphanesine değil, RxAndroid kütüphanesine aittir. Bu, olay döngüsü (event loop) olarak da adlandırılan UI iş parçacığını belirtir. Bu nokta önemlidir: Bir Android uygulamasında, UI bileşenlerinin değiştirilmesi yalnızca UI iş parçacığında yapılabilir; aksi takdirde bir istisna oluşur;
- 19-45. satırlar: Gözlemlenecek işlem yapılandırıldığına göre, şimdi bu işlem çalıştırılır;
- 21. satır: Gözlemlenen sürecin yürütülmesini başlatan işlem [Observable.subscribe]'tir. Bu işlem, daha önce yapılandırılmış olan [nbAleas] asenkron süreçlerini başlatacaktır. Bu süreçlerin sonuçları, UI iş parçacığı üzerinde gözlemciye otomatik olarak sunulacaktır;
- Gözlemlenebilirin üç tür olay yaydığını hatırlayalım:
- [onNext]: bir öğe yayınladığında;
- [onError]: bir istisna ile karşılaştığında;
- [onCompleted]: artık olay yaymayacağını bildirdiğinde;
[Observable.subscribe] yönteminin parametreleri, her bir olayı işlemek için [call] yöntemlerini kullanan üç [Action1<Integer>, Action1<Throwable>, Action0] nesnesidir;
- 21-27. satırlar: [Action1<Integer>] türündeki ilk parametre, [onNext] olayını işlemek için kullanılır. Bu parametrenin [call] yöntemi, gözlemlenebilir nesne tarafından gönderilen öğeyi alır (23. satır);
- 25. satır: önceki örnekteki [showInfo] yöntemi yeniden kullanılır;
- satır 27-35: [Action1<Throwable>] türündeki ikinci parametre, [onError] olayını işlemek için kullanılır. [call] yöntemi, gözlemlenebilir tarafından üretilen istisnayı alır (satır 29);
- satır 31: önceki örnekteki [showAlert] yöntemi yeniden kullanılır;
- satır 33: kullanıcının isteğinin iptal prosedürü başlatılır. Bu, yürütülmekte olan tüm gözlemlenebilirleri iptal etmeyi içerir;
- satır 35-41: [Action0] türündeki üçüncü parametre, [onCompleted] olayını işlemek için kullanılır. [call] yöntemi hiçbir parametre almaz;
- 39. satır: Bekleme işlemi iptal edilir;
[showInfo] yöntemi şu şekilde gelişir:
// [UiThread] açıklaması gereksiz
protected void showInfo(int alea) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), String.format("showInfo(%s)", alea));
}
if (!hasBeenCanceled) {
// ek bilgi
nbInfos++;
infoReponses.setText(String.format("Liste des réponses (%s)", nbInfos));
// bilgi, yanıtlar listesine eklenir
reponses.add(0, String.valueOf(alea));
// yanıtlar görüntüleniyor
adapterReponses.notifyDataSetChanged();
}
}
Yöntemde iki değişiklik vardır:
- 1. satır: AA [@UiThread] açıklaması kaldırılmıştır;
- Beklemeyi sonlandırıp sonlandırmayacağımızı belirlemek için artık yanıtlar sayılmamaktadır. Artık bu bilgiyi bize gözlemlenebilirin [onCompleted] olayı vermektedir;
[showAlert] yöntemi şu şekilde değişmiştir:
// açıklama [UiThread] gereksiz
protected void showAlert(Throwable th) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), "Exception reçue");
}
if (!hasBeenCanceled) {
// her şeyi iptal eder
doAnnuler();
// görüntüleniyor
new AlertDialog.Builder(activity).setTitle("Des erreurs se sont produites").setMessage(Utils.getMessagesForAlert(th)).setNeutralButton("Fermer", null).show();
}
}
- tek değişiklik 1. satırda: AA [@UiThread] açıklaması kaldırıldı;
Son olarak, [doAnnuler] yöntemi şu şekilde değişir:
@Click(R.id.btn_Annuler)
protected void doAnnuler() {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), "Annulation demandée");
}
// bellek
hasBeenCanceled = true;
// asenkron görevleri iptal ediliyor
if (abonnements != null) {
for (Subscription abonnement : abonnements) {
abonnement.unsubscribe();
}
}
// bekleme sona erdi
cancelWaiting();
}
- 12. satır: bir aboneliği ve dolayısıyla ilgili sürecin izlenmesini iptal eder;
1.17.9. Çalıştırma
Web hizmetini başlatın (paragraf 1.16.1.7), Android istemcisini başlatın ve önceki örnekle yaptığınız testleri tekrarlayın (paragraf 1.16.2.8).
1.17.10. İptal Yönetimi
Önceki örnekteki (paragraf 1.16.2.9) testlerin aynısını tekrar yapın.
Test 1
Sunucu başlatılmamışken 5 sayı isteniyor. Aşağıdaki günlük kayıtları elde ediyoruz:
- satırdan sonra günlük kaydı kalmamıştır; bu da gözlemcinin (Vue1Fragment) gözlemlenen işlemden artık bildirim almadığını göstermektedir.
Test 2
Şimdi sunucuyu başlatıp 5 saniyelik bir aralıkla 5 sayı isteyelim ve bu süre dolmadan [Annuler]'e tıklayalım. Günlükler şu şekildedir:
- satırdan sonra başka günlük kaydı bulunmamaktadır; bu da gözlemcinin (Vue1Fragment) gözlemlenen süreçten artık bildirim almadığını göstermektedir.
Bu, bir iptal işleminden beklenen davranıştır. Dolayısıyla, [Vue1Fragment] kodunda, önceki örnekte eklediğimiz [hasBeenCanceled] boole değerini kaldırabiliriz; çünkü iptal işlemi beklendiği gibi çalışmamıştı.
Gözlemlenenin iptal edilmesinden sonra gözlemcinin artık bildirim almaması, HTTP isteklerinin kendilerinin de iptal edildiği anlamına gelmez. Bunu görmek için, [Dao] sınıfını şu şekilde değiştiriyoruz:
@Override
public Observable<Integer> getAlea(final int a, final int b) {
// günlük
if (isDebugEnabled) {
Log.d(String.format("%s", className), String.format("getAlea [%s, %s] en cours", a, b));
}
// web istemcisi çalıştırılıyor
return getResponse(new IRequest<Integer>() {
@Override
public Response<Integer> getResponse() {
// bekleme
waitSomeTime(delay);
// senkron HTTP çağrısı
Response<Integer> response= webClient.getAlea(a, b);
if (isDebugEnabled) {
try {
Log.d(String.format("%s", className), String.format("response [%s]", new ObjectMapper().writeValueAsString(response)));
} catch (JsonProcessingException e) {
Log.d(String.format("%s", className),"erreur désérialisation jSON");
}
}
return response;
}
});
}
- 15-21. satırlar: 14. satırdaki HTTP isteğinin sonucunu günlüğe kaydediyoruz;
2 numaralı test için günlük kayıtları şu şekildedir:
- satır 1-5: 5 istek gönderildi;
- 6. satır: kullanıcı işlemi iptal etti;
- 7-11. satırlar: HTTP numaralı beş sorgunun yanıtları başarıyla alınmaktadır. Ancak, gözlemlenebilirin iptal edilmesi nedeniyle bu öğeler gözlemciye iletilmemektedir;
1.17.11. Sonuç
Bu belgenin devamında, istemci/sunucu uygulamaları aşağıdaki nedenlerden dolayı AA kütüphanesi yerine RxAndroid kütüphanesi kullanılarak gerçekleştirilecektir:
- RxAndroid, AA kullanmayan bir Android uygulamasında kullanılabilir;
- RxAndroid, asenkron işlemleri kolaylaştırmaktan daha fazlasını yapar. Bir gözlemlenebilirden başka bir gözlemlenebilir oluşturmak için çok sayıda yöntem sunar. Bu yöntemlerin AA'te karşılığı yoktur;
- AA ile etiketlenmiş bir sınıfı (örneğin bir fragman) türetmek istediğimizde ciddi sorunlarla karşılaşırız. Bu durumda AA’ten vazgeçip asenkron programlama için 1 numaralı çözümü kullanmak zorunda kalırız;
RxAndroid kütüphanesinin olanaklarını daha ayrıntılı olarak incelemek isteyen okuyucular, [Introduction à RxJava. Application aux environnements Swing et Android] belgesini okuyabilirler. Bu belgede, AA kütüphanesi kullanılmadan RxAndroid kullanılmaktadır.
1.18. Örnek-17: Veri giriş bileşenleri
Veri giriş formlarında sıklıkla kullanılan bazı bileşenleri tanıtmak için yeni bir proje yazacağız.
1.18.1. Projenin oluşturulması
[Exemple-13] projesini [Exemple-17] olarak kopyalıyoruz:
![]() | ![]() |
Yeni proje sadece [vue1.xml] görünümüne sahip olacaktır. Bu nedenle, [vue2.xml] görünümünü ve buna bağlı [Vue2Fragment] ile [2] parçalarını siliyoruz. Bu değişikliği [Mainactivity] parça yöneticisinde dikkate alıyoruz:
// her uygulama için yeniden tanımlanması gereken parça yöneticimiz
// aşağıdaki yöntemleri tanımlamalıdır: getItem, getCount, getPageTitle
public class SectionsPagerAdapter extends FragmentPagerAdapter {
// parçalar
private final Fragment[] fragments = {new Vue1Fragment_()};
....
}
Projeyi yeniden çalıştırın. Proje, daha önce olduğu gibi 1 numaralı görünümü göstermelidir. Bu projeyi temel alarak çalışacağız.
1.18.2. Formun XML görünümü
![]() |
[vue1.xml] dosyası tarafından oluşturulan görünüm şöyledir:

Görünümdeki XML metni şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="30dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textViewFormulaireTitre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="10dp"
android:layout_marginTop="30dp"
android:text="@string/titre_vue1"
android:textSize="30sp"/>
<Button
android:id="@+id/formulaireButtonValider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/TextViewFormulaireCombo"
android:layout_below="@+id/TextViewFormulaireCombo"
android:layout_marginTop="30dp"
android:text="@string/formulaire_valider"/>
<TextView
android:id="@+id/textViewFormulaireCheckBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textViewFormulaireTitre"
android:layout_below="@+id/textViewFormulaireTitre"
android:layout_marginTop="30dp"
android:text="@string/formulaire_checkbox"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireRadioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textViewFormulaireCheckBox"
android:layout_below="@+id/textViewFormulaireCheckBox"
android:layout_marginTop="30dp"
android:text="@string/formulaire_radioButton"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireSeekBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textViewFormulaireRadioButton"
android:layout_below="@+id/textViewFormulaireRadioButton"
android:layout_marginTop="30dp"
android:text="@string/formulaire_seekBar"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireEdtText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textViewFormulaireSeekBar"
android:layout_below="@+id/textViewFormulaireSeekBar"
android:layout_marginTop="30dp"
android:text="@string/formulaire_saisie"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireBool"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textViewFormulaireEdtText"
android:layout_below="@+id/textViewFormulaireEdtText"
android:layout_marginTop="30dp"
android:text="@string/formulaire_bool"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireDate"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_alignLeft="@+id/textViewFormulaireBool"
android:layout_below="@+id/textViewFormulaireBool"
android:layout_marginTop="50dp"
android:gravity="center"
android:text="@string/formulaire_date"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireMultilignes"
android:layout_width="150dp"
android:layout_height="100dp"
android:gravity="center"
android:layout_alignBaseline="@+id/textViewFormulaireTitre"
android:layout_alignParentTop="true"
android:layout_marginLeft="400dp"
android:layout_toRightOf="@+id/textViewFormulaireTitre"
android:text="@string/formulaire_multilignes"
android:textSize="20sp"/>
<TextView
android:id="@+id/textViewFormulaireTime"
android:layout_width="wrap_content"
android:layout_height="200dp"
android:gravity="center"
android:layout_alignLeft="@+id/textViewFormulaireMultilignes"
android:layout_below="@+id/textViewFormulaireMultilignes"
android:layout_marginTop="30dp"
android:text="@string/formulaire_time"
android:textSize="20sp"/>
<TextView
android:id="@+id/TextViewFormulaireCombo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textViewFormulaireTime"
android:layout_below="@+id/textViewFormulaireTime"
android:layout_marginTop="30dp"
android:text="@string/formulaire_combo"
android:textSize="20sp"/>
<CheckBox
android:id="@+id/formulaireCheckBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textViewFormulaireCheckBox"
android:layout_marginLeft="100dp"
android:layout_toRightOf="@+id/textViewFormulaireCheckBox"
android:text="@string/formulaire_checkbox1"/>
<RadioGroup
android:id="@+id/formulaireRadioGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textViewFormulaireRadioButton"
android:layout_alignLeft="@+id/formulaireCheckBox1"
android:orientation="horizontal">
<RadioButton
android:id="@+id/formulaireRadioButton1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/formulaire_radiobutton1"/>
<RadioButton
android:id="@+id/formulaireRadioButton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/formulaire_radionbutton2"/>
<RadioButton
android:id="@+id/formulaireRadionButton3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/formulaire_radiobutton3"/>
</RadioGroup>
<SeekBar
android:id="@+id/formulaireSeekBar"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textViewFormulaireSeekBar"
android:layout_alignLeft="@+id/formulaireCheckBox1"/>
<EditText
android:id="@+id/formulaireEditText1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textViewFormulaireEdtText"
android:layout_alignLeft="@+id/formulaireCheckBox1"
android:ems="10"
android:inputType="text">
</EditText>
<Switch
android:id="@+id/formulaireSwitch1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textViewFormulaireBool"
android:layout_alignLeft="@+id/formulaireCheckBox1"
android:text="@string/formulaire_switch"
android:textOff="Non"
android:textOn="Oui"/>
<TimePicker
android:id="@+id/formulaireTimePicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textViewFormulaireTime"
android:layout_alignLeft="@+id/formulaireEditTextMultiLignes"
android:timePickerMode="spinner"
/>
<EditText
android:id="@+id/formulaireEditTextMultiLignes"
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_alignBaseline="@+id/textViewFormulaireMultilignes"
android:layout_alignBottom="@+id/textViewFormulaireMultilignes"
android:layout_marginLeft="50dp"
android:layout_toRightOf="@+id/textViewFormulaireMultilignes"
android:ems="10"
android:inputType="textMultiLine">
</EditText>
<Spinner
android:id="@+id/formulaireDropDownList"
android:layout_width="200dp"
android:layout_height="50dp"
android:layout_alignBottom="@+id/TextViewFormulaireCombo"
android:layout_alignLeft="@+id/formulaireEditTextMultiLignes">
</Spinner>
<DatePicker
android:id="@+id/formulaireDatePicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textViewFormulaireDate"
android:layout_alignLeft="@+id/formulaireCheckBox1"
android:datePickerMode="spinner"
android:calendarViewShown="false">
</DatePicker>
<TextView
android:id="@+id/textViewSeekBarValue"
android:layout_width="30dp"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textViewFormulaireSeekBar"
android:layout_marginLeft="30dp"
android:layout_toRightOf="@+id/formulaireSeekBar"
android:text=""/>
</RelativeLayout>
Formun ana bileşenleri şunlardır:
| |
| |
| |
| |
| |
| |
| ![]() |
| ![]() |
| ![]() |
| ![]() |
|
1.18.3. Formdaki karakter dizeleri
Formdaki karakter dizileri aşağıdaki [res / values / strings.xml] dosyasında tanımlanmıştır:
![]() |
<resources>
<string name="app_name">Exemple-17</string>
<string name="action_settings">Settings</string>
<string name="section_format">Hello World from section: %1$d</string>
<!-- görünüm 1 -->
<string name="titre_vue1">Vue n° 1</string>
<string name="formulaire_checkbox">Cases à cocher</string>
<string name="formulaire_radioButton">Boutons Radio</string>
<string name="formulaire_seekBar">Seek Bar</string>
<string name="formulaire_saisie">Champ de saisie</string>
<string name="formulaire_bool">Booléen</string>
<string name="formulaire_date">Date</string>
<string name="formulaire_time">Heure</string>
<string name="formulaire_multilignes">Champ de saisie multilignes</string>
<string name="formulaire_listview">Liste</string>
<string name="formulaire_combo">Liste déroulante</string>
<string name="formulaire_checkbox1">1</string>
<string name="formulaire_checkbox2">2</string>
<string name="formulaire_radiobutton1">1</string>
<string name="formulaire_radionbutton2">2</string>
<string name="formulaire_radiobutton3">3</string>
<string name="formulaire_switch"></string>
<string name="formulaire_valider">Valider</string>
</resources>
1.18.4. Form parçası
![]() |
[Vue1Fragment] sınıfı şu şekildedir:
package exemples.android.fragments;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.widget.*;
import android.widget.SeekBar.OnSeekBarChangeListener;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
import java.util.ArrayList;
import java.util.List;
// bir parça, bir parça konteyneri tarafından görüntülenen bir görünümdür
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// parça tarafından görüntülenen görünümün alanları
@ViewById(R.id.formulaireDropDownList)
Spinner dropDownList;
@ViewById(R.id.formulaireButtonValider)
Button buttonValider;
@ViewById(R.id.formulaireCheckBox1)
CheckBox checkBox1;
@ViewById(R.id.formulaireRadioGroup)
RadioGroup radioGroup;
@ViewById(R.id.formulaireSeekBar)
SeekBar seekBar;
@ViewById(R.id.formulaireEditText1)
EditText saisie;
@ViewById(R.id.formulaireSwitch1)
Switch switch1;
@ViewById(R.id.formulaireDatePicker1)
DatePicker datePicker1;
@ViewById(R.id.formulaireTimePicker1)
TimePicker timePicker1;
@ViewById(R.id.formulaireEditTextMultiLignes)
EditText multiLignes;
@ViewById(R.id.formulaireRadioButton1)
RadioButton radioButton1;
@ViewById(R.id.formulaireRadioButton2)
RadioButton radioButton2;
@ViewById(R.id.formulaireRadionButton3)
RadioButton radioButton3;
@ViewById(R.id.textViewSeekBarValue)
TextView seekBarValue;
// açılır liste
private List<String> list;
private ArrayAdapter<String> dataAdapter;
@AfterViews
void afterViews() {
// ilk düğme işaretlenir
radioButton1.setChecked(true);
// takvim
datePicker1.setCalendarViewShown(false);
// seekBar
seekBar.setMax(100);
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
public void onStopTrackingTouch(SeekBar seekBar) {
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
seekBarValue.setText(String.valueOf(progress));
}
});
// açılır liste
list = new ArrayList<>();
list.add("list 1");
list.add("list 2");
list.add("list 3");
}
@SuppressLint("DefaultLocale")
@Click(R.id.formulaireButtonValider)
protected void doValider() {
...
}
@Override
protected void updateFragment() {
// açılır menü adaptörünün başlatılması
dataAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_item, list);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
dropDownList.setAdapter(dataAdapter);
}
}
- 22-49. satırlar: XML formundaki tüm bileşenlerin referansları alınır [vue1] (18. satır);
- 58. satır: [setChecked] yöntemi, bir radyo düğmesini veya onay kutusunu işaretlemeye olanak tanır;
- satır 60: Varsayılan olarak [DatePicker] bileşeni hem bir tarih giriş kutusu hem de bir takvim görüntüler. Satır 60, takvimi kaldırır;
- satır 62: [SeekBar].setMax() işlevi, kaydırma çubuğunun maksimum değerini belirler. Minimum değer 0'dır;
- 63-74. satırlar: kaydırma çubuğunun olayları yönetilir. Kullanıcı tarafından yapılan her değişiklikte, 49. satırdaki [TextView]'te çubuğun değerini görüntülemek istenir;
- 71. satır: [progress] parametresi, ayar çubuğunun değerini temsil eder;
- satır 76-79: açılır listeyle ilişkilendireceğimiz [String] listesi;
- 90. satır: parçanın [updateFragment] yöntemi. Yürütüldüğünde, üst sınıftaki [activity] değişkeni başlatılmıştır;
- satır 92: [list] veri kaynağı, açılır listenin adaptörüne atanır;
- 93-94. satırlar: [dataAdapter] adaptörü, [dropDownList] açılır listeyle ilişkilendirilir;
- satır 84: [doValider] yöntemi, [Valider] düğmesine tıklanmasına atanır;
[doValider] yöntemi, kullanıcı tarafından girilen değerleri görüntülemek amacıyla kullanılır. Kod şöyledir:
@Click(R.id.formulaireButtonValider)
protected void doValider() {
// görüntülenecek mesaj listesi
List<String> messages = new ArrayList<>();
// onay kutusu
boolean isChecked = checkBox1.isChecked();
messages.add(String.format("CheckBox1 [checked=%s]", isChecked));
// radyo düğmeleri
int id = radioGroup.getCheckedRadioButtonId();
String radioGroupText = id == -1 ? "" : ((RadioButton) activity.findViewById(id)).getText().toString();
messages.add(String.format("RadioGroup [checked=%s]", radioGroupText));
// SeekBar
int progress = seekBar.getProgress();
messages.add(String.format("SeekBar [value=%d]", progress));
// giriş alanı
String texte = String.valueOf(saisie.getText());
messages.add(String.format("Saisie simple [value=%s]", texte));
// anahtar
boolean état = switch1.isChecked();
messages.add(String.format("Switch [value=%s]", état));
// tarih
int an = datePicker1.getYear();
int mois = datePicker1.getMonth() + 1;
int jour = datePicker1.getDayOfMonth();
messages.add(String.format("Date [%d, %d, %d]", jour, mois, an));
// çok satırlı metin
String lignes = String.valueOf(multiLignes.getText());
messages.add(String.format("Saisie multi-lignes [value=%s]", lignes));
// saat
int heure = timePicker1.getHour();
int minutes = timePicker1.getMinute();
messages.add(String.format("Heure [%d, %d]", heure, minutes));
// açılır liste
int position = dropDownList.getSelectedItemPosition();
String selectedItem = String.valueOf(dropDownList.getSelectedItem());
messages.add(String.format("DropDownList [position=%d, item=%s]", position, selectedItem));
// görüntüleme
doAfficher(messages);
}
- 4. satır: Girilen değerler bir mesaj listesinde toplanacaktır;
- 6. satır: [CheckBox].isCkecked() yöntemi, bir kutucuğun işaretli olup olmadığını belirlemeye yarar;
- 9. satır: [RadioGroup].getCheckedButtonId() yöntemi, işaretlenen radyo düğmesinin kimliğini veya hiçbirinin işaretlenmemiş olması durumunda -1 değerini döndürür;
- 10. satır: [activity.findViewById(id)] kodu, işaretli radyo düğmesini bulup etiketini almayı sağlar;
- 13. satır: [SeekBar].getProgress() yöntemi, bir kaydırma çubuğunun değerini almayı sağlar;
- 19. satır: [Switch].isChecked() yöntemi, bir anahtarın On (true) mı yoksa Off (false) mı olduğunu belirler;
- 22. satır: [DatePicker].getYear() yöntemi, bir [DatePicker] nesnesi ile seçilen yılı elde etmeyi sağlar;
- satır 23: [DatePicker].getMonth() yöntemi, [0,11] aralığı içinde bir [DatePicker] nesnesi ile seçilen ayı elde etmeyi sağlar;
- 24. satır: [DatePicker].getDayOfMonh() yöntemi, [1,31] aralığında [DatePicker] nesnesiyle seçilen ayın gününü elde etmeyi sağlar;
- satır 30: [TimePicker].getHour() yöntemi, seçilen saati bir [TimePicker] nesnesiyle elde etmeyi sağlar;
- satır 31: [TimePicker].getMinute() yöntemi, seçilen dakikaları bir [TimePicker] nesnesiyle elde etmeyi sağlar;
- satır 34: [Spinner].getSelectedItemPosition() yöntemi, bir açılır listeden seçilen öğenin konumunu almayı sağlar;
- satır 35: [Spinner].getSelectedItem() yöntemi, açılır listeden seçilen nesneyi elde etmeyi sağlar;
Girilen değerlerin listesini görüntüleyen [doAfficher] yöntemi şöyledir:
private void doAfficher(List<String> messages) {
// görüntülenecek metni oluşturuyoruz
StringBuilder texte = new StringBuilder();
for (String message : messages) {
texte.append(String.format("%s\n", message));
}
// görüntüleniyor
new AlertDialog.Builder(activité).setTitle("Valeurs saisies").setMessage(texte).setNeutralButton("Fermer", null).show();
}
- 1. satır: Yöntem, görüntülenecek mesajların bir listesini alır;
- satır 3-6: bu mesajlardan bir [StringBuilder] nesnesi oluşturulur. Dizeleri birleştirmek için [StringBuilder] türü, [String] türünden daha etkilidir;
- 8. satır: Bir iletişim kutusu, 3. satırdaki metni görüntüler:

1.18.5. Projenin çalıştırılması
Projeyi çalıştırın ve çeşitli giriş bileşenlerini test edin.
1.19. Örnek-18: Görünüm şablonunun kullanılması
1.19.1. Projenin oluşturulması
[Exemple-13] projesini kopyalayarak [Exemple-18] adlı yeni bir proje oluşturuyoruz.
![]() | ![]() |
1.19.2. Görünüm şablonu
Projedeki iki görünümü alıp bunları bir şablona dahil etmek istiyoruz:
![]() |

Her iki görünüm de aynı şekilde yapılandırılacaktır:
- [1]'te bir başlık;
- [2]'te, bağlantılar içerebilecek bir sol sütun;
- [3]'te bir altbilgi;
- [4]'te bir içerik.
Bu, etkinliğin temel görünümü olan [activity_main.xml]'i değiştirerek elde edilir;
![]() | ![]() |
[main] görünümünün XML kodu şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".activity.MainActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_marginTop="75dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_weight="0.1"
android:background="@color/lavenderblushh2">
<TextView
android:id="@+id/textViewHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:text="@string/txt_header"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/red"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_weight="0.8"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/left"
android:layout_width="100dp"
android:layout_height="match_parent"
android:background="@color/lightcyan2">
<TextView
android:id="@+id/txt_left"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical|center_horizontal"
android:text="@string/txt_left"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/red"/>
</LinearLayout>
<exemples.android.architecture.MyPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/floral_white"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</LinearLayout>
<LinearLayout
android:id="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_weight="0.1"
android:background="@color/wheat1">
<TextView
android:id="@+id/textViewBottom"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_vertical|center_horizontal"
android:text="@string/txt_bottom"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="@color/red"/>
</LinearLayout>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
- [1] başlığı 38-54. satırlardan elde edilir;
- sol şerit [2], 56-84. satırlardan elde edilir;
- [3] altbilgisi, 86-101. satırlardan elde edilir;
- [4] içeriği, 78-84. satırlardan elde edilir;
XML [main] görünümü, [res / values / colors.xml] ve [res / values / strings.xml] dosyalarında bulunan bilgileri kullanır:
![]() |
[colors.xml] dosyası şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="wheat">#FFEFD5</color>
<color name="floral_white">#FFFAF0</color>
<color name="lavenderblushh2">#EEE0E5</color>
<color name="lightcyan2">#D1EEEE</color>
<color name="wheat1">#FFE7BA</color>
</resources>
ve [strings.xml] dosyası şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">exemple-12</string>
<string name="action_settings">Settings</string>
<string name="titre_vue1">Vue n° 1</string>
<string name="textView_nom">Quel est votre nom :</string>
<string name="btn_Valider">Validez</string>
<string name="btn_vue2">Vue n° 2</string>
<string name="titre_vue2">Vue n° 2</string>
<string name="btn_vue1">Vue n° 1</string>
<string name="textView_bonjour">"Bonjour "</string>
<string name="txt_header">Header</string>
<string name="txt_left">Left</string>
<string name="txt_bottom">Bottom</string>
</resources>
Bu proje için bir yürütme ortamı oluşturun ve projeyi çalıştırın.
1.20. Örnek-19: [ListView] bileşeni
[ListView] bileşeni, bir listenin her bir öğesi için belirli bir görünümü tekrarlamaya olanak tanır. Tekrarlanan görünüm, basit bir karakter dizisinden listenin her bir öğesi için bilgi girilmesine olanak tanıyan bir görünüme kadar herhangi bir karmaşıklıkta olabilir. Aşağıdaki [ListView] bileşenini oluşturacağız:

Listenin her bir görünümü üç bileşenden oluşur:
- bir [TextView] bilgi bileşeni;
- bir [CheckBox];
- tıklanabilir bir [TextView];
1.20.1. Projenin Oluşturulması
[Exemple-18] projesini kopyalayarak [Exemple-19] adlı yeni bir proje oluşturuyoruz.
![]() | ![]() |
![]() |
Projeyi [3]'te belirtildiği şekilde geliştireceğiz.
1.20.2. Oturum
![]() |
Oturum, etkinlik ve parçalar arasında paylaşılan verileri saklar:
package exemples.android.architecture;
import org.androidannotations.annotations.EBean;
import java.util.ArrayList;
import java.util.List;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
// bir veri listesi
private List<Data> liste=new ArrayList<>();
// alıcı ve ayarlayıcılar
...
}
- 11. satır: her iki görünüm tarafından kullanılan veri listesi;
[Data] sınıfı şu şekildedir:
package exemples.android.architecture;
public class Data {
// veriler
private String texte;
private boolean isChecked;
// oluşturucu
public Data(String texte, boolean isCkecked) {
this.texte = texte;
this.isChecked = isCkecked;
}
// alıcı ve ayarlayıcılar
...
}
- 6. satır: listenin her bir öğesinin ilk [TextView]'sini dolduracak metin;
- 7. satır: Listenin her bir öğesindeki [checkBox] öğesini işaretlemek veya işaretlememek için kullanılacak boole değeri;
1.20.3. [MainActivity] etkinliği
[@AfterInject] yönteminin kodu şu şekilde olur:
// oturum enjeksiyonu
@Bean(Session.class)
protected Session session;
...
@AfterInject
protected void afterInject() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterInject");
}
// bir veri listesi oluşturulur
List<Data> liste = session.getListe();
for (int i = 0; i < 20; i++) {
liste.add(new Data("Texte n° " + i, false));
}
}
- 12-15. satırlar: oturumda bulunan veri listesinin başlatılması;
1.20.4. Başlangıçtaki [Vue1] görünümü
![]() | ![]() |
XML [vue1.xml] görünümü, yukarıdaki [1] alanını görüntüler. Kodu şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView_titre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:text="@string/titre_vue1"
android:textSize="50sp" />
<Button
android:id="@+id/button_vue2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView_titre"
android:layout_below="@+id/textView_titre"
android:layout_marginTop="50dp"
android:text="@string/btn_vue2" />
<ListView
android:id="@+id/listView1"
android:layout_width="600dp"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_below="@+id/button_vue2"
android:layout_marginLeft="30dp"
android:layout_marginTop="50dp" >
</ListView>
</RelativeLayout>
- 7-16. satırlar: [TextView] [2] bileşeni;
- 27-35. satırlar: [ListView] ve [4] bileşenleri;
- satır 18-25: [Button] ve [3] bileşenleri;
1.20.5. [ListView] tarafından tekrarlanan görünüm
![]() |
[ListView] tarafından tekrarlanan görünüm, aşağıdaki [list_data] görünümüdür:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/wheat" >
<TextView
android:id="@+id/txt_Libellé"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:text="@string/txt_dummy" />
<CheckBox
android:id="@+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/txt_Libellé"
android:layout_marginLeft="37dp"
android:layout_toRightOf="@+id/txt_Libellé"
android:text="@string/txt_dummy" />
<TextView
android:id="@+id/textViewRetirer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/txt_Libellé"
android:layout_alignBottom="@+id/txt_Libellé"
android:layout_marginLeft="68dp"
android:layout_toRightOf="@+id/checkBox1"
android:text="@string/txt_retirer"
android:textColor="@color/blue"
android:textSize="20sp" />
</RelativeLayout>
- 8-14. satırlar: [TextView] [1] bileşeni;
- 16-23. satırlar: [CheckBox] ve [2] bileşenleri;
- 25-35. satırlar: [TextView] ve [3] bileşenleri;
1.20.6. [Vue1Fragment] parçası
![]() |
[Vue1Fragment] parçası, XML ve [vue1] görünümlerini yönetir. Kodu şöyledir:
package exemples.android.fragments;
import android.view.View;
import android.widget.ListView;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import exemples.android.architecture.Data;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
import java.util.List;
@EFragment(R.layout.vue1)
public class Vue1Fragment extends AbstractFragment {
// parça tarafından görüntülenen görünümün alanları
@ViewById(R.id.listView1)
protected ListView listView;
// liste adaptörü
private ListAdapter adapter;
// başlatma tamamlandı
private boolean initDone = false;
@AfterViews
void afterViews() {
// bellek
afterViewsDone = true;
}
@Click(R.id.button_vue2)
void navigateToView2() {
// 2. görünüme geçiliyor
mainActivity.navigateToView(1);
}
public void doRetirer(int position) {
...
}
@Override
protected void updateFragment() {
if (!initDone) {
// [ListView]'e veriler atanıyor
adapter = new ListAdapter(activity, R.layout.list_data, session.getListe(), this);
initDone = true;
}
// parçanın (yeniden) oluşturulduğu durum - bu durumda ListView'i adaptörüne yeniden bağlamak gerekir
listView.setAdapter(adapter);
// diğer parçaların veri kaynağını değiştirdiği durumlar - bu durumda ListView'i yenilemek gerekir
adapter.notifyDataSetChanged();
}
}
- 15. satır: XML [vue1] görünümü parçaya atanmıştır;
- 26-30. satırlar: [@AfterViews] yöntemi hiçbir işlem yapmaz. Ancak bu yöntem, [afterViewsDone] değişkenini true değerine ayarlamak için gereklidir; çünkü bu değişken, üst sınıf olan [AbstractFragment] tarafından kullanılmaktadır;
- satır 42-53: Parça her görünür hale geldiğinde çağrılan [updateFragment] yöntemi. Yöntem burada, fragmanın görüntülenen fragmanın komşuluğundan çıkıp yaşam döngüsünü sıfırlayabileceği varsayımıyla yazılmıştır. Burada durum böyle değildir, ancak uygulamada komşuluk değeri 1 olan 3 fragman olsaydı durum böyle olurdu;
- 44. satır: [ListView] adaptörünün yalnızca bir kez başlatılması gerekir;
- 46. satır: Bu [ListView]'e, [ListAdapter] türünde bir adaptör atanır. Şimdi bu sınıfı oluşturacağız. Bu sınıf, daha önce bir [ListView]'e veri atamak için kullandığımız [ArrayAdapter] sınıfından türetilmiştir. [ListAdapter]'in oluşturucusuna çeşitli bilgiler aktarıyoruz:
- geçerli etkinliğe bir referans,
- listenin her bir öğesi için örneklenecek görünümün kimliği,
- listeyi beslemek için bir veri kaynağı,
- parçaya ilişkin bir referans. Bu referans, 38. satırdaki [doRetirer] yöntemi aracılığıyla [ListView]'teki bir [Retirer] bağlantısına yapılan tıklamayı yönetmek için kullanılacaktır;
- 50. satır: Adaptör, [ListView] ile ilişkilendirilir. Aynı zamanda, [listes] veri kaynağı da [ListView] ile ilişkilendirilir. Bu işlem burada 1 numaralı görünüm her görüntülendiğinde yapılacaktır. Oysa gerçekte bu işlemin yalnızca [@AfterViews] yöntemi yürütüldüğünde yapılması gerekir. Burada komut çok sık yürütülmektedir. [@AfterViews] yönteminin az önce yürütüldüğünü ve dolayısıyla [ListView]'in adaptatörüne yeniden eşleştirilmesi gerektiğini bize bildirecek bir boole değişkenine ihtiyaç duyulmaktadır;
- 52. satır: [ListView] yenilenir. Bu örnekte bunun bir anlamı yoktur, çünkü yalnızca 1 numaralı görünüm [ListView]'in veri kaynağını değiştirebilir. Daha genel bir duruma geçelim; burada 2 numaralı görünüm de [ListView]'in veri kaynağını değiştirebilir. Bu tür örneklerle bu belgenin ilerleyen bölümlerinde karşılaşacağız. Bu durumda, 2 numaralı görünümden 1 numaralı görünüme geçildiğinde, 1 numaralı görünümdeki [ListView]'in yenilenmesi gerekir;
1.20.7. [ListView]'in [ListAdapter] adaptörü
![]() |
[ListAdapter] sınıfı
- , [ListView]'in veri kaynağını yapılandırır;
- [ListView]'in çeşitli öğelerinin görüntülenmesini yönetir;
- bu öğelerin olaylarını yönetir;
Kodu şu şekildedir:
package exemples.android.fragments;
import java.util.List;
...
public class ListAdapter extends ArrayAdapter<Data> {
// çalışma bağlamı
private Context context;
// liste satırının görüntüleme düzeninin kimliği
private int layoutResourceId;
// liste verileri
private List<Data> data;
// [ListView] öğesini görüntüleyen parça
private Vue1Fragment fragment;
// adaptör
final ListAdapter adapter = this;
// üretici
public ListAdapter(Context context, int layoutResourceId, List<Data> data, Vue1Fragment fragment) {
super(context, layoutResourceId, data);
// bilgiler kaydedilir
this.context = context;
this.layoutResourceId = layoutResourceId;
this.data = data;
this.fragment = fragment;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
...
}
}
- 5. satır: [ListAdapter] sınıfı, [ArrayAdapter] sınıfını genişletir;
- 19. satır: oluşturucu;
- 20. satır: ilk üç parametre ile üst sınıf [ArrayAdapter]'in oluşturucusunu çağırmayı unutmayın;
- 22-25. satırlar: oluşturucunun bilgileri saklanır;
- 29. satır: [getView] yöntemi, [ListView] tarafından [position] numaralı öğenin görünümünü oluşturmak üzere tekrar tekrar çağrılacaktır. Oluşturulan [View] sonucu, oluşturulan görünüme bir referanstır.
[getView] yönteminin kodu şöyledir:
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// ListView'in geçerli satırı oluşturuluyor
View row = ((Activity) context).getLayoutInflater().inflate(layoutResourceId, parent, false);
// metin
TextView textView = (TextView) row.findViewById(R.id.txt_Libellé);
textView.setText(data.get(position).getTexte());
// onay kutusu
CheckBox checkBox = (CheckBox) row.findViewById(R.id.checkBox1);
checkBox.setChecked(data.get(position).isChecked());
// bağlantı [Retirer]
TextView txtRetirer = (TextView) row.findViewById(R.id.textViewRetirer);
txtRetirer.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
fragment.doRetirer(position);
}
});
// on gère le clic sur la case à cocher
checkBox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
data.get(position).setChecked(isChecked);
}
});
// satırı oluşturma
return row;
}
- 2. satır: Yöntem üç parametre alır. Biz sadece ilkini kullanacağız;
- 4. satır: [position] numaralı öğenin görünümünü oluşturuyoruz. Bu, yapıcıya ikinci parametre olarak ID'si aktarılan [list_data] görünümüdür. Ardından, az önce örneklendirdiğimiz görünümün bileşenlerinin referanslarını alıyoruz;
- 6. satır: 1 numaralı [TextView]'in referansını alıyoruz;
- 7. satır: Ona, oluşturucuya üçüncü parametre olarak geçirilen veri kaynağından gelen bir metin atanır;
- 9. satır: 2 numaralı [CheckBox]'in referansını alıyoruz;
- 10. satır: [ListView] veri kaynağından alınan bir değerle işaretlenir veya işaretlenmez;
- 12. satır: [TextView] n° 3 referansı alınır;
- satır 13-18: [Retirer] bağlantısına yapılan tıklama işlenir;
- 16. satır: Bu tıklamayı yönetecek olan yöntem [Vue1Fragment].doRetirer'tir. Aslında bu olayı, [ListView]'i görüntüleyen parça tarafından yönetilmesi daha mantıklı görünmektedir. Bu fragman, [ListAdapter] sınıfında bulunmayan bir genel bakışa sahiptir. [Vue1Fragment] fragmanının referansı, sınıfın oluşturucusuna dördüncü parametre olarak aktarılmıştı;
- 20-25. satırlar: onay kutusuna yapılan tıklama işlenir. Onay kutusu üzerinde yapılan işlem, kutunun görüntülediği veriye yansıtılır. Bunun nedeni şudur: [ListView], öğelerin yalnızca bir kısmını görüntüleyen bir listedir. Dolayısıyla, listedeki bir öğe bazen gizli, bazen de görünür durumdadır. i numaralı öğenin görüntülenmesi gerektiğinde, yukarıdaki 2. satırdaki [getView] yöntemi, i numaralı konum için çağrılır. 10. satır, onay kutusunun bağlı olduğu veriden yola çıkarak durumunu yeniden hesaplayacaktır. Bu nedenle, bu öğenin zaman içinde onay kutusunun durumunu kaydetmesi gerekir;
1.20.8. Listeden bir öğeyi kaldırma
[Retirer] bağlantısına yapılan tıklama, [Vue1Fragment] parçasında aşağıdaki [doRetirer] yöntemi ile yönetilir:
public void doRetirer(int position) {
// listeden [position] numaralı öğeyi kaldırıyoruz
List<Data> liste = mainActivity.getListe();
liste.remove(position);
// geri dönmek için kaydırma konumunu kaydediyoruz
// okuma
// [http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview]
// 1. öğenin konumu tamamen görünür mü, değil mi
int firstPosition = listView.getFirstVisiblePosition();
// bu öğenin ListView'in üst kenarına göre Y eksenindeki ofseti
// gizlenmiş olabilecek kısmın yüksekliğini ölçer
View v = listView.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// [ListView] yenilenir
adapter.notifyDataSetChanged();
// ListView'in doğru konumuna geçilir
listView.setSelectionFromTop(firstPosition, top);
}
- 1. satır: Tıklanan [Retirer] bağlantısının [ListView] içindeki konumu alınır;
- 3. satır: Veri listesi alınır;
- 4. satır: [position] numaralı öğe kaldırılır;
- 15. satır: [ListView] yenilenir. Aksi takdirde görsel olarak hiçbir değişiklik olmaz.
- satır 5-13, 17: oldukça karmaşık bir işlem. Bu işlem yapılmazsa şu durum ortaya çıkar:
- [ListView], veri listesinin 15-18. satırlarını görüntüler,
- 16. satır silinir,
- yukarıdaki 15. satır onu tamamen sıfırlar ve [ListView], veri listesinin 0-3. satırlarını görüntüler;
Yukarıdaki satırlarla silme işlemi gerçekleştirilir ve [ListView], silinen satırı takip eden satırda kalır.
1.20.9. XML görünümü [Vue2]
![]() | ![]() |
Görünümün XML kodu şöyledir:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView_titre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="30dp"
android:layout_marginTop="20dp"
android:text="@string/titre_vue2"
android:textSize="50sp" />
<Button
android:id="@+id/button_vue1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textViewResultats"
android:layout_marginTop="25dp"
android:layout_alignLeft="@+id/textView_titre"
android:text="@string/btn_vue1" />
<TextView
android:id="@+id/textViewResultats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView_titre"
android:layout_marginTop="50dp"
android:layout_alignLeft="@+id/textView_titre"
android:text="" />
</RelativeLayout>
- 6-15. satırlar: 1 numaralı [TextView] bileşeni;
- 26-33. satırlar: 2 numaralı [TextView] bileşeni;
- 17-24. satırlar: 3 numaralı [Button] bileşeni;
1.20.10. [Vue2Fragment] parçası
![]() | 123 ![]() |
[Vue2Fragment] parçası, XML ve [vue2] görünümlerini yönetir. Kodu şöyledir:
package exemples.android.fragments;
import android.widget.TextView;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import exemples.android.architecture.Data;
import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Click;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.ViewById;
@EFragment(R.layout.vue2)
public class Vue2Fragment extends AbstractFragment {
// görünüm alanları
@ViewById(R.id.textViewResultats)
TextView txtResultats;
@AfterViews
void initFragment(){
// bellek
afterViewsDone=true;
}
@Click(R.id.button_vue1)
void navigateToView1() {
// Görünüm 1'e geçiyoruz
mainActivity.navigateToView(0);
}
@Override
protected void updateFragment() {
// Görünüm 1'de seçilen liste öğelerini görüntülüyoruz
StringBuilder texte = new StringBuilder("Eléments sélectionnés [");
for (Data data : mainActivity.getListe()) {
if (data.isChecked()) {
texte.append(String.format("(%s)", data.getTexte()));
}
}
texte.append("]");
txtResultats.setText(texte);
}
}
Önemli kod, 32. satırdaki [updateFragment] yönteminde yer almaktadır:
- 34. satır: [TextView] no. 2'de görüntülenecek metin hesaplanır;
- 35-39. satırlar: [ListView] tarafından görüntülenen veri listesi taranır. Bu liste etkinlikte depolanır;
- satır 36: i numaralı veri işaretlenmişse, ilgili etiket bir [StringBuilder] türüne eklenir;
- satır 41: [TextView], hesaplanan metni görüntüler;
1.20.11. Çalıştırma
Bu proje için bir yürütme yapılandırması oluşturun ve bunu çalıştırın.
1.20.12. İyileştirme
Önceki örnekte, List<Data> veri kaynağı kullandık; burada [Data] sınıfı şu şekildeydi:
package exemples.android.fragments;
public class Data {
// veriler
private String texte;
private boolean isChecked;
// üretici
public Data(String texte, boolean isCkecked) {
this.texte = texte;
this.isChecked = isCkecked;
}
...
}
7. satırda, [ListView] öğelerinin onay kutularını yönetmek için bir boole değeri kullanmıştık. Çoğu zaman [ListView], veri kaynağındaki öğenin bu kutucuğa karşılık gelen bir boole alanı olmaksızın, bir kutucuğu işaretleyerek seçilebilen verileri görüntülemelidir. Bu durumda şu şekilde ilerleyebiliriz:
[Data] sınıfı şu şekilde olur:
package exemples.android.fragments;
public class Data {
// veriler
private String texte;
// yapıcı
public Data(String texte) {
this.texte = texte;
}
// alıcı ve ayarlayıcılar
...
}
Öncekinden türetilen [CheckedData] sınıfı oluşturulur:
package exemples.android.fragments;
public class CheckedData extends Data {
// işaretli öğe
private boolean isChecked;
// yapıcı
public CheckedData(String text, boolean isChecked) {
// üst sınıf
super(text);
// yerel
this.isChecked = isChecked;
}
// getter ve setter'lar
...
}
Ardından kodun her yerinde (MainActivity, ListAdapter, Vue1Fragment, Vue2Fragment) [Data] türünü [CheckedData] türüyle değiştirmek yeterlidir. Örneğin, [MainActivity]'te:
@AfterInject
protected void afterInject() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d("MainActivity", "afterInject");
}
// bir veri listesi oluşturulur
List<CheckedData> liste = session.getListe();
for (int i = 0; i < 20; i++) {
liste.add(new CheckedData("Texte n° " + i, false));
}
}
Bu sürümün projesi size [Exemple-19B] adı altında sunulmaktadır.
1.21. Örnek-20: Menü kullanma
1.21.1. Projenin oluşturulması
[Exemple-19B] projesini [Exemple-20] projesine kopyalıyoruz:
![]() | ![]() |
![]() | 3 ![]() |
1 ve 2 numaralı görünümlerdeki düğmeleri kaldırıp, bunların yerine [1-2] menü seçeneklerini ekleyeceğiz.
1.21.2. Menülerin tanımı XML
![]() |
[res / menu / menu_vue1] dosyası, 1 numaralı görünümün menüsünü tanımlar:
<menu 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"
tools:context=".activity.MainActivity">
<item
android:id="@+id/menuOptions"
app:showAsAction="ifRoom"
android:title="@string/menuOptions">
<menu>
<item
android:id="@+id/actionCacherMontrerTout"
android:title="@string/actionCacherMontrerTout"/>
<item
android:id="@+id/actionCacherMontrerActions"
android:title="@string/actionCacherMontrerActions"/>
<item
android:id="@+id/actionCacherMontrerActionsValider"
android:title="@string/actionCacherMontrerActionsValider"/>
</menu>
</item>
<item
android:id="@+id/menuActions"
app:showAsAction="ifRoom"
android:title="@string/menuActions">
<menu>
<item
android:id="@+id/actionValider"
android:title="@string/actionValider"/>
</menu>
</item>
<item
android:id="@+id/menuNavigation"
app:showAsAction="ifRoom"
android:title="@string/menuNavigation">
<menu>
<item
android:id="@+id/navigationVue2"
android:title="@string/navigationVue2"/>
</menu>
</item>
</menu>
Menü öğeleri aşağıdaki bilgilerle tanımlanır:
- android:id: öğenin kimliği;
- android:title: öğenin başlığı;
- app:showsAsAction: menü öğesinin etkinliğin eylem çubuğuna yerleştirilip yerleştirilemeyeceğini belirtir. [ifRoom], öğe için yer varsa öğenin eylem çubuğuna yerleştirilmesi gerektiğini belirtir;
- bir menü seçeneği kendisi de bir alt menü olabilir (<menu> etiketi, 25. ve 29. satırlar);
[res / menu / menu_vue2] dosyası, 2 numaralı görünümün menüsünü tanımlar:
<menu 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"
tools:context=".activity.MainActivity">
<item
android:id="@+id/menuNavigation"
app:showAsAction="ifRoom"
android:title="@string/menuNavigation">
<menu>
<item
android:id="@+id/navigationVue1"
android:title="@string/navigationVue1"/>
</menu>
</item>
</menu>
1.21.3. Soyut [AbstractFragment] sınıfında menü yönetimi
Menü yönetimini, her iki görünümün üst sınıfı olan [AbstractFragment] sınıfında faktörlere ayıracağız:
package exemples.android.architecture;
import android.app.Activity;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractFragment extends Fragment {
// alt sınıflardan erişilebilen veriler
final protected boolean isDebugEnabled = IMainActivity.IS_DEBUG_ENABLED;
protected String className;
// etkinlik
protected IMainActivity mainActivity;
protected Activity activity;
// oturum
protected Session session;
// menü
private Menu menu;
private int[] menuOptions;
private boolean initDone;
// yapıcı
public AbstractFragment() {
// başlatma
className = getClass().getSimpleName();
// günlük
if (isDebugEnabled) {
Log.d("AbstractFragment", String.format("constructor %s", className));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// bellek
this.menu = menu;
// günlük
if (isDebugEnabled) {
Log.d(className, String.format("création menu en cours"));
}
// henüz yapılmadıysa menüdeki # seçenekleri alınır
if (!initDone) {
// menüdeki # seçenekleri alınır
List<Integer> menuOptionsIds = new ArrayList<>();
getMenuOptions(menu, menuOptionsIds);
// seçenek listesini bir diziye aktarılır
menuOptions = new int[menuOptionsIds.size()];
for (int i = 0; i < menuOptions.length; i++) {
menuOptions[i] = menuOptionsIds.get(i);
}
// işlem
this.activity = getActivity();
this.mainActivity = (IMainActivity) activity;
this.session = this.mainActivity.getSession();
// bellek
initDone = true;
}
// alt parçaya kendini ayarlamasını istiyoruz
updateFragment();
}
private void getMenuOptions(Menu menu, List<Integer> menuOptionsIds) {
...
}
// menü seçeneklerini görüntüleme -----------------------------------
protected void setAllMenuOptions(boolean isVisible) {
....
}
protected void setMenuOptions(MenuItemState[] menuItemStates) {
...
}
// alt sınıfı güncelle
protected abstract void updateFragment();
}
- 42. satır: Günlükler, [onCreateOptionsMenu] yönteminin parça her görüntülendiğinde çağrıldığını gösteriyor. Bu yöntem çok geç çağrılıyor, özellikle de [updateFragment] yöntemi çağrıldıktan sonra. Bu durum, yöntemin parçayı güncellemek için kullanılabileceğini düşündürüyor. İşte burada da bunu yapacağız (63. satır);
- 42. satır: Yöntemin iki parametresi vardır:
- [menu]: boş bir menüdür;
- [inflater]: menüyü başlangıç tanımından oluşturmaya yarayan bir araçtır. Burada bu seçeneği kullanmayacağız çünkü bunu bizim için yapacak olan AA ek açıklaması kullanacağız;
- 44. satır: menüyü saklıyoruz. Buna daha sonra ihtiyacımız olacak;
- 52-53. satırlar: Menüdeki tüm öğelerin kimliklerini 28. satırdaki diziye kaydediyoruz;
- 55-57. satırlar: Günlük kayıtları, [onCreateOptionsMenu] yöntemi çağrıldığında, [Fragment.getActivity()] yönteminin fragmana bağlı etkinliği döndürdüğünü göstermektedir;
- satır 55: etkinliği Android [Activity] sınıfının bir örneği olarak saklıyoruz;
- satır 56: aktiviteyi [IMainActivity] arayüzünün bir örneği olarak saklıyoruz;
- satır 57: oturumu saklıyoruz;
- satır 59: sınıfın başlatılmasının, yeniden başlatılması gerekmemesi için yapıldığını not ediyoruz (satır 50);
- 63. satır: alt parçanın kendini güncellemesini istiyoruz. Bu mümkündür çünkü parça hem görünürdür hem de adresindeki görünümü ve menüsüyle ilişkilendirilmiştir;
Bir menüdeki öğelerin tanımlayıcılarını elde etmeyi sağlayan [getMenuOptions] yöntemi şöyledir:
private void getMenuOptions(Menu menu, List<Integer> menuOptionsIds) {
// menüdeki tüm öğeler taranıyor
for (int i = 0; i < menu.size(); i++) {
// i numaralı öğe
MenuItem menuItem = menu.getItem(i);
menuOptionsIds.add(menuItem.getItemId());
// i numaralı öğe bir alt menü ise, işleme yeniden başlanır
if (menuItem.hasSubMenu()) {
// rekürsiflik
getMenuOptions(menuItem.getSubMenu(), menuOptionsIds);
}
}
}
[setAllMenuOptions] yöntemi, menüdeki tüm seçenekleri gizlemeye/göstermeye olanak tanır;
protected void setAllMenuOptions(boolean isVisible) {
// menüdeki tüm seçenekleri güncelle
for (int menuItemId : menuOptions) {
menu.findItem(menuItemId).setVisible(isVisible);
}
}
[setMenuOptions] yöntemi, menüdeki belirli seçenekleri gizlemeyi veya göstermeyi sağlar;
protected void setMenuOptions(MenuItemState[] menuItemStates) {
// menünün belirli seçeneklerini güncelle
for (MenuItemState menuItemState : menuItemStates) {
menu.findItem(menuItemState.getMenuItemId()).setVisible(menuItemState.isVisible());
}
}
[MenuItemState] sınıfı şu şekildedir:
![]() |
package exemples.android.architecture;
public class MenuItemState {
// menü seçeneğinin kimliği
private int menuItemId;
// seçeneğin görünürlüğü
private boolean isVisible;
// yapıcılar
public MenuItemState() {
}
public MenuItemState(int menuItemId, boolean isVisible) {
this.menuItemId = menuItemId;
this.isVisible = isVisible;
}
// alıcı ve ayarlayıcılar
...
}
1.21.4. [Vue1Fragment] parçasında menü yönetimi
[Vue1Fragment] sınıfı şu şekilde olur:
@EFragment(R.layout.vue1)
@OptionsMenu(R.menu.menu_vue1)
public class Vue1Fragment extends AbstractFragment {
...
@OptionsItem(R.id.navigationVue2)
void navigateToView2() {
// 2. görünüme geçilir
mainActivity.navigateToView(1);
}
@OptionsItem(R.id.actionValider)
void valider() {
// bir mesaj görüntülenir
Toast.makeText(activity, "Valider", Toast.LENGTH_SHORT).show();
}
private boolean actionCacherMontrerTout = true;
@OptionsItem(R.id.actionCacherMontrerTout)
void cacherMontrerTout() {
// durum değiştirme
actionCacherMontrerTout = !actionCacherMontrerTout;
setMenuOptions(new MenuItemState[]{new MenuItemState(R.id.menuNavigation, actionCacherMontrerTout), new MenuItemState(R.id.menuActions, actionCacherMontrerTout)});
}
private boolean actionCacherMontrerActions = true;
@OptionsItem(R.id.actionCacherMontrerActions)
void actionCacherMontrerActions() {
// durum değiştiriliyor
actionCacherMontrerActions = !actionCacherMontrerActions;
setMenuOptions(new MenuItemState[]{new MenuItemState(R.id.menuActions, actionCacherMontrerActions)});
}
private boolean actionCacherMontrerActionsValider = true;
@OptionsItem(R.id.actionCacherMontrerActionsValider)
void actionCacherMontrerActionsValider() {
// durum değiştiriliyor
actionCacherMontrerActionsValider = !actionCacherMontrerActionsValider;
setMenuOptions(new MenuItemState[]{new MenuItemState(R.id.menuActions, true), new MenuItemState(R.id.actionValider, actionCacherMontrerActionsValider)});
}
...
@Override
protected void updateFragment() {
....
// menü güncelleniyor
//setMenuOptions(...)
}
}
- 2. satır: [res / menu / menu_vue1.xml] menüsü parçaya atanır;
- 48. satır: [updateFragment] yöntemi çalıştırıldığında, menü de parçanın yeni durumunu yansıtacak şekilde güncellenebilir;
- 7. satır: [@OptionsItem(R.id.navigationVue2)] açıklaması, [Navigation / Vue 2] menü seçeneğine tıklandığında çalıştırılması gereken yöntemi belirtir;
- 19-25. satırlar: menünün bir dalını gizlemek için, o dalın kök seçeneğini gizlemek yeterlidir;
- 24. satır: [menuNavigation, menuActions] kök seçenekleri gösterilir/gizlenir;
- satır 40: Bir menü dalındaki bir seçeneği göstermek için, sadece o seçeneği değil, aynı zamanda yaprak seçeneğinden menünün köküne doğru yukarı doğru ilerlerken karşılaşılan tüm seçenekleri de göstermek gerekir;
1.21.5. [Vue2Fragment] parçasında menü yönetimi
Benzer bir kod, 2 numaralı görünüm parçasına da rastlanır:
package exemples.android.fragments;
import android.widget.TextView;
import exemples.android.R;
import exemples.android.architecture.AbstractFragment;
import exemples.android.models.CheckedData;
import org.androidannotations.annotations.*;
@EFragment(R.layout.vue2)
@OptionsMenu(R.menu.menu_vue2)
public class Vue2Fragment extends AbstractFragment {
// görünümdeki alanlar
@ViewById(R.id.textViewResultats)
TextView txtResultats;
@OptionsItem(R.id.navigationVue1)
void navigateToView1() {
// 1 numaralı görünüme geçiliyor
mainActivity.navigateToView(0);
}
@Override
protected void updateFragment() {
// Görünüm 1'de seçilen liste öğelerini görüntülenir
StringBuilder texte = new StringBuilder("Eléments sélectionnés [");
for (CheckedData data : session.getListe()) {
if (data.isChecked()) {
texte.append(String.format("(%s)", data.getTexte()));
}
}
texte.append("]");
txtResultats.setText(texte);
// menü güncelleniyor
// setMenuOptions(...)
}
}
- 35. satır: [Navigation / Vue 1] seçeneği görüntülenir;
- 17-20. satırlar: [Navigation / Vue1] seçeneğine tıklandığında, [navigateToView1] yöntemi çağrılır;
1.21.6. Çalıştırma
Bu proje için bir yürütme bağlamı oluşturun ve projeyi çalıştırın.
1.22. Örnek-21: [AbstractFragment] soyut sınıfının yeniden yapılandırılması
Önceki örnek bize, parçanın bir menüsü olduğunda, [onCreateOptionsMenu] yönteminin parçanın güncellenmesini istemek için uygun bir yer olduğunu göstermiştir:
- bu yöntem, fragman görüntülenecekken tam olarak bir kez çağrılır;
- çağrıldığında, parçanın etkinliği, görünümü ve menüsüyle olan ilişkilendirmeleri yapılır;
Bunu göstermek için, komşuluk ilişkilerini değiştirebileceğimiz çok sayıda parçaya sahip olan Örnek 12'yi ele alalım. Bu örnekte, parçaların menüsü yoktu. Onlara boş bir menü atayacağız.
1.22.1. Projenin oluşturulması
[Exemple-12] projesini [Exemple-21] projesinde çoğaltıyoruz:
![]() | ![]() |
1.22.2. Parçaların menüsü
![]() |
Parçalar için eklenen menü boş olacaktır:
<menu 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"
tools:context="exemples.android.MainActivity">
</menu>
Burada anlaşılması gereken nokta, etkinliğin zaten kendi menüsüne sahip olduğudur: [menu_main] :
<menu 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"
tools:context="exemples.android.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment1"
android:title="@string/fragment1"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment2"
android:title="@string/fragment2"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment3"
android:title="@string/fragment3"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/fragment4"
android:title="@string/fragment4"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
Bir etkinliğin halihazırda bir menüsü varsa, parçacıklarla ilişkili menü etkinliğin menüsüne eklenir: böylece iki menünün seçenekleri de mevcut olur. Burada, parçacık menüsü boş olacaktır. Dolayısıyla sadece etkinliğin menüsü görünecektir.
1.22.3. Parçalar
![]() |
Önceki örnekteki (bkz. paragraf 1.21.3) [AbstractFragment] soyut sınıfını kullanıyoruz. [menu_fragment] menüsünü şu iki parçaya ilişkilendiriyoruz:
@EFragment(R.layout.fragment_main)
@OptionsMenu(R.menu.menu_fragment)
public class PlaceholderFragment extends AbstractFragment {
@EFragment(R.layout.vue1)
@OptionsMenu(R.menu.menu_fragment)
public class Vue1Fragment extends AbstractFragment {
[PlaceholderFragment] ve [Vue1Fragment] parçalarında, eski [AbstractFragment] soyut sınıfına yapılan referansları kaldırıyoruz.
1.22.4. Çalıştırma
Uygulamayı çalıştırın ve çalıştığını kontrol edin. Günlükleri takip ederek [AbstractFragment] sınıfındaki [onCreateOptionsMenu] yönteminin ne zaman yürütüldüğünü görün. Artık bu yöntem, alt parçaların [updateFragment] yöntemini çağırıyor.
1.23. Örnek-22: Etkinlik ve parçaların durumunun yedeklenmesi / geri yüklenmesi
1.23.1. Sorun
Burada Android cihazının döndürülmesi (dikey <--> yatay) sorununu ele alıyoruz. Bunu açıklamak için önceki Örnek 21’i tekrar ele alıyoruz:

[1] cihazını döndürdüğümüzde, aşağıdaki yeni görünüm elde edilir:

Görüldüğü gibi:
- [1]'te, [Fragment n° 3] sekmesi kaybolmuştur;
- [2]'te, görüntülenen metin 3 numaralı parçaya ait olmakla birlikte ziyaret sayacı yanlış;
Bu dönüşüm sırasında günlükler şu şekildedir:
- 1. satır: etkinliğin tamamen yeniden oluşturulduğu görülüyor;
- satır 3-7: etkinlik tarafından yönetilen beş parça için de durum aynıdır;
- 21. satır: 3 numaralı parça görüntülenecek. Artırma işleminden önce ziyaret numarasının 0 olduğu görülüyor;
Dönüş işleminden sonra elde edilen sonuç şu şekilde açıklanabilir:
- [MainActivity] sınıfı başlangıçta, [Vue 1] adlı tek bir sekme içeren bir sekme çubuğu oluşturur. Görüntülenen sekme budur;
- cihaz döndürüldükten sonra, [mViewPager] sayfa yöneticisi aynı parçayı, yani burada 3 numaralı parçayı yeniden görüntüler. Burada, sekmelerin ve parçaların farklı kavramlar olduğunu ve farklı yaşam döngülerine sahip olduklarını unutmamak gerekir. 3 numaralı parçanın [updateFragment] yöntemi çalıştırılacaktır:
public void updateFragment() {
// günlük
if (isDebugEnabled) {
Log.d("PlaceholderFragment", String.format("update %s - %s - %s", getArguments().getInt(ARG_SECTION_NUMBER), className, getLocalInfos()));
}
// ziyaret numarasını artır
numVisit = session.getNumVisit();
numVisit++;
session.setNumVisit(numVisit);
// metin değiştirildi
textViewInfo.setText(String.format("%s, visite %s", text, numVisit));
}
- 7. satır: oturumdan son ziyaret numarası okunur. Ancak bu oturum, diğer her şey gibi yeniden oluşturulmuş ve ziyaret numarası sıfırlanmıştır. Bu da 3 numaralı parçada görüntülenen sonucu açıklar;
1.23.2. Etkinlik ve parçaların yedeklenmesi / geri yüklenmesi yöntemleri
1.23.2.1. Çözüm 1: Manuel yedekleme
Cihazın döndürülmesi sırasında, etkinliğin iki yöntemi çağrılır:
// etkinliğin yedeklenmesi / geri yüklenmesi yönetimi ------------------------------------
@Override
protected void onSaveInstanceState(Bundle outState) {
// üst öğe
super.onSaveInstanceState(outState);
// etkinlik durumunun yedeklenmesi
// ....
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// üst öğe
super.onCreate(savedInstanceState);
// faaliyetin geri yüklenmesi
// ...
}
- 2-8. satırlar: [onSaveInstanceState] yöntemi, döndürme sırasında sistem tarafından çağrılır. Etkinliğin yedeklemesi burada yapılabilir. Herhangi bir işlem yapılmazsa, hiçbir şey yedeklenmez. Etkinliğin durumunun kaydı, yönteme aktarılan [Bundle outState] parametresinde yapılmalıdır. [Bundle] sınıfı bir sözlüğe benzer. Bu sınıf, iki parametreli [putString, putInt, putLong, putBoolean, putChar, ...] yöntemlerine sahiptir: void putT(String key, T value);
- 10-16. satırlar: Etkinlik oluşturulurken [onCreate] yöntemi çağrılır. Etkinliğin durumu kaydedilmişse, bu kayıt [Bundle savedInstanceState] parametresiyle yönteme aktarılır. Kaydedilen değerleri geri almak için, tek parametreli [getString, getInt, getLong, geBoolean, getChar, ...] gibi yöntemler mevcuttur: T getT(String key);
Parçalar da durumlarını kaydetmek için bu iki yöntemi kullanır.
Bu bilgileri, Örnek 21’in durumunu kaydetmek ve geri yüklemek için kullanacağız. Bunun için [Exemple-21] projesini [Exemple-22] olarak kopyalayacağız.
1.23.2.2. Çözüm 2: Otomatik kaydetme
Android belgelerinde, cihazın döndürülmesi sırasında [Fragment].setRetainInstance(true) komutunu kullanarak bir fragmanın yok olmasını önleyebileceğiniz belirtilmektedir. [StackOverflow] ile ilgili birçok makale, bu komutun yalnızca görsel arayüzü olmayan fragmanlar için kullanılmasını önermektedir ([http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean, http://stackoverflow.com/questions/12640316/further-understanding-setretaininstancetrue, http://stackoverflow.com/questions/21203948/setretaininstancetrue-in-oncreate-fragment-in-android]). Bu komutu iki örnek üzerinde test ettim: Örnek-17 (1.18. paragraf – bir form görüntüleyen bir parçaya uygulama) ve Örnek-21 (1.22. paragraf), beş parçadan oluşan bir uygulama). Her iki durumda da, uygulamanın tüm parçalarına uygulanan bu tek komut, cihaz döndürüldüğünde görüntülenen görünümü doğru şekilde geri yüklemek için yetersiz kaldı. [setRetainInstance(true)]'e dayalı bir model ile varsayılan değer olan [setRetainInstance(false)]'e dayalı bir model olmak üzere iki model oluşturmak yerine, [StackOverflow]'in önerilerini takip etmeye ve [setRetainInstance(boolean )] yönteminin varsayılan değeri olan false'i korumaya karar verdim. [Fragment].setRetainInstance(true) komutu, bu belgenin geri kalanında hiçbir zaman kullanılmamıştır.
1.23.3. [Exemple-22] projesinin yedekleme/geri yükleme yöntemi
[Exemple-22] projesi şu şekilde gelişmektedir:
![]() |
Burada iki yeni sınıf görülmektedir:
- [PlaceHolderFragmentState], [PlaceHolderFragment] türündeki bir parçanın durumunu kaydedecektir;
- [Vue1FragmentState], [Vue1Fragment] türündeki parçanın durumunu depolayacak;
Bu sınıflar şunlardır:
package exemples.android;
public class Vue1FragmentState {
// Vue1Fragment durumu
private boolean hasBeenVisited=false;
// alıcı ve ayarlayıcılar
...
}
- 5. satır: [hasBeenVisited] boole değeri, [Vue1Fragment] parçası en az bir kez ziyaret edilmiş (görüntülenmiş) ise doğrudur. Bu alan, [Vue1Fragment] parçasının kaydedilecek hiçbir şeyi olmadığı için örnek amaçlı oluşturulmuştur;
[PlaceHolderFragmentState] sınıfı şu şekildedir:
package exemples.android;
public class PlaceHolderFragmentState {
// ziyaret edilip edilmediğine dair durum
private boolean hasBeenVisited;
// görüntülenen metin
private String text;
// alıcı ve ayarlayıcılar
...
}
- 5. satır: [hasBeenVisited] boole değeri bulunur;
- 7. satır: Parçanın kaydedilmesi gerektiği anda görüntülediği metin. Bu metnin döndürme işlemi sırasında kaybolduğunu görmüştük;
Parçaların durumu oturumda depolanacak ve bu oturumu kaydetme/geri yükleme görevi etkinliğe ait olacaktır. Oturum şu şekilde gelişir:
package exemples.android;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.androidannotations.annotations.EBean;
@EBean(scope = EBean.Scope.Singleton)
public class Session {
// ziyaret edilen parça sayısı
private int numVisit;
// ikinci sekmede görüntülenen [PlaceholderFragment] türündeki parça numarası
private int numFragment = -1;
// seçili sekme numarası
private int selectedTab = 0;
// geçerli görünüm numarası
private int currentView;
// parça yedeklemeleri ---------------
private Vue1FragmentState vue1FragmentState;
private PlaceHolderFragmentState[] placeHolderFragmentStates = new PlaceHolderFragmentState[IMainActivity.FRAGMENTS_COUNT - 1];
// oluşturucu
public Session() {
for (int i = 0; i < placeHolderFragmentStates.length; i++) {
placeHolderFragmentStates[i] = new PlaceHolderFragmentState();
}
vue1FragmentState = new Vue1FragmentState();
}
// alıcı ve ayarlayıcılar
...
}
- 18. satır: [Vue1Fragment] fragmanının durumu;
- 19. satır: [PlaceHolderFragment] türündeki parçaların durumu;
- 22-27. satırlar: oturum oluşturucuda, 18. ve 19. satırlardaki alanlar başlatılır;
- satır 12-15: iki yeni alan ortaya çıkar:
- 13. satır: en son seçilen sekmenin numarası;
- 15. satır: en son görüntülenen parçanın numarası;
Etkinlik, oturumu şu şekilde kaydeder/geri yükler:
// etkinliğin yedekleme / geri yükleme yönetimi ----------------------------
@Override
protected void onSaveInstanceState(Bundle outState) {
// üst nesne
super.onSaveInstanceState(outState);
// oturum kaydetme
try {
outState.putString("session", jsonMapper.writeValueAsString(session));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// günlük
if (IS_DEBUG_ENABLED) {
try {
Log.d(className, String.format("onSaveInstanceState session=%s", jsonMapper.writeValueAsString(session)));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// üst öğe
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// oturum kurtarma
try {
session = jsonMapper.readValue(savedInstanceState.getString("session"), new TypeReference<Session>() {
});
} catch (IOException e) {
e.printStackTrace();
}
// günlük
if (IS_DEBUG_ENABLED) {
try {
Log.d(className, String.format("onCreate session=%s", jsonMapper.writeValueAsString(session)));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
}
- 8. satır: oturum, jSON dizesi biçiminde kaydedilir;
- satır 29: oturum, jSON dizesi kullanılarak geri yüklenir;
Parçaların yedeklenmesi ve geri yüklenmesini yönetmek için, [AbstractFragment] soyut sınıfı şu şekilde değiştirilir:
// yedekleme / geri yükleme yönetimi -----------------------------------------------
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
// üst öğe
super.setUserVisibleHint(isVisibleToUser);
// yedekleme?
if (this.isVisibleToUser && !isVisibleToUser && !saveFragmentDone) {
// parça gizlenecek - yedekleniyor
saveFragment();
saveFragmentDone = true;
}
// bellek
this.isVisibleToUser = isVisibleToUser;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// üst öğe
super.onActivityCreated(savedInstanceState);
// günlük
if (isDebugEnabled) {
Log.d(className, "onActivityCreated");
}
// parça geri yüklenmeli
fragmentHasToBeInitialized = true;
}
@Override
public void onSaveInstanceState(final Bundle outState) {
// günlük
if (isDebugEnabled) {
Log.d(className, "onSaveInstanceState");
}
// üst öğe
super.onSaveInstanceState(outState);
// parça yalnızca görünürse yedeklenir
if (isVisibleToUser && !saveFragmentDone) {
saveFragment();
saveFragmentDone = true;
}
}
// alt sınıflar
protected abstract void updateFragment();
protected abstract void saveFragment();
- parçaların durumunun oturumda iki farklı anda kaydedilmesine karar verilir:
- 2-14. satırlar: parça görünür durumdan gizli duruma geçtiğinde;
- 29-42. satırlar: sistem, parçanın yedeklenmesi gerektiğini bildirdiğinde ve parça görünür durumdayken (38. satır);
Bu mekanizma, gereğinden fazla sıklıkta yedekleme yapılmasını önler. Nitekim, i fragmanının durumu görünür durumdan gizli duruma geçtiğinde kaydedildiğinden, j fragmanı görüntülendiğinde ve bir döndürme işlemi yapıldığında, i fragmanını yeniden kaydetmeye gerek kalmaz. Son kaydetme işleminden bu yana yeniden görüntülenmemişse, durumu değişmemiştir. Yalnızca j fragmanının durumu kaydedilmelidir. Bu mekanizmanın bir başka avantajı daha vardır: Bir fragmanın durumunu kaydetme ihtiyacı sadece cihaz döndürüldüğünde ortaya çıkmaz. Örneğin sekmeli bir sistemde olduğu gibi, fragmanlar arasında salt gezinme durumu da söz konusudur. Bu durumda, bir parçayı en son görüntülendiği andaki durumunda bulmak isteriz. Bu parça belirli bir anda görüntülenen parçaların komşuluğundan çıkmışsa, bu durum kısmen kaybolmuş olabilir. Bu durumda fragman tamamen yeniden oluşturulmaz, ancak ona ait görünüm yeniden oluşturulur. Fragman gizlendiğinde yaptığımız yedekleme, bu görünümün son durumunu geri getirmek için kullanılır;
- satır 10, 40: Arka arkaya iki yedekleme yapılmasını önlemek için, bir yedekleme yapıldığını belirtmek üzere [saveFragmentDone] boole değeri kullanılır;
- satır 9, 39: alt parçadan durumunu kaydetmesi istenir. [saveFragment] yöntemi soyuttur (satır 47). Dolayısıyla bunu uygulamak alt sınıflara düşer;
- satır 16-26: [onActivityCreated] yöntemi, [fragmentHasToBeInitialized] boole değerini true olarak ayarlamak için kullanılır. Zira alt parçanın, oturumda bulacağı bir durumdan yola çıkarak parçanın durumunu tamamen sıfırlaması gerektiğini bilmesi gerekir;
Yine [AbstractFragment] sınıfında, [onCreateOptionsMenu] yöntemi şu şekilde değiştirilmiştir:
// parçanın güncellenmesi
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// bellek
this.menu = menu;
// günlük
if (isDebugEnabled) {
Log.d(className, String.format("création menu en cours"));
}
...
// alt parçanın kendini güncellemesi istenir
updateFragment();
// yedekleme yapılması gerekiyor
saveFragmentDone = false;
}
- 14. satır: Bir kaydetme işlemi yapıldığında [saveFragmentDone] boole değerinin vrai'e geçtiğini görmüştük. Bir noktada bu değerin tekrar faux'e dönmesi gerekir. Alt parçanın [updateFragment] yöntemi (satır 12) yürütüldüğünde, bu parça görünür hale gelir. Oysa bir parçanın yedeklenmesi, görünür durumdan gizli duruma geçtiği o belirli anda, yani görünür olduğu sırada gerçekleşmelidir. Böylece, kaydın gerçekleşebilmesi için [saveFragmentDone] boole değerini false olarak ayarlıyoruz;
1.23.4. [Vue1Fragment] parçasının kaydedilmesi
Parçaların kaydı, üst sınıf [AbstractFragment] tarafından çağrılan [saveFragment] yönteminde gerçekleştirilir:
// parça durumunun yedeklenmesi
@Override
public void saveFragment() {
// günlük
if (isDebugEnabled) {
Log.d(className, String.format("saveFragment 1 %s - %s", className, getLocalInfos()));
}
// parçanın durumunun oturumda kaydedilmesi
Vue1FragmentState state = new Vue1FragmentState();
state.setHasBeenVisited(true);
session.setVue1FragmentState(state);
// günlük
if (isDebugEnabled) {
try {
Log.d(className, String.format("saveFragment 2 state=%s", jsonMapper.writeValueAsString(state)));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
- 9-11. satırlar: Parçanın oturum durumunun kaydedilmesi. [saveFragment] yöntemi çağrıldığında, parça görünür durumdadır. Bu nedenle, [hasBeenVisited] boole değerini vrai olarak ayarlamak gerekir (10. satır);
1.23.5. [PlaceHolderFragment] fragmanının kaydedilmesi
Parçaların kaydedilmesi, üst sınıf [AbstractFragment] tarafından çağrılan [saveFragment] yönteminde gerçekleştirilir:
@Override
public void saveFragment() {
// parça durumunun oturumda kaydedilmesi
PlaceHolderFragmentState state = new PlaceHolderFragmentState();
state.setText(textViewInfo.getText().toString());
state.setHasBeenVisited(true);
session.getPlaceHolderFragmentStates()[getArguments().getInt(ARG_SECTION_NUMBER) - 1] = state;
// günlük
if (isDebugEnabled) {
try {
Log.d(className, String.format("saveFragment state=%s", jsonMapper.writeValueAsString(state)));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
- 4-7. satırlar: parçanın durumunun oturumda kaydedilmesi;
- 5. satır: [TextView] textViewInfo tarafından o anda görüntülenen metin kaydedilir;
- 6. satır: parçanın [hasBeenVisited] boole değeri, vrai olarak değiştirilir;
- satır 7: parçanın durumu [placeHolderFragmentStates] tablosuna kaydedilir. Başlatılacak öğenin numarası, parçanın bölüm numarasından bir çıkarılır;
1.23.6. [Vue1Fragment] parçasının geri yüklenmesi
Parçaların geri yüklenmesi [updateFragment] yönteminde gerçekleştirilir:
@Override
protected void updateFragment() {
// günlük
if (isDebugEnabled) {
Log.d(className, String.format("updateFragment 1 %s - %s", className, getLocalInfos()));
}
// geri yükleme?
if (fragmentHasToBeInitialized) {
// durum geri yükleme
hasBeenVisited = session.getVue1FragmentState().isHasBeenVisited();
fragmentHasToBeInitialized = false;
}
// günlük
if (isDebugEnabled) {
Log.d(className, String.format("updateFragment 2 %s - %s", className, getLocalInfos()));
}
// gezinti?
boolean navigation = session.getCurrentView() != IMainActivity.FRAGMENTS_COUNT - 1;
if (navigation) {
// ziyaret numarasını artır
numVisit = session.getNumVisit();
numVisit++;
session.setNumVisit(numVisit);
// ziyaret numarası görüntüleniyor
Toast.makeText(activity, String.format("Visite n° %s", numVisit), Toast.LENGTH_SHORT).show();
}
// geçerli sayfa numarasını değiştir
session.setCurrentView(IMainActivity.FRAGMENTS_COUNT - 1);
}
- 8-12. satırlar: parçanın durumunun geri yüklenmesi. [fragmentHasToBeInitialized] boole değeri, üst sınıf [AbstractFragment] tarafından başlatılmıştır. Değer vrai olduğunda, parça yeniden oluşturulmuş demektir ve yeniden başlatılması gerekir. İşte bu işlem burada gerçekleşir. Bu özel örnekte yapılacak bir şey yoktur. Sadece, [hasBeenVisited] boole değerinin fragmanın kaydedilmiş durumunda (10. satır) bulunabileceğini gösterdik;
- 11. satır: [fragmentHasToBeInitialized] değerini faux olarak yeniden ayarlamayı unutmamak gerekir; böylece daha sonra bu parçaya geri döndüğümüzde, aygıtın dönüşü gerçekleşmemişse, parçanın gereksiz yere yeniden başlatılması önlenir;
- satır 18-26: ziyaret sayacının artırılması. Burada bir zorluk vardır: parçayı geri yüklediğimizde bu sayacı artırmak istemeyiz. Burada şunları birbirinden ayırmamız gerekir:
- kullanıcıyı [Vue 1] sekmesine geri götüren basit bir gezinme;
- [Vue 1] sekmesi görüntülenirken kullanıcının cihazını döndürdüğü durumlarda yapılan yeniden yükleme;
Bu iki durumu, oturumda depolanan görünüm numarası sayesinde ayırt ederiz. Bu numara, en son görüntülenen görünümün numarasıdır (28. satır).
- 18. satır: Son görünümün numarası geçerli görünümün numarasından farklıysa, geri dönüş değil, gezinme işlemi gerçekleştirilir;
- 21-25. satırlar: ziyaret sayacının artırılması ve görüntülenmesi;
1.23.7. [PlaceHolderFragment] parçasının geri yüklenmesi
Parçaların geri yüklenmesi [updateFragment] yönteminde gerçekleştirilir:
// veri
private String text;
private int numVisit;
private String newText;
private boolean hasBeenVisited = false;
private ObjectMapper jsonMapper = new ObjectMapper();
...
public void updateFragment() {
// günlük
if (isDebugEnabled) {
Log.d("PlaceholderFragment", String.format("update %s - %s - %s", getArguments().getInt(ARG_SECTION_NUMBER), className, getLocalInfos()));
}
// hangi parça söz konusu?
int numSection = getArguments().getInt(ARG_SECTION_NUMBER);
int numView = numSection - 1;
// parça başlatılmalı mı?
if (fragmentHasToBeInitialized) {
// başlangıç metni
text = getString(R.string.section_format, numSection);
fragmentHasToBeInitialized = false;
}
// gezinme?
boolean navigation = session.getCurrentView() != numView;
if (navigation) {
// ziyaret sayısını artır
numVisit = session.getNumVisit();
numVisit++;
session.setNumVisit(numVisit);
// değiştirilmiş metin
newText = String.format("%s, visite %s", text, numVisit);
} else {
// bir geri yükleme söz konusu
PlaceHolderFragmentState state = session.getPlaceHolderFragmentStates()[numView];
newText = state.getText();
}
// metin görüntüleme
textViewInfo.setText(newText);
// geçerli görünüm
session.setCurrentView(numView);
}
- satır 15-16: güncellenmekte olan görünümün numarası belirlenir;
- satır 18-22: Parça, cihazın yönünün değiştirilmesinden sonra bir kaydetme/geri yükleme döngüsündeyse, burada geri yüklenmesi gerekir. Genellikle parçanın belirli alanlarının geri yüklenmesi söz konusudur;
- 20. satır: 2. satırdaki [text] alanı, fragman tarafından görüntülenen başlangıç metnini içermelidir: [Hello world from section i]. Burada bu metin yeniden oluşturulmalıdır;
- 21. satır: Parçanın başlatılmasının yapıldığı görülmektedir;
- satır 24-36: Daha önce [Vue1Fragment] parçası için olduğu gibi, geri yükleme sırasında ziyaret sayacının artırılması yapılmamalıdır. Daha önce olduğu gibi, gezinme ile geri yükleme arasında ayrım yapmamız gerekir;
- 32-36. satırlar: geri yükleme durumu;
- satır 34: Cihazın döndürülmesinden önceki parçanın durumu oturumdan alınır;
- satır 35: o sırada görüntülenen metin buradan alınır;
- satır 38: bu metin yeniden görüntülenir;
- satır 40: oturumda, görüntülenen yeni görünümün numarası kaydedilir;
1.23.8. Sekme Yönetimi
Önceki paragraflarda sekme yönetimi ele alınmamıştır. Ancak Örnek 21'de cihazın döndürülmesi sırasında bir sorunla karşılaştık: yalnızca ilk sekme olan [Vue 1] korunmuştu. İkinci sekme ise kaybolmuştu.
Bu sorunu [MainActivity] sınıfında şu şekilde çözüyoruz:
@AfterViews
protected void afterViews() {
// günlük
if (IS_DEBUG_ENABLED) {
Log.d(className, "afterViews");
}
// araç çubuğu
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
...
// 1. sekme
TabLayout.Tab tab = tabLayout.newTab();
tab.setText("Vue 1");
tabLayout.addTab(tab);
// 2. sekme?
int numFragment = session.getNumFragment();
if (numFragment != -1) {
TabLayout.Tab tab2 = tabLayout.newTab();
tab2.setText(String.format("Fragment n° %s", (numFragment + 1)));
tabLayout.addTab(tab2);
}
// hangi sekme seçilmeli?
tabLayout.getTabAt(session.getSelectedTab()).select();
...
}
- 14-16. satırlar: 1. sekmenin oluşturulması;
- satır 18-23: 2. sekmenin oluşturulması. Bu sekmeyi oluşturup oluşturmayacağımızı belirlemek için, oturumda 2. sekmede görüntülenen parçanın numarasına bakıyoruz. Bu numara, başlangıç değeri olan -1'den farklıysa, ikinci sekme oluşturulur. Bu aşamada, varsayılan olarak ilki seçili olan iki sekmemiz vardır;
- 26. satır: oturumda, kaydetme/geri yükleme işleminden önce seçili olan sekmenin numarasını arar ve bunu yeniden seçeriz. [selectedTab] alanı kod tarafından henüz başlatılmamışsa, bu durumda başlangıç değeri olan 0 kullanılır;











































































































































































































































































































































