PreferenceGroupAdapter.java revision 8a86d734e53dcbafd86c5cc28679323798ecd8a5
1/* 2 * Copyright (C) 2007 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.preference; 18 19import java.util.ArrayList; 20import java.util.Collections; 21import java.util.List; 22 23import android.graphics.drawable.Drawable; 24import android.os.Handler; 25import android.preference.Preference.OnPreferenceChangeInternalListener; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.Adapter; 29import android.widget.BaseAdapter; 30import android.widget.ListView; 31 32/** 33 * An adapter that returns the {@link Preference} contained in this group. 34 * In most cases, this adapter should be the base class for any custom 35 * adapters from {@link Preference#getAdapter()}. 36 * <p> 37 * This adapter obeys the 38 * {@link Preference}'s adapter rule (the 39 * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of 40 * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an 41 * adapter via {@link Preference#getAdapter()}). 42 * <p> 43 * This adapter also propagates data change/invalidated notifications upward. 44 * <p> 45 * This adapter does not include this {@link PreferenceGroup} in the returned 46 * adapter, use {@link PreferenceCategoryAdapter} instead. 47 * 48 * @see PreferenceCategoryAdapter 49 * 50 * @hide 51 */ 52public class PreferenceGroupAdapter extends BaseAdapter 53 implements OnPreferenceChangeInternalListener { 54 55 private static final String TAG = "PreferenceGroupAdapter"; 56 57 /** 58 * The group that we are providing data from. 59 */ 60 private PreferenceGroup mPreferenceGroup; 61 62 /** 63 * Maps a position into this adapter -> {@link Preference}. These 64 * {@link Preference}s don't have to be direct children of this 65 * {@link PreferenceGroup}, they can be grand children or younger) 66 */ 67 private List<Preference> mPreferenceList; 68 69 /** 70 * List of unique Preference and its subclasses' names. This is used to find 71 * out how many types of views this adapter can return. Once the count is 72 * returned, this cannot be modified (since the ListView only checks the 73 * count once--when the adapter is being set). We will not recycle views for 74 * Preference subclasses seen after the count has been returned. 75 */ 76 private ArrayList<PreferenceLayout> mPreferenceLayouts; 77 78 private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout(); 79 80 /** 81 * Blocks the mPreferenceClassNames from being changed anymore. 82 */ 83 private boolean mHasReturnedViewTypeCount = false; 84 85 private volatile boolean mIsSyncing = false; 86 87 private Handler mHandler = new Handler(); 88 89 private Runnable mSyncRunnable = new Runnable() { 90 public void run() { 91 syncMyPreferences(); 92 } 93 }; 94 95 private int mHighlightedPosition = -1; 96 private Drawable mHighlightedDrawable; 97 98 private static class PreferenceLayout implements Comparable<PreferenceLayout> { 99 private int resId; 100 private int widgetResId; 101 private String name; 102 103 public int compareTo(PreferenceLayout other) { 104 int compareNames = name.compareTo(other.name); 105 if (compareNames == 0) { 106 if (resId == other.resId) { 107 if (widgetResId == other.widgetResId) { 108 return 0; 109 } else { 110 return widgetResId - other.widgetResId; 111 } 112 } else { 113 return resId - other.resId; 114 } 115 } else { 116 return compareNames; 117 } 118 } 119 } 120 121 public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) { 122 mPreferenceGroup = preferenceGroup; 123 // If this group gets or loses any children, let us know 124 mPreferenceGroup.setOnPreferenceChangeInternalListener(this); 125 126 mPreferenceList = new ArrayList<Preference>(); 127 mPreferenceLayouts = new ArrayList<PreferenceLayout>(); 128 129 syncMyPreferences(); 130 } 131 132 private void syncMyPreferences() { 133 synchronized(this) { 134 if (mIsSyncing) { 135 return; 136 } 137 138 mIsSyncing = true; 139 } 140 141 List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size()); 142 flattenPreferenceGroup(newPreferenceList, mPreferenceGroup); 143 mPreferenceList = newPreferenceList; 144 145 notifyDataSetChanged(); 146 147 synchronized(this) { 148 mIsSyncing = false; 149 notifyAll(); 150 } 151 } 152 153 private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) { 154 // TODO: shouldn't always? 155 group.sortPreferences(); 156 157 final int groupSize = group.getPreferenceCount(); 158 for (int i = 0; i < groupSize; i++) { 159 final Preference preference = group.getPreference(i); 160 161 preferences.add(preference); 162 163 if (!mHasReturnedViewTypeCount && preference.canRecycleLayout()) { 164 addPreferenceClassName(preference); 165 } 166 167 if (preference instanceof PreferenceGroup) { 168 final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference; 169 if (preferenceAsGroup.isOnSameScreenAsChildren()) { 170 flattenPreferenceGroup(preferences, preferenceAsGroup); 171 } 172 } 173 174 preference.setOnPreferenceChangeInternalListener(this); 175 } 176 } 177 178 /** 179 * Creates a string that includes the preference name, layout id and widget layout id. 180 * If a particular preference type uses 2 different resources, they will be treated as 181 * different view types. 182 */ 183 private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) { 184 PreferenceLayout pl = in != null? in : new PreferenceLayout(); 185 pl.name = preference.getClass().getName(); 186 pl.resId = preference.getLayoutResource(); 187 pl.widgetResId = preference.getWidgetLayoutResource(); 188 return pl; 189 } 190 191 private void addPreferenceClassName(Preference preference) { 192 final PreferenceLayout pl = createPreferenceLayout(preference, null); 193 int insertPos = Collections.binarySearch(mPreferenceLayouts, pl); 194 195 // Only insert if it doesn't exist (when it is negative). 196 if (insertPos < 0) { 197 // Convert to insert index 198 insertPos = insertPos * -1 - 1; 199 mPreferenceLayouts.add(insertPos, pl); 200 } 201 } 202 203 public int getCount() { 204 return mPreferenceList.size(); 205 } 206 207 public Preference getItem(int position) { 208 if (position < 0 || position >= getCount()) return null; 209 return mPreferenceList.get(position); 210 } 211 212 public long getItemId(int position) { 213 if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID; 214 return this.getItem(position).getId(); 215 } 216 217 /** 218 * @hide 219 */ 220 public void setHighlighted(int position) { 221 mHighlightedPosition = position; 222 } 223 224 /** 225 * @hide 226 */ 227 public void setHighlightedDrawable(Drawable drawable) { 228 mHighlightedDrawable = drawable; 229 } 230 231 public View getView(int position, View convertView, ViewGroup parent) { 232 final Preference preference = this.getItem(position); 233 // Build a PreferenceLayout to compare with known ones that are cacheable. 234 mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); 235 236 // If it's not one of the cached ones, set the convertView to null so that 237 // the layout gets re-created by the Preference. 238 if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) { 239 convertView = null; 240 } 241 View result = preference.getView(convertView, parent); 242 if (position == mHighlightedPosition && mHighlightedDrawable != null) { 243 result.setBackgroundDrawable(mHighlightedDrawable); 244 } 245 result.setTag(preference.getKey()); 246 return result; 247 } 248 249 @Override 250 public boolean isEnabled(int position) { 251 if (position < 0 || position >= getCount()) return true; 252 return this.getItem(position).isSelectable(); 253 } 254 255 @Override 256 public boolean areAllItemsEnabled() { 257 // There should always be a preference group, and these groups are always 258 // disabled 259 return false; 260 } 261 262 public void onPreferenceChange(Preference preference) { 263 notifyDataSetChanged(); 264 } 265 266 public void onPreferenceHierarchyChange(Preference preference) { 267 mHandler.removeCallbacks(mSyncRunnable); 268 mHandler.post(mSyncRunnable); 269 } 270 271 @Override 272 public boolean hasStableIds() { 273 return true; 274 } 275 276 @Override 277 public int getItemViewType(int position) { 278 if (!mHasReturnedViewTypeCount) { 279 mHasReturnedViewTypeCount = true; 280 } 281 282 final Preference preference = this.getItem(position); 283 if (!preference.canRecycleLayout()) { 284 return IGNORE_ITEM_VIEW_TYPE; 285 } 286 287 mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); 288 289 int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout); 290 if (viewType < 0) { 291 // This is a class that was seen after we returned the count, so 292 // don't recycle it. 293 return IGNORE_ITEM_VIEW_TYPE; 294 } else { 295 return viewType; 296 } 297 } 298 299 @Override 300 public int getViewTypeCount() { 301 if (!mHasReturnedViewTypeCount) { 302 mHasReturnedViewTypeCount = true; 303 } 304 305 return Math.max(1, mPreferenceLayouts.size()); 306 } 307 308} 309