RecyclerView进阶使用-实现仿支付宝菜单编辑页面拖拽功能
2017-11-06 18:24
701 查看
先上一张效果图
之前看见了支付宝的菜单编辑页面,有个类似GridView的拖拽排序效果,于是想自己实现一下。经过网上的大量资料搜索,最终得出了如下的解决方案。
首先主页面的item和编辑页面的子条目是一致的,那么属性都应该有
其中,group属性用于标识这个item的分组是什么,
根据你项目的需求,来决定你自己的组的类别。请注意的favorite这组。这个组是其他组选了过后才放进这个组的,也就是说里面的item的group属性并不是GROUP_FAVORITE。实际上分组标签的另一个作用是用于获取分组数据的key。比如说,你要向服务器请求favorite这组数据,那么传GROUP_FAVORITE给服务器就可以得到数据了。
然后再看看列表条目。标记页列表的条目是一个复合条目,每个条目包含一个组的标题和其包含的所有子元素,那么,我们就可以设计为:
mGroup就是上面的Group的标签,mGroupTitle是改标签所对应的要显示的标题(当然这个字段不是必须,你也可以在外部根据取到的标签进行判断),mMenuItemList就是其所包含的子列表数据了。
首先,编辑页从整体上应该分为两个部分。首部和列表部分。所以设计模式可以考虑在正常的list上面添加一个header。又或者传入两种的itemViewType,分别进行渲染视图。两种方案都可行。只是第一种的话,原生recyclerView是没有header,footer说法的,需要自己封装。在这里,我选择第一种方法,自行封装header。
在这里为了演示,我将json原始数据放到asset目录下,用于模拟从服务器获取的数据。
2)本地持久化
最正规的做法是采用数据库方式。例如GreenDao或者Realm都是很牛掰的数据库框架。这里,由于菜单数据相对较少,我就直接以sharedPreference来进行数据存储读取。另外,考虑到json对象与实体对象的相互转换,我这里推荐大家使用阿里巴巴的fastjson,来进行数据的序列化与反序列化操作,转换效率杠杠滴。
下面的就是我写的一个数据操作类。
其中recyclerview包是我自己对AdvancedRecyclerView进行的二次封装。封装后,实现起来更简单。tools包是个工具包,里面包含了一些比较有用的方法,这些大家都可以直接应用到自己的项目里面。然后就是adapter包,这里面是对recyclerview包里面的具体实现。另外MenuHelper的初始化操作,在MyApplication里面。
http://blog.csdn.net/cjs1534717040/article/details/78285741
最后放上源码地址(需要10个积分,如果你有请支持一下,如果没有,请@我chenjunsen@outlook.com或者qq1534717040)
http://download.csdn.net/download/cjs1534717040/10106300
之前看见了支付宝的菜单编辑页面,有个类似GridView的拖拽排序效果,于是想自己实现一下。经过网上的大量资料搜索,最终得出了如下的解决方案。
1.实现拖拽的控件
整个拖拽的控件,可以使用网上的可拖拽GridView或者自定义的RecyclerView.我自己是两种都尝试过。发现GridView的实现方式,并不是真正的拖拽,而是将你要拖拽的item制造一个镜像,接着将原来的item的Visiblity设置成看不见,你拖拽的时候,实际移动的是镜像。效果虽然能实现,但是和RecyclerView比起来,感觉就low了一点。RecyclerView首先是自带item插入移动变动的动画的。通过ItemTouchHelper的一个官方辅助类可以比较轻松地实现拖拽效果,流畅性比用GridView实现地假拖拽要好得多。最终在Github上找到了一个第三方地RecyclerView的强大的封装类AdvancedRecyclerView,Github的地址是:https://github.com/h6ah4i/android-advancedrecyclerview,这个类封装得还是比较强大的,支持侧滑,拖拽,header,footer等等。有兴趣的童鞋可以自己去下载原生demo看看。2.建立数据模型
首先主页面的item和编辑页面的子条目是一致的,那么属性都应该有
private String name;//名字 private String icon;//图标 private String desc;//item的描述 private String group;//item所属组
其中,group属性用于标识这个item的分组是什么,
/*分组的标签*/ public static final String GROUP_FAVORITE="favorite"; public static final String GROUP_COLD_WEAPON="cold_weapon"; public static final String GROUP_MODERN_WEAPON="modern_weapon"; public static final String GROUP_MISC="misc"; public static final String GROUP_PERSON="person"; public static final String GROUP_EQUIPMENT="equipment";
根据你项目的需求,来决定你自己的组的类别。请注意的favorite这组。这个组是其他组选了过后才放进这个组的,也就是说里面的item的group属性并不是GROUP_FAVORITE。实际上分组标签的另一个作用是用于获取分组数据的key。比如说,你要向服务器请求favorite这组数据,那么传GROUP_FAVORITE给服务器就可以得到数据了。
然后再看看列表条目。标记页列表的条目是一个复合条目,每个条目包含一个组的标题和其包含的所有子元素,那么,我们就可以设计为:
private String mGroup; private String mGroupTitle; private List<MenuItem> mMenuItemList;
mGroup就是上面的Group的标签,mGroupTitle是改标签所对应的要显示的标题(当然这个字段不是必须,你也可以在外部根据取到的标签进行判断),mMenuItemList就是其所包含的子列表数据了。
3.页面结构设计
首先主页的结构很简单,就只需要一个grid类型的recyclerView就行了。关键是编辑页。首先,编辑页从整体上应该分为两个部分。首部和列表部分。所以设计模式可以考虑在正常的list上面添加一个header。又或者传入两种的itemViewType,分别进行渲染视图。两种方案都可行。只是第一种的话,原生recyclerView是没有header,footer说法的,需要自己封装。在这里,我选择第一种方法,自行封装header。
4.数据持久化
1)服务端数据结构可以参考我的样式(json格式):在这里为了演示,我将json原始数据放到asset目录下,用于模拟从服务器获取的数据。
2)本地持久化
最正规的做法是采用数据库方式。例如GreenDao或者Realm都是很牛掰的数据库框架。这里,由于菜单数据相对较少,我就直接以sharedPreference来进行数据存储读取。另外,考虑到json对象与实体对象的相互转换,我这里推荐大家使用阿里巴巴的fastjson,来进行数据的序列化与反序列化操作,转换效率杠杠滴。
下面的就是我写的一个数据操作类。
package csii.cjs.demo.com.superboy; import android.content.Context; import android.content.SharedPreferences; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.util.ArrayList; import java.util.List; import csii.cjs.demo.com.superboy.base.ContextUtil; import csii.cjs.demo.com.superboy.entity.MenuItem; import csii.cjs.demo.com.superboy.tools.IOKit; /** * 描述:菜单数据控制助手,模拟本地数据库 * <p> * 作者:cjs * 创建时间:2017年11月03日 15:21 * 邮箱:chenjunsen@outlook.com * * @version 1.0 */ public class MenuHelper { /*分组的标签*/ public static final String GROUP_FAVORITE="favorite"; public static final String GROUP_COLD_WEAPON="cold_weapon"; public static final String GROUP_MODERN_WEAPON="modern_weapon"; public static final String GROUP_MISC="misc"; public static final String GROUP_PERSON="person"; public static final String GROUP_EQUIPMENT="equipment"; private int itemCounter=0;//用于统计共有多少个子item,依次给每个item设置独立的id /*分组数据的缓存列表,初始化分组的时候用*/ private List<MenuItem> favoriteList; private List<MenuItem> coldList; private List<MenuItem> modernList; private List<MenuItem> miscList; private List<MenuItem> eqtList; private List<MenuItem> personList; /** * 解析原始数据,用于模拟从服务器上获取到的JSON报文 */ private void parseJSONData(){ String jsonStr= IOKit.getStringFromAssets(ContextUtil.getContext(),"dummy.json");//获取到assets目录下的报文 JSONObject dataJson= JSON.parseObject(jsonStr);//将报文string转换为JSON favoriteList=parseJSONList(dataJson,GROUP_FAVORITE); coldList=parseJSONList(dataJson,GROUP_COLD_WEAPON); modernList=parseJSONList(dataJson,GROUP_MODERN_WEAPON); miscList=parseJSONList(dataJson,GROUP_MISC); eqtList=parseJSONList(dataJson,GROUP_EQUIPMENT); personList=parseJSONList(dataJson,GROUP_PERSON); savePreferFavoriteList(favoriteList); savePreferColdWeaponList(coldList); savePreferEqtList(eqtList); savePreferMiscList(miscList); savePreferModernWeaponList(modernList); savePreferPersonList(personList); } private List<MenuItem> parseJSONList(JSONObject dataJSON,String group){ List<MenuItem> list=new ArrayList<>(); JSONArray array=dataJSON.getJSONArray(group); int size=array.size(); for(int i=0;i<size;i++,itemCounter++){ JSONObject object=array.getJSONObject(i); //之所以没有在array层就进行JSON到java对象的转换,是为了进入内部遍历,产生id,并将id赋值给menuItem MenuItem item=JSON.toJavaObject(object,MenuItem.class); item.setItemId(itemCounter); list.add(item); } return list; } /** * 初始化数据 */ public static void init(){ MenuHelper helper=new MenuHelper(); helper.parseJSONData(); setInit(true); } /** * 用于保存本地数据的文件名字 */ private static final String PREFERENCE_MENU_DATA_NAME="menu_data"; /** * 是否已经进行过初始化的字段名 */ private static final String PREFERENCE_HAS_EVER_INIT="has_ever_init"; /** * 获取本地数据的文件 * @return */ public static SharedPreferences getMenuDataConfig(){ return ContextUtil.getContext().getSharedPreferences(PREFERENCE_MENU_DATA_NAME, Context.MODE_PRIVATE); } /** * 清空本地数据文件里面的内容 */ public static void clearMenuDataConfig(){ getMenuDataConfig().edit().clear().commit(); } public static boolean hasEverInit(){ return getMenuDataConfig().getBoolean(PREFERENCE_HAS_EVER_INIT,false); } public static void setInit(boolean isInit){ getMenuDataConfig().edit().putBoolean(PREFERENCE_HAS_EVER_INIT,isInit); } /*----------------------------原始方法-----------------------------------*/ /** * 将List转换为JsonString保存进SharedPreference * @param group * @param list */ private static void savePreferMenuListData(String group,List<MenuItem> list){ SharedPreferences.Editor editor=getMenuDataConfig().edit(); editor.putString(group,JSON.toJSONString(list)); editor.commit(); } /** * 从SharedPreference里面取出JsonString,再转换为List * @param group * @return */ private static List<MenuItem> getPreferMenuListData(String group){ String jsonStr=getMenuDataConfig().getString(group,""); JSONArray array=JSONArray.parseArray(jsonStr); return array.toJavaList(MenuItem.class); } /** * 从本地数据缓存列表里面删除一个item * @param group * @param item */ public static void deleteItem(String group,MenuItem item){ List<MenuItem> list=getPreferMenuListData(group); for(MenuItem i:list){ if(i.getItemId()==item.getItemId()){ list.remove(i); break; } } savePreferMenuListData(group,list); } /** * 从本地数据元素里面添加一个item * @param group * @param item */ public static void addItem(String group,MenuItem item){ List<MenuItem> list=getPreferMenuListData(group); if(!contains(list,item)){ list.add(item); savePreferMenuListData(group,list); } } private static boolean contains(List<MenuItem> list,MenuItem item){ if(list!=null && list.size()>0){ for(MenuItem i:list){ if(i.getItemId()==item.getItemId()){ return true; } } } return false; } /*----------------------------原始方法-----------------------------------*/ /*----------------------------衍生方法-----------------------------------*/ public static void savePreferFavoriteList(List<MenuItem> list){ savePreferMenuListData(GROUP_FAVORITE,list); } public static void savePreferColdWeaponList(List<MenuItem> list){ savePreferMenuListData(GROUP_COLD_WEAPON,list); } public static void savePreferModernWeaponList(List<MenuItem> list){ savePreferMenuListData(GROUP_MODERN_WEAPON,list); } public static void savePreferMiscList(List<MenuItem> list){ savePreferMenuListData(GROUP_MISC,list); } public static void savePreferEqtList(List<MenuItem> list){ savePreferMenuListData(GROUP_EQUIPMENT,list); } public static void savePreferPersonList(List<MenuItem> list){ savePreferMenuListData(GROUP_PERSON,list); } public static List<MenuItem> getPreferFavoriteList(){ return getPreferMenuListData(GROUP_FAVORITE); } public static List<MenuItem> getPreferColdWeaponList(){ return getPreferMenuListData(GROUP_COLD_WEAPON); } public static List<MenuItem> getPreferModernWeaponList(){ return getPreferMenuListData(GROUP_MODERN_WEAPON); } public static List<MenuItem> getPreferMiscList(){ return getPreferMenuListData(GROUP_MISC); } public static List<MenuItem> getPreferEquipmentList(){ return getPreferMenuListData(GROUP_EQUIPMENT); } public static List<MenuItem> getPreferPersonList(){ return getPreferMenuListData(GROUP_PERSON); } public static void addPreferFavoriteItem(MenuItem item){ addItem(GROUP_FAVORITE,item); } public static void addPreferColdItem(MenuItem item){ addItem(GROUP_COLD_WEAPON,item); } public static void addPreferEqtItem(MenuItem item){ addItem(GROUP_EQUIPMENT,item); } public static void addPreferModernItem(MenuItem item){ addItem(GROUP_MODERN_WEAPON,item); } public static void addPreferMiscItem(MenuItem item){ addItem(GROUP_MISC,item); } public static void addPreferPersonItem(MenuItem item){ addItem(GROUP_PERSON,item); } public static void deletePreferFavoriteItem(MenuItem item){ deleteItem(GROUP_FAVORITE,item); } public static void deletePreferColdItem(MenuItem item){ deleteItem(GROUP_COLD_WEAPON,item); } public static void deletePreferModernItem(MenuItem item){ deleteItem(GROUP_MODERN_WEAPON,item); } public static void deletePreferMiscItem(MenuItem item){ deleteItem(GROUP_MISC,item); } public static void deletePreferEqtItem(MenuItem item){ deleteItem(GROUP_EQUIPMENT,item); } public static void deletePreferPersonItem(MenuItem item){ deleteItem(GROUP_PERSON,item); } /*----------------------------衍生方法-----------------------------------*/ }
5.源码相关说明
其中recyclerview包是我自己对AdvancedRecyclerView进行的二次封装。封装后,实现起来更简单。tools包是个工具包,里面包含了一些比较有用的方法,这些大家都可以直接应用到自己的项目里面。然后就是adapter包,这里面是对recyclerview包里面的具体实现。另外MenuHelper的初始化操作,在MyApplication里面。
Warining:
关于AdvancedRecyclerView,目前不支持安卓26的编译,请用25+。还有,如果你采用UniversalImageLoader加载图片出现闪烁的问题,请移步这篇文章:http://blog.csdn.net/cjs1534717040/article/details/78285741
最后放上源码地址(需要10个积分,如果你有请支持一下,如果没有,请@我chenjunsen@outlook.com或者qq1534717040)
http://download.csdn.net/download/cjs1534717040/10106300
相关文章推荐
- [置顶] Android LRecyclerView实现Item侧滑菜单、长按拖拽Item、滑动删除Item等功能
- Android View进阶之RecyclerView 实现滑动删除和拖拽功能
- Android开发——使用高级的RecyclerView实现侧滑菜单删除功能(SwipeRecyclerView)
- RecyclerView进阶:使用ItemTouchHelper实现拖拽和侧滑删除
- Android LRecyclerView实现Item侧滑菜单、长按拖拽Item、滑动删除Item等功能
- RecyclerView进阶:使用ItemTouchHelper实现拖拽和侧滑删除效果
- iOS之UI--使用SWRevealViewController实现侧边菜单功能详解实例
- 菜单管理功能实现-treegrid行编辑以及拖拽效果
- 使用ItemTouchHelper类轻松实现RecyclerView的拖拽和侧滑
- 最强RecyclerView,Item侧滑菜单,长按拖拽Item,滑动删除Item。可以和任何下拉刷新框架结合使用
- Android——RecyclerView——使用ItemDragHelper来实现酷炫拖拽效果
- Recycleview实现复杂页面 三种以上布局 瀑布流 多布局 scrollview嵌套recyclerView 显示不全 滑动冲突 之进阶终极篇
- Android 仿联系人菜单,带字母索引,顶部挤压动画,recyclerview实现联系人页面
- 使用SWRevealViewController实现侧边菜单功能详解
- 使用少量代码实现自己的RecyclerView侧滑菜单
- 使用ItemTouchHelper轻松实现RecyclerView拖拽排序和滑动删除
- Android一步一步带你实现RecyclerView的拖拽和侧滑删除功能
- RecyclerView之使用ItemTouchHelper和ItemTouchHelper.Callback实现条目拖拽排序
- iOS之UI--使用SWRevealViewController实现侧边菜单功能详解实例
- 使用ItemTouchHelper轻松实现RecyclerView拖拽排序和滑动删除