* #34178: new scroll container with parallax effect
diff --git a/src/org/sflphone/views/parallaxscrollview/AnotherView.java b/src/org/sflphone/views/parallaxscrollview/AnotherView.java
new file mode 100644
index 0000000..1b43d3c
--- /dev/null
+++ b/src/org/sflphone/views/parallaxscrollview/AnotherView.java
@@ -0,0 +1,49 @@
+package org.sflphone.views.parallaxscrollview;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+public class AnotherView extends LinearLayout{
+
+ private ScrollCallbacks mCallbacks;
+
+ public AnotherView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt)
+ {
+ super.onScrollChanged(l, t, oldl, oldt);
+ if (mCallbacks != null)
+ {
+ mCallbacks.onScrollChanged(l, t, oldl, oldt);
+ }
+ }
+
+ @Override
+ public int computeVerticalScrollRange()
+ {
+ return super.computeVerticalScrollRange();
+ }
+
+ public void setCallbacks(ScrollCallbacks listener)
+ {
+ mCallbacks = listener;
+ }
+
+ @Override
+ public void draw(Canvas canvas)
+ {
+ super.draw(canvas);
+ }
+
+ static interface ScrollCallbacks
+ {
+ public void onScrollChanged(int l, int t, int oldl, int oldt);
+ }
+
+}
diff --git a/src/org/sflphone/views/parallaxscrollview/ObservableScrollView.java b/src/org/sflphone/views/parallaxscrollview/ObservableScrollView.java
new file mode 100644
index 0000000..921121a
--- /dev/null
+++ b/src/org/sflphone/views/parallaxscrollview/ObservableScrollView.java
@@ -0,0 +1,48 @@
+package org.sflphone.views.parallaxscrollview;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+public class ObservableScrollView extends ScrollView
+{
+ private ScrollCallbacks mCallbacks;
+
+ public ObservableScrollView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt)
+ {
+ super.onScrollChanged(l, t, oldl, oldt);
+ if (mCallbacks != null)
+ {
+ mCallbacks.onScrollChanged(l, t, oldl, oldt);
+ }
+ }
+
+ @Override
+ public int computeVerticalScrollRange()
+ {
+ return super.computeVerticalScrollRange();
+ }
+
+ public void setCallbacks(ScrollCallbacks listener)
+ {
+ mCallbacks = listener;
+ }
+
+ @Override
+ public void draw(Canvas canvas)
+ {
+ super.draw(canvas);
+ }
+
+ static interface ScrollCallbacks
+ {
+ public void onScrollChanged(int l, int t, int oldl, int oldt);
+ }
+}
\ No newline at end of file
diff --git a/src/org/sflphone/views/parallaxscrollview/ParallaxScrollView.java b/src/org/sflphone/views/parallaxscrollview/ParallaxScrollView.java
new file mode 100644
index 0000000..4102d10
--- /dev/null
+++ b/src/org/sflphone/views/parallaxscrollview/ParallaxScrollView.java
@@ -0,0 +1,396 @@
+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);
+ }
+ }
+
+}
\ No newline at end of file