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