Android仿QQ好友列表分组实现增删改及持久化
作者:EricFantastic
Android自带的控件ExpandableListView实现了分组列表功能,本案例在此基础上进行优化,为此控件添加增删改分组及子项的功能,以及列表数据的持久化。
Demo实现效果:
GroupListDemo具体实现:
①demo中将列表页面设计为Fragment页面,方便后期调用;在主界面MainActivity中动态添加GroupListFragment页面;
MainActivity.java
package com.eric.grouplistdemo; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.os.Bundle; import android.widget.RelativeLayout; public class MainActivity extends Activity { public static GroupListFragment fragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fragment = new GroupListFragment(); getFragmentManager().beginTransaction() .replace(R.id.fragContainer, fragment).commit(); } }
动态添加GroupListFragment实例到界面的fragContainer布局中;将fragment声明为static用于在Adapter中组添加子项时进行调用。
activity_main.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:orientation="vertical" > <RelativeLayout android:id="@+id/fragContainer" android:layout_width="match_parent" android:layout_height="match_parent" > </RelativeLayout> </LinearLayout>
②实现自定义适配器类MyAdapter,继承自BaseExpandableListAdapter;组项布局及子项布局;
list_item_parent.xml组项布局文件,展开图标及名称,增删图标;
<?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="50dp" android:background="#0099ff" android:orientation="horizontal"> <ImageView android:id="@+id/image_parent" android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/image_parent1"/> <TextView android:id="@+id/text_parent" android:layout_width="wrap_content" android:layout_height="50dp" android:textColor="#FFF" android:textSize="20sp" android:text="parent1" android:layout_toRightOf="@id/image_parent" android:gravity="center"/> <ImageView android:id="@+id/image_delete" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:src="@drawable/delete"/> <ImageView android:id="@+id/image_add" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/image_delete" android:src="@drawable/add"/> </RelativeLayout>
list_item_child.xml子项布局文件,名称及删除图标;
<?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="40dp" > <TextView android:id="@+id/text_child" android:layout_width="wrap_content" android:layout_height="40dp" android:layout_margin="5dp" android:textColor="#0099ff" android:text="child" android:layout_centerInParent="true" android:gravity="center"/> <ImageView android:id="@+id/image_delete" android:layout_width="40dp" android:layout_height="40dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:src="@drawable/delete"/> </RelativeLayout>
MyAdapter.java自定义适配器
package com.eric.grouplistdemo; import java.util.List; import java.util.Map; import android.content.Context; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; public class MyAdapter extends BaseExpandableListAdapter{ private List<String> parentList; private Map<String,List<String>> map; private Context context; private EditText edit_modify; private ModifyDialog dialog; //构造函数 public MyAdapter(Context context, List<String> parentList, Map<String,List<String>> map) { this.context = context; this.parentList = parentList; this.map = map; } //获取分组数 @Override public int getGroupCount() { return parentList.size(); } //获取当前组的子项数 @Override public int getChildrenCount(int groupPosition) { String groupName = parentList.get(groupPosition); int childCount = map.get(groupName).size(); return childCount; } //获取当前组对象 @Override public Object getGroup(int groupPosition) { String groupName = parentList.get(groupPosition); return groupName; } //获取当前子项对象 @Override public Object getChild(int groupPosition, int childPosition) { String groupName = parentList.get(groupPosition); String chidlName = map.get(groupName).get(childPosition); return chidlName; } //获取组ID @Override public long getGroupId(int groupPosition) { return groupPosition; } //获取子项ID @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return true; } //组视图初始化 @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { final int groupPos = groupPosition; if(convertView == null){ convertView = LayoutInflater.from(context).inflate(R.layout.list_item_parent, null); } ImageView image = (ImageView) convertView.findViewById(R.id.image_parent); ImageView image_add = (ImageView) convertView.findViewById(R.id.image_add); ImageView image_delete = (ImageView) convertView.findViewById(R.id.image_delete); if(isExpanded){ image.setImageResource(R.drawable.image_parent2); }else{ image.setImageResource(R.drawable.image_parent1); } image_add.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { alertAddDialog(MainActivity.fragment.getActivity(), "新增子项", groupPos); } }); image_delete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { GroupListFragment.deleteGroup(groupPos); } }); TextView parentText = (TextView) convertView.findViewById(R.id.text_parent); parentText.setText(parentList.get(groupPosition)); return convertView; } //子项视图初始化 @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { final int groupPos = groupPosition; final int childPos = childPosition; if(convertView == null){ convertView = LayoutInflater.from(context).inflate(R.layout.list_item_child, null); } TextView childText = (TextView) convertView.findViewById(R.id.text_child); ImageView image_delete = (ImageView) convertView.findViewById(R.id.image_delete); String parentName = parentList.get(groupPosition); String childName = map.get(parentName).get(childPosition); childText.setText(childName); image_delete.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { GroupListFragment.deleteChild(groupPos, childPos); } }); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } //弹新增子项对话框 public void alertAddDialog(Context context, String title, int currentGroup){ final int group = currentGroup; dialog = new ModifyDialog(context, title, null); edit_modify = dialog.getEditText(); dialog.setOnClickCommitListener(new OnClickListener() { @Override public void onClick(View v) { GroupListFragment.addChild(group, edit_modify.getText().toString()); dialog.dismiss(); } }); dialog.show(); } }
构造函数:将传入的parentList和map进行数据同步,parentList保存组数据,map保存对应组及其子项list数据;
获取分组数及子项数等很简单,就不介绍了,主要讲述一下,组视图初始化和子项视图初始化这两个函数;
组视图初始化getGroupView():尽量复用convertView防止内存泄露,首先是进行判断,若convertView为空,则进行组视图初始化,加载list_item_parent子项布局;获取到相应的组布局控件:展开图标image、添加图标image_add、删除图标image_delete;通过传递过来的布尔类型参数isExpanded,进行判断给image赋值展开图标或合并图标;分别给添加图标和删除图标添加点击事件,分别调用GroupListFragment中的弹出添加窗口函数alertAddDialog()和删除组函数deleteGroup();
子项视图初始化getChildView():同样尽量复用convertView防止内存泄露,首先是进行判断,若convertView为空,则进行子项视图初始化,加载list_item_child子项布局;获取到相应的子布局控件:内容文本ChildText和删除图标Image_delete;从parentList和map中分别获取到,当前子项的组名parentName和子项名childName,赋值ChildText,删除图标添加点击事件,调用删除子项函数deleteChild();
③实现自定义对话框类ModifyDialog,继承自Dialog类,对输入修改内容,或新增项内容进行过渡;
no_title_dialog.xml,在values目录下自定义Dialog的style类型xml,除去Dialog的标题栏;
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android"> <style name="noTitleDialog" parent="android:style/Theme.Dialog"> <item name="android:width">300dp</item> <item name="android:height">40dp</item> <item name="android:windowNoTitle">true</item> </style> </resources>
dialog_modify.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:orientation="vertical" > <TextView android:id="@+id/text_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center" android:background="#0099ff" android:text="修改名称" android:textColor="#FFF" android:textSize="20sp"/> <EditText android:id="@+id/edit_modify" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="组名称"/> <Button android:id="@+id/btn_commit" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="确定" android:textColor="#FFF" android:background="#0099ff" /> </LinearLayout>
ModifyDialog.java
package com.eric.grouplistdemo; import android.app.Dialog; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class ModifyDialog extends Dialog{ private TextView text_title; private EditText edit_modify; private Button btn_commit; public ModifyDialog(Context context, String title, String name) { super(context, R.style.noTitleDialog); View view = LayoutInflater.from(getContext()) .inflate(R.layout.dialog_modify, null); text_title = (TextView) view.findViewById(R.id.text_title); edit_modify = (EditText)view.findViewById(R.id.edit_modify); btn_commit = (Button) view.findViewById(R.id.btn_commit); text_title.setText(title); edit_modify.setText(name); super.setContentView(view); } public EditText getEditText(){ return edit_modify; } public void setOnClickCommitListener(View.OnClickListener listener){ btn_commit.setOnClickListener(listener); } }
ModifyDialog自定义构造函数中,通过super()加载刚刚自定义的no_title_dialog.xml,声明View加载layout布局dialog_modify.xml;并且获取布局中的相应控件,将构造函数中传来的字符串title和name,分别赋值到标题文本和输入框控件中;最后调用setContentView()初始化对话框视图;
添加一个返回输入框控件的函数getEditText(),用于获取输入框输入的内容;
还需要一个自定义的点击事件监听器,绑定在确定按钮上;
④准备工作都完成了,下面就实现GroupListFragment,包括数据的初始化及持久化保存,组项和子项的增删改操作,列表子项点击事件,列表组项和子项的长按事件;
fragment_group_list.xml 页面的布局文件ExpandableListView列表以及一个添加组图标;
<?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" > <ExpandableListView android:id="@+id/expandablelistview" android:layout_width="match_parent" android:layout_height="match_parent" android:groupIndicator="@null" > </ExpandableListView> <ImageView android:id="@+id/image_add" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" android:src="@drawable/add"/> </RelativeLayout>
这里需要将ExpandableListView的groupIndicator属性设置为@null,不使用其自带的展开图标;
GroupListFragment.java 加载列表的页面
package com.eric.grouplistdemo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.R.integer; import android.app.Fragment; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ImageView; import android.widget.Toast; public class GroupListFragment extends Fragment{ private View view; private ExpandableListView expandableListView; public static MyAdapter adapter; public static List<String> parentList; public static Map<String,List<String>> map; private ModifyDialog dialog; private EditText edit_modify; private ImageView image_add; private int currentGroup,currentChild; public static SharedPreferences sp; public static Editor editor; public static String dataMap,dataParentList; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.fragment_group_list, container, false); expandableListView = (ExpandableListView) view.findViewById(R.id.expandablelistview); image_add = (ImageView) view.findViewById(R.id.image_add); image_add.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { alertAddDialog(getActivity(), "新增组"); } }); initData(); adapter = new MyAdapter(getActivity().getApplicationContext(), parentList, map); expandableListView.setAdapter(adapter); //设置子项点击事件 MyOnClickListener myListener = new MyOnClickListener(); expandableListView.setOnChildClickListener(myListener); //设置长按点击事件 MyOnLongClickListener myLongListener = new MyOnLongClickListener(); expandableListView.setOnItemLongClickListener(myLongListener); return view; } public void initData(){ map = new HashMap<String, List<String>>(); parentList = new ArrayList<String>(); sp = getActivity().getApplicationContext().getSharedPreferences("spfile", getActivity().MODE_PRIVATE); dataMap = sp.getString("dataMap", null); dataParentList = sp.getString("dataParentList", null); if(dataMap == null || dataParentList == null){ parentList = new ArrayList<String>(); parentList.add("客厅"); parentList.add("厨房"); parentList.add("卧室"); List<String> list1 = new ArrayList<String>(); list1.add("客厅空调"); list1.add("客厅电视"); list1.add("客厅电灯"); map.put("客厅", list1); List<String> list2 = new ArrayList<String>(); list2.add("厨房油烟机"); list2.add("厨房电灯"); list2.add("厨房电器"); map.put("厨房", list2); List<String> list3 = new ArrayList<String>(); list3.add("卧室空调"); list3.add("卧室灯光"); list3.add("卧室电视"); map.put("卧室", list3); }else{ try { //初始化parentList JSONArray jsonArray = new JSONArray(dataParentList); for (int i = 0; i < jsonArray.length(); i++) { parentList.add(jsonArray.get(i).toString()); } //初始化map JSONObject jsonObject = new JSONObject(dataMap); for (int i = 0; i < jsonObject.length(); i++) { String key = jsonObject.getString(parentList.get(i)); JSONArray array = new JSONArray(key); List<String> list = new ArrayList<String>(); for (int j = 0; j < array.length(); j++) { list.add(array.get(j).toString()); } map.put(parentList.get(i), list); } Log.d("eric", "①:"+map+"②:"+parentList); } catch (JSONException e) { e.printStackTrace(); Log.e("eric","String转Map或List出错"+e); } } Log.e("eric", dataMap+"!&&!"+dataParentList); saveData(); } //自定义点击监听事件 public class MyOnClickListener implements ExpandableListView.OnChildClickListener{ @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { String str = "choose"+groupPosition+"-"+childPosition; Toast.makeText(getActivity(), str, Toast.LENGTH_SHORT).show(); return false; } } //自定义长按监听事件 public class MyOnLongClickListener implements AdapterView.OnItemLongClickListener{ @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { //长按子项 if (ExpandableListView.getPackedPositionType(id) == ExpandableListView.PACKED_POSITION_TYPE_CHILD){ long packedPos = ((ExpandableListView) parent).getExpandableListPosition(position); int groupPosition = ExpandableListView.getPackedPositionGroup(packedPos); int childPosition = ExpandableListView.getPackedPositionChild(packedPos); currentGroup = groupPosition; currentChild = childPosition; String str = (String)adapter.getChild(groupPosition, childPosition); alertModifyDialog("修改此项名称",str); Toast.makeText(getActivity(),str,Toast.LENGTH_SHORT).show(); return true; //长按组 }else if(ExpandableListView.getPackedPositionType(id) == ExpandableListView.PACKED_POSITION_TYPE_GROUP){ long packedPos = ((ExpandableListView) parent).getExpandableListPosition(position); int groupPosition = ExpandableListView.getPackedPositionGroup(packedPos); int childPosition = ExpandableListView.getPackedPositionChild(packedPos); currentGroup = groupPosition; currentChild = childPosition; String group = parentList.get(groupPosition); alertModifyDialog("修改组名称", group); String str = (String)adapter.getGroup(groupPosition); Toast.makeText(getActivity(),str,Toast.LENGTH_SHORT).show(); } return false; } } //新增组 public static void addGroup(String newGroupName){ parentList.add(newGroupName); List<String> list = new ArrayList<String>(); map.put(newGroupName, list); adapter.notifyDataSetChanged(); saveData(); } //新增子项到指定组 public static void addChild(int groupPosition, String newChildName){ String groupName = parentList.get(groupPosition); List<String> list = map.get(groupName); list.add(newChildName); adapter.notifyDataSetChanged(); saveData(); } //删除指定组 public static void deleteGroup(int groupPos){ String groupName = parentList.get(groupPos); map.remove(groupName); parentList.remove(groupPos); adapter.notifyDataSetChanged(); saveData(); } //删除指定子项 public static void deleteChild(int groupPos, int childPos){ String groupName = parentList.get(groupPos); List<String> list = map.get(groupName); list.remove(childPos); adapter.notifyDataSetChanged(); saveData(); } //修改该项名称 public void modifyName(int groupPosition, int childPosition, String modifyName){ Toast.makeText(getActivity(), String.valueOf(groupPosition)+'-'+String.valueOf(childPosition), Toast.LENGTH_SHORT).show(); if(childPosition<0){ //修改组名称 String groupName = parentList.get(groupPosition); if(!groupName.equals(modifyName)){ map.put(modifyName, map.get(groupName)); map.remove(groupName); parentList.set(groupPosition, modifyName); } }else{ //修改子项名称 String group = parentList.get(groupPosition); List<String> list =map.get(group); list.set(childPosition, modifyName); map.put(group, list); } adapter.notifyDataSetChanged(); saveData(); } //弹修改对话框 public void alertModifyDialog(String title, String name){ dialog = new ModifyDialog(getActivity(), title, name); edit_modify = dialog.getEditText(); dialog.setOnClickCommitListener(new OnClickListener() { @Override public void onClick(View v) { modifyName(currentGroup, currentChild, edit_modify.getText().toString()); dialog.dismiss(); } }); dialog.show(); } //弹新增组对话框 public void alertAddDialog(Context context, String title){ dialog = new ModifyDialog(context, title, null); edit_modify = dialog.getEditText(); dialog.setOnClickCommitListener(new OnClickListener() { @Override public void onClick(View v) { addGroup(edit_modify.getText().toString()); dialog.dismiss(); } }); dialog.show(); } //保存数据 public static void saveData(){ JSONObject jsonObject = new JSONObject(map); dataMap = jsonObject.toString(); dataParentList = parentList.toString(); editor = sp.edit(); editor.putString("dataMap", dataMap); editor.putString("dataParentList", dataParentList); editor.commit(); } }
内容有点多,一个个来:
初始化Fragment页面函数onCreateView():
首先,要进行layout布局fragment_group_list.xml加载,获取相应控件的实例,expandableListView列表控件以及添加组图标image_add,添加组图标添加点击事件;调用initData()方法进行组数据parentList和组对应子项map的初始化;然后,实例化adapter传入parentList和map数据到自定义适配器MyAdapter中,并将其绑定到expandableListView上;最后,给expandableListView添加自定义子项点击事件监听器,组项和子项长按事件监听器;
初始化数据函数initData():该函数通过ShareReference来保存数据;
首先,实例化parentList和map,从ShareReference中获取到保存的String类型的parentList和map实际数据,赋值到dataMap和dataParentList中,若当中数据不存在,默认返回null,第一次运行程序的时候肯定是没有数据的,故进行判断,若没获取到数据,那就给parentList和Map赋值“客厅”、“厨房”、“卧室”等一系列数据;如果有数据的话,那就得进行数据的转换,因为之前保存的String类型数据,parentList初始化:将dataParentList转换为JSONArray类型,通过循环将数据赋值到parentList中;同理,将dataMap转换为JSONObject类型,通过两层for循环将数据赋值到map中。
自定义点击子项监听其类MyOnClickListener:实现ExpandableListView.OnChildClickListener接口,这里就简单的进行Toast操作,读者可以修改为跳转等其他自定义功能;
自定义长按组项或子项监听器类MyOnLongClickListener:实现AdapterView.OnItemLongClickListener接口,通过调用ExpandableListView的getPackedPositionType()方法来判断此时长按的是组项还是子项;这里将长按组和子项分离出来,方便根据功能修改;无论是组项还是子项长按后,都调用alertModifyDialog()弹修改对话框,传入当前项的名称;
新增组addGroup函数:将传递过来的newGroupName,添加parentList中,且定义一个空的list,绑定newGroupName,将这对string和list,添加到map中;最后,调用adapter.notifyDataSetChanged()刷新列表,调用saveData()保存数据到ShareReference;
同理,新增子项到指定组addChild(),删除指定组deleteGroup(),删除指定子项deleteChild(),都是对parentList和map进行操作,最后刷新列表和保存数据;
修改该项名称modifyName():通过传递过来childPosition进行判断,当修改项为组项时,childPosition的值为-1,以此区分组项和子项;这里遇到一个问题,当组项提交的名称与原名称相同会报错,故添加一个判断,仅提交的名称不同时才进行修改操作;修改的具体实现还是对parentList和map的操作,以修改组项为例,同新增组,添加一个modifyName的组,其对应的List为原来组名对应的List数据,然后再将原来的groupName组及其对应的list删除,实现修改;最后同样要刷新列表和保存数据;
弹修改对话框alertModifyDialog()函数:首先对自定义的dialog进行实例化,初始化标题和输入框中的数据;调用getEditText()函数获取输入框实例,添加提交按钮点击事件,调用modifyName方法传入当前组和子项数据,以及输入框中提交的文本;
弹新增组对话框alertAddDialog()函数:同样,实例化dialog,获取输入框控件,点击事件中调用addGroup()方法添加数据;
保存数据saveData()函数:将parentList和map数据转化为String类型,分别赋值,保存至ShareReference的Editor中并提交;
以上就是本文的全部内容,希望对大家的学习有所帮助。