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  	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 }