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