OSDN Git Service

Migrate to AndroidX
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / slidinguppanel / SlidingUpPanelLayout.java
1 package org.lineageos.eleven.slidinguppanel;
2
3 import android.annotation.SuppressLint;
4 import android.content.Context;
5 import android.content.res.TypedArray;
6 import android.graphics.Canvas;
7 import android.graphics.Paint;
8 import android.graphics.PixelFormat;
9 import android.graphics.Rect;
10 import android.graphics.drawable.Drawable;
11 import android.os.Parcel;
12 import android.os.Parcelable;
13 import android.util.AttributeSet;
14 import android.view.Gravity;
15 import android.view.MotionEvent;
16 import android.view.View;
17 import android.view.ViewGroup;
18 import android.view.accessibility.AccessibilityEvent;
19
20 import androidx.core.view.MotionEventCompat;
21 import androidx.core.view.ViewCompat;
22
23 import org.lineageos.eleven.R;
24
25 public class SlidingUpPanelLayout extends ViewGroup {
26
27     private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
28
29     /**
30      * Default peeking out panel height
31      */
32     private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
33
34     /**
35      * Default anchor point height
36      */
37     private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %
38
39     /**
40      * Default initial state for the component
41      */
42     private static SlideState DEFAULT_SLIDE_STATE = SlideState.COLLAPSED;
43
44     /**
45      * Default height of the shadow above the peeking out panel
46      */
47     private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
48
49     /**
50      * If no fade color is given by default it will fade to 80% gray.
51      */
52     private static final int DEFAULT_FADE_COLOR = 0x99000000;
53
54     /**
55      * Whether we should hook up the drag view clickable state
56      */
57     private static final boolean DEFAULT_DRAG_VIEW_CLICKABLE = true;
58
59     /**
60      * Default Minimum velocity that will be detected as a fling
61      */
62     private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
63     /**
64      * Default is set to false because that is how it was written
65      */
66     private static final boolean DEFAULT_OVERLAY_FLAG = false;
67     /**
68      * Default attributes for layout
69      */
70     private static final int[] DEFAULT_ATTRS = new int[] {
71         android.R.attr.gravity
72     };
73
74     /**
75      * Minimum velocity that will be detected as a fling
76      */
77     private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
78
79     /**
80      * The fade color used for the panel covered by the slider. 0 = no fading.
81      */
82     private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
83
84     /**
85      * Default paralax length of the main view
86      */
87     private static final int DEFAULT_PARALAX_OFFSET = 0;
88
89     /**
90      * Default slide panel offset when collapsed
91      */
92     private static final int DEFAULT_SLIDE_PANEL_OFFSET = 0;
93
94     /**
95      * Default direct offset flag
96      */
97     private static final boolean DEFAULT_DIRECT_OFFSET_FLAG = false;
98
99     /**
100      * The paint used to dim the main layout when sliding
101      */
102     private final Paint mCoveredFadePaint = new Paint();
103
104     /**
105      * Drawable used to draw the shadow between panes.
106      */
107     private final Drawable mShadowDrawable;
108
109     /**
110      * The size of the overhang in pixels.
111      */
112     private int mPanelHeight = -1;
113
114     /**
115      * Determines how much to slide the panel off when expanded
116      */
117     private int mSlidePanelOffset = 0;
118
119     /**
120      * The size of the shadow in pixels.
121      */
122     private int mShadowHeight = -1;
123
124     /**
125      * Paralax offset
126      */
127     private int mParallaxOffset = -1;
128
129     /**
130      * Clamps the Main view to the slideable view
131      */
132     private boolean mDirectOffset = false;
133
134     /**
135      * True if the collapsed panel should be dragged up.
136      */
137     private boolean mIsSlidingUp;
138
139     /**
140      * Panel overlays the windows instead of putting it underneath it.
141      */
142     private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
143
144     /**
145      * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
146      * used for dragging.
147      */
148     private View mDragView;
149
150     /**
151      * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
152      * used for dragging.
153      */
154     private int mDragViewResId = -1;
155
156     /**
157      * Whether clicking on the drag view will expand/collapse
158      */
159     private boolean mDragViewClickable = DEFAULT_DRAG_VIEW_CLICKABLE;
160
161     /**
162      * The child view that can slide, if any.
163      */
164     private View mSlideableView;
165
166     /**
167      * The main view
168      */
169     private View mMainView;
170
171     /**
172      * The background view
173      */
174     private View mBackgroundView;
175
176     /**
177      * Current state of the slideable view.
178      */
179     private enum SlideState {
180         EXPANDED,
181         COLLAPSED,
182         ANCHORED,
183         HIDDEN,
184         DRAGGING
185     }
186     private SlideState mSlideState = SlideState.COLLAPSED;
187
188     /**
189      * How far the panel is offset from its expanded position.
190      * range [0, 1] where 0 = collapsed, 1 = expanded.
191      */
192     private float mSlideOffset;
193
194     /**
195      * How far in pixels the slideable panel may move.
196      */
197     private int mSlideRange;
198
199     /**
200      * A panel view is locked into internal scrolling or another condition that
201      * is preventing a drag.
202      */
203     private boolean mIsUnableToDrag;
204
205     /**
206      * Flag indicating that sliding feature is enabled\disabled
207      */
208     private boolean mIsSlidingEnabled;
209
210     /**
211      * Flag indicating if a drag view can have its own touch events.  If set
212      * to true, a drag view can scroll horizontally and have its own click listener.
213      *
214      * Default is set to false.
215      */
216     private boolean mIsUsingDragViewTouchEvents;
217
218     private float mInitialMotionX;
219     private float mInitialMotionY;
220     private float mAnchorPoint = 1.f;
221
222     private PanelSlideListener mPanelSlideListener;
223
224     private final ViewDragHelper mDragHelper;
225
226     /**
227      * Stores whether or not the pane was expanded the last time it was slideable.
228      * If expand/collapse operations are invoked this state is modified. Used by
229      * instance state save/restore.
230      */
231     private boolean mFirstLayout = true;
232
233     private final Rect mTmpRect = new Rect();
234
235     /**
236      * Listener for monitoring events about sliding panes.
237      */
238     public interface PanelSlideListener {
239         /**
240          * Called when a sliding pane's position changes.
241          * @param panel The child view that was moved
242          * @param slideOffset The new offset of this sliding pane within its range, from 0-1
243          */
244         public void onPanelSlide(View panel, float slideOffset);
245         /**
246          * Called when a sliding panel becomes slid completely collapsed.
247          * @param panel The child view that was slid to an collapsed position
248          */
249         public void onPanelCollapsed(View panel);
250
251         /**
252          * Called when a sliding panel becomes slid completely expanded.
253          * @param panel The child view that was slid to a expanded position
254          */
255         public void onPanelExpanded(View panel);
256
257         /**
258          * Called when a sliding panel becomes anchored.
259          * @param panel The child view that was slid to a anchored position
260          */
261         public void onPanelAnchored(View panel);
262
263         /**
264          * Called when a sliding panel becomes completely hidden.
265          * @param panel The child view that was slid to a hidden position
266          */
267         public void onPanelHidden(View panel);
268     }
269
270     /**
271      * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
272      * of the listener methods you can extend this instead of implement the full interface.
273      */
274     public static class SimplePanelSlideListener implements PanelSlideListener {
275         @Override
276         public void onPanelSlide(View panel, float slideOffset) {
277         }
278         @Override
279         public void onPanelCollapsed(View panel) {
280         }
281         @Override
282         public void onPanelExpanded(View panel) {
283         }
284         @Override
285         public void onPanelAnchored(View panel) {
286         }
287         @Override
288         public void onPanelHidden(View panel) {
289         }
290     }
291
292     public SlidingUpPanelLayout(Context context) {
293         this(context, null);
294     }
295
296     public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
297         this(context, attrs, 0);
298     }
299
300     public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
301         super(context, attrs, defStyle);
302
303         if(isInEditMode()) {
304             mShadowDrawable = null;
305             mDragHelper = null;
306             return;
307         }
308
309         if (attrs != null) {
310             TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
311
312             if (defAttrs != null) {
313                 int gravity = defAttrs.getInt(0, Gravity.NO_GRAVITY);
314                 if (gravity != Gravity.TOP && gravity != Gravity.BOTTOM) {
315                     throw new IllegalArgumentException("gravity must be set to either top or bottom");
316                 }
317                 mIsSlidingUp = gravity == Gravity.BOTTOM;
318             }
319
320             defAttrs.recycle();
321
322             TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
323
324             if (ta != null) {
325                 mPanelHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_panelHeight, -1);
326                 mSlidePanelOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_slidePanelOffset, DEFAULT_SLIDE_PANEL_OFFSET);
327                 mShadowHeight = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_shadowHeight, -1);
328                 mParallaxOffset = ta.getDimensionPixelSize(R.styleable.SlidingUpPanelLayout_paralaxOffset, -1);
329                 mDirectOffset = ta.getBoolean(R.styleable.SlidingUpPanelLayout_directOffset,DEFAULT_DIRECT_OFFSET_FLAG);
330
331                 mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
332                 mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_fadeColor, DEFAULT_FADE_COLOR);
333
334                 mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_dragView, -1);
335                 mDragViewClickable = ta.getBoolean(R.styleable.SlidingUpPanelLayout_dragViewClickable, DEFAULT_DRAG_VIEW_CLICKABLE);
336
337                 mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_overlay,DEFAULT_OVERLAY_FLAG);
338
339                 mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_anchorPoint, DEFAULT_ANCHOR_POINT);
340
341                 mSlideState = SlideState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_initialState, DEFAULT_SLIDE_STATE.ordinal())];
342             }
343
344             ta.recycle();
345         }
346
347         final float density = context.getResources().getDisplayMetrics().density;
348         if (mPanelHeight == -1) {
349             mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
350         }
351         if (mShadowHeight == -1) {
352             mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
353         }
354         if (mParallaxOffset == -1) {
355             mParallaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density);
356         }
357         // If the shadow height is zero, don't show the shadow
358         if (mShadowHeight > 0) {
359             if (mIsSlidingUp) {
360                 mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
361             } else {
362                 mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
363             }
364
365         } else {
366             mShadowDrawable = null;
367         }
368
369         setWillNotDraw(false);
370
371         mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
372         mDragHelper.setMinVelocity(mMinFlingVelocity * density);
373
374         mIsSlidingEnabled = true;
375     }
376
377     /**
378      * Set the Drag View after the view is inflated
379      */
380     @Override
381     protected void onFinishInflate() {
382         super.onFinishInflate();
383         if (mDragViewResId != -1) {
384             setDragView(findViewById(mDragViewResId));
385         }
386     }
387
388     /**
389      * Set the color used to fade the pane covered by the sliding pane out when the pane
390      * will become fully covered in the expanded state.
391      *
392      * @param color An ARGB-packed color value
393      */
394     public void setCoveredFadeColor(int color) {
395         mCoveredFadeColor = color;
396         invalidate();
397     }
398
399     /**
400      * @return The ARGB-packed color value used to fade the fixed pane
401      */
402     public int getCoveredFadeColor() {
403         return mCoveredFadeColor;
404     }
405
406     /**
407      * Set sliding enabled flag
408      * @param enabled flag value
409      */
410     public void setSlidingEnabled(boolean enabled) {
411         mIsSlidingEnabled = enabled;
412     }
413
414     public boolean isSlidingEnabled() {
415         return mIsSlidingEnabled && mSlideableView != null;
416     }
417
418     /**
419      * Set the collapsed panel height in pixels
420      *
421      * @param val A height in pixels
422      */
423     public void setPanelHeight(int val) {
424         mPanelHeight = val;
425         requestLayout();
426     }
427
428     /**
429      * @return The current collapsed panel height
430      */
431     public int getPanelHeight() {
432         return mPanelHeight;
433     }
434
435     /**
436      * Sets the panel offset when collapsed so you can exit
437      * the boundaries of the top of the screen
438      *
439      * @param val Offset in pixels
440      */
441     public void setSlidePanelOffset(int val) {
442         mSlidePanelOffset = val;
443         requestLayout();
444     }
445
446     /**
447      * @return The current paralax offset
448      */
449     public int getCurrentParalaxOffset() {
450         if (mParallaxOffset < 0) {
451             return 0;
452         }
453
454         return (int)(mParallaxOffset * getDirectionalSlideOffset());
455     }
456
457     /**
458      * @return The directional slide offset
459      */
460     protected float getDirectionalSlideOffset() {
461         return mIsSlidingUp ? -mSlideOffset : mSlideOffset;
462     }
463
464     /**
465      * Sets the panel slide listener
466      * @param listener
467      */
468     public void setPanelSlideListener(PanelSlideListener listener) {
469         mPanelSlideListener = listener;
470     }
471
472     /**
473      * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
474      *
475      * @param dragView A view that will be used to drag the panel.
476      */
477     public void setDragView(View dragView) {
478         if (mDragView != null && mDragViewClickable) {
479             mDragView.setOnClickListener(null);
480         }
481         mDragView = dragView;
482         if (mDragView != null) {
483             mDragView.setClickable(true);
484             mDragView.setFocusable(false);
485             mDragView.setFocusableInTouchMode(false);
486             if (mDragViewClickable) {
487                 mDragView.setOnClickListener(new OnClickListener() {
488                     @Override
489                     public void onClick(View v) {
490                         if (!isEnabled()) return;
491                         if (!isPanelExpanded() && !isPanelAnchored()) {
492                             expandPanel(mAnchorPoint);
493                         } else {
494                             collapsePanel();
495                         }
496                     }
497                 });
498             }
499         }
500     }
501
502     /**
503      * Set an anchor point where the panel can stop during sliding
504      *
505      * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
506      *                    starting from the top of the layout.
507      */
508     public void setAnchorPoint(float anchorPoint) {
509         if (anchorPoint > 0 && anchorPoint <= 1) {
510             mAnchorPoint = anchorPoint;
511         }
512     }
513
514     /**
515      * Gets the currently set anchor point
516      *
517      * @return the currently set anchor point
518      */
519     public float getAnchorPoint() {
520         return mAnchorPoint;
521     }
522
523     /**
524      * Sets whether or not the panel overlays the content
525      * @param overlayed
526      */
527     public void setOverlayed(boolean overlayed) {
528         mOverlayContent = overlayed;
529     }
530
531     /**
532      * Check if the panel is set as an overlay.
533      */
534     public boolean isOverlayed() {
535         return mOverlayContent;
536     }
537
538     void dispatchOnPanelSlide(View panel) {
539         if (mPanelSlideListener != null) {
540             mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
541         }
542     }
543
544     void dispatchOnPanelExpanded(View panel) {
545         if (mPanelSlideListener != null) {
546             mPanelSlideListener.onPanelExpanded(panel);
547         }
548         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
549     }
550
551     void dispatchOnPanelCollapsed(View panel) {
552         if (mPanelSlideListener != null) {
553             mPanelSlideListener.onPanelCollapsed(panel);
554         }
555         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
556     }
557
558     void dispatchOnPanelAnchored(View panel) {
559         if (mPanelSlideListener != null) {
560             mPanelSlideListener.onPanelAnchored(panel);
561         }
562         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
563     }
564
565     void dispatchOnPanelHidden(View panel) {
566         if (mPanelSlideListener != null) {
567             mPanelSlideListener.onPanelHidden(panel);
568         }
569         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
570     }
571
572     void updateObscuredViewVisibility() {
573         if (getChildCount() == 0) {
574             return;
575         }
576         final int leftBound = getPaddingLeft();
577         final int rightBound = getWidth() - getPaddingRight();
578         final int topBound = getPaddingTop();
579         final int bottomBound = getHeight() - getPaddingBottom();
580         final int left;
581         final int right;
582         final int top;
583         final int bottom;
584         if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
585             left = mSlideableView.getLeft();
586             right = mSlideableView.getRight();
587             top = mSlideableView.getTop();
588             bottom = mSlideableView.getBottom();
589         } else {
590             left = right = top = bottom = 0;
591         }
592         View child = mMainView;
593         final int clampedChildLeft = Math.max(leftBound, child.getLeft());
594         final int clampedChildTop = Math.max(topBound, child.getTop());
595         final int clampedChildRight = Math.min(rightBound, child.getRight());
596         final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
597         final int vis;
598         if (clampedChildLeft >= left && clampedChildTop >= top &&
599                 clampedChildRight <= right && clampedChildBottom <= bottom) {
600             vis = INVISIBLE;
601         } else {
602             vis = VISIBLE;
603         }
604         child.setVisibility(vis);
605     }
606
607     void setAllChildrenVisible() {
608         for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
609             final View child = getChildAt(i);
610             if (child.getVisibility() == INVISIBLE) {
611                 child.setVisibility(VISIBLE);
612             }
613         }
614     }
615
616     private static boolean hasOpaqueBackground(View v) {
617         final Drawable bg = v.getBackground();
618         return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
619     }
620
621     @Override
622     protected void onAttachedToWindow() {
623         super.onAttachedToWindow();
624         mFirstLayout = true;
625     }
626
627     @Override
628     protected void onDetachedFromWindow() {
629         super.onDetachedFromWindow();
630         mFirstLayout = true;
631     }
632
633     @Override
634     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
635         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
636         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
637         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
638         final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
639
640         if (widthMode != MeasureSpec.EXACTLY) {
641             throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
642         } else if (heightMode != MeasureSpec.EXACTLY) {
643             throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
644         }
645
646         final int childCount = getChildCount();
647
648         if (childCount != 2 && childCount != 3) {
649             throw new IllegalStateException("Sliding up panel layout must have exactly 2 or 3 children!");
650         }
651
652         if (childCount == 2) {
653             mMainView = getChildAt(0);
654             mSlideableView = getChildAt(1);
655         } else {
656             mBackgroundView = getChildAt(0);
657             mMainView = getChildAt(1);
658             mSlideableView = getChildAt(2);
659         }
660
661         if (mDragView == null) {
662             setDragView(mSlideableView);
663         }
664
665         // If the sliding panel is not visible, then put the whole view in the hidden state
666         if (mSlideableView.getVisibility() == GONE) {
667             mSlideState = SlideState.HIDDEN;
668         }
669
670         int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
671
672         // First pass. Measure based on child LayoutParams width/height.
673         for (int i = 0; i < childCount; i++) {
674             final View child = getChildAt(i);
675             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
676
677             // We always measure the sliding panel in order to know it's height (needed for show panel)
678             if (child.getVisibility() == GONE && child == mMainView) {
679                 continue;
680             }
681
682             int height = layoutHeight;
683             if (child == mMainView && !mOverlayContent && mSlideState != SlideState.HIDDEN) {
684                 height -= mPanelHeight;
685             }
686
687             int childWidthSpec;
688             if (lp.width == LayoutParams.WRAP_CONTENT) {
689                 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
690             } else if (lp.width == LayoutParams.MATCH_PARENT) {
691                 childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
692             } else {
693                 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
694             }
695
696             int childHeightSpec;
697             if (lp.height == LayoutParams.WRAP_CONTENT) {
698                 childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
699             } else if (lp.height == LayoutParams.MATCH_PARENT) {
700                 childHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
701             } else {
702                 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
703             }
704
705             if (child == mSlideableView) {
706                 mSlideRange = MeasureSpec.getSize(childHeightSpec) - mPanelHeight + mSlidePanelOffset;
707                 childHeightSpec += mSlidePanelOffset;
708             }
709
710             child.measure(childWidthSpec, childHeightSpec);
711         }
712
713         setMeasuredDimension(widthSize, heightSize);
714     }
715
716     @Override
717     protected void onLayout(boolean changed, int l, int t, int r, int b) {
718         final int paddingLeft = getPaddingLeft();
719         final int paddingTop = getPaddingTop();
720
721         final int childCount = getChildCount();
722
723         if (mFirstLayout) {
724             switch (mSlideState) {
725             case EXPANDED:
726                 mSlideOffset = 1.0f;
727                 break;
728             case ANCHORED:
729                 mSlideOffset = mAnchorPoint;
730                 break;
731             case HIDDEN:
732                 int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
733                 mSlideOffset = computeSlideOffset(newTop);
734                 break;
735             default:
736                 mSlideOffset = 0.f;
737                 break;
738             }
739         }
740
741         for (int i = 0; i < childCount; i++) {
742             final View child = getChildAt(i);
743
744             // Always layout the sliding view on the first layout
745             if (child.getVisibility() == GONE && (child == mMainView || mFirstLayout)) {
746                 continue;
747             }
748
749             final int childHeight = child.getMeasuredHeight();
750             int childTop = paddingTop;
751
752             if (child == mSlideableView) {
753                 childTop = computePanelTopPosition(mSlideOffset);
754             }
755
756             if (!mIsSlidingUp) {
757                 if (child == mMainView && !mOverlayContent) {
758                     childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight();
759                 }
760             }
761             final int childBottom = childTop + childHeight;
762             final int childLeft = paddingLeft;
763             final int childRight = childLeft + child.getMeasuredWidth();
764
765             child.layout(childLeft, childTop, childRight, childBottom);
766         }
767
768         if (mFirstLayout) {
769             updateObscuredViewVisibility();
770         }
771
772         mFirstLayout = false;
773     }
774
775     @Override
776     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
777         super.onSizeChanged(w, h, oldw, oldh);
778         // Recalculate sliding panes and their details
779         if (h != oldh) {
780             mFirstLayout = true;
781         }
782     }
783
784     /**
785      * Set if the drag view can have its own touch events.  If set
786      * to true, a drag view can scroll horizontally and have its own click listener.
787      *
788      * Default is set to false.
789      */
790     public void setEnableDragViewTouchEvents(boolean enabled) {
791         mIsUsingDragViewTouchEvents = enabled;
792     }
793
794     @Override
795     public void setEnabled(boolean enabled) {
796         if (!enabled) {
797             collapsePanel();
798         }
799         super.setEnabled(enabled);
800     }
801
802     @Override
803     public boolean onInterceptTouchEvent(MotionEvent ev) {
804         final int action = MotionEventCompat.getActionMasked(ev);
805
806
807         if (!isEnabled() || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
808             mDragHelper.cancel();
809             return super.onInterceptTouchEvent(ev);
810         }
811
812         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
813             mDragHelper.cancel();
814             return false;
815         }
816
817         final float x = ev.getX();
818         final float y = ev.getY();
819
820         switch (action) {
821             case MotionEvent.ACTION_DOWN: {
822                 mIsUnableToDrag = false;
823                 mInitialMotionX = x;
824                 mInitialMotionY = y;
825                 break;
826             }
827
828             case MotionEvent.ACTION_MOVE: {
829                 final float adx = Math.abs(x - mInitialMotionX);
830                 final float ady = Math.abs(y - mInitialMotionY);
831                 final int dragSlop = mDragHelper.getTouchSlop();
832
833                 // Handle any horizontal scrolling on the drag view.
834                 if (mIsUsingDragViewTouchEvents && adx > dragSlop && ady < dragSlop) {
835                     return super.onInterceptTouchEvent(ev);
836                 }
837
838                 if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int)mInitialMotionX, (int)mInitialMotionY)) {
839                     mDragHelper.cancel();
840                     mIsUnableToDrag = true;
841                     return false;
842                 }
843                 break;
844             }
845         }
846
847         return mDragHelper.shouldInterceptTouchEvent(ev);
848     }
849
850     @Override
851     public boolean onTouchEvent(MotionEvent ev) {
852         if (!isSlidingEnabled()) {
853             return super.onTouchEvent(ev);
854         }
855         mDragHelper.processTouchEvent(ev);
856         return true;
857     }
858
859     private boolean isDragViewUnder(int x, int y) {
860         if (mDragView == null) return false;
861         int[] viewLocation = new int[2];
862         mDragView.getLocationOnScreen(viewLocation);
863         int[] parentLocation = new int[2];
864         this.getLocationOnScreen(parentLocation);
865         int screenX = parentLocation[0] + x;
866         int screenY = parentLocation[1] + y;
867         return screenX >= viewLocation[0] && screenX < viewLocation[0] + mDragView.getWidth() &&
868                 screenY >= viewLocation[1] && screenY < viewLocation[1] + mDragView.getHeight();
869     }
870
871     private boolean expandPanel(View pane, int initialVelocity, float mSlideOffset) {
872         return mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity);
873     }
874
875     private boolean collapsePanel(View pane, int initialVelocity) {
876         return mFirstLayout || smoothSlideTo(0.0f, initialVelocity);
877     }
878
879     /*
880      * Computes the top position of the panel based on the slide offset.
881      */
882     private int computePanelTopPosition(float slideOffset) {
883         int slidingViewHeight = mSlideableView != null ? mSlideableView.getMeasuredHeight() : 0;
884         int slidePixelOffset = (int) (slideOffset * mSlideRange);
885         // Compute the top of the panel if its collapsed
886         return mIsSlidingUp
887                 ? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset
888                 : getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset;
889     }
890
891     /*
892      * Computes the slide offset based on the top position of the panel
893      */
894     private float computeSlideOffset(int topPosition) {
895         // Compute the panel top position if the panel is collapsed (offset 0)
896         final int topBoundCollapsed = computePanelTopPosition(0);
897
898         // Determine the new slide offset based on the collapsed top position and the new required
899         // top position
900         return (mIsSlidingUp
901                 ? (float) (topBoundCollapsed - topPosition) / mSlideRange
902                 : (float) (topPosition - topBoundCollapsed) / mSlideRange);
903     }
904
905     /**
906      * Collapse the sliding pane if it is currently slideable. If first layout
907      * has already completed this will animate.
908      *
909      * @return true if the pane was slideable and is now collapsed/in the process of collapsing
910      */
911     public boolean collapsePanel() {
912         if (mFirstLayout) {
913             mSlideState = SlideState.COLLAPSED;
914             return true;
915         } else {
916             if (mSlideState == SlideState.HIDDEN || mSlideState == SlideState.COLLAPSED)
917                 return false;
918             return collapsePanel(mSlideableView, 0);
919         }
920     }
921
922     /**
923      * Expand the sliding pane if it is currently slideable.
924      *
925      * @return true if the pane was slideable and is now expanded/in the process of expading
926      */
927     public boolean expandPanel() {
928         if (mFirstLayout) {
929             mSlideState = SlideState.EXPANDED;
930             return true;
931         } else {
932             return expandPanel(1.0f);
933         }
934     }
935
936     /**
937      * Expand the sliding pane to the anchor point if it is currently slideable.
938      *
939      * @return true if the pane was slideable and is now expanded/in the process of expading
940      */
941     public boolean anchorPanel() {
942         if (mFirstLayout) {
943             mSlideState = SlideState.ANCHORED;
944             return true;
945         } else {
946             return expandPanel(mAnchorPoint);
947         }
948     }
949
950     /**
951      * Partially expand the sliding panel up to a specific offset
952      *
953      * @param mSlideOffset Value between 0 and 1, where 0 is completely expanded.
954      * @return true if the pane was slideable and is now expanded/in the process of expanding
955      */
956     public boolean expandPanel(float mSlideOffset) {
957         if (mSlideableView == null || mSlideState == SlideState.EXPANDED) return false;
958         mSlideableView.setVisibility(View.VISIBLE);
959         return expandPanel(mSlideableView, 0, mSlideOffset);
960     }
961
962     /**
963      * Check if the sliding panel in this layout is fully expanded.
964      *
965      * @return true if sliding panel is completely expanded
966      */
967     public boolean isPanelExpanded() {
968         return mSlideState == SlideState.EXPANDED;
969     }
970
971     /**
972      * Check if the sliding panel in this layout is anchored.
973      *
974      * @return true if sliding panel is anchored
975      */
976     public boolean isPanelAnchored() {
977         return mSlideState == SlideState.ANCHORED;
978     }
979
980     /**
981      * Check if the sliding panel in this layout is currently visible.
982      *
983      * @return true if the sliding panel is visible.
984      */
985     public boolean isPanelHidden() {
986         return mSlideState == SlideState.HIDDEN;
987     }
988
989     /**
990      * Shows the panel from the hidden state
991      */
992     public void showPanel() {
993         if (mFirstLayout) {
994             mSlideState = SlideState.COLLAPSED;
995         } else {
996             if (mSlideableView == null || mSlideState != SlideState.HIDDEN) return;
997             mSlideableView.setVisibility(View.VISIBLE);
998             requestLayout();
999             smoothSlideTo(0, 0);
1000         }
1001     }
1002
1003     /**
1004      * Hides the sliding panel entirely.
1005      */
1006     public void hidePanel() {
1007         if (mFirstLayout) {
1008             mSlideState = SlideState.HIDDEN;
1009         } else {
1010             if (mSlideState == SlideState.DRAGGING || mSlideState == SlideState.HIDDEN) return;
1011             int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
1012             smoothSlideTo(computeSlideOffset(newTop), 0);
1013         }
1014     }
1015
1016     @SuppressLint("NewApi")
1017     private void onPanelDragged(int newTop) {
1018         mSlideState = SlideState.DRAGGING;
1019         // Recompute the slide offset based on the new top position
1020         mSlideOffset = computeSlideOffset(newTop);
1021         // Update the parallax based on the new slide offset
1022         if ((mParallaxOffset > 0 || mDirectOffset) && mSlideOffset >= 0) {
1023             int mainViewOffset = 0;
1024             if (mParallaxOffset > 0) {
1025                 mainViewOffset = getCurrentParalaxOffset();
1026             } else {
1027                 mainViewOffset = (int)(getDirectionalSlideOffset() * mSlideRange);
1028             }
1029
1030             mMainView.setTranslationY(mainViewOffset);
1031         }
1032
1033         // Dispatch the slide event
1034         dispatchOnPanelSlide(mSlideableView);
1035         // If the slide offset is negative, and overlay is not on, we need to increase the
1036         // height of the main content
1037         if (mSlideOffset <= 0 && !mOverlayContent) {
1038             // expand the main view
1039             LayoutParams lp = (LayoutParams)mMainView.getLayoutParams();
1040             lp.height = mIsSlidingUp ? (newTop - getPaddingBottom()) : (getHeight() - getPaddingBottom() - mSlideableView.getMeasuredHeight() - newTop);
1041             mMainView.requestLayout();
1042         }
1043     }
1044
1045     @Override
1046     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1047         boolean result;
1048         final int save = canvas.save();
1049
1050         if (isSlidingEnabled() && mMainView == child) {
1051             // Clip against the slider; no sense drawing what will immediately be covered,
1052             // Unless the panel is set to overlay content
1053             if (!mOverlayContent) {
1054                 canvas.getClipBounds(mTmpRect);
1055                 if (mIsSlidingUp) {
1056                     mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
1057                 } else {
1058                     mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
1059                 }
1060                 canvas.clipRect(mTmpRect);
1061             }
1062         }
1063
1064         result = super.drawChild(canvas, child, drawingTime);
1065         canvas.restoreToCount(save);
1066
1067         if (mCoveredFadeColor != 0 && mSlideOffset > 0) {
1068             final int baseAlpha = (mCoveredFadeColor & 0xff000000) >>> 24;
1069             final int imag = (int) (baseAlpha * mSlideOffset);
1070             final int color = imag << 24 | (mCoveredFadeColor & 0xffffff);
1071             mCoveredFadePaint.setColor(color);
1072             canvas.drawRect(mTmpRect, mCoveredFadePaint);
1073         }
1074
1075         return result;
1076     }
1077
1078     /**
1079      * Smoothly animate mDraggingPane to the target X position within its range.
1080      *
1081      * @param slideOffset position to animate to
1082      * @param velocity initial velocity in case of fling, or 0.
1083      */
1084     boolean smoothSlideTo(float slideOffset, int velocity) {
1085         if (!isSlidingEnabled()) {
1086             // Nothing to do.
1087             return false;
1088         }
1089
1090         int panelTop = computePanelTopPosition(slideOffset);
1091         if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {
1092             setAllChildrenVisible();
1093             ViewCompat.postInvalidateOnAnimation(this);
1094             return true;
1095         }
1096         return false;
1097     }
1098
1099     @Override
1100     public void computeScroll() {
1101         if (mDragHelper != null && mDragHelper.continueSettling(true)) {
1102             if (!isSlidingEnabled()) {
1103                 mDragHelper.abort();
1104                 return;
1105             }
1106
1107             ViewCompat.postInvalidateOnAnimation(this);
1108         }
1109     }
1110
1111     @Override
1112     public void draw(Canvas c) {
1113         super.draw(c);
1114
1115         if (!isSlidingEnabled()) {
1116             // No need to draw a shadow if we don't have one.
1117             return;
1118         }
1119
1120         final int right = mSlideableView.getRight();
1121         final int top;
1122         final int bottom;
1123         if (mIsSlidingUp) {
1124             top = mSlideableView.getTop() - mShadowHeight;
1125             bottom = mSlideableView.getTop();
1126         } else {
1127             top = mSlideableView.getBottom();
1128             bottom = mSlideableView.getBottom() + mShadowHeight;
1129         }
1130         final int left = mSlideableView.getLeft();
1131
1132         if (mShadowDrawable != null) {
1133             mShadowDrawable.setBounds(left, top, right, bottom);
1134             mShadowDrawable.draw(c);
1135         }
1136     }
1137
1138     /**
1139      * Tests scrollability within child views of v given a delta of dx.
1140      *
1141      * @param v View to test for horizontal scrollability
1142      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1143      *               or just its children (false).
1144      * @param dx Delta scrolled in pixels
1145      * @param x X coordinate of the active touch point
1146      * @param y Y coordinate of the active touch point
1147      * @return true if child views of v can be scrolled by delta of dx.
1148      */
1149     protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1150         if (v instanceof ViewGroup) {
1151             final ViewGroup group = (ViewGroup) v;
1152             final int scrollX = v.getScrollX();
1153             final int scrollY = v.getScrollY();
1154             final int count = group.getChildCount();
1155             // Count backwards - let topmost views consume scroll distance first.
1156             for (int i = count - 1; i >= 0; i--) {
1157                 final View child = group.getChildAt(i);
1158                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1159                         y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1160                         canScroll(child, true, dx, x + scrollX - child.getLeft(),
1161                                 y + scrollY - child.getTop())) {
1162                     return true;
1163                 }
1164             }
1165         }
1166         return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1167     }
1168
1169
1170     @Override
1171     protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1172         return new LayoutParams();
1173     }
1174
1175     @Override
1176     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1177         return p instanceof MarginLayoutParams
1178                 ? new LayoutParams((MarginLayoutParams) p)
1179                 : new LayoutParams(p);
1180     }
1181
1182     @Override
1183     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1184         return p instanceof LayoutParams && super.checkLayoutParams(p);
1185     }
1186
1187     @Override
1188     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1189         return new LayoutParams(getContext(), attrs);
1190     }
1191
1192     @Override
1193     public Parcelable onSaveInstanceState() {
1194         Parcelable superState = super.onSaveInstanceState();
1195
1196         SavedState ss = new SavedState(superState);
1197         ss.mSlideState = mSlideState;
1198
1199         return ss;
1200     }
1201
1202     @Override
1203     public void onRestoreInstanceState(Parcelable state) {
1204         SavedState ss = (SavedState) state;
1205         super.onRestoreInstanceState(ss.getSuperState());
1206         mSlideState = ss.mSlideState;
1207     }
1208
1209     private class DragHelperCallback extends ViewDragHelper.Callback {
1210
1211         @Override
1212         public boolean tryCaptureView(View child, int pointerId) {
1213             if (mIsUnableToDrag) {
1214                 return false;
1215             }
1216
1217             return child == mSlideableView;
1218         }
1219
1220         @Override
1221         public void onViewDragStateChanged(int state) {
1222             if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1223                 mSlideOffset = computeSlideOffset(mSlideableView.getTop());
1224
1225                 if (mSlideOffset == 1) {
1226                     if (mSlideState != SlideState.EXPANDED) {
1227                         updateObscuredViewVisibility();
1228                         mSlideState = SlideState.EXPANDED;
1229                         dispatchOnPanelExpanded(mSlideableView);
1230                     }
1231                 } else if (mSlideOffset == 0) {
1232                     if (mSlideState != SlideState.COLLAPSED) {
1233                         mSlideState = SlideState.COLLAPSED;
1234                         dispatchOnPanelCollapsed(mSlideableView);
1235                     }
1236                 } else if (mSlideOffset < 0) {
1237                     mSlideState = SlideState.HIDDEN;
1238                     mSlideableView.setVisibility(View.GONE);
1239                     dispatchOnPanelHidden(mSlideableView);
1240                 } else if (mSlideState != SlideState.ANCHORED) {
1241                     updateObscuredViewVisibility();
1242                     mSlideState = SlideState.ANCHORED;
1243                     dispatchOnPanelAnchored(mSlideableView);
1244                 }
1245             }
1246         }
1247
1248         @Override
1249         public void onViewCaptured(View capturedChild, int activePointerId) {
1250             setAllChildrenVisible();
1251         }
1252
1253         @Override
1254         public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1255             onPanelDragged(top);
1256             invalidate();
1257         }
1258
1259         @Override
1260         public void onViewReleased(View releasedChild, float xvel, float yvel) {
1261             int target = 0;
1262
1263             // direction is always positive if we are sliding in the expanded direction
1264             float direction = mIsSlidingUp ? -yvel : yvel;
1265
1266             if (direction > 0) {
1267                 // swipe up -> expand
1268                 target = computePanelTopPosition(1.0f);
1269             } else if (direction < 0) {
1270                 // swipe down -> collapse
1271                 target = computePanelTopPosition(0.0f);
1272             } else if (mAnchorPoint != 1 && mSlideOffset >= (1.f + mAnchorPoint) / 2) {
1273                 // zero velocity, and far enough from anchor point => expand to the top
1274                 target = computePanelTopPosition(1.0f);
1275             } else if (mAnchorPoint == 1 && mSlideOffset >= 0.5f) {
1276                 // zero velocity, and far enough from anchor point => expand to the top
1277                 target = computePanelTopPosition(1.0f);
1278             } else if (mAnchorPoint != 1 && mSlideOffset >= mAnchorPoint) {
1279                 target = computePanelTopPosition(mAnchorPoint);
1280             } else if (mAnchorPoint != 1 && mSlideOffset >= mAnchorPoint / 2) {
1281                 target = computePanelTopPosition(mAnchorPoint);
1282             } else {
1283                 // settle at the bottom
1284                 target = computePanelTopPosition(0.0f);
1285             }
1286
1287             mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);
1288             invalidate();
1289         }
1290
1291         @Override
1292         public int getViewVerticalDragRange(View child) {
1293             return mSlideRange;
1294         }
1295
1296         @Override
1297         public int clampViewPositionVertical(View child, int top, int dy) {
1298             final int collapsedTop = computePanelTopPosition(0.f);
1299             final int expandedTop = computePanelTopPosition(1.0f);
1300             if (mIsSlidingUp) {
1301                 return Math.min(Math.max(top, expandedTop), collapsedTop);
1302             } else {
1303                 return Math.min(Math.max(top, collapsedTop), expandedTop);
1304             }
1305         }
1306     }
1307
1308     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1309         private static final int[] ATTRS = new int[] {
1310             android.R.attr.layout_weight
1311         };
1312
1313         public LayoutParams() {
1314             super(MATCH_PARENT, MATCH_PARENT);
1315         }
1316
1317         public LayoutParams(int width, int height) {
1318             super(width, height);
1319         }
1320
1321         public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1322             super(source);
1323         }
1324
1325         public LayoutParams(MarginLayoutParams source) {
1326             super(source);
1327         }
1328
1329         public LayoutParams(LayoutParams source) {
1330             super(source);
1331         }
1332
1333         public LayoutParams(Context c, AttributeSet attrs) {
1334             super(c, attrs);
1335
1336             final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1337             a.recycle();
1338         }
1339
1340     }
1341
1342     static class SavedState extends BaseSavedState {
1343         SlideState mSlideState;
1344
1345         SavedState(Parcelable superState) {
1346             super(superState);
1347         }
1348
1349         private SavedState(Parcel in) {
1350             super(in);
1351             try {
1352                 mSlideState = Enum.valueOf(SlideState.class, in.readString());
1353             } catch (IllegalArgumentException e) {
1354                 mSlideState = SlideState.COLLAPSED;
1355             }
1356         }
1357
1358         @Override
1359         public void writeToParcel(Parcel out, int flags) {
1360             super.writeToParcel(out, flags);
1361             out.writeString(mSlideState.toString());
1362         }
1363
1364         public static final Parcelable.Creator<SavedState> CREATOR =
1365                 new Parcelable.Creator<SavedState>() {
1366             @Override
1367             public SavedState createFromParcel(Parcel in) {
1368                 return new SavedState(in);
1369             }
1370
1371             @Override
1372             public SavedState[] newArray(int size) {
1373                 return new SavedState[size];
1374             }
1375         };
1376     }
1377 }