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