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.

2 yorum:

  1. Merhaba,

    public class MainActivity extends Activity {

    private String[] HazirMesajlar =
    {"Nasılsın",
    "Müsait Değilim",

    şeklinde hazır mesajlarım var bunları listview ile yazdım.. İlk açılan sayfada bu hazır mesajlar bulunacak şekilde hazır şu an herhangi birine tıkladığımda minik bir mesaj alıyorum tamam butonuna basıp kapatabiliyorum.. Benim amacım bir sonraki loyouttaki edittext içine bu seçtiğim mesajı yazdırmak bir türlü başaramadım yardımcı olursanız sevinirim..Teşekkürler..

    YanıtlaSil
  2. Affedersiniz tüm kodu koymayı unutmuşum..


    package com.example.serdar.plkmsg;

    import android.content.DialogInterface;
    import android.support.v7.app.AlertDialog;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.AdapterView;
    import android.widget.ArrayAdapter;
    import android.widget.EditText;
    import android.widget.ListView;
    import android.app.Activity;

    public class MainActivity extends Activity {

    private String[] HazirMesajlar =
    {"Nasılsın",
    "Müsait Değilim",

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    ListView listemiz=(ListView) findViewById(R.id.listView1);

    ArrayAdapter veriAdaptoru=new ArrayAdapter
    (this, android.R.layout.simple_list_item_1, android.R.id.text1,HazirMesajlar);

    listemiz.setAdapter(veriAdaptoru);

    listemiz.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView parent, View view, int position, long id) {

    AlertDialog.Builder diyalogOlusturucu = new AlertDialog.Builder(MainActivity.this);
    diyalogOlusturucu.setMessage(HazirMesajlar[position])
    .setCancelable(false)
    .setPositiveButton("Tamam", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
    dialog.dismiss();
    }
    });
    diyalogOlusturucu.create().show();

    YanıtlaSil