您的位置:首页 > 其它

RecyclerView进阶使用-实现仿支付宝菜单编辑页面拖拽功能

2017-11-06 18:24 701 查看
先上一张效果图



之前看见了支付宝的菜单编辑页面,有个类似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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐