ActionBarContextView.java revision 45f1e08c348ccb129bcc25e438c05421f7123f41
1/*
2 * Copyright (C) 2010 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 */
16package com.android.internal.widget;
17
18import com.android.internal.R;
19import com.android.internal.view.menu.ActionMenuView;
20import com.android.internal.view.menu.MenuBuilder;
21
22import android.animation.Animator;
23import android.animation.Animator.AnimatorListener;
24import android.animation.AnimatorSet;
25import android.animation.ObjectAnimator;
26import android.content.Context;
27import android.content.res.TypedArray;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.ActionMode;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.animation.DecelerateInterpolator;
35import android.widget.LinearLayout;
36import android.widget.TextView;
37
38/**
39 * @hide
40 */
41public class ActionBarContextView extends ViewGroup implements AnimatorListener {
42    private int mContentHeight;
43
44    private CharSequence mTitle;
45    private CharSequence mSubtitle;
46
47    private View mClose;
48    private View mCustomView;
49    private LinearLayout mTitleLayout;
50    private TextView mTitleView;
51    private TextView mSubtitleView;
52    private int mTitleStyleRes;
53    private int mSubtitleStyleRes;
54    private ActionMenuView mMenuView;
55
56    private Animator mCurrentAnimation;
57    private boolean mAnimateInOnLayout;
58    private int mAnimationMode;
59
60    private static final int ANIMATE_IDLE = 0;
61    private static final int ANIMATE_IN = 1;
62    private static final int ANIMATE_OUT = 2;
63
64    public ActionBarContextView(Context context) {
65        this(context, null);
66    }
67
68    public ActionBarContextView(Context context, AttributeSet attrs) {
69        this(context, attrs, com.android.internal.R.attr.actionModeStyle);
70    }
71
72    public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
73        super(context, attrs, defStyle);
74
75        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0);
76        setBackgroundDrawable(a.getDrawable(
77                com.android.internal.R.styleable.ActionMode_background));
78        mTitleStyleRes = a.getResourceId(
79                com.android.internal.R.styleable.ActionMode_titleTextStyle, 0);
80        mSubtitleStyleRes = a.getResourceId(
81                com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0);
82
83        mContentHeight = a.getLayoutDimension(
84                com.android.internal.R.styleable.ActionMode_height, 0);
85        a.recycle();
86    }
87
88    @Override
89    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
90        // No starting an action mode for an existing action mode UI child! (Where would it go?)
91        return null;
92    }
93
94    public void setHeight(int height) {
95        mContentHeight = height;
96    }
97
98    public void setCustomView(View view) {
99        if (mCustomView != null) {
100            removeView(mCustomView);
101        }
102        mCustomView = view;
103        if (mTitleLayout != null) {
104            removeView(mTitleLayout);
105            mTitleLayout = null;
106        }
107        if (view != null) {
108            addView(view);
109        }
110        requestLayout();
111    }
112
113    public void setTitle(CharSequence title) {
114        mTitle = title;
115        initTitle();
116    }
117
118    public void setSubtitle(CharSequence subtitle) {
119        mSubtitle = subtitle;
120        initTitle();
121    }
122
123    public CharSequence getTitle() {
124        return mTitle;
125    }
126
127    public CharSequence getSubtitle() {
128        return mSubtitle;
129    }
130
131    private void initTitle() {
132        if (mTitleLayout == null) {
133            LayoutInflater inflater = LayoutInflater.from(getContext());
134            inflater.inflate(R.layout.action_bar_title_item, this);
135            mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
136            mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
137            mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
138            if (mTitle != null) {
139                mTitleView.setText(mTitle);
140                if (mTitleStyleRes != 0) {
141                    mTitleView.setTextAppearance(mContext, mTitleStyleRes);
142                }
143            }
144            if (mSubtitle != null) {
145                mSubtitleView.setText(mSubtitle);
146                if (mSubtitleStyleRes != 0) {
147                    mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
148                }
149                mSubtitleView.setVisibility(VISIBLE);
150            }
151        } else {
152            mTitleView.setText(mTitle);
153            mSubtitleView.setText(mSubtitle);
154            mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE);
155            if (mTitleLayout.getParent() == null) {
156                addView(mTitleLayout);
157            }
158        }
159    }
160
161    public void initForMode(final ActionMode mode) {
162        finishAnimation();
163
164        if (mClose == null) {
165            LayoutInflater inflater = LayoutInflater.from(mContext);
166            mClose = inflater.inflate(R.layout.action_mode_close_item, this, false);
167            addView(mClose);
168        } else if (mClose.getParent() == null) {
169            addView(mClose);
170        }
171
172        View closeButton = mClose.findViewById(R.id.action_mode_close_button);
173        closeButton.setOnClickListener(new OnClickListener() {
174            public void onClick(View v) {
175                mode.finish();
176            }
177        });
178
179        final MenuBuilder menu = (MenuBuilder) mode.getMenu();
180        mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
181        mMenuView.setOverflowReserved(true);
182        mMenuView.updateChildren(false);
183        addView(mMenuView);
184
185        mAnimateInOnLayout = true;
186    }
187
188    public void closeMode() {
189        if (mAnimationMode == ANIMATE_OUT) {
190            // Called again during close; just finish what we were doing.
191            return;
192        }
193        if (mClose == null) {
194            killMode();
195            return;
196        }
197
198        mAnimationMode = ANIMATE_OUT;
199        finishAnimation();
200        mCurrentAnimation = makeOutAnimation();
201        mCurrentAnimation.start();
202    }
203
204    private void finishAnimation() {
205        final Animator a = mCurrentAnimation;
206        if (a != null && a.isRunning()) {
207            mCurrentAnimation = null;
208            a.end();
209        }
210    }
211
212    public void killMode() {
213        finishAnimation();
214        removeAllViews();
215        mCustomView = null;
216        mMenuView = null;
217    }
218
219    public boolean showOverflowMenu() {
220        if (mMenuView != null) {
221            return mMenuView.showOverflowMenu();
222        }
223        return false;
224    }
225
226    public void openOverflowMenu() {
227        if (mMenuView != null) {
228            mMenuView.openOverflowMenu();
229        }
230    }
231
232    public boolean hideOverflowMenu() {
233        if (mMenuView != null) {
234            return mMenuView.hideOverflowMenu();
235        }
236        return false;
237    }
238
239    public boolean isOverflowMenuShowing() {
240        if (mMenuView != null) {
241            return mMenuView.isOverflowMenuShowing();
242        }
243        return false;
244    }
245
246    @Override
247    protected LayoutParams generateDefaultLayoutParams() {
248        // Used by custom views if they don't supply layout params. Everything else
249        // added to an ActionBarContextView should have them already.
250        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
251    }
252
253    @Override
254    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
255        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
256        if (widthMode != MeasureSpec.EXACTLY) {
257            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
258                    "with android:layout_width=\"match_parent\" (or fill_parent)");
259        }
260
261        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
262        if (heightMode == MeasureSpec.UNSPECIFIED) {
263            throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
264                    "with android:layout_height=\"wrap_content\"");
265        }
266
267        final int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
268
269        int maxHeight = mContentHeight > 0 ?
270                mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
271
272        final int verticalPadding = getPaddingTop() + getPaddingBottom();
273        int availableWidth = contentWidth - getPaddingLeft() - getPaddingRight();
274        final int height = maxHeight - verticalPadding;
275        final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
276
277        if (mClose != null) {
278            availableWidth = measureChildView(mClose, availableWidth, childSpecHeight, 0);
279        }
280
281        if (mTitleLayout != null && mCustomView == null) {
282            availableWidth = measureChildView(mTitleLayout, availableWidth, childSpecHeight, 0);
283        }
284
285        final int childCount = getChildCount();
286        for (int i = 0; i < childCount; i++) {
287            final View child = getChildAt(i);
288            if (child == mClose || child == mTitleLayout || child == mCustomView) {
289                continue;
290            }
291
292            availableWidth = measureChildView(child, availableWidth, childSpecHeight, 0);
293        }
294
295        if (mCustomView != null) {
296            LayoutParams lp = mCustomView.getLayoutParams();
297            final int customWidthMode = lp.width != LayoutParams.WRAP_CONTENT ?
298                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
299            final int customWidth = lp.width >= 0 ?
300                    Math.min(lp.width, availableWidth) : availableWidth;
301            final int customHeightMode = lp.height != LayoutParams.WRAP_CONTENT ?
302                    MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
303            final int customHeight = lp.height >= 0 ?
304                    Math.min(lp.height, height) : height;
305            mCustomView.measure(MeasureSpec.makeMeasureSpec(customWidth, customWidthMode),
306                    MeasureSpec.makeMeasureSpec(customHeight, customHeightMode));
307        }
308
309        if (mContentHeight <= 0) {
310            int measuredHeight = 0;
311            final int count = getChildCount();
312            for (int i = 0; i < count; i++) {
313                View v = getChildAt(i);
314                int paddedViewHeight = v.getMeasuredHeight() + verticalPadding;
315                if (paddedViewHeight > measuredHeight) {
316                    measuredHeight = paddedViewHeight;
317                }
318            }
319            setMeasuredDimension(contentWidth, measuredHeight);
320        } else {
321            setMeasuredDimension(contentWidth, maxHeight);
322        }
323    }
324
325    private Animator makeInAnimation() {
326        mClose.setTranslationX(-mClose.getWidth());
327        ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX", 0);
328        buttonAnimator.setDuration(200);
329        buttonAnimator.addListener(this);
330        buttonAnimator.setInterpolator(new DecelerateInterpolator());
331
332        AnimatorSet set = new AnimatorSet();
333        AnimatorSet.Builder b = set.play(buttonAnimator);
334
335        if (mMenuView != null) {
336            final int count = mMenuView.getChildCount();
337            if (count > 0) {
338                for (int i = count - 1, j = 0; i >= 0; i--, j++) {
339                    View child = mMenuView.getChildAt(i);
340                    child.setScaleY(0);
341                    ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 0, 1);
342                    a.setDuration(100);
343                    a.setStartDelay(j * 70);
344                    b.with(a);
345                }
346            }
347        }
348
349        return set;
350    }
351
352    private Animator makeOutAnimation() {
353        ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mClose, "translationX",
354                0, -mClose.getWidth());
355        buttonAnimator.setDuration(200);
356        buttonAnimator.addListener(this);
357        buttonAnimator.setInterpolator(new DecelerateInterpolator());
358
359        AnimatorSet set = new AnimatorSet();
360        AnimatorSet.Builder b = set.play(buttonAnimator);
361
362        if (mMenuView != null) {
363            final int count = mMenuView.getChildCount();
364            if (count > 0) {
365                for (int i = 0; i < 0; i++) {
366                    View child = mMenuView.getChildAt(i);
367                    child.setScaleY(0);
368                    ObjectAnimator a = ObjectAnimator.ofFloat(child, "scaleY", 1, 0);
369                    a.setDuration(100);
370                    a.setStartDelay(i * 70);
371                    b.with(a);
372                }
373            }
374        }
375
376        return set;
377    }
378
379    @Override
380    protected void onLayout(boolean changed, int l, int t, int r, int b) {
381        int x = getPaddingLeft();
382        final int y = getPaddingTop();
383        final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
384
385        if (mClose != null && mClose.getVisibility() != GONE) {
386            x += positionChild(mClose, x, y, contentHeight);
387
388            if (mAnimateInOnLayout) {
389                mAnimationMode = ANIMATE_IN;
390                mCurrentAnimation = makeInAnimation();
391                mCurrentAnimation.start();
392                mAnimateInOnLayout = false;
393            }
394        }
395
396        if (mTitleLayout != null && mCustomView == null) {
397            x += positionChild(mTitleLayout, x, y, contentHeight);
398        }
399
400        if (mCustomView != null) {
401            x += positionChild(mCustomView, x, y, contentHeight);
402        }
403
404        x = r - l - getPaddingRight();
405
406        if (mMenuView != null) {
407            x -= positionChildInverse(mMenuView, x, y, contentHeight);
408        }
409    }
410
411    private int measureChildView(View child, int availableWidth, int childSpecHeight, int spacing) {
412        child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
413                childSpecHeight);
414
415        availableWidth -= child.getMeasuredWidth();
416        availableWidth -= spacing;
417
418        return availableWidth;
419    }
420
421    private int positionChild(View child, int x, int y, int contentHeight) {
422        int childWidth = child.getMeasuredWidth();
423        int childHeight = child.getMeasuredHeight();
424        int childTop = y + (contentHeight - childHeight) / 2;
425
426        child.layout(x, childTop, x + childWidth, childTop + childHeight);
427
428        return childWidth;
429    }
430
431    private int positionChildInverse(View child, int x, int y, int contentHeight) {
432        int childWidth = child.getMeasuredWidth();
433        int childHeight = child.getMeasuredHeight();
434        int childTop = y + (contentHeight - childHeight) / 2;
435
436        child.layout(x - childWidth, childTop, x, childTop + childHeight);
437
438        return childWidth;
439    }
440
441    @Override
442    public void onAnimationStart(Animator animation) {
443    }
444
445    @Override
446    public void onAnimationEnd(Animator animation) {
447        if (mAnimationMode == ANIMATE_OUT) {
448            killMode();
449        }
450        mAnimationMode = ANIMATE_IDLE;
451    }
452
453    @Override
454    public void onAnimationCancel(Animator animation) {
455    }
456
457    @Override
458    public void onAnimationRepeat(Animator animation) {
459    }
460}
461