Three pane layout

Implement basic behavior of the new layout.  Supports collapsing/expanding
message list on portrait.

TODO Collapsing the middle pane should cancel the selection mode on
     message list
TODO Implement animation
TODO On STATE_PORTRAIT_MIDDLE_EXPANDED state, right pane should be pushed out,
     rather than squished.

Change-Id: I0306516845de3a1f05a102864c3dc4aba809a49a
This commit is contained in:
Makoto Onuki 2010-09-23 17:10:46 -07:00
parent cd2e98d000
commit 8818a658a1
5 changed files with 390 additions and 36 deletions

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- ThreePaneLayout is based on LinearLayout with the orientation always horizontal -->
<!-- for portrait -->
<com.android.email.activity.ThreePaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:splitMotionEvents="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/left_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<FrameLayout
android:id="@+id/middle_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="2"
/>
<!-- STOPSHIP Don't use hardcoded color -->
<View
android:id="@+id/collapser"
android:layout_width="30dip"
android:layout_height="match_parent"
android:layout_weight="0"
android:background="#ff0"
/>
<FrameLayout
android:id="@+id/right_pane_with_fog"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="2"
>
<FrameLayout
android:id="@+id/right_pane"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<View
android:id="@+id/fogged_glass"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#c0ffffff"
/>
</FrameLayout>
</com.android.email.activity.ThreePaneLayout>

View File

@ -14,29 +14,13 @@
limitations under the License.
-->
<LinearLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:splitMotionEvents="true"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/left_pane"
android:layout_width="300dip"
android:layout_height="match_parent"
android:layout_weight="0"
android:layout_height="match_parent"
>
<include
android:id="@+id/three_pane"
layout="@layout/three_pane"
/>
<FrameLayout
android:id="@+id/middle_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<FrameLayout
android:id="@+id/right_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="2"
/>
</LinearLayout>
</FrameLayout>

45
res/layout/three_pane.xml Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- ThreePaneLayout is based on LinearLayout with the orientation always horizontal -->
<!-- for landscape -->
<com.android.email.activity.ThreePaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:splitMotionEvents="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/left_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
/>
<FrameLayout
android:id="@+id/middle_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="3"
/>
<FrameLayout
android:id="@+id/right_pane"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="6"
/>
</com.android.email.activity.ThreePaneLayout>

View File

@ -66,9 +66,7 @@ class MessageListXLFragmentManager {
/** Current message id. (-1 = not selected) */
private long mMessageId = -1;
private View mLeftPane;
private View mMiddlePane;
private View mRightPane;
private ThreePaneLayout mThreePane;
private MailboxListFragment mMailboxListFragment;
private MessageListFragment mMessageListFragment;
@ -123,9 +121,7 @@ class MessageListXLFragmentManager {
* the constructor.)
*/
public void onActivityViewReady() {
mLeftPane = mTargetActivity.findViewById(R.id.left_pane);
mMiddlePane = mTargetActivity.findViewById(R.id.middle_pane);
mRightPane = mTargetActivity.findViewById(R.id.right_pane);
mThreePane = (ThreePaneLayout) mTargetActivity.findViewById(R.id.three_pane);
}
/** Set callback for fragment. */
@ -312,7 +308,7 @@ class MessageListXLFragmentManager {
// We can put it directly in the layout file, but then it'll have slightly different
// lifecycle as the other fragments. Let's create it here this way for now.
MailboxListFragment f = new MailboxListFragment();
ft.replace(R.id.left_pane, f);
ft.replace(mThreePane.getLeftPaneId(), f);
}
if (mMessageListFragment != null) {
ft.remove(mMessageListFragment);
@ -400,7 +396,7 @@ class MessageListXLFragmentManager {
f.doAutoRefresh();
}
FragmentTransaction ft = mFragmentManager.openTransaction()
.replace(R.id.middle_pane, f);
.replace(mThreePane.getMiddlePaneId(), f);
if (mMessageViewFragment != null) {
// Message view will disappear.
ft.remove(mMessageViewFragment);
@ -467,7 +463,7 @@ class MessageListXLFragmentManager {
// We don't use the built-in back mechanism.
// See MessageListXL.onBackPressed().
mFragmentManager.openTransaction().replace(R.id.right_pane, f)
mFragmentManager.openTransaction().replace(mThreePane.getRightPaneId(), f)
// .addToBackStack(null)
.commit();
} else {
@ -490,14 +486,12 @@ class MessageListXLFragmentManager {
}
private void hideMessageBoxList() {
mLeftPane.setVisibility(View.GONE);
mRightPane.setVisibility(View.VISIBLE);
mThreePane.showRightPane(true);
}
private void hideMessageView() {
mMessageId = -1;
mRightPane.setVisibility(View.GONE);
mLeftPane.setVisibility(View.VISIBLE);
mThreePane.showRightPane(false);
if (mMessageViewFragment != null) {
mFragmentManager.openTransaction().remove(mMessageViewFragment).commit();
mMessageViewFragment = null;

View File

@ -0,0 +1,265 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.R;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
// TODO Collapsing the middle pane should cancel the selection mode on message list
// TODO Implement animation
// TODO On STATE_PORTRAIT_MIDDLE_EXPANDED state, right pane should be pushed out, rather than
// squished.
/**
* The "three pane" layout used on tablet.
*
* It'll encapsulate the behavioral differences between portrait mode and landscape mode.
*/
public class ThreePaneLayout extends LinearLayout implements View.OnClickListener {
/** Uninitialized state -- {@link #changePaneState} hasn't been called yet. */
private static final int STATE_LEFT_UNINITIALIZED = 0;
/** Mailbox list + message list */
private static final int STATE_LEFT_VISIBLE = 1;
/** Message view on portrait, + message list on landscape. */
private static final int STATE_RIGHT_VISIBLE = 2;
/** Portrait mode only: message view + expanded message list */
private static final int STATE_PORTRAIT_MIDDLE_EXPANDED = 3;
private int mPaneState = STATE_LEFT_UNINITIALIZED;
private View mLeftPane;
private View mMiddlePane;
private View mRightPane;
// Views used only on portrait
private View mCollapser;
private View mFoggedGlass;
private View mRightWithFog;
public ThreePaneLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
public ThreePaneLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public ThreePaneLayout(Context context) {
super(context);
initView();
}
/** Perform basic initialization */
private void initView() {
setOrientation(LinearLayout.HORIZONTAL); // Always horizontal
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
getViews();
if (!isLandscape()) {
mFoggedGlass.setOnClickListener(this);
mCollapser.setOnClickListener(this);
}
changePaneState(STATE_LEFT_VISIBLE, false);
}
/**
* Look for views and set to members. {@link #isLandscape} can be used after this method.
*/
private void getViews() {
mLeftPane = findViewById(R.id.left_pane);
mMiddlePane = findViewById(R.id.middle_pane);
mRightPane = findViewById(R.id.right_pane);
mCollapser = findViewById(R.id.collapser);
if (mCollapser != null) { // If it's there, it's portrait.
mFoggedGlass = findViewById(R.id.fogged_glass);
mRightWithFog = findViewById(R.id.right_pane_with_fog);
}
}
private boolean isLandscape() {
return mCollapser == null;
}
@Override
protected Parcelable onSaveInstanceState() {
SavedState ss = new SavedState(super.onSaveInstanceState());
ss.mPaneState = mPaneState;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
// Called after onFinishInflate()
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
changePaneState(ss.mPaneState, false);
}
/**
* Show/hide the right most pane. (i.e. message view)
*/
public void showRightPane(boolean show) {
changePaneState(show ? STATE_RIGHT_VISIBLE : STATE_LEFT_VISIBLE, true);
}
private void changePaneState(int newState, boolean animate) {
if (isLandscape() && (newState == STATE_PORTRAIT_MIDDLE_EXPANDED)) {
newState = STATE_RIGHT_VISIBLE;
}
if (newState == mPaneState) {
return;
}
mPaneState = newState;
switch (mPaneState) {
case STATE_LEFT_VISIBLE:
mLeftPane.setVisibility(View.VISIBLE);
if (isLandscape()) {
mMiddlePane.setVisibility(View.VISIBLE);
mRightPane.setVisibility(View.GONE);
} else { // Portrait
mMiddlePane.setVisibility(View.VISIBLE);
mCollapser.setVisibility(View.GONE);
mRightWithFog.setVisibility(View.GONE);
}
break;
case STATE_RIGHT_VISIBLE:
mLeftPane.setVisibility(View.GONE);
if (isLandscape()) {
mMiddlePane.setVisibility(View.VISIBLE);
mRightPane.setVisibility(View.VISIBLE);
} else { // Portrait
mMiddlePane.setVisibility(View.GONE);
mCollapser.setVisibility(View.VISIBLE);
mRightWithFog.setVisibility(View.VISIBLE);
mRightPane.setVisibility(View.VISIBLE);
mFoggedGlass.setVisibility(View.GONE);
}
break;
case STATE_PORTRAIT_MIDDLE_EXPANDED:
mLeftPane.setVisibility(View.GONE);
mMiddlePane.setVisibility(View.VISIBLE);
mCollapser.setVisibility(View.VISIBLE);
mRightWithFog.setVisibility(View.VISIBLE);
mRightPane.setVisibility(View.VISIBLE);
mFoggedGlass.setVisibility(View.VISIBLE);
break;
}
}
/**
* @return The ID of the view for the left pane fragment. (i.e. mailbox list)
*/
public int getLeftPaneId() {
return R.id.left_pane;
}
/**
* @return The ID of the view for the middle pane fragment. (i.e. message list)
*/
public int getMiddlePaneId() {
return R.id.middle_pane;
}
/**
* @return The ID of the view for the right pane fragment. (i.e. message view)
*/
public int getRightPaneId() {
return R.id.right_pane;
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.collapser:
if (isLandscape()) {
return; // Shouldn't happen
}
changePaneState((mPaneState == STATE_RIGHT_VISIBLE)
? STATE_PORTRAIT_MIDDLE_EXPANDED
: STATE_RIGHT_VISIBLE, true);
break;
case R.id.fogged_glass:
if (isLandscape()) {
return; // Shouldn't happen
}
changePaneState(STATE_RIGHT_VISIBLE, true);
break;
}
}
private static class SavedState extends BaseSavedState {
int mPaneState;
/**
* Constructor called from {@link ThreePaneLayout#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
mPaneState = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeLong(mPaneState);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}