/*
* Android Split Pane Layout.
* https://github.com/MobiDevelop/android-split-pane-layout
*
* Copyright (C) 2012 Justin Shapcott
*
* 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.mobidevelop.widget;
import org.alfresco.mobile.android.application.thirdparty.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.ViewGroup;
/**
* A layout that splits the available space between two child views. An
* optionally movable bar exists between the children which allows the user to
* redistribute the space allocated to each view.
*/
public class SplitPaneLayout extends ViewGroup
{
public static final int ORIENTATION_HORIZONTAL = 0;
public static final int ORIENTATION_VERTICAL = 1;
private int mOrientation = 0;
private int mSplitterSize = 12;
private boolean mSplitterMovable = true;
private int mSplitterPosition = Integer.MIN_VALUE;
private float mSplitterPositionPercent = 0.5f;
private Drawable mSplitterDrawable;
private Drawable mSplitterDraggingDrawable;
private Rect mSplitterRect = new Rect();
private int lastX;
private int lastY;
private Rect temp = new Rect();
private boolean isDragging = false;
public SplitPaneLayout(Context context)
{
super(context);
mSplitterPositionPercent = 0.5f;
mSplitterDrawable = new PaintDrawable(0x88FFFFFF);
mSplitterDraggingDrawable = new PaintDrawable(0x88FFFFFF);
}
public SplitPaneLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
extractAttributes(context, attrs);
}
public SplitPaneLayout(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
extractAttributes(context, attrs);
}
private void extractAttributes(Context context, AttributeSet attrs)
{
if (attrs != null)
{
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SplitPaneLayout);
mOrientation = a.getInt(R.styleable.SplitPaneLayout_orientation, 0);
mSplitterSize = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_splitterSize, context.getResources()
.getDimensionPixelSize(R.dimen.splitpanelayout_default_splitter_size));
mSplitterMovable = a.getBoolean(R.styleable.SplitPaneLayout_splitterMovable, true);
TypedValue value = a.peekValue(R.styleable.SplitPaneLayout_splitterPosition);
if (value != null)
{
if (value.type == TypedValue.TYPE_DIMENSION)
{
mSplitterPosition = a.getDimensionPixelSize(R.styleable.SplitPaneLayout_splitterPosition,
Integer.MIN_VALUE);
}
else if (value.type == TypedValue.TYPE_FRACTION)
{
mSplitterPositionPercent = a
.getFraction(R.styleable.SplitPaneLayout_splitterPosition, 100, 100, 50) * 0.01f;
}
}
else
{
mSplitterPosition = Integer.MIN_VALUE;
mSplitterPositionPercent = 0.5f;
}
value = a.peekValue(R.styleable.SplitPaneLayout_splitterBackground);
if (value != null)
{
if (value.type == TypedValue.TYPE_REFERENCE || value.type == TypedValue.TYPE_STRING)
{
mSplitterDrawable = a.getDrawable(R.styleable.SplitPaneLayout_splitterBackground);
}
else if (value.type == TypedValue.TYPE_INT_COLOR_ARGB8 || value.type == TypedValue.TYPE_INT_COLOR_ARGB4
|| value.type == TypedValue.TYPE_INT_COLOR_RGB8 || value.type == TypedValue.TYPE_INT_COLOR_RGB4)
{
mSplitterDrawable = new PaintDrawable(a.getColor(R.styleable.SplitPaneLayout_splitterBackground,
0xFF000000));
}
}
value = a.peekValue(R.styleable.SplitPaneLayout_splitterDraggingBackground);
if (value != null)
{
if (value.type == TypedValue.TYPE_REFERENCE || value.type == TypedValue.TYPE_STRING)
{
mSplitterDraggingDrawable = a.getDrawable(R.styleable.SplitPaneLayout_splitterDraggingBackground);
}
else if (value.type == TypedValue.TYPE_INT_COLOR_ARGB8 || value.type == TypedValue.TYPE_INT_COLOR_ARGB4
|| value.type == TypedValue.TYPE_INT_COLOR_RGB8 || value.type == TypedValue.TYPE_INT_COLOR_RGB4)
{
mSplitterDraggingDrawable = new PaintDrawable(a.getColor(
R.styleable.SplitPaneLayout_splitterDraggingBackground, 0x88FFFFFF));
}
}
else
{
mSplitterDraggingDrawable = new PaintDrawable(0x88FFFFFF);
}
a.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
check();
if (widthSize > 0 && heightSize > 0)
{
switch (mOrientation)
{
case 0:
{
if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent < 0)
{
mSplitterPosition = widthSize / 2;
}
else if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent >= 0)
{
mSplitterPosition = (int) (widthSize * mSplitterPositionPercent);
}
else if (mSplitterPosition != Integer.MIN_VALUE && mSplitterPositionPercent < 0)
{
mSplitterPositionPercent = (float) mSplitterPosition / (float) widthSize;
}
getChildAt(0).measure(
MeasureSpec.makeMeasureSpec(mSplitterPosition - (mSplitterSize / 2), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
getChildAt(1).measure(
MeasureSpec.makeMeasureSpec(widthSize - (mSplitterSize / 2) - mSplitterPosition,
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
break;
}
case 1:
{
if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent < 0)
{
mSplitterPosition = heightSize / 2;
}
else if (mSplitterPosition == Integer.MIN_VALUE && mSplitterPositionPercent >= 0)
{
mSplitterPosition = (int) (heightSize * mSplitterPositionPercent);
}
else if (mSplitterPosition != Integer.MIN_VALUE && mSplitterPositionPercent < 0)
{
mSplitterPositionPercent = (float) mSplitterPosition / (float) heightSize;
}
getChildAt(0).measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mSplitterPosition - (mSplitterSize / 2), MeasureSpec.EXACTLY));
getChildAt(1).measure(
MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(heightSize - (mSplitterSize / 2) - mSplitterPosition,
MeasureSpec.EXACTLY));
break;
}
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int w = r - l;
int h = b - t;
switch (mOrientation)
{
case 0:
{
getChildAt(0).layout(0, 0, mSplitterPosition - (mSplitterSize / 2), h);
mSplitterRect.set(mSplitterPosition - (mSplitterSize / 2), 0, mSplitterPosition + (mSplitterSize / 2),
h);
getChildAt(1).layout(mSplitterPosition + (mSplitterSize / 2), 0, r, h);
break;
}
case 1:
{
getChildAt(0).layout(0, 0, w, mSplitterPosition - (mSplitterSize / 2));
mSplitterRect.set(0, mSplitterPosition - (mSplitterSize / 2), w, mSplitterPosition
+ (mSplitterSize / 2));
getChildAt(1).layout(0, mSplitterPosition + (mSplitterSize / 2), w, h);
break;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (mSplitterMovable)
{
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
if (mSplitterRect.contains(x, y))
{
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
isDragging = true;
temp.set(mSplitterRect);
invalidate(temp);
lastX = x;
lastY = y;
}
break;
}
case MotionEvent.ACTION_MOVE:
{
if (isDragging)
{
switch (mOrientation)
{
case ORIENTATION_HORIZONTAL:
{
temp.offset((x - lastX), 0);
break;
}
case ORIENTATION_VERTICAL:
{
temp.offset(0, (y - lastY));
break;
}
}
lastX = x;
lastY = y;
invalidate();
}
break;
}
case MotionEvent.ACTION_UP:
{
if (isDragging)
{
isDragging = false;
switch (mOrientation)
{
case ORIENTATION_HORIZONTAL:
{
mSplitterPosition = x;
break;
}
case ORIENTATION_VERTICAL:
{
mSplitterPosition = y;
break;
}
}
mSplitterPositionPercent = -1;
remeasure();
requestLayout();
}
break;
}
}
return true;
}
return false;
}
@Override
protected Parcelable onSaveInstanceState()
{
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.mSplitterPositionPercent = mSplitterPositionPercent;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state)
{
if (!(state instanceof SavedState))
{
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setSplitterPositionPercent(ss.mSplitterPositionPercent);
}
/**
* Convenience for calling own measure method.
*/
private void remeasure()
{
measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
}
/**
* Checks that we have exactly two children.
*/
private void check()
{
if (getChildCount() != 2) { throw new RuntimeException("SplitPaneLayout must have exactly two child views."); }
}
@Override
protected void dispatchDraw(Canvas canvas)
{
super.dispatchDraw(canvas);
if (mSplitterDrawable != null)
{
mSplitterDrawable.setBounds(mSplitterRect);
mSplitterDrawable.draw(canvas);
}
if (isDragging)
{
mSplitterDraggingDrawable.setBounds(temp);
mSplitterDraggingDrawable.draw(canvas);
}
}
/**
* Gets the current drawable used for the splitter.
*
* @return the drawable used for the splitter
*/
public Drawable getSplitterDrawable()
{
return mSplitterDrawable;
}
/**
* Sets the drawable used for the splitter.
*
* @param splitterDrawable the desired orientation of the layout
*/
public void setSplitterDrawable(Drawable splitterDrawable)
{
mSplitterDrawable = splitterDrawable;
if (getChildCount() == 2)
{
remeasure();
}
}
/**
* Gets the current drawable used for the splitter dragging overlay.
*
* @return the drawable used for the splitter
*/
public Drawable getSplitterDraggingDrawable()
{
return mSplitterDraggingDrawable;
}
/**
* Sets the drawable used for the splitter dragging overlay.
*
* @param splitterDraggingDrawable the desired orientation of the layout
*/
public void setSplitterDraggingDrawable(Drawable splitterDraggingDrawable)
{
mSplitterDraggingDrawable = splitterDraggingDrawable;
if (isDragging)
{
invalidate();
}
}
/**
* Gets the current orientation of the layout.
*
* @return the orientation of the layout
*/
public int getOrientation()
{
return mOrientation;
}
/**
* Sets the orientation of the layout.
*
* @param orientation the desired orientation of the layout
*/
public void setOrientation(int orientation)
{
if (mOrientation != orientation)
{
mOrientation = orientation;
if (getChildCount() == 2)
{
remeasure();
}
}
}
/**
* Gets the current size of the splitter in pixels.
*
* @return the size of the splitter
*/
public int getSplitterSize()
{
return mSplitterSize;
}
/**
* Sets the current size of the splitter in pixels.
*
* @param splitterSize the desired size of the splitter
*/
public void setSplitterSize(int splitterSize)
{
mSplitterSize = splitterSize;
if (getChildCount() == 2)
{
remeasure();
}
}
/**
* Gets whether the splitter is movable by the user.
*
* @return whether the splitter is movable
*/
public boolean isSplitterMovable()
{
return mSplitterMovable;
}
/**
* Sets whether the splitter is movable by the user.
*
* @param splitterMovable whether the splitter is movable
*/
public void setSplitterMovable(boolean splitterMovable)
{
mSplitterMovable = splitterMovable;
}
/**
* Gets the current position of the splitter in pixels.
*
* @return the position of the splitter
*/
public int getSplitterPosition()
{
return mSplitterPosition;
}
/**
* Sets the current position of the splitter in pixels.
*
* @param position the desired position of the splitter
*/
public void setSplitterPosition(int position)
{
if (position < 0)
{
position = 0;
}
mSplitterPosition = position;
mSplitterPositionPercent = -1;
remeasure();
}
/**
* Gets the current position of the splitter as a percent.
*
* @return the position of the splitter
*/
public float getSplitterPositionPercent()
{
return mSplitterPositionPercent;
}
/**
* Sets the current position of the splitter as a percentage of the layout.
*
* @param position the desired position of the splitter
*/
public void setSplitterPositionPercent(float position)
{
if (position < 0)
{
position = 0;
}
if (position > 1)
{
position = 1;
}
mSplitterPosition = Integer.MIN_VALUE;
mSplitterPositionPercent = position;
remeasure();
}
/**
* Holds important values when we need to save instance state.
*/
public static class SavedState extends BaseSavedState
{
float mSplitterPositionPercent;
SavedState(Parcelable superState)
{
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags)
{
super.writeToParcel(out, flags);
out.writeFloat(mSplitterPositionPercent);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>()
{
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
private SavedState(Parcel in)
{
super(in);
mSplitterPositionPercent = in.readFloat();
}
}
}