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