KeyPreviewChoreographer.java revision ac88f3d8459e17236654aa38debea749ecb631d1
1/*
2 * Copyright (C) 2014 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 */
16
17package com.android.inputmethod.keyboard.internal;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.content.Context;
22import android.view.View;
23import android.view.ViewGroup;
24
25import com.android.inputmethod.keyboard.Key;
26import com.android.inputmethod.latin.utils.CoordinateUtils;
27import com.android.inputmethod.latin.utils.ViewLayoutUtils;
28
29import java.util.ArrayDeque;
30import java.util.HashMap;
31import java.util.HashSet;
32
33/**
34 * This class controls pop up key previews. This class decides:
35 * - what kind of key previews should be shown.
36 * - where key previews should be placed.
37 * - how key previews should be shown and dismissed.
38 */
39public final class KeyPreviewChoreographer {
40    // Free {@link KeyPreviewView} pool that can be used for key preview.
41    private final ArrayDeque<KeyPreviewView> mFreeKeyPreviewViews = new ArrayDeque<>();
42    // Map from {@link Key} to {@link KeyPreviewView} that is currently being displayed as key
43    // preview.
44    private final HashMap<Key,KeyPreviewView> mShowingKeyPreviewViews = new HashMap<>();
45
46    private final KeyPreviewDrawParams mParams;
47
48    public KeyPreviewChoreographer(final KeyPreviewDrawParams params) {
49        mParams = params;
50    }
51
52    public KeyPreviewView getKeyPreviewView(final Key key, final ViewGroup placerView) {
53        KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.remove(key);
54        if (keyPreviewView != null) {
55            return keyPreviewView;
56        }
57        keyPreviewView = mFreeKeyPreviewViews.poll();
58        if (keyPreviewView != null) {
59            return keyPreviewView;
60        }
61        final Context context = placerView.getContext();
62        keyPreviewView = new KeyPreviewView(context, null /* attrs */);
63        keyPreviewView.setBackgroundResource(mParams.mPreviewBackgroundResId);
64        placerView.addView(keyPreviewView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
65        return keyPreviewView;
66    }
67
68    public boolean isShowingKeyPreview(final Key key) {
69        return mShowingKeyPreviewViews.containsKey(key);
70    }
71
72    public void dismissAllKeyPreviews() {
73        for (final Key key : new HashSet<>(mShowingKeyPreviewViews.keySet())) {
74            dismissKeyPreview(key, false /* withAnimation */);
75        }
76    }
77
78    public void dismissKeyPreview(final Key key, final boolean withAnimation) {
79        if (key == null) {
80            return;
81        }
82        final KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.get(key);
83        if (keyPreviewView == null) {
84            return;
85        }
86        final Object tag = keyPreviewView.getTag();
87        if (withAnimation) {
88            if (tag instanceof KeyPreviewAnimators) {
89                final KeyPreviewAnimators animators = (KeyPreviewAnimators)tag;
90                animators.startDismiss();
91                return;
92            }
93        }
94        // Dismiss preview without animation.
95        mShowingKeyPreviewViews.remove(key);
96        if (tag instanceof Animator) {
97            ((Animator)tag).cancel();
98        }
99        keyPreviewView.setTag(null);
100        keyPreviewView.setVisibility(View.INVISIBLE);
101        mFreeKeyPreviewViews.add(keyPreviewView);
102    }
103
104    public void placeAndShowKeyPreview(final Key key, final KeyboardIconsSet iconsSet,
105            final KeyDrawParams drawParams, final int keyboardViewWidth, final int[] keyboardOrigin,
106            final ViewGroup placerView, final boolean withAnimation) {
107        final KeyPreviewView keyPreviewView = getKeyPreviewView(key, placerView);
108        placeKeyPreview(
109                key, keyPreviewView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin);
110        showKeyPreview(key, keyPreviewView, withAnimation);
111    }
112
113    private void placeKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
114            final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
115            final int keyboardViewWidth, final int[] originCoords) {
116        keyPreviewView.setPreviewVisual(key, iconsSet, drawParams);
117        keyPreviewView.measure(
118                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
119        mParams.setGeometry(keyPreviewView);
120        final int previewWidth = keyPreviewView.getMeasuredWidth();
121        final int previewHeight = mParams.mPreviewHeight;
122        final int keyDrawWidth = key.getDrawWidth();
123        // The key preview is horizontally aligned with the center of the visible part of the
124        // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
125        // the left/right background is used if such background is specified.
126        final int keyPreviewPosition;
127        int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
128                + CoordinateUtils.x(originCoords);
129        if (previewX < 0) {
130            previewX = 0;
131            keyPreviewPosition = KeyPreviewView.POSITION_LEFT;
132        } else if (previewX > keyboardViewWidth - previewWidth) {
133            previewX = keyboardViewWidth - previewWidth;
134            keyPreviewPosition = KeyPreviewView.POSITION_RIGHT;
135        } else {
136            keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
137        }
138        final boolean hasMoreKeys = (key.getMoreKeys() != null);
139        keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition);
140        // The key preview is placed vertically above the top edge of the parent key with an
141        // arbitrary offset.
142        final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
143                + CoordinateUtils.y(originCoords);
144
145        ViewLayoutUtils.placeViewAt(
146                keyPreviewView, previewX, previewY, previewWidth, previewHeight);
147        keyPreviewView.setPivotX(previewWidth / 2.0f);
148        keyPreviewView.setPivotY(previewHeight);
149    }
150
151    private void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
152            final boolean withAnimation) {
153        if (!withAnimation) {
154            keyPreviewView.setVisibility(View.VISIBLE);
155            mShowingKeyPreviewViews.put(key, keyPreviewView);
156            return;
157        }
158
159        // Show preview with animation.
160        final Animator showUpAnimator = createShowUpAnimator(key, keyPreviewView);
161        final Animator dismissAnimator = createDismissAnimator(key, keyPreviewView);
162        final KeyPreviewAnimators animators = new KeyPreviewAnimators(
163                showUpAnimator, dismissAnimator);
164        keyPreviewView.setTag(animators);
165        animators.startShowUp();
166    }
167
168    public Animator createShowUpAnimator(final Key key, final KeyPreviewView keyPreviewView) {
169        final Animator animator = mParams.createShowUpAnimator(keyPreviewView);
170        animator.addListener(new AnimatorListenerAdapter() {
171            @Override
172            public void onAnimationStart(final Animator animator) {
173                showKeyPreview(key, keyPreviewView, false /* withAnimation */);
174            }
175        });
176        return animator;
177    }
178
179    private Animator createDismissAnimator(final Key key, final KeyPreviewView keyPreviewView) {
180        final Animator animator = mParams.createDismissAnimator(keyPreviewView);
181        animator.addListener(new AnimatorListenerAdapter() {
182            @Override
183            public void onAnimationEnd(final Animator animator) {
184                dismissKeyPreview(key, false /* withAnimation */);
185            }
186        });
187        return animator;
188    }
189
190    private static class KeyPreviewAnimators extends AnimatorListenerAdapter {
191        private final Animator mShowUpAnimator;
192        private final Animator mDismissAnimator;
193
194        public KeyPreviewAnimators(final Animator showUpAnimator, final Animator dismissAnimator) {
195            mShowUpAnimator = showUpAnimator;
196            mDismissAnimator = dismissAnimator;
197        }
198
199        public void startShowUp() {
200            mShowUpAnimator.start();
201        }
202
203        public void startDismiss() {
204            if (mShowUpAnimator.isRunning()) {
205                mShowUpAnimator.addListener(this);
206                return;
207            }
208            mDismissAnimator.start();
209        }
210
211        @Override
212        public void onAnimationEnd(final Animator animator) {
213            mDismissAnimator.start();
214        }
215    }
216}
217