package com.mapsaurus.paneslayout;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import com.actionbarsherlock.R;
import com.mapsaurus.paneslayout.PanesSizer.PaneSizer;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
public class PanesLayout extends FrameLayout {
/**
* Each pane exists inside of a scroll view.
*/
private ArrayList<SimpleScrollView> scrollers;
/**
* Each pane contains a container for something else (fragment, view, etc)
*/
private ArrayList<PaneView> panes;
/**
* Index (index) of the currently selected index
*/
private int currentIndex = 0;
/**
* Currently visible indexes. Complete means the pane is completely visible.
*/
private int firstIndex;
private int firstCompleteIndex;
private int lastIndex;
private int lastCompleteIndex;
private int parentWidth;
private int parentHeight;
/**
* Set whenever panes are changed
* Unset whenever index is set
*/
private boolean panesChanged = true;
/**
* This object sets the size of every pane base on it's type and focus
*/
private PaneSizer mPaneSizer;
public void setPaneSizer(PaneSizer sizer) {
this.mPaneSizer = sizer;
this.requestLayout();
}
/**
* Whenever the visible panes change, this is fired.
*/
public interface OnIndexChangedListener {
/**
* @param firstIndex = the bottom-most visible pane
* @param lastIndex = the top-most visible pane
* @param firstCompleteIndex = the bottom-most completely visible pane
* @param lastCompleteIndex = the top-most completely visible pane
*/
public void onIndexChanged(int firstIndex, int lastIndex,
int firstCompleteIndex, int lastCompleteIndex);
}
private WeakReference<OnIndexChangedListener> wIndexChangedListener =
new WeakReference<OnIndexChangedListener>(null);
public void setOnIndexChangedListener(OnIndexChangedListener l) {
this.wIndexChangedListener = new WeakReference<OnIndexChangedListener>(l);
}
public PanesLayout(Context context) {
this(context, null);
}
public PanesLayout(Context context, AttributeSet attrs) {
super(context, attrs);
scrollers = new ArrayList<SimpleScrollView>();
panes = new ArrayList<PaneView>();
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
parentWidth = MeasureSpec.getSize(widthMeasureSpec)
- this.getPaddingLeft() - this.getPaddingRight();
parentHeight = MeasureSpec.getSize(heightMeasureSpec)
- this.getPaddingTop() - this.getPaddingBottom();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* Inside onLayout, we might need to update the index/scroll of the panes.
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (panesChanged || changed) {
setIndex(currentIndex);
panesChanged = false;
}
}
private int getPaneX(SimpleScrollView scroll) {
return parentWidth - scroll.getScrollX();
}
private PaneView getPaneFromScroll(double x) {
for (int i = lastIndex; i >= 0; i --) {
SimpleScrollView scroll = scrollers.get(i);
PaneView pane = panes.get(i);
if (pane == null || scroll == null) return null;
if (x > getPaneX(scroll))
return pane;
}
return null;
}
private double clampIndex(double index) {
if (index < 0) index = 0;
if (index > scrollers.size() - 1)
index = scrollers.size() - 1;
return index;
}
private void scrollHelper(SimpleScrollView s, double scrollX, boolean smooth) {
scrollX = Math.min(scrollX, parentWidth);
scrollX = Math.max(scrollX, 0);
if (!s.isLayoutDirty()) {
if (smooth)
s.smoothScrollTo((int) scrollX, 0);
else s.scrollTo((int) scrollX, 0);
}
}
/**
* Scroll all the panes based on some index.
* Returns which index is the top-most pane.
*/
private boolean partiallyVisible(int scroll, int width) {
return (scroll - width < parentWidth + 5 && scroll >= 5);
}
private boolean completelyVisible(int scroll, int width) {
return (scroll <= parentWidth + 5 && scroll - width >= -5);
}
private boolean scrollEverything(double index, boolean smooth) {
if (parentWidth <= 0) return false;
index = clampIndex(index);
// get the top index
int topIndex = (int) Math.ceil(index);
if (topIndex < 0 || topIndex >= panes.size()) return false;
// get the minX & maxX of the top index
PaneView topPane = panes.get(topIndex);
SimpleScrollView topScroller = scrollers.get(topIndex);
if (topPane == null) return false;
int topWidth = topPane.containerWidth;
double p = (topIndex - index);
int topScroll = (int) (topWidth - topWidth * p);// + scrollOffset;
int firstScroll = topScroll;
for (int i = topIndex - 1; i >= 0; i --) {
PaneView pane = panes.get(i);
if (pane == null) continue;
firstScroll += pane.containerWidth;
}
if (firstScroll < parentWidth)
topScroll += parentWidth - firstScroll;
// scroll the top pane!
scrollHelper(topScroller, topScroll, smooth);
int firstIndex = topIndex;
int firstCompleteIndex = topIndex;
int lastIndex = topIndex;
int lastCompleteIndex = topIndex;
int scroll = topScroll - topWidth;
// move all panes after the top pane
for (int i = topIndex + 1; i < panes.size(); i ++) {
SimpleScrollView scroller = scrollers.get(i);
PaneView pane = panes.get(i);
if (scroller == null || pane == null) continue;
int width = pane.containerWidth;
scrollHelper(scroller, scroll, smooth);
if (partiallyVisible(scroll, width))
lastIndex = i;
if (completelyVisible(scroll, width))
lastCompleteIndex = i;
scroll -= pane.containerWidth;
}
scroll = topScroll;
// move each pane before the top pane
for (int i = topIndex - 1; i >= 0; i --) {
SimpleScrollView scroller = scrollers.get(i);
PaneView pane = panes.get(i);
if (scroller == null || pane == null) continue;
int width = pane.containerWidth;
scroll += pane.containerWidth;
scrollHelper(scroller, scroll, smooth);
if (partiallyVisible(scroll, width))
firstIndex = i;
if (completelyVisible(scroll, width))
firstCompleteIndex = i;
}
if (this.firstIndex != firstIndex || this.firstCompleteIndex != firstCompleteIndex ||
this.lastIndex != lastIndex || this.lastCompleteIndex != lastCompleteIndex) {
this.firstIndex = firstIndex;
this.firstCompleteIndex = firstCompleteIndex;
this.lastIndex = lastIndex;
this.lastCompleteIndex = lastCompleteIndex;
OnIndexChangedListener l = wIndexChangedListener.get();
if (l != null) l.onIndexChanged(firstIndex, lastIndex,
firstCompleteIndex, lastCompleteIndex);
}
return true;
}
public void setIndex(int index) {
index = (int) clampIndex(index);
if (scrollEverything(index, true))
currentIndex = lastCompleteIndex;
else currentIndex = index;
}
public int getCurrentIndex() {
return currentIndex;
}
public int getNumPanes() {
return panes.size();
}
public PaneView getPane(int i) {
if (i >= panes.size()) return null;
if (i < 0) return null;
return panes.get(i);
}
public PaneView addPane(int type, boolean focused) {
panesChanged = true;
int index = panes.size();
PaneView pane = new PaneView(getContext(), type, index, focused);
panes.add(pane);
SimpleScrollView scroller = new SimpleScrollView(getContext());
scrollers.add(scroller);
scroller.addView(pane);
addView(scroller, index);
return pane;
}
public ArrayList<PaneView> removePanes(int removeI) {
panesChanged = true;
ArrayList<PaneView> deletedPanes = new ArrayList<PaneView>();
for (int i = scrollers.size() - 1; i >= removeI; i --) {
if (i < 0) break;
scrollers.remove(i);
deletedPanes.add(panes.remove(i));
removeViewAt(i);
}
if (removeI <= currentIndex)
setIndex(removeI - 1);
return deletedPanes;
}
private double currentX;
private double startX;
private double currentY;
private double startY;
private boolean dragging;
private void onTouchEventHelper(MotionEvent event) {
currentX = event.getX();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
startX = currentX;
dragging = true;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
onTouchEventHelper(event);
double dx = Math.abs(currentX - startX);
double dy = Math.abs(currentY - startY);
if (dx > 4 * dy && dx > 10 ) {
PaneView p = getPaneFromScroll(startX);
int bevelSize = getResources().getDimensionPixelSize(R.dimen.bevel_size);
if (p != null) {
if (p.index < firstCompleteIndex || p.index > lastCompleteIndex
|| p.focused != true || startX < bevelSize) {
} else {
return false;
}
}
return true;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!dragging) return true;
onTouchEventHelper(event);
double scroll = currentX - startX;
double dIndex = -scroll / (parentWidth * 0.25);
scrollEverything(currentIndex + dIndex, false);
if ((event.getAction() == MotionEvent.ACTION_MOVE && Math.abs(dIndex) > 0.5)
|| event.getAction() == MotionEvent.ACTION_UP) {
setIndex((int) Math.round(currentIndex + dIndex));
dragging = false;
if (Math.abs(dIndex) > 0.25)
return true;
}
return true;
}
public class PaneView extends LinearLayout {
public int containerWidth;
public int index;
public int type;
public boolean focused;
private View startPadding;
private View leftShadow;
private ViewGroup container;
private View rightShadow;
private View endPadding;
public int getInnerId() {
return container.getId();
}
public ViewGroup getInner() {
return container;
}
public PaneView(Context context, int type_, int index_, boolean focused_) {
super(context);
focused = focused_;
index = index_;
type = type_;
setOrientation(LinearLayout.HORIZONTAL);
setLayoutParams(new LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
startPadding = new View(context);
startPadding.setVisibility(View.INVISIBLE);
leftShadow = new View(context);
leftShadow.setBackgroundResource(R.drawable.panes_shadow_left);
addView(startPadding);
addView(leftShadow);
container = new FrameLayout(context);
container.setId(Integer.MAX_VALUE - index);
container.setClickable(true);
addView(container);
rightShadow = new View(context);
rightShadow.setBackgroundResource(R.drawable.panes_shadow_right);
endPadding = new View(context);
endPadding.setVisibility(View.INVISIBLE);
addView(rightShadow);
addView(endPadding);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int shadowWidth = getResources().getDimensionPixelSize(R.dimen.shadow_size);
startPadding.measure(
MeasureSpec.makeMeasureSpec(parentWidth - shadowWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
leftShadow.measure(
MeasureSpec.makeMeasureSpec(shadowWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
PaneSizer paneSizer = mPaneSizer;
if (paneSizer != null)
containerWidth = paneSizer.getWidth(index, type, parentWidth, parentHeight);
else
containerWidth = (int) (0.7 * parentWidth);
container.measure(MeasureSpec.makeMeasureSpec(containerWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
rightShadow.measure(
MeasureSpec.makeMeasureSpec(shadowWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
endPadding.measure(MeasureSpec.makeMeasureSpec(parentWidth - containerWidth - shadowWidth,
MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.EXACTLY));
this.setMeasuredDimension(
resolveSize(parentWidth * 2, widthMeasureSpec),
resolveSize(parentHeight, heightMeasureSpec));
}
}
public boolean onBackPressed() {
int oldIndex = currentIndex;
setIndex(currentIndex - 1);
if (oldIndex != currentIndex)
return true;
return false;
}
}