1 package org.lineageos.eleven.slidinguppanel;
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;
20 import androidx.core.view.MotionEventCompat;
21 import androidx.core.view.ViewCompat;
23 import org.lineageos.eleven.R;
25 public class SlidingUpPanelLayout extends ViewGroup {
27 private static final String TAG = SlidingUpPanelLayout.class.getSimpleName();
30 * Default peeking out panel height
32 private static final int DEFAULT_PANEL_HEIGHT = 68; // dp;
35 * Default anchor point height
37 private static final float DEFAULT_ANCHOR_POINT = 1.0f; // In relative %
40 * Default initial state for the component
42 private static SlideState DEFAULT_SLIDE_STATE = SlideState.COLLAPSED;
45 * Default height of the shadow above the peeking out panel
47 private static final int DEFAULT_SHADOW_HEIGHT = 4; // dp;
50 * If no fade color is given by default it will fade to 80% gray.
52 private static final int DEFAULT_FADE_COLOR = 0x99000000;
55 * Whether we should hook up the drag view clickable state
57 private static final boolean DEFAULT_DRAG_VIEW_CLICKABLE = true;
60 * Default Minimum velocity that will be detected as a fling
62 private static final int DEFAULT_MIN_FLING_VELOCITY = 400; // dips per second
64 * Default is set to false because that is how it was written
66 private static final boolean DEFAULT_OVERLAY_FLAG = false;
68 * Default attributes for layout
70 private static final int[] DEFAULT_ATTRS = new int[] {
71 android.R.attr.gravity
75 * Minimum velocity that will be detected as a fling
77 private int mMinFlingVelocity = DEFAULT_MIN_FLING_VELOCITY;
80 * The fade color used for the panel covered by the slider. 0 = no fading.
82 private int mCoveredFadeColor = DEFAULT_FADE_COLOR;
85 * Default paralax length of the main view
87 private static final int DEFAULT_PARALAX_OFFSET = 0;
90 * Default slide panel offset when collapsed
92 private static final int DEFAULT_SLIDE_PANEL_OFFSET = 0;
95 * Default direct offset flag
97 private static final boolean DEFAULT_DIRECT_OFFSET_FLAG = false;
100 * The paint used to dim the main layout when sliding
102 private final Paint mCoveredFadePaint = new Paint();
105 * Drawable used to draw the shadow between panes.
107 private final Drawable mShadowDrawable;
110 * The size of the overhang in pixels.
112 private int mPanelHeight = -1;
115 * Determines how much to slide the panel off when expanded
117 private int mSlidePanelOffset = 0;
120 * The size of the shadow in pixels.
122 private int mShadowHeight = -1;
127 private int mParallaxOffset = -1;
130 * Clamps the Main view to the slideable view
132 private boolean mDirectOffset = false;
135 * True if the collapsed panel should be dragged up.
137 private boolean mIsSlidingUp;
140 * Panel overlays the windows instead of putting it underneath it.
142 private boolean mOverlayContent = DEFAULT_OVERLAY_FLAG;
145 * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
148 private View mDragView;
151 * If provided, the panel can be dragged by only this view. Otherwise, the entire panel can be
154 private int mDragViewResId = -1;
157 * Whether clicking on the drag view will expand/collapse
159 private boolean mDragViewClickable = DEFAULT_DRAG_VIEW_CLICKABLE;
162 * The child view that can slide, if any.
164 private View mSlideableView;
169 private View mMainView;
172 * The background view
174 private View mBackgroundView;
177 * Current state of the slideable view.
179 private enum SlideState {
186 private SlideState mSlideState = SlideState.COLLAPSED;
189 * How far the panel is offset from its expanded position.
190 * range [0, 1] where 0 = collapsed, 1 = expanded.
192 private float mSlideOffset;
195 * How far in pixels the slideable panel may move.
197 private int mSlideRange;
200 * A panel view is locked into internal scrolling or another condition that
201 * is preventing a drag.
203 private boolean mIsUnableToDrag;
206 * Flag indicating that sliding feature is enabled\disabled
208 private boolean mIsSlidingEnabled;
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.
214 * Default is set to false.
216 private boolean mIsUsingDragViewTouchEvents;
218 private float mInitialMotionX;
219 private float mInitialMotionY;
220 private float mAnchorPoint = 1.f;
222 private PanelSlideListener mPanelSlideListener;
224 private final ViewDragHelper mDragHelper;
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.
231 private boolean mFirstLayout = true;
233 private final Rect mTmpRect = new Rect();
236 * Listener for monitoring events about sliding panes.
238 public interface PanelSlideListener {
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
244 public void onPanelSlide(View panel, float slideOffset);
246 * Called when a sliding panel becomes slid completely collapsed.
247 * @param panel The child view that was slid to an collapsed position
249 public void onPanelCollapsed(View panel);
252 * Called when a sliding panel becomes slid completely expanded.
253 * @param panel The child view that was slid to a expanded position
255 public void onPanelExpanded(View panel);
258 * Called when a sliding panel becomes anchored.
259 * @param panel The child view that was slid to a anchored position
261 public void onPanelAnchored(View panel);
264 * Called when a sliding panel becomes completely hidden.
265 * @param panel The child view that was slid to a hidden position
267 public void onPanelHidden(View panel);
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.
274 public static class SimplePanelSlideListener implements PanelSlideListener {
276 public void onPanelSlide(View panel, float slideOffset) {
279 public void onPanelCollapsed(View panel) {
282 public void onPanelExpanded(View panel) {
285 public void onPanelAnchored(View panel) {
288 public void onPanelHidden(View panel) {
292 public SlidingUpPanelLayout(Context context) {
296 public SlidingUpPanelLayout(Context context, AttributeSet attrs) {
297 this(context, attrs, 0);
300 public SlidingUpPanelLayout(Context context, AttributeSet attrs, int defStyle) {
301 super(context, attrs, defStyle);
304 mShadowDrawable = null;
310 TypedArray defAttrs = context.obtainStyledAttributes(attrs, DEFAULT_ATTRS);
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");
317 mIsSlidingUp = gravity == Gravity.BOTTOM;
322 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingUpPanelLayout);
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);
331 mMinFlingVelocity = ta.getInt(R.styleable.SlidingUpPanelLayout_flingVelocity, DEFAULT_MIN_FLING_VELOCITY);
332 mCoveredFadeColor = ta.getColor(R.styleable.SlidingUpPanelLayout_fadeColor, DEFAULT_FADE_COLOR);
334 mDragViewResId = ta.getResourceId(R.styleable.SlidingUpPanelLayout_dragView, -1);
335 mDragViewClickable = ta.getBoolean(R.styleable.SlidingUpPanelLayout_dragViewClickable, DEFAULT_DRAG_VIEW_CLICKABLE);
337 mOverlayContent = ta.getBoolean(R.styleable.SlidingUpPanelLayout_overlay,DEFAULT_OVERLAY_FLAG);
339 mAnchorPoint = ta.getFloat(R.styleable.SlidingUpPanelLayout_anchorPoint, DEFAULT_ANCHOR_POINT);
341 mSlideState = SlideState.values()[ta.getInt(R.styleable.SlidingUpPanelLayout_initialState, DEFAULT_SLIDE_STATE.ordinal())];
347 final float density = context.getResources().getDisplayMetrics().density;
348 if (mPanelHeight == -1) {
349 mPanelHeight = (int) (DEFAULT_PANEL_HEIGHT * density + 0.5f);
351 if (mShadowHeight == -1) {
352 mShadowHeight = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f);
354 if (mParallaxOffset == -1) {
355 mParallaxOffset = (int) (DEFAULT_PARALAX_OFFSET * density);
357 // If the shadow height is zero, don't show the shadow
358 if (mShadowHeight > 0) {
360 mShadowDrawable = getResources().getDrawable(R.drawable.above_shadow);
362 mShadowDrawable = getResources().getDrawable(R.drawable.below_shadow);
366 mShadowDrawable = null;
369 setWillNotDraw(false);
371 mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback());
372 mDragHelper.setMinVelocity(mMinFlingVelocity * density);
374 mIsSlidingEnabled = true;
378 * Set the Drag View after the view is inflated
381 protected void onFinishInflate() {
382 super.onFinishInflate();
383 if (mDragViewResId != -1) {
384 setDragView(findViewById(mDragViewResId));
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.
392 * @param color An ARGB-packed color value
394 public void setCoveredFadeColor(int color) {
395 mCoveredFadeColor = color;
400 * @return The ARGB-packed color value used to fade the fixed pane
402 public int getCoveredFadeColor() {
403 return mCoveredFadeColor;
407 * Set sliding enabled flag
408 * @param enabled flag value
410 public void setSlidingEnabled(boolean enabled) {
411 mIsSlidingEnabled = enabled;
414 public boolean isSlidingEnabled() {
415 return mIsSlidingEnabled && mSlideableView != null;
419 * Set the collapsed panel height in pixels
421 * @param val A height in pixels
423 public void setPanelHeight(int val) {
429 * @return The current collapsed panel height
431 public int getPanelHeight() {
436 * Sets the panel offset when collapsed so you can exit
437 * the boundaries of the top of the screen
439 * @param val Offset in pixels
441 public void setSlidePanelOffset(int val) {
442 mSlidePanelOffset = val;
447 * @return The current paralax offset
449 public int getCurrentParalaxOffset() {
450 if (mParallaxOffset < 0) {
454 return (int)(mParallaxOffset * getDirectionalSlideOffset());
458 * @return The directional slide offset
460 protected float getDirectionalSlideOffset() {
461 return mIsSlidingUp ? -mSlideOffset : mSlideOffset;
465 * Sets the panel slide listener
468 public void setPanelSlideListener(PanelSlideListener listener) {
469 mPanelSlideListener = listener;
473 * Set the draggable view portion. Use to null, to allow the whole panel to be draggable
475 * @param dragView A view that will be used to drag the panel.
477 public void setDragView(View dragView) {
478 if (mDragView != null && mDragViewClickable) {
479 mDragView.setOnClickListener(null);
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() {
489 public void onClick(View v) {
490 if (!isEnabled()) return;
491 if (!isPanelExpanded() && !isPanelAnchored()) {
492 expandPanel(mAnchorPoint);
503 * Set an anchor point where the panel can stop during sliding
505 * @param anchorPoint A value between 0 and 1, determining the position of the anchor point
506 * starting from the top of the layout.
508 public void setAnchorPoint(float anchorPoint) {
509 if (anchorPoint > 0 && anchorPoint <= 1) {
510 mAnchorPoint = anchorPoint;
515 * Gets the currently set anchor point
517 * @return the currently set anchor point
519 public float getAnchorPoint() {
524 * Sets whether or not the panel overlays the content
527 public void setOverlayed(boolean overlayed) {
528 mOverlayContent = overlayed;
532 * Check if the panel is set as an overlay.
534 public boolean isOverlayed() {
535 return mOverlayContent;
538 void dispatchOnPanelSlide(View panel) {
539 if (mPanelSlideListener != null) {
540 mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
544 void dispatchOnPanelExpanded(View panel) {
545 if (mPanelSlideListener != null) {
546 mPanelSlideListener.onPanelExpanded(panel);
548 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
551 void dispatchOnPanelCollapsed(View panel) {
552 if (mPanelSlideListener != null) {
553 mPanelSlideListener.onPanelCollapsed(panel);
555 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
558 void dispatchOnPanelAnchored(View panel) {
559 if (mPanelSlideListener != null) {
560 mPanelSlideListener.onPanelAnchored(panel);
562 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
565 void dispatchOnPanelHidden(View panel) {
566 if (mPanelSlideListener != null) {
567 mPanelSlideListener.onPanelHidden(panel);
569 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
572 void updateObscuredViewVisibility() {
573 if (getChildCount() == 0) {
576 final int leftBound = getPaddingLeft();
577 final int rightBound = getWidth() - getPaddingRight();
578 final int topBound = getPaddingTop();
579 final int bottomBound = getHeight() - getPaddingBottom();
584 if (mSlideableView != null && hasOpaqueBackground(mSlideableView)) {
585 left = mSlideableView.getLeft();
586 right = mSlideableView.getRight();
587 top = mSlideableView.getTop();
588 bottom = mSlideableView.getBottom();
590 left = right = top = bottom = 0;
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());
598 if (clampedChildLeft >= left && clampedChildTop >= top &&
599 clampedChildRight <= right && clampedChildBottom <= bottom) {
604 child.setVisibility(vis);
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);
616 private static boolean hasOpaqueBackground(View v) {
617 final Drawable bg = v.getBackground();
618 return bg != null && bg.getOpacity() == PixelFormat.OPAQUE;
622 protected void onAttachedToWindow() {
623 super.onAttachedToWindow();
628 protected void onDetachedFromWindow() {
629 super.onDetachedFromWindow();
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);
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");
646 final int childCount = getChildCount();
648 if (childCount != 2 && childCount != 3) {
649 throw new IllegalStateException("Sliding up panel layout must have exactly 2 or 3 children!");
652 if (childCount == 2) {
653 mMainView = getChildAt(0);
654 mSlideableView = getChildAt(1);
656 mBackgroundView = getChildAt(0);
657 mMainView = getChildAt(1);
658 mSlideableView = getChildAt(2);
661 if (mDragView == null) {
662 setDragView(mSlideableView);
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;
670 int layoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
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();
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) {
682 int height = layoutHeight;
683 if (child == mMainView && !mOverlayContent && mSlideState != SlideState.HIDDEN) {
684 height -= mPanelHeight;
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);
693 childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
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);
702 childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
705 if (child == mSlideableView) {
706 mSlideRange = MeasureSpec.getSize(childHeightSpec) - mPanelHeight + mSlidePanelOffset;
707 childHeightSpec += mSlidePanelOffset;
710 child.measure(childWidthSpec, childHeightSpec);
713 setMeasuredDimension(widthSize, heightSize);
717 protected void onLayout(boolean changed, int l, int t, int r, int b) {
718 final int paddingLeft = getPaddingLeft();
719 final int paddingTop = getPaddingTop();
721 final int childCount = getChildCount();
724 switch (mSlideState) {
729 mSlideOffset = mAnchorPoint;
732 int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
733 mSlideOffset = computeSlideOffset(newTop);
741 for (int i = 0; i < childCount; i++) {
742 final View child = getChildAt(i);
744 // Always layout the sliding view on the first layout
745 if (child.getVisibility() == GONE && (child == mMainView || mFirstLayout)) {
749 final int childHeight = child.getMeasuredHeight();
750 int childTop = paddingTop;
752 if (child == mSlideableView) {
753 childTop = computePanelTopPosition(mSlideOffset);
757 if (child == mMainView && !mOverlayContent) {
758 childTop = computePanelTopPosition(mSlideOffset) + mSlideableView.getMeasuredHeight();
761 final int childBottom = childTop + childHeight;
762 final int childLeft = paddingLeft;
763 final int childRight = childLeft + child.getMeasuredWidth();
765 child.layout(childLeft, childTop, childRight, childBottom);
769 updateObscuredViewVisibility();
772 mFirstLayout = false;
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
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.
788 * Default is set to false.
790 public void setEnableDragViewTouchEvents(boolean enabled) {
791 mIsUsingDragViewTouchEvents = enabled;
795 public void setEnabled(boolean enabled) {
799 super.setEnabled(enabled);
803 public boolean onInterceptTouchEvent(MotionEvent ev) {
804 final int action = MotionEventCompat.getActionMasked(ev);
807 if (!isEnabled() || !mIsSlidingEnabled || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
808 mDragHelper.cancel();
809 return super.onInterceptTouchEvent(ev);
812 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
813 mDragHelper.cancel();
817 final float x = ev.getX();
818 final float y = ev.getY();
821 case MotionEvent.ACTION_DOWN: {
822 mIsUnableToDrag = false;
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();
833 // Handle any horizontal scrolling on the drag view.
834 if (mIsUsingDragViewTouchEvents && adx > dragSlop && ady < dragSlop) {
835 return super.onInterceptTouchEvent(ev);
838 if ((ady > dragSlop && adx > ady) || !isDragViewUnder((int)mInitialMotionX, (int)mInitialMotionY)) {
839 mDragHelper.cancel();
840 mIsUnableToDrag = true;
847 return mDragHelper.shouldInterceptTouchEvent(ev);
851 public boolean onTouchEvent(MotionEvent ev) {
852 if (!isSlidingEnabled()) {
853 return super.onTouchEvent(ev);
855 mDragHelper.processTouchEvent(ev);
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();
871 private boolean expandPanel(View pane, int initialVelocity, float mSlideOffset) {
872 return mFirstLayout || smoothSlideTo(mSlideOffset, initialVelocity);
875 private boolean collapsePanel(View pane, int initialVelocity) {
876 return mFirstLayout || smoothSlideTo(0.0f, initialVelocity);
880 * Computes the top position of the panel based on the slide offset.
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
887 ? getMeasuredHeight() - getPaddingBottom() - mPanelHeight - slidePixelOffset
888 : getPaddingTop() - slidingViewHeight + mPanelHeight + slidePixelOffset;
892 * Computes the slide offset based on the top position of the panel
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);
898 // Determine the new slide offset based on the collapsed top position and the new required
901 ? (float) (topBoundCollapsed - topPosition) / mSlideRange
902 : (float) (topPosition - topBoundCollapsed) / mSlideRange);
906 * Collapse the sliding pane if it is currently slideable. If first layout
907 * has already completed this will animate.
909 * @return true if the pane was slideable and is now collapsed/in the process of collapsing
911 public boolean collapsePanel() {
913 mSlideState = SlideState.COLLAPSED;
916 if (mSlideState == SlideState.HIDDEN || mSlideState == SlideState.COLLAPSED)
918 return collapsePanel(mSlideableView, 0);
923 * Expand the sliding pane if it is currently slideable.
925 * @return true if the pane was slideable and is now expanded/in the process of expading
927 public boolean expandPanel() {
929 mSlideState = SlideState.EXPANDED;
932 return expandPanel(1.0f);
937 * Expand the sliding pane to the anchor point if it is currently slideable.
939 * @return true if the pane was slideable and is now expanded/in the process of expading
941 public boolean anchorPanel() {
943 mSlideState = SlideState.ANCHORED;
946 return expandPanel(mAnchorPoint);
951 * Partially expand the sliding panel up to a specific offset
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
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);
963 * Check if the sliding panel in this layout is fully expanded.
965 * @return true if sliding panel is completely expanded
967 public boolean isPanelExpanded() {
968 return mSlideState == SlideState.EXPANDED;
972 * Check if the sliding panel in this layout is anchored.
974 * @return true if sliding panel is anchored
976 public boolean isPanelAnchored() {
977 return mSlideState == SlideState.ANCHORED;
981 * Check if the sliding panel in this layout is currently visible.
983 * @return true if the sliding panel is visible.
985 public boolean isPanelHidden() {
986 return mSlideState == SlideState.HIDDEN;
990 * Shows the panel from the hidden state
992 public void showPanel() {
994 mSlideState = SlideState.COLLAPSED;
996 if (mSlideableView == null || mSlideState != SlideState.HIDDEN) return;
997 mSlideableView.setVisibility(View.VISIBLE);
1004 * Hides the sliding panel entirely.
1006 public void hidePanel() {
1008 mSlideState = SlideState.HIDDEN;
1010 if (mSlideState == SlideState.DRAGGING || mSlideState == SlideState.HIDDEN) return;
1011 int newTop = computePanelTopPosition(0.0f) + (mIsSlidingUp ? +mPanelHeight : -mPanelHeight);
1012 smoothSlideTo(computeSlideOffset(newTop), 0);
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();
1027 mainViewOffset = (int)(getDirectionalSlideOffset() * mSlideRange);
1030 mMainView.setTranslationY(mainViewOffset);
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();
1046 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
1048 final int save = canvas.save();
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);
1056 mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
1058 mTmpRect.top = Math.max(mTmpRect.top, mSlideableView.getBottom());
1060 canvas.clipRect(mTmpRect);
1064 result = super.drawChild(canvas, child, drawingTime);
1065 canvas.restoreToCount(save);
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);
1079 * Smoothly animate mDraggingPane to the target X position within its range.
1081 * @param slideOffset position to animate to
1082 * @param velocity initial velocity in case of fling, or 0.
1084 boolean smoothSlideTo(float slideOffset, int velocity) {
1085 if (!isSlidingEnabled()) {
1090 int panelTop = computePanelTopPosition(slideOffset);
1091 if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), panelTop)) {
1092 setAllChildrenVisible();
1093 ViewCompat.postInvalidateOnAnimation(this);
1100 public void computeScroll() {
1101 if (mDragHelper != null && mDragHelper.continueSettling(true)) {
1102 if (!isSlidingEnabled()) {
1103 mDragHelper.abort();
1107 ViewCompat.postInvalidateOnAnimation(this);
1112 public void draw(Canvas c) {
1115 if (!isSlidingEnabled()) {
1116 // No need to draw a shadow if we don't have one.
1120 final int right = mSlideableView.getRight();
1124 top = mSlideableView.getTop() - mShadowHeight;
1125 bottom = mSlideableView.getTop();
1127 top = mSlideableView.getBottom();
1128 bottom = mSlideableView.getBottom() + mShadowHeight;
1130 final int left = mSlideableView.getLeft();
1132 if (mShadowDrawable != null) {
1133 mShadowDrawable.setBounds(left, top, right, bottom);
1134 mShadowDrawable.draw(c);
1139 * Tests scrollability within child views of v given a delta of dx.
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.
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())) {
1166 return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1171 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1172 return new LayoutParams();
1176 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1177 return p instanceof MarginLayoutParams
1178 ? new LayoutParams((MarginLayoutParams) p)
1179 : new LayoutParams(p);
1183 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1184 return p instanceof LayoutParams && super.checkLayoutParams(p);
1188 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1189 return new LayoutParams(getContext(), attrs);
1193 public Parcelable onSaveInstanceState() {
1194 Parcelable superState = super.onSaveInstanceState();
1196 SavedState ss = new SavedState(superState);
1197 ss.mSlideState = mSlideState;
1203 public void onRestoreInstanceState(Parcelable state) {
1204 SavedState ss = (SavedState) state;
1205 super.onRestoreInstanceState(ss.getSuperState());
1206 mSlideState = ss.mSlideState;
1209 private class DragHelperCallback extends ViewDragHelper.Callback {
1212 public boolean tryCaptureView(View child, int pointerId) {
1213 if (mIsUnableToDrag) {
1217 return child == mSlideableView;
1221 public void onViewDragStateChanged(int state) {
1222 if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
1223 mSlideOffset = computeSlideOffset(mSlideableView.getTop());
1225 if (mSlideOffset == 1) {
1226 if (mSlideState != SlideState.EXPANDED) {
1227 updateObscuredViewVisibility();
1228 mSlideState = SlideState.EXPANDED;
1229 dispatchOnPanelExpanded(mSlideableView);
1231 } else if (mSlideOffset == 0) {
1232 if (mSlideState != SlideState.COLLAPSED) {
1233 mSlideState = SlideState.COLLAPSED;
1234 dispatchOnPanelCollapsed(mSlideableView);
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);
1249 public void onViewCaptured(View capturedChild, int activePointerId) {
1250 setAllChildrenVisible();
1254 public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
1255 onPanelDragged(top);
1260 public void onViewReleased(View releasedChild, float xvel, float yvel) {
1263 // direction is always positive if we are sliding in the expanded direction
1264 float direction = mIsSlidingUp ? -yvel : yvel;
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);
1283 // settle at the bottom
1284 target = computePanelTopPosition(0.0f);
1287 mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), target);
1292 public int getViewVerticalDragRange(View child) {
1297 public int clampViewPositionVertical(View child, int top, int dy) {
1298 final int collapsedTop = computePanelTopPosition(0.f);
1299 final int expandedTop = computePanelTopPosition(1.0f);
1301 return Math.min(Math.max(top, expandedTop), collapsedTop);
1303 return Math.min(Math.max(top, collapsedTop), expandedTop);
1308 public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1309 private static final int[] ATTRS = new int[] {
1310 android.R.attr.layout_weight
1313 public LayoutParams() {
1314 super(MATCH_PARENT, MATCH_PARENT);
1317 public LayoutParams(int width, int height) {
1318 super(width, height);
1321 public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1325 public LayoutParams(MarginLayoutParams source) {
1329 public LayoutParams(LayoutParams source) {
1333 public LayoutParams(Context c, AttributeSet attrs) {
1336 final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1342 static class SavedState extends BaseSavedState {
1343 SlideState mSlideState;
1345 SavedState(Parcelable superState) {
1349 private SavedState(Parcel in) {
1352 mSlideState = Enum.valueOf(SlideState.class, in.readString());
1353 } catch (IllegalArgumentException e) {
1354 mSlideState = SlideState.COLLAPSED;
1359 public void writeToParcel(Parcel out, int flags) {
1360 super.writeToParcel(out, flags);
1361 out.writeString(mSlideState.toString());
1364 public static final Parcelable.Creator<SavedState> CREATOR =
1365 new Parcelable.Creator<SavedState>() {
1367 public SavedState createFromParcel(Parcel in) {
1368 return new SavedState(in);
1372 public SavedState[] newArray(int size) {
1373 return new SavedState[size];