KeyboardView.java revision 47e2bf329882ea45294ea6e6dd80b4d4eb3789e6
1/* 2 * Copyright (C) 2010 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; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.Paint; 25import android.graphics.Paint.Align; 26import android.graphics.PorterDuff; 27import android.graphics.Rect; 28import android.graphics.Region.Op; 29import android.graphics.Typeface; 30import android.graphics.drawable.Drawable; 31import android.os.Message; 32import android.util.AttributeSet; 33import android.util.SparseArray; 34import android.util.TypedValue; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.ViewGroup; 38import android.widget.TextView; 39 40import com.android.inputmethod.keyboard.internal.PreviewPlacerView; 41import com.android.inputmethod.latin.Constants; 42import com.android.inputmethod.latin.LatinImeLogger; 43import com.android.inputmethod.latin.R; 44import com.android.inputmethod.latin.StaticInnerHandlerWrapper; 45import com.android.inputmethod.latin.StringUtils; 46import com.android.inputmethod.latin.define.ProductionFlag; 47import com.android.inputmethod.research.ResearchLogger; 48 49import java.util.HashSet; 50 51/** 52 * A view that renders a virtual {@link Keyboard}. 53 * 54 * @attr ref R.styleable#KeyboardView_backgroundDimAlpha 55 * @attr ref R.styleable#KeyboardView_keyBackground 56 * @attr ref R.styleable#KeyboardView_keyLetterRatio 57 * @attr ref R.styleable#KeyboardView_keyLargeLetterRatio 58 * @attr ref R.styleable#KeyboardView_keyLabelRatio 59 * @attr ref R.styleable#KeyboardView_keyHintLetterRatio 60 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintRatio 61 * @attr ref R.styleable#KeyboardView_keyHintLabelRatio 62 * @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding 63 * @attr ref R.styleable#KeyboardView_keyHintLetterPadding 64 * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding 65 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding 66 * @attr ref R.styleable#KeyboardView_keyTextStyle 67 * @attr ref R.styleable#KeyboardView_keyPreviewLayout 68 * @attr ref R.styleable#KeyboardView_keyPreviewTextRatio 69 * @attr ref R.styleable#KeyboardView_keyPreviewOffset 70 * @attr ref R.styleable#KeyboardView_keyPreviewHeight 71 * @attr ref R.styleable#KeyboardView_keyTextColor 72 * @attr ref R.styleable#KeyboardView_keyTextColorDisabled 73 * @attr ref R.styleable#KeyboardView_keyHintLetterColor 74 * @attr ref R.styleable#KeyboardView_keyHintLabelColor 75 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintInactivatedColor 76 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintActivatedColor 77 * @attr ref R.styleable#KeyboardView_shadowColor 78 * @attr ref R.styleable#KeyboardView_shadowRadius 79 */ 80public class KeyboardView extends View implements PointerTracker.DrawingProxy { 81 // Miscellaneous constants 82 private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable }; 83 84 // XML attributes 85 protected final float mVerticalCorrection; 86 protected final int mMoreKeysLayout; 87 private final int mBackgroundDimAlpha; 88 89 // HORIZONTAL ELLIPSIS "...", character for popup hint. 90 private static final String POPUP_HINT_CHAR = "\u2026"; 91 92 // Margin between the label and the icon on a key that has both of them. 93 // Specified by the fraction of the key width. 94 // TODO: Use resource parameter for this value. 95 private static final float LABEL_ICON_MARGIN = 0.05f; 96 97 // The maximum key label width in the proportion to the key width. 98 private static final float MAX_LABEL_RATIO = 0.90f; 99 100 // Main keyboard 101 private Keyboard mKeyboard; 102 protected final KeyDrawParams mKeyDrawParams; 103 104 // Key preview 105 private final int mKeyPreviewLayoutId; 106 protected final KeyPreviewDrawParams mKeyPreviewDrawParams; 107 private boolean mShowKeyPreviewPopup = true; 108 private int mDelayAfterPreview; 109 private final PreviewPlacerView mPreviewPlacerView; 110 111 /** True if {@link KeyboardView} should handle gesture events. */ 112 protected boolean mShouldHandleGesture; 113 114 // Drawing 115 /** True if the entire keyboard needs to be dimmed. */ 116 private boolean mNeedsToDimEntireKeyboard; 117 /** Whether the keyboard bitmap buffer needs to be redrawn before it's blitted. **/ 118 private boolean mBufferNeedsUpdate; 119 /** True if all keys should be drawn */ 120 private boolean mInvalidateAllKeys; 121 /** The keys that should be drawn */ 122 private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>(); 123 /** The region of invalidated keys */ 124 private final Rect mInvalidatedKeysRect = new Rect(); 125 /** The keyboard bitmap buffer for faster updates */ 126 private Bitmap mBuffer; 127 /** The canvas for the above mutable keyboard bitmap */ 128 private Canvas mCanvas; 129 private final Paint mPaint = new Paint(); 130 private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); 131 // This sparse array caches key label text height in pixel indexed by key label text size. 132 private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>(); 133 // This sparse array caches key label text width in pixel indexed by key label text size. 134 private static final SparseArray<Float> sTextWidthCache = new SparseArray<Float>(); 135 private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' }; 136 private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' }; 137 138 private final DrawingHandler mDrawingHandler = new DrawingHandler(this); 139 140 public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> { 141 private static final int MSG_DISMISS_KEY_PREVIEW = 1; 142 143 public DrawingHandler(KeyboardView outerInstance) { 144 super(outerInstance); 145 } 146 147 @Override 148 public void handleMessage(Message msg) { 149 final KeyboardView keyboardView = getOuterInstance(); 150 if (keyboardView == null) return; 151 final PointerTracker tracker = (PointerTracker) msg.obj; 152 switch (msg.what) { 153 case MSG_DISMISS_KEY_PREVIEW: 154 tracker.getKeyPreviewText().setVisibility(View.INVISIBLE); 155 break; 156 } 157 } 158 159 public void dismissKeyPreview(long delay, PointerTracker tracker) { 160 sendMessageDelayed(obtainMessage(MSG_DISMISS_KEY_PREVIEW, tracker), delay); 161 } 162 163 public void cancelDismissKeyPreview(PointerTracker tracker) { 164 removeMessages(MSG_DISMISS_KEY_PREVIEW, tracker); 165 } 166 167 public void cancelAllDismissKeyPreviews() { 168 removeMessages(MSG_DISMISS_KEY_PREVIEW); 169 } 170 171 public void cancelAllMessages() { 172 cancelAllDismissKeyPreviews(); 173 } 174 } 175 176 protected static class KeyDrawParams { 177 // XML attributes 178 public final int mKeyTextColor; 179 public final int mKeyTextInactivatedColor; 180 public final Typeface mKeyTextStyle; 181 public final float mKeyLabelHorizontalPadding; 182 public final float mKeyHintLetterPadding; 183 public final float mKeyPopupHintLetterPadding; 184 public final float mKeyShiftedLetterHintPadding; 185 public final int mShadowColor; 186 public final float mShadowRadius; 187 public final Drawable mKeyBackground; 188 public final int mKeyHintLetterColor; 189 public final int mKeyHintLabelColor; 190 public final int mKeyShiftedLetterHintInactivatedColor; 191 public final int mKeyShiftedLetterHintActivatedColor; 192 193 /* package */ final float mKeyLetterRatio; 194 private final float mKeyLargeLetterRatio; 195 private final float mKeyLabelRatio; 196 private final float mKeyLargeLabelRatio; 197 private final float mKeyHintLetterRatio; 198 private final float mKeyShiftedLetterHintRatio; 199 private final float mKeyHintLabelRatio; 200 private static final float UNDEFINED_RATIO = -1.0f; 201 202 public final Rect mPadding = new Rect(); 203 public int mKeyLetterSize; 204 public int mKeyLargeLetterSize; 205 public int mKeyLabelSize; 206 public int mKeyLargeLabelSize; 207 public int mKeyHintLetterSize; 208 public int mKeyShiftedLetterHintSize; 209 public int mKeyHintLabelSize; 210 public int mAnimAlpha; 211 212 public KeyDrawParams(TypedArray a) { 213 mKeyBackground = a.getDrawable(R.styleable.KeyboardView_keyBackground); 214 if (a.hasValue(R.styleable.KeyboardView_keyLetterSize)) { 215 mKeyLetterRatio = UNDEFINED_RATIO; 216 mKeyLetterSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLetterSize, 0); 217 } else { 218 mKeyLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLetterRatio); 219 } 220 if (a.hasValue(R.styleable.KeyboardView_keyLabelSize)) { 221 mKeyLabelRatio = UNDEFINED_RATIO; 222 mKeyLabelSize = a.getDimensionPixelSize(R.styleable.KeyboardView_keyLabelSize, 0); 223 } else { 224 mKeyLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLabelRatio); 225 } 226 mKeyLargeLabelRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLabelRatio); 227 mKeyLargeLetterRatio = getRatio(a, R.styleable.KeyboardView_keyLargeLetterRatio); 228 mKeyHintLetterRatio = getRatio(a, R.styleable.KeyboardView_keyHintLetterRatio); 229 mKeyShiftedLetterHintRatio = getRatio(a, 230 R.styleable.KeyboardView_keyShiftedLetterHintRatio); 231 mKeyHintLabelRatio = getRatio(a, R.styleable.KeyboardView_keyHintLabelRatio); 232 mKeyLabelHorizontalPadding = a.getDimension( 233 R.styleable.KeyboardView_keyLabelHorizontalPadding, 0); 234 mKeyHintLetterPadding = a.getDimension( 235 R.styleable.KeyboardView_keyHintLetterPadding, 0); 236 mKeyPopupHintLetterPadding = a.getDimension( 237 R.styleable.KeyboardView_keyPopupHintLetterPadding, 0); 238 mKeyShiftedLetterHintPadding = a.getDimension( 239 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0); 240 mKeyTextColor = a.getColor(R.styleable.KeyboardView_keyTextColor, 0xFF000000); 241 mKeyTextInactivatedColor = a.getColor( 242 R.styleable.KeyboardView_keyTextInactivatedColor, 0xFF000000); 243 mKeyHintLetterColor = a.getColor(R.styleable.KeyboardView_keyHintLetterColor, 0); 244 mKeyHintLabelColor = a.getColor(R.styleable.KeyboardView_keyHintLabelColor, 0); 245 mKeyShiftedLetterHintInactivatedColor = a.getColor( 246 R.styleable.KeyboardView_keyShiftedLetterHintInactivatedColor, 0); 247 mKeyShiftedLetterHintActivatedColor = a.getColor( 248 R.styleable.KeyboardView_keyShiftedLetterHintActivatedColor, 0); 249 mKeyTextStyle = Typeface.defaultFromStyle( 250 a.getInt(R.styleable.KeyboardView_keyTextStyle, Typeface.NORMAL)); 251 mShadowColor = a.getColor(R.styleable.KeyboardView_shadowColor, 0); 252 mShadowRadius = a.getFloat(R.styleable.KeyboardView_shadowRadius, 0f); 253 254 mKeyBackground.getPadding(mPadding); 255 } 256 257 public void updateKeyHeight(int keyHeight) { 258 if (mKeyLetterRatio >= 0.0f) 259 mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio); 260 if (mKeyLabelRatio >= 0.0f) 261 mKeyLabelSize = (int)(keyHeight * mKeyLabelRatio); 262 mKeyLargeLabelSize = (int)(keyHeight * mKeyLargeLabelRatio); 263 mKeyLargeLetterSize = (int)(keyHeight * mKeyLargeLetterRatio); 264 mKeyHintLetterSize = (int)(keyHeight * mKeyHintLetterRatio); 265 mKeyShiftedLetterHintSize = (int)(keyHeight * mKeyShiftedLetterHintRatio); 266 mKeyHintLabelSize = (int)(keyHeight * mKeyHintLabelRatio); 267 } 268 269 public void blendAlpha(Paint paint) { 270 final int color = paint.getColor(); 271 paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE, 272 Color.red(color), Color.green(color), Color.blue(color)); 273 } 274 } 275 276 /* package */ static class KeyPreviewDrawParams { 277 // XML attributes. 278 public final Drawable mPreviewBackground; 279 public final Drawable mPreviewLeftBackground; 280 public final Drawable mPreviewRightBackground; 281 public final int mPreviewTextColor; 282 public final int mPreviewOffset; 283 public final int mPreviewHeight; 284 public final Typeface mKeyTextStyle; 285 public final int mLingerTimeout; 286 287 private final float mPreviewTextRatio; 288 private final float mKeyLetterRatio; 289 290 // The graphical geometry of the key preview. 291 // <-width-> 292 // +-------+ ^ 293 // | | | 294 // |preview| height (visible) 295 // | | | 296 // + + ^ v 297 // \ / |offset 298 // +-\ /-+ v 299 // | +-+ | 300 // |parent | 301 // | key| 302 // +-------+ 303 // The background of a {@link TextView} being used for a key preview may have invisible 304 // paddings. To align the more keys keyboard panel's visible part with the visible part of 305 // the background, we need to record the width and height of key preview that don't include 306 // invisible paddings. 307 public int mPreviewVisibleWidth; 308 public int mPreviewVisibleHeight; 309 // The key preview may have an arbitrary offset and its background that may have a bottom 310 // padding. To align the more keys keyboard and the key preview we also need to record the 311 // offset between the top edge of parent key and the bottom of the visible part of key 312 // preview background. 313 public int mPreviewVisibleOffset; 314 315 public int mPreviewTextSize; 316 public int mKeyLetterSize; 317 public final int[] mCoordinates = new int[2]; 318 319 private static final int PREVIEW_ALPHA = 240; 320 321 public KeyPreviewDrawParams(TypedArray a, KeyDrawParams keyDrawParams) { 322 mPreviewBackground = a.getDrawable(R.styleable.KeyboardView_keyPreviewBackground); 323 mPreviewLeftBackground = a.getDrawable( 324 R.styleable.KeyboardView_keyPreviewLeftBackground); 325 mPreviewRightBackground = a.getDrawable( 326 R.styleable.KeyboardView_keyPreviewRightBackground); 327 setAlpha(mPreviewBackground, PREVIEW_ALPHA); 328 setAlpha(mPreviewLeftBackground, PREVIEW_ALPHA); 329 setAlpha(mPreviewRightBackground, PREVIEW_ALPHA); 330 mPreviewOffset = a.getDimensionPixelOffset( 331 R.styleable.KeyboardView_keyPreviewOffset, 0); 332 mPreviewHeight = a.getDimensionPixelSize( 333 R.styleable.KeyboardView_keyPreviewHeight, 80); 334 mPreviewTextRatio = getRatio(a, R.styleable.KeyboardView_keyPreviewTextRatio); 335 mPreviewTextColor = a.getColor(R.styleable.KeyboardView_keyPreviewTextColor, 0); 336 mLingerTimeout = a.getInt(R.styleable.KeyboardView_keyPreviewLingerTimeout, 0); 337 338 mKeyLetterRatio = keyDrawParams.mKeyLetterRatio; 339 mKeyTextStyle = keyDrawParams.mKeyTextStyle; 340 } 341 342 public void updateKeyHeight(int keyHeight) { 343 mPreviewTextSize = (int)(keyHeight * mPreviewTextRatio); 344 mKeyLetterSize = (int)(keyHeight * mKeyLetterRatio); 345 } 346 347 private static void setAlpha(Drawable drawable, int alpha) { 348 if (drawable == null) 349 return; 350 drawable.setAlpha(alpha); 351 } 352 } 353 354 public KeyboardView(Context context, AttributeSet attrs) { 355 this(context, attrs, R.attr.keyboardViewStyle); 356 } 357 358 public KeyboardView(Context context, AttributeSet attrs, int defStyle) { 359 super(context, attrs, defStyle); 360 361 final TypedArray a = context.obtainStyledAttributes( 362 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView); 363 364 mKeyDrawParams = new KeyDrawParams(a); 365 mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams); 366 mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0); 367 if (mKeyPreviewLayoutId == 0) { 368 mShowKeyPreviewPopup = false; 369 } 370 mVerticalCorrection = a.getDimensionPixelOffset( 371 R.styleable.KeyboardView_verticalCorrection, 0); 372 mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0); 373 mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0); 374 a.recycle(); 375 376 mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout; 377 378 mPaint.setAntiAlias(true); 379 380 mPreviewPlacerView = new PreviewPlacerView(context); 381 } 382 383 // Read fraction value in TypedArray as float. 384 /* package */ static float getRatio(TypedArray a, int index) { 385 return a.getFraction(index, 1000, 1000, 1) / 1000.0f; 386 } 387 388 /** 389 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 390 * view will re-layout itself to accommodate the keyboard. 391 * @see Keyboard 392 * @see #getKeyboard() 393 * @param keyboard the keyboard to display in this view 394 */ 395 public void setKeyboard(Keyboard keyboard) { 396 mKeyboard = keyboard; 397 LatinImeLogger.onSetKeyboard(keyboard); 398 requestLayout(); 399 invalidateAllKeys(); 400 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 401 mKeyDrawParams.updateKeyHeight(keyHeight); 402 mKeyPreviewDrawParams.updateKeyHeight(keyHeight); 403 } 404 405 /** 406 * Returns the current keyboard being displayed by this view. 407 * @return the currently attached keyboard 408 * @see #setKeyboard(Keyboard) 409 */ 410 public Keyboard getKeyboard() { 411 return mKeyboard; 412 } 413 414 /** 415 * Enables or disables the key feedback popup. This is a popup that shows a magnified 416 * version of the depressed key. By default the preview is enabled. 417 * @param previewEnabled whether or not to enable the key feedback preview 418 * @param delay the delay after which the preview is dismissed 419 * @see #isKeyPreviewPopupEnabled() 420 */ 421 public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { 422 mShowKeyPreviewPopup = previewEnabled; 423 mDelayAfterPreview = delay; 424 } 425 426 /** 427 * Returns the enabled state of the key feedback preview 428 * @return whether or not the key feedback preview is enabled 429 * @see #setKeyPreviewPopupEnabled(boolean, int) 430 */ 431 public boolean isKeyPreviewPopupEnabled() { 432 return mShowKeyPreviewPopup; 433 } 434 435 public void setGestureHandlingMode(boolean shouldHandleGesture, 436 boolean drawsGesturePreviewTrail, boolean drawsGestureFloatingPreviewText) { 437 mShouldHandleGesture = shouldHandleGesture; 438 mPreviewPlacerView.setGesturePreviewMode( 439 drawsGesturePreviewTrail, drawsGestureFloatingPreviewText); 440 } 441 442 @Override 443 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 444 if (mKeyboard != null) { 445 // The main keyboard expands to the display width. 446 final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); 447 setMeasuredDimension(widthMeasureSpec, height); 448 } else { 449 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 450 } 451 } 452 453 @Override 454 public void onDraw(Canvas canvas) { 455 super.onDraw(canvas); 456 if (mBufferNeedsUpdate || mBuffer == null) { 457 mBufferNeedsUpdate = false; 458 onBufferDraw(); 459 } 460 canvas.drawBitmap(mBuffer, 0, 0, null); 461 } 462 463 private void onBufferDraw() { 464 final int width = getWidth(); 465 final int height = getHeight(); 466 if (width == 0 || height == 0) 467 return; 468 if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) { 469 if (mBuffer != null) 470 mBuffer.recycle(); 471 mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 472 mInvalidateAllKeys = true; 473 if (mCanvas != null) { 474 mCanvas.setBitmap(mBuffer); 475 } else { 476 mCanvas = new Canvas(mBuffer); 477 } 478 } 479 480 if (mKeyboard == null) return; 481 482 final Canvas canvas = mCanvas; 483 final Paint paint = mPaint; 484 final KeyDrawParams params = mKeyDrawParams; 485 486 if (mInvalidateAllKeys || mInvalidatedKeys.isEmpty()) { 487 mInvalidatedKeysRect.set(0, 0, width, height); 488 canvas.clipRect(mInvalidatedKeysRect, Op.REPLACE); 489 canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 490 // Draw all keys. 491 for (final Key key : mKeyboard.mKeys) { 492 onDrawKey(key, canvas, paint, params); 493 } 494 if (mNeedsToDimEntireKeyboard) { 495 drawDimRectangle(canvas, mInvalidatedKeysRect, mBackgroundDimAlpha, paint); 496 } 497 } else { 498 // Draw invalidated keys. 499 for (final Key key : mInvalidatedKeys) { 500 if (!mKeyboard.hasKey(key)) { 501 continue; 502 } 503 final int x = key.mX + getPaddingLeft(); 504 final int y = key.mY + getPaddingTop(); 505 mInvalidatedKeysRect.set(x, y, x + key.mWidth, y + key.mHeight); 506 canvas.clipRect(mInvalidatedKeysRect, Op.REPLACE); 507 canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 508 onDrawKey(key, canvas, paint, params); 509 if (mNeedsToDimEntireKeyboard) { 510 drawDimRectangle(canvas, mInvalidatedKeysRect, mBackgroundDimAlpha, paint); 511 } 512 } 513 } 514 515 // ResearchLogging indicator. 516 // TODO: Reimplement using a keyboard background image specific to the ResearchLogger, 517 // and remove this call. 518 if (ProductionFlag.IS_EXPERIMENTAL) { 519 ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height); 520 } 521 522 mInvalidatedKeys.clear(); 523 mInvalidatedKeysRect.setEmpty(); 524 mInvalidateAllKeys = false; 525 } 526 527 public void dimEntireKeyboard(boolean dimmed) { 528 final boolean needsRedrawing = mNeedsToDimEntireKeyboard != dimmed; 529 mNeedsToDimEntireKeyboard = dimmed; 530 if (needsRedrawing) { 531 invalidateAllKeys(); 532 } 533 } 534 535 private void onDrawKey(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { 536 final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft(); 537 final int keyDrawY = key.mY + getPaddingTop(); 538 canvas.translate(keyDrawX, keyDrawY); 539 540 params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE; 541 if (!key.isSpacer()) { 542 onDrawKeyBackground(key, canvas, params); 543 } 544 onDrawKeyTopVisuals(key, canvas, paint, params); 545 546 canvas.translate(-keyDrawX, -keyDrawY); 547 } 548 549 // Draw key background. 550 protected void onDrawKeyBackground(Key key, Canvas canvas, KeyDrawParams params) { 551 final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight 552 + params.mPadding.left + params.mPadding.right; 553 final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom; 554 final int bgX = -params.mPadding.left; 555 final int bgY = -params.mPadding.top; 556 final int[] drawableState = key.getCurrentDrawableState(); 557 final Drawable background = params.mKeyBackground; 558 background.setState(drawableState); 559 final Rect bounds = background.getBounds(); 560 if (bgWidth != bounds.right || bgHeight != bounds.bottom) { 561 background.setBounds(0, 0, bgWidth, bgHeight); 562 } 563 canvas.translate(bgX, bgY); 564 background.draw(canvas); 565 if (LatinImeLogger.sVISUALDEBUG) { 566 drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint()); 567 } 568 canvas.translate(-bgX, -bgY); 569 } 570 571 // Draw key top visuals. 572 protected void onDrawKeyTopVisuals(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { 573 final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 574 final int keyHeight = key.mHeight; 575 final float centerX = keyWidth * 0.5f; 576 final float centerY = keyHeight * 0.5f; 577 578 if (LatinImeLogger.sVISUALDEBUG) { 579 drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint()); 580 } 581 582 // Draw key label. 583 final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha); 584 float positionX = centerX; 585 if (key.mLabel != null) { 586 final String label = key.mLabel; 587 // For characters, use large font. For labels like "Done", use smaller font. 588 paint.setTypeface(key.selectTypeface(params.mKeyTextStyle)); 589 final int labelSize = key.selectTextSize(params.mKeyLetterSize, 590 params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyLargeLabelSize, 591 params.mKeyHintLabelSize); 592 paint.setTextSize(labelSize); 593 final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint); 594 final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint); 595 596 // Vertical label text alignment. 597 final float baseline = centerY + labelCharHeight / 2; 598 599 // Horizontal label text alignment 600 float labelWidth = 0; 601 if (key.isAlignLeft()) { 602 positionX = (int)params.mKeyLabelHorizontalPadding; 603 paint.setTextAlign(Align.LEFT); 604 } else if (key.isAlignRight()) { 605 positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding; 606 paint.setTextAlign(Align.RIGHT); 607 } else if (key.isAlignLeftOfCenter()) { 608 // TODO: Parameterise this? 609 positionX = centerX - labelCharWidth * 7 / 4; 610 paint.setTextAlign(Align.LEFT); 611 } else if (key.hasLabelWithIconLeft() && icon != null) { 612 labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth() 613 + LABEL_ICON_MARGIN * keyWidth; 614 positionX = centerX + labelWidth / 2; 615 paint.setTextAlign(Align.RIGHT); 616 } else if (key.hasLabelWithIconRight() && icon != null) { 617 labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth() 618 + LABEL_ICON_MARGIN * keyWidth; 619 positionX = centerX - labelWidth / 2; 620 paint.setTextAlign(Align.LEFT); 621 } else { 622 positionX = centerX; 623 paint.setTextAlign(Align.CENTER); 624 } 625 if (key.needsXScale()) { 626 paint.setTextScaleX( 627 Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint))); 628 } 629 630 paint.setColor(key.isShiftedLetterActivated() 631 ? params.mKeyTextInactivatedColor : params.mKeyTextColor); 632 if (key.isEnabled()) { 633 // Set a drop shadow for the text 634 paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor); 635 } else { 636 // Make label invisible 637 paint.setColor(Color.TRANSPARENT); 638 } 639 params.blendAlpha(paint); 640 canvas.drawText(label, 0, label.length(), positionX, baseline, paint); 641 // Turn off drop shadow and reset x-scale. 642 paint.setShadowLayer(0, 0, 0, 0); 643 paint.setTextScaleX(1.0f); 644 645 if (icon != null) { 646 final int iconWidth = icon.getIntrinsicWidth(); 647 final int iconHeight = icon.getIntrinsicHeight(); 648 final int iconY = (keyHeight - iconHeight) / 2; 649 if (key.hasLabelWithIconLeft()) { 650 final int iconX = (int)(centerX - labelWidth / 2); 651 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 652 } else if (key.hasLabelWithIconRight()) { 653 final int iconX = (int)(centerX + labelWidth / 2 - iconWidth); 654 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 655 } 656 } 657 658 if (LatinImeLogger.sVISUALDEBUG) { 659 final Paint line = new Paint(); 660 drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line); 661 drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line); 662 } 663 } 664 665 // Draw hint label. 666 if (key.mHintLabel != null) { 667 final String hint = key.mHintLabel; 668 final int hintColor; 669 final int hintSize; 670 if (key.hasHintLabel()) { 671 hintColor = params.mKeyHintLabelColor; 672 hintSize = params.mKeyHintLabelSize; 673 paint.setTypeface(Typeface.DEFAULT); 674 } else if (key.hasShiftedLetterHint()) { 675 hintColor = key.isShiftedLetterActivated() 676 ? params.mKeyShiftedLetterHintActivatedColor 677 : params.mKeyShiftedLetterHintInactivatedColor; 678 hintSize = params.mKeyShiftedLetterHintSize; 679 } else { // key.hasHintLetter() 680 hintColor = params.mKeyHintLetterColor; 681 hintSize = params.mKeyHintLetterSize; 682 } 683 paint.setColor(hintColor); 684 params.blendAlpha(paint); 685 paint.setTextSize(hintSize); 686 final float hintX, hintY; 687 if (key.hasHintLabel()) { 688 // The hint label is placed just right of the key label. Used mainly on 689 // "phone number" layout. 690 // TODO: Generalize the following calculations. 691 hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2; 692 hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2; 693 paint.setTextAlign(Align.LEFT); 694 } else if (key.hasShiftedLetterHint()) { 695 // The hint label is placed at top-right corner of the key. Used mainly on tablet. 696 hintX = keyWidth - params.mKeyShiftedLetterHintPadding 697 - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2; 698 paint.getFontMetrics(mFontMetrics); 699 hintY = -mFontMetrics.top; 700 paint.setTextAlign(Align.CENTER); 701 } else { // key.hasHintLetter() 702 // The hint letter is placed at top-right corner of the key. Used mainly on phone. 703 hintX = keyWidth - params.mKeyHintLetterPadding 704 - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2; 705 hintY = -paint.ascent(); 706 paint.setTextAlign(Align.CENTER); 707 } 708 canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint); 709 710 if (LatinImeLogger.sVISUALDEBUG) { 711 final Paint line = new Paint(); 712 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); 713 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); 714 } 715 } 716 717 // Draw key icon. 718 if (key.mLabel == null && icon != null) { 719 final int iconWidth = icon.getIntrinsicWidth(); 720 final int iconHeight = icon.getIntrinsicHeight(); 721 final int iconX, alignX; 722 final int iconY = (keyHeight - iconHeight) / 2; 723 if (key.isAlignLeft()) { 724 iconX = (int)params.mKeyLabelHorizontalPadding; 725 alignX = iconX; 726 } else if (key.isAlignRight()) { 727 iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth; 728 alignX = iconX + iconWidth; 729 } else { // Align center 730 iconX = (keyWidth - iconWidth) / 2; 731 alignX = iconX + iconWidth / 2; 732 } 733 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 734 735 if (LatinImeLogger.sVISUALDEBUG) { 736 final Paint line = new Paint(); 737 drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line); 738 drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line); 739 } 740 } 741 742 if (key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0) { 743 drawKeyPopupHint(key, canvas, paint, params); 744 } 745 } 746 747 // Draw popup hint "..." at the bottom right corner of the key. 748 protected void drawKeyPopupHint(Key key, Canvas canvas, Paint paint, KeyDrawParams params) { 749 final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 750 final int keyHeight = key.mHeight; 751 752 paint.setTypeface(params.mKeyTextStyle); 753 paint.setTextSize(params.mKeyHintLetterSize); 754 paint.setColor(params.mKeyHintLabelColor); 755 paint.setTextAlign(Align.CENTER); 756 final float hintX = keyWidth - params.mKeyHintLetterPadding 757 - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2; 758 final float hintY = keyHeight - params.mKeyPopupHintLetterPadding; 759 canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint); 760 761 if (LatinImeLogger.sVISUALDEBUG) { 762 final Paint line = new Paint(); 763 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); 764 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); 765 } 766 } 767 768 private static int getCharGeometryCacheKey(char referenceChar, Paint paint) { 769 final int labelSize = (int)paint.getTextSize(); 770 final Typeface face = paint.getTypeface(); 771 final int codePointOffset = referenceChar << 15; 772 if (face == Typeface.DEFAULT) { 773 return codePointOffset + labelSize; 774 } else if (face == Typeface.DEFAULT_BOLD) { 775 return codePointOffset + labelSize + 0x1000; 776 } else if (face == Typeface.MONOSPACE) { 777 return codePointOffset + labelSize + 0x2000; 778 } else { 779 return codePointOffset + labelSize; 780 } 781 } 782 783 // Working variable for the following methods. 784 private final Rect mTextBounds = new Rect(); 785 786 private float getCharHeight(char[] referenceChar, Paint paint) { 787 final int key = getCharGeometryCacheKey(referenceChar[0], paint); 788 final Float cachedValue = sTextHeightCache.get(key); 789 if (cachedValue != null) 790 return cachedValue; 791 792 paint.getTextBounds(referenceChar, 0, 1, mTextBounds); 793 final float height = mTextBounds.height(); 794 sTextHeightCache.put(key, height); 795 return height; 796 } 797 798 private float getCharWidth(char[] referenceChar, Paint paint) { 799 final int key = getCharGeometryCacheKey(referenceChar[0], paint); 800 final Float cachedValue = sTextWidthCache.get(key); 801 if (cachedValue != null) 802 return cachedValue; 803 804 paint.getTextBounds(referenceChar, 0, 1, mTextBounds); 805 final float width = mTextBounds.width(); 806 sTextWidthCache.put(key, width); 807 return width; 808 } 809 810 public float getLabelWidth(String label, Paint paint) { 811 paint.getTextBounds(label.toString(), 0, label.length(), mTextBounds); 812 return mTextBounds.width(); 813 } 814 815 protected static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, 816 int height) { 817 canvas.translate(x, y); 818 icon.setBounds(0, 0, width, height); 819 icon.draw(canvas); 820 canvas.translate(-x, -y); 821 } 822 823 private static void drawHorizontalLine(Canvas canvas, float y, float w, int color, 824 Paint paint) { 825 paint.setStyle(Paint.Style.STROKE); 826 paint.setStrokeWidth(1.0f); 827 paint.setColor(color); 828 canvas.drawLine(0, y, w, y, paint); 829 } 830 831 private static void drawVerticalLine(Canvas canvas, float x, float h, int color, Paint paint) { 832 paint.setStyle(Paint.Style.STROKE); 833 paint.setStrokeWidth(1.0f); 834 paint.setColor(color); 835 canvas.drawLine(x, 0, x, h, paint); 836 } 837 838 private static void drawRectangle(Canvas canvas, float x, float y, float w, float h, int color, 839 Paint paint) { 840 paint.setStyle(Paint.Style.STROKE); 841 paint.setStrokeWidth(1.0f); 842 paint.setColor(color); 843 canvas.translate(x, y); 844 canvas.drawRect(0, 0, w, h, paint); 845 canvas.translate(-x, -y); 846 } 847 848 // Overlay a dark rectangle to dim. 849 private static void drawDimRectangle(Canvas canvas, Rect rect, int alpha, Paint paint) { 850 paint.setColor(Color.BLACK); 851 paint.setAlpha(alpha); 852 canvas.drawRect(rect, paint); 853 } 854 855 public Paint newDefaultLabelPaint() { 856 final Paint paint = new Paint(); 857 paint.setAntiAlias(true); 858 paint.setTypeface(mKeyDrawParams.mKeyTextStyle); 859 paint.setTextSize(mKeyDrawParams.mKeyLabelSize); 860 return paint; 861 } 862 863 public void cancelAllMessages() { 864 mDrawingHandler.cancelAllMessages(); 865 } 866 867 // Called by {@link PointerTracker} constructor to create a TextView. 868 @Override 869 public TextView inflateKeyPreviewText() { 870 final Context context = getContext(); 871 if (mKeyPreviewLayoutId != 0) { 872 return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null); 873 } else { 874 return new TextView(context); 875 } 876 } 877 878 @Override 879 public void dismissKeyPreview(PointerTracker tracker) { 880 mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker); 881 } 882 883 private void addKeyPreview(TextView keyPreview) { 884 locatePreviewPlacerView(); 885 mPreviewPlacerView.addView( 886 keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacerView, 0, 0)); 887 } 888 889 private void locatePreviewPlacerView() { 890 if (mPreviewPlacerView.getParent() != null) { 891 return; 892 } 893 final int[] viewOrigin = new int[2]; 894 getLocationInWindow(viewOrigin); 895 mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]); 896 final ViewGroup windowContentView = 897 (ViewGroup)getRootView().findViewById(android.R.id.content); 898 windowContentView.addView(mPreviewPlacerView); 899 } 900 901 public void showGesturePreviewText(String gesturePreviewText) { 902 locatePreviewPlacerView(); 903 mPreviewPlacerView.setGesturePreviewText(gesturePreviewText); 904 } 905 906 @Override 907 public void showGestureTrail(PointerTracker tracker) { 908 locatePreviewPlacerView(); 909 mPreviewPlacerView.invalidatePointer(tracker); 910 } 911 912 @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16 913 @Override 914 public void showKeyPreview(PointerTracker tracker) { 915 if (!mShowKeyPreviewPopup) return; 916 917 final TextView previewText = tracker.getKeyPreviewText(); 918 // If the key preview has no parent view yet, add it to the ViewGroup which can place 919 // key preview absolutely in SoftInputWindow. 920 if (previewText.getParent() == null) { 921 addKeyPreview(previewText); 922 } 923 924 mDrawingHandler.cancelDismissKeyPreview(tracker); 925 final Key key = tracker.getKey(); 926 // If key is invalid or IME is already closed, we must not show key preview. 927 // Trying to show key preview while root window is closed causes 928 // WindowManager.BadTokenException. 929 if (key == null) 930 return; 931 932 final KeyPreviewDrawParams params = mKeyPreviewDrawParams; 933 final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel; 934 // What we show as preview should match what we show on a key top in onBufferDraw(). 935 if (label != null) { 936 // TODO Should take care of temporaryShiftLabel here. 937 previewText.setCompoundDrawables(null, null, null, null); 938 if (StringUtils.codePointCount(label) > 1) { 939 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize); 940 previewText.setTypeface(Typeface.DEFAULT_BOLD); 941 } else { 942 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize); 943 previewText.setTypeface(params.mKeyTextStyle); 944 } 945 previewText.setText(label); 946 } else { 947 previewText.setCompoundDrawables(null, null, null, 948 key.getPreviewIcon(mKeyboard.mIconsSet)); 949 previewText.setText(null); 950 } 951 previewText.setBackgroundDrawable(params.mPreviewBackground); 952 953 previewText.measure( 954 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 955 final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 956 final int previewWidth = previewText.getMeasuredWidth(); 957 final int previewHeight = params.mPreviewHeight; 958 // The width and height of visible part of the key preview background. The content marker 959 // of the background 9-patch have to cover the visible part of the background. 960 params.mPreviewVisibleWidth = previewWidth - previewText.getPaddingLeft() 961 - previewText.getPaddingRight(); 962 params.mPreviewVisibleHeight = previewHeight - previewText.getPaddingTop() 963 - previewText.getPaddingBottom(); 964 // The distance between the top edge of the parent key and the bottom of the visible part 965 // of the key preview background. 966 params.mPreviewVisibleOffset = params.mPreviewOffset - previewText.getPaddingBottom(); 967 getLocationInWindow(params.mCoordinates); 968 // The key preview is horizontally aligned with the center of the visible part of the 969 // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and 970 // the left/right background is used if such background is specified. 971 int previewX = key.mX + key.mVisualInsetsLeft - (previewWidth - keyDrawWidth) / 2 972 + params.mCoordinates[0]; 973 if (previewX < 0) { 974 previewX = 0; 975 if (params.mPreviewLeftBackground != null) { 976 previewText.setBackgroundDrawable(params.mPreviewLeftBackground); 977 } 978 } else if (previewX > getWidth() - previewWidth) { 979 previewX = getWidth() - previewWidth; 980 if (params.mPreviewRightBackground != null) { 981 previewText.setBackgroundDrawable(params.mPreviewRightBackground); 982 } 983 } 984 // The key preview is placed vertically above the top edge of the parent key with an 985 // arbitrary offset. 986 final int previewY = key.mY - previewHeight + params.mPreviewOffset 987 + params.mCoordinates[1]; 988 989 // Set the preview background state 990 previewText.getBackground().setState( 991 key.mMoreKeys != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); 992 previewText.setTextColor(params.mPreviewTextColor); 993 ViewLayoutUtils.placeViewAt( 994 previewText, previewX, previewY, previewWidth, previewHeight); 995 previewText.setVisibility(VISIBLE); 996 } 997 998 /** 999 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 1000 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 1001 * draws the cached buffer. 1002 * @see #invalidateKey(Key) 1003 */ 1004 public void invalidateAllKeys() { 1005 mInvalidatedKeys.clear(); 1006 mInvalidateAllKeys = true; 1007 mBufferNeedsUpdate = true; 1008 invalidate(); 1009 } 1010 1011 /** 1012 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 1013 * one key is changing it's content. Any changes that affect the position or size of the key 1014 * may not be honored. 1015 * @param key key in the attached {@link Keyboard}. 1016 * @see #invalidateAllKeys 1017 */ 1018 @Override 1019 public void invalidateKey(Key key) { 1020 if (mInvalidateAllKeys) return; 1021 if (key == null) return; 1022 mInvalidatedKeys.add(key); 1023 final int x = key.mX + getPaddingLeft(); 1024 final int y = key.mY + getPaddingTop(); 1025 mInvalidatedKeysRect.union(x, y, x + key.mWidth, y + key.mHeight); 1026 mBufferNeedsUpdate = true; 1027 invalidate(mInvalidatedKeysRect); 1028 } 1029 1030 public void closing() { 1031 PointerTracker.dismissAllKeyPreviews(); 1032 cancelAllMessages(); 1033 1034 mInvalidateAllKeys = true; 1035 requestLayout(); 1036 } 1037 1038 @Override 1039 public boolean dismissMoreKeysPanel() { 1040 return false; 1041 } 1042 1043 public void purgeKeyboardAndClosing() { 1044 mKeyboard = null; 1045 closing(); 1046 } 1047 1048 @Override 1049 protected void onDetachedFromWindow() { 1050 super.onDetachedFromWindow(); 1051 closing(); 1052 mPreviewPlacerView.removeAllViews(); 1053 if (mBuffer != null) { 1054 mBuffer.recycle(); 1055 mBuffer = null; 1056 } 1057 } 1058} 1059