CallCardFragment.java revision a2694eb9f05b8fc1f954c35a0bfd1acd1e9376fa
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 mCallStateLabel.getAnimation().cancel(); 309 mCallStateLabel.setAlpha(0); 310 mCallStateLabel.setVisibility(View.GONE); 311 } 312 313 if (Call.State.INCOMING == state) { 314 setBluetoothOn(bluetoothOn); 315 } 316 317 mHandoffButton.setEnabled(isHandoffCapable && !isHandoffPending); 318 mHandoffButton.setVisibility(isWiFi || mHandoffButton.isEnabled() ? 319 View.VISIBLE : View.GONE); 320 mHandoffButton.setImageResource(isWiFi ? 321 R.drawable.ic_in_call_wifi : R.drawable.ic_in_call_pstn); 322 } 323 324 private void showInternetCallLabel(boolean show) { 325 if (show) { 326 final String label = getView().getContext().getString( 327 R.string.incall_call_type_label_sip); 328 mCallTypeLabel.setVisibility(View.VISIBLE); 329 mCallTypeLabel.setText(label); 330 } else { 331 mCallTypeLabel.setVisibility(View.GONE); 332 } 333 } 334 335 @Override 336 public void setPrimaryCallElapsedTime(boolean show, String callTimeElapsed) { 337 if (show) { 338 if (mElapsedTime.getVisibility() != View.VISIBLE) { 339 AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION); 340 } 341 mElapsedTime.setText(callTimeElapsed); 342 } else { 343 // hide() animation has no effect if it is already hidden. 344 AnimUtils.fadeOut(mElapsedTime, AnimUtils.DEFAULT_DURATION); 345 } 346 } 347 348 private void setDrawableToImageView(ImageView view, Drawable photo) { 349 if (photo == null) { 350 photo = view.getResources().getDrawable(R.drawable.picture_unknown); 351 } 352 353 final Drawable current = view.getDrawable(); 354 if (current == null) { 355 view.setImageDrawable(photo); 356 AnimUtils.fadeIn(mElapsedTime, AnimUtils.DEFAULT_DURATION); 357 } else { 358 InCallAnimationUtils.startCrossFade(view, current, photo); 359 view.setVisibility(View.VISIBLE); 360 } 361 } 362 363 private String getConferenceString(boolean isGeneric) { 364 Log.v(this, "isGenericString: " + isGeneric); 365 final int resId = isGeneric ? R.string.card_title_in_call : R.string.card_title_conf_call; 366 return getView().getResources().getString(resId); 367 } 368 369 private Drawable getConferencePhoto(boolean isGeneric) { 370 Log.v(this, "isGenericPhoto: " + isGeneric); 371 final int resId = isGeneric ? R.drawable.picture_dialing : R.drawable.picture_conference; 372 return getView().getResources().getDrawable(resId); 373 } 374 375 private void setBluetoothOn(boolean onOff) { 376 // Also, display a special icon (alongside the "Incoming call" 377 // label) if there's an incoming call and audio will be routed 378 // to bluetooth when you answer it. 379 final int bluetoothIconId = R.drawable.ic_in_call_bt_dk; 380 381 if (onOff) { 382 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0); 383 mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5)); 384 } else { 385 // Clear out any icons 386 mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); 387 } 388 } 389 390 /** 391 * Gets the call state label based on the state of the call and 392 * cause of disconnect 393 */ 394 private String getCallStateLabelFromState(int state, int cause) { 395 final Context context = getView().getContext(); 396 String callStateLabel = null; // Label to display as part of the call banner 397 398 if (Call.State.IDLE == state) { 399 // "Call state" is meaningless in this state. 400 401 } else if (Call.State.ACTIVE == state) { 402 // We normally don't show a "call state label" at all in 403 // this state (but see below for some special cases). 404 405 } else if (Call.State.ONHOLD == state) { 406 callStateLabel = context.getString(R.string.card_title_on_hold); 407 } else if (Call.State.DIALING == state) { 408 callStateLabel = context.getString(R.string.card_title_dialing); 409 } else if (Call.State.REDIALING == state) { 410 callStateLabel = context.getString(R.string.card_title_redialing); 411 } else if (Call.State.INCOMING == state || Call.State.CALL_WAITING == state) { 412 callStateLabel = context.getString(R.string.card_title_incoming_call); 413 414 } else if (Call.State.DISCONNECTING == state) { 415 // While in the DISCONNECTING state we display a "Hanging up" 416 // message in order to make the UI feel more responsive. (In 417 // GSM it's normal to see a delay of a couple of seconds while 418 // negotiating the disconnect with the network, so the "Hanging 419 // up" state at least lets the user know that we're doing 420 // something. This state is currently not used with CDMA.) 421 callStateLabel = context.getString(R.string.card_title_hanging_up); 422 423 } else if (Call.State.DISCONNECTED == state) { 424 callStateLabel = getCallFailedString(cause); 425 426 } else { 427 Log.wtf(this, "updateCallStateWidgets: unexpected call: " + state); 428 } 429 430 return callStateLabel; 431 } 432 433 /** 434 * Maps the disconnect cause to a resource string. 435 * 436 * @param cause disconnect cause as defined in {@link DisconnectCause} 437 */ 438 private String getCallFailedString(int cause) { 439 int resID = R.string.card_title_call_ended; 440 441 // TODO: The card *title* should probably be "Call ended" in all 442 // cases, but if the DisconnectCause was an error condition we should 443 // probably also display the specific failure reason somewhere... 444 445 switch (cause) { 446 case DisconnectCause.BUSY: 447 resID = R.string.callFailed_userBusy; 448 break; 449 450 case DisconnectCause.CONGESTION: 451 resID = R.string.callFailed_congestion; 452 break; 453 454 case DisconnectCause.TIMED_OUT: 455 resID = R.string.callFailed_timedOut; 456 break; 457 458 case DisconnectCause.SERVER_UNREACHABLE: 459 resID = R.string.callFailed_server_unreachable; 460 break; 461 462 case DisconnectCause.NUMBER_UNREACHABLE: 463 resID = R.string.callFailed_number_unreachable; 464 break; 465 466 case DisconnectCause.INVALID_CREDENTIALS: 467 resID = R.string.callFailed_invalid_credentials; 468 break; 469 470 case DisconnectCause.SERVER_ERROR: 471 resID = R.string.callFailed_server_error; 472 break; 473 474 case DisconnectCause.OUT_OF_NETWORK: 475 resID = R.string.callFailed_out_of_network; 476 break; 477 478 case DisconnectCause.LOST_SIGNAL: 479 case DisconnectCause.CDMA_DROP: 480 resID = R.string.callFailed_noSignal; 481 break; 482 483 case DisconnectCause.LIMIT_EXCEEDED: 484 resID = R.string.callFailed_limitExceeded; 485 break; 486 487 case DisconnectCause.POWER_OFF: 488 resID = R.string.callFailed_powerOff; 489 break; 490 491 case DisconnectCause.ICC_ERROR: 492 resID = R.string.callFailed_simError; 493 break; 494 495 case DisconnectCause.OUT_OF_SERVICE: 496 resID = R.string.callFailed_outOfService; 497 break; 498 499 case DisconnectCause.INVALID_NUMBER: 500 case DisconnectCause.UNOBTAINABLE_NUMBER: 501 resID = R.string.callFailed_unobtainable_number; 502 break; 503 504 default: 505 resID = R.string.card_title_call_ended; 506 break; 507 } 508 return this.getView().getContext().getString(resID); 509 } 510 511 private void showAndInitializeSecondaryCallInfo() { 512 mSecondaryCallInfo.setVisibility(View.VISIBLE); 513 514 // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccesible 515 // until mSecondaryCallInfo is inflated in the call above. 516 if (mSecondaryCallName == null) { 517 mSecondaryCallName = (TextView) getView().findViewById(R.id.secondaryCallName); 518 } 519 mSecondaryCallInfo.setOnClickListener(new View.OnClickListener() { 520 @Override 521 public void onClick(View v) { 522 getPresenter().secondaryInfoClicked(); 523 } 524 }); 525 } 526 527 public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 528 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 529 dispatchPopulateAccessibilityEvent(event, mPrimaryName); 530 dispatchPopulateAccessibilityEvent(event, mPhoneNumber); 531 return; 532 } 533 dispatchPopulateAccessibilityEvent(event, mCallStateLabel); 534 dispatchPopulateAccessibilityEvent(event, mPrimaryName); 535 dispatchPopulateAccessibilityEvent(event, mPhoneNumber); 536 dispatchPopulateAccessibilityEvent(event, mCallTypeLabel); 537 dispatchPopulateAccessibilityEvent(event, mSecondaryCallName); 538 539 return; 540 } 541 542 @Override 543 public void setEndCallButtonEnabled(boolean enabled) { 544 mEndCallButton.setVisibility(enabled ? View.VISIBLE : View.GONE); 545 } 546 547 private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) { 548 if (view == null) return; 549 final List<CharSequence> eventText = event.getText(); 550 int size = eventText.size(); 551 view.dispatchPopulateAccessibilityEvent(event); 552 // if no text added write null to keep relative position 553 if (size == eventText.size()) { 554 eventText.add(null); 555 } 556 } 557 558 public void animateForNewOutgoingCall() { 559 final ViewGroup parent = (ViewGroup) mPrimaryCallCardContainer.getParent(); 560 561 final ViewTreeObserver observer = getView().getViewTreeObserver(); 562 observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 563 @Override 564 public void onGlobalLayout() { 565 final ViewTreeObserver observer = getView().getViewTreeObserver(); 566 if (!observer.isAlive()) { 567 return; 568 } 569 observer.removeOnGlobalLayoutListener(this); 570 571 final int originalHeight = mPrimaryCallCardContainer.getHeight(); 572 final LayoutIgnoringListener listener = new LayoutIgnoringListener(); 573 mPrimaryCallCardContainer.addOnLayoutChangeListener(listener); 574 575 // Prepare the state of views before the circular reveal animation 576 mPrimaryCallCardContainer.setBottom(parent.getHeight()); 577 mEndCallButton.setTranslationY(200); 578 mCallButtonsContainer.setAlpha(0); 579 mCallStateLabel.setAlpha(0); 580 mPrimaryName.setAlpha(0); 581 mCallTypeLabel.setAlpha(0); 582 mCallNumberAndLabel.setAlpha(0); 583 584 final Animator revealAnimator = getRevealAnimator(); 585 final Animator shrinkAnimator = 586 getShrinkAnimator(parent.getHeight(), originalHeight); 587 588 final AnimatorSet set = new AnimatorSet(); 589 set.playSequentially(revealAnimator, shrinkAnimator); 590 set.addListener(new AnimatorListenerAdapter() { 591 @Override 592 public void onAnimationCancel(Animator animation) { 593 mPrimaryCallCardContainer.removeOnLayoutChangeListener(listener); 594 } 595 596 @Override 597 public void onAnimationEnd(Animator animation) { 598 mPrimaryCallCardContainer.removeOnLayoutChangeListener(listener); 599 } 600 }); 601 set.start(); 602 } 603 }); 604 } 605 606 /** 607 * Animator that performs the upwards shrinking animation of the blue call card scrim. 608 * At the start of the animation, each child view is moved downwards by a pre-specified amount 609 * and then translated upwards together with the scrim. 610 */ 611 private Animator getShrinkAnimator(int startHeight, int endHeight) { 612 final Animator shrinkAnimator = 613 ObjectAnimator.ofInt(mPrimaryCallCardContainer, "bottom", 614 startHeight, endHeight); 615 shrinkAnimator.setDuration(SHRINK_ANIMATION_DURATION); 616 shrinkAnimator.addListener(new AnimatorListenerAdapter() { 617 @Override 618 public void onAnimationStart(Animator animation) { 619 assignTranslateAnimation(mCallStateLabel, 1); 620 assignTranslateAnimation(mPrimaryName, 2); 621 assignTranslateAnimation(mCallNumberAndLabel, 3); 622 assignTranslateAnimation(mCallTypeLabel, 4); 623 assignTranslateAnimation(mCallButtonsContainer, 5); 624 625 mEndCallButton.animate().translationY(0) 626 .setDuration(SHRINK_ANIMATION_DURATION); 627 } 628 }); 629 shrinkAnimator.setInterpolator(AnimUtils.EASE_IN); 630 return shrinkAnimator; 631 } 632 633 private Animator getRevealAnimator() { 634 final Activity activity = getActivity(); 635 final View view = activity.getWindow().getDecorView(); 636 final Display display = activity.getWindowManager().getDefaultDisplay(); 637 final Point size = new Point(); 638 display.getSize(size); 639 640 final ValueAnimator valueAnimator = ViewAnimationUtils.createCircularReveal(view, 641 size.x / 2, size.y / 2, 0, Math.max(size.x, size.y)); 642 valueAnimator.setDuration(REVEAL_ANIMATION_DURATION); 643 return valueAnimator; 644 } 645 646 private void assignTranslateAnimation(View view, int offset) { 647 view.setTranslationY(mTranslationOffset * offset); 648 view.animate().translationY(0).alpha(1).withLayer() 649 .setDuration(SHRINK_ANIMATION_DURATION).setInterpolator(AnimUtils.EASE_IN); 650 } 651 652 private final class LayoutIgnoringListener implements View.OnLayoutChangeListener { 653 @Override 654 public void onLayoutChange(View v, 655 int left, 656 int top, 657 int right, 658 int bottom, 659 int oldLeft, 660 int oldTop, 661 int oldRight, 662 int oldBottom) { 663 v.setLeft(oldLeft); 664 v.setRight(oldRight); 665 v.setTop(oldTop); 666 v.setBottom(oldBottom); 667 } 668 } 669} 670