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