KeyPreviewChoreographer.java revision bb476be4e62b3bed7848d37df42f8fa7363b58d1
1bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka/*
2bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * Copyright (C) 2014 The Android Open Source Project
3bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka *
4bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
5bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * you may not use this file except in compliance with the License.
6bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * You may obtain a copy of the License at
7bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka *
8bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
9bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka *
10bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
11bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
12bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * See the License for the specific language governing permissions and
14bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * limitations under the License.
15bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka */
16bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
17bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokapackage com.android.inputmethod.keyboard.internal;
18bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
19bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.animation.Animator;
20bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.animation.AnimatorListenerAdapter;
21bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.animation.AnimatorSet;
22bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.animation.ObjectAnimator;
23bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.content.Context;
24bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.graphics.drawable.Drawable;
25bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.util.TypedValue;
26bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.view.LayoutInflater;
27bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.view.View;
28bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.view.ViewGroup;
29bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.view.animation.AccelerateInterpolator;
30bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.view.animation.DecelerateInterpolator;
31bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport android.widget.TextView;
32bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
33bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
34bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport com.android.inputmethod.latin.R;
35bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport com.android.inputmethod.latin.utils.CollectionUtils;
36bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport com.android.inputmethod.latin.utils.CoordinateUtils;
37bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport com.android.inputmethod.latin.utils.ViewLayoutUtils;
38bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
39bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport java.util.ArrayDeque;
40bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport java.util.HashMap;
41bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport java.util.HashSet;
42bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
43bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka/**
44bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * This class controls pop up key previews. This class decides:
45bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * - what kind of key previews should be shown.
46bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * - where key previews should be placed.
47bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * - how key previews should be shown and dismissed.
48bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka */
49bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokapublic final class KeyPreviewChoreographer {
50bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    // Free {@link TextView} pool that can be used for key preview.
51bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque();
52bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
53bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap();
54bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
55bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private final KeyPreviewDrawParams mParams;
56bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
57bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public KeyPreviewChoreographer(final KeyPreviewDrawParams params) {
58bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        mParams = params;
59bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
60bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
61bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public TextView getKeyPreviewTextView(final Key key, final ViewGroup placerView) {
62bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
63bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewTextView != null) {
64bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return previewTextView;
65bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
66bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView = mFreeKeyPreviewTextViews.poll();
67bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewTextView != null) {
68bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return previewTextView;
69bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
70bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Context context = placerView.getContext();
71bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (mParams.mLayoutId != 0) {
72bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView = (TextView)LayoutInflater.from(context)
73bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                    .inflate(mParams.mLayoutId, null);
74bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else {
75bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView = new TextView(context);
76bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
77bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        placerView.addView(previewTextView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
78bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        return previewTextView;
79bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
80bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
81bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public boolean isShowingKeyPreview(final Key key) {
82bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        return mShowingKeyPreviewTextViews.containsKey(key);
83bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
84bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
85bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public void dismissAllKeyPreviews() {
86bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        for (final Key key : new HashSet<Key>(mShowingKeyPreviewTextViews.keySet())) {
87bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            dismissKeyPreview(key, false /* withAnimation */);
88bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
89bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
90bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
91bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public void dismissKeyPreview(final Key key, final boolean withAnimation) {
92bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (key == null) {
93bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
94bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
95bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final TextView previewTextView = mShowingKeyPreviewTextViews.get(key);
96bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewTextView == null) {
97bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
98bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
99bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Object tag = previewTextView.getTag();
100bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (withAnimation) {
101bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            if (tag instanceof KeyPreviewAnimations) {
102bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
103bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                animation.startZoomOut();
104bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
105bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
106bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
107bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // Dismiss preview without animation.
108bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (tag instanceof Animator) {
109bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            ((Animator)tag).cancel();
110bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
111bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setTag(null);
112bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setVisibility(View.INVISIBLE);
113bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        mFreeKeyPreviewTextViews.add(previewTextView);
114bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
115bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
116bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    // Background state set
117bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
118bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        { // STATE_MIDDLE
119bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            {},
120bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            { R.attr.state_has_morekeys }
121bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        },
122bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        { // STATE_LEFT
123bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            { R.attr.state_left_edge },
124bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            { R.attr.state_left_edge, R.attr.state_has_morekeys }
125bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        },
126bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        { // STATE_RIGHT
127bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            { R.attr.state_right_edge },
128bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            { R.attr.state_right_edge, R.attr.state_has_morekeys }
129bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
130bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    };
131bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final int STATE_MIDDLE = 0;
132bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final int STATE_LEFT = 1;
133bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final int STATE_RIGHT = 2;
134bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final int STATE_NORMAL = 0;
135bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final int STATE_HAS_MOREKEYS = 1;
136bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
137bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public void placeKeyPreview(final Key key, final TextView previewTextView,
138bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
139bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final int keyboardViewWidth, final int[] originCoords) {
140bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setTextColor(drawParams.mPreviewTextColor);
141bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Drawable background = previewTextView.getBackground();
142bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final String label = key.getPreviewLabel();
143bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // What we show as preview should match what we show on a key top in onDraw().
144bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (label != null) {
145bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            // TODO Should take care of temporaryShiftLabel here.
146bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setCompoundDrawables(null, null, null, null);
147bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
148bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                    key.selectPreviewTextSize(drawParams));
149bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
150bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setText(label);
151bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else {
152bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
153bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setText(null);
154bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
155bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
156bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.measure(
157bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
158bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        mParams.setGeometry(previewTextView);
159bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int previewWidth = previewTextView.getMeasuredWidth();
160bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int previewHeight = mParams.mPreviewHeight;
161bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int keyDrawWidth = key.getDrawWidth();
162bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // The key preview is horizontally aligned with the center of the visible part of the
163bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
164bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // the left/right background is used if such background is specified.
165bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int statePosition;
166bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
167bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                + CoordinateUtils.x(originCoords);
168bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewX < 0) {
169bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewX = 0;
170bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            statePosition = STATE_LEFT;
171bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else if (previewX > keyboardViewWidth - previewWidth) {
172bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewX = keyboardViewWidth - previewWidth;
173bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            statePosition = STATE_RIGHT;
174bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else {
175bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            statePosition = STATE_MIDDLE;
176bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
177bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // The key preview is placed vertically above the top edge of the parent key with an
178bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // arbitrary offset.
179bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
180bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                + CoordinateUtils.y(originCoords);
181bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
182bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (background != null) {
183bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
184bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
185bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
186bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        ViewLayoutUtils.placeViewAt(
187bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                previewTextView, previewX, previewY, previewWidth, previewHeight);
188bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setPivotX(previewWidth / 2.0f);
189bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setPivotY(previewHeight);
190bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
191bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
192bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public void showKeyPreview(final Key key, final TextView previewTextView,
193bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final boolean withAnimation) {
194bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (!withAnimation) {
195bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setVisibility(View.VISIBLE);
196bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mShowingKeyPreviewTextViews.put(key, previewTextView);
197bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
198bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
199bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
200bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // Show preview with animation.
201bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Animator zoomIn = createZoomInAniation(key, previewTextView);
202bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Animator zoomOut = createZoomOutAnimation(key, previewTextView);
203bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final KeyPreviewAnimations animation = new KeyPreviewAnimations(zoomIn, zoomOut);
204bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setTag(animation);
205bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        animation.startZoomIn();
206bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
207bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
208bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    // TODO: Move these parameters to resources or preferences.
209bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final float KEY_PREVIEW_START_ZOOM_IN_SCALE = 0.7f;
210bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final float KEY_PREVIEW_END_ZOOM_IN_SCALE = 1.0f;
211bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final float KEY_PREVIEW_END_ZOOM_OUT_SCALE = 0.7f;
212bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
213bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            new AccelerateInterpolator();
214bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
215bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            new DecelerateInterpolator();
216bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
217bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private Animator createZoomInAniation(final Key key, final TextView previewTextView) {
218bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
219bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                previewTextView, View.SCALE_X, KEY_PREVIEW_START_ZOOM_IN_SCALE,
220bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                KEY_PREVIEW_END_ZOOM_IN_SCALE);
221bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
222bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                previewTextView, View.SCALE_Y, KEY_PREVIEW_START_ZOOM_IN_SCALE,
223bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                KEY_PREVIEW_END_ZOOM_IN_SCALE);
224bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final AnimatorSet zoomInAnimation = new AnimatorSet();
225bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomInAnimation.play(scaleXAnimation).with(scaleYAnimation);
226bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // TODO: Implement preference option to control key preview animation duration.
227bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomInAnimation.setDuration(mParams.mZoomInDuration);
228bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomInAnimation.setInterpolator(DECELERATE_INTERPOLATOR);
229bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomInAnimation.addListener(new AnimatorListenerAdapter() {
230bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            @Override
231bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            public void onAnimationStart(final Animator animation) {
232bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                showKeyPreview(key, previewTextView, false /* withAnimation */);
233bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
234bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        });
235bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        return zoomInAnimation;
236bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
237bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
238bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private Animator createZoomOutAnimation(final Key key, final TextView previewTextView) {
239bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
240bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                previewTextView, View.SCALE_X, KEY_PREVIEW_END_ZOOM_OUT_SCALE);
241bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
242bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                previewTextView, View.SCALE_Y, KEY_PREVIEW_END_ZOOM_OUT_SCALE);
243bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final AnimatorSet zoomOutAnimation = new AnimatorSet();
244bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomOutAnimation.play(scaleXAnimation).with(scaleYAnimation);
245bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // TODO: Implement preference option to control key preview animation duration.
246bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int zoomOutDuration = Math.min(mParams.mZoomOutDuration, mParams.getLingerTimeout());
247bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomOutAnimation.setDuration(zoomOutDuration);
248bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomOutAnimation.setInterpolator(ACCELERATE_INTERPOLATOR);
249bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        zoomOutAnimation.addListener(new AnimatorListenerAdapter() {
250bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            @Override
251bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            public void onAnimationEnd(final Animator animation) {
252bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                dismissKeyPreview(key, false /* withAnimation */);
253bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
254bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        });
255bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        return zoomOutAnimation;
256bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
257bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
258bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static class KeyPreviewAnimations extends AnimatorListenerAdapter {
259bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        private final Animator mZoomIn;
260bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        private final Animator mZoomOut;
261bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
262bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        public KeyPreviewAnimations(final Animator zoomIn, final Animator zoomOut) {
263bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mZoomIn = zoomIn;
264bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mZoomOut = zoomOut;
265bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
266bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
267bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        public void startZoomIn() {
268bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mZoomIn.start();
269bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
270bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
271bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        public void startZoomOut() {
272bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            if (mZoomIn.isRunning()) {
273bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                mZoomIn.addListener(this);
274bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                return;
275bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
276bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mZoomOut.start();
277bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
278bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
279bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        @Override
280bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        public void onAnimationEnd(final Animator animation) {
281bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mZoomOut.start();
282bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
283bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
284bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka}
285