Android Studio İle Düzey Göstergesi Tasarımı (Java)
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.
Görsel 1: Vektörel çizim seçimi
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.
Görsel 2: Göstergenin arkaplanının eklenmesi
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.
Görsel 3: Önplan çiziminin eklenmesi
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 Clip Drawable dosyası oluşturacağız. Clip drawable ön plandaki resmin düzey miktarı kadarının görülmesini sağlayan bir Drawable resource nesnesidir. Bilmeyenler için; yaptığı işten anlayacağınız gibi clip sözcüğünün anlamı kırpmaktır.
Görsel 4: Clip Drawable oluşturma
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.
<?xml version="1.0" encoding="utf-8"?>
<clip xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/pil_duzey"
android:gravity="bottom"
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ğini bottom tanımladık.
- Kırpma yönelimi clipOrientation niteliğini de çizimimiz dikey olduğu için vertical 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 Layer List drawable dosyası oluşturacağız.
Görsel 5: Layer List oluşturma
Dosyayı ekledikten sonra açıp aşağıdaki kodu girin.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/pil_arkaplan"/>
<item android:drawable="@drawable/duzey_clip"/>
</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.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".DuzeyGostergesiActivity">
<View
android:id="@+id/view_gosterge"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginTop="32dp"
android:background="@drawable/duzey_layer_list"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView_yuzde"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="%0"
app:layout_constraintBottom_toBottomOf="@+id/view_gosterge"
app:layout_constraintEnd_toEndOf="@+id/view_gosterge"
app:layout_constraintStart_toStartOf="@+id/view_gosterge"
app:layout_constraintTop_toTopOf="@+id/view_gosterge" />
<TextView
android:id="@+id/textView_bilgi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Pili doldur / boşalt"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view_gosterge" />
<SeekBar
android:id="@+id/seekBar_ayar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:max="100"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_bilgi" />
<Button
android:id="@+id/button_doldur"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Doldur"
app:layout_constraintEnd_toEndOf="@+id/editText_miktar"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/button_bosalt"
app:layout_constraintTop_toBottomOf="@+id/editText_miktar" />
<Button
android:id="@+id/button_bosalt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Boşalt"
app:layout_constraintStart_toStartOf="@+id/editText_miktar"
app:layout_constraintTop_toBottomOf="@+id/editText_miktar" />
<TextView
android:id="@+id/textView_kademeBilgi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Artırma / azaltma değeri"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/seekBar_ayar" />
<EditText
android:id="@+id/editText_miktar"
style="@android:style/Widget.Material.EditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="5"
android:selectAllOnFocus="true"
android:singleLine="true"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView_kademeBilgi" />
</androidx.constraintlayout.widget.ConstraintLayout>
Görsel 6: Uygulamanın tasarım ekranı
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 Buttondan 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:
package com.kozmotronik.example.duzeygostergesi;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import com.google.android.material.snackbar.Snackbar;
public class DuzeyGostergesiActivity extends AppCompatActivity {
private static final String ETIKET = DuzeyGostergesiActivity.class.getSimpleName();
View gosterge;
TextView yuzde;
SeekBar ayar;
EditText editTextMiktar;
Button doldur, bosalt;
// Tuşlarla yapılacak artırma ve azaltma için miktar. EditText ile alınacak. Varsayılan 5.
int miktar = 5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_duzey_gostergesi);
// UI denetim öğelerini ilkleyelim
gosterge = findViewById(R.id.view_gosterge);
yuzde = findViewById(R.id.textView_yuzde);
ayar = findViewById(R.id.seekBar_ayar);
editTextMiktar = findViewById(R.id.editText_miktar);
doldur = findViewById(R.id.button_doldur);
bosalt = findViewById(R.id.button_bosalt);
/*
SeekBar'ın ilerleme (progress) değişimini dinleyip pil göstergemiz üzerinde gereken
güncellemeyi yapacağız.
*/
ayar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
/**
* Bir {@link Drawable} nesnesinin düzeyi (level)
* 0 ile 10000 arasında bir değere kurulabilir. Fakat {@link SeekBar} aracının
* maksimum düzeyini okunabilirlik açısından 100 yaptık. Bu yüzden gelen değeri
* 10000 değerine ölçeklemek için 10000 / 100 = 100 ile çarpacağız.
*/
int duzey = progress * 100;
String sYuzde = "%" + progress;
gosterge.getBackground().setLevel(duzey);
yuzde.setText(sYuzde);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
/*
Burada artırma ve azaltma miktarını EditText yoluyla alacağız. Bunun için yazı değişimini
bir TextWatcher sınıfı ile dinlememiz gerekir.
*/
editTextMiktar.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
/*
Integer sınıfının statik yordamı parseInt kullanarak String sayı girdisini int değere dönüştürüyoruz.
Bu yordam geçersiz bir sayı stringi durumunda NumberFormatException hatası atabilir.
Bu yüzden dönüştürme işlemini try-catch bloğu içinde yapacağız.
*/
try {
miktar = Integer.parseInt(s.toString(), 10); // Decimal radixte string girdiyi sayıya dönüştür
} catch (NumberFormatException numberFormatException) {
// Girilen string verisinde sayı olarak değerlendirilecek bir girdi yok, uyarı ver
Snackbar.make(editTextMiktar, s.toString()+" geçerli bir sayı değil!", 1500).show();
miktar = 5; // hata durumunda miktarı varsayılan değere kur
numberFormatException.printStackTrace(); // Hatayı loga yazdır
}
Log.d(ETIKET,"miktar: "+miktar);
}
@Override
public void afterTextChanged(Editable s) {
}
});
// Tuşların görevlerini kuralım
bosalt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Önce zaten boş olmadığından emin olmalıyız
int duzey = ayar.getProgress() - miktar;
if(duzey < 0) duzey = 0; // Sıfırın altına düştüyse sıfırda tut.
ayar.setProgress(duzey); // Seekbar progress değerini kurunca pil düzeyi seekbar onProgressChanged içinde güncellenir
}
});
doldur.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Değerin 100 ü aşmadığına emin olmalıyız
int duzey = ayar.getProgress() + miktar;
if(duzey > 100) duzey = 100;
ayar.setProgress(duzey);
}
});
}
}
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 nesnelerin background niteliğine 4. adımda hazırladığımız duzey_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.
Video: Uygulamanın demosu
Aklınıza takılanları veya önerilerinizi yorum bölümünden paylaşabilir, uygulamanın Github reposuna buradan ulaşabilir ve proje olarak indirebilirsiniz. Başka bir makalede görüşmek üzere herkese iyi çalışmalar.