Android Listview分组特效:滑动分组标题当前固定,并随内容滑动(andrroid原生电话本实现方法)

源码http://www.cnblogs.com/xiaoQLu/archive/2011/12/20/2293732.html

首先需要一个类,完全不用改动,直接加入

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.seven.view;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;

/**
 * A ListView that maintains a header pinned at the top of the list. The
 * pinned header can be pushed up and dissolved as needed.
 */
public class PinnedHeaderListView extends ListView {

    /**
     * Adapter interface.  The list adapter must implement this interface.
     */
    public interface PinnedHeaderAdapter {

        /**
         * Pinned header state: don't show the header.
         */
        public static final int PINNED_HEADER_GONE = 0;

        /**
         * Pinned header state: show the header at the top of the list.
         */
        public static final int PINNED_HEADER_VISIBLE = 1;

        /**
         * Pinned header state: show the header. If the header extends beyond
         * the bottom of the first shown element, push it up and clip.
         */
        public static final int PINNED_HEADER_PUSHED_UP = 2;

        /**
         * Computes the desired state of the pinned header for the given
         * position of the first visible list item. Allowed return values are
         * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
         * {@link #PINNED_HEADER_PUSHED_UP}.
         */
        int getPinnedHeaderState(int position);

        /**
         * Configures the pinned header view to match the first visible list item.
         *
         * @param header pinned header view.
         * @param position position of the first visible list item.
         * @param alpha fading of the header view, between 0 and 255.
         */
        void configurePinnedHeader(View header, int position, int alpha);
    }

    private static final int MAX_ALPHA = 255;

    private PinnedHeaderAdapter mAdapter;
    private View mHeaderView;
    private boolean mHeaderViewVisible;

    private int mHeaderViewWidth;

    private int mHeaderViewHeight;

    public PinnedHeaderListView(Context context) {
        super(context);
    }

    public PinnedHeaderListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setPinnedHeaderView(View view) {
        mHeaderView = view;

        // Disable vertical fading when the pinned header is present
        // TODO change ListView to allow separate measures for top and bottom fading edge;
        // in this particular case we would like to disable the top, but not the bottom edge.
        if (mHeaderView != null) {
            setFadingEdgeLength(0);
        }
        requestLayout();
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);
        mAdapter = (PinnedHeaderAdapter)adapter;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (mHeaderView != null) {
            measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
            mHeaderViewWidth = mHeaderView.getMeasuredWidth();
            mHeaderViewHeight = mHeaderView.getMeasuredHeight();
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (mHeaderView != null) {
            mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
            configureHeaderView(getFirstVisiblePosition());
        }
    }

    public void configureHeaderView(int position) {
        if (mHeaderView == null) {
            return;
        }
        int state = mAdapter.getPinnedHeaderState(position);
        switch (state) {
            case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
                mHeaderViewVisible = false;
                break;
            }

            case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
                mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
                if (mHeaderView.getTop() != 0) {
                    mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
                }
                mHeaderViewVisible = true;
                break;
            }

            case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
                View firstView = getChildAt(0);
                int bottom = firstView.getBottom();
//                int itemHeight = firstView.getHeight();
                int headerHeight = mHeaderView.getHeight();
                int y;
                int alpha;
                if (bottom < headerHeight) {
                    y = (bottom - headerHeight);
                    alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
                } else {
                    y = 0;
                    alpha = MAX_ALPHA;
                }
                mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
                if (mHeaderView.getTop() != y) {
                    mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight + y);
                }
                mHeaderViewVisible = true;
                break;
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        if (mHeaderViewVisible) {
            drawChild(canvas, mHeaderView, getDrawingTime());
        }
    }
}

先把以前类自己放在任意能够调用的位置,是个开源的,具体比较复杂。他重写了Listview

既然是分组,当然要用listview了。

在你需要潜入的地方,贴入以下

 <com.seven.view.PinnedHeaderListView
			            android:id="@+id/contact_listview"
			            android:layout_width="fill_parent"
			            android:layout_height="fill_parent"
       					 android:fadingEdge="none"
			             />
其中com.seven.view是你放上面那个类的地方,也就是包

然后就是item样式了,就是listview里面的内容样式,这都和原生的Listview是一样的用法

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
     android:orientation="vertical"
     android:background="@drawable/mm_listitem"
>
		 <LinearLayout  
		        android:id="@+id/sort_key_layout"  //这里是显示分组的样式
		        android:layout_width="fill_parent"  
		        android:layout_height="18dip"  
		        android:background="#dcdcdc" >  
		  
		        <TextView  
		            android:id="@+id/sort_key"  
		            android:layout_width="wrap_content"  
		            android:layout_height="wrap_content"  
		            android:layout_gravity="center_horizontal"  
		            android:layout_marginLeft="10dip"  
		            android:textColor="#8b4513"  
		            android:textSize="13sp" />  
		    </LinearLayout>  
		     <LinearLayout  
	        android:id="@+id/name_layout"            ///这下面是显示内容的样式
	        android:layout_width="fill_parent"  
	        android:layout_height="50dip" >  

	        <ImageView  
	            android:layout_width="wrap_content"  
	            android:layout_height="wrap_content"  
	            android:layout_gravity="center_vertical"  
	            android:layout_marginLeft="10dip"  
	            android:layout_marginRight="10dip"  
	            android:src="@drawable/icon_contact" />  
	  
	        <TextView  
	            android:id="@+id/listview_title"  
	            android:layout_width="wrap_content"  
	            android:layout_height="wrap_content"  
	            android:layout_gravity="center_vertical"  
	            android:textColor="#000"  
	            android:text="Seven"
	            android:textSize="22sp" />  
	         <TextView
	            android:id="@+id/listview_phone" 
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:paddingLeft="17dp"
                android:text="10086"
                android:layout_gravity="center"
                
                android:textColor="#666" />
   			 </LinearLayout>  

</LinearLayout>

可能你会问,分组和内容是挨着的,他们同时出现,每个分组只有一条怎么办。当然不会这么简单啦。 我们还有自定义一个adapter

package com.seven.tools;

import java.util.ArrayList;
import java.util.List;
import com.seven.bean.ContactBean;
import com.seven.quickphone.R;
import com.seven.view.PinnedHeaderListView.PinnedHeaderAdapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;

public class SortAdapter extends BaseAdapter implements SectionIndexer,PinnedHeaderAdapter,OnScrollListener{
	private List<XXX> list = null; //这里是你listview里面要用到的内容的list 可以自定义
	private Context mContext;
	private ArrayList<String> sections=new ArrayList<String>(); //这里是分组的名字也是在外面自定义,然后传进来就是,这和普通的Listview是一样的
	public SortAdapter(Context mContext, List<ContactBean> list) {
		this.mContext = mContext;
		this.list = list;
		this.setSections(list);
	}
	public void setSections(List<ContactBean> list)
	{
		//for(ContactBean c:list)
		//{
		//	if(!sections.contains(c.getSortKey()))              这个函数是我自己把list和section的数据绑定了某种关系。当然你们大可不必
		//		sections.add(c.getSortKey());               你们可以在外面填充好一个list内容,和一个分组内容就行,当然list里面需要有个
		//}<span style="white-space:pre">							</span>   字段,来区分你是哪个分组
	}
	

	public int getCount() {   //都是默认的
		return this.list.size();
	}

	public Object getItem(int position) {<pre name="code" class="java">//都是默认的
<pre name="code" class="java">public int getCount() {
		return this.list.size();
	}

	public Object getItem(int position) {
		return list.get(position);
	}

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

	public View getView(final int position, View view, ViewGroup arg2) {
		ViewHolder viewHolder = null;
		final ContactBean mContent = list.get(position);
		if (view == null) {
			viewHolder = new ViewHolder();
			view = LayoutInflater.from(mContext).inflate(R.layout.main_contact_listitem, null);
			viewHolder.tvTitle = (TextView) view.findViewById(R.id.listview_title);
			viewHolder.tvKey=(TextView) view.findViewById(R.id.sort_key);
			viewHolder.tvPhone=(TextView) view.findViewById(R.id.listview_phone);
			viewHolder.sortKeyLayout = (LinearLayout) view.findViewById(R.id.sort_key_layout);
			view.setTag(viewHolder);
		} else {
			viewHolder = (ViewHolder) view.getTag();
		}
		//根据position获取分类的首字母的Char ascii值
		int section = getSectionForPosition(position);
		viewHolder.tvTitle.setText(mContent.getDisplayName());
		String t="";int len=mContent.getPhoneNum().size();
		for(int i=0;i<len-1;i++)
			t+=mContent.getPhoneNum().get(i)+"\n";
		t+=mContent.getPhoneNum().get(len-1);
		viewHolder.tvPhone.setText(t);
		if (position == getPositionForSection(section)) {  
			viewHolder.tvKey.setText(mContent.getSortKey());  
			viewHolder.sortKeyLayout.setVisibility(View.VISIBLE);  
        } else {  
        
        	viewHolder.sortKeyLayout.setVisibility(View.GONE);  
        }  
		
		return view;

	}
	


	final static class ViewHolder {
		TextView tvKey;
		TextView tvTitle;
		TextView tvPhone;
		LinearLayout sortKeyLayout;
	}

	public int getNextSection(int section)
	{
		for(int i=0,len=sections.size();i<len;i++)
		{
			if(sections.get(i).charAt(0)==section &&i+1<len)
				return  sections.get(i+1).charAt(0);
		}
		return -1;
	}
	/**
	 * 根据ListView的当前位置获取分类的首字母的Char ascii值
	 */
	public int getSectionForPosition(int position) {
		if(list.size()==0)
			return -1;
		return list.get(position).getSortKey().charAt(0);
	}

	/**
	 * 根据分类的首字母的Char ascii值获取其第一次出现该首字母的位置
	 */
	public int getPositionForSection(int section) {
		for (int i = 0; i < getCount(); i++) {
			String sortStr = list.get(i).getSortKey();
			char firstChar = sortStr.toUpperCase().charAt(0);
			if (firstChar == section) {
				return i;
			}
		}
		
		return -1;
	}
	
	/**
	 * 提取英文的首字母,非英文字母用#代替。
	 * 
	 * @param str
	 * @return
	 */
	private String getAlpha(String str) {
		String  sortStr = str.trim().substring(0, 1).toUpperCase();
		// 正则表达式,判断首字母是否是英文字母
		if (sortStr.matches("[A-Z]")) {
			return sortStr;
		} else {
			return "#";
		}
	}

	@Override
	public Object[] getSections() {
		return null;
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem, int arg2, int arg3) {
		if (view instanceof com.seven.view.PinnedHeaderListView) {
			((com.seven.view.PinnedHeaderListView) view).configureHeaderView(firstVisibleItem);
		}
		
	}

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

	@Override
	public int getPinnedHeaderState(int position) {
			int realPosition = position;
			if (realPosition < 0) 
			{
				return PINNED_HEADER_GONE;
			}
			int section = getSectionForPosition(realPosition);// 得到此item所在的分组位置
			int nextSectionPosition = getPositionForSection(getNextSection(section));// 得到下一个分组的位置
			//当前position正好是当前分组的最后一个position,也就是下一个分组的第一个position的前面一个
			if (nextSectionPosition != -1
			&& realPosition == nextSectionPosition - 1) {
			return PINNED_HEADER_PUSHED_UP;
			}
			return PINNED_HEADER_VISIBLE;
	}

	@Override
	public void configurePinnedHeader(View header, int position, int alpha) {
		int realPosition = position;
		int section = getSectionForPosition(realPosition);

		String title = ""+(char)section;
		((TextView) header.findViewById(R.id.sort_keyTitle)).setText(title);
		
	}


 
 
 以上工具类都准备了,下面只需要很简单的一步 
 
adapter = new SortAdapter(this, list);
		sortListView.setAdapter(adapter);
		sortListView.setOnScrollListener(adapter);
		sortListView.setPinnedHeaderView(getLayoutInflater().inflate(R.layout.main_contact_title, sortListView, false));
		

在activity里面写一下就是啦。 以上内容对于初学可能难以理解,我也是初学,看了一天,换了好几个人的方法,才找到这个方法的。

如果实在看不懂,先自己看看自定义adapter的实现。

上个效果图吧。O.O


宣传下本人的小制作: 单机斗地主-wifi版   希望大家多多支持。能下载下来玩一玩。

豌豆荚市场: 搜索  "联机斗地主" "单机斗地主-wifi版"都能搜到。 

直达连接  http://www.wandoujia.com/apps/com.hj.singlejoker


你可能感兴趣的:(android,ListView)