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