1/* 2 * Copyright (C) 2011 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 19package com.android.mailcommon; 20 21import android.database.DataSetObserver; 22import android.view.View; 23import android.view.ViewGroup; 24import android.widget.BaseAdapter; 25import android.widget.ListAdapter; 26import android.widget.SpinnerAdapter; 27 28import java.util.Arrays; 29import java.util.List; 30 31/** 32 * An adapter that combines items from multiple provided adapters into a single list. 33 * 34 * @param <T> the class of each constituent adapter 35 */ 36public class MergedAdapter<T extends MergedAdapter.ListSpinnerAdapter> extends BaseAdapter { 37 38 private List<T> mAdapters; 39 private final DataSetObserver mObserver; 40 41 /** 42 * A Mergeable adapter must implement both ListAdapter and SpinnerAdapter to be useful in lists 43 * and spinners. 44 */ 45 public interface ListSpinnerAdapter extends ListAdapter, SpinnerAdapter { 46 } 47 48 public static class LocalAdapterPosition<T extends ListSpinnerAdapter> { 49 public final T mAdapter; 50 public final int mLocalPosition; 51 52 public LocalAdapterPosition(T adapter, int offset) { 53 mAdapter = adapter; 54 mLocalPosition = offset; 55 } 56 } 57 58 public MergedAdapter() { 59 mObserver = new DataSetObserver() { 60 @Override 61 public void onChanged() { 62 notifyDataSetChanged(); 63 } 64 }; 65 } 66 67 public void setAdapters(T... adapters) { 68 if (mAdapters != null) { 69 for (T adapter : mAdapters) { 70 adapter.unregisterDataSetObserver(mObserver); 71 } 72 } 73 74 mAdapters = Arrays.asList(adapters); 75 76 for (T adapter : mAdapters) { 77 adapter.registerDataSetObserver(mObserver); 78 } 79 } 80 81 public int getSubAdapterCount() { 82 return mAdapters.size(); 83 } 84 85 public T getSubAdapter(int index) { 86 return mAdapters.get(index); 87 } 88 89 @Override 90 public int getCount() { 91 int count = 0; 92 for (T adapter : mAdapters) { 93 count += adapter.getCount(); 94 } 95 return count; 96 // TODO: cache counts until next onChanged 97 } 98 99 /** 100 * For a given merged position, find the corresponding Adapter and local position within that 101 * Adapter by iterating through Adapters and summing their counts until the merged position is 102 * found. 103 * 104 * @param position a merged (global) position 105 * @return the matching Adapter and local position, or null if not found 106 */ 107 public LocalAdapterPosition<T> getAdapterOffsetForItem(final int position) { 108 final int adapterCount = mAdapters.size(); 109 int i = 0; 110 int count = 0; 111 112 while (i < adapterCount) { 113 T a = mAdapters.get(i); 114 int newCount = count + a.getCount(); 115 if (position < newCount) { 116 return new LocalAdapterPosition<T>(a, position - count); 117 } 118 count = newCount; 119 i++; 120 } 121 return null; 122 } 123 124 @Override 125 public Object getItem(int position) { 126 LocalAdapterPosition<T> result = getAdapterOffsetForItem(position); 127 if (result == null) { 128 return null; 129 } 130 return result.mAdapter.getItem(result.mLocalPosition); 131 } 132 133 @Override 134 public long getItemId(int position) { 135 return position; 136 } 137 138 @Override 139 public int getViewTypeCount() { 140 int count = 0; 141 for (T adapter : mAdapters) { 142 count += adapter.getViewTypeCount(); 143 } 144 return count; 145 } 146 147 @Override 148 public int getItemViewType(int position) { 149 LocalAdapterPosition<T> result = getAdapterOffsetForItem(position); 150 int otherViewTypeCount = 0; 151 for (T adapter : mAdapters) { 152 if (adapter == result.mAdapter) { 153 break; 154 } 155 otherViewTypeCount += adapter.getViewTypeCount(); 156 } 157 int type = result.mAdapter.getItemViewType(result.mLocalPosition); 158 // Headers (negative types) are in a separate global namespace and their values should not 159 // be affected by preceding adapter view types. 160 if (type >= 0) { 161 type += otherViewTypeCount; 162 } 163 return type; 164 } 165 166 @Override 167 public View getView(int position, View convertView, ViewGroup parent) { 168 LocalAdapterPosition<T> result = getAdapterOffsetForItem(position); 169 return result.mAdapter.getView(result.mLocalPosition, convertView, parent); 170 } 171 172 @Override 173 public View getDropDownView(int position, View convertView, ViewGroup parent) { 174 LocalAdapterPosition<T> result = getAdapterOffsetForItem(position); 175 return result.mAdapter.getDropDownView(result.mLocalPosition, convertView, parent); 176 } 177 178 @Override 179 public boolean areAllItemsEnabled() { 180 boolean enabled = true; 181 for (T adapter : mAdapters) { 182 enabled &= adapter.areAllItemsEnabled(); 183 } 184 return enabled; 185 } 186 187 @Override 188 public boolean isEnabled(int position) { 189 LocalAdapterPosition<T> result = getAdapterOffsetForItem(position); 190 return result.mAdapter.isEnabled(result.mLocalPosition); 191 } 192 193}