MultiAdapterSpinner.java revision f20088d987deb48f185f5725426db0a0c4c26918
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 com.android.mailcommon.MergedAdapter.ListSpinnerAdapter; 22import com.android.mailcommon.MergedAdapter.LocalAdapterPosition; 23 24import android.content.Context; 25import android.util.AttributeSet; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.AdapterView; 29import android.widget.FrameLayout; 30import android.widget.ListPopupWindow; 31import android.widget.ListView; 32 33 34/** 35 * <p>A spinner-like widget that combines data and views from multiple adapters (via MergedAdapter) 36 * and forwards certain events to those adapters. This widget also supports clickable but 37 * unselectable dropdown items, useful when displaying extra items that should not affect spinner 38 * selection state.</p> 39 * 40 * <p>The framework's Spinner widget can't be extended for this task because it uses a private list 41 * adapter (which prevents setting multiple item types) and hides access to its popup, which is 42 * useful for clients to know about (like when it's been opened).</p> 43 * 44 * <p>Clients must provide a set of adapters which the widget will use to load dropdown views, 45 * receive callbacks, and load the selected item's view.</p> 46 * 47 * <p>Apps incorporating this widget must declare a custom attribute: "dropDownWidth" under the 48 * "MultiAdapterSpinner" name as a "reference" format (see Gmail's attrs.xml file for an 49 * example). This attribute controls the width of the dropdown, similar to the attribute in the 50 * framework's Spinner widget.</p> 51 * 52 */ 53public class MultiAdapterSpinner extends FrameLayout 54 implements AdapterView.OnItemClickListener, View.OnClickListener { 55 56 protected MergedSpinnerAdapter mAdapter; 57 protected ListPopupWindow mPopup; 58 59 private int mSelectedPosition = -1; 60 61 /** 62 * A basic adapter with some callbacks added so clients can be involved in spinner behavior. 63 */ 64 public interface FancySpinnerAdapter extends ListSpinnerAdapter { 65 /** 66 * Whether or not an item at position should become the new selected spinner item and change 67 * the spinner item view. 68 */ 69 boolean canSelect(int position); 70 /** 71 * Handle a click on an enabled item. 72 */ 73 void onClick(int position); 74 /** 75 * Fired when the popup window is about to be displayed. 76 */ 77 void onShowPopup(); 78 } 79 80 private class MergedSpinnerAdapter extends MergedAdapter<FancySpinnerAdapter> { 81 /** 82 * ListPopupWindow uses getView() but spinners return dropdown views in getDropDownView(). 83 */ 84 @Override 85 public View getView(int position, View convertView, ViewGroup parent) { 86 return super.getDropDownView(position, convertView, parent); 87 } 88 } 89 90 public MultiAdapterSpinner(Context context) { 91 this(context, null); 92 } 93 94 public MultiAdapterSpinner(Context context, AttributeSet attrs) { 95 super(context, attrs); 96 97 mAdapter = new MergedSpinnerAdapter(); 98 mPopup = new ListPopupWindow(context, attrs); 99 mPopup.setAnchorView(this); 100 mPopup.setOnItemClickListener(this); 101 mPopup.setModal(true); 102 mPopup.setAdapter(mAdapter); 103 } 104 105 public void setAdapters(FancySpinnerAdapter... adapters) { 106 mAdapter.setAdapters(adapters); 107 } 108 109 public void setSelectedItem(final FancySpinnerAdapter adapter, final int position) { 110 int globalPosition = 0; 111 boolean found = false; 112 113 for (int i = 0, count = mAdapter.getSubAdapterCount(); i < count; i++) { 114 ListSpinnerAdapter a = mAdapter.getSubAdapter(i); 115 if (a == adapter) { 116 globalPosition += position; 117 found = true; 118 break; 119 } 120 globalPosition += a.getCount(); 121 } 122 if (found) { 123 if (adapter.canSelect(position)) { 124 removeAllViews(); 125 View itemView = adapter.getView(position, null, this); 126 itemView.setClickable(true); 127 itemView.setOnClickListener(this); 128 addView(itemView); 129 130 if (position < adapter.getCount()) { 131 mSelectedPosition = globalPosition; 132 } 133 } 134 } 135 } 136 137 @Override 138 public void onClick(View v) { 139 if (!mPopup.isShowing()) { 140 141 for (int i = 0, count = mAdapter.getSubAdapterCount(); i < count; i++) { 142 mAdapter.getSubAdapter(i).onShowPopup(); 143 } 144 145 mPopup.show(); 146 mPopup.getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); 147 mPopup.setSelection(mSelectedPosition); 148 } 149 } 150 151 @Override 152 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 153 154 if (position != mSelectedPosition) { 155 final LocalAdapterPosition<FancySpinnerAdapter> result = 156 mAdapter.getAdapterOffsetForItem(position); 157 158 if (result.mAdapter.canSelect(result.mLocalPosition)) { 159 mSelectedPosition = position; 160 } else { 161 mPopup.clearListSelection(); 162 } 163 164 post(new Runnable() { 165 @Override 166 public void run() { 167 result.mAdapter.onClick(result.mLocalPosition); 168 } 169 }); 170 } 171 172 mPopup.dismiss(); 173 } 174 175} 176