大家平时见过这样的分页么?见过的话知道怎么写么?没见过的话有思路么?
Previous 1 .. 3 4 (5) 6 7 8 .. 10 Next
就本人的话,在用grails时见到了这个强大的分页功能,但是对于里面的代码和思路一知半解。
今天心血来潮想要重新理解下原理,所以花了点时间分析了下实现,最终在回家的地铁上基本明白了其中的逻辑,特此写文分享。
在展示最终的scala代码之前,先说下分页中的几个概念,同时为了方便,这里取用grails中的变量名:
currentStep:当前页
lastStep:最后页
(firstStep):第一页,隐式常量
beginStep: 这种分页的起始页,可以认为是..之后的数字
endStep:这种分页的结束页,可以认为是第二个..之前的数字
maxSteps:控制这种分页的beginStep到endStep之间的数量的参数
有了这些参数之后,先看简单的部分:
previous:currentStep > firstStep的话就显示
1: beiginStep > firstStep的话就显示
第一个..:beginStep > (firstStep + 1)的话就显示
第二个..:endStep < (lastStep - 1)的话就显示
lastStep:endStep < lastStep的话就显示
next:currentStep < lastStep的话就显示
其实有了这些,简单的
First Previous Next Last
就可以做了,不过grails的这个分页强大的地方在于maxSteps、beginStep和endStep。以下是beginStep和endStep的groovy代码,也是一直困扰我的地方。
int beginstep = currentstep - Math.round(maxsteps / 2) + (maxsteps % 2) int endstep = currentstep + Math.round(maxsteps / 2) - 1 if (beginstep < firststep) { beginstep = firststep endstep = maxsteps } if (endstep > laststep) { beginstep = laststep - maxsteps + 1 if (beginstep < firststep) { beginstep = firststep } endstep = laststep }
这里在做什么操作呢?经过一段时间思考,我认为这段是在剪裁beginStep..endStep的区间。剪裁的理由显而易见,当前页可能就是第一页或者最后一页。遇到这种情况,beginStep肯定不能从负数开始,endStep页不能超过最后一页。但是grails这段代码是怎么剪裁的呢?
|************| |************| |----------| => |----------| |************| |************| |**********| |----------| => |----------| => |----------|
首先是判断左边,做向右移动操作,其次是判断右边,做向左移动操作,同时判断是否超过左边界了,如果超过了剪裁。
你可能会问,为什么判断右边时剪裁了而判断左边时没剪?个人认为可能是因为“重复”而被去掉了,那么是否会出问题呢?看上图,需要剪裁的场景没出问题。
另外个人认为判断左边时剪裁可以做,多做没啥问题,因为之后直接跳过右边的判断了。还有需要注意的是判断左边时没办法判定那种左边在范围内,右边在范围外的情况,所以单独的右边界判断是必须的。
虽然这段逻辑运行起来没太大问题,但是有点难以理解。我在用scala模拟时用的是另一个方法:
private def trim(beginStep: Int, endStep: Int, lastSteps: Int): (Int, Int) = { if(beginStep >= FIRST_STEP) { if(endStep <= lastSteps) (beginStep, endStep) else (scala.math.max(FIRST_STEP, beginStep - endStep + lastSteps), lastSteps) } else { if(endStep > lastSteps) (FIRST_STEP, lastSteps) else (FIRST_STEP, scala.math.min(lastSteps, endStep + FIRST_STEP - beginStep)) } }
我把剪裁分为四种情况(左边和左边界判定有两种情况,右边和右边界判定有两种情况),剪裁结果如下
左边判定 | 右边判定 | 处理 |
---|---|---|
firstStep <= beginStep | endStep <= lastStep | 无剪裁 |
firstStep <= beginStep | endStep > lastStep | 向左边移动,存在剪裁可能 |
firstStep > beginStep | endStep > lastStep | 全剪裁 |
firstStep > beginStep | endStep <= lastStep | 向右边移动,存在剪裁可能 |
虽然比起前面代码可能要多一些,不过更直观。
最后是scala代码:
object Paginate { def main(args: Array[String]): Unit = { if(args.length < 3) println("usage <currentStep> <lastStep> <maxSteps>") else paginate(args(0).toInt, args(1).toInt, args(2).toInt) } private final val FIRST_STEP = 1 private def paginate(currentStep: Int, lastStep: Int, maxSteps: Int): Unit = { val beginStep = currentStep - maxSteps / 2 + 1 - (maxSteps % 2) val endStep = currentStep + maxSteps / 2 val (fromStep, toStep) = trim(beginStep, endStep, lastStep) if(currentStep > FIRST_STEP) print("Previous ") if(fromStep > FIRST_STEP) print("1 ") if(fromStep > (FIRST_STEP + 1)) print(".. ") (fromStep to toStep).foreach{step => if(step == currentStep) print("("+ step + ") ") else print(step + " ") } if(endStep < lastStep - 1) print(".. ") if(endStep < lastStep) print(lastStep + " ") if(currentStep < lastStep) print("Next") println } private def trim(beginStep: Int, endStep: Int, lastSteps: Int): (Int, Int) = { if(beginStep >= FIRST_STEP) { if(endStep <= lastSteps) (beginStep, endStep) else (scala.math.max(FIRST_STEP, beginStep - endStep + lastSteps), lastSteps) } else { if(endStep > lastSteps) (FIRST_STEP, lastSteps) else (FIRST_STEP, scala.math.min(lastSteps, endStep + FIRST_STEP - beginStep)) } } }