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.

4 Haziran 2012 Pazartesi

Adım Adım Java ile MySQL Bağlantısı


Java'da MySQL bağlantısı oluşturmak sanıldığı kadar zor değildir aksine diğer veritabanlarına göre oldukça kolaydır, yalnız bu veritabanı bağlantı mimarisini verimli bir şekilde oluşturmak ileride yazılımda karşılaşacağınız veritabanı sorunlarını; SQL Expection ve benzeri hataları minimuma indirecektir.

Bu girdimizde benim kendi oluşturduğum bir mimariyle ilerleyeceğiz. Öncelikle MySQL'in resmi sitesinden bağlantı sürücüsünü indirmemiz gerekmektedir. İndirmek için bir defalığına kayıt yaptırmanız yeterlidir. İndirdikten sonra sıkıştırılmış dosyanın içindeki jar uzantılı dosyayı projenize import edin.

Sürücümüzü projemize entegre ettikten sonra mimarimize giriş yapabiliriz. Başlarken mimarimizi kısaca özetlememiz yararımıza olacaktır. Öncelikle veritabanıyla ilgili bütün işlemleri ayrı bir pakette tutmanızı öneririm. Bir defa oluşturacağınız sınıfları bu paketin içine atarsanız hem diğer sınıflarla karışmayacak hem de bir daha girip bakmanıza gerek kalmayacaktır. İkincisi bağlantı için DAO isminde bir sınıf oluşturacağız bağlantılar bu sınıftan gerçekleştirilecek. Bununla birlikte veritabanımızdaki her tablo için ayrı bir sınıf oluşturacağız böylece tablolarla ilgili her türlü işlemi sınıflarda metod aramadan kolayca yapabileceğiz. Mimarimize Database isimli bir paket oluşturarak başlayalım.

Paketi oluşturduktan sonra paketimizin bel kemiği DAO isimli sınıfımızı oluşturalım. Öncelikle dört adet global değişkene ihtiyacımız vardır.

private static Connection con;
private static String dbURL= "jdbc:mysql://localhost:3306/dbName";
private static String username = "root";
private static String password = "root";

Şimdi bu değişkenler ne işimize yarayacak inceleyelim.

Connection: Import ettiğimiz sürücümüzle bağlantı kuran değişken.
dbURL: Veritabanımızla bağlantı kurmasını sağlayan link.
username: Veritabanımızın kullanıcı adı.
password: Veritabanımızın şifresi.

Burada dikkat etmeniz gereken nokta dbURL değişkenindeki dbName yerine kendi oluşturduğunuz veritabanınızın ismini girmeniz gerektiğidir. Aksi takdirde veritabanı ile bağlantı kuramazsınız!

Global değişkenlerimizi oluşturduktan sonra iki adet metod girmemiz gerekmektedir.

public static Statement dbConnect()
{
    try {
        Class.forName("com.mysql.jdbc.Driver").newInstance(); //My sql JDBC sürücüsü kullanılıyor
        con = (Connection) DriverManager.getConnection(DAO.dbURL, DAO.username,DAO.password);//bağlantı açılıyor
        return (Statement) con.createStatement();
    } catch (SQLException ex) {
        Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    } catch (ClassNotFoundException ex) {
        Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    } catch (InstantiationException ex) {
        Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    } catch (IllegalAccessException ex) {
        Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    }
}

public void dbClose() throws Exception
{
    con.close();
}

Şimdi bu metodlar neler yapıyor inceleyelim.

dbConnect: Bağlantı session'u açılıyor.
dbClose: Bağlantı session'ları kapatılıyor.

Böylece sınıfımızı tamamlamış olduk, aşağıda yazdığımız DAO sınıfımızın son hali gösterilmektedir.

package Database;

import com.mysql.jdbc.Statement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Egemen
 */
public class DAO {
    private static Connection con;
    private static String dbURL= "jdbc:mysql://localhost:3306/dbName";
    private static String username = "root";
    private static String password = "root";
    
    public static Statement dbConnect()
    {
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance(); //My sql JDBC sürücüsü kullanılıyor
            con = (Connection) DriverManager.getConnection(DAO.dbURL, DAO.username,DAO.password);//bağlantı açılıyor
            return (Statement) con.createStatement();
        } catch (SQLException ex) {
            Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (InstantiationException ex) {
            Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (IllegalAccessException ex) {
            Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }
    
    public void dbClose() throws Exception
    {
        con.close();
    }
}

Burada dikkat edilmesi gereken husus import ettiğimiz kütüphanenin Statement'ını kullanmaktır. Kesinlikle java.sql.Statement isim uzayını import etmeyin!

DAO sınıfımızı oluşturduktan sonra geriye tabloların sınıfı oluşturmak kalıyor. Biz örnek olarak Customer isimli bir tablonun sınıfını oluşturacağız.

Customer sınıfımızda bir adet global değişkene ihtiyacımız vardır.

public static Statement st = DAO.dbConnect();

Bu değişken yazdığımız DAO sınıfını kullanarak veritabanı ile Customer sınıfı arasında bir bağlantı session'u açmaktadır.

Bundan sonra veritabanında select/insert/update/delete işlemlerini gerçekleştiren metodları yazarak sınıfımızı tamamlayacağız.

public static ResultSet SelectCustomer(String QSelect)
{
    try
    {
        return st.executeQuery(QSelect);
    }
    catch (SQLException ex) {
        Logger.getLogger(Customer.class.getName()).log(Level.SEVERE, null, ex);
        return null;
    }
}

public static void UpdateCustomer(String QUpdate)
{
    try
    {
        st.executeUpdate(QUpdate);
    }
    catch (SQLException ex) {
        Logger.getLogger(Customer.class.getName()).log(Level.SEVERE, null, ex);
    }
}
   
public static void DeleteCustomer(int _iStatusID)
{
    try
    {
        String QDelete = "DELETE FROM Customer WHERE customerID = " + _iStatusID;
        st.executeUpdate(QDelete);
    }
    catch (SQLException ex) {
        Logger.getLogger(Customer.class.getName()).log(Level.SEVERE, null, ex);
    }
}

Şimdi bu metodlar neler yapıyor inceleyelim.

SelectCustomer: Select sorgusu gönderir. Aldığı string parametresine bütün Select sorgusunu yazınız. ResultSet olarak döner.
UpdateCustomer: Insert ve Update sorgularını gönderir. Aldığı string parametresine bütün sorguyu yazınız.
DeleteCustomer: Delete sorgusunu gönderir. Aldığı integer parametreye silmek istediğiniz satırın primary key numarasını giriniz.

Böylece tablo sınıfımızı da bitirdik. Aşağıda Customer sınıfının son hali gösterilmektedir.

package Database;

import com.mysql.jdbc.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Egemen
 */
public class Customer {
    public static Statement st = DAO.dbConnect();
    
    public static ResultSet SelectCustomer(String QSelect)
    {
        try
        {
            return st.executeQuery(QSelect);
        }
        catch (SQLException ex) {
            Logger.getLogger(Customer.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public static void UpdateCustomer(String QUpdate)
    {
        try
        {
            st.executeUpdate(QUpdate);
        }
        catch (SQLException ex) {
            Logger.getLogger(Customer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public static void DeleteCustomer(int _iStatusID)
    {
        try
        {
            String QDelete = "DELETE FROM Customer WHERE customerID = " + _iStatusID;
            st.executeUpdate(QDelete);
        }
        catch (SQLException ex) {
            Logger.getLogger(Customer.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Bu şekilde diğer tablo sınıflarını da oluşturarak Database paketimizi sorunsuz bir şekilde tamamlamış olacağız. Burada da DAO sınıfında olduğu gibi doğru Statement isim uzayını import etmeyi unutmuyoruz. Artık veritabanımıza istediğimiz bir sınıfta işlem yapmak istediğimiz tablonun sınıfındaki metodları static bir şekilde çağırarak işlemlerimizi gerçekleştirebiliriz.

ResultSet rsCustomer = Customer.Select("SELECT * FROM Customer");

while(rsCustomer.next())
{
    //Select sorgusuyla gelen tablonun içindeki satırlarda sırayla ilerleyerek ilgili işlemler burada yapılır
}

Customer.Update("UPDATE Customer Set customerName='Ahmet' WHERE customerID=3"); //customerID'si 3 olan müşterinin adı Ahmet olarak değiştiriliyor
Customer.Delete(3); //customerID'si 3 olan müşteri tablodan siliniyor

Görüldüğü üzere yukarıdaki örnek işlemlerle veritabanı bağlantısı hızla kurulup istediğimiz işlemleri bir kaç satır koda indirgemeyi başarmış olduk.

MySQL veritabanıyla bağlantı kurarken 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.