Android联系人中的ListView是做得比较独特的,但是源码写得比较复制,当我们想使用他的时候再从源码中提取,实属不易啊,而且容易出错,这几天,我把他提取出来了,写成一个简单的例子,一是给自己备忘,而是跟大家分享一下,好了,先来看看效果图:
首先是封装好的带头部的PinnedHeaderListView:
public class PinnedHeaderListView extends ListView { public interface PinnedHeaderAdapter { public static final int PINNED_HEADER_GONE = 0; public static final int PINNED_HEADER_VISIBLE = 1; public static final int PINNED_HEADER_PUSHED_UP = 2; int getPinnedHeaderState(int position); 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); } 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()); } } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHeaderView != null) { measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); mHeaderViewWidth = mHeaderView.getMeasuredWidth(); mHeaderViewHeight = mHeaderView.getMeasuredHeight(); } } public void setPinnedHeaderView(View view) { mHeaderView = view; if (mHeaderView != null) { setFadingEdgeLength(0); } requestLayout(); } public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); mAdapter = (PinnedHeaderAdapter)adapter; } 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 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; } } } protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (mHeaderViewVisible) { drawChild(canvas, mHeaderView, getDrawingTime()); } } }
然后是旁边那个快速导航BladeView(刀锋):
public class BladeView extends View { private OnItemClickListener mOnItemClickListener; String[] b = { "#", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; int choose = -1; Paint paint = new Paint(); boolean showBkg = false; private PopupWindow mPopupWindow; private TextView mPopupText; private Handler handler = new Handler(); public BladeView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public BladeView(Context context, AttributeSet attrs) { super(context, attrs); } public BladeView(Context context) { super(context); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (showBkg) { canvas.drawColor(Color.parseColor("#00000000")); } int height = getHeight(); int width = getWidth(); int singleHeight = height / b.length; for (int i = 0; i < b.length; i++) { paint.setColor(Color.BLACK); paint.setTypeface(Typeface.DEFAULT_BOLD); paint.setFakeBoldText(true); paint.setAntiAlias(true); if (i == choose) { paint.setColor(Color.parseColor("#3399ff")); } float xPos = width / 2 - paint.measureText(b[i]) / 2; float yPos = singleHeight * i + singleHeight; canvas.drawText(b[i], xPos, yPos, paint); paint.reset(); } } @Override public boolean dispatchTouchEvent(MotionEvent event) { final int action = event.getAction(); final float y = event.getY(); final int oldChoose = choose; final int c = (int) (y / getHeight() * b.length); switch (action) { case MotionEvent.ACTION_DOWN: showBkg = true; if (oldChoose != c) { if (c > 0 && c < b.length) { performItemClicked(c); choose = c; invalidate(); } } break; case MotionEvent.ACTION_MOVE: if (oldChoose != c) { if (c > 0 && c < b.length) { performItemClicked(c); choose = c; invalidate(); } } break; case MotionEvent.ACTION_UP: showBkg = false; choose = -1; dismissPopup(); invalidate(); break; } return true; } private void showPopup(int item) { if (mPopupWindow == null) { handler.removeCallbacks(dismissRunnable); mPopupText = new TextView(getContext()); mPopupText.setBackgroundColor(Color.GRAY); mPopupText.setTextColor(Color.CYAN); mPopupText.setTextSize(50); mPopupText.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL); mPopupWindow = new PopupWindow(mPopupText, 100, 100); } String text = ""; if (item == 0) { text = "#"; } else { text = Character.toString((char) ('A' + item - 1)); } mPopupText.setText(text); if (mPopupWindow.isShowing()) { mPopupWindow.update(); } else { mPopupWindow.showAtLocation(getRootView(), Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL, 0, 0); } } private void dismissPopup() { handler.postDelayed(dismissRunnable, 800); } Runnable dismissRunnable = new Runnable() { @Override public void run() { // TODO Auto-generated method stub if (mPopupWindow != null) { mPopupWindow.dismiss(); } } }; public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } public void setOnItemClickListener(OnItemClickListener listener) { mOnItemClickListener = listener; } private void performItemClicked(int item) { if (mOnItemClickListener != null) { mOnItemClickListener.onItemClick(b[item]); showPopup(item); } } public interface OnItemClickListener { void onItemClick(String s); } }
接下来就是使用了,先在布局文件中声明activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.way.view.PinnedHeaderListView android:id="@+id/friends_display" android:layout_width="fill_parent" android:layout_height="fill_parent" android:cacheColorHint="#00000000" android:divider="@null" android:footerDividersEnabled="false" android:headerDividersEnabled="false" /> <com.way.view.BladeView android:id="@+id/friends_myletterlistview" android:layout_width="30dip" android:layout_height="fill_parent" android:layout_alignParentRight="true" android:background="#00000000" /> </RelativeLayout>
public class FriendsAdapter extends BaseAdapter implements SectionIndexer, PinnedHeaderAdapter, OnScrollListener { private int mLocationPosition = -1; private String[] mDatas; // 首字母集 private List<String> mFriendsSections; private List<Integer> mFriendsPositions; private LayoutInflater inflater; public FriendsAdapter(Context context,String[] datas, List<String> friendsSections, List<Integer> friendsPositions) { // TODO Auto-generated constructor stub inflater = LayoutInflater.from(context); mDatas = datas; mFriendsSections = friendsSections; mFriendsPositions = friendsPositions; } @Override public int getCount() { // TODO Auto-generated method stub return mDatas.length; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return mDatas[position]; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub int section = getSectionForPosition(position); if (convertView == null) { convertView = inflater.inflate(R.layout.listview_item, null); } LinearLayout mHeaderParent = (LinearLayout) convertView .findViewById(R.id.friends_item_header_parent); TextView mHeaderText = (TextView) convertView .findViewById(R.id.friends_item_header_text); if (getPositionForSection(section) == position) { mHeaderParent.setVisibility(View.VISIBLE); mHeaderText.setText(mFriendsSections.get(section)); } else { mHeaderParent.setVisibility(View.GONE); } TextView textView = (TextView) convertView .findViewById(R.id.friends_item); textView.setText(mDatas[position]); return convertView; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub if (view instanceof PinnedHeaderListView) { ((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem); } } @Override public int getPinnedHeaderState(int position) { int realPosition = position; if (realPosition < 0 || (mLocationPosition != -1 && mLocationPosition == realPosition)) { return PINNED_HEADER_GONE; } mLocationPosition = -1; int section = getSectionForPosition(realPosition); int nextSectionPosition = getPositionForSection(section + 1); 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) { // TODO Auto-generated method stub int realPosition = position; int section = getSectionForPosition(realPosition); String title = (String) getSections()[section]; ((TextView) header.findViewById(R.id.friends_list_header_text)) .setText(title); } @Override public Object[] getSections() { // TODO Auto-generated method stub return mFriendsSections.toArray(); } @Override public int getPositionForSection(int section) { if (section < 0 || section >= mFriendsSections.size()) { return -1; } return mFriendsPositions.get(section); } @Override public int getSectionForPosition(int position) { // TODO Auto-generated method stub if (position < 0 || position >= getCount()) { return -1; } int index = Arrays.binarySearch(mFriendsPositions.toArray(), position); return index >= 0 ? index : -index - 2; } }
最后就是MainActivity中的处理了:
public class MainActivity extends Activity { private static final String FORMAT = "^[a-z,A-Z].*$"; private PinnedHeaderListView mListView; private BladeView mLetter; private FriendsAdapter mAdapter; private String[] datas; // 首字母集 private List<String> mSections; // 根据首字母存放数据 private Map<String, List<String>> mMap; // 首字母位置集 private List<Integer> mPositions; // 首字母对应的位置 private Map<String, Integer> mIndexer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); } private void initData() { datas = getResources().getStringArray(R.array.countries); mSections = new ArrayList<String>(); mMap = new HashMap<String, List<String>>(); mPositions = new ArrayList<Integer>(); mIndexer = new HashMap<String, Integer>(); for (int i = 0; i < datas.length; i++) { String firstName = datas[i].substring(0, 1); if (firstName.matches(FORMAT)) { if (mSections.contains(firstName)) { mMap.get(firstName).add(datas[i]); } else { mSections.add(firstName); List<String> list = new ArrayList<String>(); list.add(datas[i]); mMap.put(firstName, list); } } else { if (mSections.contains("#")) { mMap.get("#").add(datas[i]); } else { mSections.add("#"); List<String> list = new ArrayList<String>(); list.add(datas[i]); mMap.put("#", list); } } } Collections.sort(mSections); int position = 0; for (int i = 0; i < mSections.size(); i++) { mIndexer.put(mSections.get(i), position);// 存入map中,key为首字母字符串,value为首字母在listview中位置 mPositions.add(position);// 首字母在listview中位置,存入list中 position += mMap.get(mSections.get(i)).size();// 计算下一个首字母在listview的位置 } } private void initView() { // TODO Auto-generated method stub mListView = (PinnedHeaderListView) findViewById(R.id.friends_display); mLetter = (BladeView) findViewById(R.id.friends_myletterlistview); mLetter.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(String s) { if (mIndexer.get(s) != null) { mListView.setSelection(mIndexer.get(s)); } } }); mAdapter = new FriendsAdapter(this, datas, mSections, mPositions); mListView.setAdapter(mAdapter); mListView.setOnScrollListener(mAdapter); mListView.setPinnedHeaderView(LayoutInflater.from(this).inflate( R.layout.listview_head, mListView, false)); } }