Android Open Source - ThreePaneLayout Three Pane Layout






From Project

Back to project page ThreePaneLayout.

License

The source code is released under:

This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a co...

If you think the Android project ThreePaneLayout listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.panels.controls;
/*from   ww  w . j ava2 s  . co  m*/
import java.util.ArrayList;
import java.util.List;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;

import com.panels.R;

/**
 * <p>Control to display up to three panels with a maximum of two simultaneously visible.</p>
 
 * @author Diego Palomar <dfpalomar@gmail.com>
 *
 */
public class ThreePaneLayout extends LinearLayout {
  
  /**
   * Time control takes for the state change animation
   */
  public static final int ANIMATION_DURATION = 300;
  
  private boolean isScrollingViews;
  
  /**
   * Possible control visibility states. The states are exclusive, ie, if the control is 
   * in state {@ link LEFT_AND_MIDDLE_VISIBLE} implies that the right pane is not visible.
   */
  public enum VisibilityState {
    LEFT_VISIBLE,
    LEFT_AND_MIDDLE_VISIBLE, 
    MIDDLE_VISIBLE, 
    MIDDLE_AND_RIGHT_VISIBLE,
    RIGHT_VISIBLE
  }
  
  /**
   * Interface to be implemented by clients that require to be notified
   * when the visibility state change.
   */
  public interface OnStateChangeListener {
    
    /**
     * Method invoked by the control just prior to the transition state of visibility  
     * 
     * @param oldState   Estado actual de visibilidad control
     * @param newState   Proximo estado de visibilidad del control
     */
    void onBeginTransitionState(VisibilityState oldState, VisibilityState newState);
    
    /**
     * Method invoked by the control when its visibility status has been updated
     * 
     * @param newState   New visibility state of the control panels
     */
    void onNewStateVisible(VisibilityState newState);
    
  }
  
  // Reference to the three panels of the control
  private View mLeftView;
  private View mMiddleView;
  private View mRightView;
  
  // Variables that store the minimum and maximum widths that can have different panels
  private int mMinPaneWidth = -1;
  private int mMaxPaneWidth;
  private int mFullScreenWidth;
  
  // Stores the current device orientation
  private int mScreenOrientation;

  // Reference to the current state of the panels
  private VisibilityState mVisibilityState;
    
  /**
   * Lista de observadores de cambio de estado del control
   */
  private List<OnStateChangeListener> mStateListeners;
  
  private Handler mHandler = new Handler();
  
  private Context mContext;
  
  public ThreePaneLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    
    Log.i("Log", "Llamado el constructor del control");
    
    mContext = context;
    
    mScreenOrientation = getResources().getConfiguration().orientation;
    
    init(context, attrs);
  }
  
  private void init(Context context, AttributeSet attrs) {

    setOrientation(HORIZONTAL);
    
      TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ThreePaneLayout);
      String initialState = a.getString(R.styleable.ThreePaneLayout_initialState);
      
      if ( initialState != null ) { 
        // The initial state has been defined in the XML
        
        if (initialState.equals("left_visible")) {
          mVisibilityState = VisibilityState.LEFT_VISIBLE;
        } else if (initialState.equals("left_and_middle_visible")) {
          mVisibilityState = VisibilityState.LEFT_AND_MIDDLE_VISIBLE;
        } 
        
      } else { 
        // The initial state is not defined in the XML, set the default state
        
        mVisibilityState = VisibilityState.LEFT_AND_MIDDLE_VISIBLE;
      }
  }

  @Override
  public void onFinishInflate() {
    super.onFinishInflate();

    if ( getChildCount() != 3 ) {
      throw new IllegalStateException("ThreePaneLayout requires defining three daughters views in the XML");
    }
    
    // Get a reference to the views that make control
    mLeftView   = getChildAt(0);
    mMiddleView = getChildAt(1);
    mRightView  = getChildAt(2);
        
    configureWidth();
  }
  
  /**
   * Set the weights of each of the views of the layout container.
   * 
   * If the method takes no arguments recalculates the weights based on the current  visibility 
   * state of the control. If this method receive a parameter is used as visibility state from 
   * which will be held on recalculation
   * 
   * @param args
   */
  private void configureWidth(VisibilityState ... args) {
    
    LayoutParams leftPaneLayoutParams   = (LayoutParams) mLeftView.getLayoutParams();
    LayoutParams middlePaneLayoutParams = (LayoutParams) mMiddleView.getLayoutParams();
    LayoutParams rightPaneLayoutParams  = (LayoutParams) mRightView.getLayoutParams();
    
    VisibilityState visibilityState = args.length == 0 ? mVisibilityState : args[0];
  
    if (args.length > 0) {
      
      leftPaneLayoutParams.width   = 0;
      middlePaneLayoutParams.width = 0;
      rightPaneLayoutParams.width  = 0;
    }
    
    if ( visibilityState == VisibilityState.LEFT_VISIBLE ) {
      
      leftPaneLayoutParams.weight   = 1.0f; 
      middlePaneLayoutParams.weight = 0.0f; 
      rightPaneLayoutParams.weight  = 0.0f; 
      
    } else if ( visibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE ) {

      leftPaneLayoutParams.weight   = 0.35f;
      middlePaneLayoutParams.weight = 0.65f;
      rightPaneLayoutParams.weight  = 0.0f; 
      
    } else if ( visibilityState == VisibilityState.MIDDLE_VISIBLE ) {
      
      leftPaneLayoutParams.weight   = 0.0f; 
      middlePaneLayoutParams.weight = 1.0f;
      rightPaneLayoutParams.weight  = 0.0f;
      
    } else if ( visibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE ) {
      
      leftPaneLayoutParams.weight   = 0.0f;
      middlePaneLayoutParams.weight = 0.35f;
      rightPaneLayoutParams.weight  = 0.65f;
      
    } else if ( visibilityState == VisibilityState.RIGHT_VISIBLE ) {
      
      leftPaneLayoutParams.weight   = 0.0f;
      middlePaneLayoutParams.weight = 0.0f;
      rightPaneLayoutParams.weight  = 1.0f;
    }
    
    // Refresh the view and compute the size of the view in the screen. 
    requestLayout();
  }
  
  /**
   * Method that performs the visibility state transition control (redistribution of the panels)
   * 
   * @param newVisibilityState   New visibility state required.
   * @param resetDimensions      This parameter is optional and should be used only when you are performing a 
   *                             state transcion due to a change of device orientation so we can recalculated 
   *                             weights and widths of the panels.
   */
  public void setVisibilityState(VisibilityState newVisibilityState, boolean ... resetDimensions) {
    
    // Ignore the request if the control is being resized or if the requested state is equal to the current
    if ( isScrollingViews || newVisibilityState == mVisibilityState ) {
      return;
    }
    
    // If requested any state that contains more than one panel and device orientation is portrait, 
    // ignore the request (this control only supports multiple panels visible in landscape)
    if (mScreenOrientation == Configuration.ORIENTATION_PORTRAIT) {
      if ( newVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE || 
         newVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE ) {
        return;
      }
    }
    
    if (resetDimensions.length > 0 && resetDimensions[0] == true) {
      
      configureWidth(newVisibilityState);
      mMinPaneWidth = -1;
    }
    
    // Calculate the maximum and minimum widths of the control panel if the have not been defined
    if (mMinPaneWidth == -1) {
      
      DisplayMetrics displayMetrics = new DisplayMetrics();
      WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
      wm.getDefaultDisplay().getMetrics(displayMetrics);
      int screenWidth = displayMetrics.widthPixels;
      
      if ( mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE ) {
        
        mMinPaneWidth    = (int) (screenWidth * 0.35);
        mMaxPaneWidth    = screenWidth - mMinPaneWidth;
        mFullScreenWidth = screenWidth;
        
      } else { // Configuration.ORIENTATION_PORTRAIT
        
        mMinPaneWidth = mMaxPaneWidth = mFullScreenWidth = screenWidth;
      }
      
      resetWidget(mLeftView, mMinPaneWidth); 
      resetWidget(mMiddleView, mMaxPaneWidth);
      resetWidget(mRightView, mMaxPaneWidth);
      
      requestLayout();
    }
    
    isScrollingViews = true;
    mHandler.postDelayed(new Runnable() {
      
      @Override
      public void run() {
        isScrollingViews = false;
      }
    }, ANIMATION_DURATION + 100);
    
    
    VisibilityState currentVisibilityState = mVisibilityState;
    
    // Notify control observers will produce a state transition 
    if ( mStateListeners != null ) {
      for ( OnStateChangeListener observer : mStateListeners ) {
        observer.onBeginTransitionState(currentVisibilityState, newVisibilityState);
      }
    }
    
    if (resetDimensions.length == 0) {
      // Perform the movement of the panels to match the new state required
      animateVisibilityStateTransition(currentVisibilityState, newVisibilityState);      
    }

    // Update the reference to the current state
    mVisibilityState = newVisibilityState;
    
    // Notify the new control state to the control's observers 
    if ( mStateListeners != null ) {
      for ( OnStateChangeListener observer : mStateListeners ) {
        observer.onNewStateVisible(mVisibilityState);
      }
    }
  }
  
  /**
   * Moves on the x axis and resize the panels to suit new visibility state required.
   * 
   * @param currentVisibilityState    Current state control visibility
   * @param requiredVisibilityState   New visibility state required
   */
  private void animateVisibilityStateTransition(VisibilityState currentVisibilityState, 
      VisibilityState requiredVisibilityState) {
    
    switch (requiredVisibilityState) {
    
    case LEFT_VISIBLE:
    
      if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
      
        if (currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
          
          ObjectAnimator.ofInt(this, "leftWidth", mMinPaneWidth, mFullScreenWidth)
              .setDuration(ANIMATION_DURATION).start();
        }
        
      } else { // Configuration.ORIENTATION_PORTRAIT
        
        if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
          
          translateView(mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
          
        } else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
          
          translateView(2 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
        }
      } 

      break;
      
    case LEFT_AND_MIDDLE_VISIBLE:
      // Este estado solo es posible en orientacion panoramica
      
      if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
        
        translateView(mMinPaneWidth, mLeftView, mMiddleView, mRightView);
        
        ObjectAnimator.ofInt(this, "middleWidth", mFullScreenWidth, mMaxPaneWidth)
            .setDuration(ANIMATION_DURATION).start();

      } else if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
        
        translateView(mMinPaneWidth, mLeftView, mMiddleView, mRightView);

        ObjectAnimator.ofInt(this, "middleWidth", mMinPaneWidth, mMaxPaneWidth)
            .setDuration(ANIMATION_DURATION).start();
        
      } else if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {

        ObjectAnimator.ofInt(this, "leftWidth", mFullScreenWidth, mMinPaneWidth)
            .setDuration(ANIMATION_DURATION).start();
      }
      
      break;
      
    case MIDDLE_VISIBLE:
      
      if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) { 
        
        if (currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
          
          translateView(-1 * mMinPaneWidth, mLeftView, mMiddleView, mRightView);
          
          ObjectAnimator.ofInt(this, "middleWidth", mMaxPaneWidth, mFullScreenWidth)
              .setDuration(ANIMATION_DURATION).start();
        
        } else if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
          
          ObjectAnimator.ofInt(this, "middleWidth", mMinPaneWidth, mFullScreenWidth)
              .setDuration(ANIMATION_DURATION).start();
        }
        
      } else { // Configuration.ORIENTATION_PORTRAIT
        
        if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
          
          translateView(-1 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
          
        } else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
          
          translateView(mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
        }
        
      }
      
      break;
      
    case MIDDLE_AND_RIGHT_VISIBLE:
      // Este estado solo es posible en orientacion panoramica
      
      if (currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE) {
        
        translateView(-1 * mMinPaneWidth, mLeftView, mMiddleView, mRightView);

        ObjectAnimator.ofInt(this, "middleWidth", mMaxPaneWidth, mMinPaneWidth)
            .setDuration(ANIMATION_DURATION).start();

      } else if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {

        ObjectAnimator.ofInt(this, "middleWidth", mFullScreenWidth, mMinPaneWidth)
            .setDuration(ANIMATION_DURATION).start();
        
      
      } else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
        
        translateView(mMinPaneWidth, mLeftView, mMiddleView, mRightView);

        ObjectAnimator.ofInt(this, "rightWidth", mFullScreenWidth, mMaxPaneWidth)
            .setDuration(ANIMATION_DURATION).start();
      }
      
      break;
      
    case RIGHT_VISIBLE:
      
      if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) { 
        
        if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
          
          translateView(-1 * mMinPaneWidth, mLeftView, mMiddleView, mRightView);

          ObjectAnimator.ofInt(this, "rightWidth", mMaxPaneWidth, mFullScreenWidth)
              .setDuration(ANIMATION_DURATION).start();
        }
        
      } else { // Configuration.ORIENTATION_PORTRAIT
        
        if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
          
          translateView(-2 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
          
        } else if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
          
          translateView(-1 * mMaxPaneWidth, mLeftView, mMiddleView, mRightView);
        }
      }
      
      break;
    }
  }
  
  public VisibilityState getVisibityState() {
    return mVisibilityState;
  }

  @SuppressWarnings("unused")
  private void setLeftWidth(int value) {
    mLeftView.getLayoutParams().width = value;
    requestLayout();
  }
  
  @SuppressWarnings("unused")
  private void setMiddleWidth(int value) {
    mMiddleView.getLayoutParams().width = value;
    requestLayout();
  }
  
  @SuppressWarnings("unused")
  private void setRightWidth(int value) {
    mRightView.getLayoutParams().width = value;
    requestLayout();
  }

  /**
   * Moves in the X axis the views received as parameter.
   *   
   * @param deltaX   Number of pixels that are shifted in the x-axis views
   * @param views    Views on which it will move
   */
  private void translateView(int deltaX, View... views) {
    
    for (final View view : views) {
      
      view.setLayerType(View.LAYER_TYPE_NONE, null);

      view.animate().translationXBy(deltaX).setDuration(ANIMATION_DURATION)
          .setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
              view.setLayerType(View.LAYER_TYPE_NONE, null);
            }
          });
    }
  }

  /**
   * Updates the properties of length and weight on the layout of the view passed as parameter 
   * 
   * @param view    Vista on which perform the update of the properties
   * @param width   New width
   */
  private void resetWidget(View view, int width) {
    
    LinearLayout.LayoutParams p = (LinearLayout.LayoutParams) view.getLayoutParams();
    p.width = width;
    p.weight = 0;
  }
  
  /**
   * Adds the component passed as a parameter to the list of control observers. The observers are notified
   * every time the control change the panels distribution. Observers also receive a notification just before 
   * starting the transition state by the control.
   * 
   * @param observer   component that implements the {@ link OnStateChangeListener} interface
   *                   to recieve notifications when the visibility control state change
   */
  public void addStateObserver(OnStateChangeListener observer) {
    
    if ( mStateListeners == null ) {
      mStateListeners = new ArrayList<OnStateChangeListener>();
    }
    
    mStateListeners.add(observer);
  }
  
  /**
   * Removes the component passed as parameter from the list of observers of 
   * state change. Observers are added to the list through the method {@ link # addStateObserver}
   * 
   * @param observer   component that implements the {@ link OnStateChangeListener} interface
   *                   to recieve notifications when the visibility control state change
   */
  public void deleteStatetObserver(OnStateChangeListener observer) {
    
    if ( mStateListeners == null ) return;
    
    mStateListeners.remove(observer);
  }


  /**
   * Method invoked by the host activity when the orientation of the device has changed. Based on the 
   * new orientation the control redistributes panels to match the new orientation and improve usability
   */
  public void deviceOrientationHasChange() 
  {
    int newScreenOrientation = getResources().getConfiguration().orientation;

    VisibilityState currentVisibilityState = getVisibityState();
    VisibilityState newVisibilityState = null;

    if (newScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
      // When orientation chamge to landscape the only possible states in the previous orientation 
      // (portrait) can only be those where not coexist multiple views (eg LEFT_VISIBLE, 
      // MIDDLE_VISIBLE, RIGHT_VISIBLE)

      if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
        newVisibilityState = VisibilityState.LEFT_AND_MIDDLE_VISIBLE;
      } else if (currentVisibilityState == VisibilityState.MIDDLE_VISIBLE) {
        newVisibilityState = VisibilityState.MIDDLE_VISIBLE;
      } else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
        newVisibilityState = VisibilityState.RIGHT_VISIBLE;
      }

    } else { // Configuration.ORIENTATION_PORTRAIT

      if (currentVisibilityState == VisibilityState.LEFT_VISIBLE) {
        newVisibilityState = VisibilityState.LEFT_VISIBLE;
      } else if ( currentVisibilityState == VisibilityState.LEFT_AND_MIDDLE_VISIBLE || 
              currentVisibilityState == VisibilityState.MIDDLE_VISIBLE ) {
        newVisibilityState = VisibilityState.MIDDLE_VISIBLE;
      } else if (currentVisibilityState == VisibilityState.MIDDLE_AND_RIGHT_VISIBLE) {
        newVisibilityState = VisibilityState.RIGHT_VISIBLE;
      } else if (currentVisibilityState == VisibilityState.RIGHT_VISIBLE) {
        newVisibilityState = VisibilityState.RIGHT_VISIBLE;
      }
    }

    mScreenOrientation = newScreenOrientation;
    
    mMinPaneWidth = -1;
    configureWidth(newVisibilityState);
    
    mVisibilityState = newVisibilityState;
  }
    
  public View getLeftView() {
    return mLeftView;
  }

  public View getMiddleView() {
    return mMiddleView;
  }

  public View getRightView() {
    return mRightView;
  }

}




Java Source Code List

com.panels.controls.ThreePaneLayout.java
com.panels.ui.CategoriesListFragment.java
com.panels.ui.MainActivity.java
com.panels.ui.TaskDetailFragment.java
com.panels.ui.TasksListFragment.java