SlidingChallengeLayout.java revision eee209313d564be37b8fc068f785b3844cd6597e
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 com.android.internal.policy.impl.keyguard; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.drawable.Drawable; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.MotionEvent; 26import android.view.VelocityTracker; 27import android.view.View; 28import android.view.ViewConfiguration; 29import android.view.ViewGroup; 30import android.view.animation.Interpolator; 31import android.widget.Scroller; 32 33import com.android.internal.R; 34 35/** 36 * This layout handles interaction with the sliding security challenge views 37 * that overlay/resize other keyguard contents. 38 */ 39public class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout { 40 private static final String TAG = "SlidingChallengeLayout"; 41 42 // Drawn to show the drag handle in closed state; crossfades to the challenge view 43 // when challenge is fully visible 44 private Drawable mHandleDrawable; 45 private boolean mShowHandle = true; 46 47 // Initialized during measurement from child layoutparams 48 private View mChallengeView; 49 private View mScrimView; 50 51 // Range: 0 (fully hidden) to 1 (fully visible) 52 private float mChallengeOffset = 1.f; 53 private boolean mChallengeShowing = true; 54 private boolean mIsBouncing = false; 55 56 private final Scroller mScroller; 57 private int mScrollState; 58 private OnChallengeScrolledListener mListener; 59 60 public static final int SCROLL_STATE_IDLE = 0; 61 public static final int SCROLL_STATE_DRAGGING = 1; 62 public static final int SCROLL_STATE_SETTLING = 2; 63 64 private static final int MAX_SETTLE_DURATION = 600; // ms 65 66 // ID of the pointer in charge of a current drag 67 private int mActivePointerId = INVALID_POINTER; 68 private static final int INVALID_POINTER = -1; 69 70 // True if the user is currently dragging the slider 71 private boolean mDragging; 72 // True if the user may not drag until a new gesture begins 73 private boolean mBlockDrag; 74 75 private VelocityTracker mVelocityTracker; 76 private int mMinVelocity; 77 private int mMaxVelocity; 78 private float mLastTouchY; 79 private int mDragHandleSize; 80 81 private static final int DRAG_HANDLE_DEFAULT_SIZE = 32; // dp 82 83 // True if at least one layout pass has happened since the view was attached. 84 private boolean mHasLayout; 85 86 private static final Interpolator sMotionInterpolator = new Interpolator() { 87 public float getInterpolation(float t) { 88 t -= 1.0f; 89 return t * t * t * t * t + 1.0f; 90 } 91 }; 92 93 private static final Interpolator sHandleFadeInterpolator = new Interpolator() { 94 public float getInterpolation(float t) { 95 return t * t; 96 } 97 }; 98 99 private final Runnable mEndScrollRunnable = new Runnable () { 100 public void run() { 101 completeChallengeScroll(); 102 } 103 }; 104 105 private final OnClickListener mScrimClickListener = new OnClickListener() { 106 @Override 107 public void onClick(View v) { 108 hideBouncer(); 109 } 110 }; 111 112 /** 113 * Listener interface that reports changes in scroll state of the challenge area. 114 */ 115 public interface OnChallengeScrolledListener { 116 /** 117 * The scroll state itself changed. 118 * 119 * <p>scrollState will be one of the following:</p> 120 * 121 * <ul> 122 * <li><code>SCROLL_STATE_IDLE</code> - The challenge area is stationary.</li> 123 * <li><code>SCROLL_STATE_DRAGGING</code> - The user is actively dragging 124 * the challenge area.</li> 125 * <li><code>SCROLL_STATE_SETTLING</code> - The challenge area is animating 126 * into place.</li> 127 * </ul> 128 * 129 * <p>Do not perform expensive operations (e.g. layout) 130 * while the scroll state is not <code>SCROLL_STATE_IDLE</code>.</p> 131 * 132 * @param scrollState The new scroll state of the challenge area. 133 */ 134 public void onScrollStateChanged(int scrollState); 135 136 /** 137 * The precise position of the challenge area has changed. 138 * 139 * <p>NOTE: It is NOT safe to modify layout or call any View methods that may 140 * result in a requestLayout anywhere in your view hierarchy as a result of this call. 141 * It may be called during drawing.</p> 142 * 143 * @param scrollPosition New relative position of the challenge area. 144 * 1.f = fully visible/ready to be interacted with. 145 * 0.f = fully invisible/inaccessible to the user. 146 * @param challengeTop Position of the top edge of the challenge view in px in the 147 * SlidingChallengeLayout's coordinate system. 148 */ 149 public void onScrollPositionChanged(float scrollPosition, int challengeTop); 150 } 151 152 public SlidingChallengeLayout(Context context) { 153 this(context, null); 154 } 155 156 public SlidingChallengeLayout(Context context, AttributeSet attrs) { 157 this(context, attrs, 0); 158 } 159 160 public SlidingChallengeLayout(Context context, AttributeSet attrs, int defStyle) { 161 super(context, attrs, defStyle); 162 163 final TypedArray a = context.obtainStyledAttributes(attrs, 164 R.styleable.SlidingChallengeLayout, defStyle, 0); 165 setDragHandleDrawable(a.getDrawable(R.styleable.SlidingChallengeLayout_dragHandle)); 166 167 a.recycle(); 168 169 mScroller = new Scroller(context, sMotionInterpolator); 170 171 final ViewConfiguration vc = ViewConfiguration.get(context); 172 mMinVelocity = vc.getScaledMinimumFlingVelocity(); 173 mMaxVelocity = vc.getScaledMaximumFlingVelocity(); 174 175 setWillNotDraw(false); 176 } 177 178 public void setDragHandleDrawable(Drawable d) { 179 if (d != null) { 180 mDragHandleSize = d.getIntrinsicHeight(); 181 } 182 if (mDragHandleSize == 0 || d == null) { 183 final float density = getResources().getDisplayMetrics().density; 184 mDragHandleSize = (int) (DRAG_HANDLE_DEFAULT_SIZE * density + 0.5f); 185 } 186 mHandleDrawable = d; 187 } 188 189 private void sendInitialListenerUpdates() { 190 if (mListener != null) { 191 int challengeTop = mChallengeView != null ? mChallengeView.getTop() : 0; 192 mListener.onScrollPositionChanged(mChallengeOffset, challengeTop); 193 mListener.onScrollStateChanged(mScrollState); 194 } 195 } 196 197 public void setOnChallengeScrolledListener(OnChallengeScrolledListener listener) { 198 mListener = listener; 199 if (mHasLayout) { 200 sendInitialListenerUpdates(); 201 } 202 } 203 204 @Override 205 public void onAttachedToWindow() { 206 super.onAttachedToWindow(); 207 208 mHasLayout = false; 209 } 210 211 @Override 212 public void onDetachedFromWindow() { 213 super.onDetachedFromWindow(); 214 215 removeCallbacks(mEndScrollRunnable); 216 mHasLayout = false; 217 } 218 219 // We want the duration of the page snap animation to be influenced by the distance that 220 // the screen has to travel, however, we don't want this duration to be effected in a 221 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 222 // of travel has on the overall snap duration. 223 float distanceInfluenceForSnapDuration(float f) { 224 f -= 0.5f; // center the values about 0. 225 f *= 0.3f * Math.PI / 2.0f; 226 return (float) Math.sin(f); 227 } 228 229 void setScrollState(int state) { 230 if (mScrollState != state) { 231 mScrollState = state; 232 233 if (mListener != null) { 234 mListener.onScrollStateChanged(state); 235 } 236 } 237 } 238 239 void completeChallengeScroll() { 240 setChallengeShowing(mChallengeOffset != 0); 241 setScrollState(SCROLL_STATE_IDLE); 242 } 243 244 void setScrimView(View scrim) { 245 if (mScrimView != null) { 246 mScrimView.setOnClickListener(null); 247 } 248 mScrimView = scrim; 249 mScrimView.setVisibility(mIsBouncing ? VISIBLE : GONE); 250 mScrimView.setFocusable(true); 251 mScrimView.setOnClickListener(mScrimClickListener); 252 } 253 254 /** 255 * Animate the bottom edge of the challenge view to the given position. 256 * 257 * @param y desired final position for the bottom edge of the challenge view in px 258 * @param velocity velocity in 259 */ 260 void animateChallengeTo(int y, int velocity) { 261 if (mChallengeView == null) { 262 // Nothing to do. 263 return; 264 } 265 int sy = mChallengeView.getBottom(); 266 int dy = y - sy; 267 if (dy == 0) { 268 completeChallengeScroll(); 269 return; 270 } 271 272 setScrollState(SCROLL_STATE_SETTLING); 273 274 final int childHeight = mChallengeView.getHeight(); 275 final int halfHeight = childHeight / 2; 276 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / childHeight); 277 final float distance = halfHeight + halfHeight * 278 distanceInfluenceForSnapDuration(distanceRatio); 279 280 int duration = 0; 281 velocity = Math.abs(velocity); 282 if (velocity > 0) { 283 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 284 } else { 285 final float childDelta = (float) Math.abs(dy) / childHeight; 286 duration = (int) ((childDelta + 1) * 100); 287 } 288 duration = Math.min(duration, MAX_SETTLE_DURATION); 289 290 mScroller.startScroll(0, sy, 0, dy, duration); 291 postInvalidateOnAnimation(); 292 } 293 294 private void setChallengeShowing(boolean showChallenge) { 295 if (mChallengeShowing != showChallenge) { 296 mChallengeShowing = showChallenge; 297 if (mChallengeView != null) { 298 mChallengeView.setVisibility(showChallenge ? VISIBLE : INVISIBLE); 299 } 300 } 301 } 302 303 /** 304 * @return true if the challenge is at all visible. 305 */ 306 public boolean isChallengeShowing() { 307 return mChallengeShowing; 308 } 309 310 @Override 311 public boolean isChallengeOverlapping() { 312 return mChallengeShowing; 313 } 314 315 @Override 316 public boolean isBouncing() { 317 return mIsBouncing; 318 } 319 320 @Override 321 public void showBouncer() { 322 if (mIsBouncing) return; 323 showChallenge(true); 324 mIsBouncing = true; 325 if (mScrimView != null) { 326 mScrimView.setVisibility(GONE); 327 } 328 } 329 330 @Override 331 public void hideBouncer() { 332 if (!mIsBouncing) return; 333 setChallengeShowing(false); 334 mIsBouncing = false; 335 if (mScrimView != null) { 336 mScrimView.setVisibility(GONE); 337 } 338 } 339 340 @Override 341 public void requestDisallowInterceptTouchEvent(boolean allowIntercept) { 342 // We'll intercept whoever we feel like! ...as long as it isn't a challenge view. 343 // If there are one or more pointers in the challenge view before we take over 344 // touch events, onInterceptTouchEvent will set mBlockDrag. 345 } 346 347 @Override 348 public boolean onInterceptTouchEvent(MotionEvent ev) { 349 if (mVelocityTracker == null) { 350 mVelocityTracker = VelocityTracker.obtain(); 351 } 352 mVelocityTracker.addMovement(ev); 353 354 final int action = ev.getActionMasked(); 355 switch (action) { 356 case MotionEvent.ACTION_DOWN: 357 mLastTouchY = ev.getY(); 358 break; 359 360 case MotionEvent.ACTION_CANCEL: 361 case MotionEvent.ACTION_UP: 362 resetTouch(); 363 break; 364 365 case MotionEvent.ACTION_MOVE: 366 final int count = ev.getPointerCount(); 367 for (int i = 0; i < count; i++) { 368 final float x = ev.getX(i); 369 final float y = ev.getY(i); 370 371 if ((isInDragHandle(x, y) || crossedDragHandle(x, y, mLastTouchY) || 372 (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING)) && 373 mActivePointerId == INVALID_POINTER) { 374 mActivePointerId = ev.getPointerId(i); 375 mLastTouchY = y; 376 mDragging = true; 377 } else if (isInChallengeView(x, y)) { 378 mBlockDrag = true; 379 } 380 } 381 break; 382 } 383 384 if (mBlockDrag) { 385 mActivePointerId = INVALID_POINTER; 386 mDragging = false; 387 } 388 389 return mDragging; 390 } 391 392 private void resetTouch() { 393 mVelocityTracker.recycle(); 394 mVelocityTracker = null; 395 mActivePointerId = INVALID_POINTER; 396 mDragging = mBlockDrag = false; 397 } 398 399 @Override 400 public boolean onTouchEvent(MotionEvent ev) { 401 if (mVelocityTracker == null) { 402 mVelocityTracker = VelocityTracker.obtain(); 403 } 404 mVelocityTracker.addMovement(ev); 405 406 final int action = ev.getActionMasked(); 407 switch (action) { 408 case MotionEvent.ACTION_CANCEL: 409 if (mDragging) { 410 showChallenge(0); 411 } 412 resetTouch(); 413 break; 414 415 case MotionEvent.ACTION_POINTER_UP: 416 if (mActivePointerId != ev.getPointerId(ev.getActionIndex())) { 417 break; 418 } 419 case MotionEvent.ACTION_UP: 420 if (mDragging) { 421 mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); 422 showChallenge((int) mVelocityTracker.getYVelocity(mActivePointerId)); 423 } 424 resetTouch(); 425 break; 426 427 case MotionEvent.ACTION_MOVE: 428 if (!mDragging && !mBlockDrag) { 429 final int count = ev.getPointerCount(); 430 for (int i = 0; i < count; i++) { 431 final float x = ev.getX(i); 432 final float y = ev.getY(i); 433 434 if ((isInDragHandle(x, y) || crossedDragHandle(x, y, mLastTouchY) || 435 (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING)) 436 && mActivePointerId == INVALID_POINTER) { 437 mActivePointerId = ev.getPointerId(i); 438 mDragging = true; 439 break; 440 } 441 } 442 } 443 // Not an else; this can be set above. 444 if (mDragging) { 445 // No-op if already in this state, but set it here in case we arrived 446 // at this point from either intercept or the above. 447 setScrollState(SCROLL_STATE_DRAGGING); 448 449 final int index = ev.findPointerIndex(mActivePointerId); 450 if (index < 0) { 451 // Oops, bogus state. We lost some touch events somewhere. 452 // Just drop it with no velocity and let things settle. 453 resetTouch(); 454 showChallenge(0); 455 return true; 456 } 457 final float y = Math.max(ev.getY(index), getChallengeOpenedTop()); 458 final float delta = y - mLastTouchY; 459 final int idelta = (int) delta; 460 // Don't lose the rounded component 461 mLastTouchY = y + delta - idelta; 462 463 moveChallengeBy(idelta); 464 } 465 break; 466 } 467 return true; 468 } 469 470 private boolean isInChallengeView(float x, float y) { 471 if (mChallengeView == null) return false; 472 473 return x >= mChallengeView.getLeft() && y >= mChallengeView.getTop() && 474 x < mChallengeView.getRight() && y < mChallengeView.getBottom(); 475 } 476 477 private boolean isInDragHandle(float x, float y) { 478 if (mChallengeView == null) return false; 479 480 return x >= 0 && y >= mChallengeView.getTop() && x < getWidth() && 481 y < mChallengeView.getTop() + mDragHandleSize; 482 } 483 484 private boolean crossedDragHandle(float x, float y, float lastY) { 485 final int challengeTop = mChallengeView.getTop(); 486 return x >= 0 && x < getWidth() && lastY < challengeTop && 487 y > challengeTop + mDragHandleSize; 488 } 489 490 @Override 491 protected void onMeasure(int widthSpec, int heightSpec) { 492 if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY || 493 MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) { 494 throw new IllegalArgumentException( 495 "SlidingChallengeLayout must be measured with an exact size"); 496 } 497 498 final int width = MeasureSpec.getSize(widthSpec); 499 final int height = MeasureSpec.getSize(heightSpec); 500 setMeasuredDimension(width, height); 501 502 // Find one and only one challenge view. 503 final View oldChallengeView = mChallengeView; 504 mChallengeView = null; 505 final int count = getChildCount(); 506 for (int i = 0; i < count; i++) { 507 final View child = getChildAt(i); 508 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 509 510 if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) { 511 if (mChallengeView != null) { 512 throw new IllegalStateException( 513 "There may only be one child with layout_isChallenge=\"true\""); 514 } 515 mChallengeView = child; 516 if (mChallengeView != oldChallengeView) { 517 mChallengeView.setVisibility(mChallengeShowing ? VISIBLE : INVISIBLE); 518 } 519 } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) { 520 setScrimView(child); 521 } 522 523 if (child.getVisibility() == GONE) continue; 524 525 measureChildWithMargins(child, widthSpec, 0, heightSpec, 0); 526 } 527 } 528 529 @Override 530 protected void onLayout(boolean changed, int l, int t, int r, int b) { 531 final int paddingLeft = getPaddingLeft(); 532 final int paddingTop = getPaddingTop(); 533 final int paddingRight = getPaddingRight(); 534 final int paddingBottom = getPaddingBottom(); 535 final int width = r - l; 536 final int height = b - t; 537 538 final int count = getChildCount(); 539 for (int i = 0; i < count; i++) { 540 final View child = getChildAt(i); 541 542 if (child.getVisibility() == GONE) continue; 543 544 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 545 546 if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) { 547 // Challenge views pin to the bottom, offset by a portion of their height, 548 // and center horizontally. 549 final int center = (paddingLeft + width - paddingRight) / 2; 550 final int childWidth = child.getMeasuredWidth(); 551 final int childHeight = child.getMeasuredHeight(); 552 final int left = center - childWidth / 2; 553 final int layoutBottom = height - paddingBottom - lp.bottomMargin; 554 // We use the top of the challenge view to position the handle, so 555 // we never want less than the handle size showing at the bottom. 556 final int bottom = layoutBottom + (int) ((childHeight - mDragHandleSize) 557 * (1 - mChallengeOffset)); 558 float offset = 1.f - (bottom - layoutBottom) / childHeight; 559 child.setAlpha(offset); 560 child.layout(left, bottom - childHeight, left + childWidth, bottom); 561 } else { 562 // Non-challenge views lay out from the upper left, layered. 563 child.layout(paddingLeft + lp.leftMargin, 564 paddingTop + lp.topMargin, 565 paddingLeft + child.getMeasuredWidth(), 566 paddingTop + child.getMeasuredHeight()); 567 } 568 } 569 570 if (!mHasLayout) { 571 // We want to trigger the initial listener updates outside of layout pass, 572 // in case the listeners trigger requestLayout(). 573 post(new Runnable() { 574 @Override 575 public void run() { 576 sendInitialListenerUpdates(); 577 } 578 }); 579 } 580 mHasLayout = true; 581 } 582 583 public void computeScroll() { 584 super.computeScroll(); 585 586 if (!mScroller.isFinished()) { 587 if (mChallengeView == null) { 588 // Can't scroll if the view is missing. 589 Log.e(TAG, "Challenge view missing in computeScroll"); 590 mScroller.abortAnimation(); 591 return; 592 } 593 594 mScroller.computeScrollOffset(); 595 moveChallengeTo(mScroller.getCurrY()); 596 597 if (mScroller.isFinished()) { 598 post(mEndScrollRunnable); 599 } 600 } 601 } 602 603 @Override 604 public void draw(Canvas c) { 605 super.draw(c); 606 if (mChallengeOffset < 1.f 607 && mChallengeView != null && mHandleDrawable != null && mShowHandle) { 608 final int top = mChallengeView.getTop(); 609 mHandleDrawable.setBounds(0, top, getWidth(), top + mDragHandleSize); 610 final float alpha = sHandleFadeInterpolator.getInterpolation(1 - mChallengeOffset); 611 mHandleDrawable.setAlpha((int) (alpha * 0xFF)); 612 mHandleDrawable.draw(c); 613 } 614 } 615 616 /** 617 * Move the bottom edge of mChallengeView to a new position and notify the listener 618 * if it represents a change in position. Changes made through this method will 619 * be stable across layout passes. If this method is called before first layout of 620 * this SlidingChallengeLayout it will have no effect. 621 * 622 * @param bottom New bottom edge in px in this SlidingChallengeLayout's coordinate system. 623 * @return true if the challenge view was moved 624 */ 625 private boolean moveChallengeTo(int bottom) { 626 if (mChallengeView == null || !mHasLayout) { 627 return false; 628 } 629 630 final int bottomMargin = ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin; 631 final int layoutBottom = getHeight() - getPaddingBottom() - bottomMargin; 632 final int challengeHeight = mChallengeView.getHeight(); 633 634 bottom = Math.max(layoutBottom, 635 Math.min(bottom, layoutBottom + challengeHeight - mDragHandleSize)); 636 637 float offset = 1.f - (float) (bottom - layoutBottom) / (challengeHeight - mDragHandleSize); 638 mChallengeOffset = offset; 639 if (offset > 0 && !mChallengeShowing) { 640 setChallengeShowing(true); 641 } 642 643 mChallengeView.layout(mChallengeView.getLeft(), 644 bottom - mChallengeView.getHeight(), mChallengeView.getRight(), bottom); 645 646 mChallengeView.setAlpha(offset); 647 if (mListener != null) { 648 mListener.onScrollPositionChanged(offset, mChallengeView.getTop()); 649 } 650 postInvalidateOnAnimation(); 651 return true; 652 } 653 654 private int getChallengeBottom() { 655 if (mChallengeView == null) return 0; 656 657 return mChallengeView.getBottom(); 658 } 659 660 private int getChallengeOpenedTop() { 661 final int paddedBottom = getHeight() - getPaddingBottom(); 662 if (mChallengeView == null) return paddedBottom; 663 664 final int bottomMargin = ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin; 665 final int layoutBottom = paddedBottom - bottomMargin; 666 667 return layoutBottom - mChallengeView.getHeight(); 668 } 669 670 private void moveChallengeBy(int delta) { 671 moveChallengeTo(getChallengeBottom() + delta); 672 } 673 674 /** 675 * Show or hide the challenge view, animating it if necessary. 676 * @param show true to show, false to hide 677 */ 678 public void showChallenge(boolean show) { 679 showChallenge(show, 0); 680 } 681 682 private void showChallenge(int velocity) { 683 boolean show = false; 684 if (Math.abs(velocity) > mMinVelocity) { 685 show = velocity < 0; 686 } else { 687 show = mChallengeOffset >= 0.5f; 688 } 689 showChallenge(show, velocity); 690 } 691 692 private void showChallenge(boolean show, int velocity) { 693 if (mChallengeView == null) { 694 setChallengeShowing(false); 695 return; 696 } 697 698 if (mHasLayout) { 699 final int bottomMargin = ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin; 700 final int layoutBottom = getHeight() - getPaddingBottom() - bottomMargin; 701 animateChallengeTo(show ? layoutBottom : 702 layoutBottom + mChallengeView.getHeight() - mDragHandleSize, velocity); 703 } 704 } 705 706 public void showHandle(boolean show) { 707 mShowHandle = show; 708 invalidate(); 709 } 710 711 @Override 712 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 713 return new LayoutParams(getContext(), attrs); 714 } 715 716 @Override 717 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 718 return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) : 719 p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) : 720 new LayoutParams(p); 721 } 722 723 @Override 724 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 725 return new LayoutParams(); 726 } 727 728 @Override 729 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 730 return p instanceof LayoutParams; 731 } 732 733 public static class LayoutParams extends MarginLayoutParams { 734 public int childType = CHILD_TYPE_NONE; 735 public static final int CHILD_TYPE_NONE = 0; 736 public static final int CHILD_TYPE_CHALLENGE = 2; 737 public static final int CHILD_TYPE_SCRIM = 4; 738 739 public LayoutParams() { 740 this(MATCH_PARENT, WRAP_CONTENT); 741 } 742 743 public LayoutParams(int width, int height) { 744 super(width, height); 745 } 746 747 public LayoutParams(android.view.ViewGroup.LayoutParams source) { 748 super(source); 749 } 750 751 public LayoutParams(MarginLayoutParams source) { 752 super(source); 753 } 754 755 public LayoutParams(LayoutParams source) { 756 super(source); 757 758 childType = source.childType; 759 } 760 761 public LayoutParams(Context c, AttributeSet attrs) { 762 super(c, attrs); 763 764 final TypedArray a = c.obtainStyledAttributes(attrs, 765 R.styleable.SlidingChallengeLayout_Layout); 766 childType = a.getInt(R.styleable.SlidingChallengeLayout_Layout_layout_childType, 767 CHILD_TYPE_NONE); 768 a.recycle(); 769 } 770 } 771} 772