1 /* 2 * Copyright (C) 2015 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 package com.android.messaging.ui.conversation; 17 18 import android.animation.Animator; 19 import android.animation.Animator.AnimatorListener; 20 import android.animation.ObjectAnimator; 21 import android.content.Context; 22 import android.util.AttributeSet; 23 import android.view.ViewGroup; 24 import android.widget.LinearLayout; 25 26 import com.android.messaging.R; 27 import com.android.messaging.annotation.VisibleForAnimation; 28 import com.android.messaging.datamodel.data.ConversationMessageBubbleData; 29 import com.android.messaging.datamodel.data.ConversationMessageData; 30 import com.android.messaging.util.UiUtils; 31 32 /** 33 * Shows the message bubble for one conversation message. It is able to animate size changes 34 * by morphing when the message content changes size. 35 */ 36 // TODO: Move functionality from ConversationMessageView into this class as appropriate 37 public class ConversationMessageBubbleView extends LinearLayout { 38 private int mIntrinsicWidth; 39 private int mMorphedWidth; 40 private ObjectAnimator mAnimator; 41 private boolean mShouldAnimateWidthChange; 42 private final ConversationMessageBubbleData mData; 43 private int mRunningStartWidth; 44 private ViewGroup mBubbleBackground; 45 ConversationMessageBubbleView(final Context context, final AttributeSet attrs)46 public ConversationMessageBubbleView(final Context context, final AttributeSet attrs) { 47 super(context, attrs); 48 mData = new ConversationMessageBubbleData(); 49 } 50 51 @Override onFinishInflate()52 protected void onFinishInflate() { 53 super.onFinishInflate(); 54 mBubbleBackground = (ViewGroup) findViewById(R.id.message_text_and_info); 55 } 56 57 @Override onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)58 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 59 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 60 61 final int newIntrinsicWidth = getMeasuredWidth(); 62 if (mIntrinsicWidth == 0 && newIntrinsicWidth != mIntrinsicWidth) { 63 if (mShouldAnimateWidthChange) { 64 kickOffMorphAnimation(mIntrinsicWidth, newIntrinsicWidth); 65 } 66 mIntrinsicWidth = newIntrinsicWidth; 67 } 68 69 if (mMorphedWidth > 0) { 70 mBubbleBackground.getLayoutParams().width = mMorphedWidth; 71 } else { 72 mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT; 73 } 74 mBubbleBackground.requestLayout(); 75 } 76 77 @VisibleForAnimation setMorphWidth(final int width)78 public void setMorphWidth(final int width) { 79 mMorphedWidth = width; 80 requestLayout(); 81 } 82 bind(final ConversationMessageData data)83 public void bind(final ConversationMessageData data) { 84 final boolean changed = mData.bind(data); 85 // Animate width change only when we are binding to the same message, so that we may 86 // animate view size changes on the same message bubble due to things like status text 87 // change. 88 // Don't animate width change when the bubble contains attachments. Width animation is 89 // only suitable for text-only messages (where the bubble size change due to status or 90 // time stamp changes). 91 mShouldAnimateWidthChange = !changed && !data.hasAttachments(); 92 if (mAnimator == null) { 93 mMorphedWidth = 0; 94 } 95 } 96 kickOffMorphAnimation(final int oldWidth, final int newWidth)97 public void kickOffMorphAnimation(final int oldWidth, final int newWidth) { 98 if (mAnimator != null) { 99 mAnimator.setIntValues(mRunningStartWidth, newWidth); 100 return; 101 } 102 mRunningStartWidth = oldWidth; 103 mAnimator = ObjectAnimator.ofInt(this, "morphWidth", oldWidth, newWidth); 104 mAnimator.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION); 105 mAnimator.addListener(new AnimatorListener() { 106 @Override 107 public void onAnimationStart(Animator animator) { 108 } 109 110 @Override 111 public void onAnimationEnd(Animator animator) { 112 mAnimator = null; 113 mMorphedWidth = 0; 114 // Allow the bubble to resize if, for example, the status text changed during 115 // the animation. This will snap to the bigger size if needed. This is intentional 116 // as animating immediately after looks really bad and switching layout params 117 // during the original animation does not achieve the desired effect. 118 mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT; 119 mBubbleBackground.requestLayout(); 120 } 121 122 @Override 123 public void onAnimationCancel(Animator animator) { 124 } 125 126 @Override 127 public void onAnimationRepeat(Animator animator) { 128 } 129 }); 130 mAnimator.start(); 131 } 132 } 133