Saad
Saad

Reputation: 309

How can i add a sticky header to my ListView?

I am having a listView, so i want to add a sticky header so that it sticks to the top of the listView and when a different category starts in the listView a Different header take its place, Like the contacts, where is "a" as a Sticky header is at the top till "b" comes in. Is there any library to do it?? I am using custom List Adapter to show my List...

this is my custom adapter class

public class NewsRowAdapter extends ArrayAdapter<Item>  {

private Activity activity;
private List<Item> items;
private Item objBean;
private int row;

private DisplayImageOptions options;
ImageLoader imageLoader;

public NewsRowAdapter(Activity act, int resource, List<Item> arrayList) {
    super(act, resource, arrayList);
    this.activity = act;
    this.row = resource;
    this.items = arrayList;



    imageLoader = ImageLoader.getInstance();
    File cacheDir1 = StorageUtils.getCacheDirectory(activity);

    ImageLoaderConfiguration config = new
    ImageLoaderConfiguration.Builder(activity)
    .maxImageWidthForMemoryCache(600)
    .maxImageHeightForMemoryCache(400)
    .httpConnectTimeout(5000)
    .httpReadTimeout(20000)
    .threadPoolSize(3)
    .threadPriority(Thread.MIN_PRIORITY + 3)
    .denyCacheImageMultipleSizesInMemory()
    .memoryCache(new UsingFreqLimitedMemoryCache(20000)) // You can pass your own memory cache implementation
    .discCache(new TotalSizeLimitedDiscCache(cacheDir1, 30000)) // You can pass your own disc cache implementation
    .defaultDisplayImageOptions(DisplayImageOptions.createSimple())
    .build();


    ImageLoader.getInstance().init(config);
//              imageLoader = ImageLoader;
//      

   options = new DisplayImageOptions.Builder()
    .showStubImage(R.drawable.icon2x)
    .showImageForEmptyUrl(R.drawable.icon2x).cacheInMemory()
    .cacheOnDisc().build();
//imageLoader = ImageLoader.getInstance();

}

@Override
public Item getItem(int position)
{
    return items.get(position);
}

@Override
public int getCount()
{
    return items.size();
}

@Override
public int getViewTypeCount()
{
    return 3;
}

@Override
public int getItemViewType(int position)
{
    Item item = items.get(position);
    if (item.isHeader())
    {
        return TYPE_SECTION_HEADER;
    }
    else
    {
        return TYPE_LIST_ITEM;
    }
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    View view = convertView;
    ViewHolder holder;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) activity
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(row, null);

        holder = new ViewHolder();
        view.setTag(holder);
    } else {
        holder = (ViewHolder) view.getTag();
    }

    if ((items == null) || ((position + 1) > items.size()))
        return view;

    objBean = items.get(position);

    holder.tvName = (TextView) view.findViewById(R.id.title);
    holder.tvId = (TextView) view.findViewById(R.id.id);
    holder.tvFlag = (TextView) view.findViewById(R.id.flag);
    holder.tvimageurl=(TextView) view.findViewById(R.id.imageurl);
    holder.tvGender = (ImageView) view.findViewById(R.id.image);
    //holder.tvAge = (TextView) view.findViewById(R.id.tvage);
    holder.pbar = (ProgressBar) view.findViewById(R.id.pbar);
    if (holder.tvName != null && null != objBean.getName()
            && objBean.getName().trim().length() > 0) {
        holder.tvName.setText(Html.fromHtml(objBean.getName()));
    }
    if (holder.tvId != null && null != objBean.getId()
            && objBean.getId().trim().length() > 0) {
        holder.tvId.setText(Html.fromHtml(objBean.getId()));

    }
    if (holder.tvFlag != null && null != objBean.getFlag()
            && objBean.getFlag().trim().length() > 0) {
        holder.tvFlag.setText(Html.fromHtml(objBean.getFlag()));

    }

    if (holder.tvimageurl != null && null != objBean.getGender()
            && objBean.getFlag().trim().length() > 0) {
        holder.tvimageurl.setText(Html.fromHtml(objBean.getGender()));

    }

    //if (holder.tvBDate != null && null != objBean.getBirthdate()
    //      && objBean.getBirthdate().trim().length() > 0) {
    //  holder.tvBDate.setText(Html.fromHtml(objBean.getBirthdate()));
    //}
    if (holder.tvGender != null) {
        if (null != objBean.getGender()
                && objBean.getGender().trim().length() > 0) {
            final ProgressBar pbar = holder.pbar;


            imageLoader.displayImage(objBean.getGender(), holder.tvGender,
                    options, new ImageLoadingListener() {

                        @Override
                        public void onLoadingComplete() {
                            pbar.setVisibility(View.INVISIBLE);

                        }


                        @Override
                        public void onLoadingFailed() {
                            pbar.setVisibility(View.INVISIBLE);
                        }


                        @Override
                        public void onLoadingStarted() {
                            pbar.setVisibility(View.INVISIBLE);

                        }
                    });

        } else {
            holder.tvGender.setImageResource(R.drawable.icon2x);
        }
    }


    return view;
}

public class ViewHolder {
    public TextView tvimageurl;
    public TextView tvFlag;
    public TextView tvId;
    public ProgressBar pbar;
    public TextView tvName, tvCity, tvBDate, tvAge;
    ImageView tvGender;
}


}

help needed.....

Upvotes: 4

Views: 20130

Answers (2)

HpTerm
HpTerm

Reputation: 8281

I struggled quite a long time writing my own StickyHeader as the StickyListHeaders git was not perfectly matching my needs. However, the StickyListHeaders help me a lot in understanding how to finally have it working and therefore the writer of the StickyListHeaders deserve mentioning it.

Many things have to be put together to have a sticky header working.

First, declare the following

private TextView mStickyHeader;
private TextView mStickyHeader2;
private int mCurrentStickyHeaderSection;

in onActivityCreated

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    ...     
    mCurrentStickyHeaderSection = -1;

    mStickyHeader = (TextView) getView().findViewById(R.id.textview_sticky_header_section);
    mStickyHeader2 = (TextView) getView().findViewById(R.id.textview_sticky_header_section_2);

    mListView = (ListView) getView().findViewById(R.id.listView_with_sticky_headers); 
    mListView.setOnScrollListener(new OnScrollListener() {
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            updateStickyHeader(firstVisibleItem);
        }

        @Override
        public void onScrollStateChanged(AbsListView arg0, int arg1) {
            // TODO Auto-generated method stub
        }
    });

I assume you have written a CustomAdapter with section indexes and will only focus on the sticky headers.

The following method contains all that you need to have the sticky header. The tricky part is determining if the firstVisibleItem is a header (pos = -1) or not and the corresponding section. This part can take you a few times to have it working properly.

private void updateStickyHeader(int firstVisibleItem) {
    // here is the tricky part. You have to determine pos and section
    // pos is the position within a section pos = -1 means it's the header of the section        
    // you have to determine if firstVisibleItem is a header or not
    // you also have to determine the section to which belongs the first item
    int pos = whatIsThePositionOfTheItem[firstVisibleItem];
    int section = whatIsTheSectionOfTheItem[firstVisibleItem];

    if (section != mCurrentStickyHeaderSection) {
        mStickyHeader.setText("Your_Previous_Section_Text");
        mStickyHeader2.setText("Your_Next_Section_Text");
        mCurrentStickyHeaderSection = section;
    }

    int stickyHeaderHeight = mStickyHeader.getHeight();
    if (stickyHeaderHeight == 0) {
        stickyHeaderHeight = mStickyHeader.getMeasuredHeight();
    }

    View SectionLastView = mListView.getChildAt(0);
    if (SectionLastView != null && pos == -1 && SectionLastView.getBottom() <= stickyHeaderHeight) {
        int lastViewBottom = SectionLastView.getBottom();
        mStickyHeader.setTranslationY(lastViewBottom - stickyHeaderHeight);
        mStickyHeader2.setTranslationY(lastViewBottom - stickyHeaderHeight + mStickyHeader.getHeight());
    } else if (stickyHeaderHeight != 0) {
        mStickyHeader.setTranslationY(0);
        mStickyHeader2.setTranslationY(mStickyHeader.getHeight());
    }
}

Finally, the layout must contain a FrameLayout and should look like that

<?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" >

    //this textview is just a global title of your listview if you need one but can be remove
    <TextView
        android:id="@+id/textview_title"
        style="?android:attr/listSeparatorTextViewStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="6dp"
        android:paddingRight="6dp"
        android:textIsSelectable="false" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textview_sticky_header_section"
            style="?android:attr/listSeparatorTextViewStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textIsSelectable="false" />

        <TextView
            android:id="@+id/textview_sticky_header_section_2"
            style="?android:attr/listSeparatorTextViewStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textIsSelectable="false" />
    </FrameLayout>

    <ListView
        android:id="@+id/listView_with_sticky_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fastScrollEnabled="true"
        android:longClickable="true" >
    </ListView>

</LinearLayout>

Update

As asked in the comments here is an explanation of the whatIsThePositionOfTheItem and whatIsTheSectionOfTheItem. These methods have to give back the position and section of a given item.

In my case it was quite simple, my data was already containing the section and position. Actually, I am displaying text, and that text starts with the section and position. So I simply parse that text to determine the section and position.

For others, it is hard to give an example because it really depends on what you are displaying. But to make a long story short whatIsThePositionOfTheItem and whatIsTheSectionOfTheItem have to return the position and section of a given item. You will probably have to fill a table with the position and section of each item in your list and get the position and section from that table.

Upvotes: 7

neteinstein
neteinstein

Reputation: 17613

There is a library exactly with that name that implements what you want:

Library demo image

You can check it out here: https://github.com/emilsjolander/StickyListHeaders

Upvotes: 5

Related Questions