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