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