package com.android.widget;
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This class originally comes from the android support library v4:
* android.support.v4.widget.SlidingPaneLayout
* It was modified to slide the view vertically instead of horizontally
* and a simplified to fit the needs of the VLC Android app.
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import org.videolan.vlc.BuildConfig;
import org.videolan.vlc.R;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class SlidingPaneLayout extends ViewGroup {
private static final String TAG = "VLC/SlidingPaneLayout";
/**
* Default size of the overhang for a pane in the open state.
* At least this much of a sliding pane will remain visible.
* This indicates that there is more content available and provides
* a "physical" edge to grab to pull it closed.
*/
private static final int DEFAULT_OVERHANG_SIZE = 64; // dp;
/**
* Minimum velocity that will be detected as a fling
*/
private static final int MIN_FLING_VELOCITY = 400; // dips per second
/**
* The fade color used for the panel covered by the slider. 0 = no fading.
*/
private int mCoveredFadeColor;
/**
* Drawable used to draw the shadow between panes.
*/
private Drawable mShadowDrawable;
/**
* The size of the overhang in pixels.
* This is the minimum section of the sliding panel that will
* be visible in the open state to allow for a closing drag.
*/
private int mOverhangSize;
/**
* True if a panel can slide with the current measurements
*/
private boolean mCanSlide;
/**
* The child view that can slide, if any.
*/
private View mSlideableView;
/**
* How far the panel is offset from its closed position.
* range [0, 1] where 0 = closed, 1 = open.
*/
private float mSlideOffset;
/**
* How far in pixels the slideable panel may move.
*/
private int mSlideRange;
/**
* A panel view is locked into internal scrolling or another condition that
* is preventing a drag.
*/
private boolean mIsUnableToDrag;
/**
* The opening states of the pane.
*/
public final int STATE_OPENED_ENTIRELY = 0;
public final int STATE_OPENED = 1;
public final int STATE_CLOSED = 2;
private int mState;
private float mInitialMotionX;
private float mInitialMotionY;
private final ViewDragHelper mDragHelper;
private boolean mFirstLayout = true;
private final Rect mTmpRect = new Rect();
private final ArrayList<DisableLayerRunnable> mPostedRunnables =
new ArrayList<DisableLayerRunnable>();
static final SlidingPanelLayoutImpl IMPL;
static {
final int deviceVersion = Build.VERSION.SDK_INT;
if (deviceVersion >= 16) {
IMPL = new SlidingPanelLayoutImplJB();
} else {
IMPL = new SlidingPanelLayoutImplBase();
}
}
private PanelSlideListener mPanelSlideListener;
/**
* Listener for monitoring events about the sliding pane.
*/
public interface PanelSlideListener {
/**
* Called when the sliding pane position changes.
* @param slideOffset The new offset of this sliding pane within its range, from 0-1
*/
public void onPanelSlide(float slideOffset);
/**
* Called when the sliding pane becomes slid open.
*/
public void onPanelOpened();
/**
* Called when the sliding pane becomes slid open entirely.
*/
public void onPanelOpenedEntirely();
/**
* Called when a sliding pane becomes slid completely closed.
*/
public void onPanelClosed();
}
public SlidingPaneLayout(Context context) {
this(context, null);
}
public SlidingPaneLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mOverhangSize = -1;
if (attrs != null) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingPaneLayout);
if (ta != null) {
mOverhangSize = ta.getDimensionPixelSize(R.styleable.SlidingPaneLayout_overhangSize, -1);
ta.recycle();
}
}
final float density = context.getResources().getDisplayMetrics().density;
if (mOverhangSize == -1) {
mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
}
setWillNotDraw(false);
ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate());
ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
mDragHelper.setMinVelocity(MIN_FLING_VELOCITY * density);
}
/**
* Set the panel slide listener.
* @param l the PanelSlideListener
*/
public void setPanelSlideListener(PanelSlideListener l) {
mPanelSlideListener = l;
}
/**
* Set the color used to fade the pane covered by the sliding pane out when the pane
* will become fully covered in the closed state.
*
* @param color An ARGB-packed color value
*/
public void setCoveredFadeColor(int color) {
mCoveredFadeColor = color;
}
/**
* @return The ARGB-packed color value used to fade the fixed pane
*/
public int getCoveredFadeColor() {
return mCoveredFadeColor;
}
void updateObscuredViewsVisibility(View panel) {
final int leftBound = getPaddingLeft();
final int rightBound = getWidth() - getPaddingRight();
final int topBound = getPaddingTop();
final int bottomBound = getHeight() - getPaddingBottom();
final int left;
final int right;
final int top;
final int bottom;
if (panel != null && viewIsOpaque(panel)) {
left = panel.getLeft();
right = panel.getRight();
top = panel.getTop();
bottom = panel.getBottom();
} else {
left = right = top = bottom = 0;
}
for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
final View child = getChildAt(i);
if (child == panel) {
// There are still more children above the panel but they won't be affected.
break;
}
final int clampedChildLeft = Math.max(leftBound, child.getLeft());
final int clampedChildTop = Math.max(topBound, child.getTop());
final int clampedChildRight = Math.min(rightBound, child.getRight());
final int clampedChildBottom = Math.min(bottomBound, child.getBottom());
final int vis;
if (clampedChildLeft >= left && clampedChildTop >= top &&
clampedChildRight <= right && clampedChildBottom <= bottom) {
vis = INVISIBLE;
} else {
vis = VISIBLE;
}
child.setVisibility(vis);
}
}
void setAllChildrenVisible() {
for (int i = 0, childCount = getChildCount(); i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == INVISIBLE) {
child.setVisibility(VISIBLE);
}
}
}
private static boolean viewIsOpaque(View v) {
if (ViewCompat.isOpaque(v)) return true;
// View#isOpaque didn't take all valid opaque scrollbar modes into account
// before API 18 (JB-MR2). On newer devices rely solely on isOpaque above and return false
// here. On older devices, check the view's background drawable directly as a fallback.
if (Build.VERSION.SDK_INT >= 18) return false;
final Drawable bg = v.getBackground();
if (bg != null) {
return bg.getOpacity() == PixelFormat.OPAQUE;
}
return false;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mFirstLayout = true;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mFirstLayout = true;
for (int i = 0, count = mPostedRunnables.size(); i < count; i++) {
final DisableLayerRunnable dlr = mPostedRunnables.get(i);
dlr.run();
}
mPostedRunnables.clear();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
if (isInEditMode()) {
// Don't crash the layout editor. Consume all of the space if specified
// or pick a magic number from thin air otherwise.
// TODO Better communication with tools of this bogus state.
// It will crash on a real device.
if (heightMode == MeasureSpec.AT_MOST) {
heightMode = MeasureSpec.EXACTLY;
} else if (heightMode == MeasureSpec.UNSPECIFIED) {
heightMode = 300;
}
} else {
throw new IllegalStateException("Height must have an exact value or MATCH_PARENT");
}
} else if (widthMode == MeasureSpec.UNSPECIFIED) {
if (isInEditMode()) {
// Don't crash the layout editor. Pick a magic number from thin air instead.
// TODO Better communication with tools of this bogus state.
// It will crash on a real device.
if (widthMode == MeasureSpec.UNSPECIFIED) {
widthMode = 300;
}
} else {
throw new IllegalStateException("Width must not be UNSPECIFIED");
}
}
int layoutWidth = 0;
int maxLayoutWidth = -1;
switch (widthMode) {
case MeasureSpec.EXACTLY:
layoutWidth = maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
break;
case MeasureSpec.AT_MOST:
maxLayoutWidth = widthSize - getPaddingLeft() - getPaddingRight();
break;
}
boolean canSlide = false;
final int heightAvailable = heightSize - getPaddingTop() - getPaddingBottom();
final int childCount = getChildCount();
if (childCount > 2) {
Log.e(TAG, "onMeasure: More than two child views are not supported.");
}
// We'll find the current one below.
mSlideableView = null;
// First pass. Measure based on child LayoutParams width/height.
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (child.getVisibility() == GONE) {
continue;
}
int childHeightSpec;
final int verticalMargin = lp.topMargin + lp.bottomMargin;
if (lp.height == LayoutParams.WRAP_CONTENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin,
MeasureSpec.AT_MOST);
} else if (lp.height == LayoutParams.MATCH_PARENT) {
childHeightSpec = MeasureSpec.makeMeasureSpec(heightAvailable - verticalMargin,
MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
}
int childWidthSpec;
if (lp.width == LayoutParams.WRAP_CONTENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.AT_MOST);
} else if (lp.width == LayoutParams.MATCH_PARENT) {
childWidthSpec = MeasureSpec.makeMeasureSpec(maxLayoutWidth, MeasureSpec.EXACTLY);
} else {
childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
}
child.measure(childWidthSpec, childHeightSpec);
final int childWidth = child.getMeasuredWidth();
if (widthMode == MeasureSpec.AT_MOST && childWidth > layoutWidth) {
layoutWidth = Math.min(childWidth, maxLayoutWidth);
}
if (i == childCount - 1) {
canSlide |= lp.slideable = true;
mSlideableView = child;
}
}
final int measuredHeight = heightSize;
final int measuredWidth = layoutWidth + getPaddingLeft() + getPaddingRight();
setMeasuredDimension(measuredWidth, measuredHeight);
mCanSlide = canSlide;
if (mDragHelper.getViewDragState() != ViewDragHelper.STATE_IDLE && !canSlide) {
// Cancel scrolling in progress, it's no longer relevant.
mDragHelper.abort();
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int height = b - t;
final int paddingTop = getPaddingTop();
final int paddingBottom = getPaddingBottom();
final int paddingLeft = getPaddingLeft();
final int childCount = getChildCount();
int yStart = paddingTop;
int nextYStart = yStart;
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int childHeight = child.getMeasuredHeight();
int offset = 0;
if (lp.slideable) {
final int margin = lp.topMargin + lp.bottomMargin;
final int range = Math.min(nextYStart, height - paddingBottom) - yStart - margin;
mSlideRange = range;
if (mFirstLayout) {
switch (mState) {
case STATE_CLOSED:
mSlideOffset = mCanSlide ? 0.f : 1.f;
break;
case STATE_OPENED:
mSlideOffset = mCanSlide ? 1 - (float)mOverhangSize / mSlideRange : 1.f;
break;
default: // STATE_OPENED_ENTIRELY
mSlideOffset = 1.f;
break;
}
}
final int pos = (int) (range * mSlideOffset);
yStart += pos + lp.topMargin;
mSlideOffset = (float) pos / mSlideRange;
} else {
yStart = nextYStart;
}
final int childTop = yStart - offset;
final int childBottom = childTop + childHeight;
final int childLeft = paddingLeft;
final int childRight = childLeft + child.getMeasuredWidth();
child.layout(paddingLeft, childTop, childRight, childBottom);
nextYStart += child.getHeight();
}
if (mFirstLayout) {
updateObscuredViewsVisibility(mSlideableView);
}
mFirstLayout = false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// Recalculate sliding panes and their details
if (h != oldh) {
mFirstLayout = true;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
mDragHelper.cancel();
return super.onInterceptTouchEvent(ev);
}
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
mDragHelper.cancel();
return false;
}
float x,y;
switch (action) {
case MotionEvent.ACTION_DOWN:
mIsUnableToDrag = false;
x = ev.getX();
y = ev.getY();
mInitialMotionX = x;
mInitialMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
x = ev.getX();
y = ev.getY();
final float adx = Math.abs(x - mInitialMotionX);
final float ady = Math.abs(y - mInitialMotionY);
final int slop = mDragHelper.getTouchSlop();
if (ady > slop && adx > ady) {
mDragHelper.cancel();
mIsUnableToDrag = true;
return false;
}
}
final boolean interceptForDrag = mDragHelper.shouldInterceptTouchEvent(ev)
// Intercept touch events only in the overhang area.
&& (ev.getY() <= mSlideOffset * mSlideRange + mOverhangSize && mState != STATE_OPENED_ENTIRELY);
return interceptForDrag;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!mCanSlide) {
return super.onTouchEvent(ev);
}
try {
mDragHelper.processTouchEvent(ev);
}
catch (IllegalArgumentException ex) { }
catch (ArrayIndexOutOfBoundsException ex) { }
return true;
}
private boolean closePane(View pane, int initialVelocity) {
if (mFirstLayout) {
mState = STATE_CLOSED;
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelClosed();
return true;
}
else if (smoothSlideTo(0.f, initialVelocity))
return true;
else
return false;
}
private boolean openPaneEntirely(View pane, int initialVelocity) {
if (mFirstLayout) {
mState = STATE_OPENED_ENTIRELY;
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelOpenedEntirely();
return true;
}
else if (smoothSlideTo(1.f, initialVelocity))
return true;
else
return false;
}
private boolean openPane(View pane, int initialVelocity) {
if (mFirstLayout) {
mState = STATE_OPENED;
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelOpened();
return true;
}
else if (smoothSlideTo(1 - (float)mOverhangSize / mSlideRange, initialVelocity))
return true;
else
return false;
}
/**
* Open partially the sliding pane if it is currently slideable. If first layout
* has already completed this will animate.
*
* @return true if the pane was slideable and is now open/in the process of opening
*/
public boolean openPane() {
return openPane(mSlideableView, 0);
}
/**
* Open entirely the sliding pane if it is currently slideable. If first layout
* has already completed this will animate.
*
* @return true if the pane was slideable and is now closed/in the process of closing
*/
public boolean openPaneEntirely() {
return openPaneEntirely(mSlideableView, 0);
}
/**
* Close the sliding pane if it is currently slideable. If first layout
* has already completed this will animate.
*
* @return true if the pane was slideable and is now closed/in the process of closing
*/
public boolean closePane() {
return closePane(mSlideableView, 0);
}
/**
* Return the open state of the pane.
* @return STATE_OPEN, STATE_OPEN_ENTIRELY or STATE_CLOSED
*/
public int getState()
{
return mState;
}
/**
* Check if the content in this layout cannot fully fit side by side and therefore
* the content pane can be slid back and forth.
*
* @return true if content in this layout can be slid open and closed
*/
public boolean isSlideable() {
return mCanSlide;
}
private void onPanelDragged(int newTop) {
if (mSlideableView == null) {
// This can happen if we're aborting motion during layout because everything now fits.
mSlideOffset = 0;
return;
}
final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
final int topBound = getPaddingTop() + lp.topMargin;
mSlideOffset = (float) (newTop - topBound) / mSlideRange;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
boolean result;
final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
if (mCanSlide && !lp.slideable && mSlideableView != null) {
// Clip against the slider; no sense drawing what will immediately be covered.
canvas.getClipBounds(mTmpRect);
mTmpRect.bottom = Math.min(mTmpRect.bottom, mSlideableView.getTop());
canvas.clipRect(mTmpRect);
}
result = super.drawChild(canvas, child, drawingTime);
canvas.restoreToCount(save);
return result;
}
private void invalidateChildRegion(View v) {
IMPL.invalidateChildRegion(this, v);
}
/**
* Smoothly animate mDraggingPane to the target X position within its range.
*
* @param slideOffset position to animate to
* @param velocity initial velocity in case of fling, or 0.
*/
boolean smoothSlideTo(float slideOffset, int velocity) {
if (!mCanSlide) {
// Nothing to do.
return false;
}
final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
final int topBound = getPaddingTop() + lp.topMargin;
int y = (int) (topBound + slideOffset * mSlideRange);
if (mDragHelper.smoothSlideViewTo(mSlideableView, mSlideableView.getLeft(), y)) {
setAllChildrenVisible();
ViewCompat.postInvalidateOnAnimation(this);
return true;
}
return false;
}
@Override
public void computeScroll() {
if (mDragHelper.continueSettling(true)) {
if (!mCanSlide) {
mDragHelper.abort();
return;
}
ViewCompat.postInvalidateOnAnimation(this);
}
}
/**
* Set a drawable to use as a shadow cast by the right pane onto the left pane
* during opening/closing.
*
* @param d drawable to use as a shadow
*/
public void setShadowDrawable(Drawable d) {
mShadowDrawable = d;
}
/**
* Set a drawable to use as a shadow cast by the right pane onto the left pane
* during opening/closing.
*
* @param resId Resource ID of a drawable to use
*/
public void setShadowResource(int resId) {
setShadowDrawable(getResources().getDrawable(resId));
}
@Override
public void draw(Canvas c) {
super.draw(c);
final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
if (shadowView == null || mShadowDrawable == null) {
// No need to draw a shadow if we don't have one.
return;
}
final int shadowHeight = mShadowDrawable.getIntrinsicHeight();
final int right = shadowView.getRight();
final int left = shadowView.getLeft();
final int bottom = shadowView.getTop();
final int top = bottom - shadowHeight;
mShadowDrawable.setBounds(left, top, right, bottom);
mShadowDrawable.draw(c);
}
/**
* Tests scrollability within child views of v given a delta of dy.
*
* @param v View to test for vertical scrollability
* @param checkV Whether the view v passed should itself be checked for scrollability (true),
* or just its children (false).
* @param dy Delta scrolled in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dy.
*/
protected boolean canScroll(View v, boolean checkV, int dy, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int scrollX = v.getScrollX();
final int scrollY = v.getScrollY();
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
// TODO: Add versioned support here for transformed views.
// This will not work for transformed views in Honeycomb+
final View child = group.getChildAt(i);
if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
canScroll(child, true, dy, x + scrollX - child.getLeft(),
y + scrollY - child.getTop())) {
return true;
}
}
}
return checkV && ViewCompat.canScrollVertically(v, -dy);
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof MarginLayoutParams
? new LayoutParams((MarginLayoutParams) p)
: new LayoutParams(p);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof LayoutParams && super.checkLayoutParams(p);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.state = mState;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
switch(ss.state)
{
case STATE_OPENED:
openPane();
break;
case STATE_OPENED_ENTIRELY:
openPaneEntirely();
break;
case STATE_CLOSED:
closePane();
}
}
private class DragHelperCallback extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
if (mIsUnableToDrag) {
return false;
}
return ((LayoutParams) child.getLayoutParams()).slideable;
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
// Make all child views visible in preparation for sliding things around
setAllChildrenVisible();
}
@Override
public void onViewDragStateChanged(int state) {
if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_IDLE) {
if (mSlideOffset == 0) {
if (mState != STATE_CLOSED) {
mState = STATE_CLOSED;
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelClosed();
}
} else if (Math.abs(mSlideOffset - (1 - (float)mOverhangSize / mSlideRange))
<= 0.001) {
if (mState != STATE_OPENED) {
mState = STATE_OPENED;
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelOpened();
}
} else if (mSlideOffset == 1) {
if (mState != STATE_OPENED_ENTIRELY) {
mState = STATE_OPENED_ENTIRELY;
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelOpenedEntirely();
}
}
}
}
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
onPanelDragged(top);
if (mPanelSlideListener != null)
mPanelSlideListener.onPanelSlide(mSlideOffset);
invalidate();
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
final LayoutParams lp = (LayoutParams) releasedChild.getLayoutParams();
int top = getPaddingTop() + lp.topMargin;
if (yvel > 0 || (yvel == 0 && mSlideOffset > 0.5f)) {
top += mSlideRange - mOverhangSize;
}
mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
invalidate();
}
@Override
public int getViewVerticalDragRange(View child) {
return mSlideRange;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
final int topBound = getPaddingTop() + lp.topMargin;
final int bottomBound = topBound + mSlideRange - mOverhangSize;
final int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// Make sure we never move views horizontally.
// This could happen if the child has less width than its parent.
return child.getLeft();
}
}
/**
* Test if the second child is displayed under the coordinates given in parameters.
* @param x
* @param y
* @return true if the second child is displayed else false.
*/
public boolean isSecondChildUnder(int x, int y) {
View secondChild = getChildAt(1);
if (secondChild == null)
return false;
return mDragHelper.isViewUnder(secondChild, x, y);
}
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
/**
* True if this pane is the slidable pane in the layout.
*/
boolean slideable;
public LayoutParams() {
super(MATCH_PARENT, MATCH_PARENT);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(android.view.ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(LayoutParams source) {
super(source);
}
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
}
static class SavedState extends BaseSavedState {
int state;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
state = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(state);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
interface SlidingPanelLayoutImpl {
void invalidateChildRegion(SlidingPaneLayout parent, View child);
}
static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
@Override
public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
child.getRight(), child.getBottom());
}
}
static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
/*
* Private API hacks! Nasty! Bad!
*
* In Jellybean, some optimizations in the hardware UI renderer
* prevent a changed Paint on a View using a hardware layer from having
* the intended effect. This twiddles some internal bits on the view to force
* it to recreate the display list.
*/
private Method mGetDisplayList;
private Field mRecreateDisplayList;
SlidingPanelLayoutImplJB() {
try {
mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
} catch (NoSuchMethodException e) {
Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
}
try {
mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
mRecreateDisplayList.setAccessible(true);
} catch (NoSuchFieldException e) {
Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
}
}
@Override
public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
if (mGetDisplayList != null && mRecreateDisplayList != null) {
try {
mRecreateDisplayList.setBoolean(child, true);
mGetDisplayList.invoke(child, (Object[]) null);
} catch (Exception e) {
Log.e(TAG, "Error refreshing display list state", e);
}
} else {
// Slow path. REALLY slow path. Let's hope we don't get here.
child.invalidate();
return;
}
super.invalidateChildRegion(parent, child);
}
}
class AccessibilityDelegate extends AccessibilityDelegateCompat {
private final Rect mTmpRect = new Rect();
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
final AccessibilityNodeInfoCompat superNode = AccessibilityNodeInfoCompat.obtain(info);
super.onInitializeAccessibilityNodeInfo(host, superNode);
copyNodeInfoNoChildren(info, superNode);
superNode.recycle();
info.setClassName(SlidingPaneLayout.class.getName());
info.setSource(host);
final ViewParent parent = ViewCompat.getParentForAccessibility(host);
if (parent instanceof View) {
info.setParent((View) parent);
}
// This is a best-approximation of addChildrenForAccessibility()
// that accounts for filtering.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
// Force importance to "yes" since we can't read the value.
ViewCompat.setImportantForAccessibility(
child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
info.addChild(child);
}
}
}
@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
event.setClassName(SlidingPaneLayout.class.getName());
}
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
AccessibilityEvent event) {
return super.onRequestSendAccessibilityEvent(host, child, event);
}
/**
* This should really be in AccessibilityNodeInfoCompat, but there unfortunately
* seem to be a few elements that are not easily cloneable using the underlying API.
* Leave it private here as it's not general-purpose useful.
*/
private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest,
AccessibilityNodeInfoCompat src) {
final Rect rect = mTmpRect;
src.getBoundsInParent(rect);
dest.setBoundsInParent(rect);
src.getBoundsInScreen(rect);
dest.setBoundsInScreen(rect);
dest.setVisibleToUser(src.isVisibleToUser());
dest.setPackageName(BuildConfig.APPLICATION_ID);
dest.setClassName(src.getClassName());
dest.setContentDescription(src.getContentDescription());
dest.setEnabled(src.isEnabled());
dest.setClickable(src.isClickable());
dest.setFocusable(src.isFocusable());
dest.setFocused(src.isFocused());
dest.setAccessibilityFocused(src.isAccessibilityFocused());
dest.setSelected(src.isSelected());
dest.setLongClickable(src.isLongClickable());
dest.addAction(src.getActions());
dest.setMovementGranularities(src.getMovementGranularities());
}
}
private class DisableLayerRunnable implements Runnable {
final View mChildView;
@SuppressWarnings("unused")
DisableLayerRunnable(View childView) {
mChildView = childView;
}
@Override
public void run() {
if (mChildView.getParent() == SlidingPaneLayout.this) {
ViewCompat.setLayerType(mChildView, ViewCompat.LAYER_TYPE_NONE, null);
invalidateChildRegion(mChildView);
}
mPostedRunnables.remove(this);
}
}
}