Android之联系人PinnedHeaderListView使用介绍

时间:2022-02-27 23:17:10

Android联系人中的ListView是做得比较独特的,但是源码写得比较复制,当我们想使用他的时候再从源码中提取,实属不易啊,而且容易出错,这几天,我把他提取出来了,写成一个简单的例子,一是给自己备忘,而是跟大家分享一下,好了,先来看看效果图:
Android之联系人PinnedHeaderListView使用介绍 
首先是封装好的带头部的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>


然后是一个独立Adapter,这次我没有作为内部类放在MainActivity中:

复制代码 代码如下:


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));
}
}


还有一个数据arrays.xml,我就不贴出来了,有兴趣的朋友可以下载源码