1 /*** 2 * Ambient - A music player for the Android platform 3 Copyright (C) 2007 Martin Vysny 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 package sk.baka.ambient.views.gesturelist; 19 20 import java.util.HashMap; 21 import java.util.IdentityHashMap; 22 import java.util.Iterator; 23 import java.util.Map; 24 import java.util.Map.Entry; 25 26 import android.content.Context; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.AbsListView; 31 import android.widget.ArrayAdapter; 32 import android.widget.BaseAdapter; 33 34 /*** 35 * An adapter backed by a list. Unlike {@link ArrayAdapter} this adapter 36 * supports the list redraw operation - you don't need to set new adapter 37 * instance to the ListView when the items are changed. 38 * 39 * @author Martin Vysny 40 */ 41 public final class MutableListAdapter extends BaseAdapter { 42 43 /*** 44 * The inflater used to inflate new views. 45 */ 46 protected final LayoutInflater inflater; 47 48 /*** 49 * Owning list view. 50 */ 51 private final GesturesListView owner; 52 53 private final ModelHolder model; 54 55 /*** 56 * Creates new adapter. 57 * 58 * @param owner 59 * owning list view 60 * @param model 61 * the model 62 */ 63 public MutableListAdapter(final GesturesListView owner, 64 final ModelHolder model) { 65 super(); 66 this.owner = owner; 67 this.model = model; 68 this.inflater = (LayoutInflater) owner.getContext().getSystemService( 69 Context.LAYOUT_INFLATER_SERVICE); 70 } 71 72 /*** 73 * Height of zoomed views, in pixels. 74 */ 75 public static final int ZOOMED_HEIGHT = 48; 76 77 public final View getView(int arg0, View arg1, ViewGroup arg2) { 78 final View view = arg1 != null ? arg1 : inflater.inflate( 79 owner.itemLayoutId, owner, false); 80 view.setLayoutParams(new AbsListView.LayoutParams( 81 ViewGroup.LayoutParams.FILL_PARENT, zoom ? ZOOMED_HEIGHT 82 : ViewGroup.LayoutParams.WRAP_CONTENT)); 83 registerView(view, arg0); 84 update(view, arg0); 85 return view; 86 } 87 88 public int getCount() { 89 final int result = model.getModel().size(); 90 return isEOPShown ? result + 1 : result; 91 } 92 93 private boolean isEOPShown = false; 94 95 /*** 96 * <p> 97 * Checks if the EndOfPlaylist special item should be shown in the playlist. 98 * The special item is shown only when the list view: 99 * </p> 100 * <ul> 101 * <li>is modifiable</li> 102 * <li>has focus (this property however does not alter the return value of 103 * the function, i.e. the function can return <code>true</code> even if the 104 * list view does not have focus)</li> 105 * <li>is not in the {@link View#isInTouchMode() touch mode}</li> 106 * <li>the clipboard is not empty</li> 107 * </ul> 108 * 109 * @return <code>true</code> if the item should be shown, <code>false</code> 110 * otherwise. 111 */ 112 private boolean isEOPProposed() { 113 if (owner.listener == null) { 114 return false; 115 } 116 if (owner.listener.isReadOnly()) { 117 return false; 118 } 119 if (owner.isInTouchMode()) { 120 return false; 121 } 122 if (TrackListClipboardObject.isEmpty(owner.getClipboard())) { 123 return false; 124 } 125 return true; 126 } 127 128 /*** 129 * Checks if given item is the EOP item. 130 * 131 * @param position 132 * the position 133 * @return <code>true</code> if it is EOP, <code>false</code> otherwise. 134 */ 135 public boolean isEOP(final int position) { 136 if (!isEOPShown) { 137 return false; 138 } 139 return position == model.getModel().size(); 140 } 141 142 /*** 143 * Marks the EOP item in the model. 144 */ 145 public final static Object EOP_MODEL_MARKER = new Object(); 146 147 public Object getItem(int position) { 148 if (isEOP(position)) { 149 return EOP_MODEL_MARKER; 150 } 151 return model.getModel().get(position); 152 } 153 154 public long getItemId(int position) { 155 return position; 156 } 157 158 /*** 159 * The EOP state should be modified. Update if needed. 160 */ 161 public void eopModified() { 162 setEOP(isEOPProposed()); 163 } 164 165 /*** 166 * The EOP state should be modified. Update if needed. 167 * 168 * @param visible 169 * if <code>true</code> then show EOP, if <code>false</code> then 170 * hide it. 171 */ 172 public void setEOP(final boolean visible) { 173 if (visible != isEOPShown) { 174 notifyModified(visible); 175 } 176 } 177 178 /*** 179 * Redraws items and reflects changes made to the 180 * {@link ModelHolder#getModel()} list. 181 */ 182 public void notifyModified() { 183 notifyModified(null); 184 } 185 186 private void notifyModified(final Boolean eopProposal) { 187 isEOPShown = eopProposal != null ? eopProposal : isEOPProposed(); 188 final boolean modelSizeChanged = modelSize != getCount(); 189 if (modelSizeChanged) { 190 notifyDataSetChanged(); 191 } else { 192 update(); 193 } 194 modelSize = getCount(); 195 } 196 197 private int modelSize = 0; 198 199 /*** 200 * Invokes 201 * {@link IGestureListViewListener#update(GesturesListView, View, int, Object)} 202 * on all visible items. 203 */ 204 private void update() { 205 final int modelSize = getCount(); 206 for (final Iterator<Entry<Integer, View>> i = visibleIndices.entrySet() 207 .iterator(); i.hasNext();) { 208 final Entry<Integer, View> entry = i.next(); 209 final Integer index = entry.getKey(); 210 if (index >= modelSize) { 211 visibleViews.remove(entry.getValue()); 212 i.remove(); 213 } else { 214 update(entry.getValue(), index); 215 } 216 } 217 } 218 219 private void update(final View view, final int index) { 220 owner.listener.update(owner, view, index, getItem(index)); 221 } 222 223 private final IdentityHashMap<View, Integer> visibleViews = new IdentityHashMap<View, Integer>(); 224 225 private final Map<Integer, View> visibleIndices = new HashMap<Integer, View>(); 226 227 private void registerView(final View view, final Integer index) { 228 final Integer oldIndex = visibleViews.get(view); 229 if (oldIndex != null) { 230 visibleIndices.remove(oldIndex); 231 } 232 final View oldView = visibleIndices.get(index); 233 if (oldView != null) { 234 visibleViews.remove(oldView); 235 } 236 visibleViews.put(view, index); 237 visibleIndices.put(index, view); 238 } 239 240 private boolean zoom = false; 241 242 /*** 243 * Zooms or un-zooms the items. 244 * 245 * @param zoom 246 * <code>true</code> if zoom the view. 247 */ 248 public void zoom(boolean zoom) { 249 this.zoom = zoom; 250 final int minHeight = zoom ? ZOOMED_HEIGHT 251 : ViewGroup.LayoutParams.WRAP_CONTENT; 252 for (final View view : visibleViews.keySet()) { 253 view.setLayoutParams(new AbsListView.LayoutParams( 254 ViewGroup.LayoutParams.FILL_PARENT, minHeight)); 255 } 256 for (final View view : visibleIndices.values()) { 257 view.setLayoutParams(new AbsListView.LayoutParams( 258 ViewGroup.LayoutParams.FILL_PARENT, minHeight)); 259 } 260 } 261 }