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