Sunday, 25 August 2013

Android Custom ListView with Seperate Header

In this tutorial we will display a custom listview with header like,

........

Android Layout Files.

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
  <ListView
      android:id="@+id/Listview"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent" />
   
</LinearLayout>

list_item_header.xml

<?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/list_header_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

home_list_item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="5dip" >
    <LinearLayout
        android:id="@+id/thumbnail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginRight="5dip"
        android:padding="3dip" >
        <ImageView
            android:id="@+id/list_image"
            android:layout_width="50dip"
            android:layout_height="50dip"
            android:scaleType="fitXY"
            android:src="@drawable/ic_launcher" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/detailsLL"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@+id/BtnClose"
        android:layout_toRightOf="@+id/thumbnail"
        android:orientation="vertical" >
        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Name"
            android:textColor="#040404"
            android:textSize="15dip"
            android:textStyle="bold"
            android:typeface="sans" />
        <TextView
            android:id="@+id/description"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="1dip"
            android:maxLines="2"
            android:text="Description"
            android:textColor="#343434"
            android:textSize="10dip" />
    </LinearLayout>

</RelativeLayout>


Java Files.

MainActivity.Java

import java.util.ArrayList;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Toast;

public class MainActivity extends Activity {

    private ListView listView;
    private  HomeListAdapter HomeListAdapter;
    private SeparatedListAdapter adapter;
    private ArrayList<HomeItem> HomeItemList;
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
        listView = (ListView) findViewById(R.id.Listview);

        listView.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position,
                    long id) {
                HomeItem homeItem = (HomeItem) adapter.getItem(position);
                Toast.makeText(getBaseContext(), homeItem.getHomeItemName().toString(), Toast.LENGTH_LONG).show();
                System.out.println("Selected Item : " +  homeItem.getHomeItemID());
               
            }
        });
       
        PopulateHomePageItemList();   
    }

   
   
    private void PopulateHomePageItemList(){
        adapter = new SeparatedListAdapter(this);
       
        HomeItemList = new ArrayList<HomeItem>();
        for(int i =0; i< 4 ; i++){
            HomeItem homeItem = new HomeItem();
            homeItem.setHomeItemID(i);
            homeItem.setHomeItemName("Name"+i);
            homeItem.setHomeItemDescription("Description"+i);
            homeItem.setImageUrl("https://www.google.co.in/images/srpr/logo4w.png");
            HomeItemList.add(homeItem);
        }
        HomeListAdapter = new  HomeListAdapter(getApplicationContext(), 0, HomeItemList);
        if(HomeListAdapter!= null)
            adapter.addSection(" ONE ", HomeListAdapter );
       
       
        HomeItemList = new ArrayList<HomeItem>();
        for(int i = 5; i< 8 ; i++){
            HomeItem homeItem = new HomeItem();
            homeItem.setHomeItemID(i);
            homeItem.setHomeItemName("Name"+i);
            homeItem.setHomeItemDescription("Description"+i);
            homeItem.setImageUrl("https://www.google.co.in/images/srpr/logo4w.png");
            HomeItemList.add(homeItem);
        }
        HomeListAdapter = new  HomeListAdapter(getApplicationContext(), 0, HomeItemList);
        if(HomeListAdapter!= null)
            adapter.addSection(" TWO ", HomeListAdapter );
       
       
        HomeItemList = new ArrayList<HomeItem>();
        for(int i =9; i< 12 ; i++){
            HomeItem homeItem = new HomeItem();
            homeItem.setHomeItemID(i);
            homeItem.setHomeItemName("Name"+i);
            homeItem.setHomeItemDescription("Description"+i);
            homeItem.setImageUrl("https://www.google.co.in/images/srpr/logo4w.png");
            HomeItemList.add(homeItem);
        }
        HomeListAdapter = new  HomeListAdapter(getApplicationContext(), 0, HomeItemList);
        if(HomeListAdapter!= null)
            adapter.addSection(" THREE ", HomeListAdapter );
       
        listView.setAdapter(adapter);
    }
   
}

HomeListAdapter.java

import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class HomeListAdapter extends ArrayAdapter<HomeItem> implements OnClickListener {
    public Context context;
    public ArrayList<HomeItem> HomeItemList;
    public ImageLoader imageLoader;
   
    public HomeListAdapter(Context context, int resource, ArrayList<HomeItem> HomeItemLst) {
        super(context, resource, HomeItemLst);
        this.context = context;
        this.HomeItemList = HomeItemLst;
        imageLoader = new ImageLoader(context);
    }
   
    @Override
    public int getCount() {
        if (HomeItemList != null)
            return HomeItemList.size();

        return 0;
    }

    @Override
    public HomeItem getItem(int position) {
        return HomeItemList.get(position);
    }
   
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        View myConvertView = null;
        try {
            final HomeItem HomeItem = HomeItemList.get(position);
            myConvertView = convertView;
            if (myConvertView == null) {
                LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                myConvertView = inflater.inflate(R.layout.home_list_item_view, null);
            }
            ImageView CatImage = (ImageView) myConvertView.findViewById(R.id.list_image);
            TextView CatHeader = (TextView) myConvertView.findViewById(R.id.name);
            TextView CatTitle = (TextView) myConvertView.findViewById(R.id.description);
           
            CatHeader.setText(HomeItem.getHomeItemName());
            CatTitle.setText(HomeItem.getHomeItemDescription());
           
            imageLoader.DisplayImage(HomeItem.getImageUrl(), CatImage);
           
        } catch (Exception e) {
             e.printStackTrace();
        }
        return myConvertView;
    }
   
    @Override
    public long getItemId(int position) {
        return position;
    }

    public void onClick(View v) {
        // TODO Auto-generated method stub
       
    }
   
}

SeparatedListAdapter.java

import java.util.LinkedHashMap;
import java.util.Map;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;

public class SeparatedListAdapter extends BaseAdapter
{
    public final Map<String, Adapter> sections = new LinkedHashMap<String, Adapter>();
    public final ArrayAdapter<String> headers;
    public final static int TYPE_SECTION_HEADER = 0;

    public SeparatedListAdapter(Context context)
        {
            headers = new ArrayAdapter<String>(context, R.layout.list_item_header);
        }

    public void addSection(String section, Adapter adapter)
        {
            this.headers.add(section);
            this.sections.put(section, adapter);
        }

    public Object getItem(int position)
        {
            for (Object section : this.sections.keySet())
                {
                    Adapter adapter = sections.get(section);
                    int size = adapter.getCount() + 1;

                    // check if position inside this section
                    if (position == 0) return section;
                    if (position < size) return adapter.getItem(position - 1);

                    // otherwise jump into next section
                    position -= size;
                }
            return null;
        }

    public int getCount()
        {
            // total together all sections, plus one for each section header
            int total = 0;
            for (Adapter adapter : this.sections.values())
                total += adapter.getCount() + 1;
            return total;
        }

    @Override
    public int getViewTypeCount()
        {
            // assume that headers count as one, then total all sections
            int total = 1;
            for (Adapter adapter : this.sections.values())
                total += adapter.getViewTypeCount();
            return total;
        }

    @Override
    public int getItemViewType(int position)
        {
            int type = 1;
            for (Object section : this.sections.keySet())
                {
                    Adapter adapter = sections.get(section);
                    int size = adapter.getCount()+1;

                    // check if position inside this section
                    if (position == 0) return TYPE_SECTION_HEADER;
                    if (position < size) return type + adapter.getItemViewType(position - 1);

                    // otherwise jump into next section
                    position -= size;
                    type += adapter.getViewTypeCount();
                }
            return -1;
        }

    public boolean areAllItemsSelectable()
        {
            return false;
        }

    @Override
    public boolean isEnabled(int position)
        {
            return (getItemViewType(position) != TYPE_SECTION_HEADER);
        }

    public View getView(int position, View convertView, ViewGroup parent)
        {
            int sectionnum = 0;
            for (Object section : this.sections.keySet())
                {
                    Adapter adapter = sections.get(section);
                    int size = adapter.getCount() + 1;

                    // check if position inside this section
                    if (position == 0) return headers.getView(sectionnum, convertView, parent);
                    if (position < size) return adapter.getView(position - 1, convertView, parent);

                    // otherwise jump into next section
                    position -= size;
                    sectionnum++;
                }
            return null;
        }

    public long getItemId(int position)
        {
            return position;
        }

}

HomeItem.java

public class HomeItem {
    private String ImageUrl;
    private int HomeItemID;
    private String HomeItemName;
    private String HomeItemDescription;


    public String getImageUrl() {
        return ImageUrl;
    }
    public void setImageUrl(String imageUrl) {
        this.ImageUrl = imageUrl;
    }
   
    public int getHomeItemID() {
        return HomeItemID;
    }
    public void setHomeItemID(int ID) {
        this.HomeItemID = ID;
    }

    public String getHomeItemName() {
        return HomeItemName;
    }
    public void setHomeItemName(String Name) {
        this.HomeItemName = Name;
    }
   

    public String getHomeItemDescription() {
        return HomeItemDescription;
    }
    public void setHomeItemDescription(String Description) {
        this.HomeItemDescription = Description;
    }
   
}

ImageLoader.java

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.widget.ImageView;

public class ImageLoader {
    MemoryCache memoryCache=new MemoryCache();
    FileCache fileCache;
    private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
    ExecutorService executorService;
    Handler handler=new Handler();//handler to display images in UI thread
   
    public ImageLoader(Context context){
        fileCache=new FileCache(context);
        executorService=Executors.newFixedThreadPool(5);
    }
   
    final int stub_id=R.drawable.ic_launcher;
    public void DisplayImage(String url, ImageView imageView)
    {
        imageViews.put(imageView, url);
        Bitmap bitmap=memoryCache.get(url);
        if(bitmap!=null)
            imageView.setImageBitmap(bitmap);
        else
        {
            queuePhoto(url, imageView);
            imageView.setImageResource(stub_id);
        }
    }
       
    private void queuePhoto(String url, ImageView imageView)
    {
        PhotoToLoad p=new PhotoToLoad(url, imageView);
        executorService.submit(new PhotosLoader(p));
    }
   
    private Bitmap getBitmap(String url)
    {
        File f=fileCache.getFile(url);
       
        //from SD cache
        Bitmap b = decodeFile(f);
        if(b!=null)
            return b;
       
        //from web
        try {
            Bitmap bitmap=null;
            URL imageUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            conn.setInstanceFollowRedirects(true);
            InputStream is=conn.getInputStream();
            OutputStream os = new FileOutputStream(f);
            Utils.CopyStream(is, os);
            os.close();
            conn.disconnect();
            bitmap = decodeFile(f);
            return bitmap;
        } catch (Throwable ex){
           ex.printStackTrace();
           if(ex instanceof OutOfMemoryError)
               memoryCache.clear();
           return null;
        }
    }

    //decodes image and scales it to reduce memory consumption
    private Bitmap decodeFile(File f){
        try {
            //decode image size
            BitmapFactory.Options o = new BitmapFactory.Options();
            o.inJustDecodeBounds = true;
            FileInputStream stream1=new FileInputStream(f);
            BitmapFactory.decodeStream(stream1,null,o);
            stream1.close();
           
            //Find the correct scale value. It should be the power of 2.
            final int REQUIRED_SIZE=70;
            int width_tmp=o.outWidth, height_tmp=o.outHeight;
            int scale=1;
            while(true){
                if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                    break;
                width_tmp/=2;
                height_tmp/=2;
                scale*=2;
            }
           
            //decode with inSampleSize
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize=scale;
            FileInputStream stream2=new FileInputStream(f);
            Bitmap bitmap=BitmapFactory.decodeStream(stream2, null, o2);
            stream2.close();
            return bitmap;
        } catch (FileNotFoundException e) {
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
   
    //Task for the queue
    private class PhotoToLoad
    {
        public String url;
        public ImageView imageView;
        public PhotoToLoad(String u, ImageView i){
            url=u;
            imageView=i;
        }
    }
   
    class PhotosLoader implements Runnable {
        PhotoToLoad photoToLoad;
        PhotosLoader(PhotoToLoad photoToLoad){
            this.photoToLoad=photoToLoad;
        }
       
        public void run() {
            try{
                if(imageViewReused(photoToLoad))
                    return;
                Bitmap bmp=getBitmap(photoToLoad.url);
                memoryCache.put(photoToLoad.url, bmp);
                if(imageViewReused(photoToLoad))
                    return;
                BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);
                handler.post(bd);
            }catch(Throwable th){
                th.printStackTrace();
            }
        }
    }
   
    boolean imageViewReused(PhotoToLoad photoToLoad){
        String tag=imageViews.get(photoToLoad.imageView);
        if(tag==null || !tag.equals(photoToLoad.url))
            return true;
        return false;
    }
   
    //Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable
    {
        Bitmap bitmap;
        PhotoToLoad photoToLoad;
        public BitmapDisplayer(Bitmap b, PhotoToLoad p){bitmap=b;photoToLoad=p;}
        public void run()
        {
            if(imageViewReused(photoToLoad))
                return;
            if(bitmap!=null)
                photoToLoad.imageView.setImageBitmap(bitmap);
            else
                photoToLoad.imageView.setImageResource(stub_id);
        }
    }

    public void clearCache() {
        memoryCache.clear();
        fileCache.clear();
    }

}

FileCache.java

import java.io.File;
import android.content.Context;

public class FileCache {

    private File cacheDir;
   
    public FileCache(Context context){
        //Find the dir to save cached images
        if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
            cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"LazyList");
        else
            cacheDir=context.getCacheDir();
        if(!cacheDir.exists())
            cacheDir.mkdirs();
    }
   
    public File getFile(String url){
        //I identify images by hashcode. Not a perfect solution, good for the demo.
        String filename=String.valueOf(url.hashCode());
        //Another possible solution (thanks to grantland)
        //String filename = URLEncoder.encode(url);
        File f = new File(cacheDir, filename);
        return f;
       
    }
   
    public void clear(){
        File[] files=cacheDir.listFiles();
        if(files==null)
            return;
        for(File f:files)
            f.delete();
    }

}

MemoryCache.java

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import android.graphics.Bitmap;
import android.util.Log;

public class MemoryCache {
    private static final String TAG = "MemoryCache";
    private Map<String, Bitmap> cache=Collections.synchronizedMap(
            new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering
    private long size=0;//current allocated size
    private long limit=1000000;//max memory in bytes

    public MemoryCache(){
        //use 25% of available heap size
        setLimit(Runtime.getRuntime().maxMemory()/4);
    }
   
    public void setLimit(long new_limit){
        limit=new_limit;
        Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
    }

    public Bitmap get(String id){
        try{
            if(!cache.containsKey(id))
                return null;
            //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78
            return cache.get(id);
        }catch(NullPointerException ex){
            ex.printStackTrace();
            return null;
        }
    }

    public void put(String id, Bitmap bitmap){
        try{
            if(cache.containsKey(id))
                size-=getSizeInBytes(cache.get(id));
            cache.put(id, bitmap);
            size+=getSizeInBytes(bitmap);
            checkSize();
        }catch(Throwable th){
            th.printStackTrace();
        }
    }
   
    private void checkSize() {
        Log.i(TAG, "cache size="+size+" length="+cache.size());
        if(size>limit){
            Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated 
            while(iter.hasNext()){
                Entry<String, Bitmap> entry=iter.next();
                size-=getSizeInBytes(entry.getValue());
                iter.remove();
                if(size<=limit)
                    break;
            }
            Log.i(TAG, "Clean cache. New size "+cache.size());
        }
    }

    public void clear() {
        try{
            //NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78
            cache.clear();
            size=0;
        }catch(NullPointerException ex){
            ex.printStackTrace();
        }
    }

    long getSizeInBytes(Bitmap bitmap) {
        if(bitmap==null)
            return 0;
        return bitmap.getRowBytes() * bitmap.getHeight();
    }
}

Utils.java

import java.io.InputStream;
import java.io.OutputStream;

public class Utils {

     public static void CopyStream(InputStream is, OutputStream os)
        {
            final int buffer_size=1024;
            try
            {
                byte[] bytes=new byte[buffer_size];
                for(;;)
                {
                  int count=is.read(bytes, 0, buffer_size);
                  if(count==-1)
                      break;
                  os.write(bytes, 0, count);
                }
            }
            catch(Exception ex){}
        }
}

 

Click Here to Read my other Blogs..

3 comments:

  1. Having an issue with the headers when I am scrolling. They go all out of wack and out of order. Please help

    ReplyDelete
  2. I have done it its coolllll buddy

    ReplyDelete
  3. Where is position 4 ?
    Not the way to choose an item.

    ReplyDelete