Home | History | Annotate | Download | only in mailcommon
      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