KeyPreviewChoreographer.java revision 3ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34f
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.CoordinateUtils;
36bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport com.android.inputmethod.latin.utils.ViewLayoutUtils;
37bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
38bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport java.util.ArrayDeque;
39bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport java.util.HashMap;
40bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokaimport java.util.HashSet;
41bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
42bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka/**
43bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * This class controls pop up key previews. This class decides:
44bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * - what kind of key previews should be shown.
45bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * - where key previews should be placed.
46bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka * - how key previews should be shown and dismissed.
47bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka */
48bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaokapublic final class KeyPreviewChoreographer {
49bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    // Free {@link TextView} pool that can be used for key preview.
50a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka    private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = new ArrayDeque<>();
51bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
52a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka    private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = new HashMap<>();
53bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
54bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private final KeyPreviewDrawParams mParams;
55bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
56bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public KeyPreviewChoreographer(final KeyPreviewDrawParams params) {
57bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        mParams = params;
58bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
59bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
603ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka    private TextView getKeyPreviewTextView(final Key key, final ViewGroup placerView) {
61bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
62bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewTextView != null) {
63bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return previewTextView;
64bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
65bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView = mFreeKeyPreviewTextViews.poll();
66bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewTextView != null) {
67bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return previewTextView;
68bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
69bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Context context = placerView.getContext();
70bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (mParams.mLayoutId != 0) {
71bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView = (TextView)LayoutInflater.from(context)
72bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                    .inflate(mParams.mLayoutId, null);
73bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else {
74bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView = new TextView(context);
75bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
76bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        placerView.addView(previewTextView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
77bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        return previewTextView;
78bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
79bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
80bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public boolean isShowingKeyPreview(final Key key) {
81bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        return mShowingKeyPreviewTextViews.containsKey(key);
82bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
83bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
84bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public void dismissAllKeyPreviews() {
85a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        for (final Key key : new HashSet<>(mShowingKeyPreviewTextViews.keySet())) {
86bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            dismissKeyPreview(key, false /* withAnimation */);
87bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
88bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
89bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
90bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    public void dismissKeyPreview(final Key key, final boolean withAnimation) {
91bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (key == null) {
92bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
93bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
94bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final TextView previewTextView = mShowingKeyPreviewTextViews.get(key);
95bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewTextView == null) {
96bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
97bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
98bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Object tag = previewTextView.getTag();
99bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (withAnimation) {
100bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            if (tag instanceof KeyPreviewAnimations) {
101bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
102276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                animation.startDismiss();
1037e9b0b42bfd5c51f4bc236a73df3f14cf583c989Tadashi G. Takaoka                return;
104bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
105bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
106bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // Dismiss preview without animation.
1077e9b0b42bfd5c51f4bc236a73df3f14cf583c989Tadashi G. Takaoka        mShowingKeyPreviewTextViews.remove(key);
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
1373ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka    public void placeKeyPreviewAndShow(final Key key, final KeyboardIconsSet iconsSet,
1383ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka            final KeyDrawParams drawParams, final int keyboardViewWidth, final int[] keyboardOrigin,
1393ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka            final ViewGroup placerView, final boolean withAnimation) {
1403ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka        final TextView previewTextView = getKeyPreviewTextView(key, placerView);
1413ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka        placeKeyPreview(
1423ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka                key, previewTextView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin);
1433ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka        showKeyPreview(key, previewTextView, withAnimation);
1443ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka    }
1453ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka
1463ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka    private void placeKeyPreview(final Key key, final TextView previewTextView,
147bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
148bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final int keyboardViewWidth, final int[] originCoords) {
149bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setTextColor(drawParams.mPreviewTextColor);
150bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final Drawable background = previewTextView.getBackground();
151bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final String label = key.getPreviewLabel();
152bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // What we show as preview should match what we show on a key top in onDraw().
153bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (label != null) {
154bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            // TODO Should take care of temporaryShiftLabel here.
155bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setCompoundDrawables(null, null, null, null);
156bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
157bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                    key.selectPreviewTextSize(drawParams));
158bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
159bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setText(label);
160bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else {
161bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
162bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setText(null);
163bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
164bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
165bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.measure(
166bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
167bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        mParams.setGeometry(previewTextView);
168bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int previewWidth = previewTextView.getMeasuredWidth();
169bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int previewHeight = mParams.mPreviewHeight;
170bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int keyDrawWidth = key.getDrawWidth();
171bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // The key preview is horizontally aligned with the center of the visible part of the
172bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
173bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // the left/right background is used if such background is specified.
174bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int statePosition;
175bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
176bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                + CoordinateUtils.x(originCoords);
177bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (previewX < 0) {
178bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewX = 0;
179bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            statePosition = STATE_LEFT;
180bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else if (previewX > keyboardViewWidth - previewWidth) {
181bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewX = keyboardViewWidth - previewWidth;
182bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            statePosition = STATE_RIGHT;
183bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        } else {
184bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            statePosition = STATE_MIDDLE;
185bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
186bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // The key preview is placed vertically above the top edge of the parent key with an
187bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // arbitrary offset.
188bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
189bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                + CoordinateUtils.y(originCoords);
190bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
191bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (background != null) {
192bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
193bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
194bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
195bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        ViewLayoutUtils.placeViewAt(
196bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                previewTextView, previewX, previewY, previewWidth, previewHeight);
197bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setPivotX(previewWidth / 2.0f);
198bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setPivotY(previewHeight);
199bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
200bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
2013ff72dd0daa9baaeb4d6b8ade59d65c1f0e8b34fTadashi G. Takaoka    private void showKeyPreview(final Key key, final TextView previewTextView,
202bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            final boolean withAnimation) {
203bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        if (!withAnimation) {
204bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            previewTextView.setVisibility(View.VISIBLE);
205bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            mShowingKeyPreviewTextViews.put(key, previewTextView);
206bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            return;
207bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
208bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
209bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        // Show preview with animation.
210276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        final Animator showUpAnimation = createShowUpAniation(key, previewTextView);
211276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        final Animator dismissAnimation = createDismissAnimation(key, previewTextView);
212276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        final KeyPreviewAnimations animation = new KeyPreviewAnimations(
213276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                showUpAnimation, dismissAnimation);
214bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        previewTextView.setTag(animation);
215276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        animation.startShowUp();
216bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
217bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
218276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka    private static final float KEY_PREVIEW_SHOW_UP_END_SCALE = 1.0f;
219bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR =
220bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            new AccelerateInterpolator();
221bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
222bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            new DecelerateInterpolator();
223bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
224276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka    private Animator createShowUpAniation(final Key key, final TextView previewTextView) {
225276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        // TODO: Optimization for no scale animation and no duration.
226bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
227276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                previewTextView, View.SCALE_X, mParams.getShowUpStartScale(),
228276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                KEY_PREVIEW_SHOW_UP_END_SCALE);
229bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
230276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                previewTextView, View.SCALE_Y, mParams.getShowUpStartScale(),
231276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                KEY_PREVIEW_SHOW_UP_END_SCALE);
232276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        final AnimatorSet showUpAnimation = new AnimatorSet();
233276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        showUpAnimation.play(scaleXAnimation).with(scaleYAnimation);
234276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        showUpAnimation.setDuration(mParams.getShowUpDuration());
235276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        showUpAnimation.setInterpolator(DECELERATE_INTERPOLATOR);
236276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        showUpAnimation.addListener(new AnimatorListenerAdapter() {
237bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            @Override
238bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            public void onAnimationStart(final Animator animation) {
239bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                showKeyPreview(key, previewTextView, false /* withAnimation */);
240bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
241bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        });
242276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        return showUpAnimation;
243bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
244bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
245276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka    private Animator createDismissAnimation(final Key key, final TextView previewTextView) {
246276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        // TODO: Optimization for no scale animation and no duration.
247bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
248276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                previewTextView, View.SCALE_X, mParams.getDismissEndScale());
249bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
250276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                previewTextView, View.SCALE_Y, mParams.getDismissEndScale());
251276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        final AnimatorSet dismissAnimation = new AnimatorSet();
252276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        dismissAnimation.play(scaleXAnimation).with(scaleYAnimation);
253276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        final int dismissDuration = Math.min(
254276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                mParams.getDismissDuration(), mParams.getLingerTimeout());
255276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        dismissAnimation.setDuration(dismissDuration);
256276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        dismissAnimation.setInterpolator(ACCELERATE_INTERPOLATOR);
257276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        dismissAnimation.addListener(new AnimatorListenerAdapter() {
258bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            @Override
259bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            public void onAnimationEnd(final Animator animation) {
260bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                dismissKeyPreview(key, false /* withAnimation */);
261bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
262bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        });
263276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        return dismissAnimation;
264bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
265bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
266bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    private static class KeyPreviewAnimations extends AnimatorListenerAdapter {
267276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        private final Animator mShowUpAnimation;
268276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        private final Animator mDismissAnimation;
269bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
270276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        public KeyPreviewAnimations(final Animator showUpAnimation,
271276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                final Animator dismissAnimation) {
272276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka            mShowUpAnimation = showUpAnimation;
273276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka            mDismissAnimation = dismissAnimation;
274bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
275bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
276276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        public void startShowUp() {
277276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka            mShowUpAnimation.start();
278bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
279bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
280276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka        public void startDismiss() {
281276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka            if (mShowUpAnimation.isRunning()) {
282276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka                mShowUpAnimation.addListener(this);
283bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka                return;
284bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka            }
285276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka            mDismissAnimation.start();
286bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
287bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka
288bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        @Override
289bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        public void onAnimationEnd(final Animator animation) {
290276b1a2ebc6d5ea3fa36fa9271cdc6310db34021Tadashi G. Takaoka            mDismissAnimation.start();
291bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka        }
292bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka    }
293bb476be4e62b3bed7848d37df42f8fa7363b58d1Tadashi G. Takaoka}
294