SlidingPaneLayout.java revision 1e43161e9e1f1dc10637a68d5c2304c1f95c9c46
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.PorterDuff;
25import android.graphics.PorterDuffColorFilter;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.os.Build;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.support.v4.view.MotionEventCompat;
32import android.support.v4.view.VelocityTrackerCompat;
33import android.support.v4.view.ViewCompat;
34import android.support.v4.view.ViewConfigurationCompat;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.view.MotionEvent;
38import android.view.VelocityTracker;
39import android.view.View;
40import android.view.ViewConfiguration;
41import android.view.ViewGroup;
42import android.view.animation.Interpolator;
43import android.widget.Scroller;
44
45import java.lang.reflect.Field;
46import java.lang.reflect.Method;
47
48/**
49 * SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level
50 * of a UI. A left (or first) pane is treated as a content list or browser, subordinate to a
51 * primary detail view for displaying content.
52 *
53 * <p>Child views may overlap if their combined width exceeds the available width
54 * in the SlidingPaneLayout. When this occurs the user may slide the topmost view out of the way
55 * by dragging it, or by navigating in the direction of the overlapped view using a keyboard.
56 * If the content of the dragged child view is itself horizontally scrollable, the user may
57 * grab it by the very edge.</p>
58 *
59 * <p>Thanks to this sliding behavior, SlidingPaneLayout may be suitable for creating layouts
60 * that can smoothly adapt across many different screen sizes, expanding out fully on larger
61 * screens and collapsing on smaller screens.</p>
62 *
63 * <p>SlidingPaneLayout is distinct from a navigation drawer as described in the design
64 * guide and should not be used in the same scenarios. SlidingPaneLayout should be thought
65 * of only as a way to allow a two-pane layout normally used on larger screens to adapt to smaller
66 * screens in a natural way. The interaction patterns expressed by SlidingPaneLayout imply
67 * a physicality and direct information hierarchy between panes that does not necessarily exist
68 * in a scenario where a navigation drawer should be used instead.</p>
69 *
70 * <p>Appropriate uses of SlidingPaneLayout include pairings of panes such as a contact list and
71 * subordinate interactions with those contacts, or an email thread list with the content pane
72 * displaying the contents of the selected thread. Inappropriate uses of SlidingPaneLayout include
73 * switching between disparate functions of your app, such as jumping from a social stream view
74 * to a view of your personal profile - cases such as this should use the navigation drawer
75 * pattern instead. (TODO: insert doc link to nav drawer widget.)</p>
76 *
77 * <p>Like {@link android.widget.LinearLayout LinearLayout}, SlidingPaneLayout supports
78 * the use of the layout parameter <code>layout_weight</code> on child views to determine
79 * how to divide leftover space after measurement is complete. It is only relevant for width.
80 * When views do not overlap weight behaves as it does in a LinearLayout.</p>
81 *
82 * <p>When views do overlap, weight on a slideable pane indicates that the pane should be
83 * sized to fill all available space in the closed state. Weight on a pane that becomes covered
84 * indicates that the pane should be sized to fill all available space except a small minimum strip
85 * that the user may use to grab the slideable view and pull it back over into a closed state.</p>
86 *
87 * <p>Experimental. This class may be removed.</p>
88 */
89public class SlidingPaneLayout extends ViewGroup {
90    private static final String TAG = "SlidingPaneLayout";
91
92    /**
93     * Default size of the touch gutter along the edge where the user
94     * may grab and drag a sliding pane, even if its internal content
95     * may horizontally scroll.
96     */
97    private static final int DEFAULT_GUTTER_SIZE = 16; // dp
98
99    /**
100     * Default size of the overhang for a pane in the open state.
101     * At least this much of a sliding pane will remain visible.
102     * This indicates that there is more content available and provides
103     * a "physical" edge to grab to pull it closed.
104     */
105    private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
106
107    private static final int MAX_SETTLE_DURATION = 600; // ms
108
109    /**
110     * If no fade color is given by default it will fade to 80% gray.
111     */
112    private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
113
114    /**
115     * Base duration for programmatic scrolling of the sliding pane.
116     * This will be increased relative to the distance to be covered.
117     */
118    private static final int BASE_SCROLL_DURATION = 256; // ms
119
120    /**
121     * The fade color used for the sliding panel. 0 = no fading.
122     */
123    private int mSliderFadeColor = DEFAULT_FADE_COLOR;
124
125    /**
126     * The fade color used for the panel covered by the slider. 0 = no fading.
127     */
128    private int mCoveredFadeColor;
129
130    /**
131     * Drawable used to draw the shadow between panes.
132     */
133    private Drawable mShadowDrawable;
134
135    /**
136     * The size of the touch gutter in pixels
137     */
138    private final int mGutterSize;
139
140    /**
141     * The size of the overhang in pixels.
142     * This is the minimum section of the sliding panel that will
143     * be visible in the open state to allow for a closing drag.
144     */
145    private final int mOverhangSize;
146
147    /**
148     * True if a panel can slide with the current measurements
149     */
150    private boolean mCanSlide;
151
152    /**
153     * The child view that can slide, if any.
154     */
155    private View mSlideableView;
156
157    /**
158     * How far the panel is offset from its closed position.
159     * range [0, 1] where 0 = closed, 1 = open.
160     */
161    private float mSlideOffset;
162
163    /**
164     * How far the non-sliding panel is parallaxed from its usual position when open.
165     * range [0, 1]
166     */
167    private float mParallaxOffset;
168
169    /**
170     * How far in pixels the slideable panel may move.
171     */
172    private int mSlideRange;
173
174    /**
175     * A panel view is locked into internal scrolling or another condition that
176     * is preventing a drag.
177     */
178    private boolean mIsUnableToDrag;
179
180    /**
181     * Distance in pixels to parallax the fixed pane by when fully closed
182     */
183    private int mParallaxBy;
184
185    private int mTouchSlop;
186    private float mInitialMotionX;
187    private float mInitialMotionY;
188    private float mLastMotionX;
189    private float mLastMotionY;
190    private int mActivePointerId = INVALID_POINTER;
191
192    private VelocityTracker mVelocityTracker;
193    private float mMaxVelocity;
194
195    private PanelSlideListener mPanelSlideListener;
196
197    private static final int INVALID_POINTER = -1;
198
199    /**
200     * Indicates that the panels are in an idle, settled state. The current panel
201     * is fully in view and no animation is in progress.
202     */
203    public static final int SCROLL_STATE_IDLE = 0;
204
205    /**
206     * Indicates that a panel is currently being dragged by the user.
207     */
208    public static final int SCROLL_STATE_DRAGGING = 1;
209
210    /**
211     * Indicates that a panel is in the process of settling to a final position.
212     */
213    public static final int SCROLL_STATE_SETTLING = 2;
214
215    private int mScrollState = SCROLL_STATE_IDLE;
216
217    private final Rect mTmpRect = new Rect();
218
219    /**
220     * Interpolator defining the animation curve for mScroller
221     */
222    private static final Interpolator sInterpolator = new Interpolator() {
223        public float getInterpolation(float t) {
224            t -= 1.0f;
225            return t * t * t * t * t + 1.0f;
226        }
227    };
228
229    /**
230     * Used to animate flinging panes.
231     */
232    private final Scroller mScroller;
233
234    static final SlidingPanelLayoutImpl IMPL;
235
236    static {
237        final int deviceVersion = Build.VERSION.SDK_INT;
238        if (deviceVersion >= 17) {
239            IMPL = new SlidingPanelLayoutImplJBMR1();
240        } else if (deviceVersion >= 16) {
241            IMPL = new SlidingPanelLayoutImplJB();
242        } else {
243            IMPL = new SlidingPanelLayoutImplBase();
244        }
245    }
246
247    /**
248     * Listener for monitoring events about sliding panes.
249     */
250    public interface PanelSlideListener {
251        /**
252         * Called when a sliding pane's position changes.
253         * @param panel The child view that was moved
254         * @param slideOffset The new offset of this sliding pane within its range, from 0-1
255         */
256        public void onPanelSlide(View panel, float slideOffset);
257        /**
258         * Called when a sliding pane becomes slid completely open. The pane may or may not
259         * be interactive at this point depending on how much of the pane is visible.
260         * @param panel The child view that was slid to an open position, revealing other panes
261         */
262        public void onPanelOpened(View panel);
263
264        /**
265         * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
266         * to be interactive. It may now obscure other views in the layout.
267         * @param panel The child view that was slid to a closed position
268         */
269        public void onPanelClosed(View panel);
270    }
271
272    /**
273     * No-op stubs for {@link PanelSlideListener}. If you only want to implement a subset
274     * of the listener methods you can extend this instead of implement the full interface.
275     */
276    public static class SimplePanelSlideListener implements PanelSlideListener {
277        @Override
278        public void onPanelSlide(View panel, float slideOffset) {
279        }
280        @Override
281        public void onPanelOpened(View panel) {
282        }
283        @Override
284        public void onPanelClosed(View panel) {
285        }
286    }
287
288    public SlidingPaneLayout(Context context) {
289        this(context, null);
290    }
291
292    public SlidingPaneLayout(Context context, AttributeSet attrs) {
293        this(context, attrs, 0);
294    }
295
296    public SlidingPaneLayout(Context context, AttributeSet attrs, int defStyle) {
297        super(context, attrs, defStyle);
298
299        mScroller = new Scroller(context, sInterpolator);
300
301        final float density = context.getResources().getDisplayMetrics().density;
302        mGutterSize = (int) (DEFAULT_GUTTER_SIZE * density + 0.5f);
303        mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
304
305        final ViewConfiguration viewConfig = ViewConfiguration.get(context);
306        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(viewConfig);
307        mMaxVelocity = viewConfig.getScaledMaximumFlingVelocity();
308
309        setWillNotDraw(false);
310    }
311
312    /**
313     * Set a distance to parallax the lower pane by when the upper pane is in its
314     * fully closed state. The lower pane will scroll between this position and
315     * its fully open state.
316     *
317     * @param parallaxBy Distance to parallax by in pixels
318     */
319    public void setParallaxDistance(int parallaxBy) {
320        mParallaxBy = parallaxBy;
321        requestLayout();
322    }
323
324    /**
325     * @return The distance the lower pane will parallax by when the upper pane is fully closed.
326     *
327     * @see #setParallaxDistance(int)
328     */
329    public int getParallaxDistance() {
330        return mParallaxBy;
331    }
332
333    /**
334     * Set the color used to fade the sliding pane out when it is slid most of the way offscreen.
335     *
336     * @param color An ARGB-packed color value
337     */
338    public void setSliderFadeColor(int color) {
339        mSliderFadeColor = color;
340    }
341
342    /**
343     * @return The ARGB-packed color value used to fade the sliding pane
344     */
345    public int getSliderFadeColor() {
346        return mSliderFadeColor;
347    }
348
349    /**
350     * Set the color used to fade the pane covered by the sliding pane out when the pane
351     * will become fully covered in the closed state.
352     *
353     * @param color An ARGB-packed color value
354     */
355    public void setCoveredFadeColor(int color) {
356        mCoveredFadeColor = color;
357    }
358
359    /**
360     * @return The ARGB-packed color value used to fade the fixed pane
361     */
362    public int getCoveredFadeColor() {
363        return mCoveredFadeColor;
364    }
365
366    void setScrollState(int state) {
367        if (mScrollState != state) {
368            mScrollState = state;
369        }
370    }
371
372    public void setPanelSlideListener(PanelSlideListener listener) {
373        mPanelSlideListener = listener;
374    }
375
376    void dispatchOnPanelSlide(View panel) {
377        if (mPanelSlideListener != null) {
378            mPanelSlideListener.onPanelSlide(panel, mSlideOffset);
379        }
380    }
381
382    void dispatchOnPanelOpened(View panel) {
383        if (mPanelSlideListener != null) {
384            mPanelSlideListener.onPanelOpened(panel);
385        }
386    }
387
388    void dispatchOnPanelClosed(View panel) {
389        if (mPanelSlideListener != null) {
390            mPanelSlideListener.onPanelClosed(panel);
391        }
392    }
393
394    @Override
395    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
396        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
397        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
398        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
399        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
400
401        if (widthMode != MeasureSpec.EXACTLY) {
402            throw new IllegalStateException("Width must have an exact value or MATCH_PARENT");
403        } else if (heightMode == MeasureSpec.UNSPECIFIED) {
404            throw new IllegalStateException("Height must not be UNSPECIFIED");
405        }
406
407        int layoutHeight = 0;
408        int maxLayoutHeight = -1;
409        switch (heightMode) {
410            case MeasureSpec.EXACTLY:
411                layoutHeight = maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
412                break;
413            case MeasureSpec.AT_MOST:
414                maxLayoutHeight = heightSize - getPaddingTop() - getPaddingBottom();
415                break;
416        }
417
418        float weightSum = 0;
419        boolean canSlide = false;
420        int widthRemaining = widthSize - getPaddingLeft() - getPaddingRight();
421        final int childCount = getChildCount();
422
423        if (childCount > 2) {
424            Log.e(TAG, "onMeasure: More than two child views are not supported.");
425        }
426
427        // We'll find the current one below.
428        mSlideableView = null;
429
430        // First pass. Measure based on child LayoutParams width/height.
431        // Weight will incur a second pass.
432        for (int i = 0; i < childCount; i++) {
433            final View child = getChildAt(i);
434            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
435
436            if (child.getVisibility() == GONE) {
437                lp.dimWhenOffset = false;
438                continue;
439            }
440
441            if (lp.weight > 0) {
442                weightSum += lp.weight;
443
444                // If we have no width, weight is the only contributor to the final size.
445                // Measure this view on the weight pass only.
446                if (lp.width == 0) continue;
447            }
448
449            int childWidthSpec;
450            final int horizontalMargin = lp.leftMargin + lp.rightMargin;
451            if (lp.width == LayoutParams.WRAP_CONTENT) {
452                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin,
453                        MeasureSpec.AT_MOST);
454            } else if (lp.width == LayoutParams.FILL_PARENT) {
455                childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - horizontalMargin,
456                        MeasureSpec.EXACTLY);
457            } else {
458                childWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
459            }
460
461            int childHeightSpec;
462            if (lp.height == LayoutParams.WRAP_CONTENT) {
463                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.AT_MOST);
464            } else if (lp.height == LayoutParams.FILL_PARENT) {
465                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight, MeasureSpec.EXACTLY);
466            } else {
467                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
468            }
469
470            child.measure(childWidthSpec, childHeightSpec);
471            final int childWidth = child.getMeasuredWidth();
472            final int childHeight = child.getMeasuredHeight();
473
474            if (heightMode == MeasureSpec.AT_MOST && childHeight > layoutHeight) {
475                layoutHeight = Math.min(childHeight, maxLayoutHeight);
476            }
477
478            widthRemaining -= childWidth;
479            canSlide |= lp.slideable = widthRemaining < 0;
480            if (lp.slideable) {
481                mSlideableView = child;
482            }
483        }
484
485        // Resolve weight and make sure non-sliding panels are smaller than the full screen.
486        if (canSlide || weightSum > 0) {
487            final int fixedPanelWidthLimit = widthSize - mOverhangSize;
488
489            for (int i = 0; i < childCount; i++) {
490                final View child = getChildAt(i);
491
492                if (child.getVisibility() == GONE) {
493                    continue;
494                }
495
496                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
497
498                final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
499                final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth();
500                if (canSlide && child != mSlideableView) {
501                    if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) {
502                        // Fixed panels in a sliding configuration should
503                        // be clamped to the fixed panel limit.
504                        final int childHeightSpec;
505                        if (skippedFirstPass) {
506                            // Do initial height measurement if we skipped measuring this view
507                            // the first time around.
508                            if (lp.height == LayoutParams.WRAP_CONTENT) {
509                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
510                                        MeasureSpec.AT_MOST);
511                            } else if (lp.height == LayoutParams.FILL_PARENT) {
512                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
513                                        MeasureSpec.EXACTLY);
514                            } else {
515                                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
516                                        MeasureSpec.EXACTLY);
517                            }
518                        } else {
519                            childHeightSpec = MeasureSpec.makeMeasureSpec(
520                                    child.getMeasuredHeight(), MeasureSpec.EXACTLY);
521                        }
522                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
523                                fixedPanelWidthLimit, MeasureSpec.EXACTLY);
524                        child.measure(childWidthSpec, childHeightSpec);
525                    }
526                } else if (lp.weight > 0) {
527                    int childHeightSpec;
528                    if (lp.width == 0) {
529                        // This was skipped the first time; figure out a real height spec.
530                        if (lp.height == LayoutParams.WRAP_CONTENT) {
531                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
532                                    MeasureSpec.AT_MOST);
533                        } else if (lp.height == LayoutParams.FILL_PARENT) {
534                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
535                                    MeasureSpec.EXACTLY);
536                        } else {
537                            childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
538                                    MeasureSpec.EXACTLY);
539                        }
540                    } else {
541                        childHeightSpec = MeasureSpec.makeMeasureSpec(
542                                child.getMeasuredHeight(), MeasureSpec.EXACTLY);
543                    }
544
545                    if (canSlide) {
546                        // Consume available space
547                        final int horizontalMargin = lp.leftMargin + lp.rightMargin;
548                        final int newWidth = widthSize - horizontalMargin;
549                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
550                                newWidth, MeasureSpec.EXACTLY);
551                        if (measuredWidth != newWidth) {
552                            child.measure(childWidthSpec, childHeightSpec);
553                        }
554                    } else {
555                        // Distribute the extra width proportionally similar to LinearLayout
556                        final int widthToDistribute = Math.max(0, widthRemaining);
557                        final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
558                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
559                                measuredWidth + addedWidth, MeasureSpec.EXACTLY);
560                        child.measure(childWidthSpec, childHeightSpec);
561                    }
562                }
563            }
564        }
565
566        setMeasuredDimension(widthSize, layoutHeight);
567        mCanSlide = canSlide;
568        if (mScrollState != SCROLL_STATE_IDLE && !canSlide) {
569            // Cancel scrolling in progress, it's no longer relevant.
570            setScrollState(SCROLL_STATE_IDLE);
571        }
572    }
573
574    @Override
575    protected void onLayout(boolean changed, int l, int t, int r, int b) {
576        final int width = r - l;
577        final int height = b - t;
578        final int paddingLeft = getPaddingLeft();
579        final int paddingRight = getPaddingRight();
580        final int paddingTop = getPaddingTop();
581        final int paddingBottom = getPaddingBottom();
582
583        final int childCount = getChildCount();
584        int xStart = paddingLeft;
585        int nextXStart = xStart;
586
587        for (int i = 0; i < childCount; i++) {
588            final View child = getChildAt(i);
589
590            if (child.getVisibility() == GONE) {
591                continue;
592            }
593
594            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
595
596            final int childWidth = child.getMeasuredWidth();
597            int offset = 0;
598
599            if (lp.slideable) {
600                final int margin = lp.leftMargin + lp.rightMargin;
601                final int range = Math.min(nextXStart,
602                        width - paddingRight - mOverhangSize) - xStart - margin;
603                mSlideRange = range;
604                lp.dimWhenOffset = width - paddingRight - (xStart + range) < childWidth / 2;
605                xStart += (int) (range * mSlideOffset) + lp.leftMargin;
606            } else if (mCanSlide && mParallaxBy != 0) {
607                offset = (int) ((1 - mSlideOffset) * mParallaxBy);
608                xStart = nextXStart;
609            } else {
610                xStart = nextXStart;
611            }
612
613            final int childLeft = xStart - offset;
614            final int childRight = childLeft + childWidth;
615            final int childTop = paddingTop;
616            final int childBottom = childTop + child.getMeasuredHeight();
617            child.layout(childLeft, paddingTop, childRight, childBottom);
618
619            nextXStart += child.getWidth();
620        }
621    }
622
623    @Override
624    public boolean onInterceptTouchEvent(MotionEvent ev) {
625        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
626
627        if (!mCanSlide || (mIsUnableToDrag && action != MotionEvent.ACTION_DOWN)) {
628            return super.onInterceptTouchEvent(ev);
629        }
630
631        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
632            mActivePointerId = INVALID_POINTER;
633            if (mVelocityTracker != null) {
634                mVelocityTracker.recycle();
635                mVelocityTracker = null;
636            }
637            return false;
638        }
639
640        boolean interceptTap = false;
641
642        switch (action) {
643            case MotionEvent.ACTION_MOVE: {
644                final int activePointerId = mActivePointerId;
645                if (activePointerId == INVALID_POINTER) {
646                    // No valid pointer = no valid drag. Ignore.
647                    break;
648                }
649
650                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
651                final float x = MotionEventCompat.getX(ev, pointerIndex);
652                final float dx = x - mLastMotionX;
653                final float xDiff = Math.abs(dx);
654                final float y = MotionEventCompat.getY(ev, pointerIndex);
655                final float yDiff = Math.abs(y - mLastMotionY);
656
657                if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
658                        canScroll(this, false, (int) dx, (int) x, (int) y)) {
659                    mInitialMotionX = mLastMotionX = x;
660                    mLastMotionY = y;
661                    mIsUnableToDrag = true;
662                    return false;
663                }
664                if (xDiff > mTouchSlop && xDiff > yDiff && isSlideablePaneUnder(x, y)) {
665                    mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
666                            mInitialMotionX - mTouchSlop;
667                    setScrollState(SCROLL_STATE_DRAGGING);
668                } else if (yDiff > mTouchSlop) {
669                    mIsUnableToDrag = true;
670                }
671                if (mScrollState == SCROLL_STATE_DRAGGING && performDrag(x)) {
672                    invalidate();
673                }
674                break;
675            }
676
677            case MotionEvent.ACTION_DOWN: {
678                final float x = ev.getX();
679                final float y = ev.getY();
680                mIsUnableToDrag = false;
681                mInitialMotionX = x;
682                mInitialMotionY = y;
683                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
684                if (isSlideablePaneUnder(x, y)) {
685                    mLastMotionX = x;
686                    mLastMotionY = y;
687                    if (mScrollState == SCROLL_STATE_SETTLING) {
688                        // Start dragging immediately. "Catch"
689                        setScrollState(SCROLL_STATE_DRAGGING);
690                    } else if (isDimmed(mSlideableView)) {
691                        interceptTap = true;
692                    }
693                }
694                break;
695            }
696
697            case MotionEventCompat.ACTION_POINTER_UP:
698                onSecondaryPointerUp(ev);
699                break;
700        }
701
702        if (mVelocityTracker == null) {
703            mVelocityTracker = VelocityTracker.obtain();
704        }
705        mVelocityTracker.addMovement(ev);
706        return mScrollState == SCROLL_STATE_DRAGGING || interceptTap;
707    }
708
709    @Override
710    public boolean onTouchEvent(MotionEvent ev) {
711        if (!mCanSlide) {
712            return super.onTouchEvent(ev);
713        }
714
715        if (mVelocityTracker == null) {
716            mVelocityTracker = VelocityTracker.obtain();
717        }
718        mVelocityTracker.addMovement(ev);
719
720        final int action = ev.getAction();
721        boolean needsInvalidate = false;
722        boolean wantTouchEvents = true;
723
724        switch (action & MotionEventCompat.ACTION_MASK) {
725            case MotionEvent.ACTION_DOWN: {
726                final float x = ev.getX();
727                final float y = ev.getY();
728                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
729                mInitialMotionX = x;
730                mInitialMotionY = y;
731
732                if (isSlideablePaneUnder(x, y)) {
733                    mScroller.abortAnimation();
734                    wantTouchEvents = true;
735                    mLastMotionX = x;
736                    setScrollState(SCROLL_STATE_DRAGGING);
737                }
738                break;
739            }
740
741            case MotionEvent.ACTION_MOVE: {
742                if (mScrollState != SCROLL_STATE_DRAGGING) {
743                    final int pointerIndex = MotionEventCompat.findPointerIndex(
744                            ev, mActivePointerId);
745                    final float x = MotionEventCompat.getX(ev, pointerIndex);
746                    final float y = MotionEventCompat.getY(ev, pointerIndex);
747                    final float dx = Math.abs(x - mLastMotionX);
748                    final float dy = Math.abs(y - mLastMotionY);
749                    if (dx > mTouchSlop && dx > dy && isSlideablePaneUnder(x, y)) {
750                        mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
751                                mInitialMotionX - mTouchSlop;
752                        setScrollState(SCROLL_STATE_DRAGGING);
753                    }
754                }
755                if (mScrollState == SCROLL_STATE_DRAGGING) {
756                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
757                            ev, mActivePointerId);
758                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
759                    needsInvalidate |= performDrag(x);
760                }
761                break;
762            }
763
764            case MotionEvent.ACTION_UP: {
765                if (isDimmed(mSlideableView)) {
766                    final int pi = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
767                    final float x = MotionEventCompat.getX(ev, pi);
768                    final float y = MotionEventCompat.getY(ev, pi);
769                    final float dx = x - mInitialMotionX;
770                    final float dy = y - mInitialMotionY;
771                    if (dx * dx + dy * dy < mTouchSlop * mTouchSlop && isSlideablePaneUnder(x, y)) {
772                        // Taps close a dimmed open pane.
773                        closePane(mSlideableView, 0);
774                        mActivePointerId = INVALID_POINTER;
775                        break;
776                    }
777                }
778                if (mScrollState == SCROLL_STATE_DRAGGING) {
779                    final VelocityTracker vt = mVelocityTracker;
780                    vt.computeCurrentVelocity(1000, mMaxVelocity);
781                    int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(vt,
782                            mActivePointerId);
783                    if (initialVelocity < 0 || (initialVelocity == 0 && mSlideOffset < 0.5f)) {
784                        closePane(mSlideableView, initialVelocity);
785                    } else {
786                        openPane(mSlideableView, initialVelocity);
787                    }
788                    mActivePointerId = INVALID_POINTER;
789                }
790                break;
791            }
792
793            case MotionEvent.ACTION_CANCEL: {
794                if (mScrollState == SCROLL_STATE_DRAGGING) {
795                    mActivePointerId = INVALID_POINTER;
796                    if (mSlideOffset < 0.5f) {
797                        closePane(mSlideableView, 0);
798                    } else {
799                        openPane(mSlideableView, 0);
800                    }
801                }
802                break;
803            }
804
805            case MotionEventCompat.ACTION_POINTER_DOWN: {
806                final int index = MotionEventCompat.getActionIndex(ev);
807                mLastMotionX = MotionEventCompat.getX(ev, index);
808                mLastMotionY = MotionEventCompat.getY(ev, index);
809                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
810                break;
811            }
812
813            case MotionEventCompat.ACTION_POINTER_UP: {
814                onSecondaryPointerUp(ev);
815                break;
816            }
817        }
818
819        if (needsInvalidate) {
820            invalidate();
821        }
822        return wantTouchEvents;
823    }
824
825    private void closePane(View pane, int initialVelocity) {
826        if (mCanSlide) {
827            smoothSlideTo(0.f, initialVelocity);
828        }
829    }
830
831    private void openPane(View pane, int initialVelocity) {
832        if (mCanSlide) {
833            smoothSlideTo(1.f, initialVelocity);
834        }
835    }
836
837    /**
838     * Animate the sliding panel to its open state.
839     */
840    public void smoothSlideOpen() {
841        if (mCanSlide) {
842            openPane(mSlideableView, 0);
843        }
844    }
845
846    /**
847     * Animate the sliding panel to its closed state.
848     */
849    public void smoothSlideClosed() {
850        if (mCanSlide) {
851            closePane(mSlideableView, 0);
852        }
853    }
854
855    /**
856     * @return true if sliding panels are completely open
857     */
858    public boolean isOpen() {
859        return !mCanSlide || mSlideOffset == 1;
860    }
861
862    /**
863     * @return true if content in this layout can be slid open and closed
864     */
865    public boolean canSlide() {
866        return mCanSlide;
867    }
868
869    private boolean performDrag(float x) {
870        final float dxMotion = x - mLastMotionX;
871        mLastMotionX = x;
872
873        final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
874        final int leftBound = getPaddingLeft() + lp.leftMargin;
875        final int rightBound = leftBound + mSlideRange;
876
877        final float oldLeft = mSlideableView.getLeft();
878        final float newLeft = Math.min(Math.max(oldLeft + dxMotion, leftBound), rightBound);
879
880        final int dxPane = (int) (newLeft - oldLeft);
881
882        if (dxPane == 0) {
883            return false;
884        }
885
886        mSlideableView.offsetLeftAndRight(dxPane);
887
888        mSlideOffset = (newLeft - leftBound) / mSlideRange;
889
890        if (mParallaxBy != 0) {
891            parallaxOtherViews(mSlideOffset);
892        }
893
894        mLastMotionX += newLeft - (int) newLeft;
895        if (lp.dimWhenOffset) {
896            dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
897        }
898        dispatchOnPanelSlide(mSlideableView);
899
900        return true;
901    }
902
903    private void dimChildView(View v, float mag, int fadeColor) {
904        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
905
906        if (mag > 0 && fadeColor != 0) {
907            final int baseAlpha = (fadeColor & 0xff000000) >>> 24;
908            int imag = (int) (baseAlpha * mag);
909            int color = imag << 24 | (fadeColor & 0xffffff);
910            if (lp.dimPaint == null) {
911                lp.dimPaint = new Paint();
912            }
913            lp.dimPaint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_OVER));
914            if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_HARDWARE) {
915                ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, lp.dimPaint);
916            }
917            invalidateChildRegion(v);
918        } else if (ViewCompat.getLayerType(v) != ViewCompat.LAYER_TYPE_NONE) {
919            ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_NONE, null);
920        }
921    }
922
923    @Override
924    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
925        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
926        boolean result;
927        final int save = canvas.save(Canvas.CLIP_SAVE_FLAG);
928
929        if (mCanSlide && !lp.slideable && mSlideableView != null) {
930            // Clip against the slider; no sense drawing what will immediately be covered.
931            canvas.getClipBounds(mTmpRect);
932            mTmpRect.right = Math.min(mTmpRect.right, mSlideableView.getLeft());
933            canvas.clipRect(mTmpRect);
934        }
935
936        if (Build.VERSION.SDK_INT >= 11) { // HC
937            result = super.drawChild(canvas, child, drawingTime);
938        } else {
939            if (lp.dimWhenOffset && mSlideOffset > 0) {
940                if (!child.isDrawingCacheEnabled()) {
941                    child.setDrawingCacheEnabled(true);
942                }
943                final Bitmap cache = child.getDrawingCache();
944                canvas.drawBitmap(cache, child.getLeft(), child.getTop(), lp.dimPaint);
945                result = false;
946            } else {
947                if (child.isDrawingCacheEnabled()) {
948                    child.setDrawingCacheEnabled(false);
949                }
950                result = super.drawChild(canvas, child, drawingTime);
951            }
952        }
953
954        canvas.restoreToCount(save);
955
956        return result;
957    }
958
959    private void invalidateChildRegion(View v) {
960        IMPL.invalidateChildRegion(this, v);
961    }
962
963    private boolean isGutterDrag(float x, float dx) {
964        return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
965    }
966
967    /**
968     * Smoothly animate mDraggingPane to the target X position within its range.
969     *
970     * @param slideOffset position to animate to
971     * @param velocity initial velocity in case of fling, or 0.
972     */
973    void smoothSlideTo(float slideOffset, int velocity) {
974        if (!mCanSlide) {
975            // Nothing to do.
976            return;
977        }
978
979        final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
980
981        final int leftBound = getPaddingLeft() + lp.leftMargin;
982        int sx = mSlideableView.getLeft();
983        int x = (int) (leftBound + slideOffset * mSlideRange);
984        int dx = x - sx;
985        if (dx == 0) {
986            setScrollState(SCROLL_STATE_IDLE);
987            if (mSlideOffset == 0) {
988                dispatchOnPanelClosed(mSlideableView);
989            } else {
990                dispatchOnPanelOpened(mSlideableView);
991            }
992            return;
993        }
994
995        setScrollState(SCROLL_STATE_SETTLING);
996
997        final int width = getWidth();
998        final int halfWidth = width / 2;
999        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
1000        final float distance = halfWidth + halfWidth *
1001                distanceInfluenceForSnapDuration(distanceRatio);
1002
1003        int duration = 0;
1004        velocity = Math.abs(velocity);
1005        if (velocity > 0) {
1006            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1007        } else {
1008            final float range = (float) Math.abs(dx) / mSlideRange;
1009            duration = (int) ((range + 1) * BASE_SCROLL_DURATION);
1010        }
1011        duration = Math.min(duration, MAX_SETTLE_DURATION);
1012
1013        mScroller.startScroll(sx, 0, dx, 0, duration);
1014        ViewCompat.postInvalidateOnAnimation(this);
1015    }
1016
1017    // We want the duration of the page snap animation to be influenced by the distance that
1018    // the screen has to travel, however, we don't want this duration to be effected in a
1019    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1020    // of travel has on the overall snap duration.
1021    float distanceInfluenceForSnapDuration(float f) {
1022        f -= 0.5f; // center the values about 0.
1023        f *= 0.3f * Math.PI / 2.0f;
1024        return (float) Math.sin(f);
1025    }
1026
1027    @Override
1028    public void computeScroll() {
1029        if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1030            if (!mCanSlide) {
1031                mScroller.abortAnimation();
1032                return;
1033            }
1034
1035            final int oldLeft = mSlideableView.getLeft();
1036            final int newLeft = mScroller.getCurrX();
1037            final int dx = newLeft - oldLeft;
1038            mSlideableView.offsetLeftAndRight(dx);
1039
1040            final LayoutParams lp = (LayoutParams) mSlideableView.getLayoutParams();
1041            final int leftBound = getPaddingLeft() + lp.leftMargin;
1042            mSlideOffset = (float) (newLeft - leftBound) / mSlideRange;
1043            if (lp.dimWhenOffset) {
1044                dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
1045            }
1046            dispatchOnPanelSlide(mSlideableView);
1047
1048            if (mParallaxBy != 0) {
1049                parallaxOtherViews(mSlideOffset);
1050            }
1051
1052            if (newLeft == mScroller.getFinalX()) {
1053                mScroller.abortAnimation();
1054            }
1055
1056            if (mScroller.isFinished()) {
1057                setScrollState(SCROLL_STATE_IDLE);
1058                post(new Runnable() {
1059                    public void run() {
1060                        if (mSlideOffset == 0) {
1061                            dispatchOnPanelClosed(mSlideableView);
1062                        } else {
1063                            dispatchOnPanelOpened(mSlideableView);
1064                        }
1065                    }
1066                });
1067            }
1068            ViewCompat.postInvalidateOnAnimation(this);
1069        }
1070
1071    }
1072
1073    /**
1074     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1075     * during opening/closing.
1076     *
1077     * @param d drawable to use as a shadow
1078     */
1079    public void setShadowDrawable(Drawable d) {
1080        mShadowDrawable = d;
1081    }
1082
1083    /**
1084     * Set a drawable to use as a shadow cast by the right pane onto the left pane
1085     * during opening/closing.
1086     *
1087     * @param resId Resource ID of a drawable to use
1088     */
1089    public void setShadowResource(int resId) {
1090        setShadowDrawable(getResources().getDrawable(resId));
1091    }
1092
1093    @Override
1094    public void draw(Canvas c) {
1095        super.draw(c);
1096
1097        final View shadowView = getChildCount() > 1 ? getChildAt(1) : null;
1098        if (shadowView == null || mShadowDrawable == null) {
1099            // No need to draw a shadow if we don't have one.
1100            return;
1101        }
1102
1103        final int shadowWidth = mShadowDrawable.getIntrinsicWidth();
1104        final int right = shadowView.getLeft();
1105        final int top = shadowView.getTop();
1106        final int bottom = shadowView.getBottom();
1107        final int left = right - shadowWidth;
1108        mShadowDrawable.setBounds(left, top, right, bottom);
1109        mShadowDrawable.draw(c);
1110    }
1111
1112    private void parallaxOtherViews(float slideOffset) {
1113        final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
1114        final boolean dimViews = slideLp.dimWhenOffset && slideLp.leftMargin <= 0;
1115        final int childCount = getChildCount();
1116        for (int i = 0; i < childCount; i++) {
1117            final View v = getChildAt(i);
1118            if (v == mSlideableView) continue;
1119
1120            final int oldOffset = (int) ((1 - mParallaxOffset) * mParallaxBy);
1121            mParallaxOffset = slideOffset;
1122            final int newOffset = (int) ((1 - slideOffset) * mParallaxBy);
1123            final int dx = oldOffset - newOffset;
1124
1125            v.offsetLeftAndRight(dx);
1126
1127            if (dimViews) {
1128                dimChildView(v, 1 - mParallaxOffset, mCoveredFadeColor);
1129            }
1130        }
1131    }
1132
1133    /**
1134     * Tests scrollability within child views of v given a delta of dx.
1135     *
1136     * @param v View to test for horizontal scrollability
1137     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1138     *               or just its children (false).
1139     * @param dx Delta scrolled in pixels
1140     * @param x X coordinate of the active touch point
1141     * @param y Y coordinate of the active touch point
1142     * @return true if child views of v can be scrolled by delta of dx.
1143     */
1144    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1145        if (v instanceof ViewGroup) {
1146            final ViewGroup group = (ViewGroup) v;
1147            final int scrollX = v.getScrollX();
1148            final int scrollY = v.getScrollY();
1149            final int count = group.getChildCount();
1150            // Count backwards - let topmost views consume scroll distance first.
1151            for (int i = count - 1; i >= 0; i--) {
1152                // TODO: Add versioned support here for transformed views.
1153                // This will not work for transformed views in Honeycomb+
1154                final View child = group.getChildAt(i);
1155                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1156                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1157                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
1158                                y + scrollY - child.getTop())) {
1159                    return true;
1160                }
1161            }
1162        }
1163
1164        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1165    }
1166
1167    private void onSecondaryPointerUp(MotionEvent ev) {
1168        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1169        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1170        if (pointerId == mActivePointerId) {
1171            // This was our active pointer going up. Choose a new
1172            // active pointer and adjust accordingly.
1173            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1174            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
1175            mLastMotionY = MotionEventCompat.getY(ev, newPointerIndex);
1176            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1177            if (mVelocityTracker != null) {
1178                mVelocityTracker.clear();
1179            }
1180        }
1181    }
1182
1183    boolean isSlideablePaneUnder(float x, float y) {
1184        final View child = mSlideableView;
1185        return child != null &&
1186                x >= child.getLeft() - mGutterSize &&
1187                x < child.getRight() + mGutterSize &&
1188                y >= child.getTop() &&
1189                y < child.getBottom();
1190    }
1191
1192    boolean isDimmed(View child) {
1193        if (child == null) {
1194            return false;
1195        }
1196        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1197        return mCanSlide && lp.dimWhenOffset && mSlideOffset > 0;
1198    }
1199
1200    @Override
1201    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
1202        return new LayoutParams();
1203    }
1204
1205    @Override
1206    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
1207        return p instanceof MarginLayoutParams
1208                ? new LayoutParams((MarginLayoutParams) p)
1209                : new LayoutParams(p);
1210    }
1211
1212    @Override
1213    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
1214        return p instanceof LayoutParams && super.checkLayoutParams(p);
1215    }
1216
1217    @Override
1218    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
1219        return new LayoutParams(getContext(), attrs);
1220    }
1221
1222    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
1223        private static final int[] ATTRS = new int[] {
1224            android.R.attr.layout_weight
1225        };
1226
1227        /**
1228         * The weighted proportion of how much of the leftover space
1229         * this child should consume after measurement.
1230         */
1231        public float weight = 0;
1232
1233        /**
1234         * True if this pane is the slideable pane in the layout.
1235         */
1236        boolean slideable;
1237
1238        /**
1239         * True if this view should be drawn dimmed
1240         * when it's been offset from its default position.
1241         */
1242        boolean dimWhenOffset;
1243
1244        Paint dimPaint;
1245
1246        public LayoutParams() {
1247            super(FILL_PARENT, FILL_PARENT);
1248        }
1249
1250        public LayoutParams(int width, int height) {
1251            super(width, height);
1252        }
1253
1254        public LayoutParams(android.view.ViewGroup.LayoutParams source) {
1255            super(source);
1256        }
1257
1258        public LayoutParams(MarginLayoutParams source) {
1259            super(source);
1260        }
1261
1262        public LayoutParams(LayoutParams source) {
1263            super(source);
1264            this.weight = source.weight;
1265        }
1266
1267        public LayoutParams(Context c, AttributeSet attrs) {
1268            super(c, attrs);
1269
1270            final TypedArray a = c.obtainStyledAttributes(attrs, ATTRS);
1271            this.weight = a.getFloat(0, 0);
1272            a.recycle();
1273        }
1274
1275    }
1276
1277    static class SavedState extends BaseSavedState {
1278        boolean canSlide;
1279        boolean isOpen;
1280
1281        SavedState(Parcelable superState) {
1282            super(superState);
1283        }
1284
1285        private SavedState(Parcel in) {
1286            super(in);
1287            canSlide = in.readInt() != 0;
1288            isOpen = in.readInt() != 0;
1289        }
1290
1291        @Override
1292        public void writeToParcel(Parcel out, int flags) {
1293            super.writeToParcel(out, flags);
1294            out.writeInt(canSlide ? 1 : 0);
1295            out.writeInt(isOpen ? 1 : 0);
1296        }
1297
1298        public static final Parcelable.Creator<SavedState> CREATOR =
1299                new Parcelable.Creator<SavedState>() {
1300            public SavedState createFromParcel(Parcel in) {
1301                return new SavedState(in);
1302            }
1303
1304            public SavedState[] newArray(int size) {
1305                return new SavedState[size];
1306            }
1307        };
1308    }
1309
1310    interface SlidingPanelLayoutImpl {
1311        void invalidateChildRegion(SlidingPaneLayout parent, View child);
1312    }
1313
1314    static class SlidingPanelLayoutImplBase implements SlidingPanelLayoutImpl {
1315        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1316            ViewCompat.postInvalidateOnAnimation(parent, child.getLeft(), child.getTop(),
1317                    child.getRight(), child.getBottom());
1318        }
1319    }
1320
1321    static class SlidingPanelLayoutImplJB extends SlidingPanelLayoutImplBase {
1322        /*
1323         * Private API hacks! Nasty! Bad!
1324         *
1325         * In Jellybean, some optimizations in the hardware UI renderer
1326         * prevent a changed Paint on a View using a hardware layer from having
1327         * the intended effect. This twiddles some internal bits on the view to force
1328         * it to recreate the display list.
1329         */
1330        private Method mGetDisplayList;
1331        private Field mRecreateDisplayList;
1332
1333        SlidingPanelLayoutImplJB() {
1334            try {
1335                mGetDisplayList = View.class.getDeclaredMethod("getDisplayList", (Class[]) null);
1336            } catch (NoSuchMethodException e) {
1337                Log.e(TAG, "Couldn't fetch getDisplayList method; dimming won't work right.", e);
1338            }
1339            try {
1340                mRecreateDisplayList = View.class.getDeclaredField("mRecreateDisplayList");
1341                mRecreateDisplayList.setAccessible(true);
1342            } catch (NoSuchFieldException e) {
1343                Log.e(TAG, "Couldn't fetch mRecreateDisplayList field; dimming will be slow.", e);
1344            }
1345        }
1346
1347        @Override
1348        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1349            if (mGetDisplayList != null && mRecreateDisplayList != null) {
1350                try {
1351                    mRecreateDisplayList.setBoolean(child, true);
1352                    mGetDisplayList.invoke(child, (Object[]) null);
1353                } catch (Exception e) {
1354                    Log.e(TAG, "Error refreshing display list state", e);
1355                }
1356            } else {
1357                // Slow path. REALLY slow path. Let's hope we don't get here.
1358                child.invalidate();
1359                return;
1360            }
1361            super.invalidateChildRegion(parent, child);
1362        }
1363    }
1364
1365    static class SlidingPanelLayoutImplJBMR1 extends SlidingPanelLayoutImplBase {
1366        @Override
1367        public void invalidateChildRegion(SlidingPaneLayout parent, View child) {
1368            ViewCompat.setLayerPaint(child, ((LayoutParams) child.getLayoutParams()).dimPaint);
1369        }
1370    }
1371}
1372