一個垂直滾動的顯示兩個級別(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它是如何定義長按事件的呢?
- @Override
- public boolean onItemLongClick(AdapterView<?> arg0, View view,
- int pos, long id) {
- //pos不可用說明見下文
- return false;
- }
如果這個方法是用在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>
基本就是我們高中學的數學那套,找規律,判斷點擊的child是在第幾組,在判斷是第幾個。2.這種方法我看了一下,和第一種方法差不多,這種是通過id來判斷位置
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>
4,(這也是網上說的最多的,自己也被坑了,不知道view是複用的啊)看到了onItemLongClick方法第二個參數:view。這裏的view是你按中的位置對應的view。view有個方法getTag(int key)。如果在創建此view的時候就把groupPos,childPos通過setTag(int key, Object value)設置進去,在響應onItemLongClick不就可以直接拿出來用了麼。
現在就要講到必須使用自定義的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,自己看到了自己很多不足,自己要多努力了。