一、概述
列表自定义排序,在QQ音乐、网易云音乐等APP中是一个很常见的功能,用户收藏歌曲和歌单里的歌曲都可以通过拖动的实现自定义排序。
刚接到这个需求,要实现这个功能的时候,惯性思维会觉得不难,不就加个排序字段吗,有多难。但是仔细考虑之后,其实当中会引发许多问题。
二、功能问题点
1、排序值设计以及排序更改后如何变化
假设我们在列表的数据中增加一个position字段,以添加顺序1、2、3、4、5往下排,那么我想把第4位数据移动至第1位,就需要把第4位的排序值4改为1,把1、2、3位的排序值分别改为2、3、4。如此,才能满足需求。
上述方式中,每移动一个排序,就需要对许多数据进行修改,对数据库的修改太频繁了。如果数据量不大的情况下,可以考虑这种方式,数据量一大,很明显会有性能问题。
2、数据分页查询问题
拖动排序,一般都是在一个单独的页面进行排序,此页面包含了排序列表的全部数据。以用户收藏的歌曲举例,如果用户收藏2000条数据,难道我们要一次性全部查询出来?这明显不合理。
这样做,不仅会对服务器造成压力,对用户网络流量及手机性能的消耗也是巨大的。
三、解决方案
针对上述问题,在查询了许多资料及结合我们项目的特点和需求之后,笔者整理出一套比较适合于我们的方式。具体如下。
1、使用中值排序法
中值排序法,顾名思义,便是采用取中值的方式。
首先让两个相邻的数据之间的排序值,有一定的间隔,默认是2^16 = 65536
,如:65536 * 1 = 65536
、65536 * 2 = 131072
、65536 * 3 = 196608
。
然后移动排序时,比如要将第3个数据一直1和2的中间,就取1和2的中值,计算公式如下。
中值计算公式
// 移动到两个元素中间(倒序排序时)
double newPosition = next.position + (prev.position - next.position) / 2;
// 移动到第一个(倒序排序时)
double newPosition = nowFirst.position + 65536
// 移动到最后一个(倒序排序时)
double newPosition = nowLast.position / 2
将得到的newPosition
赋值给第3个数据,便完成了此次排序移动。
整个过程中,我们只对第3个数据的position进行了修改,不需要频繁对列表中的其他不参与排序的数据进行修改。
但是这种做法,在用户多次移动之后,难免会出现小数的情况,对于这种情况,我们需要对position字段进行重排操作。
即一旦判断newPosition出现小数,则依据现在的position排序结果,对position的字段进行重新赋值(赋初始值),此操作,可以另外创建一个线程来执行。以免影响用户排序接口的请求速度。
此处的计算应该在后端进行,避免前端恶意制造小数点,造成每次请求都进行重排。
2、数据分页查询
数据分页的话,我们可以先将数据正常分页查询出来,假设数据有5页,用户已经浏览到3页,此时用户如果点击排序,再去将剩余页的数据全部查询出来。
在数据查询期间,可以给用户一个提示,提示用户数据正在加载,然后数据全部请求完成之后,将数据带入排序页面即可。
四、前后端交互流程
前端在用户进行完排序之后,将用户更改的排序值的相对位置信息传到后端即可,由后端计算出新的position位置。
举个例子,假设有数据如下。
排序示例
// 初始化数据(数据库数据)
let list = [
{
id: 1004,
position: 262144
},
{
id: 1003,
position: 196608
},
{
id: 1002,
position: 131072
},
{
id: 1001,
position: 65536
}
]
// 此时想要将1001数据的位置移动到第 2 位,只要告诉后端相对位置即可。移动到第 2 位之后,1001的下一个是1003, 所以移动接口请求的参数如下:
let payload = {
target: 1001,
next: 1003
}
// 就是告诉后端要移动的目标(target)是1001,移动之后,1001的下一个(next)是1003
// 后端在接收到数据之后,根据前端传入的next查询当前next记录相邻的上一个,然后进行上述的公式计算即可算出新的位置信息。
// 排序后数据(数据库数据)
let list = [
{
id: 1004,
position: 262144
},
{
id: 1001,
// 196608 + (262144 - 196608) / 2 = 229376
position: 229376
},
{
id: 1003,
position: 196608
},
{
id: 1002,
position: 131072
}
]