一个垂直滚动的显示两个级别(Child,Group)列表项的视图,列表项来自ExpandableListAdapter 。组可以单独展开。
1.重要方法
expandGroup(int groupPos) :在分组列表视图中展开一组,
setSelectedGroup(int groupPosition) :设置选择指定的组。
setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) :设置选择指定的子项。
getPackedPositionGroup(long packedPosition) :返回所选择的组
getPackedPositionForChild(int groupPosition, int childPosition) :返回所选择的子项
getPackedPositionType(long packedPosition) :返回所选择项的类型(Child,Group)
isGroupExpanded(int groupPosition) :判断此组是否展开
ExpandableListAdapter
一个接口,将基础数据链接到一个ExpandableListView。此接口的实施将提供访问Child的数据(由组分类),并实例化的Child和Group。
1.重要方法
getChildId(int groupPosition, int childPosition)获取与在给定组给予孩子相关的数据。
getChildrenCount(int groupPosition) 返回在指定Group的Child数目。
2.上代码
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">public class MyExpandableListAdapter extends BaseExpandableListAdapter { private List<String> groupData; private List<List<String>> childrenData; private Context context; public MyExpandableListAdapter(Context context, List<String> groupData, List<List<String>> childrenData) { this.context = context; this.groupData = groupData; this.childrenData = childrenData; } @Override public int getGroupCount() { return groupData.size(); } @Override public int getChildrenCount(int i) { return childrenData.get(i).size(); } @Override public Object getGroup(int i) { return groupData.get(i); } @Override public Object getChild(int i, int i1) { return childrenData.get(i).get(i1); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int groupPosition, boolean b, View convertView, ViewGroup viewGroup) { GroupHolder groupHolder = null; if (convertView == null) { convertView = View.inflate(context, R.layout.item_head, null); groupHolder = new GroupHolder(); groupHolder.txt = (TextView) convertView.findViewById(R.id.txt); convertView.setTag(groupHolder); } else { groupHolder = (GroupHolder) convertView.getTag(); } groupHolder.txt.setText(groupData.get(groupPosition)); return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { ItemHolder itemHolder = null; if (convertView == null) { convertView = View.inflate(context, R.layout.item_child, null); itemHolder = new ItemHolder(); itemHolder.tv_mes = (TextView) convertView.findViewById(R.id.tv_mes); convertView.setTag(itemHolder); } else { itemHolder = (ItemHolder) convertView.getTag(); } itemHolder.tv_mes.setText(childrenData.get(groupPosition).get( childPosition)); return convertView; } @Override public boolean isChildSelectable(int i, int i1) { return true; } class GroupHolder { public TextView txt; } class ItemHolder { public TextView tv_mes; } } </span></span></span>3.MainActivity的代码
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;">public class MainActivity extends Activity implements ExpandableListView.OnChildClickListener { private List<String> groupArray; private List<List<String>> childArray; private ExpandableListView expandableListView; private MyExpandableListAdapter mMyExpandableListAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initData(); } private void initData() { //随便一堆测试数据 groupArray = new ArrayList<String>(); groupArray.add("A"); groupArray.add("B"); groupArray.add("C"); List<String> item_list1 = new ArrayList<String>(); item_list1.add("1"); item_list1.add("2"); item_list1.add("3"); List<String> item_list2 = new ArrayList<String>(); item_list2.add("4"); item_list2.add("5"); item_list2.add("6"); List<String> item_list3 = new ArrayList<String>(); item_list3.add("7"); item_list3.add("8"); item_list3.add("9"); childArray = new ArrayList<List<String>>(); childArray.add(item_list1); childArray.add(item_list2); childArray.add(item_list3); mMyExpandableListAdapter = new MyExpandableListAdapter(MainActivity.this, groupArray, childArray); expandableListView.setAdapter(mMyExpandableListAdapter); for (int i = 0; i < mMyExpandableListAdapter.getGroupCount(); i++) {//展开所有父分组 expandableListView.expandGroup(i); } } private void initView() { expandableListView = (ExpandableListView) findViewById(R.id.expandableListView); expandableListView.setOnChildClickListener(this); } @Override public boolean onChildClick(ExpandableListView expandableListView, View view, int groupPosition, int childPosition, long l) { Toast.makeText(MainActivity.this, "点击了" + childArray.get(groupPosition).get(childPosition), Toast.LENGTH_SHORT).show(); return true; } } </span></span></span>效果如图
4.expandableListview它是如何定义长按事件的呢?
如果这个方法是用在ListView长按事件中刚刚好,但在ExpandableListView中,第三个参数pos不能区分开点击的是父项还是子项,以及哪个父项或子项。
在ExpandableListView响应的onItemLongCkick方法中,pos参数值为:从上到下,父项+展现的子项到点击位置的数目(注意:是展现的,隐藏的子项不包括,从0开始)。
例如:
父项1(隐藏3个子项)
父项2
|—子项2-0
|—子项2-1
|—子项2-2
长按子项2-1时,pos值为3。显然根据pos值是无法确定点击的是哪个子项或父项的。
因此依赖pos是很难处理点击位置的。
如果可以直接在onItemLongClick方法中获取groupPos,及childPos该多好呢?
下面我就来所说有几种方法:
1.这种方法是通过下标来找到位置,
<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"> @Override public boolean onItemLongClick(AdapterView<?> arg0, View view, int pos, long id) { long packedPosition = mExpandableListView.getExpandableListPosition(i); int itemType = ExpandableListView.getPackedPositionType(packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition); int childPosition = ExpandableListView.getPackedPositionChild(packedPosition); //根据groupPos判断你长按的是哪个父项,做相应处理(弹框等) } else { //根据groupPos及childPos判断你长按的是哪个父项下的哪个子项,然后做相应处理。 } return false; } </span> //根据groupPos判断你长按的是哪个父项,做相应处理(弹框等) } else { //根据groupPos及childPos判断你长按的是哪个父项下的哪个子项,然后做相应处理。 } return false; } </span> </span><pre name="code" class="html"><span style="font-size:18px;">PositionMetadata getUnflattenedPos(final int flPos) { /* Keep locally since frequent use */ final ArrayList<GroupMetadata> egml = mExpGroupMetadataList; final int numExpGroups = egml.size(); /* Binary search variables */ int leftExpGroupIndex = 0; int rightExpGroupIndex = numExpGroups - 1; int midExpGroupIndex = 0; GroupMetadata midExpGm; if (numExpGroups == 0) { /* * There aren't any expanded groups (hence no visible children * either), so flPos must be a group and its group pos will be the * same as its flPos */ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, flPos, -1, null, 0); } /* * Binary search over the expanded groups to find either the exact * expanded group (if we're looking for a group) or the group that * contains the child we're looking for. If we are looking for a * collapsed group, we will not have a direct match here, but we will * find the expanded group just before the group we're searching for (so * then we can calculate the group position of the group we're searching * for). If there isn't an expanded group prior to the group being * searched for, then the group being searched for's group position is * the same as the flat list position (since there are no children before * it, and all groups before it are collapsed). */ while (leftExpGroupIndex <= rightExpGroupIndex) { midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex) / 2 + leftExpGroupIndex; midExpGm = egml.get(midExpGroupIndex); if (flPos > midExpGm.lastChildFlPos) { /* * The flat list position is after the current middle group's * last child's flat list position, so search right */ leftExpGroupIndex = midExpGroupIndex + 1; } else if (flPos < midExpGm.flPos) { /* * The flat list position is before the current middle group's * flat list position, so search left */ rightExpGroupIndex = midExpGroupIndex - 1; } else if (flPos == midExpGm.flPos) { /* * The flat list position is this middle group's flat list * position, so we've found an exact hit */ return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, midExpGm.gPos, -1, midExpGm, midExpGroupIndex); } else if (flPos <= midExpGm.lastChildFlPos /* && flPos > midGm.flPos as deduced from previous * conditions */) { /* The flat list position is a child of the middle group */ /* * Subtract the first child's flat list position from the * specified flat list pos to get the child's position within * the group */ final int childPos = flPos - (midExpGm.flPos + 1); return PositionMetadata.obtain(flPos, ExpandableListPosition.CHILD, midExpGm.gPos, childPos, midExpGm, midExpGroupIndex); } } /* * If we've reached here, it means the flat list position must be a * group that is not expanded, since otherwise we would have hit it * in the above search. */ /** * If we are to expand this group later, where would it go in the * mExpGroupMetadataList ? */ int insertPosition = 0; /** What is its group position in the list of all groups? */ int groupPos = 0; /* * To figure out exact insertion and prior group positions, we need to * determine how we broke out of the binary search. We backtrack * to see this. */ if (leftExpGroupIndex > midExpGroupIndex) { /* * This would occur in the first conditional, so the flat list * insertion position is after the left group. Also, the * leftGroupPos is one more than it should be (since that broke out * of our binary search), so we decrement it. */ final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex-1); insertPosition = leftExpGroupIndex; /* * Sums the number of groups between the prior exp group and this * one, and then adds it to the prior group's group pos */ groupPos = (flPos - leftExpGm.lastChildFlPos) + leftExpGm.gPos; } else if (rightExpGroupIndex < midExpGroupIndex) { /* * This would occur in the second conditional, so the flat list * insertion position is before the right group. Also, the * rightGroupPos is one less than it should be, so increment it. */ final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex); insertPosition = rightExpGroupIndex; /* * Subtracts this group's flat list pos from the group after's flat * list position to find out how many groups are in between the two * groups. Then, subtracts that number from the group after's group * pos to get this group's pos. */ groupPos = rightExpGm.gPos - (rightExpGm.flPos - flPos); } else { // TODO: clean exit throw new RuntimeException("Unknown state"); } return PositionMetadata.obtain(flPos, ExpandableListPosition.GROUP, groupPos, -1, null, insertPosition); } /** * Translates either a group pos or a child pos (+ group it belongs to) to a * flat list position. If searching for a child and its group is not expanded, this will * return null since the child isn't being shown in the ListView, and hence it has no * position. * * @param pos a {@link ExpandableListPosition} representing either a group position * or child position * @return the flat list position encompassed in a {@link PositionMetadata} * object that contains additional useful info for insertion, etc., or null. */ PositionMetadata getFlattenedPos(final ExpandableListPosition pos) { final ArrayList<GroupMetadata> egml = mExpGroupMetadataList; final int numExpGroups = egml.size(); /* Binary search variables */ int leftExpGroupIndex = 0; int rightExpGroupIndex = numExpGroups - 1; int midExpGroupIndex = 0; GroupMetadata midExpGm; if (numExpGroups == 0) { /* * There aren't any expanded groups, so flPos must be a group and * its flPos will be the same as its group pos. The * insert position is 0 (since the list is empty). */ return PositionMetadata.obtain(pos.groupPos, pos.type, pos.groupPos, pos.childPos, null, 0); } /* * Binary search over the expanded groups to find either the exact * expanded group (if we're looking for a group) or the group that * contains the child we're looking for. */ while (leftExpGroupIndex <= rightExpGroupIndex) { midExpGroupIndex = (rightExpGroupIndex - leftExpGroupIndex)/2 + leftExpGroupIndex; midExpGm = egml.get(midExpGroupIndex); if (pos.groupPos > midExpGm.gPos) { /* * It's after the current middle group, so search right */ leftExpGroupIndex = midExpGroupIndex + 1; } else if (pos.groupPos < midExpGm.gPos) { /* * It's before the current middle group, so search left */ rightExpGroupIndex = midExpGroupIndex - 1; } else if (pos.groupPos == midExpGm.gPos) { /* * It's this middle group, exact hit */ if (pos.type == ExpandableListPosition.GROUP) { /* If it's a group, give them this matched group's flPos */ return PositionMetadata.obtain(midExpGm.flPos, pos.type, pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex); } else if (pos.type == ExpandableListPosition.CHILD) { /* If it's a child, calculate the flat list pos */ return PositionMetadata.obtain(midExpGm.flPos + pos.childPos + 1, pos.type, pos.groupPos, pos.childPos, midExpGm, midExpGroupIndex); } else { return null; } } } /* * If we've reached here, it means there was no match in the expanded * groups, so it must be a collapsed group that they're search for */ if (pos.type != ExpandableListPosition.GROUP) { /* If it isn't a group, return null */ return null; } /* * To figure out exact insertion and prior group positions, we need to * determine how we broke out of the binary search. We backtrack to see * this. */ if (leftExpGroupIndex > midExpGroupIndex) { /* * This would occur in the first conditional, so the flat list * insertion position is after the left group. * * The leftGroupPos is one more than it should be (from the binary * search loop) so we subtract 1 to get the actual left group. Since * the insertion point is AFTER the left group, we keep this +1 * value as the insertion point */ final GroupMetadata leftExpGm = egml.get(leftExpGroupIndex-1); final int flPos = leftExpGm.lastChildFlPos + (pos.groupPos - leftExpGm.gPos); return PositionMetadata.obtain(flPos, pos.type, pos.groupPos, pos.childPos, null, leftExpGroupIndex); } else if (rightExpGroupIndex < midExpGroupIndex) { /* * This would occur in the second conditional, so the flat list * insertion position is before the right group. Also, the * rightGroupPos is one less than it should be (from binary search * loop), so we increment to it. */ final GroupMetadata rightExpGm = egml.get(++rightExpGroupIndex); final int flPos = rightExpGm.flPos - (rightExpGm.gPos - pos.groupPos); return PositionMetadata.obtain(flPos, pos.type, pos.groupPos, pos.childPos, null, rightExpGroupIndex); } else { return null; } } </span>
getExpandableListView().setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { if (ExpandableListView.getPackedPositionType(id) == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { int groupPosition = ExpandableListView.getPackedPositionGroup(id); int childPosition = ExpandableListView.getPackedPositionChild(id); // You now have everything that you would as if this was an OnChildClickListener() // Add your logic here. // Return true as we are handling the event. return true; } return false; } });
3,(这是网上搜的,你说写就写呗,还不让人看明白了,自己被坑了)
<span style="font-size:18px;"><span style="font-size:18px;">使用上下文菜单实现长按事件 注册上下文菜单 registerForContextMenu(downElv);,并重新下面两个方法: @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; //menuinfo该对象提供了选中对象的附加信息 int type = ExpandableListView .getPackedPositionType(info.packedPosition); int group = ExpandableListView .getPackedPositionGroup(info.packedPosition); int child = ExpandableListView .getPackedPositionChild(info.packedPosition); System.out.println("LongClickListener*type-------------------------" + type); System.out.println("LongClickListener*group-------------------------" + group); System.out.println("LongClickListener*child-------------------------" + child); this.setMGroupID(group); this.setMChildrenID(child); //处理逻辑 if (type == 0) {// 分组长按事件 showDialog(Globals.DIALOG_GROUPS_LONGCLICK); } else if (type == 1) {// 长按好友列表项 //showDialog(Globals.DIALOG_FRIENDlIST_LONGCLICK);可以自定义Dialog显示, 也可以使用menu.add(0,0,0,"增加");添加菜单项 } } //一般在此函数下面编写响应事件 @Override public boolean onContextItemSelected(MenuItem item) { return super.onContextItemSelected(item); } </span></span>
现在就要讲到必须使用自定义的BaseExpandableListAdapter的理由了。
要把groupPos,childPos通过setTag的方式绑定到view中,就必须操作该view的创建过程。要控制这个过程就必须要在自定义BaseExpandableListAdapter中重写getGroupView及getChildView方法进行操作。如下:
@Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { //我这里仅通过自己写的mkChildView()方法创建TextView来显示文字,更复杂的可以通过LayoutInflater来填充一个view TextView childTv = mkChildView(); // 标记位置 // 必须使用资源Id当key(不是资源id会出现运行时异常),android本意应该是想用tag来保存资源id对应组件。 // 将groupPosition,childPosition通过setTag保存,在onItemLongClick方法中就可以通过view参数直接拿到了! childTv.setTag(R.id.xxx01, groupPosition); childTv.setTag(R.id.xxx02, childPosition); return childTv; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { TextView groupTv = mkGroupView(); // 设置同getChildView一样 groupTv.setTag(R.id.xxx01, groupPosition); groupTv.setTag(R.id.xxx02, -1); //设置-1表示长按时点击的是父项,到时好判断。 groupTv.setText(groups[groupPosition]); return groupTv; } }通过这个expandablelistview,自己看到了自己很多不足,自己要多努力了。