ThreePaneLayout.java revision 4ca7580e5d0311b12e2e062056d675c7bc45b68b
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity;
18
19import com.android.email.R;
20
21import android.content.Context;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.util.AttributeSet;
25import android.view.View;
26import android.widget.LinearLayout;
27
28// TODO Collapsing the middle pane should cancel the selection mode on message list
29// TODO Implement animation
30// TODO On STATE_PORTRAIT_MIDDLE_EXPANDED state, right pane should be pushed out, rather than
31// squished.
32// TODO Test SavedState too.
33
34/**
35 * The "three pane" layout used on tablet.
36 *
37 * It'll encapsulate the behavioral differences between portrait mode and landscape mode.
38 */
39public class ThreePaneLayout extends LinearLayout implements View.OnClickListener {
40
41    /** Uninitialized state -- {@link #changePaneState} hasn't been called yet. */
42    private static final int STATE_LEFT_UNINITIALIZED = 0;
43
44    /** Mailbox list + message list */
45    private static final int STATE_LEFT_VISIBLE = 1;
46
47    /** Message view on portrait, + message list on landscape. */
48    private static final int STATE_RIGHT_VISIBLE = 2;
49
50    /** Portrait mode only: message view + expanded message list */
51    private static final int STATE_PORTRAIT_MIDDLE_EXPANDED = 3;
52
53    // Flags for getVisiblePanes()
54    public static final int PANE_LEFT = 1 << 2;
55    public static final int PANE_MIDDLE = 1 << 1;
56    public static final int PANE_RIGHT = 1 << 0;
57
58    private int mPaneState = STATE_LEFT_UNINITIALIZED;
59
60    private View mLeftPane;
61    private View mMiddlePane;
62    private View mRightPane;
63
64    // Views used only on portrait
65    private View mFoggedGlass;
66    private View mRightWithFog;
67
68    private Callback mCallback = EmptyCallback.INSTANCE;
69
70    public interface Callback {
71        /** Called when {@link ThreePaneLayout#getVisiblePanes()} has changed. */
72        public void onVisiblePanesChanged(int previousVisiblePanes);
73    }
74
75    private static final class EmptyCallback implements Callback {
76        public static final Callback INSTANCE = new EmptyCallback();
77
78        @Override public void onVisiblePanesChanged(int previousVisiblePanes) {}
79    }
80
81    public ThreePaneLayout(Context context, AttributeSet attrs, int defStyle) {
82        super(context, attrs, defStyle);
83        initView();
84    }
85
86    public ThreePaneLayout(Context context, AttributeSet attrs) {
87        super(context, attrs);
88        initView();
89    }
90
91    public ThreePaneLayout(Context context) {
92        super(context);
93        initView();
94    }
95
96    /** Perform basic initialization */
97    private void initView() {
98        setOrientation(LinearLayout.HORIZONTAL); // Always horizontal
99    }
100
101    @Override
102    protected void onFinishInflate() {
103        super.onFinishInflate();
104
105        getViews();
106
107        if (!isLandscape()) {
108            mFoggedGlass.setOnClickListener(this);
109        }
110
111        changePaneState(STATE_LEFT_VISIBLE, false);
112    }
113
114    public void setCallback(Callback callback) {
115        mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
116    }
117
118    /**
119     * Look for views and set to members.  {@link #isLandscape} can be used after this method.
120     */
121    private void getViews() {
122        mLeftPane = findViewById(R.id.left_pane);
123        mMiddlePane = findViewById(R.id.middle_pane);
124        mRightPane = findViewById(R.id.right_pane);
125
126        mFoggedGlass = findViewById(R.id.fogged_glass);
127        if (mFoggedGlass != null) { // If it's there, it's portrait.
128            mRightWithFog = findViewById(R.id.right_pane_with_fog);
129        }
130    }
131
132    private boolean isLandscape() {
133        return mFoggedGlass == null;
134    }
135
136    @Override
137    protected Parcelable onSaveInstanceState() {
138        SavedState ss = new SavedState(super.onSaveInstanceState());
139        ss.mPaneState = mPaneState;
140        return ss;
141    }
142
143    @Override
144    protected void onRestoreInstanceState(Parcelable state) {
145        // Called after onFinishInflate()
146        SavedState ss = (SavedState) state;
147        super.onRestoreInstanceState(ss.getSuperState());
148        changePaneState(ss.mPaneState, false);
149    }
150
151    /**
152     * @return bit flags for visible panes.  Combination of {@link #PANE_LEFT}, {@link #PANE_MIDDLE}
153     * and {@link #PANE_RIGHT},
154     */
155    public int getVisiblePanes() {
156        int ret = 0;
157        if (mLeftPane.getVisibility() == View.VISIBLE) ret |= PANE_LEFT;
158        if (mMiddlePane.getVisibility() == View.VISIBLE) ret |= PANE_MIDDLE;
159        if (mRightPane.getVisibility() == View.VISIBLE) ret |= PANE_RIGHT;
160        return ret;
161    }
162
163    public boolean onBackPressed() {
164        if (isLandscape()) {
165            switch (mPaneState) {
166            case STATE_RIGHT_VISIBLE:
167                changePaneState(STATE_LEFT_VISIBLE, true); // Close the right pane
168                return true;
169            }
170        } else {
171            switch (mPaneState) {
172                case STATE_RIGHT_VISIBLE:
173                    changePaneState(STATE_PORTRAIT_MIDDLE_EXPANDED, true);
174                    return true;
175                case STATE_PORTRAIT_MIDDLE_EXPANDED:
176                    changePaneState(STATE_LEFT_VISIBLE, true);
177                    return true;
178                }
179        }
180        return false;
181    }
182
183    /**
184     * Show the left most pane.  (i.e. mailbox list)
185     */
186    public void showLeftPane() {
187        changePaneState(STATE_LEFT_VISIBLE, true);
188    }
189
190    /**
191     * Show the right most pane.  (i.e. message view)
192     */
193    public void showRightPane() {
194        changePaneState(STATE_RIGHT_VISIBLE, true);
195    }
196
197    private void changePaneState(int newState, boolean animate) {
198        if (isLandscape() && (newState == STATE_PORTRAIT_MIDDLE_EXPANDED)) {
199            newState = STATE_RIGHT_VISIBLE;
200        }
201        if (newState == mPaneState) {
202            return;
203        }
204        final int previousVisiblePanes = getVisiblePanes();
205        mPaneState = newState;
206        switch (mPaneState) {
207            case STATE_LEFT_VISIBLE:
208                mLeftPane.setVisibility(View.VISIBLE);
209
210                if (isLandscape()) {
211                    mMiddlePane.setVisibility(View.VISIBLE);
212                    mRightPane.setVisibility(View.GONE);
213                } else { // Portrait
214                    mMiddlePane.setVisibility(View.VISIBLE);
215
216                    mRightWithFog.setVisibility(View.GONE);
217                }
218                break;
219            case STATE_RIGHT_VISIBLE:
220                mLeftPane.setVisibility(View.GONE);
221
222                if (isLandscape()) {
223                    mMiddlePane.setVisibility(View.VISIBLE);
224                    mRightPane.setVisibility(View.VISIBLE);
225                } else { // Portrait
226                    mMiddlePane.setVisibility(View.GONE);
227
228                    mRightWithFog.setVisibility(View.VISIBLE);
229                    mRightPane.setVisibility(View.VISIBLE);
230                    mFoggedGlass.setVisibility(View.GONE);
231                }
232                break;
233            case STATE_PORTRAIT_MIDDLE_EXPANDED:
234                mLeftPane.setVisibility(View.GONE);
235
236                mMiddlePane.setVisibility(View.VISIBLE);
237
238                mRightWithFog.setVisibility(View.VISIBLE);
239                mRightPane.setVisibility(View.VISIBLE);
240                mFoggedGlass.setVisibility(View.VISIBLE);
241                break;
242        }
243        mCallback.onVisiblePanesChanged(previousVisiblePanes);
244    }
245
246    /**
247     * @return The ID of the view for the left pane fragment.  (i.e. mailbox list)
248     */
249    public int getLeftPaneId() {
250        return R.id.left_pane;
251    }
252
253    /**
254     * @return The ID of the view for the middle pane fragment.  (i.e. message list)
255     */
256    public int getMiddlePaneId() {
257        return R.id.middle_pane;
258    }
259
260    /**
261     * @return The ID of the view for the right pane fragment.  (i.e. message view)
262     */
263    public int getRightPaneId() {
264        return R.id.right_pane;
265    }
266
267    @Override
268    public void onClick(View v) {
269        switch (v.getId()) {
270            case R.id.fogged_glass:
271                if (isLandscape()) {
272                    return; // Shouldn't happen
273                }
274                changePaneState(STATE_RIGHT_VISIBLE, true);
275                break;
276        }
277    }
278
279    private static class SavedState extends BaseSavedState {
280        int mPaneState;
281
282        /**
283         * Constructor called from {@link ThreePaneLayout#onSaveInstanceState()}
284         */
285        SavedState(Parcelable superState) {
286            super(superState);
287        }
288
289        /**
290         * Constructor called from {@link #CREATOR}
291         */
292        private SavedState(Parcel in) {
293            super(in);
294            mPaneState = in.readInt();
295        }
296
297        @Override
298        public void writeToParcel(Parcel out, int flags) {
299            super.writeToParcel(out, flags);
300            out.writeInt(mPaneState);
301        }
302
303        public static final Parcelable.Creator<SavedState> CREATOR
304                = new Parcelable.Creator<SavedState>() {
305            public SavedState createFromParcel(Parcel in) {
306                return new SavedState(in);
307            }
308
309            public SavedState[] newArray(int size) {
310                return new SavedState[size];
311            }
312        };
313    }
314}
315