119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo/* 219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Copyright (C) 2011 Google Inc. 319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Licensed to The Android Open Source Project. 419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Licensed under the Apache License, Version 2.0 (the "License"); 619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * you may not use this file except in compliance with the License. 719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * You may obtain a copy of the License at 819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * http://www.apache.org/licenses/LICENSE-2.0 1019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 1119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Unless required by applicable law or agreed to in writing, software 1219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * distributed under the License is distributed on an "AS IS" BASIS, 1319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * See the License for the specific language governing permissions and 1519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * limitations under the License. 1619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 1719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 1819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 1919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalopackage com.android.mailcommon; 2019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 2119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport com.android.mailcommon.MergedAdapter.ListSpinnerAdapter; 2219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport com.android.mailcommon.MergedAdapter.LocalAdapterPosition; 2319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 2419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.content.Context; 25d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereiraimport android.graphics.Rect; 26d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereiraimport android.graphics.drawable.Drawable; 2719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.util.AttributeSet; 2819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.view.View; 2919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.view.ViewGroup; 3019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.widget.AdapterView; 3119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.widget.FrameLayout; 3219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.widget.ListPopupWindow; 3319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komaloimport android.widget.ListView; 3419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 3519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 3619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo/** 3719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * <p>A spinner-like widget that combines data and views from multiple adapters (via MergedAdapter) 3819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * and forwards certain events to those adapters. This widget also supports clickable but 3919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * unselectable dropdown items, useful when displaying extra items that should not affect spinner 4019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * selection state.</p> 4119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 4219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * <p>The framework's Spinner widget can't be extended for this task because it uses a private list 4319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * adapter (which prevents setting multiple item types) and hides access to its popup, which is 4419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * useful for clients to know about (like when it's been opened).</p> 4519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 4619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * <p>Clients must provide a set of adapters which the widget will use to load dropdown views, 4719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * receive callbacks, and load the selected item's view.</p> 4819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 4919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * <p>Apps incorporating this widget must declare a custom attribute: "dropDownWidth" under the 5019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * "MultiAdapterSpinner" name as a "reference" format (see Gmail's attrs.xml file for an 5119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * example). This attribute controls the width of the dropdown, similar to the attribute in the 5219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * framework's Spinner widget.</p> 5319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * 5419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 5519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalopublic class MultiAdapterSpinner extends FrameLayout 5619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo implements AdapterView.OnItemClickListener, View.OnClickListener { 5719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 582859eb0fec820a0677dce9a72f49601f10592a62Andy Huang protected MergedAdapter<FancySpinnerAdapter> mAdapter; 5919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo protected ListPopupWindow mPopup; 6019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 61f20088d987deb48f185f5725426db0a0c4c26918Andy Huang private int mSelectedPosition = -1; 62d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira private Rect mTempRect = new Rect(); 6319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 6419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo /** 6519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * A basic adapter with some callbacks added so clients can be involved in spinner behavior. 6619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 6719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public interface FancySpinnerAdapter extends ListSpinnerAdapter { 6819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo /** 6919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Whether or not an item at position should become the new selected spinner item and change 7019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * the spinner item view. 7119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 7219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo boolean canSelect(int position); 7319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo /** 7419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Handle a click on an enabled item. 7519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 7619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo void onClick(int position); 7719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo /** 7819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * Fired when the popup window is about to be displayed. 7919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 8019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo void onShowPopup(); 8119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 8219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 832859eb0fec820a0677dce9a72f49601f10592a62Andy Huang private static class MergedSpinnerAdapter extends MergedAdapter<FancySpinnerAdapter> { 8419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo /** 8519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo * ListPopupWindow uses getView() but spinners return dropdown views in getDropDownView(). 8619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo */ 8719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo @Override 8819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public View getView(int position, View convertView, ViewGroup parent) { 8919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo return super.getDropDownView(position, convertView, parent); 9019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 9119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 9219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 9319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public MultiAdapterSpinner(Context context) { 9419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo this(context, null); 9519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 9619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 9719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public MultiAdapterSpinner(Context context, AttributeSet attrs) { 9819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo super(context, attrs); 9919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 10019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mAdapter = new MergedSpinnerAdapter(); 10119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup = new ListPopupWindow(context, attrs); 10219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.setAnchorView(this); 10319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.setOnItemClickListener(this); 10419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.setModal(true); 10519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.setAdapter(mAdapter); 10619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 10719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 10819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public void setAdapters(FancySpinnerAdapter... adapters) { 10919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mAdapter.setAdapters(adapters); 11019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 11119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 11219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public void setSelectedItem(final FancySpinnerAdapter adapter, final int position) { 11319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo int globalPosition = 0; 11419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo boolean found = false; 11519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 11619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo for (int i = 0, count = mAdapter.getSubAdapterCount(); i < count; i++) { 11719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo ListSpinnerAdapter a = mAdapter.getSubAdapter(i); 11819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (a == adapter) { 11919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo globalPosition += position; 12019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo found = true; 12119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo break; 12219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 12319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo globalPosition += a.getCount(); 12419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 12519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (found) { 12619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (adapter.canSelect(position)) { 12719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo removeAllViews(); 12819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo View itemView = adapter.getView(position, null, this); 12919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo itemView.setClickable(true); 13019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo itemView.setOnClickListener(this); 13119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo addView(itemView); 13219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 13319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (position < adapter.getCount()) { 13419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mSelectedPosition = globalPosition; 13519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 13619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 13719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 13819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 13919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 14019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo @Override 14119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public void onClick(View v) { 14219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (!mPopup.isShowing()) { 14319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 14419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo for (int i = 0, count = mAdapter.getSubAdapterCount(); i < count; i++) { 14519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mAdapter.getSubAdapter(i).onShowPopup(); 14619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 14719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 148d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira final int spinnerPaddingLeft = getPaddingLeft(); 149d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira final Drawable background = mPopup.getBackground(); 150d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira int bgOffset = 0; 151d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira if (background != null) { 152d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira background.getPadding(mTempRect); 153d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira bgOffset = -mTempRect.left; 154d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira } 155d48018c1345a557221f2f2a36e8507ff891b8bf5Mindy Pereira mPopup.setHorizontalOffset(bgOffset + spinnerPaddingLeft); 15619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.show(); 15719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); 15819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.setSelection(mSelectedPosition); 15919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 16019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 16119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 16219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo @Override 16319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 16419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 16519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (position != mSelectedPosition) { 16619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo final LocalAdapterPosition<FancySpinnerAdapter> result = 16719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mAdapter.getAdapterOffsetForItem(position); 16819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 16919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo if (result.mAdapter.canSelect(result.mLocalPosition)) { 17019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mSelectedPosition = position; 17119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } else { 17219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.clearListSelection(); 17319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 17419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 17519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo post(new Runnable() { 17619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo @Override 17719acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo public void run() { 17819acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo result.mAdapter.onClick(result.mLocalPosition); 17919acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 18019acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo }); 18119acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 18219acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 18319acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo mPopup.dismiss(); 18419acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo } 18519acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo 18619acfbb21739e88784fae4844b5ea1e9a7f8974bBen Komalo} 187