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.utils.CoordinateUtils; 27 import com.android.inputmethod.latin.utils.ViewLayoutUtils; 28 29 import java.util.ArrayDeque; 30 import java.util.HashMap; 31 import 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 */ 39 public 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