ResolverDrawerLayout.java revision f0af0ea33b6c10753c071c3881d5d50c79d90c72
1/* 2 * Copyright (C) 2014 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 17 18package com.android.internal.widget; 19 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.graphics.Rect; 23import android.os.Parcel; 24import android.os.Parcelable; 25import android.util.AttributeSet; 26import android.util.Log; 27import android.view.MotionEvent; 28import android.view.VelocityTracker; 29import android.view.View; 30import android.view.ViewConfiguration; 31import android.view.ViewGroup; 32import android.view.ViewParent; 33import android.view.ViewTreeObserver; 34import android.view.animation.AnimationUtils; 35import android.widget.AbsListView; 36import android.widget.OverScroller; 37import com.android.internal.R; 38 39public class ResolverDrawerLayout extends ViewGroup { 40 private static final String TAG = "ResolverDrawerLayout"; 41 42 /** 43 * Max width of the whole drawer layout 44 */ 45 private int mMaxWidth; 46 47 /** 48 * Max total visible height of views not marked always-show when in the closed/initial state 49 */ 50 private int mMaxCollapsedHeight; 51 52 /** 53 * Max total visible height of views not marked always-show when in the closed/initial state 54 * when a default option is present 55 */ 56 private int mMaxCollapsedHeightSmall; 57 58 private boolean mSmallCollapsed; 59 60 /** 61 * Move views down from the top by this much in px 62 */ 63 private float mCollapseOffset; 64 65 private int mCollapsibleHeight; 66 private int mUncollapsibleHeight; 67 68 private int mTopOffset; 69 70 private boolean mIsDragging; 71 private boolean mOpenOnClick; 72 private boolean mOpenOnLayout; 73 private boolean mDismissOnScrollerFinished; 74 private final int mTouchSlop; 75 private final float mMinFlingVelocity; 76 private final OverScroller mScroller; 77 private final VelocityTracker mVelocityTracker; 78 79 private OnDismissedListener mOnDismissedListener; 80 private RunOnDismissedListener mRunOnDismissedListener; 81 82 private float mInitialTouchX; 83 private float mInitialTouchY; 84 private float mLastTouchY; 85 private int mActivePointerId = MotionEvent.INVALID_POINTER_ID; 86 87 private final Rect mTempRect = new Rect(); 88 89 private final ViewTreeObserver.OnTouchModeChangeListener mTouchModeChangeListener = 90 new ViewTreeObserver.OnTouchModeChangeListener() { 91 @Override 92 public void onTouchModeChanged(boolean isInTouchMode) { 93 if (!isInTouchMode && hasFocus() && isDescendantClipped(getFocusedChild())) { 94 smoothScrollTo(0, 0); 95 } 96 } 97 }; 98 99 public ResolverDrawerLayout(Context context) { 100 this(context, null); 101 } 102 103 public ResolverDrawerLayout(Context context, AttributeSet attrs) { 104 this(context, attrs, 0); 105 } 106 107 public ResolverDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) { 108 super(context, attrs, defStyleAttr); 109 110 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout, 111 defStyleAttr, 0); 112 mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_maxWidth, -1); 113 mMaxCollapsedHeight = a.getDimensionPixelSize( 114 R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0); 115 mMaxCollapsedHeightSmall = a.getDimensionPixelSize( 116 R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall, 117 mMaxCollapsedHeight); 118 a.recycle(); 119 120 mScroller = new OverScroller(context, AnimationUtils.loadInterpolator(context, 121 android.R.interpolator.decelerate_quint)); 122 mVelocityTracker = VelocityTracker.obtain(); 123 124 final ViewConfiguration vc = ViewConfiguration.get(context); 125 mTouchSlop = vc.getScaledTouchSlop(); 126 mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 127 } 128 129 public void setSmallCollapsed(boolean smallCollapsed) { 130 mSmallCollapsed = smallCollapsed; 131 requestLayout(); 132 } 133 134 public boolean isSmallCollapsed() { 135 return mSmallCollapsed; 136 } 137 138 public boolean isCollapsed() { 139 return mCollapseOffset > 0; 140 } 141 142 private boolean isMoving() { 143 return mIsDragging || !mScroller.isFinished(); 144 } 145 146 private int getMaxCollapsedHeight() { 147 return isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight; 148 } 149 150 public void setOnDismissedListener(OnDismissedListener listener) { 151 mOnDismissedListener = listener; 152 } 153 154 @Override 155 public boolean onInterceptTouchEvent(MotionEvent ev) { 156 final int action = ev.getActionMasked(); 157 158 if (action == MotionEvent.ACTION_DOWN) { 159 mVelocityTracker.clear(); 160 } 161 162 mVelocityTracker.addMovement(ev); 163 164 switch (action) { 165 case MotionEvent.ACTION_DOWN: { 166 final float x = ev.getX(); 167 final float y = ev.getY(); 168 mInitialTouchX = x; 169 mInitialTouchY = mLastTouchY = y; 170 mOpenOnClick = isListChildUnderClipped(x, y) && mCollapsibleHeight > 0; 171 } 172 break; 173 174 case MotionEvent.ACTION_MOVE: { 175 final float x = ev.getX(); 176 final float y = ev.getY(); 177 final float dy = y - mInitialTouchY; 178 if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null && 179 (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) { 180 mActivePointerId = ev.getPointerId(0); 181 mIsDragging = true; 182 mLastTouchY = Math.max(mLastTouchY - mTouchSlop, 183 Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop)); 184 } 185 } 186 break; 187 188 case MotionEvent.ACTION_POINTER_UP: { 189 onSecondaryPointerUp(ev); 190 } 191 break; 192 193 case MotionEvent.ACTION_CANCEL: 194 case MotionEvent.ACTION_UP: { 195 resetTouch(); 196 } 197 break; 198 } 199 200 if (mIsDragging) { 201 abortAnimation(); 202 } 203 return mIsDragging || mOpenOnClick; 204 } 205 206 @Override 207 public boolean onTouchEvent(MotionEvent ev) { 208 final int action = ev.getActionMasked(); 209 210 mVelocityTracker.addMovement(ev); 211 212 boolean handled = false; 213 switch (action) { 214 case MotionEvent.ACTION_DOWN: { 215 final float x = ev.getX(); 216 final float y = ev.getY(); 217 mInitialTouchX = x; 218 mInitialTouchY = mLastTouchY = y; 219 mActivePointerId = ev.getPointerId(0); 220 final boolean hitView = findChildUnder(mInitialTouchX, mInitialTouchY) != null; 221 handled = (!hitView && mOnDismissedListener != null) || mCollapsibleHeight > 0; 222 mIsDragging = hitView && handled; 223 abortAnimation(); 224 } 225 break; 226 227 case MotionEvent.ACTION_MOVE: { 228 int index = ev.findPointerIndex(mActivePointerId); 229 if (index < 0) { 230 Log.e(TAG, "Bad pointer id " + mActivePointerId + ", resetting"); 231 index = 0; 232 mActivePointerId = ev.getPointerId(0); 233 mInitialTouchX = ev.getX(); 234 mInitialTouchY = mLastTouchY = ev.getY(); 235 } 236 final float x = ev.getX(index); 237 final float y = ev.getY(index); 238 if (!mIsDragging) { 239 final float dy = y - mInitialTouchY; 240 if (Math.abs(dy) > mTouchSlop && findChildUnder(x, y) != null) { 241 handled = mIsDragging = true; 242 mLastTouchY = Math.max(mLastTouchY - mTouchSlop, 243 Math.min(mLastTouchY + dy, mLastTouchY + mTouchSlop)); 244 } 245 } 246 if (mIsDragging) { 247 final float dy = y - mLastTouchY; 248 performDrag(dy); 249 } 250 mLastTouchY = y; 251 } 252 break; 253 254 case MotionEvent.ACTION_POINTER_DOWN: { 255 final int pointerIndex = ev.getActionIndex(); 256 final int pointerId = ev.getPointerId(pointerIndex); 257 mActivePointerId = pointerId; 258 mInitialTouchX = ev.getX(pointerIndex); 259 mInitialTouchY = mLastTouchY = ev.getY(pointerIndex); 260 } 261 break; 262 263 case MotionEvent.ACTION_POINTER_UP: { 264 onSecondaryPointerUp(ev); 265 } 266 break; 267 268 case MotionEvent.ACTION_UP: { 269 final boolean wasDragging = mIsDragging; 270 mIsDragging = false; 271 if (!wasDragging && findChildUnder(mInitialTouchX, mInitialTouchY) == null && 272 findChildUnder(ev.getX(), ev.getY()) == null) { 273 if (mOnDismissedListener != null) { 274 dispatchOnDismissed(); 275 resetTouch(); 276 return true; 277 } 278 } 279 if (mOpenOnClick && Math.abs(ev.getX() - mInitialTouchX) < mTouchSlop && 280 Math.abs(ev.getY() - mInitialTouchY) < mTouchSlop) { 281 smoothScrollTo(0, 0); 282 return true; 283 } 284 mVelocityTracker.computeCurrentVelocity(1000); 285 final float yvel = mVelocityTracker.getYVelocity(mActivePointerId); 286 if (Math.abs(yvel) > mMinFlingVelocity) { 287 if (mOnDismissedListener != null 288 && yvel > 0 && mCollapseOffset > mCollapsibleHeight) { 289 smoothScrollTo(mCollapsibleHeight + mUncollapsibleHeight, yvel); 290 mDismissOnScrollerFinished = true; 291 } else { 292 smoothScrollTo(yvel < 0 ? 0 : mCollapsibleHeight, yvel); 293 } 294 } else { 295 smoothScrollTo( 296 mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0); 297 } 298 resetTouch(); 299 } 300 break; 301 302 case MotionEvent.ACTION_CANCEL: { 303 if (mIsDragging) { 304 smoothScrollTo( 305 mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0); 306 } 307 resetTouch(); 308 return true; 309 } 310 } 311 312 return handled; 313 } 314 315 private void onSecondaryPointerUp(MotionEvent ev) { 316 final int pointerIndex = ev.getActionIndex(); 317 final int pointerId = ev.getPointerId(pointerIndex); 318 if (pointerId == mActivePointerId) { 319 // This was our active pointer going up. Choose a new 320 // active pointer and adjust accordingly. 321 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 322 mInitialTouchX = ev.getX(newPointerIndex); 323 mInitialTouchY = mLastTouchY = ev.getY(newPointerIndex); 324 mActivePointerId = ev.getPointerId(newPointerIndex); 325 } 326 } 327 328 private void resetTouch() { 329 mActivePointerId = MotionEvent.INVALID_POINTER_ID; 330 mIsDragging = false; 331 mOpenOnClick = false; 332 mInitialTouchX = mInitialTouchY = mLastTouchY = 0; 333 mVelocityTracker.clear(); 334 } 335 336 @Override 337 public void computeScroll() { 338 super.computeScroll(); 339 if (mScroller.computeScrollOffset()) { 340 final boolean keepGoing = !mScroller.isFinished(); 341 performDrag(mScroller.getCurrY() - mCollapseOffset); 342 if (keepGoing) { 343 postInvalidateOnAnimation(); 344 } else if (mDismissOnScrollerFinished && mOnDismissedListener != null) { 345 mRunOnDismissedListener = new RunOnDismissedListener(); 346 post(mRunOnDismissedListener); 347 } 348 } 349 } 350 351 private void abortAnimation() { 352 mScroller.abortAnimation(); 353 mRunOnDismissedListener = null; 354 mDismissOnScrollerFinished = false; 355 } 356 357 private float performDrag(float dy) { 358 final float newPos = Math.max(0, Math.min(mCollapseOffset + dy, 359 mCollapsibleHeight + mUncollapsibleHeight)); 360 if (newPos != mCollapseOffset) { 361 dy = newPos - mCollapseOffset; 362 final int childCount = getChildCount(); 363 for (int i = 0; i < childCount; i++) { 364 final View child = getChildAt(i); 365 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 366 if (!lp.ignoreOffset) { 367 child.offsetTopAndBottom((int) dy); 368 } 369 } 370 mCollapseOffset = newPos; 371 mTopOffset += dy; 372 postInvalidateOnAnimation(); 373 return dy; 374 } 375 return 0; 376 } 377 378 void dispatchOnDismissed() { 379 if (mOnDismissedListener != null) { 380 mOnDismissedListener.onDismissed(); 381 } 382 if (mRunOnDismissedListener != null) { 383 removeCallbacks(mRunOnDismissedListener); 384 mRunOnDismissedListener = null; 385 } 386 } 387 388 private void smoothScrollTo(int yOffset, float velocity) { 389 abortAnimation(); 390 final int sy = (int) mCollapseOffset; 391 int dy = yOffset - sy; 392 if (dy == 0) { 393 return; 394 } 395 396 final int height = getHeight(); 397 final int halfHeight = height / 2; 398 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / height); 399 final float distance = halfHeight + halfHeight * 400 distanceInfluenceForSnapDuration(distanceRatio); 401 402 int duration = 0; 403 velocity = Math.abs(velocity); 404 if (velocity > 0) { 405 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 406 } else { 407 final float pageDelta = (float) Math.abs(dy) / height; 408 duration = (int) ((pageDelta + 1) * 100); 409 } 410 duration = Math.min(duration, 300); 411 412 mScroller.startScroll(0, sy, 0, dy, duration); 413 postInvalidateOnAnimation(); 414 } 415 416 private float distanceInfluenceForSnapDuration(float f) { 417 f -= 0.5f; // center the values about 0. 418 f *= 0.3f * Math.PI / 2.0f; 419 return (float) Math.sin(f); 420 } 421 422 /** 423 * Note: this method doesn't take Z into account for overlapping views 424 * since it is only used in contexts where this doesn't affect the outcome. 425 */ 426 private View findChildUnder(float x, float y) { 427 return findChildUnder(this, x, y); 428 } 429 430 private static View findChildUnder(ViewGroup parent, float x, float y) { 431 final int childCount = parent.getChildCount(); 432 for (int i = childCount - 1; i >= 0; i--) { 433 final View child = parent.getChildAt(i); 434 if (isChildUnder(child, x, y)) { 435 return child; 436 } 437 } 438 return null; 439 } 440 441 private View findListChildUnder(float x, float y) { 442 View v = findChildUnder(x, y); 443 while (v != null) { 444 x -= v.getX(); 445 y -= v.getY(); 446 if (v instanceof AbsListView) { 447 // One more after this. 448 return findChildUnder((ViewGroup) v, x, y); 449 } 450 v = v instanceof ViewGroup ? findChildUnder((ViewGroup) v, x, y) : null; 451 } 452 return v; 453 } 454 455 /** 456 * This only checks clipping along the bottom edge. 457 */ 458 private boolean isListChildUnderClipped(float x, float y) { 459 final View listChild = findListChildUnder(x, y); 460 return listChild != null && isDescendantClipped(listChild); 461 } 462 463 private boolean isDescendantClipped(View child) { 464 mTempRect.set(0, 0, child.getWidth(), child.getHeight()); 465 offsetDescendantRectToMyCoords(child, mTempRect); 466 View directChild; 467 if (child.getParent() == this) { 468 directChild = child; 469 } else { 470 View v = child; 471 ViewParent p = child.getParent(); 472 while (p != this) { 473 v = (View) p; 474 p = v.getParent(); 475 } 476 directChild = v; 477 } 478 479 // ResolverDrawerLayout lays out vertically in child order; 480 // the next view and forward is what to check against. 481 int clipEdge = getHeight() - getPaddingBottom(); 482 final int childCount = getChildCount(); 483 for (int i = indexOfChild(directChild) + 1; i < childCount; i++) { 484 final View nextChild = getChildAt(i); 485 if (nextChild.getVisibility() == GONE) { 486 continue; 487 } 488 clipEdge = Math.min(clipEdge, nextChild.getTop()); 489 } 490 return mTempRect.bottom > clipEdge; 491 } 492 493 private static boolean isChildUnder(View child, float x, float y) { 494 final float left = child.getX(); 495 final float top = child.getY(); 496 final float right = left + child.getWidth(); 497 final float bottom = top + child.getHeight(); 498 return x >= left && y >= top && x < right && y < bottom; 499 } 500 501 @Override 502 public void requestChildFocus(View child, View focused) { 503 super.requestChildFocus(child, focused); 504 if (!isInTouchMode() && isDescendantClipped(focused)) { 505 smoothScrollTo(0, 0); 506 } 507 } 508 509 @Override 510 protected void onAttachedToWindow() { 511 super.onAttachedToWindow(); 512 getViewTreeObserver().addOnTouchModeChangeListener(mTouchModeChangeListener); 513 } 514 515 @Override 516 protected void onDetachedFromWindow() { 517 super.onDetachedFromWindow(); 518 getViewTreeObserver().removeOnTouchModeChangeListener(mTouchModeChangeListener); 519 abortAnimation(); 520 } 521 522 @Override 523 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 524 return (nestedScrollAxes & View.SCROLL_AXIS_VERTICAL) != 0; 525 } 526 527 @Override 528 public void onNestedScrollAccepted(View child, View target, int axes) { 529 super.onNestedScrollAccepted(child, target, axes); 530 } 531 532 @Override 533 public void onStopNestedScroll(View child) { 534 super.onStopNestedScroll(child); 535 if (mScroller.isFinished()) { 536 smoothScrollTo(mCollapseOffset < mCollapsibleHeight / 2 ? 0 : mCollapsibleHeight, 0); 537 } 538 } 539 540 @Override 541 public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 542 int dxUnconsumed, int dyUnconsumed) { 543 if (dyUnconsumed < 0) { 544 performDrag(-dyUnconsumed); 545 } 546 } 547 548 @Override 549 public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 550 if (dy > 0) { 551 consumed[1] = (int) -performDrag(-dy); 552 } 553 } 554 555 @Override 556 public boolean onNestedPreFling(View target, float velocityX, float velocityY) { 557 if (velocityY > mMinFlingVelocity && mCollapseOffset != 0) { 558 smoothScrollTo(0, velocityY); 559 return true; 560 } 561 return false; 562 } 563 564 @Override 565 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 566 if (!consumed && Math.abs(velocityY) > mMinFlingVelocity) { 567 smoothScrollTo(velocityY > 0 ? 0 : mCollapsibleHeight, velocityY); 568 return true; 569 } 570 return false; 571 } 572 573 @Override 574 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 575 final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec); 576 int widthSize = sourceWidth; 577 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 578 579 // Single-use layout; just ignore the mode and use available space. 580 // Clamp to maxWidth. 581 if (mMaxWidth >= 0) { 582 widthSize = Math.min(widthSize, mMaxWidth); 583 } 584 585 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY); 586 final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY); 587 final int widthPadding = getPaddingLeft() + getPaddingRight(); 588 int heightUsed = getPaddingTop() + getPaddingBottom(); 589 590 // Measure always-show children first. 591 final int childCount = getChildCount(); 592 for (int i = 0; i < childCount; i++) { 593 final View child = getChildAt(i); 594 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 595 if (lp.alwaysShow && child.getVisibility() != GONE) { 596 measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed); 597 heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin; 598 } 599 } 600 601 final int alwaysShowHeight = heightUsed; 602 603 // And now the rest. 604 for (int i = 0; i < childCount; i++) { 605 final View child = getChildAt(i); 606 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 607 if (!lp.alwaysShow && child.getVisibility() != GONE) { 608 measureChildWithMargins(child, widthSpec, widthPadding, heightSpec, heightUsed); 609 heightUsed += lp.topMargin + child.getMeasuredHeight() + lp.bottomMargin; 610 } 611 } 612 613 mCollapsibleHeight = Math.max(0, 614 heightUsed - alwaysShowHeight - getMaxCollapsedHeight()); 615 mUncollapsibleHeight = heightUsed - mCollapsibleHeight; 616 617 if (isLaidOut()) { 618 mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight); 619 } else { 620 // Start out collapsed at first unless we restored state for otherwise 621 mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight; 622 } 623 624 mTopOffset = Math.max(0, heightSize - heightUsed) + (int) mCollapseOffset; 625 626 setMeasuredDimension(sourceWidth, heightSize); 627 } 628 629 @Override 630 protected void onLayout(boolean changed, int l, int t, int r, int b) { 631 final int width = getWidth(); 632 633 int ypos = mTopOffset; 634 int leftEdge = getPaddingLeft(); 635 int rightEdge = width - getPaddingRight(); 636 637 final int childCount = getChildCount(); 638 for (int i = 0; i < childCount; i++) { 639 final View child = getChildAt(i); 640 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 641 642 if (child.getVisibility() == GONE) { 643 continue; 644 } 645 646 int top = ypos + lp.topMargin; 647 if (lp.ignoreOffset) { 648 top -= mCollapseOffset; 649 } 650 final int bottom = top + child.getMeasuredHeight(); 651 652 final int childWidth = child.getMeasuredWidth(); 653 final int widthAvailable = rightEdge - leftEdge; 654 final int left = leftEdge + (widthAvailable - childWidth) / 2; 655 final int right = left + childWidth; 656 657 child.layout(left, top, right, bottom); 658 659 ypos = bottom + lp.bottomMargin; 660 } 661 } 662 663 @Override 664 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 665 return new LayoutParams(getContext(), attrs); 666 } 667 668 @Override 669 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 670 if (p instanceof LayoutParams) { 671 return new LayoutParams((LayoutParams) p); 672 } else if (p instanceof MarginLayoutParams) { 673 return new LayoutParams((MarginLayoutParams) p); 674 } 675 return new LayoutParams(p); 676 } 677 678 @Override 679 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 680 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 681 } 682 683 @Override 684 protected Parcelable onSaveInstanceState() { 685 final SavedState ss = new SavedState(super.onSaveInstanceState()); 686 ss.open = mCollapsibleHeight > 0 && mCollapseOffset == 0; 687 return ss; 688 } 689 690 @Override 691 protected void onRestoreInstanceState(Parcelable state) { 692 final SavedState ss = (SavedState) state; 693 super.onRestoreInstanceState(ss.getSuperState()); 694 mOpenOnLayout = ss.open; 695 } 696 697 public static class LayoutParams extends MarginLayoutParams { 698 public boolean alwaysShow; 699 public boolean ignoreOffset; 700 701 public LayoutParams(Context c, AttributeSet attrs) { 702 super(c, attrs); 703 704 final TypedArray a = c.obtainStyledAttributes(attrs, 705 R.styleable.ResolverDrawerLayout_LayoutParams); 706 alwaysShow = a.getBoolean( 707 R.styleable.ResolverDrawerLayout_LayoutParams_layout_alwaysShow, 708 false); 709 ignoreOffset = a.getBoolean( 710 R.styleable.ResolverDrawerLayout_LayoutParams_layout_ignoreOffset, 711 false); 712 a.recycle(); 713 } 714 715 public LayoutParams(int width, int height) { 716 super(width, height); 717 } 718 719 public LayoutParams(LayoutParams source) { 720 super(source); 721 this.alwaysShow = source.alwaysShow; 722 this.ignoreOffset = source.ignoreOffset; 723 } 724 725 public LayoutParams(MarginLayoutParams source) { 726 super(source); 727 } 728 729 public LayoutParams(ViewGroup.LayoutParams source) { 730 super(source); 731 } 732 } 733 734 static class SavedState extends BaseSavedState { 735 boolean open; 736 737 SavedState(Parcelable superState) { 738 super(superState); 739 } 740 741 private SavedState(Parcel in) { 742 super(in); 743 open = in.readInt() != 0; 744 } 745 746 @Override 747 public void writeToParcel(Parcel out, int flags) { 748 super.writeToParcel(out, flags); 749 out.writeInt(open ? 1 : 0); 750 } 751 752 public static final Parcelable.Creator<SavedState> CREATOR = 753 new Parcelable.Creator<SavedState>() { 754 @Override 755 public SavedState createFromParcel(Parcel in) { 756 return new SavedState(in); 757 } 758 759 @Override 760 public SavedState[] newArray(int size) { 761 return new SavedState[size]; 762 } 763 }; 764 } 765 766 public interface OnDismissedListener { 767 public void onDismissed(); 768 } 769 770 private class RunOnDismissedListener implements Runnable { 771 @Override 772 public void run() { 773 dispatchOnDismissed(); 774 } 775 } 776} 777