通訊錄的原型實現(二)- 類似QQ好友列表實現,分組名懸浮在最頂部

上一節通訊錄原型的實現(-)中,將到瞭最基本最簡單的通訊錄的實現,這節就講講類似QQ好友列表的分組名稱懸浮在最頂部的實現。我的基本實現思路如下:

1.使用ExpandableListView來作為整個通訊錄列表的控件,這樣,通訊錄的group和具體的列表項就可以分開來展示,不用像通訊錄原型的實現(-)中的見解實現,現在隻需要將group的indicator做簡單的修改,並且可以定義自己的indicator。

2.每一部分的groupname當該部分沒有完全滑動到不可見的時候會懸浮在列表項的最頂部,在此我采用和ExpandableListView中groupItem的實現相同的XML作為一個GroupHeadView,這樣列表滑動group改變的時候,滑動效果顯得自然。

3.做右側字母導航欄和ExpandableListView的聯動

4.做搜索框與數據顯示列表的聯動

先看看具體效果實現圖

首先說說上一節通訊錄原型實現(-)中的RightLettersSlideBar中設計不好的地方,在上一節,我們將26個大寫字母和#作為字符數組,這樣會導致一種情況就是,加入我們的Listview的數據中不存在以“O”開頭的字符集,我們的Adapter裡面沒有做相應的判斷,就會出現很嚴重的錯誤,導致程序崩潰。有兩種解決方案可以解決這個問題,第一就是將每一個字符做相應的判斷,沒有對應的數據的時候顯示上一Section或者下一個Section的數據。第二種做法是,動態生成我們的原始導航數據的數組,意思就是將我們所有的數據的首字母提取出來,按順序排列,那麼沒有數據的對應部分的首字母不會出現在我們的原始字符數組裡面,在此我們選擇這種做法。將RightLettersSlideBar中的charaters數組動態傳入,采用set,get方法讀寫數據。這裡就不詳細再將,文章寫完之後會將代碼貼在後面。裡面的註釋也非常詳細,有需要的可以下載看看。

下面首先講一個回調接口,為什麼要先將回調接口?因為我再寫自定義的ExpandableListView的時候,發現要改變頂部groupHeadView的顯示狀態的時候,我們有很多數據是不能再ExpandableListView中直接獲得的,必須在其adapter中獲得,所以定義瞭一個可以回調的接口,讓adapter去實現它,這樣就可以將我們的界面和數據聯系起來。

public interface GroupCallbackAdapter {
	public static final int GROUP_HEAD_VIEW_GONE = 0;  //不可見
	public static final int GROUP_HEAD_VIEW_VISIBLE = 1;  //可見
	public static final int GROUP_HEAD_VIEW_UP=2;   //正在向上移動 就是2個group即將要重疊的時候
	/**
	 * 獲取某個groupHeadView的狀態
	 * @param groupPosition  分組的下標
	 * @param childPosition   對應分組的某一個Item
	 * @return  返回上面3個數據當中的一個
	 */
	int getGroupHeadViewState(int groupPosition, int childPosition);
	/**
	 * 配置 groupHeadView, 讓 groupHeadView 知道顯示的內容
	 * @param groupHeadView
	 * @param groupPosition
	 * @param childPosition
	 */
	void configureGroupView(View groupHeadView, int groupPosition,int childPosition);
}

正如註釋所說 getGroupHeadViewState獲取groupHeadView的狀態,configureGroupView動態改變groupHeadView裡面顯示的內容,對其進行配置。

 

下面看看自定義的ExpandableListView的實現,它繼承瞭ExpandableListView還實現瞭OnScrollListener:

package com.ling.contacts2.view;

import com.ling.contacts2.callackinterface.GroupCallbackAdapter;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.AbsListView.OnScrollListener;

public class ContactExpandableListView extends ExpandableListView implements
		OnScrollListener {
	private View groupHeadView; // 每個分組的組名的View
	private int width; // groupView的寬度
	private int height; // groupView的高度

	private GroupCallbackAdapter gCallbackAdapter; // group顯示與否的回調
	private boolean groupViewVisibility = false; // group默認不顯示

	private int mOldState = -1;         //groupHeadView的默認狀態設置為-1

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

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

	public ContactExpandableListView(Context context) {
		super(context);
		init();
	}

	// 初始化函數
	public void init() {
		setOnScrollListener(this);
	}

	public void setHeaderView(View view) {
		groupHeadView = view;
		AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
				ViewGroup.LayoutParams.MATCH_PARENT,
				ViewGroup.LayoutParams.WRAP_CONTENT);
		view.setLayoutParams(lp);

		if (groupHeadView != null) {
			setFadingEdgeLength(0);
		}
		requestLayout();
	}

	@Override
	public void setAdapter(ExpandableListAdapter adapter) {
		// TODO Auto-generated method stub
		super.setAdapter(adapter);
		//重寫這個方法 主要就是初始化gCallbackAdapter 並且調用裡面的方法
		gCallbackAdapter = (GroupCallbackAdapter) adapter;
	}
	
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {

	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		final long flatPos = getExpandableListPosition(firstVisibleItem);
		int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);
		int childPosition = ExpandableListView.getPackedPositionChild(flatPos);
		configureGroupView(groupPosition, childPosition);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		if (groupHeadView != null) {
			measureChild(groupHeadView, widthMeasureSpec, heightMeasureSpec);
			width = groupHeadView.getMeasuredWidth();
			height = groupHeadView.getMeasuredHeight(); // 測量titleView的寬和高
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		super.onLayout(changed, l, t, r, b);
		final long flatPostion = getExpandableListPosition(getFirstVisiblePosition());
		final int groupPos = ExpandableListView
				.getPackedPositionGroup(flatPostion);
		final int childPos = ExpandableListView
				.getPackedPositionChild(flatPostion);
		int state = gCallbackAdapter.getGroupHeadViewState(groupPos, childPos);
		if (groupHeadView != null && gCallbackAdapter != null
				&& state != mOldState) {
			mOldState = state;
			groupHeadView.layout(0, 0, width, height);
		}

		configureGroupView(groupPos, childPos);

	}

	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		//groupHeadView直接繪制到頂層view中,它一直存在 我們隻是控制他的可見性 
		if (groupViewVisibility)
			drawChild(canvas, groupHeadView, getDrawingTime());
	}
	
	/**
	 * 根據groupPosition和childPosition 去adapter中集合childData和parentData判斷
	 * groupHeadView是什麼狀態  確定時候將groupHeadView繪制在界面上
	 * 因為在onScroll回調方法中也調用這個方法 所以不用調用invalidate()方法重繪。
	 * @param groupPosition
	 * @param childPosition
	 */
	public void configureGroupView(int groupPosition, int childPosition) {
		if (groupHeadView == null
				|| gCallbackAdapter == null
				|| ((ExpandableListAdapter) gCallbackAdapter).getGroupCount() == 0) {
			return;
		}
		int state = gCallbackAdapter.getGroupHeadViewState(groupPosition,
				childPosition);

		switch (state) {
		case GroupCallbackAdapter.GROUP_HEAD_VIEW_GONE: {
			groupViewVisibility = false;
			break;
		}

		case GroupCallbackAdapter.GROUP_HEAD_VIEW_VISIBLE: {
			gCallbackAdapter.configureGroupView(groupHeadView, groupPosition,
					childPosition);

			if (groupHeadView.getTop() != 0) {
				groupHeadView.layout(0, 0, width, height);
			}

			groupViewVisibility = true;

			break;
		}

		case GroupCallbackAdapter.GROUP_HEAD_VIEW_UP: {  //最頂部有兩個Group顯示的時候
			View firstView = getChildAt(0);  //第一個group
			int bottom = firstView.getBottom();
			int headerHeight = groupHeadView.getHeight();
			int y;
			if (bottom < headerHeight) {
				y = (bottom - headerHeight);
			} else {
				y = 0;
			}

			gCallbackAdapter.configureGroupView(groupHeadView, groupPosition,
					childPosition);

			if (groupHeadView.getTop() != y) {
				groupHeadView.layout(0, y, width, height + y);  //讓group慢慢從頂部滑出
			}
			groupViewVisibility = true;
			break;
		}
		}
	}
}

代碼43行,給自定義的view添加滾動事件的監聽,不要忘記瞭,不然onScorll方法不會回調。

 

代碼46行的setHeadView方法主要是設置我們的自定義groupHeadView,一般和我們的ExpandableListView的groupView樣式完全一樣,這樣當我們滑動的時候,過渡效果會顯得順暢自然。

代碼60行重寫瞭 setAdapter方法,這裡其實沒有什麼其他作用,主要是為瞭初始化我們自定義的回調接口GroupCallbackAdapter。

代碼104行groupHeadView.layout(0,0,width,height); 其實設置瞭我們的groupHeadView一直是在ExpandableListView的最頂部。

在這個自定義控件裡面主要是configureGroupView()方法。這個方法主要是設置我們的groupHeadView的狀態和位置的顯示。分別對三種狀態的groupHeadView做瞭相應的處理,這裡將狀態為GroupCallbackAdapter.GROUP_HEAD_VIEW_UP拿出來單獨講解。

因為這種狀態是當兩個groupHeadView快要滑動到開始重合的時候代碼154行得到瞭ExpandableListView的第一個子view,其實就是我們的groupHeadView,代碼155-165行就是做高度的處理,代碼168行,設置groupHeadView的具體位置。當其完全滑出去之後y=0,重新將groupHeadView繪制在最頂部,同時groupHeadview的state改變,就會執行GROUP_HEAD_VIEW_GONE或者GROUP_HEAD_VIEW_VISIABLE相應的處理邏輯。

Adapter的編寫邏輯如下:

package com.ling.contacts2.adapter;

import java.util.List;
import java.util.Map;

import com.ling.contacts2.R;
import com.ling.contacts2.callackinterface.GroupCallbackAdapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.SectionIndexer;
import android.widget.TextView;

public class ContactsAdapter extends BaseExpandableListAdapter implements SectionIndexer,GroupCallbackAdapter{
	private Context context;
	private List parentData;
	private Map<string,list> childData;
	private LayoutInflater inflater;
	
	
	public ContactsAdapter(Context context, List parent,
			Map<string,list>child) {
		this.context = context;
		this.parentData = parent;
		this.childData = child;
		this.inflater=LayoutInflater.from(this.context);
	}

	@Override
	public int getGroupCount() {
		// TODO Auto-generated method stub
		return parentData==null ? 0 : parentData.size();
	}

	@Override
	public int getChildrenCount(int groupPosition) {
		// TODO Auto-generated method stub
		return childData.get(parentData.get(groupPosition)).size();
	}

	@Override
	public Object getGroup(int groupPosition) {
		// TODO Auto-generated method stub
		return parentData.get(groupPosition);
	}

	@Override
	public Object getChild(int groupPosition, int childPosition) {
		// TODO Auto-generated method stub
		return childData.get(parentData.get(groupPosition)).get(childPosition);
	}

	@Override
	public long getGroupId(int groupPosition) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public long getChildId(int groupPosition, int childPosition) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public boolean hasStableIds() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public View getGroupView(int groupPosition, boolean isExpanded,
			View convertView, ViewGroup parent) {
		if(convertView==null)
			convertView=inflater.inflate(R.layout.groupview, null);
		TextView groupname=(TextView) convertView.findViewById(R.id.groupname);
		groupname.setText(parentData.get(groupPosition));
		return convertView;
	}

	@Override
	public View getChildView(int groupPosition, int childPosition,
			boolean isLastChild, View convertView, ViewGroup parent) {
		if(convertView==null)
			convertView=inflater.inflate(R.layout.childview, null);
		TextView title=(TextView) convertView.findViewById(R.id.childvalue);
		title.setText(childData.get(parentData.get(groupPosition)).get(childPosition));
		return convertView;
	}

	@Override
	public boolean isChildSelectable(int groupPosition, int childPosition) {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public Object[] getSections() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int getPositionForSection(int sectionIndex) {
		return sectionIndex;
	}

	@Override
	public int getSectionForPosition(int position) {
		String str=parentData.get(position);
		for(int i=0;i主要是getGroupHeadViewState方法的邏輯和configureGroupView的邏輯的處理。註釋很清楚瞭。

 

下面是MainActivity中的一些代碼邏輯:

package com.ling.contacts2;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ling.contacts2.adapter.ContactsAdapter;
import com.ling.contacts2.view.ContactExpandableListView;
import com.ling.contacts2.view.RightLettersSlideBar;
import com.ling.contacts2.view.RightLettersSlideBar.OnTouchingLetterChangedListener;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnGroupClickListener;

public class MainActivity extends Activity {

	private String mDatas[];
	private ContactExpandableListView expListview;
	private RightLettersSlideBar slideBar;
	private ContactsAdapter adapter;
	private List<string> parentData;
	private Map<string, string="">> childData;;
	public String[] charaters;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mDatas = getResources().getStringArray(R.array.countries);
		expListview = (ContactExpandableListView) findViewById(R.id.explist);
		slideBar = (RightLettersSlideBar) findViewById(R.id.slidebar);
		parentData = new ArrayList<string>();
		childData = new HashMap<string, string="">>();
		expListview.setHeaderView(getLayoutInflater().inflate(
				R.layout.groupheadview, expListview, false));
		initData(mDatas);
		slideBar.setCharaters(charaters);
		adapter = new ContactsAdapter(this, parentData, childData);
		expListview.setAdapter(adapter);
		expListview.setOnGroupClickListener(new OnGroupClickListener() {

			@Override
			public boolean onGroupClick(ExpandableListView parent, View v,
					int groupPosition, long id) {
				// TODO Auto-generated method stub
				return true;   //返回TRUE的時候 主要是設置每一個group不可再點擊,防止其收縮
			}
		});
		int groupCount = expListview.getCount();

		for (int i = 0; i < groupCount; i++) {

			expListview.expandGroup(i);  //設置每一個group都展開

		};
		slideBar.setOnTouchingLetterChangedListener(new OnTouchingLetterChangedListener() {

			@Override
			public void onTouchingLetterChanged(String s, int index) {
				int section = adapter.getSectionForPosition(index);
				expListview.setSelectedGroup(section);  //點擊右側的slideBar之後ExpandableListView隨之改變
			}
		});
	}

	// 初始化數據
	private void initData(String[] data) {
		for (int i = 0; i < data.length; i++) {
			String groupName = data[i].substring(0, 1).toUpperCase();
			if (!parentData.contains(groupName)) {
				parentData.add(groupName);
			}
			if (!childData.containsKey(groupName)) {
				List<string> temp = new ArrayList<string>();
				temp.add(data[i]);
				childData.put(groupName, temp);
			} else {
				childData.get(groupName).add(data[i]);
			}
		}
		charaters = new String[parentData.size()];
		for (int i = 0; i < parentData.size(); i++) {
			charaters[i] = parentData.get(i);
		}
	}
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *