Android仿QQ好友列表分组实现增删改及持久化
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中并提交;
以上就是本文的全部内容,希望对大家的学习有所帮助。