ActionBarContextView.java revision a1e6358a4c62c8b6de1f2428901e45b688bd9e9f
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.view.ActionMode;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.animation.DecelerateInterpolator;
34import android.widget.LinearLayout;
35import android.widget.TextView;
36
37/**
38 * @hide
39 */
40public class ActionBarContextView extends ViewGroup implements AnimatorListener {
41    private static final String TAG = "ActionBarContextView";
42
43    private int mContentHeight;
44
45    private CharSequence mTitle;
46    private CharSequence mSubtitle;
47
48    private View mClose;
49    private View mCustomView;
50    private LinearLayout mTitleLayout;
51    private TextView mTitleView;
52    private TextView mSubtitleView;
53    private int mTitleStyleRes;
54    private int mSubtitleStyleRes;
55    private ActionMenuView mMenuView;
56
57    private Animator mCurrentAnimation;
58    private boolean mAnimateInOnLayout;
59    private int mAnimationMode;
60
61    private static final int ANIMATE_IDLE = 0;
62    private static final int ANIMATE_IN = 1;
63    private static final int ANIMATE_OUT = 2;
64
65    public ActionBarContextView(Context context) {
66        this(context, null);
67    }
68
69    public ActionBarContextView(Context context, AttributeSet attrs) {
70        this(context, attrs, com.android.internal.R.attr.actionModeStyle);
71    }
72
73    public ActionBarContextView(Context context, AttributeSet attrs, int defStyle) {
74        super(context, attrs, defStyle);
75
76        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionMode, defStyle, 0);
77        setBackgroundDrawable(a.getDrawable(
78                com.android.internal.R.styleable.ActionMode_background));
79        mTitleStyleRes = a.getResourceId(
80                com.android.internal.R.styleable.ActionMode_titleTextStyle, 0);
81        mSubtitleStyleRes = a.getResourceId(
82                com.android.internal.R.styleable.ActionMode_subtitleTextStyle, 0);
83
84        mContentHeight = a.getLayoutDimension(
85                com.android.internal.R.styleable.ActionMode_height, 0);
86        a.recycle();
87    }
88
89    @Override
90    public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
91        // No starting an action mode for an existing action mode UI child! (Where would it go?)
92        return null;
93    }
94
95    public void setHeight(int height) {
96        mContentHeight = height;
97    }
98
99    public void setCustomView(View view) {
100        if (mCustomView != null) {
101            removeView(mCustomView);
102        }
103        mCustomView = view;
104        if (mTitleLayout != null) {
105            removeView(mTitleLayout);
106            mTitleLayout = null;
107        }
108        if (view != null) {
109            addView(view);
110        }
111        requestLayout();
112    }
113
114    public void setTitle(CharSequence title) {
115        mTitle = title;
116        initTitle();
117    }
118
119    public void setSubtitle(CharSequence subtitle) {
120        mSubtitle = subtitle;
121        initTitle();
122    }
123
124    public CharSequence getTitle() {
125        return mTitle;
126    }
127
128    public CharSequence getSubtitle() {
129        return mSubtitle;
130    }
131
132    private void initTitle() {
133        if (mTitleLayout == null) {
134            LayoutInflater inflater = LayoutInflater.from(getContext());
135            inflater.inflate(R.layout.action_bar_title_item, this);
136            mTitleLayout = (LinearLayout) getChildAt(getChildCount() - 1);
137            mTitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_title);
138            mSubtitleView = (TextView) mTitleLayout.findViewById(R.id.action_bar_subtitle);
139            if (mTitle != null) {
140                mTitleView.setText(mTitle);
141                if (mTitleStyleRes != 0) {
142                    mTitleView.setTextAppearance(mContext, mTitleStyleRes);
143                }
144            }
145            if (mSubtitle != null) {
146                mSubtitleView.setText(mSubtitle);
147                if (mSubtitleStyleRes != 0) {
148                    mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes);
149                }
150                mSubtitleView.setVisibility(VISIBLE);
151            }
152        } else {
153            mTitleView.setText(mTitle);
154            mSubtitleView.setText(mSubtitle);
155            mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE);
156            if (mTitleLayout.getParent() == null) {
157                addView(mTitleLayout);
158            }
159        }
160    }
161
162    public void initForMode(final ActionMode mode) {
163        if (mClose == null) {
164            LayoutInflater inflater = LayoutInflater.from(mContext);
165            mClose = inflater.inflate(R.layout.action_mode_close_item, this, false);
166            addView(mClose);
167        } else if (mClose.getParent() == null) {
168            addView(mClose);
169        }
170
171        View closeButton = mClose.findViewById(R.id.action_mode_close_button);
172        closeButton.setOnClickListener(new OnClickListener() {
173            public void onClick(View v) {
174                mode.finish();
175            }
176        });
177
178        final MenuBuilder menu = (MenuBuilder) mode.getMenu();
179        mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
180        mMenuView.setOverflowReserved(true);
181        mMenuView.updateChildren(false);
182        addView(mMenuView);
183
184        mAnimateInOnLayout = true;
185    }
186
187    public void closeMode() {
188        if (mAnimationMode == ANIMATE_OUT) {
189            // Called again during close; just finish what we were doing.
190            return;
191        }
192        if (mClose == null) {
193            killMode();
194            return;
195        }
196
197        finishAnimation();
198        mAnimationMode = ANIMATE_OUT;
199        mCurrentAnimation = makeOutAnimation();
200        mCurrentAnimation.start();
201    }
202
203    private void finishAnimation() {
204        final Animator a = mCurrentAnimation;
205        if (a != null) {
206            mCurrentAnimation = null;
207            a.end();
208        }
209    }
210
211    public void killMode() {
212        finishAnimation();
213        removeAllViews();
214        mCustomView = null;
215        mMenuView = null;
216        mAnimateInOnLayout = false;
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