通讯录的原形实现(二)- 类似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<String> parentData; private Map<String,List<String>> childData; private LayoutInflater inflater; public ContactsAdapter(Context context, List<String> parent, Map<String,List<String>>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<parentData.size();i++){ if(parentData.get(i).equals(str)) return i; } return 0; } @Override public int getGroupHeadViewState(int groupPosition, int childPosition) { if(childPosition==-1){ //最上部显示group的时候 childPosition==-1 return GroupCallbackAdapter.GROUP_HEAD_VIEW_VISIBLE; //防止滑动的时候 一闪而过的效果 } if(childPosition==getChildrenCount(groupPosition)-1){ return GroupCallbackAdapter.GROUP_HEAD_VIEW_UP; } return GroupCallbackAdapter.GROUP_HEAD_VIEW_VISIBLE; } @Override public void configureGroupView(View groupHeadView, int groupPosition, int childPosition) { String name=(String) getGroup(groupPosition); TextView text=(TextView) groupHeadView.findViewById(R.id.groupname); text.setText(name); } }主要是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, List<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, List<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); } } }此时此刻 文章也写完了 ,大家可以对比看看通讯录原型实现(-),比较学习,如有什么问题欢迎大家交流。源码地址:源代码下载地址