9 Ekim 2013 Çarşamba

Android'de Bir Uygulama Nasıl Patlatılmaz?

Android'de uygulama gelşitiriyoruz. Aklımıza gelen bütün exception'ları handle ediyoruz ama elbet bir yerde bir exception gözümüzden kaçabiliyor. Uygulama burada patladığı zaman yüzümüz kızarıyor. Artık bundan çok sıkıldığınızı biliyorum. :)

Öyleyse neden bu yakalayamadığımız exception'ları uygulamayı patlatmadan bir yere kanalize edip uygulamayı ve kullanıcıyı istediğimiz yere yönlendirmeyelim? Bu sorunun cevabını Java'nın durdurak bilmeyen nimetleri içinde bulabilmemiz mümkün.

Java bu noktada bize UncaughtExceptionHandler sınıfını sunuyor ve bununla (adı üstünde) yakalanamayan exception'ları handle etmemizi sağlıyor. Hatta bu exception'ların stack trace'ini bir String objesinde saklayabileceğinizi söylesem?

Gözlerinizin yuvalarından çıkmasına gerek yok, böyle bir şeyin olmaması Java'nın ayıbı olurdu zaten. :) Bu özelliklerle dilerseniz yayındaki bir uygulamanın sadece patlamasını önlemekle kalmaz, dilerseniz stack trace'i kendinize mail olarak atabilir, uygulamanın neresinin patladığını izleyebilirsiniz.

Sistem elbetteki bizim hayal ettiğimiz şekilde işlemiyor. Bir uygulama patladıysa onun geri dönüşü olamaz, mevcut uygulama kapatılması gerekir ki, bu en sağlıklı yöntemdir ama kullanıcı dostu değildir. Biz bu yazımızda bunu kullanıcı dostu bir hale getireceğiz.

Bunun için eğlenceli bir uygulama yapalım. İki tane activity olsun, ilk activity'e Patlat butonu koyalım ve uygulama patlasın. Fakat yazdığımız sınıflarla bu patlama kullanıcıya belli edilmesin ve ikinci activity'e geçsin. Uygulamamıza yakalanamayan exception'ı handle edecek class'ımızı ekleyelim. Adı CustomExceptionHandler.java olsun.

import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;

import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.util.Log;

public class CustomExceptionHandler implements UncaughtExceptionHandler {

 private final Context context;
 
 public CustomExceptionHandler(Context context) {
  this.context = context;
 }
 
 @Override
 public void uncaughtException(Thread t, Throwable e) {
  final Writer result = new StringWriter();
     final PrintWriter printWriter = new PrintWriter(result);
     e.printStackTrace(printWriter);
     String stacktrace = result.toString();
     printWriter.close();
     
     Log.d("Stack Trace", stacktrace);
     
     Intent intent = new Intent(context, ErrorActivity.class);
     context.startActivity(intent);
     
     Process.killProcess(Process.myPid());
  System.exit(10);
 }

}

UncaughtExceptionHandler sınıfından implement edilmiş sınıfımızda, uygulama patladığı zaman uncaoughException metodu çalışacak. Burada istediğimiz şeyi yapabiliriz. Kodda görüldüğü üzere ben stack trace'i LogCat'e yazdırıyorum ve ikinci activity'miz olan ErrorActivity'ye geçiyorum. Daha sonrasında patlayan uygulamayı kill ederek tamamen kapatıyorum. Eğer uygulamada önemli static değerleriniz varsa bunlara kill etmeden önce erişebilirsiniz.

Şimdi sırasıyla ilk activity'mizin XML'ini daha sonra ikinci activity'mizin XML'inin tasarımına geçelim. İlk activity'miz activity_main.xml



    

İkinci activity'miz activity_error.xml




    

        

        

Uygulamamız açıldığında ilk activity_main XML'iyle tasarlanmış MainActivity açılacak. Burada bir patlatma butonu bulunuyor. Bu butona basıldığında NullPointerException ile patlayacak bir takım kodlar çalışacak. Patlama işlemi gerçekleşince CustomExceptionHandler sınıfımız bu exception'ı yakalayacak ve konsola yazdıracak, ardından activity_error XML'iyle tasarlanmış ErrorActivity'miz açılacak.

Şimdi bu iki activity'ni Java tarafındaki sınıflarının içini dolduralım. İlk olarak MainActivity'.java

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
 }

 public void onPatlatClick(View v) {
  String funye = null;
  if(funye.equals("BOM")) {
   
  }
 }
}

Burada dikkat etmeniz gereken husus onCreate metodunda çağırdığım setDefaultUncaughtException'dır. Bu kod, uygulamanın başında çağrılmıştır ve yakalanamayan exception'ları yakalamak için CustomExceptionHandler sınıfımızı aktif hale getirmiştir. Bu koddan itibaren olabilecek bütün yakalanamayan exception'lar bu sınıfımıza düşecektir. Öte yandan. onPatlatClick metodumuzda Patlat butonuna basıldığında olacak işlemlerin implementasyonu yapılmıştır. Uygulama tam burada patlamaktadır ve NullPointerException fırlatır.

İkinci activity'miz ErrorActivity.java da aşağıdaki gibidir.

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class ErrorActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_error);
 }
 
 public void onAnaSayfaClick(View v) {
  Intent intent = new Intent(ErrorActivity.this, MainActivity.class);
  startActivity(intent);
  finish();
 }
}

Bu activity uygulama patladıktan sonra açılır. Activity'de ilk activity'mize geri dönüş yapabileceğimiz bir buton bulunur ve bu butona basıldığında bu activity sonlandırılır ve uygulama ilk açıldığı haline döner.

Böylece uygulamayı kullanıcıya 'Durduruldu' mesajı vermeden patlatır ve eski haline geri dönmesini sağlayabiliriz. Bu özellikle uygulamaya hiç bir şey olmamış gibi öncekini öldürüp farklı bir kanaldan yeni bir yaşam döngüsüne sokabilir ve kullanıcıya bunu farkettirmezsiniz. Dahası dilerseniz uygulamanın patladığı yerin stack trace'ini kendinize mail atabilir ve anında müdahale edebilirsiniz.

8 Ekim 2013 Salı

Android ile QR Kod ve Barkod Okuma

Mobil cihazların hayatımıza girmesiyle QR Kod ve Barkod okumak iş hayatında gereksinimler sıralamasında üst sıralara tırmanmaya devam ediyor. Bugün Play Store, Apple Store ve Windows Store'a baktığımız zaman birçok QR ve Barkod okuyabilen uygulamalara erişebilmemiz mümkün. Fakat bunlar sadece genel kullanımlarda işe yaramakta.

Kurumsal şirketler kendi mobil uygulamalarını yaptığı zaman özellikle stok takibi için barkod ve QR kod okumayı en hızlı ve pratik çözüm olarak görüyorlar. Böyle durumlarda QR kod ve barkod okuma spesifik istekler arasına giriyor ve internetten uygulama bulmak yerine biz yazılımcılara büyük iş düşüyor. Kurumsal şirketlerin mobil uygulamalarında kendi istekleri doğrultusunda çalışacak bir barkod okuyucu geliştirmek durumunda kalıyoruz.

QR kod ve barkod okuyabilmek için birçok implementasyon yapmamız gerekiyor. Öncelikle zbar, armeabi, armeabi-v7a, x86 gibi bir çok kütüphaneyi uygulamamıza entegre etmemiz gerekiyor. Bu kütüphanelerin uygulamanın libs klasöründe durması yeterli olacaktır. Blog sonunda örnek bir uygulamayı sizinle paylaşacağım.

Öncelikle AndroidManifest.xml dosyamızda kameranın açılması için gerekli izinleri vermemiz gerekiyor. Aşağıdaki permission'ı kopyalayıp yapıştıralım.



Şimdi gerekli implementasyonlara başlayabiliriz. Barkod okumak için kameranın açılacağı activity'nin xml'ini oluşturalım.




  

  

  


FrameLayout'ta kameramız açılacak. TextView'da da okuma başarılı olduğu takdirde okuduğu şeyi yazacak. Örneğin bir URL, bir yazı veya bir e-mail adresi, farketmez.

Şimdi kameramıza gerekli ayarları verip, FrameLayout'umuza yerleştirmek için CameraPreview.java dosyamızı oluşturuyoruz.

import java.io.IOException;

import android.content.Context;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private PreviewCallback previewCallback;
    private AutoFocusCallback autoFocusCallback;

    public CameraPreview(Context context, Camera camera,
                         PreviewCallback previewCb,
                         AutoFocusCallback autoFocusCb) {
        super(context);
        mCamera = camera;
        previewCallback = previewCb;
        autoFocusCallback = autoFocusCb;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);

        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            Log.d("DBG", "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // Camera preview released in activity
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        /*
         * If your preview can change or rotate, take care of those events here.
         * Make sure to stop the preview before resizing or reformatting it.
         */
        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        try {
         if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
          mCamera.setDisplayOrientation(90);        
         }
         else if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
          mCamera.setDisplayOrientation(0);
         }

            mCamera.setPreviewDisplay(mHolder);
            mCamera.setPreviewCallback(previewCallback);
            mCamera.startPreview();
            mCamera.autoFocus(autoFocusCallback);
        } catch (Exception e){
            Log.d("DBG", "Error starting camera preview: " + e.getMessage());
        }
    }
}

Bu ayarlarla kameramızın bulunduğu oryantasyona göre görüntülenme açısı belirlenecek. Şimdi barkodun okutulacağı MainActivity.java'ımızın içini dolduralım.

import net.sourceforge.zbar.Config;
import net.sourceforge.zbar.Image;
import net.sourceforge.zbar.ImageScanner;
import net.sourceforge.zbar.Symbol;
import net.sourceforge.zbar.SymbolSet;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;

public class MainActivity extends Activity {

 private Camera mCamera;
    private CameraPreview mPreview;
    private Handler autoFocusHandler;

    TextView scanText;
    Button scanButton;

    ImageScanner scanner;

    private boolean barcodeScanned = false;
    private boolean previewing = true;

    static {
        System.loadLibrary("iconv");
    } 

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        autoFocusHandler = new Handler();
        mCamera = getCameraInstance();

        /* Instance barcode scanner */
        scanner = new ImageScanner();
        scanner.setConfig(0, Config.X_DENSITY, 3);
        scanner.setConfig(0, Config.Y_DENSITY, 3);

        mPreview = new CameraPreview(this, mCamera, previewCb, autoFocusCB);
        FrameLayout preview = (FrameLayout)findViewById(R.id.cameraPreview);
        preview.addView(mPreview);

        scanText = (TextView)findViewById(R.id.scanText);

    }

    public void onPause() {
        super.onPause();
        releaseCamera();
    }

    /** A safe way to get an instance of the Camera object. */
    public static Camera getCameraInstance(){
        Camera c = null;
        try {
            c = Camera.open();
        } catch (Exception e){
        }
        return c;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            previewing = false;
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }
    }

    private Runnable doAutoFocus = new Runnable() {
            public void run() {
                if (previewing)
                    mCamera.autoFocus(autoFocusCB);
            }
        };

    PreviewCallback previewCb = new PreviewCallback() {
            public void onPreviewFrame(byte[] data, Camera camera) {
                Camera.Parameters parameters = camera.getParameters();
                Size size = parameters.getPreviewSize();

                Image barcode = new Image(size.width, size.height, "Y800");
                barcode.setData(data);

                int result = scanner.scanImage(barcode);
                
                if (result != 0) {
                    
                    SymbolSet syms = scanner.getResults();
                    for (Symbol sym : syms) {
                        scanText.setText("Barkod Sonucu: " + sym.getData());
                        scanText.setTextColor(Color.parseColor("#00AF03"));
                        barcodeScanned = true;
                        releaseCamera();
                        Intent intent = new Intent();
                        intent.putExtra("SCAN_RESULT", sym.getData());
                        setResult(RESULT_OK, intent);
                    }
                }
            }
        };

    // Mimic continuous auto-focusing
    AutoFocusCallback autoFocusCB = new AutoFocusCallback() {
            public void onAutoFocus(boolean success, Camera camera) {
                autoFocusHandler.postDelayed(doAutoFocus, 1000);
            }
        };

}

Barkod okuyucumuz hazır. Artık kameramızla hem barkod hem de kare barkod okuyabiliyor olacağız. Okuma işlemi başarılı olduğunda kameranın altında okunulan şeyi görebileceksiniz. Aşağıdaki linkten uygulamanın source kodunu paylaşıyorum. Siz okuyucuyu dilediğiniz şekle dönüştürebilirsiniz.

BarcodeReader'ı İndir

7 Ekim 2013 Pazartesi

Java ile Linux Server'a SSH Kodu Gönderme, Sonuçları İzleme

Kullanıcıların artık yazılımlarla kullanıcı dostu arayüzler ile iletişime geçmek istediği bir dünyada yaşıyoruz. Bu konu sadece son kullanıcıların değil, artık biz geliştiricilerin de talepleri arasına girmiş durumda.

Bu bağlamda Linux Server'ı örnek olarak gösterebiliriz. Putty gibi çeşitli araçlarla bağlanabilen bu serverlar DOS gibi komutlarla çalışmaktadır ve hayli uğraştırıcıdır. İşin içine büyük script'ler girdiği zaman gereksiz bir efor sarfedildiğini iddia edersek yanılmış olmayız.

Java ile Linux Server'ları birbiri ile iletişim haline soktuğumuzda bu sorunu ortadan kaldırabiliriz. Daha önceden hazırladığımız SSH kodunu Java ile gönderirsek ve bu aksiyonları Java'nın desteklediği arayüzlere bağlayabilirsek, bu işi tamamen arayüzlerle halledebiliriz.

Java ile Linux Server'a SSH kodu göndermek Ganymed isimli bir kütüphanenin yardımıyla mümkün. Buraya tıklayarak Ganymed'in güncel JAR dosyasını indirip projenize import edin.

Şimdi aşağıda linux serverımıza bir ls kodu göndereceğiz ve sonuçları konsola yazdıracağız.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

public class Main {

 private static int exitStatus;
 
 public static void main(String[] args) {
  // ipAdress: IP adresi
  // userName: Linux serverınızdaki kullanıcı adı
  // password: Kullanıcının şifresi
  // dir: ls kodunun sorgulayacağı directory
  // Bu değerler yerine kendi değerlerinizi girmeyi unutmayın
  ArrayList result = lsViaSSH("ipAddress", "userName", "password", "dir");
  
  if(result != null && result.size() > 0) {
   System.out.println("Results are listing below... \n");   
   for(String item : result) {
    System.out.println(item);
   }
  }
  else {
   System.out.println("Result not found");
  }
  
  System.out.println("\nExitStatus: " + exitStatus);
  System.out.println("\nSSH done!");
 }
 
 public static ArrayList lsViaSSH(String hostname, String username, String password, String dir) {
     ArrayList ls = new ArrayList();

     try {
         Connection conn = new Connection(hostname);

         System.out.println("Trying to connect " + hostname);
         conn.connect();
         System.out.println("Connected!");

         System.out.println("Authenticating...");
         boolean isAuthenticated = conn.authenticateWithPassword(username, password);

         if (isAuthenticated == false) {
          System.out.println("Authentication failed!");
             return null;
         }
         else {
          System.out.println("Authenticated!");
         }

         Session sess = conn.openSession();
         System.out.println("Session opened");
         
         System.out.println("Executing command: ls -r " + dir);
         sess.execCommand("ls -r " + dir);

         InputStream stdout = new StreamGobbler(sess.getStdout());
         InputStream stderr = new StreamGobbler(sess.getStderr());

         BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
         BufferedReader br1 = new BufferedReader(new InputStreamReader(stderr));
         
         while (true) {
             String line = br.readLine();
             if (line == null) {
              break;              
             }
             ls.add(line);
         }
         
         while (true) {
             String line = br1.readLine();
             if (line == null) {
              break;              
             }
             ls.add(line);
         }

         sess.close();
         conn.close();
         exitStatus = sess.getExitStatus();
     }
     catch (IOException e) {
         return null;
     }

     if(ls.size() == 0) {
      return null;      
     }

     return ls;
 }

lsViaSSH metoduna gerekli parametreleri gönderdikten sonra Ganymed kütüphanesini kullanarak önce connect oluyoruz. Daha sonra servera kullanıcı adı ve şifre ile authenticate oluyoruz. Hata çıkmazsa bağlantımızdan kendimize bir session açıyoruz.

Bu işlemlerden sonra istediğimiz SSH komutunu server'a gönderebiliriz. execCommand metodu ile SSH kodunu gönderiyoruz. Sonuçlar session'ımızın Stdout objesine düşüyor. Bu objeyi inputstream'e atarak BufferedReader ile sonuçları satır satır okuyarak ls isminde bir ArrayList'e atıyoruz. Bu liste main metodumuza dönüyor ve listeyi konsola yazdırıyoruz.

Eğer SSH kodunda hata yoksa session'ın içindeki ExitStatus objesi 0'a setleniyor, hata varsa hata koduna setleniyor. SSH kodunda hata olması halinde lsViaSSH metodu ArrayList'e hatanın açıklamalarını koyuyor ve konsola sonuçlar yerine hata mesajı gönderiyor.

Böylece Java ile gönderilen SSH kodunun sonuçlarını Java'da görebiliyor ve istediğimiz şekilde kullanıyoruz. Eğer kullanıcı adı ve şifre ile authenticate olmak yerine bir Public Key'iniz varsa Ganymed kütüphanesi bu tür authentication'ları destekleyecek gerekli implementasyonu yapmış durumda.

Ganymed kütüphanesi Linux Server üzerinde yapılan çalışmaları otomatize etmek için iddialı bir araç olacağa benziyor.

19 Haziran 2012 Salı

Java'da Dosya ve Klasör Oluşturma, Yönetme



Dosya ve klasör oluşturmak için Java'nın BufferedReader, File, ve FileWriter sınıflarını kullanmamız gerekmektedir. Aşağıdaki örnekte projenin içine bir klasör oluşturup, bu klasörün içine txt uzantılı bir dosya atıp, dosyanın içine birkaç cümle yazacağız.

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FolderAndFile {
    public File file;

    public static void main(String[] args) {
        FolderAndFile faf = new FolderAndFile();
        
        faf.createFolder();
        faf.createFile();
        faf.write();
    }
    
    private void createFolder() {
        File dir = new File("Klasor");
        dir.mkdir(); // Klasör oluşturuluyor
    }
    
    private void createFile() {
        try {
            file = new File("Klasor\\Dosya.txt");
            file.createNewFile(); // Dosya oluşturuluyor
        } catch (IOException ex) {
            Logger.getLogger(XmlBuildier.class.getName()).log(Level.SEVERE, null, ex);
        } 
    }

    private void write() {
        BufferedWriter bfrWriter = null;
        try {
            bfrWriter = new BufferedWriter(new FileWriter(file));
            
            bfrWriter.write("Ne Mutlu Türküm Diyene."); // Dosyaya yazılıyor
            bfrWriter.newLine(); // Yeni satıra geçiliyor
            bfrWriter.write("Hayatta En Hakiki Mürşit İlimdir.");
        } catch (IOException ex) {
            Logger.getLogger(XmlBuildier.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            //BufferedWriter kapatılıyor
            try {
                if (bfrWriter != null) {
                    bfrWriter.flush();
                    bfrWriter.close();
                }
            } catch (IOException ex) {
            }
        }
    }
}

Örnek uygulamamızda görüldüğü üzere createFolder metodu ile klasörümüz oluşturuldu, createFile metodu ile dosyamız klasörümüzün içine atıldı, write metodu ile de txt uzantılı dosyamıza Atatürk'ten iki cümle yazdık.

Dikkat ederseniz dosyaları oluştururken C: gibi bir dizin adresi vermedim. Dosyayı oluşturmak için "Klasor\\Dosya.txt" yazdım. Adresi bu şekilde yazdığınız takdirde söz konusu klasör, projenizin bulunduğu klasörün içinde oluşturulacaktır.

Eğer ki sizin belirleyeceğiniz bir dizine atmak istiyorsanız dizinin tam adresini yazabilirsiniz. Adresi yazarken atlatıcı \ bayraklarını koymayı unutmayın. Örneğin C:\Egemen diye bir klasörün içine Hamutcu adında bir XML dosyası açacaksınız. Bunun içine verilecek adres kısmına "C:\\Egemen\\Hamutcu.xml" yazmanız gerekmektedir. Aynı şey klasör oluşturmak için de geçerlidir.

Programı çalıştırdığınız zaman txt dosyasında bulacağınız çıktı şöyle olacaktır:

Ne Mutlu Türküm Diyene.
Hayatta En Hakiki Mürşit İlimdir.

Son olarak write metodundaki finally bölümünde flush ve close metodlarını çağırarak BufferedWriter objemizin içini boşaltarak RAM'i rahatlatıyoruz.

Bu şekilde klasör ve dosya oluşturma, içine yazı yazma işlemlerini halledebiliriz.

15 Haziran 2012 Cuma

Genişletilmiş ExpandableListView Rehberi



Android'in birçok projesinde ExpandableListView gösterimine ihtiyaç duyulmaktadır. Uygulamayı kategorilere ayırmak, kategorilerin altına menüler eklemek hem kullanıcıya daha pratik bir kullanım sunar hem de Android'in bilgisayarın aksine daha küçük ekranlarda çalışmasından ötürü kullanıcıya kullanım kolaylığı sağlar.

Fakat ExpandableListView sanıldığı aksine her projede farklı ihtiyaçlar gerektirir ve programlama tarafında oldukça zorlu bir süreçtir. Bu girdimizde diğer tutorial'ların yaptığı gibi tek bir ekrana tek bir expandablelistview gösterimi aksine piyasadaki birçok projeye uyum sağlayabilecek bir Activity'nin içinde herhangi bir yere yerleştirebilecek expandablelistview gösterilecektir.

Anlatacağımız örneğin program mimarisi Sağlık Bakanlığı için hazırladığım bir projeden alınmıştır.

Projemize başlarken ExpandableListView ile ilgili sınıfımızı yerleştirmek istediğimiz ekranın Activity sınıfının içine private halde yazacağımızı hatırlatmamızda fayda var.

Android projemizi oluşturalım, ben ExpandableListViewTutorial adında bir proje oluşturdum. Projemize Activity'mizin içine yerleştirilecek ExpandableAdapter adında BaseExpandableListAdapter sınıfından inherit edilmiş private bir sınıf yazarak başlayalım.

public class ExpandableListViewTutorialActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    
    private class ExpandableAdapterHA extends BaseExpandableListAdapter {
     
    }
}

Şimdi sıra yazdığımız sınıfın içini doldurmaya geldi. ExpandableListView'ın ihtiyaç duyduğu bütün metodları teker teker içine yazacağız. Eğer Eclipse IDE'si kullanıyorsanız proje bu halde hatalı duracaktır, Eclipse size bu metodları otomatikman yazıp içini doldurmaya hazır hale getirecektir.

public class ExpandableListViewTutorialActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    
    private class ExpandableAdapter extends BaseExpandableListAdapter {
     private String[] groups = { "Türk Erkek İsimleri", "Türk Kız İsimleri", "Yabancı Erkek İsimleri", "Yabancı Kız İsimleri" };
     private String[][] children = { { "Ahmet", "Mehmet", "Egemen", "Poyraz" }, { "Ayşe", "Fatma", "Fadime", "Sıdıka" }, { "John", "Kennedy", "Stejan", "Ivan" }, { "Jeniffer", "Angelica", "Roberta", "Eva", "Meg" } };

     @Override
     public Object getChild(int groupPosition, int childPosition) {
      // TODO Auto-generated method stub
      return children[groupPosition][childPosition];
     }

     @Override
     public long getChildId(int groupPosition, int childPosition) {
      // TODO Auto-generated method stub
      return childPosition;
     }

     public TextView getGenericView() {
            // Layout parameters for the ExpandableListView
            AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, 64);

            TextView textView = new TextView(ExpandableListViewTutorialActivity.this);
            textView.setLayoutParams(lp);
            // Center the text vertically
            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
            // Set the text starting position
            textView.setPadding(10, 0, 0, 0);
            return textView;
        }
     
     @Override
     public View getChildView(int groupPosition, int childPosition,
       boolean isLastChild, View convertView, ViewGroup parent) {
      // TODO Auto-generated method stub
      TextView textView = getGenericView();
            textView.setText(getChild(groupPosition, childPosition).toString());
            return textView;
     }

     @Override
     public int getChildrenCount(int groupPosition) {
      // TODO Auto-generated method stub
      return children[groupPosition].length;
     }

     @Override
     public Object getGroup(int groupPosition) {
      // TODO Auto-generated method stub
      return groups[groupPosition];
     }

     @Override
     public int getGroupCount() {
      // TODO Auto-generated method stub
      return groups.length;
     }

     @Override
     public long getGroupId(int groupPosition) {
      // TODO Auto-generated method stub
      return groupPosition;
     }

     @Override
     public View getGroupView(int groupPosition, boolean isExpanded,
       View convertView, ViewGroup parent) {
      // TODO Auto-generated method stub
      TextView textView = getGenericView();
            textView.setText(getGroup(groupPosition).toString());
            return textView;
     }

     @Override
     public boolean hasStableIds()
      // TODO Auto-generated method stub
      return true;
     }

     @Override
     public boolean isChildSelectable(int groupPosition, int childPosition) {
      // TODO Auto-generated method stub
      return true;
     }
    }
    }
}

Sınıfın içindeki kodlara dikkat ederseniz Override edilmiş bütün sınıflar ExpandableListView'ın temelde ihtiyaç duyduğu metodlardır. Override edilmemiş metodumuz listedeki her item'ın görünümünü belirlemede kullanılmaktadır.

Sınımızda kullandığımız arrayler ise listemizdeki başlıkları ve alt başlıkları belirlemektedir. Tek boyutlu array başlıkları, iki boyutlu array alt başlıkları göstermektedir. Mesela children[2][1]'deki değer, 3. başlığın 2. alt başlığını göstermektedir.

ExpandableAdapter'ımızı bitimiş olduk. Sıra gelelim asıl Activity'mizdeki düzenlemelere. Öncelikle ExpandableListView sınıfına ait private bir global değişken atıyoruz ve bu değişkeni XML'imizde bulundurduğumuz expandablelistview'ımıza atayıp içini doldurmak üzere ExpandableAdapter sınıfımıza entegre ediyoruz.

public class ExpandableListViewTutorialActivity extends Activity {
 private ExpandableListView list; //Global değişkenimiz
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final ExpandableAdapter mAdapter = new ExpandableAdapter(); //adaptörümüz
        list = (ExpandableListView) findViewById(R.id.expandableListView1); //Xml'imizdeki explistview'ımızı global değişkenimize atıyoruz
        list.setAdapter(mAdapter); //global değişkenimizi adaptörümüze bağlıyoruz
    }
}

Bu noktadan itibaren ExpandableListView'ımız uygulamamızda içindeki bütün itemlarıyla beraber görünecektir.

Şimdi gelelim bir kaç ince ayara. ExpandableListView'ımızda bir başlığı açtığımızda önceden açtığımız başlık açık kalıyor ve ekranda çok fazla yer kaplıyor, eğer bu telefonda çalışıyorsa zaten ekrandan taşıyor. Bir başlığı açtığımızda diğer bütün açık kalan başlıkları kapanmasının daha rahat bir kullanım sunacağı düşüncesiyle ExpandableListView'ımıza bir event yazalım.

public class ExpandableListViewTutorialActivity extends Activity {
 private ExpandableListView list; //Global değişkenimiz
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final ExpandableAdapter mAdapter = new ExpandableAdapter(); //adaptörümüz
        list = (ExpandableListView) findViewById(R.id.expandableListView1); //Xml'imizdeki explistview'ımızı global değişkenimize atıyoruz
        list.setAdapter(mAdapter); //global değişkenimizi adaptörümüze bağlıyoruz

        //ExpandableListView ile alakalı her event'i onCreate metoduna yazıyoruz
        list.setOnGroupExpandListener(new OnGroupExpandListener() {
        public void onGroupExpand(int groupPosition) {
            int len = mAdapter.getGroupCount();
            for (int i = 0; i < len; i++) {
                if (i != groupPosition) {
             list.collapseGroup(i);
         }
            }
        }
        });
    }
}
Bu meseleyi de hallettik, bu örnekle de kodun içinde de yazdığım gibi ExpandableListView ile ilgili bütün eventleri onCreate metoduna yazmamız gerekiyor. Buraya kadar geldik, peki her başlığın alt başlığına tıkladığında programımızın birşeyler yapmasını nereden halledeğiz? Bu sorunun cevabı elbet bir başka eventte yatıyor: onChildClick

Bu eventimizi de onCreate metodumuza yazıyoruz.
list.setOnChildClickListener(new OnChildClickListener() { 
         @Override
         public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
          // TODO Auto-generated method stub   
          if(groupPosition==3)
          {
           if(childPosition==0)
           {
            PokeAndroid();
           }
          }    
            return false;
           }

           private void PokeAndroid() {
            // TODO Auto-generated method stub
            Toast msg = Toast.makeText(ExpandableListViewTutorialActivity.this, "Poke", Toast.LENGTH_LONG);
                        msg.setGravity(Gravity.CENTER, msg.getXOffset() / 2, msg.getYOffset() / 2);
                        msg.show();
           }
        });
Gördüğünüz üzere eventi set edip if yapılarıyla ana başlığın (groupPosition) 4. sırasındaki itemın ilk alt başlığına (childPosition) ulaşarak buraya bir PokeAndroid metodu atadık. Yani Yabancı Kız İsimleri ana başlığındaki Jeniffer isimli alt başlığa tıkladığımızda uygulamamız 'Poke' yazılı bir toast mesajı gönderecektir. 

Artık tasarım mevzularına adım atabiliriz. Birçok projede ExpandableListView'daki standart oklar yerine tasarımcıların çizdiği oklar kullanılır ve genelde biri sağa, öteki aşağı bakan iki oktur ki basıldığında sağa bakan okun aşağı bakması istenir. Programa bu meseleyi hallettirmek için hileli bir yol diyebileceğimiz bir yöntemi anlatacağım.

Öncelikle okların bulunduğu iki resmi sağa bakan ok için 'arrowright', aşağı bakan ok için 'arrowdown' olarak isimlendirerek projemizdeki drawable klasörüne atın. Peşine yine drawable klasörüne indicator adında bir Android XML'i oluşturun, içeriğini tamamen silin ve aşağıdaki içeriği yapıştırın.
  
        
   
          
    
Daha sonra ExpandableListView'ımızın bulunduğu XML'i açın ve aşağıdaki attributeların eklendiği hale getirin.

        
Görüldüğü üzere groupIndicator attribute'una sıradan bir resim yerine bir xml atayarak dinamik bir şekilde çalışmasını başarmış olduk. Artık uygulamanızda kendi tasarımlarınız istediğiniz biçimde çalışacaktır.

Fakat bu noktada Android'in kötü bir sürpriziyle karşılaşabilirsiniz. Eğer resimleriniz yeterince küçükse, Android resimleri içeriği dolduracak biçimde genişletecek ve resimlerinizi pikselli bir biçimde gösterecektir. Bu sorunu aşmak için resimlerinizin arka planındaki transparent bölgeyi genişletmeniz yani resmin gerçek boyutunu yeterince büyütmelisiniz. Böylece oklarınız listenin içeriğine tam oturacak ve genişletme işleminden etkilenmeyecektir.

Gelelim ExpandableListView'ın en can alıcı bölümüne. Yukarıdaki XML'imizde indicatorLeft ve indicatorRight attribute'lar gözünüzden kaçmamıştır. Bazı projeler okların sol yerine sağda olmasını istemektedir. Android standart olarak bu okları sola yapıştırır, eğer sağa yapıştırmasını istiyorsanız bu iki attribute'un devreye girmesi gerekmektedir. Fakat bizim projemizde listemiz ekranın herhangi bir yerinde olabileceği için buraya vereceğimiz değerler asırlık aşçıların göz kararı tuz eker misali makul değerler vererek oklarımızı istediğimiz konuma yerleştirebiliriz.

Mesela benim projemde liste en solda 290dp'lik bir layout'un içinde olduğu için 220-280 değerler oklarımı listenin tam sağına oturttu. Siz de mantıklı değerler vererek oklarınızı sağa veya sola istediğiniz ölçüde çekebilirsiniz.

ExpandableListView rehberimizin burada sonuna gelmiş bulunuyoruz. Programın sorucecode'unu aşağıda paylaşıyorum. Kullanıma hazır hale getirdiğimiz listemizi programınızdaki Activity xml'inde istediğiniz yere yerleştirebilirsiniz.
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ExpandableListView.OnGroupExpandListener;
import android.widget.TextView;
import android.widget.Toast;

public class ExpandableListViewTutorialActivity extends Activity {
 private ExpandableListView list;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final ExpandableAdapter mAdapter = new ExpandableAdapter();
        list = (ExpandableListView) findViewById(R.id.expandableListView1);
        list.setAdapter(mAdapter);
        
        //ExpandableListView ile alakalı her event'i onCreate metoduna yazıyoruz
        list.setOnGroupExpandListener(new OnGroupExpandListener() {
        public void onGroupExpand(int groupPosition) {
            int len = mAdapter.getGroupCount();
            for (int i = 0; i < len; i++) {
                if (i != groupPosition) {
             list.collapseGroup(i);
         }
            }
        }
        });
    
        
        list.setOnChildClickListener(new OnChildClickListener() { 
         @Override
         public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
          // TODO Auto-generated method stub   
          if(groupPosition==3)
          {
           if(childPosition==0)
           {
            PokeAndroid();
           }
          }    
            return false;
           }

           private void PokeAndroid() {
            // TODO Auto-generated method stub
            Toast msg = Toast.makeText(ExpandableListViewTutorialActivity.this, "Poke", Toast.LENGTH_LONG);
                        msg.setGravity(Gravity.CENTER, msg.getXOffset() / 2, msg.getYOffset() / 2);
                        msg.show();
           }
        });
    }
    
    private class ExpandableAdapter extends BaseExpandableListAdapter {
     private String[] groups = { "Ameliyat Hizmetleri", "Cerrahi Uygulamalar", "Diş Hastalıkları", "Hastane Genel Hizmetleri*", "Muayene, Konsültasyon ve Takip", "Paket Uygulamaları" };
     private String[][] children = { { "Ameliyat" }, { "Cerrah" }, { "Diş Eti Hastalıkları" }, { "Genel Uygulamalar ve Girişimler*", "Servis Hizmetleri" }, { "Tedavi" }, { "AP / Poliklinik Muayene Paketi", "Check-up Programları", "PK / Cerrahi Uyulamalar", "Fizik Tedavi ve Rehabilitasyon" } };

     @Override
     public Object getChild(int groupPosition, int childPosition) {
      // TODO Auto-generated method stub
      return children[groupPosition][childPosition];
     }

     @Override
     public long getChildId(int groupPosition, int childPosition) {
      // TODO Auto-generated method stub
      return childPosition;
     }

     public TextView getGenericView() {
            // Layout parameters for the ExpandableListView
            AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, 64);

            TextView textView = new TextView(ExpandableListViewTutorialActivity.this);
            textView.setLayoutParams(lp);
            // Center the text vertically
            textView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
            // Set the text starting position
            textView.setPadding(10, 0, 0, 0);
            return textView;
        }
     
     @Override
     public View getChildView(int groupPosition, int childPosition,
       boolean isLastChild, View convertView, ViewGroup parent) {
      // TODO Auto-generated method stub
      TextView textView = getGenericView();
            textView.setText(getChild(groupPosition, childPosition).toString());
            return textView;
     }

     @Override
     public int getChildrenCount(int groupPosition) {
      // TODO Auto-generated method stub
      return children[groupPosition].length;
     }

     @Override
     public Object getGroup(int groupPosition) {
      // TODO Auto-generated method stub
      return groups[groupPosition];
     }

     @Override
     public int getGroupCount() {
      // TODO Auto-generated method stub
      return groups.length;
     }

     @Override
     public long getGroupId(int groupPosition) {
      // TODO Auto-generated method stub
      return groupPosition;
     }

     @Override
     public View getGroupView(int groupPosition, boolean isExpanded,
       View convertView, ViewGroup parent) {
      // TODO Auto-generated method stub
      TextView textView = getGenericView();
            textView.setText(getGroup(groupPosition).toString());
            return textView;
     }

     @Override
     public boolean hasStableIds() {
      // TODO Auto-generated method stub
      return true;
     }

     @Override
     public boolean isChildSelectable(int groupPosition, int childPosition) {
      // TODO Auto-generated method stub
      return true;
     }
    }
}
ExpandableListView konusunda yaşadığınız her türlü sorun için benimle iletişim kurabilirsiniz. Sorunlarınızı aşağıdaki yorum kısmına veya egmnhmtc@gmail.com adresine gönderebilirsiniz.

9 Haziran 2012 Cumartesi

Android'de Özel CheckBox Ataması



Kendi tasarladığımız CheckBox resimlerimizi Android projemize atamak istiyorsak Android'in selector özelliğini kullanmamız gerekmektedir. Selector bir XML dosyasıdır ve drawable klasöründe bulunması gerekmektedir. Öncelikle resimlerimizi drawable klasörüne atıp XML dosyamızı oluşturalım. Dosyamızın ismini checkbox_selector olarak belirleyelim.







Gördüğünüz üzere selector XML'i ile CheckBox'un her iki durumunda gösterilecek resimleri atamış olduk. android:drawable="@drawable/checked" ve android:drawable="@drawable/unchecked" attribute'larındaki checked ve unchecked, drawable klasörümüze attığımız resimlerimizi göstermektedir. Eğer sizde farklıysa lütfen değiştirmeyi unutmayınız.

Şimdi sıra geldi selector'umuzu CheckBox'umuza atama kısmına.



Gördüğünüz üzere android:button="@drawable/checkbox_selector" attribute'unda CheckBox'umuza sıradan bir resim atamak yerine dinamik çalışan selector XML'ini atamış olduk. Böylece kendi tasarladığımız CheckBox'larımızı projelerimizde kullanabilmekteyiz.

Android'de Component'ları Sağa Kaydırmak



Android'de Button, TextView veya herhangi bir layout'un içine alınmış toplu component'ları sağa kaydırma ihtiyacı birçok projede karşınıza çıkar. Bu noktada RelativeLayout yardımımıza koşmaktadır. Sağa kaydırmak istediğimiz component'ları RelativeLayout tagine alıp gerekli ayarlamaları yaptığımızda istediğimiz sonuca ulaşacağız.

Aşağıda verdiğim kodda yan yana iki butonu bir LinearLayout içine alıp topluca sağa kaydırma işlemi yapılmıştır.



   

Görüldüğü üzere butonlar orentation'u horizontal olarak ayarlanmış bir LinearLayout'un içine alınarak yanyana dizilmiştir. Bu LinearLayout'u da gravity'si top yapılmış bir RelativeLayout'un içine alınıyor ve LinearLayout'a android:layout_alignParentRight="true" attribute'u atanarak sağa kaydırılıyor ve android:layout_centerVertical="true" attribute'u atanarak da dikey olarak ortalanmasını sağlıyor.

Bu şekilde sağa kaydırma işlemini başarıyla tamamlamış bulunuyoruz.