blob: 42de400df18f4fa633bd28e4aff3b443d1d30921 [file] [log] [blame]
Alexandre Lision450458a2013-11-22 11:33:12 -05001package org.sflphone.views;
2
3import android.content.Context;
4import android.content.res.TypedArray;
5import android.graphics.Canvas;
6import android.graphics.Paint;
7import android.graphics.PixelFormat;
8import android.graphics.Rect;
9import android.graphics.drawable.Drawable;
10import android.os.Parcel;
11import android.os.Parcelable;
12import android.support.v4.view.MotionEventCompat;
13import android.support.v4.view.ViewCompat;
14import android.support.v4.widget.ViewDragHelper;
15import android.util.AttributeSet;
16import android.util.Log;
17import android.view.MotionEvent;
18import android.view.SoundEffectConstants;
19import android.view.View;
20import android.view.ViewConfiguration;
21import android.view.ViewGroup;
22import android.view.accessibility.AccessibilityEvent;
23
24public class SlidingUpPanelLayout extends ViewGroup {
25
26 private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
27
28 /**
29 * Default peeking out panel height
30 */
31 private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
32
33 /**
34 * Default height of the shadow above the peeking out panel
35 */
36 private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
37
38 /**
39 * If no fade color is given by default it will fade to 80% gray.
40 */
41 private static final int DEFAULT_FADE_COLOR = 0x99000000;
42
43 /**
44 * Minimum velocity that will be detected as a fling
45 */
46 private static final int MIN_FLING_VELOCITY = 400; // dips per second
47
48 /**
49 * The fade color used for the panel covered by the slider. 0 = no fading.
50 */
51 private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
52
53 /**
54 * The paint used to dim the main layout when sliding
55 */
56 private final Paint mCoveredFadePaint = new Paint();
57
58 /**
59 * Drawable used to draw the shadow between panes.
60 */
61 private Drawable mShadowDrawable;
62
63 /**
64 * The size of the overhang in pixels.
65 */
66 private int mPanelHeight;
67
68 /**
69 * The size of the shadow in pixels.
70 */
71 private final int mShadowHeight;
72
73 /**
74 * True if a panel can slide with the current measurements
75 */
76 private boolean mCanSlide;
77
78 /**
79 * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be used for dragging.
80 */
81 private View mDragView;
82
83 /**
84 * The child view that can slide, if any.
85 */
86 private View mSlideableView;
87
88 /**
89 * How far the panel is offset from its expanded position. range [0, 1] where 0 = expanded, 1 = collapsed.
90 */
91 private float mSlideOffset;
92
93 /**
94 * How far in pixels the slideable panel may move.
95 */
96 private int mSlideRange;
97
98 /**
99 * A panel view is locked into internal scrolling or another condition that is preventing a drag.
100 */
101 private boolean mIsUnableToDrag;
102
103 /**
104 * Flag indicating that sliding feature is enabled\disabled
105 */
106 private boolean mIsSlidingEnabled;
107
108 /**
109 * Flag indicating if a drag view can have its own touch events. If set to true, a drag view can scroll horizontally and have its own click
110 * listener.
111 *
112 * Default is set to false.
113 */
114 private boolean mIsUsingDragViewTouchEvents;
115
116 /**
117 * Threshold to tell if there was a scroll touch event.
118 */
119 private int mScrollTouchSlop;
120
121 private float mInitialMotionX;
122 private float mInitialMotionY;
123 private boolean mDragViewHit;
124 private float mAnchorPoint = 0.f;
125
126 private PanelSlideListener mPanelSlideListener;
127
128 private final ViewDragHelper mDragHelper;
129
130 /**
131 * Stores whether or not the pane was expanded the last time it was slideable. If expand/collapse operations are invoked this state is modified.
132 * Used by instance state save/restore.
133 */
134 private boolean mPreservedExpandedState;
135 private boolean mFirstLayout = true;
136
137 private final Rect mTmpRect = new Rect();
138
139 /**
140 * Listener for monitoring events about sliding panes.
141 */
142 public interface PanelSlideListener {
143 /**
144 * Called when a sliding pane's position changes.
145 *
146 * @param panel
147 * The child view that was moved
148 * @param slideOffset
149 * The new offset of this sliding pane within its range, from 0-1
150 */
151 public void onPanelSlide(View panel, float slideOffset);
152
153 /**
154 * Called when a sliding pane becomes slid completely collapsed. The pane may or may not be interactive at this point depending on if it's
155 * shown or hidden
156 *
157 * @param panel
158 * The child view that was slid to an collapsed position, revealing other panes
159 */
160 public void onPanelCollapsed(View panel);
161
162 /**
163 * Called when a sliding pane becomes slid completely expanded. The pane is now guaranteed to be interactive. It may now obscure other views
164 * in the layout.
165 *
166 * @param panel
167 * The child view that was slid to a expanded position
168 */
169 public void onPanelExpanded(View panel);
170
171 public void onPanelAnchored(View panel);
172 }
173
174 /**
175 * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset of the listener methods you can extend this instead of
176 * implement the full interface.
177 */
178 public static class SimplePanelSlideListener implements PanelSlideListener {
179 @Override
180 public void onPanelSlide(View panel, float slideOffset) {
181 }
182
183 @Override
184 public void onPanelCollapsed(View panel) {
185 }
186
187 @Override
188 public void onPanelExpanded(View panel) {
189 }
190
191 @Override
192 public void onPanelAnchored(View panel) {
193 }
194 }
195
196 public SlidingUpPanelLayout(Context context) {
197 this(context, null);
198 }
199
200 public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
201 this(context, attrs, 0);
202 }
203
204 public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
205 super(context, attrs, defStyle);
206
207 final float density = context.getResources().getDisplayMetrics().density;
208 mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
209 mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
210
211 setWillNotDraw(false);
212
213 mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
214 mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
215
216 mCanSlide = true;
217 mIsSlidingEnabled = true;
218
219 setCoveredFadeColor(DEFAULT_FADE_COLOR);
220
221 ViewConfiguration vc = ViewConfiguration.get(context);
222 mScrollTouchSlop = vc.getScaledTouchSlop();
223 }
224
225 /**
226 * Set the color used to fade the pane covered by the sliding pane out when the pane will become fully covered in the expanded state.
227 *
228 * @param color
229 * An ARGB-packed color value
230 */
231 public void setCoveredFadeColor(int color) {
232 mCoveredFadeColor = color;
233 invalidate();
234 }
235
236 /**
237 * @return The ARGB-packed color value used to fade the fixed pane
238 */
239 public int getCoveredFadeColor() {
240 return mCoveredFadeColor;
241 }
242
243 /**
244 * Set the collapsed panel height in pixels
245 *
246 * @param val
247 * A height in pixels
248 */
249 public void setPanelHeight(int val) {
250 mPanelHeight = val;
251 requestLayout();
252 }
253
254 /**
255 * @return The current collapsed panel height
256 */
257 public int getPanelHeight() {
258 return mPanelHeight;
259 }
260
261 public void setPanelSlideListener(PanelSlideListener listener) {
262 mPanelSlideListener = listener;
263 }
264
265 /**
266 * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
267 *
268 * @param dragView
269 * A view that will be used to drag the panel.
270 */
271 public void setDragView(View dragView) {
272 mDragView = dragView;
273 }
274
275 /**
276 * Set an anchor point where the panel can stop during sliding
277 *
278 * @param anchorPoint
279 * A value between 0 and 1, determining the position of the anchor point starting from the top of the layout.
280 */
281 public void setAnchorPoint(float anchorPoint) {
282 if (anchorPoint > 0 && anchorPoint < 1)
283 mAnchorPoint = anchorPoint;
284 }
285
286 /**
287 * Set the shadow for the sliding panel
288 *
289 */
290 public void setShadowDrawable(Drawable drawable) {
291 mShadowDrawable = drawable;
292 }
293
294 void dispatchOnPanelSlide(View panel) {
295 if (mPanelSlideListener != null) {
296 mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
297 }
298 }
299
300 void dispatchOnPanelExpanded(View panel) {
301 if (mPanelSlideListener != null) {
302 mPanelSlideListener.onPanelExpanded(panel);
303 }
304 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
305 }
306
307 void dispatchOnPanelCollapsed(View panel) {
308 if (mPanelSlideListener != null) {
309 mPanelSlideListener.onPanelCollapsed(panel);
310 }
311 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
312 }
313
314 void dispatchOnPanelAnchored(View panel) {
315 if (mPanelSlideListener != null) {
316 mPanelSlideListener.onPanelAnchored(panel);
317 }
318 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
319 }
320
321 void updateObscuredViewVisibility() {
322 if (getChildCount() == 0) {
323 return;
324 }
325 final int leftBound = getPaddingLeft();
326 final int rightBound = getWidth() - getPaddingRight();
327 final int topBound = getPaddingTop();
328 final int bottomBound = getHeight() - getPaddingBottom();
329 final int left;
330 final int right;
331 final int top;
332 final int bottom;
333 if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
334 left = mSlideableView.getLeft();
335 right = mSlideableView.getRight();
336 top = mSlideableView.getTop();
337 bottom = mSlideableView.getBottom();
338 } else {
339 left = right = top = bottom = 0;
340 }
341 View child = getChildAt(0);
342 final int clampedChildLeft = Math.max(leftBound, child.getLeft());
343 final int clampedChildTop = Math.max(topBound, child.getTop());
344 final int clampedChildRight = Math.min(rightBound, child.getRight());
345 final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
346 final int vis;
347 if (clampedChildLeft >= left && clampedChildTop >= top && clampedChildRight <= right && clampedChildBottom <= bottom) {
348 vis = INVISIBLE;
349 } else {
350 vis = VISIBLE;
351 }
352 child.setVisibility(vis);
353 }
354
355 void setAllChildrenVisible() {
356 for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
357 final View child = getChildAt(i);
358 if (child.getVisibility() == INVISIBLE) {
359 child.setVisibility(VISIBLE);
360 }
361 }
362 }
363
364 private static boolean hasOpaqueBackground(View v) {
365 final Drawable bg = v.getBackground();
366 if (bg != null) {
367 return bg.getOpacity() == PixelFormat.OPAQUE;
368 }
369 return false;
370 }
371
372 @Override
373 protected void onAttachedToWindow() {
374 super.onAttachedToWindow();
375 mFirstLayout = true;
376 }
377
378 @Override
379 protected void onDetachedFromWindow() {
380 super.onDetachedFromWindow();
381 mFirstLayout = true;
382 }
383
384 @Override
385 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
386 final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
387 final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
388 final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
389 final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
390
391 if (widthMode != MeasureSpec.EXACTLY) {
392 throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
393 } else if (heightMode != MeasureSpec.EXACTLY) {
394 throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
395 }
396
397 int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
398 int panelHeight = mPanelHeight;
399
400 final int childCount = getChildCount();
401
402 if (childCount > 2) {
403 Log.e(TAG, "onMeasure: More than two child views are not supported.");
404 } else if (getChildAt(1).getVisibility() == GONE) {
405 panelHeight = 0;
406 }
407
408 // We'll find the current one below.
409 mSlideableView = null;
410 mCanSlide = false;
411
412 // First pass. Measure based on child LayoutParams width/height.
413 for (int i = 0; i < childCount; i++) {
414 final View child = getChildAt(i);
415 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
416
417 int height = layoutHeight;
418 if (child.getVisibility() == GONE) {
419 lp.dimWhenOffset = false;
420 continue;
421 }
422
423 if (i == 1) {
424 lp.slideable = true;
425 lp.dimWhenOffset = true;
426 mSlideableView = child;
427 mCanSlide = true;
428 } else {
429 height -= panelHeight;
430 }
431
432 int childWidthSpec;
433 if (lp.width == LayoutParams.WRAP_CONTENT) {
434 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
435 } else if (lp.width == LayoutParams.MATCH_PARENT) {
436 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
437 } else {
438 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
439 }
440
441 int childHeightSpec;
442 if (lp.height == LayoutParams.WRAP_CONTENT) {
443 childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
444 } else if (lp.height == LayoutParams.MATCH_PARENT) {
445 childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
446 } else {
447 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
448 }
449
450 child.measure(childWidthSpec, childHeightSpec);
451 }
452
453 setMeasuredDimension(widthSize, heightSize);
454 }
455
456 @Override
457 protected void onLayout(boolean changed, int l, int t, int r, int b) {
458 final int paddingLeft = getPaddingLeft();
459 final int paddingTop = getPaddingTop();
460
461 final int childCount = getChildCount();
462 int yStart = paddingTop;
463 int nextYStart = yStart;
464
465 if (mFirstLayout) {
466 mSlideOffset = mCanSlide && mPreservedExpandedState ? 0.f : 1.f;
467 }
468
469 for (int i = 0; i < childCount; i++) {
470 final View child = getChildAt(i);
471
472 if (child.getVisibility() == GONE) {
473 continue;
474 }
475
476 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
477
478 int childHeight = child.getMeasuredHeight();
479
480 if (lp.slideable) {
481 mSlideRange = childHeight - mPanelHeight;
482 yStart += (int) (mSlideRange * mSlideOffset);
483 } else {
484 yStart = nextYStart;
485 }
486
487 final int childTop = yStart;
488 final int childBottom = childTop + childHeight;
489 final int childLeft = paddingLeft;
490 final int childRight = childLeft + child.getMeasuredWidth();
491 child.layout(childLeft, childTop, childRight, childBottom);
492
493 nextYStart += child.getHeight();
494 }
495
496 if (mFirstLayout) {
497 updateObscuredViewVisibility();
498 }
499
500 mFirstLayout = false;
501 }
502
503 @Override
504 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
505 super.onSizeChanged(w, h, oldw, oldh);
506 // Recalculate sliding panes and their details
507 if (h != oldh) {
508 mFirstLayout = true;
509 }
510 }
511
512 /**
513 * Set sliding enabled flag
514 *
515 * @param enabled
516 * flag value
517 */
518 public void setSlidingEnabled(boolean enabled) {
519 mIsSlidingEnabled = enabled;
520 }
521
522 /**
523 * Set if the drag view can have its own touch events. If set to true, a drag view can scroll horizontally and have its own click listener.
524 *
525 * Default is set to false.
526 */
527 public void setEnableDragViewTouchEvents(boolean enabled) {
528 mIsUsingDragViewTouchEvents = enabled;
529 }
530
531 private boolean isDragViewHit(int x, int y) {
532 View v = mDragView != null ? mDragView : mSlideableView;
533 if (v == null)
534 return false;
535 int[] viewLocation = new int[2];
536 v.getLocationOnScreen(viewLocation);
537 int[] parentLocation = new int[2];
538 this.getLocationOnScreen(parentLocation);
539 int screenX = parentLocation[0] + x;
540 int screenY = parentLocation[1] + y;
541 return screenX >= viewLocation[0] && screenX < viewLocation[0] + v.getWidth() && screenY >= viewLocation[1]
542 && screenY < viewLocation[1] + v.getHeight();
543 }
544
545 @Override
546 public void requestChildFocus(View child, View focused) {
547 super.requestChildFocus(child, focused);
548 if (!isInTouchMode() && !mCanSlide) {
549 mPreservedExpandedState = child == mSlideableView;
550 }
551 }
552
553 @Override
554 public boolean onInterceptTouchEvent(MotionEvent ev) {
555 final int action = MotionEventCompat.getActionMasked(ev);
556
557 if (!mCanSlide || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
558 mDragHelper.cancel();
559 return super.onInterceptTouchEvent(ev);
560 }
561
562 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
563 mDragHelper.cancel();
564 return false;
565 }
566
567 final float x = ev.getX();
568 final float y = ev.getY();
569 boolean interceptTap = false;
570
571 switch (action) {
572 case MotionEvent.ACTION_DOWN: {
573 mIsUnableToDrag = false;
574 mInitialMotionX = x;
575 mInitialMotionY = y;
576 mDragViewHit = isDragViewHit((int) x, (int) y);
577
578 if (mDragViewHit && !mIsUsingDragViewTouchEvents) {
579 interceptTap = true;
580 }
581 break;
582 }
583
584 case MotionEvent.ACTION_MOVE: {
585 final float adx = Math.abs(x - mInitialMotionX);
586 final float ady = Math.abs(y - mInitialMotionY);
587 final int dragSlop = mDragHelper.getTouchSlop();
588
589 // Handle any horizontal scrolling on the drag view.
590 if (mIsUsingDragViewTouchEvents) {
591 if (adx > mScrollTouchSlop && ady < mScrollTouchSlop) {
592 return super.onInterceptTouchEvent(ev);
593 }
594 // Intercept the touch if the drag view has any vertical scroll.
595 // onTouchEvent will determine if the view should drag vertically.
596 else if (ady > mScrollTouchSlop) {
597 interceptTap = mDragViewHit;
598 }
599 }
600
601 if (ady > dragSlop && adx > ady) {
602 mDragHelper.cancel();
603 mIsUnableToDrag = true;
604 return false;
605 }
606 break;
607 }
608 }
609
610 final boolean interceptForDrag = mDragViewHit && mDragHelper.shouldInterceptTouchEvent(ev);
611
612 return interceptForDrag || interceptTap;
613 }
614
615 @Override
616 public boolean onTouchEvent(MotionEvent ev) {
617 if (!mCanSlide || !mIsSlidingEnabled) {
618 return super.onTouchEvent(ev);
619 }
620
621 mDragHelper.processTouchEvent(ev);
622
623 final int action = ev.getAction();
624 boolean wantTouchEvents = true;
625
626 switch (action & MotionEventCompat.ACTION_MASK) {
627 case MotionEvent.ACTION_DOWN: {
628 final float x = ev.getX();
629 final float y = ev.getY();
630 mInitialMotionX = x;
631 mInitialMotionY = y;
632 break;
633 }
634
635 case MotionEvent.ACTION_UP: {
636 final float x = ev.getX();
637 final float y = ev.getY();
638 final float dx = x - mInitialMotionX;
639 final float dy = y - mInitialMotionY;
640 final int slop = mDragHelper.getTouchSlop();
641 if (dx * dx + dy * dy < slop * slop && isDragViewHit((int) x, (int) y)) {
642 View v = mDragView != null ? mDragView : mSlideableView;
643 v.playSoundEffect(SoundEffectConstants.CLICK);
644 if (!isExpanded() && !isAnchored()) {
645 expandPane(mSlideableView, 0, mAnchorPoint);
646 } else {
647 collapsePane();
648 }
649 break;
650 }
651 break;
652 }
653 }
654
655 return wantTouchEvents;
656 }
657
658 private boolean expandPane(View pane, int initialVelocity, float mSlideOffset) {
659 if (mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity)) {
660 mPreservedExpandedState = true;
661 return true;
662 }
663 return false;
664 }
665
666 private boolean collapsePane(View pane, int initialVelocity) {
667 if (mFirstLayout || smoothSlideTo(1.f, initialVelocity)) {
668 mPreservedExpandedState = false;
669 return true;
670 }
671 return false;
672 }
673
674 /**
675 * Collapse the sliding pane if it is currently slideable. If first layout has already completed this will animate.
676 *
677 * @return true if the pane was slideable and is now collapsed/in the process of collapsing
678 */
679 public boolean collapsePane() {
680 return collapsePane(mSlideableView, 0);
681 }
682
683 /**
684 * Expand the sliding pane if it is currently slideable. If first layout has already completed this will animate.
685 *
686 * @return true if the pane was slideable and is now expanded/in the process of expading
687 */
688 public boolean expandPane() {
689 return expandPane(0);
690 }
691
692 /**
693 * Partially expand the sliding pane up to a specific offset
694 *
695 * @param mSlideOffset
696 * Value between 0 and 1, where 0 is completely expanded.
697 * @return true if the pane was slideable and is now expanded/in the process of expading
698 */
699 public boolean expandPane(float mSlideOffset) {
700 if (!isPaneVisible()) {
701 showPane();
702 }
703 return expandPane(mSlideableView, 0, mSlideOffset);
704 }
705
706 /**
707 * Check if the layout is completely expanded.
708 *
709 * @return true if sliding panels are completely expanded
710 */
711 public boolean isExpanded() {
712 return mFirstLayout && mPreservedExpandedState || !mFirstLayout && mCanSlide && mSlideOffset == 0;
713 }
714
715 /**
716 * Check if the layout is anchored in an intermediate point.
717 *
718 * @return true if sliding panels are anchored
719 */
720 public boolean isAnchored() {
721 int anchoredTop = (int) (mAnchorPoint * mSlideRange);
722 return !mFirstLayout && mCanSlide && mSlideOffset == (float) anchoredTop / (float) mSlideRange;
723 }
724
725 /**
726 * Check if the content in this layout cannot fully fit side by side and therefore the content pane can be slid back and forth.
727 *
728 * @return true if content in this layout can be expanded
729 */
730 public boolean isSlideable() {
731 return mCanSlide;
732 }
733
734 public boolean isPaneVisible() {
735 if (getChildCount() < 2) {
736 return false;
737 }
738 View slidingPane = getChildAt(1);
739 return slidingPane.getVisibility() == View.VISIBLE;
740 }
741
742 public void showPane() {
743 if (getChildCount() < 2) {
744 return;
745 }
746 View slidingPane = getChildAt(1);
747 slidingPane.setVisibility(View.VISIBLE);
748 requestLayout();
749 }
750
751 public void hidePane() {
752 if (mSlideableView == null) {
753 return;
754 }
755 mSlideableView.setVisibility(View.GONE);
756 requestLayout();
757 }
758
759 private void onPanelDragged(int newTop) {
760 final int topBound = getPaddingTop();
761 mSlideOffset = (float) (newTop - topBound) / mSlideRange;
762 dispatchOnPanelSlide(mSlideableView);
763 }
764
765 @Override
766 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
767 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
768 boolean result;
769 final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
770
771 boolean drawScrim = false;
772
773 if (mCanSlide && !lp.slideable && mSlideableView != null) {
774 // Clip against the slider; no sense drawing what will immediately be covered.
775 canvas.getClipBounds(mTmpRect);
Alexandre Lision0936ba62013-11-25 10:04:56 -0500776 mTmpRect.bottom = (int) Math.min(mTmpRect.bottom, mSlideableView.getTop() + getResources().getDisplayMetrics().density * 68); // + 60
777 // cause of
778 // the
779 // rounded
780 // shape
781 // handle
Alexandre Lision450458a2013-11-22 11:33:12 -0500782 canvas.clipRect(mTmpRect);
783 if (mSlideOffset < 1) {
784 drawScrim = true;
785 }
786 }
787
788 result = super.drawChild(canvas, child, drawingTime);
789 canvas.restoreToCount(save);
790
791 if (drawScrim) {
792 final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
793 final int imag = (int) (baseAlpha * (1 - mSlideOffset));
794 final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
795 mCoveredFadePaint.setColor(color);
796 canvas.drawRect(mTmpRect, mCoveredFadePaint);
797 }
798
799 return result;
800 }
801
802 /**
803 * Smoothly animate mDraggingPane to the target X position within its range.
804 *
805 * @param slideOffset
806 * position to animate to
807 * @param velocity
808 * initial velocity in case of fling, or 0.
809 */
810 boolean smoothSlideTo(float slideOffset, int velocity) {
811 if (!mCanSlide) {
812 // Nothing to do.
813 return false;
814 }
815
816 final int topBound = getPaddingTop();
817 int y = (int) (topBound + slideOffset * mSlideRange);
818
819 if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
820 setAllChildrenVisible();
821 ViewCompat.postInvalidateOnAnimation(this);
822 return true;
823 }
824 return false;
825 }
826
827 @Override
828 public void computeScroll() {
829 if (mDragHelper.continueSettling(true)) {
830 if (!mCanSlide) {
831 mDragHelper.abort();
832 return;
833 }
834
835 ViewCompat.postInvalidateOnAnimation(this);
836 }
837 }
838
839 @Override
840 public void draw(Canvas c) {
841 super.draw(c);
842
843 if (mSlideableView == null) {
844 // No need to draw a shadow if we don't have one.
845 return;
846 }
847
848 final int right = mSlideableView.getRight();
849 final int top = mSlideableView.getTop() - mShadowHeight;
850 final int bottom = mSlideableView.getTop();
851 final int left = mSlideableView.getLeft();
852
853 if (mShadowDrawable != null) {
854 mShadowDrawable.setBounds(left, top, right, bottom);
855 mShadowDrawable.draw(c);
856 }
857 }
858
859 /**
860 * Tests scrollability within child views of v given a delta of dx.
861 *
862 * @param v
863 * View to test for horizontal scrollability
864 * @param checkV
865 * Whether the view v passed should itself be checked for scrollability (true), or just its children (false).
866 * @param dx
867 * Delta scrolled in pixels
868 * @param x
869 * X coordinate of the active touch point
870 * @param y
871 * Y coordinate of the active touch point
872 * @return true if child views of v can be scrolled by delta of dx.
873 */
874 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
875 if (v instanceof ViewGroup) {
876 final ViewGroup group = (ViewGroup) v;
877 final int scrollX = v.getScrollX();
878 final int scrollY = v.getScrollY();
879 final int count = group.getChildCount();
880 // Count backwards - let topmost views consume scroll distance first.
881 for (int i = count - 1; i >= 0; i--) {
882 final View child = group.getChildAt(i);
883 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop()
884 && y + scrollY < child.getBottom() && canScroll(child, true, dx, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) {
885 return true;
886 }
887 }
888 }
889 return checkV && ViewCompat.canScrollHorizontally(v, -dx);
890 }
891
892 @Override
893 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
894 return new LayoutParams();
895 }
896
897 @Override
898 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
899 return p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) : new LayoutParams(p);
900 }
901
902 @Override
903 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
904 return p instanceof LayoutParams && super.checkLayoutParams(p);
905 }
906
907 @Override
908 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
909 return new LayoutParams(getContext(), attrs);
910 }
911
912 @Override
913 protected Parcelable onSaveInstanceState() {
914 Parcelable superState = super.onSaveInstanceState();
915
916 SavedState ss = new SavedState(superState);
917 ss.isExpanded = isSlideable() ? isExpanded() : mPreservedExpandedState;
918
919 return ss;
920 }
921
922 @Override
923 protected void onRestoreInstanceState(Parcelable state) {
924 SavedState ss = (SavedState) state;
925 super.onRestoreInstanceState(ss.getSuperState());
926
927 if (ss.isExpanded) {
928 expandPane();
929 } else {
930 collapsePane();
931 }
932 mPreservedExpandedState = ss.isExpanded;
933 }
934
935 private class DragHelperCallback extends ViewDragHelper.Callback {
936
937 @Override
938 public boolean tryCaptureView(View child, int pointerId) {
939 if (mIsUnableToDrag) {
940 return false;
941 }
942
943 return ((LayoutParams) child.getLayoutParams()).slideable;
944 }
945
946 @Override
947 public void onViewDragStateChanged(int state) {
948 if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
949 if (mSlideOffset == 0) {
950 updateObscuredViewVisibility();
951 dispatchOnPanelExpanded(mSlideableView);
952 mPreservedExpandedState = true;
953 } else if (isAnchored()) {
954 updateObscuredViewVisibility();
955 dispatchOnPanelAnchored(mSlideableView);
956 mPreservedExpandedState = true;
957 } else {
958 dispatchOnPanelCollapsed(mSlideableView);
959 mPreservedExpandedState = false;
960 }
961 }
962 }
963
964 @Override
965 public void onViewCaptured(View capturedChild, int activePointerId) {
966 // Make all child views visible in preparation for sliding things around
967 setAllChildrenVisible();
968 }
969
970 @Override
971 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
972 onPanelDragged(top);
973 invalidate();
974 }
975
976 @Override
977 public void onViewReleased(View releasedChild, float xvel, float yvel) {
978 int top = getPaddingTop();
979
980 if (mAnchorPoint != 0) {
981 int anchoredTop = (int) (mAnchorPoint * mSlideRange);
982 float anchorOffset = (float) anchoredTop / (float) mSlideRange;
983
984 if (yvel > 0 || (yvel == 0 && mSlideOffset >= (1f + anchorOffset) / 2)) {
985 top += mSlideRange;
986 } else if (yvel == 0 && mSlideOffset < (1f + anchorOffset) / 2 && mSlideOffset >= anchorOffset / 2) {
987 top += mSlideRange * mAnchorPoint;
988 }
989
990 } else if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
991 top += mSlideRange;
992 }
993
994 mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
995 invalidate();
996 }
997
998 @Override
999 public int getViewVerticalDragRange(View child) {
1000 return mSlideRange;
1001 }
1002
1003 @Override
1004 public int clampViewPositionVertical(View child, int top, int dy) {
1005 final int topBound = getPaddingTop();
1006 final int bottomBound = topBound + mSlideRange;
1007
1008 final int newLeft = Math.min(Math.max(top, topBound), bottomBound);
1009
1010 return newLeft;
1011 }
1012
1013 }
1014
1015 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1016 private static final int[] ATTRS = new int[] { android.R.attr.layout_weight };
1017
1018 /**
1019 * True if this pane is the slideable pane in the layout.
1020 */
1021 boolean slideable;
1022
1023 /**
1024 * True if this view should be drawn dimmed when it's been offset from its default position.
1025 */
1026 boolean dimWhenOffset;
1027
1028 Paint dimPaint;
1029
1030 public LayoutParams() {
1031 super(MATCH_PARENT, MATCH_PARENT);
1032 }
1033
1034 public LayoutParams(int width, int height) {
1035 super(width, height);
1036 }
1037
1038 public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1039 super(source);
1040 }
1041
1042 public LayoutParams(MarginLayoutParams source) {
1043 super(source);
1044 }
1045
1046 public LayoutParams(LayoutParams source) {
1047 super(source);
1048 }
1049
1050 public LayoutParams(Context c, AttributeSet attrs) {
1051 super(c, attrs);
1052
1053 final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1054 a.recycle();
1055 }
1056
1057 }
1058
1059 static class SavedState extends BaseSavedState {
1060 boolean isExpanded;
1061
1062 SavedState(Parcelable superState) {
1063 super(superState);
1064 }
1065
1066 private SavedState(Parcel in) {
1067 super(in);
1068 isExpanded = in.readInt() != 0;
1069 }
1070
1071 @Override
1072 public void writeToParcel(Parcel out, int flags) {
1073 super.writeToParcel(out, flags);
1074 out.writeInt(isExpanded ? 1 : 0);
1075 }
1076
1077 public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
1078 @Override
1079 public SavedState createFromParcel(Parcel in) {
1080 return new SavedState(in);
1081 }
1082
1083 @Override
1084 public SavedState[] newArray(int size) {
1085 return new SavedState[size];
1086 }
1087 };
1088 }
1089}