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.

Vektörel çizimi 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ö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.

Ö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 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.

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.

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ğini bottom tanımladık.
  • Kırpma yönelimi clipOrientationniteliğ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 LayerList drawable dosyası oluşturacağız.

Layer list oluşturma

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:

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:

  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 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.

Lisans

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.

comments powered by Disqus

Çeviriler: