View Javadoc

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  	private int getZoomedHeight() {
76  		return zoom == 0 ? ViewGroup.LayoutParams.WRAP_CONTENT : 16 + 16 * zoom;
77  	}
78  
79  	public final View getView(int arg0, View arg1, ViewGroup arg2) {
80  		final View view = arg1 != null ? arg1 : inflater.inflate(
81  				owner.itemLayoutId, owner, false);
82  		view.setLayoutParams(new AbsListView.LayoutParams(
83  				ViewGroup.LayoutParams.FILL_PARENT, getZoomedHeight()));
84  		registerView(view, arg0);
85  		update(view, arg0);
86  		return view;
87  	}
88  
89  	public int getCount() {
90  		final int result = model.getModel().size();
91  		return isEOPShown ? result + 1 : result;
92  	}
93  
94  	private boolean isEOPShown = false;
95  
96  	/***
97  	 * <p>
98  	 * Checks if the EndOfPlaylist special item should be shown in the playlist.
99  	 * The special item is shown only when the list view:
100 	 * </p>
101 	 * <ul>
102 	 * <li>is modifiable</li>
103 	 * <li>has focus (this property however does not alter the return value of
104 	 * the function, i.e. the function can return <code>true</code> even if the
105 	 * list view does not have focus)</li>
106 	 * <li>is not in the {@link View#isInTouchMode() touch mode}</li>
107 	 * <li>the clipboard is not empty</li>
108 	 * </ul>
109 	 * 
110 	 * @return <code>true</code> if the item should be shown, <code>false</code>
111 	 *         otherwise.
112 	 */
113 	private boolean isEOPProposed() {
114 		if (owner.listener == null) {
115 			return false;
116 		}
117 		if (owner.listener.isReadOnly()) {
118 			return false;
119 		}
120 		if (owner.isInTouchMode()) {
121 			return false;
122 		}
123 		if (TrackListClipboardObject.isEmpty(owner.getClipboard())) {
124 			return false;
125 		}
126 		return true;
127 	}
128 
129 	/***
130 	 * Checks if given item is the EOP item.
131 	 * 
132 	 * @param position
133 	 *            the position
134 	 * @return <code>true</code> if it is EOP, <code>false</code> otherwise.
135 	 */
136 	public boolean isEOP(final int position) {
137 		if (!isEOPShown) {
138 			return false;
139 		}
140 		return position == model.getModel().size();
141 	}
142 
143 	/***
144 	 * Marks the EOP item in the model.
145 	 */
146 	public final static Object EOP_MODEL_MARKER = new Object();
147 
148 	public Object getItem(int position) {
149 		if (isEOP(position)) {
150 			return EOP_MODEL_MARKER;
151 		}
152 		return model.getModel().get(position);
153 	}
154 
155 	public long getItemId(int position) {
156 		return position;
157 	}
158 
159 	/***
160 	 * The EOP state should be modified. Update if needed.
161 	 */
162 	public void eopModified() {
163 		setEOP(isEOPProposed());
164 	}
165 
166 	/***
167 	 * The EOP state should be modified. Update if needed.
168 	 * 
169 	 * @param visible
170 	 *            if <code>true</code> then show EOP, if <code>false</code> then
171 	 *            hide it.
172 	 */
173 	public void setEOP(final boolean visible) {
174 		if (visible != isEOPShown) {
175 			notifyModified(visible);
176 		}
177 	}
178 
179 	/***
180 	 * Redraws items and reflects changes made to the
181 	 * {@link ModelHolder#getModel()} list.
182 	 */
183 	public void notifyModified() {
184 		notifyModified(null);
185 	}
186 
187 	private void notifyModified(final Boolean eopProposal) {
188 		isEOPShown = eopProposal != null ? eopProposal : isEOPProposed();
189 		final boolean modelSizeChanged = modelSize != getCount();
190 		if (modelSizeChanged) {
191 			notifyDataSetChanged();
192 		} else {
193 			update();
194 		}
195 		modelSize = getCount();
196 	}
197 
198 	private int modelSize = 0;
199 
200 	/***
201 	 * Invokes
202 	 * {@link IGestureListViewListener#update(GesturesListView, View, int, Object)}
203 	 * on all visible items.
204 	 */
205 	private void update() {
206 		final int modelSize = getCount();
207 		for (final Iterator<Entry<Integer, View>> i = visibleIndices.entrySet()
208 				.iterator(); i.hasNext();) {
209 			final Entry<Integer, View> entry = i.next();
210 			final Integer index = entry.getKey();
211 			if (index >= modelSize) {
212 				visibleViews.remove(entry.getValue());
213 				i.remove();
214 			} else {
215 				update(entry.getValue(), index);
216 			}
217 		}
218 	}
219 
220 	private void update(final View view, final int index) {
221 		owner.listener.update(owner, view, index, getItem(index));
222 	}
223 
224 	private final IdentityHashMap<View, Integer> visibleViews = new IdentityHashMap<View, Integer>();
225 
226 	private final Map<Integer, View> visibleIndices = new HashMap<Integer, View>();
227 
228 	private void registerView(final View view, final Integer index) {
229 		final Integer oldIndex = visibleViews.get(view);
230 		if (oldIndex != null) {
231 			visibleIndices.remove(oldIndex);
232 		}
233 		final View oldView = visibleIndices.get(index);
234 		if (oldView != null) {
235 			visibleViews.remove(oldView);
236 		}
237 		visibleViews.put(view, index);
238 		visibleIndices.put(index, view);
239 	}
240 
241 	private int zoom = 0;
242 
243 	/***
244 	 * Zooms or un-zooms the items.
245 	 * 
246 	 * @param zoom
247 	 *            <code>true</code> if zoom the view.
248 	 */
249 	public void zoom(final int zoom) {
250 		if (this.zoom == zoom) {
251 			return;
252 		}
253 		this.zoom = zoom;
254 		final int minHeight = getZoomedHeight();
255 		for (final View view : visibleViews.keySet()) {
256 			view.setLayoutParams(new AbsListView.LayoutParams(
257 					ViewGroup.LayoutParams.FILL_PARENT, minHeight));
258 		}
259 		for (final View view : visibleIndices.values()) {
260 			view.setLayoutParams(new AbsListView.LayoutParams(
261 					ViewGroup.LayoutParams.FILL_PARENT, minHeight));
262 		}
263 	}
264 }