blob: 4102d10be11011adef4bc3525d08de403a59ef8c [file] [log] [blame]
package org.sflphone.views.parallaxscrollview;
import org.sflphone.R;
import org.sflphone.views.parallaxscrollview.ObservableScrollView.ScrollCallbacks;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ScrollView;
/**
* A custom ScrollView that can accept a scroll listener.
*/
public class ParallaxScrollView extends ViewGroup
{
private static final int DEFAULT_CHILD_GRAVITY = Gravity.CENTER_HORIZONTAL;
private static final String TAG = "ParallaxScrollView";
private static float PARALLAX_OFFSET_DEFAULT = 0.2f;
/**
* By how much should the background move to the foreground
*/
private float mParallaxOffset = PARALLAX_OFFSET_DEFAULT;
private View mBackground;
private ObservableScrollView mScrollView;
private final ScrollCallbacks mScrollCallbacks = new ScrollCallbacks()
{
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt)
{
requestLayout();
}
};
// Layout stuff
private int mBackgroundRight;
private int mBackgroundBottom;
/**
* Height of the Foreground ScrollView Content
*/
private int mScrollContentHeight = 0;
/**
* Height of the ScrollView, should be the same as this view
*/
private int mScrollViewHeight = 0;
/**
* The multipler by how much to move the background to the foreground
*/
private float mScrollDiff = 0f;
public ParallaxScrollView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.ParallaxScrollView, 0, 0);
try
{
setParallaxOffset(a.getFloat(R.styleable.ParallaxScrollView_parallexOffset,
PARALLAX_OFFSET_DEFAULT));
}
catch (Exception e)
{
}
finally
{
a.recycle();
}
}
public ParallaxScrollView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public ParallaxScrollView(Context context)
{
this(context, null);
}
@Override
public void addView(View child)
{
if (getChildCount() > 1)
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
super.addView(child);
}
@Override
public void addView(View child, int index)
{
if (getChildCount() > 1)
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
super.addView(child, index);
}
@Override
public void addView(View child, int index, android.view.ViewGroup.LayoutParams params)
{
if (getChildCount() > 1)
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
super.addView(child, index, params);
}
@Override
public void addView(View child, int width, int height)
{
if (getChildCount() > 1)
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
super.addView(child, width, height);
}
@Override
public void addView(View child, android.view.ViewGroup.LayoutParams params)
{
if (getChildCount() > 1)
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
super.addView(child, params);
}
/**
* Set the offset
*
* @param offset
* a number greater than 0.0
*/
public void setParallaxOffset(float offset)
{
// Make sure we only get to .05 of a floating number
offset = (float) Math.rint(offset * 100) / 100;
if (offset > 0.0)
mParallaxOffset = offset;
else
mParallaxOffset = PARALLAX_OFFSET_DEFAULT;
requestLayout();
}
/**
* Get the current offset
*
* @return
*/
public float getParallaxOffset()
{
return mParallaxOffset;
}
/**
* Sort the views out after they have been inflated from a layout
*/
@Override
protected void onFinishInflate()
{
super.onFinishInflate();
if (getChildCount() > 2)
{
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
}
organiseViews();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mScrollView != null)
{
measureChild(mScrollView, MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
MeasureSpec.AT_MOST));
mScrollContentHeight = mScrollView.getChildAt(0).getMeasuredHeight();
mScrollViewHeight = mScrollView.getMeasuredHeight();
}
if (mBackground != null)
{
int minHeight = 0;
minHeight = (int) (mScrollViewHeight + mParallaxOffset
* (mScrollContentHeight - mScrollViewHeight));
minHeight = Math.max(minHeight, MeasureSpec.getSize(heightMeasureSpec));
measureChild(mBackground, MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(minHeight, MeasureSpec.EXACTLY));
mBackgroundRight = getLeft() + mBackground.getMeasuredWidth();
mBackgroundBottom = getTop() + mBackground.getMeasuredHeight();
mScrollDiff = (float) (mBackground.getMeasuredHeight() - mScrollViewHeight)
/ (float) (mScrollContentHeight - mScrollViewHeight);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
final int parentLeft = getPaddingLeft();
final int parentRight = right - left - getPaddingRight();
final int parentTop = getPaddingTop();
final int parentBottom = bottom - top - getPaddingBottom();
if (mScrollView != null && mScrollView.getVisibility() != GONE)
{
final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mScrollView
.getLayoutParams();
final int width = mScrollView.getMeasuredWidth();
final int height = mScrollView.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1)
{
gravity = DEFAULT_CHILD_GRAVITY;
}
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (horizontalGravity)
{
case Gravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin
- lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - lp.rightMargin;
break;
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity)
{
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin
- lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
mScrollView.layout(childLeft, childTop, childLeft + width, childTop + height);
}
if (mBackground != null)
{
final int scrollYCenterOffset = mScrollView.getScrollY();
final int offset = (int) (scrollYCenterOffset * mScrollDiff);
// Log.d(TAG, "Layout Scroll Y: " + scrollYCenterOffset +
// " ScrollDiff: " + mScrollDiff
// + " Background Offset:" + offset);
mBackground.layout(getLeft(), offset, mBackgroundRight, offset + mBackgroundBottom);
}
}
/**
* {@inheritDoc}
*/
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs)
{
return new FrameLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
{
return new FrameLayout.LayoutParams(p);
}
@Override
protected LayoutParams generateDefaultLayoutParams()
{
return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
Gravity.CENTER_HORIZONTAL);
}
/**
* {@inheritDoc}
*/
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
{
return p instanceof FrameLayout.LayoutParams;
}
/**
* Take the direct children and sort them
*/
private void organiseViews()
{
if (getChildCount() <= 0) return;
if (getChildCount() == 1)
{
// Get the only child
final View forground = getChildAt(0);
organiseBackgroundView(null);
organiseForgroundView(forground);
}
else if (getChildCount() == 2)
{
final View background = getChildAt(0);
final View foreground = getChildAt(1);
organiseBackgroundView(background);
organiseForgroundView(foreground);
}
else
{
throw new IllegalStateException("ParallaxScrollView can host only two direct children");
}
}
private void organiseBackgroundView(final View background)
{
mBackground = background;
}
private void organiseForgroundView(final View forground)
{
final int insertPos = getChildCount() - 1;
// See if its a observable scroll view?
if (forground instanceof ObservableScrollView)
{
// Attach the callback to it.
mScrollView = (ObservableScrollView) forground;
}
else if (forground instanceof ViewGroup && !(forground instanceof ScrollView))
{
// See if it is a view group but not a scroll view and wrap it
// with an observable ScrollView
mScrollView = new ObservableScrollView(getContext(), null);
removeView(forground);
mScrollView.addView(forground);
addView(mScrollView, insertPos);
}
else if (forground instanceof ScrollView)
{
final View child;
if (((ScrollView) forground).getChildCount() > 0)
child = ((ScrollView) forground).getChildAt(0);
else
child = null;
mScrollView = new ObservableScrollView(getContext(), null);
removeView(forground);
if (child != null) mScrollView.addView(child);
addView(mScrollView, insertPos);
}
else if (forground instanceof View)
{
mScrollView = new ObservableScrollView(getContext(), null);
removeView(forground);
mScrollView.addView(forground);
addView(mScrollView, insertPos);
}
if (mScrollView != null)
{
mScrollView.setLayoutParams(forground.getLayoutParams());
mScrollView.setCallbacks(mScrollCallbacks);
mScrollView.setFillViewport(true);
}
}
}