CallCardFragment.java revision 00764eb981c568d08083c7cfc1b72b7bdc4d4fd2
1/* 2 * Copyright (C) 2013 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.incallui; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.ValueAnimator; 24import android.app.Activity; 25import android.content.Context; 26import android.graphics.Point; 27import android.graphics.drawable.Drawable; 28import android.os.Bundle; 29import android.telephony.DisconnectCause; 30import android.text.TextUtils; 31import android.view.Display; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.ViewAnimationUtils; 35import android.view.ViewGroup; 36import android.view.ViewTreeObserver; 37import android.view.ViewTreeObserver.OnGlobalLayoutListener; 38import android.view.accessibility.AccessibilityEvent; 39import android.view.animation.Animation; 40import android.view.animation.AnimationUtils; 41import android.widget.ImageButton; 42import android.widget.ImageView; 43import android.widget.TextView; 44 45import com.android.contacts.common.animation.AnimUtils; 46import com.android.contacts.common.util.ViewUtil; 47 48import java.util.List; 49 50/** 51 * Fragment for call card. 52 */ 53public class CallCardFragment extends BaseFragment<CallCardPresenter, CallCardPresenter.CallCardUi> 54 implements CallCardPresenter.CallCardUi { 55 56 private static final int REVEAL_ANIMATION_DURATION = 333; 57 private static final int SHRINK_ANIMATION_DURATION = 333; 58 59 // Primary caller info 60 private TextView mPhoneNumber; 61 private TextView mNumberLabel; 62 private TextView mPrimaryName; 63 private TextView mCallStateLabel; 64 private TextView mCallTypeLabel; 65 private View mCallNumberAndLabel; 66 private ImageView mPhoto; 67 private TextView mElapsedTime; 68 69 // Container view that houses the entire primary call card, including the call buttons 70 private View mPrimaryCallCardContainer; 71 // Container view that houses the primary call information 72 private View mPrimaryCallInfo; 73 private View mCallButtonsContainer; 74 75 // Secondary caller info 76 private View mSecondaryCallInfo; 77 private TextView mSecondaryCallName; 78 79 private View mEndCallButton; 80 private ImageButton mHandoffButton; 81 82 // Cached DisplayMetrics density. 83 private float mDensity; 84 85 private float mTranslationOffset; 86 private Animation mPulseAnimation; 87 88 @Override 89 CallCardPresenter.CallCardUi getUi() { 90 return this; 91 } 92 93 @Override 94 CallCardPresenter createPresenter() { 95 return new CallCardPresenter(); 96 } 97 98 @Override 99 public void onCreate(Bundle savedInstanceState) { 100 super.onCreate(savedInstanceState); 101 } 102 103 104 @Override 105 public void onActivityCreated(Bundle savedInstanceState) { 106 super.onActivityCreated(savedInstanceState); 107 108 final CallList calls = CallList.getInstance(); 109 final Call call = calls.getFirstCall(); 110 getPresenter().init(getActivity(), call); 111 } 112 113 @Override 114 public View onCreateView(LayoutInflater inflater, ViewGroup container, 115 Bundle savedInstanceState) { 116 super.onCreateView(inflater, container, savedInstanceState); 117 118 mDensity = getResources().getDisplayMetrics().density; 119 mTranslationOffset = 120 getResources().getDimensionPixelSize(R.dimen.call_card_anim_translate_y_offset); 121 122 return inflater.inflate(R.layout.call_card, container, false); 123 } 124 125 @Override 126 public void onViewCreated(View view, Bundle savedInstanceState) { 127 super.onViewCreated(view, savedInstanceState); 128 129 mPulseAnimation = 130 AnimationUtils.loadAnimation(view.getContext(), R.anim.call_status_pulse); 131 132 mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber); 133 mPrimaryName = (TextView) view.findViewById(R.id.name); 134 mNumberLabel = (TextView) view.findViewById(R.id.label); 135 mSecondaryCallInfo = (View) view.findViewById(R.id.secondary_call_info); 136 mPhoto = (ImageView) view.findViewById(R.id.photo); 137 mCallStateLabel = (TextView) view.findViewById(R.id.callStateLabel); 138 mCallNumberAndLabel = view.findViewById(R.id.labelAndNumber); 139 mCallTypeLabel = (TextView) view.findViewById(R.id.callTypeLabel); 140 mElapsedTime = (TextView) view.findViewById(R.id.elapsedTime); 141 mPrimaryCallCardContainer = view.findViewById(R.id.primary_call_info_container); 142 mPrimaryCallInfo = view.findViewById(R.id.primary_call_banner); 143 mCallButtonsContainer = view.findViewById(R.id.callButtonFragment); 144 145 mEndCallButton = view.findViewById(R.id.endButton); 146 mEndCallButton.setOnClickListener(new View.OnClickListener() { 147 @Override 148 public void onClick(View v) { 149 getPresenter().endCallClicked(); 150 } 151 }); 152 ViewUtil.setupFloatingActionButton(mEndCallButton, getResources()); 153 154 mHandoffButton = (ImageButton) view.findViewById(R.id.handoffButton); 155 mHandoffButton.setOnClickListener(new View.OnClickListener() { 156 @Override public void onClick(View v) { 157 getPresenter().connectionHandoffClicked(); 158 } 159 }); 160 ViewUtil.setupFloatingActionButton(mHandoffButton, getResources()); 161 162 mPrimaryName.setElegantTextHeight(false); 163 mCallStateLabel.setElegantTextHeight(false); 164 } 165 166 @Override 167 public void setVisible(boolean on) { 168 if (on) { 169 getView().setVisibility(View.VISIBLE); 170 } else { 171 getView().setVisibility(View.INVISIBLE); 172 } 173 } 174 175 public void setShowConnectionHandoff(boolean showConnectionHandoff) { 176 Log.v(this, "setShowConnectionHandoff: " + showConnectionHandoff); 177 } 178 179 @Override 180 public void setPrimaryName(String name, boolean nameIsNumber) { 181 if (TextUtils.isEmpty(name)) { 182 mPrimaryName.setText(""); 183 } else { 184 mPrimaryName.setText(name); 185 186 // Set direction of the name field 187 int nameDirection = View.TEXT_DIRECTION_INHERIT; 188 if (nameIsNumber) { 189 nameDirection = View.TEXT_DIRECTION_LTR; 190 } 191 mPrimaryName.setTextDirection(nameDirection); 192 } 193 } 194 195 @Override 196 public void setPrimaryImage(Drawable image) { 197 if (image != null) { 198 setDrawableToImageView(mPhoto, image); 199 } 200 } 201 202 @Override 203 public void setPrimaryPhoneNumber(String number) { 204 // Set the number 205 if (TextUtils.isEmpty(number)) { 206 mPhoneNumber.setText(""); 207 mPhoneNumber.setVisibility(View.GONE); 208 } else { 209 mPhoneNumber.setText(number); 210 mPhoneNumber.setVisibility(View.VISIBLE); 211 mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR); 212 } 213 } 214 215 @Override 216 public void setPrimaryLabel(String label) { 217 if (!TextUtils.isEmpty(label)) { 218 mNumberLabel.setText(label); 219 mNumberLabel.setVisibility(View.VISIBLE); 220 } else { 221 mNumberLabel.setVisibility(View.GONE); 222 } 223 224 } 225 226 @Override 227 public void setPrimary(String number, String name, boolean nameIsNumber, String label, 228 Drawable photo, boolean isConference, boolean isGeneric, boolean isSipCall) { 229 Log.d(this, "Setting primary call"); 230 231 if (isConference) { 232 name = getConferenceString(isGeneric); 233 photo = getConferencePhoto(isGeneric); 234 nameIsNumber = false; 235 } 236 237 // set the name field. 238 setPrimaryName(name, nameIsNumber); 239 240 if (TextUtils.isEmpty(number) && TextUtils.isEmpty(label)) { 241 mCallNumberAndLabel.setVisibility(View.GONE); 242 } else { 243 mCallNumberAndLabel.setVisibility(View.VISIBLE); 244 } 245 246 setPrimaryPhoneNumber(number); 247 248 // Set the label (Mobile, Work, etc) 249 setPrimaryLabel(label); 250 251 showInternetCallLabel(isSipCall); 252 253 setDrawableToImageView(mPhoto, photo); 254 } 255 256 @Override 257 public void setSecondary(boolean show, String name, boolean nameIsNumber, String label, 258 boolean isConference, boolean isGeneric) { 259 260 if (show) { 261 if (isConference) { 262 name = getConferenceString(isGeneric); 263 nameIsNumber = false; 264 } 265 266 showAndInitializeSecondaryCallInfo(); 267 mSecondaryCallName.setText(name); 268 269 int nameDirection = View.TEXT_DIRECTION_INHERIT; 270 if (nameIsNumber) { 271 nameDirection = View.TEXT_DIRECTION_LTR; 272 } 273 mSecondaryCallName.setTextDirection(nameDirection); 274 } else { 275 mSecondaryCallInfo.setVisibility(View.GONE); 276 } 277 } 278 279 @Override 280 public void setCallState(int state, int cause, boolean bluetoothOn, String gatewayLabel, 281 String gatewayNumber, boolean isWiFi, boolean isHandoffCapable, 282 boolean isHandoffPending) { 283 String callStateLabel = null; 284 285 if (Call.State.isDialing(state) && !TextUtils.isEmpty(gatewayLabel)) { 286 // Provider info: (e.g. "Calling via <gatewayLabel>") 287 callStateLabel = gatewayLabel; 288 } else { 289 callStateLabel = getCallStateLabelFromState(state, cause); 290 } 291 292 Log.v(this, "setCallState " + callStateLabel); 293 Log.v(this, "DisconnectCause " + DisconnectCause.toString(cause)); 294 Log.v(this, "bluetooth on " + bluetoothOn); 295 Log.v(this, "gateway " + gatewayLabel + gatewayNumber); 296 Log.v(this, "isWiFi " + isWiFi); 297 Log.v(this, "isHandoffCapable " + isHandoffCapable); 298 Log.v(this, "isHandoffPending " + isHandoffPending); 299 300 // Update the call state label. 301 if (!TextUtils.isEmpty(callStateLabel)) { 302 mCallStateLabel.setText(callStateLabel); 303 mCallStateLabel.setVisibility(View.VISIBLE); 304 if (state != Call.State.CONFERENCED) { 305 mCallStateLabel.startAnimation(mPulseAnimation); 306 } 307 } else { 308 Animation callStateAnimation = mCallStateLabel.getAnimation(); 309 if (callStateAnimation != null) { 310 callStateAnimation.cancel(); 311 } 312 mCallStateLabel.setAlpha(0); 313 mCallStateLabel.setVisibility(View.GONE); 314 } 315 316 if (Call.State.INCOMING == state) { 317 setBluetoothOn(bluetoothOn); 318 } 319 320 mHandoffButton.setEnabled(isHandoffCapable && !isHandoffPending); 321 mHandoffButton.setVisibility(mHandoffButton.isEnabled() ? View.VISIBLE : View.GONE); 322 mHandoffButton.setImageResource(isWiFi ? 323 R.drawable.ic_in_call_wifi : R.drawable.ic_in_call_pstn); 324 } 325 326 private void showInternetCallLabel(boolean show) { 327 if (show) { 328 final String label = getView().getContext().getString( 329 R.string.incall_call_type_label_sip); 330 mCallTypeLabel.setVisibility(View.VISIBLE); 331 mCallTypeLabel.setText(label); 332 } else { 333 mCallTypeLabel.setVisibility(View.GONE); 334 } 335 } 336 337 @Override 338 public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { 339 if (show) { 340 if (mElapsedTime.getVisibility() != View.VISIBLE) { 341 AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION); 342 } 343 mElapsedTime.setText(callTimeElapsed); 344 } else { 345 // hide() animation has no effect if it is already hidden. 346 AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION); 347 } 348 } 349 350 private void setDrawableToImageView(ImageView view, Drawable photo) { 351 if (photo == null) { 352 photo = view.getResources().getDrawable(R.drawable.picture_unknown); 353 } 354 355 final Drawable current = view.getDrawable(); 356 if (current == null) { 357 view.setImageDrawable(photo); 358 AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION); 359 } else { 360 InCallAnimationUtils.startCrossFade(view, current, photo); 361 view.setVisibility(View.VISIBLE); 362 } 363 } 364 365 private String getConferenceString(boolean isGeneric) { 366 Log.v(this, "isGenericString: " + isGeneric); 367 final int resId = isGeneric ? R.string.card_title_in_call : R.string.card_title_conf_call; 368 return getView().getResources().getString(resId); 369 } 370 371 private Drawable getConferencePhoto(boolean isGeneric) { 372 Log.v(this, "isGenericPhoto: " + isGeneric); 373 final int resId = isGeneric ? R.drawable.picture_dialing : R.drawable.picture_conference; 374 return getView().getResources().getDrawable(resId); 375 } 376 377 private void setBluetoothOn(boolean onOff) { 378 // Also, display a special icon (alongside the "Incoming call" 379 // label) if there's an incoming call and audio will be routed 380 // to bluetooth when you answer it. 381 final int bluetoothIconId = R.drawable.ic_in_call_bt_dk; 382 383 if (onOff) { 384 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0); 385 mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5)); 386 } else { 387 // Clear out any icons 388 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 389 } 390 } 391 392 /** 393 * Gets the call state label based on the state of the call and 394 * cause of disconnect 395 */ 396 private String getCallStateLabelFromState(int state, int cause) { 397 final Context context = getView().getContext(); 398 String callStateLabel = null; // Label to display as part of the call banner 399 400 if (Call.State.IDLE == state) { 401 // "Call state" is meaningless in this state. 402 403 } else if (Call.State.ACTIVE == state) { 404 // We normally don't show a "call state label" at all in 405 // this state (but see below for some special cases). 406 407 } else if (Call.State.ONHOLD == state) { 408 callStateLabel = context.getString(R.string.card_title_on_hold); 409 } else if (Call.State.DIALING == state) { 410 callStateLabel = context.getString(R.string.card_title_dialing); 411 } else if (Call.State.REDIALING == state) { 412 callStateLabel = context.getString(R.string.card_title_redialing); 413 } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) { 414 callStateLabel = context.getString(R.string.card_title_incoming_call); 415 416 } else if (Call.State.DISCONNECTING == state) { 417 // While in the DISCONNECTING state we display a "Hanging up" 418 // message in order to make the UI feel more responsive. (In 419 // GSM it's normal to see a delay of a couple of seconds while 420 // negotiating the disconnect with the network, so the "Hanging 421 // up" state at least lets the user know that we're doing 422 // something. This state is currently not used with CDMA.) 423 callStateLabel = context.getString(R.string.card_title_hanging_up); 424 425 } else if (Call.State.DISCONNECTED == state) { 426 callStateLabel = getCallFailedString(cause); 427 428 } else { 429 Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state); 430 } 431 432 return callStateLabel; 433 } 434 435 /** 436 * Maps the disconnect cause to a resource string. 437 * 438 * @param cause disconnect cause as defined in {@link DisconnectCause} 439 */ 440 private String getCallFailedString(int cause) { 441 int resID = R.string.card_title_call_ended; 442 443 // TODO: The card *title* should probably be "Call ended" in all 444 // cases, but if the DisconnectCause was an error condition we should 445 // probably also display the specific failure reason somewhere... 446 447 switch (cause) { 448 case DisconnectCause.BUSY: 449 resID = R.string.callFailed_userBusy; 450 break; 451 452 case DisconnectCause.CONGESTION: 453 resID = R.string.callFailed_congestion; 454 break; 455 456 case DisconnectCause.TIMED_OUT: 457 resID = R.string.callFailed_timedOut; 458 break; 459 460 case DisconnectCause.SERVER_UNREACHABLE: 461 resID = R.string.callFailed_server_unreachable; 462 break; 463 464 case DisconnectCause.NUMBER_UNREACHABLE: 465 resID = R.string.callFailed_number_unreachable; 466 break; 467 468 case DisconnectCause.INVALID_CREDENTIALS: 469 resID = R.string.callFailed_invalid_credentials; 470 break; 471 472 case DisconnectCause.SERVER_ERROR: 473 resID = R.string.callFailed_server_error; 474 break; 475 476 case DisconnectCause.OUT_OF_NETWORK: 477 resID = R.string.callFailed_out_of_network; 478 break; 479 480 case DisconnectCause.LOST_SIGNAL: 481 case DisconnectCause.CDMA_DROP: 482 resID = R.string.callFailed_noSignal; 483 break; 484 485 case DisconnectCause.LIMIT_EXCEEDED: 486 resID = R.string.callFailed_limitExceeded; 487 break; 488 489 case DisconnectCause.POWER_OFF: 490 resID = R.string.callFailed_powerOff; 491 break; 492 493 case DisconnectCause.ICC_ERROR: 494 resID = R.string.callFailed_simError; 495 break; 496 497 case DisconnectCause.OUT_OF_SERVICE: 498 resID = R.string.callFailed_outOfService; 499 break; 500 501 case DisconnectCause.INVALID_NUMBER: 502 case DisconnectCause.UNOBTAINABLE_NUMBER: 503 resID = R.string.callFailed_unobtainable_number; 504 break; 505 506 default: 507 resID = R.string.card_title_call_ended; 508 break; 509 } 510 return this.getView().getContext().getString(resID); 511 } 512 513 private void showAndInitializeSecondaryCallInfo() { 514 mSecondaryCallInfo.setVisibility(View.VISIBLE); 515 516 // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible 517 // until mSecondaryCallInfo is inflated in the call above. 518 if (mSecondaryCallName == null) { 519 mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); 520 } 521 mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() { 522 @Override 523 public void onClick(View v) { 524 getPresenter().secondaryInfoClicked(); 525 } 526 }); 527 } 528 529 public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 530 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 531 dispatchPopulateAccessibilityEvent(event, mPrimaryName); 532 dispatchPopulateAccessibilityEvent(event, mPhoneNumber); 533 return; 534 } 535 dispatchPopulateAccessibilityEvent(event, mCallStateLabel); 536 dispatchPopulateAccessibilityEvent(event, mPrimaryName); 537 dispatchPopulateAccessibilityEvent(event, mPhoneNumber); 538 dispatchPopulateAccessibilityEvent(event, mCallTypeLabel); 539 dispatchPopulateAccessibilityEvent(event, mSecondaryCallName); 540 541 return; 542 } 543 544 @Override 545 public void setEndCallButtonEnabled(boolean enabled) { 546 mEndCallButton.setVisibility(enabled ? View.VISIBLE : View.GONE); 547 } 548 549 private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) { 550 if (view == null) return; 551 final List<CharSequence> eventText = event.getText(); 552 int size = eventText.size(); 553 view.dispatchPopulateAccessibilityEvent(event); 554 // if no text added write null to keep relative position 555 if (size == eventText.size()) { 556 eventText.add(null); 557 } 558 } 559 560 public void animateForNewOutgoingCall() { 561 final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent(); 562 563 final ViewTreeObserver observer = getView().getViewTreeObserver(); 564 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 565 @Override 566 public void onGlobalLayout() { 567 final ViewTreeObserver observer = getView().getViewTreeObserver(); 568 if (!observer.isAlive()) { 569 return; 570 } 571 observer.removeOnGlobalLayoutListener(this); 572 573 final int originalHeight = mPrimaryCallCardContainer.getHeight(); 574 final LayoutIgnoringListener listener = new LayoutIgnoringListener(); 575 mPrimaryCallCardContainer.addOnLayoutChangeListener(listener); 576 577 // Prepare the state of views before the circular reveal animation 578 mPrimaryCallCardContainer.setBottom(parent.getHeight()); 579 mEndCallButton.setTranslationY(200); 580 mCallButtonsContainer.setAlpha(0); 581 mCallStateLabel.setAlpha(0); 582 mPrimaryName.setAlpha(0); 583 mCallTypeLabel.setAlpha(0); 584 mCallNumberAndLabel.setAlpha(0); 585 586 final Animator revealAnimator = getRevealAnimator(); 587 final Animator shrinkAnimator = 588 getShrinkAnimator(parent.getHeight(), originalHeight); 589 590 final AnimatorSet set = new AnimatorSet(); 591 set.playSequentially(revealAnimator, shrinkAnimator); 592 set.addListener(new AnimatorListenerAdapter() { 593 @Override 594 public void onAnimationCancel(Animator animation) { 595 mPrimaryCallCardContainer.removeOnLayoutChangeListener(listener); 596 } 597 598 @Override 599 public void onAnimationEnd(Animator animation) { 600 mPrimaryCallCardContainer.removeOnLayoutChangeListener(listener); 601 } 602 }); 603 set.start(); 604 } 605 }); 606 } 607 608 /** 609 * Animator that performs the upwards shrinking animation of the blue call card scrim. 610 * At the start of the animation, each child view is moved downwards by a pre-specified amount 611 * and then translated upwards together with the scrim. 612 */ 613 private Animator getShrinkAnimator(int startHeight, int endHeight) { 614 final Animator shrinkAnimator = 615 ObjectAnimator.ofInt(mPrimaryCallCardContainer, "bottom", 616 startHeight, endHeight); 617 shrinkAnimator.setDuration(SHRINK_ANIMATION_DURATION); 618 shrinkAnimator.addListener(new AnimatorListenerAdapter() { 619 @Override 620 public void onAnimationStart(Animator animation) { 621 assignTranslateAnimation(mCallStateLabel, 1); 622 assignTranslateAnimation(mPrimaryName, 2); 623 assignTranslateAnimation(mCallNumberAndLabel, 3); 624 assignTranslateAnimation(mCallTypeLabel, 4); 625 assignTranslateAnimation(mCallButtonsContainer, 5); 626 627 mEndCallButton.animate().translationY(0) 628 .setDuration(SHRINK_ANIMATION_DURATION); 629 } 630 }); 631 shrinkAnimator.setInterpolator(AnimUtils.EASE_IN); 632 return shrinkAnimator; 633 } 634 635 private Animator getRevealAnimator() { 636 final Activity activity = getActivity(); 637 final View view = activity.getWindow().getDecorView(); 638 final Display display = activity.getWindowManager().getDefaultDisplay(); 639 final Point size = new Point(); 640 display.getSize(size); 641 642 final ValueAnimator valueAnimator = ViewAnimationUtils.createCircularReveal(view, 643 size.x / 2, size.y / 2, 0, Math.max(size.x, size.y)); 644 valueAnimator.setDuration(REVEAL_ANIMATION_DURATION); 645 return valueAnimator; 646 } 647 648 private void assignTranslateAnimation(View view, int offset) { 649 view.setTranslationY(mTranslationOffset * offset); 650 view.animate().translationY(0).alpha(1).withLayer() 651 .setDuration(SHRINK_ANIMATION_DURATION).setInterpolator(AnimUtils.EASE_IN); 652 } 653 654 private final class LayoutIgnoringListener implements View.OnLayoutChangeListener { 655 @Override 656 public void onLayoutChange(View v, 657 int left, 658 int top, 659 int right, 660 int bottom, 661 int oldLeft, 662 int oldTop, 663 int oldRight, 664 int oldBottom) { 665 v.setLeft(oldLeft); 666 v.setRight(oldRight); 667 v.setTop(oldTop); 668 v.setBottom(oldBottom); 669 } 670 } 671} 672