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