Android İçin Düzey Göstergesi
Genel Bakış
Mobil uygulamalar geliştirirken bazı durumlarda kullanıcıya değişen düzey değerlerini uygulamamıza özel görseller kullanarak sunmamız gerekebilir. Bu, bir sürecin ilerleme düzeyi, bir pilin doluluğu, şebeke, wifi veya bluetooth gibi bir radyo sinyalinin gücü olabilir.
Elbette bunu yapmanın birden çok yöntemi olabilir. Fakat burada gerçekleyeceğimiz yöntem Android ekosisteminin doğal gücünden yararlandığı için şu ana dek denediklerim arasında en verimli ve iyi bir görsel deneyim sağlayan yöntemdir. Bu yüzden bu yöntemi ileride böyle bir tasarım yapmak isteyecekler için paylaşıyorum.
Bu örneği kavrayabilmek için yeterli düzeyde Java bilgisi ve Android Studio deneyimine sahip olmanız gerekir. Yeni öğrenenler için uygun bir çalışma materyali değildir.
Bu örnekte bir pilin doluluk değerini gösterecek, doluluk değerini bir
SeekBar
ile, ayrıca iki adet tuş ile dinamik olarak
değiştirebilecek ve bu düzey değişimlerini düzey göstergemizde
gözlemleyebileceğiz. Göstergemize düzey değerine göre değişim dinamiğini veren
Drawable
sınıfıdır.
Hazırsanız başlayalım. Tasarımımıza başlamak için yeni bir Android Studio projesi oluşturun, boş bir aktivite seçip programlama dilini Java seçin. Android Studio'nun dosyaları indekslemesi için biraz bekleyin. Hazırsa, adım adım düzey göstergesi için kullanacağımız çizimleri ekleyip kullanıma hazır hale getirelim.
1. Arka Plan Çizimini Ekleme
Studio'nun sol yanındaki Project bölümünü açıp res > drawable dizini üzerine sağ tıklayın. Açılan içerik menüsünden New > Vector Asset seçeneğini seçin. Google'ın sağladığı vektörel çizimleri uygulamanıza ekleyebileceğiniz bir pencere gelecektir. Bu pencerede Clip Art yazısının karşısındaki simgeye tıklayıp çizim galerisini açın.
Arama kutusuna battery yazın ve bulunanlardan battery std olanı seçin ve OK tuşuna basın. İsterseniz battery full olanı da seçebilirsiniz.
Bu çizimi arkaplan olarak kullanacağımız için pil_arkaplan olarak adlandırdım. Opacity yani saydamlığı %25'e ayarlayın ve NEXT'i tıklayıp eklemeyi bitirin.
2. Ön Plan Çizimini Ekleme
İlk adımdaki ekleme işlemini tekrarlayın yalnız saydamlığı değiştirmeyin.
Bunu da pil_duzey olarak adlandırdım çünkü opak olan bu çizim düzey değerinin % (yüzde) olarak görsel temsili olacaktır. Bu çizimin rengini sistemin colorPrimary rengine ayarladım, siz istediğiniz soluk olmayan bir renk seçebilirsiniz. Bu noktada, bu çizimi doğrudan değil dolaylı olarak kullanacağımızı söylemekte fayda var. Nasıl ve neden olduğunu ilerleyen adımlarda açıklayacağım.
3. Clip Drawable Oluşturma
Arka ve ön plan olarak kullanacağımız resimleri / çizimleri ekledikten sonra
sıra geldi önemli olanlardan birine. Bu adımda bir
ClipDrawable
dosyası oluşturacağız. Clip drawable
ön plandaki resmin düzey miktarı kadarının görülmesini sağlayan bir
DrawableResource
nesnesidir. Bilmeyenler için;
yaptığı işten anlayacağınız gibi clip sözcüğünün anlamı kırpmaktır.
duzey_clip olarak adlandırıp Root element olarak clip yazın ve ardından OK tuşuna basın. Ekledikten sonra dosyayı açın ve aşağıdaki kodları dosyaya girin.
1<?xml version="1.0" encoding="utf-8"?>
2<clip xmlns:android="http://schemas.android.com/apk/res/android"
3 android:drawable="@drawable/pil_duzey"
4 android:gravity="bottom"
5 android:clipOrientation="vertical"/>
Kodu kısaca açıklarsak:
- Kırpılacak çizim olarak
@drawable/pil_duzey
tanımladık. - Çizimimiz dikey olduğu ve pilin dolumunu aşağıdan yukarıya olacak şekilde
yapacağımız için
gravity
niteliğinibottom
tanımladık. - Kırpma yönelimi
clipOrientation
niteliğini de çizimimiz dikey olduğu içinvertical
tanımladık.
4. Katman Listesini Oluşturma
Artık gösterge için çizim kaynaklarını bir araya getirip bir katman listesi
oluşturabiliriz. Bunun için duzey_layer_list
adında bir
LayerList
drawable dosyası oluşturacağız.
Dosyayı ekledikten sonra açıp aşağıdaki kodu girin.
1<?xml version="1.0" encoding="utf-8"?>
2<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
3 <item android:drawable="@drawable/pil_arkaplan"/>
4 <item android:drawable="@drawable/duzey_clip"/>
5</layer-list>
XML kodunda bir şeye dikkat ettiniz mi? İlk katman olarak doğrudan arka plan çizimini kullandık ancak ikinci katmanda ön plan çizimini doğrudan kullanmadık. Neden? Çünkü ön plan çiziminin yalnızca düzey değeri oranında görünmesini geri kalanının da kırpılmasını, yani görünmemesini istiyoruz. Bu yüzden üçüncü adımda oluşturduğumuz düzeye göre kırpma işini yapacak duzey_clip'i tanımladık.
5. Arayüz Tasarımı
Buraya kadar resource dosyalarını hazırlamayı tamamladık. Şimdi basit bir arayüz oluşturup düzey göstergemizi işlevsel hale getireceğiz. Bunun için Aktivitenizin layout dosyasını açın ve aşağıdaki kodu girin.
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 xmlns:app="http://schemas.android.com/apk/res-auto"
4 xmlns:tools="http://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 tools:context=".DuzeyGostergesiActivity">
8
9 <View
10 android:id="@+id/view_gosterge"
11 android:layout_width="128dp"
12 android:layout_height="128dp"
13 android:layout_marginTop="32dp"
14 android:background="@drawable/duzey_layer_list"
15 app:layout_constraintEnd_toEndOf="parent"
16 app:layout_constraintStart_toStartOf="parent"
17 app:layout_constraintTop_toTopOf="parent" />
18
19 <TextView
20 android:id="@+id/textView_yuzde"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content"
23 android:text="%0"
24 app:layout_constraintBottom_toBottomOf="@+id/view_gosterge"
25 app:layout_constraintEnd_toEndOf="@+id/view_gosterge"
26 app:layout_constraintStart_toStartOf="@+id/view_gosterge"
27 app:layout_constraintTop_toTopOf="@+id/view_gosterge" />
28
29 <TextView
30 android:id="@+id/textView_bilgi"
31 android:layout_width="wrap_content"
32 android:layout_height="wrap_content"
33 android:layout_marginTop="32dp"
34 android:text="Pili doldur / boşalt"
35 android:textAppearance="@style/TextAppearance.AppCompat.Body1"
36 app:layout_constraintEnd_toEndOf="parent"
37 app:layout_constraintStart_toStartOf="parent"
38 app:layout_constraintTop_toBottomOf="@+id/view_gosterge" />
39
40 <SeekBar
41 android:id="@+id/seekBar_ayar"
42 android:layout_width="0dp"
43 android:layout_height="wrap_content"
44 android:layout_marginTop="8dp"
45 android:max="100"
46 app:layout_constraintEnd_toEndOf="parent"
47 app:layout_constraintHorizontal_bias="1.0"
48 app:layout_constraintStart_toStartOf="parent"
49 app:layout_constraintTop_toBottomOf="@+id/textView_bilgi" />
50
51 <Button
52 android:id="@+id/button_doldur"
53 android:layout_width="wrap_content"
54 android:layout_height="wrap_content"
55 android:layout_marginTop="16dp"
56 android:text="Doldur"
57 app:layout_constraintEnd_toEndOf="@+id/editText_miktar"
58 app:layout_constraintHorizontal_bias="1.0"
59 app:layout_constraintStart_toEndOf="@+id/button_bosalt"
60 app:layout_constraintTop_toBottomOf="@+id/editText_miktar" />
61
62 <Button
63 android:id="@+id/button_bosalt"
64 android:layout_width="wrap_content"
65 android:layout_height="wrap_content"
66 android:layout_marginTop="16dp"
67 android:text="Boşalt"
68 app:layout_constraintStart_toStartOf="@+id/editText_miktar"
69 app:layout_constraintTop_toBottomOf="@+id/editText_miktar" />
70
71 <TextView
72 android:id="@+id/textView_kademeBilgi"
73 android:layout_width="wrap_content"
74 android:layout_height="wrap_content"
75 android:layout_marginTop="32dp"
76 android:text="Artırma / azaltma değeri"
77 app:layout_constraintEnd_toEndOf="parent"
78 app:layout_constraintStart_toStartOf="parent"
79 app:layout_constraintTop_toBottomOf="@+id/seekBar_ayar" />
80
81 <EditText
82 android:id="@+id/editText_miktar"
83 style="@android:style/Widget.Material.EditText"
84 android:layout_width="wrap_content"
85 android:layout_height="wrap_content"
86 android:ems="10"
87 android:hint="5"
88 android:selectAllOnFocus="true"
89 android:singleLine="true"
90 android:textAlignment="center"
91 app:layout_constraintEnd_toEndOf="parent"
92 app:layout_constraintStart_toStartOf="parent"
93 app:layout_constraintTop_toBottomOf="@+id/textView_kademeBilgi" />
94</androidx.constraintlayout.widget.ConstraintLayout>
Kodu girdikten sonra tasarım kipine geçin. Tasarımın yapısal olarak böyle görünmesi gerekiyor ama renkler farklı olabilir:
Arayüzümüz kısaca; tasarladığımız düzey göstergesi, bir SeekBar, bir
EditText
, açıklama içeren birkaç
TextView
ve iki adet Button
dan
oluşuyor. Tuşları ve kaydırma çubuğunu kullanarak dinamik olarak bir düzey
değeri üretip bu değerin temsilini göstergemizde göstermeyi planlıyoruz.
EditText
denetimini tuşların her bir basmada ne kadar doldurma veya boşaltma
yapacağını belirlemek için kullanıyoruz.
6. Java Kodu
Son aşamamızda uygulamanın mantığını işleyecek kodları yazacağız. Aktivitenin kaynak kodu dosyasını açın ve aşağıdaki kodları girin:
1public class DuzeyGostergesiActivity extends AppCompatActivity {
2 private static final String ETIKET = DuzeyGostergesiActivity.class.getSimpleName();
3
4 View gosterge;
5 TextView yuzde;
6 SeekBar ayar;
7 EditText editTextMiktar;
8 Button doldur, bosalt;
9
10 // Tuşlarla yapılacak artırma ve azaltma için miktar. EditText ile alınacak. Varsayılan 5.
11 int miktar = 5;
12
13 @Override
14 protected void onCreate(Bundle savedInstanceState) {
15 super.onCreate(savedInstanceState);
16 setContentView(R.layout.activity_duzey_gostergesi);
17
18 // UI denetim öğelerini ilkleyelim
19 gosterge = findViewById(R.id.view_gosterge);
20 yuzde = findViewById(R.id.textView_yuzde);
21 ayar = findViewById(R.id.seekBar_ayar);
22 editTextMiktar = findViewById(R.id.editText_miktar);
23 doldur = findViewById(R.id.button_doldur);
24 bosalt = findViewById(R.id.button_bosalt);
25
26 /*
27 SeekBar'ın ilerleme (progress) değişimini dinleyip pil göstergemiz üzerinde gereken
28 güncellemeyi yapacağız.
29 */
30 ayar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
31 @Override
32 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
33 /**
34 * Bir {@link Drawable} nesnesinin düzeyi (level)
35 * 0 ile 10000 arasında bir değere kurulabilir. Fakat {@link SeekBar} aracının
36 * maksimum düzeyini okunabilirlik açısından 100 yaptık. Bu yüzden gelen değeri
37 * 10000 değerine ölçeklemek için 10000 / 100 = 100 ile çarpacağız.
38 */
39 int duzey = progress * 100;
40 String sYuzde = "%" + progress;
41 gosterge.getBackground().setLevel(duzey);
42 yuzde.setText(sYuzde);
43 }
44
45 @Override
46 public void onStartTrackingTouch(SeekBar seekBar) {
47
48 }
49
50 @Override
51 public void onStopTrackingTouch(SeekBar seekBar) {
52
53 }
54 });
55
56 /*
57 Burada artırma ve azaltma miktarını EditText yoluyla alacağız. Bunun için yazı değişimini
58 bir TextWatcher sınıfı ile dinlememiz gerekir.
59 */
60 editTextMiktar.addTextChangedListener(new TextWatcher() {
61 @Override
62 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
63
64 }
65
66 @Override
67 public void onTextChanged(CharSequence s, int start, int before, int count) {
68 /*
69 Integer sınıfının statik yordamı parseInt kullanarak String sayı girdisini int değere dönüştürüyoruz.
70 Bu yordam geçersiz bir sayı stringi durumunda NumberFormatException hatası atabilir.
71 Bu yüzden dönüştürme işlemini try-catch bloğu içinde yapacağız.
72 */
73 try {
74 miktar = Integer.parseInt(s.toString(), 10); // Decimal radixte string girdiyi sayıya dönüştür
75 } catch (NumberFormatException numberFormatException) {
76 // Girilen string verisinde sayı olarak değerlendirilecek bir girdi yok, uyarı ver
77 Snackbar.make(editTextMiktar, s.toString()+" geçerli bir sayı değil!", 1500).show();
78 miktar = 5; // hata durumunda miktarı varsayılan değere kur
79 numberFormatException.printStackTrace(); // Hatayı loga yazdır
80 }
81 Log.d(ETIKET,"miktar: "+miktar);
82 }
83
84 @Override
85 public void afterTextChanged(Editable s) {
86
87 }
88 });
89
90 // Tuşların görevlerini kuralım
91 bosalt.setOnClickListener(new View.OnClickListener() {
92 @Override
93 public void onClick(View v) {
94 // Önce zaten boş olmadığından emin olmalıyız
95 int duzey = ayar.getProgress() - miktar;
96 if(duzey < 0) duzey = 0; // Sıfırın altına düştüyse sıfırda tut.
97 // Seekbar progress değerini kurunca pil düzeyi seekbar onProgressChanged içinde güncellenir
98 ayar.setProgress(duzey);
99 }
100 });
101
102 doldur.setOnClickListener(new View.OnClickListener() {
103 @Override
104 public void onClick(View v) {
105 // Değerin 100 ü aşmadığına emin olmalıyız
106 int duzey = ayar.getProgress() + miktar;
107 if(duzey > 100) duzey = 100;
108 ayar.setProgress(duzey);
109 }
110 });
111
112 }
113}
Kod içerisinde gerekli açıklamaları yaptım. Ancak uygulamamızın en önemli noktalarına burada da kısaca değineyim:
- Gösterge olarak kullanmak istediğimiz
View
veya türevi nesnelerinbackground
niteliğine 4. adımda hazırladığımızduzey_layer_list
dosyasını tanımlıyoruz. - Düzeyi değiştirmek istediğimizde Drawable sınıfının
setLevel()
yordamını kullanıyoruz. - Drawable sınıfında düzey (level) değeri 0 - 10.000 arasında bir değer almakta, 10.000 değeri %100'ü temsil etmektedir. O yüzden oldukça iyi düzey görüntüsü oluşturma hassasiyetine sahiptir.
- Bu uygulamada maksimum değerimiz 100 olduğu için; 10.000 / 100 = 100 hesabına göre, 0-100 arası elde ettiğimiz düzey değerini 100 ile çarpmamız gerekir. Bu işlemi kod içerisinde de görebilirsiniz.
Aşağıda uygulamanın çalışan bir demosunu görebilirsiniz.
Bu uygulamanın kodları MIT lisansı altında paylaşılmaktadır.
Uygulamanın github reposuna buradan ulaşabilir ve proje olarak indirebilirsiniz. Başka bir makalede görüşmek üzere herkese iyi çalışmalar.