前面对ExpandListView进行了简单的介绍。如果您还没来得及学习ExpandListView的使用,可前往ExpandableListView的使用详解学习。前面只是对ExpandListView进行了简单的介绍,可能介绍完您会说so
easy。确实ExpandListView的用法就是这么简单。由于前面已经介绍过ExpandListView的使用,今天打算深入嵌套ExpandListView实现一个城市选择Demo。Demo的大概需求是这样的,点击省份展开其对应的城市列表,当当前城市列表是展开状态时点击其他省份不关闭城市列表而是直接更新到当前的省份的城市列表。其具体的效果图如下所示。
效果图
看到这张效果图(说明:由于这个布局上部分比较简单,这里我们主要对国内城市部分进行实现),你会作何感想呢?你可能会想,每行单独布局,每行下面默认的隐藏一个GridView,当点击的时候显示GridView否则不显示,当然这种方式肯定是能实现的。那用ExpandListView怎么来实现呢?
分析:
1. ExpandListView只有Group和Child,那怎么将其和ExpandListView结合起来呢?观察效果图发现,效果图中每一行就相当于一个Group,下面的列表就相当于一个Child。
2. Group和Child分别由什么组成呢?从效果图中可看出Group可由GridView或五个TextView组成(这里我们选择五个TextView),Child可由一个GridView组成。
Ok,上面的分析已经很到位了,不用说现在已经有一定的思路了,下面我们就对我们的设想进行实现,看到底能不能达到预期的效果。
第一步:配置相应的xml文件以及实体类(这里主要需要省名以及每个省名对应的城市名)
布局文件:
activity_main.xml文件:
<LinearLayout 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" android:orientation="horizontal" android:gravity="center" tools:context=".MainActivity" > <ExpandableListView android:id="@+id/expandlistview" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffff" android:cacheColorHint="#00000000" android:groupIndicator="@null" android:listSelector="#00000000" /> </LinearLayout>
group_item.xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal" > <TextView android:id="@+id/textAddress" android:layout_width="0dip" android:paddingTop="10dip" android:paddingBottom="10dip" android:layout_weight="1" android:gravity="center_vertical|center_horizontal" android:layout_height="wrap_content" android:text="" android:background="@drawable/press_down" /> <TextView android:id="@+id/textAddress1" android:layout_width="0dip" android:layout_weight="1" android:paddingTop="10dip" android:paddingBottom="10dip" android:gravity="center_vertical|center_horizontal" android:layout_height="wrap_content" android:text="" android:background="@drawable/press_down" /> <TextView android:id="@+id/textAddress2" android:layout_width="0dip" android:layout_weight="1" android:paddingTop="10dip" android:paddingBottom="10dip" android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal" android:text="" android:background="@drawable/press_down" /> <TextView android:id="@+id/textAddress3" android:layout_width="0dip" android:layout_weight="1" android:paddingTop="10dip" android:paddingBottom="10dip" android:layout_height="wrap_content" android:gravity="center_vertical|center_horizontal" android:text="" android:background="@drawable/press_down" /> <TextView android:id="@+id/textAddress4" android:layout_width="0dip" android:layout_weight="1" android:paddingTop="10dip" android:paddingBottom="10dip" android:gravity="center_vertical|center_horizontal" android:layout_height="wrap_content" android:text="" android:background="@drawable/press_down" /> </LinearLayout>
child_item.xml文件:(这里要使用自定义的GridView解决ExpandListView与GridView的冲突)
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <com.example.testcitysearch.CustomGridView android:id="@+id/child_gridview" android:layout_width="wrap_content" android:layout_height="wrap_content" android:numColumns="5" android:verticalSpacing="0dip" android:background="@color/main_divider" android:horizontalSpacing="0dip" android:stretchMode="columnWidth" android:gravity="center" /> </RelativeLayout>
grid_item.xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/main_divider" android:orientation="horizontal" > <TextView android:id="@+id/textAddress" android:layout_width="0dip" android:layout_weight="1" android:paddingTop="6dip" android:paddingBottom="6dip" android:gravity="center_vertical|center_horizontal" android:layout_height="wrap_content" android:text="" android:background="@drawable/bac_shape" /> </LinearLayout>
实体类:
GroupCountBean.java文件:
package com.example.testcitysearch; import java.io.Serializable; import java.util.List; /** * * @author jamy * */ public class GroupCountBean implements Serializable{ //组数 public int groupcount; //所有组集合 public List<GroupBean> list; public List<GroupBean> getList() { return list; } public void setList(List<GroupBean> list) { this.list = list; } }
GroupBean.java文件:
package com.example.testcitysearch; import java.io.Serializable; import java.util.List; /** * * @author jamy *每组元素属性 */ public class GroupBean implements Serializable{ //省份名字 public String provincename; //子元素的个数 public int childsize; //每一组中的子元素个数 public int singleSize; public List<MemberBean> list; public List<MemberBean> getList() { return list; } public void setList(List<MemberBean> list) { this.list = list; } }
MemberBean.java文件:
package com.example.testcitysearch; import java.io.Serializable; /** * * @author jamy *每个子元素的属性 */ public class MemberBean implements Serializable{ //标题 public String title; //价格 public String price; //二手价格 public String secondPrice; //标志位 public int flag; }
第二步:编写相应的逻辑代码,其具体的MainActivity.java代码如下所示。
package com.example.testcitysearch; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnGroupExpandListener; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener { private ExpandableListView mListView; // 每组的实体类对象 private GroupBean mGroupBean; // 每个子元素的实体类对象 private MemberBean mMemBean; // 存储一组数据 private List<GroupBean> group_list; // 存储子数据 private List<MemberBean> child_list; // 存储总共有多少组 private List<GroupCountBean> groupcount_list; // flag作为每行的标记 flag=0 表示每行的第一个元素后面依次类推 private int flag = -1; // 所有组实体类 private GroupCountBean mGroupCountBean; // 每个省份所有的城市适配器 private myAdapter adp = null;; // 设置每一行的其中的计数器,用于记录被点击的次数 private int countOne = 0, countTwo = 0, countThree = 0, countFor = 0, countFiv = 0; // 用来临时寄存groupPosition的值 private int groupTemp = -1; // ExpandListView 适配器 private ExAdapter adapter; private Context mContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); } /** * 初始化控件 */ private void initView() { mContext = MainActivity.this; // 存储总共有多少组 groupcount_list = new ArrayList<GroupCountBean>(); mListView = (ExpandableListView) findViewById(R.id.expandlistview); // 添加本地假数据 addDatas(); System.out.println(group_list.toString()); // 扩展ListView Adapter adapter = new ExAdapter(); mListView.setAdapter(adapter); /** * 设置Group默认不展开 */ mListView.setOnGroupExpandListener(new OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { System.out.println(groupPosition); for (int i = 0; i < groupcount_list.size(); i++) { if (groupPosition != i) { mListView.collapseGroup(i); } } } }); } /** * 添加本地数据 */ private void addDatas() { /** * 添加六组数据 k表示添加的Group数 */ for (int k = 0; k < 6; k++) { // 新建GroupBean实体类 mGroupCountBean = new GroupCountBean(); // 存储每一组数据的集合 group_list = new ArrayList<GroupBean>(); mGroupCountBean.groupcount = 5; int temp = mGroupCountBean.groupcount; // group==0表示第一组,先测试第一组 /** * 每组中有五个子元素 i表示每组中子元素的个数 */ for (int i = 0; i < temp; i++) { // 每个Group中的属性 mGroupBean = new GroupBean(); mGroupBean.provincename = "广东" + i; // 子孩子的数目 mGroupBean.childsize = 8; int tep = mGroupBean.childsize; // 总的每一Group中的TextView数目 mGroupBean.singleSize = 5; child_list = new ArrayList<MemberBean>(); for (int j = 0; j < tep; j++) { // 定义每个childItem的实体类 mMemBean = new MemberBean(); mMemBean.title = "深圳" + k + "-" + i + "-" + j; // 将child对象添加到ChildList中 child_list.add(mMemBean); } // 将child_list数据添加到GroupBean实体类中 mGroupBean.setList(child_list); // 将mGroupBean数据添加到group_list集合中 group_list.add(mGroupBean); System.out.println(child_list.toString()); } // 向每组中添加数据 mGroupCountBean.setList(group_list); // 将六组数据添加到组集合中 groupcount_list.add(mGroupCountBean); } } /** * * @author Jamy 扩展ListView Adapter */ class ExAdapter extends BaseExpandableListAdapter { @Override public Object getGroup(int groupPosition) { return null; } @Override public int getGroupCount() { return groupcount_list.size(); } @Override public long getGroupId(int groupPosition) { return groupPosition; } // 第一组 @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { View view = null; GroupHolder groupholder = null; if (convertView == null) { view = View.inflate(MainActivity.this, R.layout.group_item, null); groupholder = new GroupHolder(); groupholder.mTextView = (TextView) view .findViewById(R.id.textAddress); groupholder.mTextView1 = (TextView) view .findViewById(R.id.textAddress1); groupholder.mTextView2 = (TextView) view .findViewById(R.id.textAddress2); groupholder.mTextView3 = (TextView) view .findViewById(R.id.textAddress3); groupholder.mTextView4 = (TextView) view .findViewById(R.id.textAddress4); groupholder.mTextView.setOnClickListener(MainActivity.this); groupholder.mTextView1.setOnClickListener(MainActivity.this); groupholder.mTextView2.setOnClickListener(MainActivity.this); groupholder.mTextView3.setOnClickListener(MainActivity.this); groupholder.mTextView4.setOnClickListener(MainActivity.this); view.setTag(groupholder); } else { view = convertView; groupholder = (GroupHolder) view.getTag(); } int temp = 0; // groupholder.mTextView1.setTag(groupPosition);//设置group的标志 // 设置第groupPosition组,第一个元素 groupholder.mTextView.setTag(R.id.textAddress1, 0); groupholder.mTextView.setTag(R.id.textAddress2, groupPosition); // 设置第groupPosition组,第二个元素 groupholder.mTextView1.setTag(R.id.textAddress1, 1); groupholder.mTextView1.setTag(R.id.textAddress2, groupPosition); // 设置第groupPosition组,第三个元素 groupholder.mTextView2.setTag(R.id.textAddress1, 2); groupholder.mTextView2.setTag(R.id.textAddress2, groupPosition); // 设置第groupPosition组,第四个元素 groupholder.mTextView3.setTag(R.id.textAddress1, 3); groupholder.mTextView3.setTag(R.id.textAddress2, groupPosition); // 设置第groupPosition组,第五个元素 groupholder.mTextView4.setTag(R.id.textAddress1, 4); groupholder.mTextView4.setTag(R.id.textAddress2, groupPosition); // 设置对应的省名 groupholder.mTextView.setText(group_list.get(temp).provincename); groupholder.mTextView1.setText(group_list.get(++temp).provincename); groupholder.mTextView2.setText(group_list.get(++temp).provincename); groupholder.mTextView3.setText(group_list.get(++temp).provincename); groupholder.mTextView4.setText(group_list.get(++temp).provincename); return view; } @Override public Object getChild(int groupPosition, int childPosition) { return null; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } /** * 记得这里要设置为1,否则每组会出现多个相同的GridView * */ @Override public int getChildrenCount(int groupPosition) { return 1; } @Override public boolean hasStableIds() { return true; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } @Override public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { View view = null; ChildHolder childholder = null; if (convertView == null) { view = View.inflate(MainActivity.this, R.layout.child_item, null); childholder = new ChildHolder(); childholder.mGridView = (CustomGridView) view .findViewById(R.id.child_gridview); view.setTag(childholder); } else { view = convertView; childholder = (ChildHolder) view.getTag(); } // 传参数GroupPosition和flag,以方便对应的组和组中第一个元素查找 adp = new myAdapter(groupPosition, flag); childholder.mGridView.setAdapter(adp); childholder.mGridView .setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { GroupCountBean gb = groupcount_list .get(groupPosition); GroupBean sb = gb.getList().get(flag); String title = sb.getList().get(arg2).title; System.out.println(title + "------------->"); Toast.makeText(mContext, title, Toast.LENGTH_SHORT) .show(); } }); return view; } } /** * 每个省份所有的城市适配器 * */ class myAdapter extends BaseAdapter { private List<MemberBean> mDatas;// 定义一个中间变量存放每个省的数据 int group; int temp; public myAdapter(int group, int index) { super(); GroupCountBean gb = groupcount_list.get(group); GroupBean sb = gb.getList().get(index); mDatas = sb.getList(); this.group = group; this.temp = index; } @Override public int getCount() { return mDatas == null ? 0 : mDatas.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = null; GroupHolder groupholder = null; if (convertView == null) { view = View.inflate(MainActivity.this, R.layout.group_item2, null); groupholder = new GroupHolder(); groupholder.mTextView = (TextView) view .findViewById(R.id.textAddress); view.setTag(groupholder); } else { view = convertView; groupholder = (GroupHolder) view.getTag(); } groupholder.mTextView.setText(mDatas.get(position).title); return view; } } /** * 判断并处理Group的展开收缩以及数据更新 * @param isExpand * @param count * @param group */ private void HandleExpandEvent(boolean isExpand,int count,int group){ if (isExpand) { if (count % 2 != 0) { mListView.collapseGroup(group); isExpand = false; } else { isExpand = true; adp.notifyDataSetChanged(); adapter.notifyDataSetChanged(); } isExpand = false; } else { isExpand = true; mListView.expandGroup(group); } } /** * 1.flag作为每行的标记 flag=0 表示每行的第一个元素后面依次类推 * 2.用一个groupTemp临时变量来存放group的值,如果不同则将TextView所有的计数器的值设置为0 * 3.点击当前的TextView时将其他TextView的计数器设置为0 * 4.CountOne......CountFiv分别表示对应TextView点击的计数器 */ @Override public void onClick(View v) { int Id = v.getId(); boolean isExpand = false; int g = -1; // 每行中元素的下标 flag = (Integer) v.getTag(R.id.textAddress1); //每行所在的下标 g = (Integer) v.getTag(R.id.textAddress2);// Row num if (groupTemp != g) { groupTemp = g; //初始化计数模式 setDefault(); } //判断当前行是否展开 isExpand = mListView.isGroupExpanded(g); switch (Id) { case R.id.textAddress: countTwo = 0; countThree = 0; countFiv = 0; countFor = 0; HandleExpandEvent(isExpand, countOne, g); countOne++; break; case R.id.textAddress1: countOne = 0; countThree = 0; countFiv = 0; countFor = 0; HandleExpandEvent(isExpand, countTwo, g); countTwo++; break; case R.id.textAddress2: countTwo = 0; countOne = 0; countFiv = 0; countFor = 0; HandleExpandEvent(isExpand, countThree, g); countThree++; break; case R.id.textAddress3: countTwo = 0; countThree = 0; countFiv = 0; countOne = 0; HandleExpandEvent(isExpand, countFor, g); countFor++; break; case R.id.textAddress4: countTwo = 0; countThree = 0; countOne = 0; countFor = 0; HandleExpandEvent(isExpand, countFiv, g); countFiv++; break; default: break; } } /** * 每一个行Group包含五个TextView */ static class GroupHolder { // 每组中第一个TextView TextView mTextView; // 每组中第二个TextView TextView mTextView1; // 每组中第三个TextView TextView mTextView2; // 每组中第四个TextView TextView mTextView3; // 每组中第五个TextView TextView mTextView4; } /** * 子元素是一个GridView */ static class ChildHolder { CustomGridView mGridView; } /** * 初始化默认的点击次数 */ private void setDefault() { countOne = 0; countTwo = 0; countThree = 0; countFor = 0; countFiv = 0; } }
代码说明:
首先初始化控件加载对应的本地数据以及设置ExpandListView默认不展开,到此初始化的过程完毕。紧接着重点来了。开始设置对应的Adapter,设置前好好想想,需求是使Group中的五个TextView都可点击,并且点击不同的TextView显示不同的GridView,那咋们怎么知道点击的是哪个Group下的那个TextView呢?换言之说我们怎样将GroupPosition和对应的TextView的下标传到onClick()中呢?查找View的方法发现View有一个setTag()方法,由此我们的疑问就可迎刃而解了。在getGroupView()中分别将TextView所在的GroupPosition以及index设置进Tag里,在onClick()中获取GroupPosition以及TextView的index。这样点击关系就一一对应了。
下面是在Onclick()中加入相应的逻辑控制ExpandListView的展开以及对应数据更新。首先是获取对应的GroupPosition以及被点击TextView的index,初始化计数器(方便判断点击的次数,基数次点击展开Group,反之关闭Group)。判断当前Group是否展开,如果状态展开紧随判断是否是偶数次点击,偶数次则关闭Group,否则更新数据。当Group当前状态关闭,直接展开Group。由此ExpandListView实现城市选择的整体逻辑梳理清楚了。
下面看看其运行效果。
效果图
好了,ExpandListView完美实现了城市选择Demo。虽然看上去可能有点复杂,其实最关键的还是对应TextView点击事件的对应。虽然城市选择已经实现了,但当时在开发中确实遇到点问题,下面将本项目中需要注意的几点和大家说说。
1. getChildrenCount()这个方法必须返回1,不然会有每个Group中会有多个相同的GridView(因为这里我们是把每一行看成一个Group,每个TextView对应的其实都是同一个Child,所以这里只有一个Child)
2.一定要在getGroupView方法中设置TextView对应的GroupPositionu以及index,方便TextView的点击事件一一对应。
3.在设置GridView的Adapter时,需要传入GroupPosition和index,以方便设置对应TextView内容。
4.在onClick()中,处理事情之前必须将其他的计数器设置为0(比如:当前点击的是第一个TextView,则countTwo...countFiv都设置为0)。
好,以上就是鄙人用ExpandListView开发的一个简单的城市选择器,可能不是很完善,相当欢迎和大家一起多交流,如果您有好的建议,或者好的实现方式我可以向您讨教讨教。生活就在于不断折腾,想不到今天又时间写了两篇blog很开心。
4.4