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