1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.widget; 18 19import java.util.List; 20 21import android.support.v7.widget.AdapterHelper.UpdateOp; 22import static android.support.v7.widget.AdapterHelper.UpdateOp.ADD; 23import static android.support.v7.widget.AdapterHelper.UpdateOp.MOVE; 24import static android.support.v7.widget.AdapterHelper.UpdateOp.REMOVE; 25import static android.support.v7.widget.AdapterHelper.UpdateOp.UPDATE; 26 27class OpReorderer { 28 29 final Callback mCallback; 30 31 public OpReorderer(Callback callback) { 32 mCallback = callback; 33 } 34 35 void reorderOps(List<UpdateOp> ops) { 36 // since move operations breaks continuity, their effects on ADD/RM are hard to handle. 37 // we push them to the end of the list so that they can be handled easily. 38 int badMove; 39 while ((badMove = getLastMoveOutOfOrder(ops)) != -1) { 40 swapMoveOp(ops, badMove, badMove + 1); 41 } 42 } 43 44 private void swapMoveOp(List<UpdateOp> list, int badMove, int next) { 45 final UpdateOp moveOp = list.get(badMove); 46 final UpdateOp nextOp = list.get(next); 47 switch (nextOp.cmd) { 48 case REMOVE: 49 swapMoveRemove(list, badMove, moveOp, next, nextOp); 50 break; 51 case ADD: 52 swapMoveAdd(list, badMove, moveOp, next, nextOp); 53 break; 54 case UPDATE: 55 swapMoveUpdate(list, badMove, moveOp, next, nextOp); 56 break; 57 } 58 } 59 60 void swapMoveRemove(List<UpdateOp> list, int movePos, UpdateOp moveOp, 61 int removePos, UpdateOp removeOp) { 62 UpdateOp extraRm = null; 63 // check if move is nulled out by remove 64 boolean revertedMove = false; 65 final boolean moveIsBackwards; 66 67 if (moveOp.positionStart < moveOp.itemCount) { 68 moveIsBackwards = false; 69 if (removeOp.positionStart == moveOp.positionStart 70 && removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) { 71 revertedMove = true; 72 } 73 } else { 74 moveIsBackwards = true; 75 if (removeOp.positionStart == moveOp.itemCount + 1 && 76 removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) { 77 revertedMove = true; 78 } 79 } 80 81 // going in reverse, first revert the effect of add 82 if (moveOp.itemCount < removeOp.positionStart) { 83 removeOp.positionStart--; 84 } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) { 85 // move is removed. 86 removeOp.itemCount --; 87 moveOp.cmd = REMOVE; 88 moveOp.itemCount = 1; 89 if (removeOp.itemCount == 0) { 90 list.remove(removePos); 91 mCallback.recycleUpdateOp(removeOp); 92 } 93 // no need to swap, it is already a remove 94 return; 95 } 96 97 // now affect of add is consumed. now apply effect of first remove 98 if (moveOp.positionStart <= removeOp.positionStart) { 99 removeOp.positionStart++; 100 } else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) { 101 final int remaining = removeOp.positionStart + removeOp.itemCount 102 - moveOp.positionStart; 103 extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining); 104 removeOp.itemCount = moveOp.positionStart - removeOp.positionStart; 105 } 106 107 // if effects of move is reverted by remove, we are done. 108 if (revertedMove) { 109 list.set(movePos, removeOp); 110 list.remove(removePos); 111 mCallback.recycleUpdateOp(moveOp); 112 return; 113 } 114 115 // now find out the new locations for move actions 116 if (moveIsBackwards) { 117 if (extraRm != null) { 118 if (moveOp.positionStart > extraRm.positionStart) { 119 moveOp.positionStart -= extraRm.itemCount; 120 } 121 if (moveOp.itemCount > extraRm.positionStart) { 122 moveOp.itemCount -= extraRm.itemCount; 123 } 124 } 125 if (moveOp.positionStart > removeOp.positionStart) { 126 moveOp.positionStart -= removeOp.itemCount; 127 } 128 if (moveOp.itemCount > removeOp.positionStart) { 129 moveOp.itemCount -= removeOp.itemCount; 130 } 131 } else { 132 if (extraRm != null) { 133 if (moveOp.positionStart >= extraRm.positionStart) { 134 moveOp.positionStart -= extraRm.itemCount; 135 } 136 if (moveOp.itemCount >= extraRm.positionStart) { 137 moveOp.itemCount -= extraRm.itemCount; 138 } 139 } 140 if (moveOp.positionStart >= removeOp.positionStart) { 141 moveOp.positionStart -= removeOp.itemCount; 142 } 143 if (moveOp.itemCount >= removeOp.positionStart) { 144 moveOp.itemCount -= removeOp.itemCount; 145 } 146 } 147 148 list.set(movePos, removeOp); 149 if (moveOp.positionStart != moveOp.itemCount) { 150 list.set(removePos, moveOp); 151 } else { 152 list.remove(removePos); 153 } 154 if (extraRm != null) { 155 list.add(movePos, extraRm); 156 } 157 } 158 159 private void swapMoveAdd(List<UpdateOp> list, int move, UpdateOp moveOp, int add, 160 UpdateOp addOp) { 161 int offset = 0; 162 // going in reverse, first revert the effect of add 163 if (moveOp.itemCount < addOp.positionStart) { 164 offset--; 165 } 166 if (moveOp.positionStart < addOp.positionStart) { 167 offset++; 168 } 169 if (addOp.positionStart <= moveOp.positionStart) { 170 moveOp.positionStart += addOp.itemCount; 171 } 172 if (addOp.positionStart <= moveOp.itemCount) { 173 moveOp.itemCount += addOp.itemCount; 174 } 175 addOp.positionStart += offset; 176 list.set(move, addOp); 177 list.set(add, moveOp); 178 } 179 180 void swapMoveUpdate(List<UpdateOp> list, int move, UpdateOp moveOp, int update, 181 UpdateOp updateOp) { 182 UpdateOp extraUp1 = null; 183 UpdateOp extraUp2 = null; 184 // going in reverse, first revert the effect of add 185 if (moveOp.itemCount < updateOp.positionStart) { 186 updateOp.positionStart--; 187 } else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) { 188 // moved item is updated. add an update for it 189 updateOp.itemCount--; 190 extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1); 191 } 192 // now affect of add is consumed. now apply effect of first remove 193 if (moveOp.positionStart <= updateOp.positionStart) { 194 updateOp.positionStart++; 195 } else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) { 196 final int remaining = updateOp.positionStart + updateOp.itemCount 197 - moveOp.positionStart; 198 extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining); 199 updateOp.itemCount -= remaining; 200 } 201 list.set(update, moveOp); 202 if (updateOp.itemCount > 0) { 203 list.set(move, updateOp); 204 } else { 205 list.remove(move); 206 mCallback.recycleUpdateOp(updateOp); 207 } 208 if (extraUp1 != null) { 209 list.add(move, extraUp1); 210 } 211 if (extraUp2 != null) { 212 list.add(move, extraUp2); 213 } 214 } 215 216 private int getLastMoveOutOfOrder(List<UpdateOp> list) { 217 boolean foundNonMove = false; 218 for (int i = list.size() - 1; i >= 0; i--) { 219 final UpdateOp op1 = list.get(i); 220 if (op1.cmd == MOVE) { 221 if (foundNonMove) { 222 return i; 223 } 224 } else { 225 foundNonMove = true; 226 } 227 } 228 return -1; 229 } 230 231 static interface Callback { 232 233 UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount); 234 235 void recycleUpdateOp(UpdateOp op); 236 } 237} 238