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