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