blob: 4102d10be11011adef4bc3525d08de403a59ef8c [file] [log] [blame]
Alexandre Lision83e08dd2013-11-06 15:12:32 -05001package org.sflphone.views.parallaxscrollview;
2
3import org.sflphone.R;
4import org.sflphone.views.parallaxscrollview.ObservableScrollView.ScrollCallbacks;
5
6import android.content.Context;
7import android.content.res.TypedArray;
8import android.util.AttributeSet;
9import android.view.Gravity;
10import android.view.View;
11import android.view.ViewGroup;
12import android.widget.FrameLayout;
13import android.widget.ScrollView;
14
15/**
16 * A custom ScrollView that can accept a scroll listener.
17 */
18public class ParallaxScrollView extends ViewGroup
19{
20 private static final int DEFAULT_CHILD_GRAVITY = Gravity.CENTER_HORIZONTAL;
21
22 private static final String TAG = "ParallaxScrollView";
23
24 private static float PARALLAX_OFFSET_DEFAULT = 0.2f;
25
26 /**
27 * By how much should the background move to the foreground
28 */
29 private float mParallaxOffset = PARALLAX_OFFSET_DEFAULT;
30
31 private View mBackground;
32 private ObservableScrollView mScrollView;
33 private final ScrollCallbacks mScrollCallbacks = new ScrollCallbacks()
34 {
35 @Override
36 public void onScrollChanged(int l, int t, int oldl, int oldt)
37 {
38 requestLayout();
39 }
40 };
41
42 // Layout stuff
43
44 private int mBackgroundRight;
45 private int mBackgroundBottom;
46 /**
47 * Height of the Foreground ScrollView Content
48 */
49 private int mScrollContentHeight = 0;
50 /**
51 * Height of the ScrollView, should be the same as this view
52 */
53 private int mScrollViewHeight = 0;
54 /**
55 * The multipler by how much to move the background to the foreground
56 */
57 private float mScrollDiff = 0f;
58
59 public ParallaxScrollView(Context context, AttributeSet attrs, int defStyle)
60 {
61 super(context, attrs, defStyle);
62
63 TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
64 R.styleable.ParallaxScrollView, 0, 0);
65
66 try
67 {
68 setParallaxOffset(a.getFloat(R.styleable.ParallaxScrollView_parallexOffset,
69 PARALLAX_OFFSET_DEFAULT));
70 }
71 catch (Exception e)
72 {
73 }
74 finally
75 {
76 a.recycle();
77 }
78 }
79
80 public ParallaxScrollView(Context context, AttributeSet attrs)
81 {
82 this(context, attrs, 0);
83 }
84
85 public ParallaxScrollView(Context context)
86 {
87 this(context, null);
88 }
89
90 @Override
91 public void addView(View child)
92 {
93 if (getChildCount() > 1)
94 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
95
96 super.addView(child);
97 }
98
99 @Override
100 public void addView(View child, int index)
101 {
102 if (getChildCount() > 1)
103 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
104 super.addView(child, index);
105 }
106
107 @Override
108 public void addView(View child, int index, android.view.ViewGroup.LayoutParams params)
109 {
110 if (getChildCount() > 1)
111 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
112 super.addView(child, index, params);
113 }
114
115 @Override
116 public void addView(View child, int width, int height)
117 {
118 if (getChildCount() > 1)
119 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
120 super.addView(child, width, height);
121 }
122
123 @Override
124 public void addView(View child, android.view.ViewGroup.LayoutParams params)
125 {
126 if (getChildCount() > 1)
127 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
128 super.addView(child, params);
129 }
130
131 /**
132 * Set the offset
133 *
134 * @param offset
135 * a number greater than 0.0
136 */
137 public void setParallaxOffset(float offset)
138 {
139 // Make sure we only get to .05 of a floating number
140 offset = (float) Math.rint(offset * 100) / 100;
141 if (offset > 0.0)
142 mParallaxOffset = offset;
143 else
144 mParallaxOffset = PARALLAX_OFFSET_DEFAULT;
145
146 requestLayout();
147 }
148
149 /**
150 * Get the current offset
151 *
152 * @return
153 */
154 public float getParallaxOffset()
155 {
156 return mParallaxOffset;
157 }
158
159 /**
160 * Sort the views out after they have been inflated from a layout
161 */
162 @Override
163 protected void onFinishInflate()
164 {
165 super.onFinishInflate();
166
167 if (getChildCount() > 2)
168 {
169 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
170 }
171 organiseViews();
172 }
173
174 @Override
175 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
176 {
177 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
178
179 if (mScrollView != null)
180 {
181 measureChild(mScrollView, MeasureSpec.makeMeasureSpec(
182 MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST),
183 MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
184 MeasureSpec.AT_MOST));
185
186 mScrollContentHeight = mScrollView.getChildAt(0).getMeasuredHeight();
187 mScrollViewHeight = mScrollView.getMeasuredHeight();
188
189 }
190 if (mBackground != null)
191 {
192 int minHeight = 0;
193 minHeight = (int) (mScrollViewHeight + mParallaxOffset
194 * (mScrollContentHeight - mScrollViewHeight));
195 minHeight = Math.max(minHeight, MeasureSpec.getSize(heightMeasureSpec));
196
197 measureChild(mBackground, MeasureSpec.makeMeasureSpec(
198 MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY),
199 MeasureSpec.makeMeasureSpec(minHeight, MeasureSpec.EXACTLY));
200
201 mBackgroundRight = getLeft() + mBackground.getMeasuredWidth();
202 mBackgroundBottom = getTop() + mBackground.getMeasuredHeight();
203
204 mScrollDiff = (float) (mBackground.getMeasuredHeight() - mScrollViewHeight)
205 / (float) (mScrollContentHeight - mScrollViewHeight);
206 }
207
208 }
209
210 @Override
211 protected void onLayout(boolean changed, int left, int top, int right, int bottom)
212 {
213 final int parentLeft = getPaddingLeft();
214 final int parentRight = right - left - getPaddingRight();
215 final int parentTop = getPaddingTop();
216 final int parentBottom = bottom - top - getPaddingBottom();
217 if (mScrollView != null && mScrollView.getVisibility() != GONE)
218 {
219 final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mScrollView
220 .getLayoutParams();
221
222 final int width = mScrollView.getMeasuredWidth();
223 final int height = mScrollView.getMeasuredHeight();
224
225 int childLeft;
226 int childTop;
227
228 int gravity = lp.gravity;
229 if (gravity == -1)
230 {
231 gravity = DEFAULT_CHILD_GRAVITY;
232 }
233
234 final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
235 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
236
237 switch (horizontalGravity)
238 {
239 case Gravity.LEFT:
240 childLeft = parentLeft + lp.leftMargin;
241 break;
242 case Gravity.CENTER_HORIZONTAL:
243 childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin
244 - lp.rightMargin;
245 break;
246 case Gravity.RIGHT:
247 childLeft = parentRight - width - lp.rightMargin;
248 break;
249 default:
250 childLeft = parentLeft + lp.leftMargin;
251 }
252
253 switch (verticalGravity)
254 {
255 case Gravity.TOP:
256 childTop = parentTop + lp.topMargin;
257 break;
258 case Gravity.CENTER_VERTICAL:
259 childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin
260 - lp.bottomMargin;
261 break;
262 case Gravity.BOTTOM:
263 childTop = parentBottom - height - lp.bottomMargin;
264 break;
265 default:
266 childTop = parentTop + lp.topMargin;
267 }
268
269 mScrollView.layout(childLeft, childTop, childLeft + width, childTop + height);
270
271 }
272
273 if (mBackground != null)
274 {
275 final int scrollYCenterOffset = mScrollView.getScrollY();
276 final int offset = (int) (scrollYCenterOffset * mScrollDiff);
277 // Log.d(TAG, "Layout Scroll Y: " + scrollYCenterOffset +
278 // " ScrollDiff: " + mScrollDiff
279 // + " Background Offset:" + offset);
280 mBackground.layout(getLeft(), offset, mBackgroundRight, offset + mBackgroundBottom);
281 }
282 }
283
284 /**
285 * {@inheritDoc}
286 */
287 @Override
288 public LayoutParams generateLayoutParams(AttributeSet attrs)
289 {
290 return new FrameLayout.LayoutParams(getContext(), attrs);
291 }
292
293 @Override
294 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p)
295 {
296 return new FrameLayout.LayoutParams(p);
297 }
298
299 @Override
300 protected LayoutParams generateDefaultLayoutParams()
301 {
302 return new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
303 Gravity.CENTER_HORIZONTAL);
304 }
305
306 /**
307 * {@inheritDoc}
308 */
309 @Override
310 protected boolean checkLayoutParams(ViewGroup.LayoutParams p)
311 {
312 return p instanceof FrameLayout.LayoutParams;
313 }
314
315 /**
316 * Take the direct children and sort them
317 */
318 private void organiseViews()
319 {
320 if (getChildCount() <= 0) return;
321
322 if (getChildCount() == 1)
323 {
324 // Get the only child
325 final View forground = getChildAt(0);
326 organiseBackgroundView(null);
327 organiseForgroundView(forground);
328 }
329 else if (getChildCount() == 2)
330 {
331 final View background = getChildAt(0);
332 final View foreground = getChildAt(1);
333
334 organiseBackgroundView(background);
335 organiseForgroundView(foreground);
336 }
337 else
338 {
339 throw new IllegalStateException("ParallaxScrollView can host only two direct children");
340 }
341 }
342
343 private void organiseBackgroundView(final View background)
344 {
345 mBackground = background;
346 }
347
348 private void organiseForgroundView(final View forground)
349 {
350 final int insertPos = getChildCount() - 1;
351
352 // See if its a observable scroll view?
353 if (forground instanceof ObservableScrollView)
354 {
355 // Attach the callback to it.
356 mScrollView = (ObservableScrollView) forground;
357 }
358 else if (forground instanceof ViewGroup && !(forground instanceof ScrollView))
359 {
360 // See if it is a view group but not a scroll view and wrap it
361 // with an observable ScrollView
362 mScrollView = new ObservableScrollView(getContext(), null);
363 removeView(forground);
364 mScrollView.addView(forground);
365 addView(mScrollView, insertPos);
366 }
367 else if (forground instanceof ScrollView)
368 {
369 final View child;
370 if (((ScrollView) forground).getChildCount() > 0)
371 child = ((ScrollView) forground).getChildAt(0);
372 else
373 child = null;
374
375 mScrollView = new ObservableScrollView(getContext(), null);
376 removeView(forground);
377 if (child != null) mScrollView.addView(child);
378 addView(mScrollView, insertPos);
379 }
380 else if (forground instanceof View)
381 {
382 mScrollView = new ObservableScrollView(getContext(), null);
383 removeView(forground);
384 mScrollView.addView(forground);
385 addView(mScrollView, insertPos);
386
387 }
388 if (mScrollView != null)
389 {
390 mScrollView.setLayoutParams(forground.getLayoutParams());
391 mScrollView.setCallbacks(mScrollCallbacks);
392 mScrollView.setFillViewport(true);
393 }
394 }
395
396}