/* * * * Copyright 2015. Appsi Mobile * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.mobeta.android.dslv; import android.content.Context; import android.database.Cursor; import android.support.v4.widget.CursorAdapter; import android.util.SparseIntArray; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; /** * A subclass of {@link android.widget.CursorAdapter} that provides * reordering of the elements in the Cursor based on completed * drag-sort operations. The reordering is a simple mapping of * list positions into Cursor positions (the Cursor is unchanged). * To persist changes made by drag-sorts, one can retrieve the * mapping with the {@link #getCursorPositions()} method, which * returns the reordered list of Cursor positions. * <p/> * An instance of this class is passed * to {@link com.mobeta.android.dslv.DragSortListView#setAdapter(ListAdapter)} and, since * this class implements the {@link com.mobeta.android.dslv.DragSortListView.DragSortListener} * interface, it is automatically set as the DragSortListener for * the DragSortListView instance. */ public abstract class DragSortCursorAdapter extends CursorAdapter implements DragSortListView.DragSortListener { public static final int REMOVED = -1; /** * Key is ListView position, value is Cursor position */ private SparseIntArray mListMapping = new SparseIntArray(); private ArrayList<Integer> mRemovedCursorPositions = new ArrayList<Integer>(); public DragSortCursorAdapter(Context context, Cursor c) { super(context, c); } public DragSortCursorAdapter(Context context, Cursor c, boolean autoRequery) { super(context, c, autoRequery); } public DragSortCursorAdapter(Context context, Cursor c, int flags) { super(context, c, flags); } /** * Swaps Cursor and clears list-Cursor mapping. * * @see android.widget.CursorAdapter#swapCursor(android.database.Cursor) */ @Override public Cursor swapCursor(Cursor newCursor) { Cursor old = super.swapCursor(newCursor); resetMappings(); return old; } /** * Changes Cursor and clears list-Cursor mapping. * * @see android.widget.CursorAdapter#changeCursor(android.database.Cursor) */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); resetMappings(); } /** * Resets list-cursor mapping. */ public void reset() { resetMappings(); notifyDataSetChanged(); } private void resetMappings() { mListMapping.clear(); mRemovedCursorPositions.clear(); } @Override public Object getItem(int position) { return super.getItem(mListMapping.get(position, position)); } @Override public long getItemId(int position) { return super.getItemId(mListMapping.get(position, position)); } @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { return super.getDropDownView(mListMapping.get(position, position), convertView, parent); } @Override public View getView(int position, View convertView, ViewGroup parent) { return super.getView(mListMapping.get(position, position), convertView, parent); } /** * On drop, this updates the mapping between Cursor positions * and ListView positions. The Cursor is unchanged. Retrieve * the current mapping with {@link getCursorPositions()}. * * @see com.mobeta.android.dslv.DragSortListView.DropListener#drop(int, int) */ @Override public void drop(int from, int to) { if (from != to) { int cursorFrom = mListMapping.get(from, from); if (from > to) { for (int i = from; i > to; --i) { mListMapping.put(i, mListMapping.get(i - 1, i - 1)); } } else { for (int i = from; i < to; ++i) { mListMapping.put(i, mListMapping.get(i + 1, i + 1)); } } mListMapping.put(to, cursorFrom); cleanMapping(); notifyDataSetChanged(); } } /** * On remove, this updates the mapping between Cursor positions * and ListView positions. The Cursor is unchanged. Retrieve * the current mapping with {@link getCursorPositions()}. * * @see com.mobeta.android.dslv.DragSortListView.RemoveListener#remove(int) */ @Override public void remove(int which) { int cursorPos = mListMapping.get(which, which); if (!mRemovedCursorPositions.contains(cursorPos)) { mRemovedCursorPositions.add(cursorPos); } int newCount = getCount(); for (int i = which; i < newCount; ++i) { mListMapping.put(i, mListMapping.get(i + 1, i + 1)); } mListMapping.delete(newCount); cleanMapping(); notifyDataSetChanged(); } /** * Does nothing. Just completes DragSortListener interface. */ @Override public void drag(int from, int to) { // do nothing } /** * Remove unnecessary mappings from sparse array. */ private void cleanMapping() { ArrayList<Integer> toRemove = null; int size = mListMapping.size(); for (int i = 0; i < size; ++i) { if (mListMapping.keyAt(i) == mListMapping.valueAt(i)) { if (toRemove == null) { toRemove = new ArrayList<>(); } toRemove.add(mListMapping.keyAt(i)); } } if (toRemove != null) { size = toRemove.size(); for (int i = 0; i < size; ++i) { mListMapping.delete(toRemove.get(i)); } } } @Override public int getCount() { return super.getCount() - mRemovedCursorPositions.size(); } /** * Get the Cursor position mapped to by the provided list position * (given all previously handled drag-sort * operations). * * @param position List position * * @return The mapped-to Cursor position */ public int getCursorPosition(int position) { return mListMapping.get(position, position); } /** * Get the current order of Cursor positions presented by the * list. */ public ArrayList<Integer> getCursorPositions() { ArrayList<Integer> result = new ArrayList<Integer>(); for (int i = 0; i < getCount(); ++i) { result.add(mListMapping.get(i, i)); } return result; } /** * Get the list position mapped to by the provided Cursor position. * If the provided Cursor position has been removed by a drag-sort, * this returns {@link #REMOVED}. * * @param cursorPosition A Cursor position * * @return The mapped-to list position or REMOVED */ public int getListPosition(int cursorPosition) { if (mRemovedCursorPositions.contains(cursorPosition)) { return REMOVED; } int index = mListMapping.indexOfValue(cursorPosition); if (index < 0) { return cursorPosition; } else { return mListMapping.keyAt(index); } } }