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 17 package com.android.inputmethod.keyboard; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.graphics.Bitmap; 22 import android.graphics.Canvas; 23 import android.graphics.Color; 24 import android.graphics.Paint; 25 import android.graphics.Paint.Align; 26 import android.graphics.PorterDuff; 27 import android.graphics.Rect; 28 import android.graphics.Typeface; 29 import android.graphics.drawable.Drawable; 30 import android.graphics.drawable.NinePatchDrawable; 31 import android.text.TextUtils; 32 import android.util.AttributeSet; 33 import android.view.View; 34 35 import com.android.inputmethod.keyboard.internal.KeyDrawParams; 36 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; 37 import com.android.inputmethod.latin.R; 38 import com.android.inputmethod.latin.common.Constants; 39 import com.android.inputmethod.latin.utils.TypefaceUtils; 40 41 import java.util.HashSet; 42 43 import javax.annotation.Nonnull; 44 import javax.annotation.Nullable; 45 46 /** 47 * A view that renders a virtual {@link Keyboard}. 48 * 49 * @attr ref R.styleable#KeyboardView_keyBackground 50 * @attr ref R.styleable#KeyboardView_functionalKeyBackground 51 * @attr ref R.styleable#KeyboardView_spacebarBackground 52 * @attr ref R.styleable#KeyboardView_spacebarIconWidthRatio 53 * @attr ref R.styleable#Keyboard_Key_keyLabelFlags 54 * @attr ref R.styleable#KeyboardView_keyHintLetterPadding 55 * @attr ref R.styleable#KeyboardView_keyPopupHintLetter 56 * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding 57 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding 58 * @attr ref R.styleable#KeyboardView_keyTextShadowRadius 59 * @attr ref R.styleable#KeyboardView_verticalCorrection 60 * @attr ref R.styleable#Keyboard_Key_keyTypeface 61 * @attr ref R.styleable#Keyboard_Key_keyLetterSize 62 * @attr ref R.styleable#Keyboard_Key_keyLabelSize 63 * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio 64 * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio 65 * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio 66 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio 67 * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio 68 * @attr ref R.styleable#Keyboard_Key_keyLabelOffCenterRatio 69 * @attr ref R.styleable#Keyboard_Key_keyHintLabelOffCenterRatio 70 * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio 71 * @attr ref R.styleable#Keyboard_Key_keyTextColor 72 * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled 73 * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor 74 * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor 75 * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor 76 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor 77 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor 78 * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor 79 */ 80 public class KeyboardView extends View { 81 // XML attributes 82 private final KeyVisualAttributes mKeyVisualAttributes; 83 // Default keyLabelFlags from {@link KeyboardTheme}. 84 // Currently only "alignHintLabelToBottom" is supported. 85 private final int mDefaultKeyLabelFlags; 86 private final float mKeyHintLetterPadding; 87 private final String mKeyPopupHintLetter; 88 private final float mKeyPopupHintLetterPadding; 89 private final float mKeyShiftedLetterHintPadding; 90 private final float mKeyTextShadowRadius; 91 private final float mVerticalCorrection; 92 private final Drawable mKeyBackground; 93 private final Drawable mFunctionalKeyBackground; 94 private final Drawable mSpacebarBackground; 95 private final float mSpacebarIconWidthRatio; 96 private final Rect mKeyBackgroundPadding = new Rect(); 97 private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f; 98 99 // The maximum key label width in the proportion to the key width. 100 private static final float MAX_LABEL_RATIO = 0.90f; 101 102 // Main keyboard 103 // TODO: Consider having a base keyboard object to make this @Nonnull 104 @Nullable 105 private Keyboard mKeyboard; 106 @Nonnull 107 private final KeyDrawParams mKeyDrawParams = new KeyDrawParams(); 108 109 // Drawing 110 /** True if all keys should be drawn */ 111 private boolean mInvalidateAllKeys; 112 /** The keys that should be drawn */ 113 private final HashSet<Key> mInvalidatedKeys = new HashSet<>(); 114 /** The working rectangle for clipping */ 115 private final Rect mClipRect = new Rect(); 116 /** The keyboard bitmap buffer for faster updates */ 117 private Bitmap mOffscreenBuffer; 118 /** The canvas for the above mutable keyboard bitmap */ 119 @Nonnull 120 private final Canvas mOffscreenCanvas = new Canvas(); 121 @Nonnull 122 private final Paint mPaint = new Paint(); 123 private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); 124 KeyboardView(final Context context, final AttributeSet attrs)125 public KeyboardView(final Context context, final AttributeSet attrs) { 126 this(context, attrs, R.attr.keyboardViewStyle); 127 } 128 KeyboardView(final Context context, final AttributeSet attrs, final int defStyle)129 public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { 130 super(context, attrs, defStyle); 131 132 final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, 133 R.styleable.KeyboardView, defStyle, R.style.KeyboardView); 134 mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground); 135 mKeyBackground.getPadding(mKeyBackgroundPadding); 136 final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable( 137 R.styleable.KeyboardView_functionalKeyBackground); 138 mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground 139 : mKeyBackground; 140 final Drawable spacebarBackground = keyboardViewAttr.getDrawable( 141 R.styleable.KeyboardView_spacebarBackground); 142 mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground; 143 mSpacebarIconWidthRatio = keyboardViewAttr.getFloat( 144 R.styleable.KeyboardView_spacebarIconWidthRatio, 1.0f); 145 mKeyHintLetterPadding = keyboardViewAttr.getDimension( 146 R.styleable.KeyboardView_keyHintLetterPadding, 0.0f); 147 mKeyPopupHintLetter = keyboardViewAttr.getString( 148 R.styleable.KeyboardView_keyPopupHintLetter); 149 mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension( 150 R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f); 151 mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension( 152 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f); 153 mKeyTextShadowRadius = keyboardViewAttr.getFloat( 154 R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED); 155 mVerticalCorrection = keyboardViewAttr.getDimension( 156 R.styleable.KeyboardView_verticalCorrection, 0.0f); 157 keyboardViewAttr.recycle(); 158 159 final TypedArray keyAttr = context.obtainStyledAttributes(attrs, 160 R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView); 161 mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0); 162 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 163 keyAttr.recycle(); 164 165 mPaint.setAntiAlias(true); 166 } 167 168 @Nullable getKeyVisualAttribute()169 public KeyVisualAttributes getKeyVisualAttribute() { 170 return mKeyVisualAttributes; 171 } 172 blendAlpha(@onnull final Paint paint, final int alpha)173 private static void blendAlpha(@Nonnull final Paint paint, final int alpha) { 174 final int color = paint.getColor(); 175 paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE, 176 Color.red(color), Color.green(color), Color.blue(color)); 177 } 178 setHardwareAcceleratedDrawingEnabled(final boolean enabled)179 public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { 180 if (!enabled) return; 181 // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? 182 setLayerType(LAYER_TYPE_HARDWARE, null); 183 } 184 185 /** 186 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 187 * view will re-layout itself to accommodate the keyboard. 188 * @see Keyboard 189 * @see #getKeyboard() 190 * @param keyboard the keyboard to display in this view 191 */ setKeyboard(@onnull final Keyboard keyboard)192 public void setKeyboard(@Nonnull final Keyboard keyboard) { 193 mKeyboard = keyboard; 194 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 195 mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); 196 mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes); 197 invalidateAllKeys(); 198 requestLayout(); 199 } 200 201 /** 202 * Returns the current keyboard being displayed by this view. 203 * @return the currently attached keyboard 204 * @see #setKeyboard(Keyboard) 205 */ 206 @Nullable getKeyboard()207 public Keyboard getKeyboard() { 208 return mKeyboard; 209 } 210 getVerticalCorrection()211 protected float getVerticalCorrection() { 212 return mVerticalCorrection; 213 } 214 215 @Nonnull getKeyDrawParams()216 protected KeyDrawParams getKeyDrawParams() { 217 return mKeyDrawParams; 218 } 219 updateKeyDrawParams(final int keyHeight)220 protected void updateKeyDrawParams(final int keyHeight) { 221 mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); 222 } 223 224 @Override onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)225 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 226 final Keyboard keyboard = getKeyboard(); 227 if (keyboard == null) { 228 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 229 return; 230 } 231 // The main keyboard expands to the entire this {@link KeyboardView}. 232 final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); 233 final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); 234 setMeasuredDimension(width, height); 235 } 236 237 @Override onDraw(final Canvas canvas)238 protected void onDraw(final Canvas canvas) { 239 super.onDraw(canvas); 240 if (canvas.isHardwareAccelerated()) { 241 onDrawKeyboard(canvas); 242 return; 243 } 244 245 final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty(); 246 if (bufferNeedsUpdates || mOffscreenBuffer == null) { 247 if (maybeAllocateOffscreenBuffer()) { 248 mInvalidateAllKeys = true; 249 // TODO: Stop using the offscreen canvas even when in software rendering 250 mOffscreenCanvas.setBitmap(mOffscreenBuffer); 251 } 252 onDrawKeyboard(mOffscreenCanvas); 253 } 254 canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null); 255 } 256 maybeAllocateOffscreenBuffer()257 private boolean maybeAllocateOffscreenBuffer() { 258 final int width = getWidth(); 259 final int height = getHeight(); 260 if (width == 0 || height == 0) { 261 return false; 262 } 263 if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width 264 && mOffscreenBuffer.getHeight() == height) { 265 return false; 266 } 267 freeOffscreenBuffer(); 268 mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 269 return true; 270 } 271 freeOffscreenBuffer()272 private void freeOffscreenBuffer() { 273 mOffscreenCanvas.setBitmap(null); 274 mOffscreenCanvas.setMatrix(null); 275 if (mOffscreenBuffer != null) { 276 mOffscreenBuffer.recycle(); 277 mOffscreenBuffer = null; 278 } 279 } 280 onDrawKeyboard(@onnull final Canvas canvas)281 private void onDrawKeyboard(@Nonnull final Canvas canvas) { 282 final Keyboard keyboard = getKeyboard(); 283 if (keyboard == null) { 284 return; 285 } 286 287 final Paint paint = mPaint; 288 final Drawable background = getBackground(); 289 // Calculate clip region and set. 290 final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty(); 291 final boolean isHardwareAccelerated = canvas.isHardwareAccelerated(); 292 // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on. 293 if (drawAllKeys || isHardwareAccelerated) { 294 if (!isHardwareAccelerated && background != null) { 295 // Need to draw keyboard background on {@link #mOffscreenBuffer}. 296 canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 297 background.draw(canvas); 298 } 299 // Draw all keys. 300 for (final Key key : keyboard.getSortedKeys()) { 301 onDrawKey(key, canvas, paint); 302 } 303 } else { 304 for (final Key key : mInvalidatedKeys) { 305 if (!keyboard.hasKey(key)) { 306 continue; 307 } 308 if (background != null) { 309 // Need to redraw key's background on {@link #mOffscreenBuffer}. 310 final int x = key.getX() + getPaddingLeft(); 311 final int y = key.getY() + getPaddingTop(); 312 mClipRect.set(x, y, x + key.getWidth(), y + key.getHeight()); 313 canvas.save(); 314 canvas.clipRect(mClipRect); 315 canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 316 background.draw(canvas); 317 canvas.restore(); 318 } 319 onDrawKey(key, canvas, paint); 320 } 321 } 322 323 mInvalidatedKeys.clear(); 324 mInvalidateAllKeys = false; 325 } 326 onDrawKey(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Paint paint)327 private void onDrawKey(@Nonnull final Key key, @Nonnull final Canvas canvas, 328 @Nonnull final Paint paint) { 329 final int keyDrawX = key.getDrawX() + getPaddingLeft(); 330 final int keyDrawY = key.getY() + getPaddingTop(); 331 canvas.translate(keyDrawX, keyDrawY); 332 333 final KeyVisualAttributes attr = key.getVisualAttributes(); 334 final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(key.getHeight(), attr); 335 params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE; 336 337 if (!key.isSpacer()) { 338 final Drawable background = key.selectBackgroundDrawable( 339 mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground); 340 if (background != null) { 341 onDrawKeyBackground(key, canvas, background); 342 } 343 } 344 onDrawKeyTopVisuals(key, canvas, paint, params); 345 346 canvas.translate(-keyDrawX, -keyDrawY); 347 } 348 349 // Draw key background. onDrawKeyBackground(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Drawable background)350 protected void onDrawKeyBackground(@Nonnull final Key key, @Nonnull final Canvas canvas, 351 @Nonnull final Drawable background) { 352 final int keyWidth = key.getDrawWidth(); 353 final int keyHeight = key.getHeight(); 354 final int bgWidth, bgHeight, bgX, bgY; 355 if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags) 356 // HACK: To disable expanding normal/functional key background. 357 && !key.hasCustomActionLabel()) { 358 final int intrinsicWidth = background.getIntrinsicWidth(); 359 final int intrinsicHeight = background.getIntrinsicHeight(); 360 final float minScale = Math.min( 361 keyWidth / (float)intrinsicWidth, keyHeight / (float)intrinsicHeight); 362 bgWidth = (int)(intrinsicWidth * minScale); 363 bgHeight = (int)(intrinsicHeight * minScale); 364 bgX = (keyWidth - bgWidth) / 2; 365 bgY = (keyHeight - bgHeight) / 2; 366 } else { 367 final Rect padding = mKeyBackgroundPadding; 368 bgWidth = keyWidth + padding.left + padding.right; 369 bgHeight = keyHeight + padding.top + padding.bottom; 370 bgX = -padding.left; 371 bgY = -padding.top; 372 } 373 final Rect bounds = background.getBounds(); 374 if (bgWidth != bounds.right || bgHeight != bounds.bottom) { 375 background.setBounds(0, 0, bgWidth, bgHeight); 376 } 377 canvas.translate(bgX, bgY); 378 background.draw(canvas); 379 canvas.translate(-bgX, -bgY); 380 } 381 382 // Draw key top visuals. onDrawKeyTopVisuals(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final KeyDrawParams params)383 protected void onDrawKeyTopVisuals(@Nonnull final Key key, @Nonnull final Canvas canvas, 384 @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) { 385 final int keyWidth = key.getDrawWidth(); 386 final int keyHeight = key.getHeight(); 387 final float centerX = keyWidth * 0.5f; 388 final float centerY = keyHeight * 0.5f; 389 390 // Draw key label. 391 final Keyboard keyboard = getKeyboard(); 392 final Drawable icon = (keyboard == null) ? null 393 : key.getIcon(keyboard.mIconsSet, params.mAnimAlpha); 394 float labelX = centerX; 395 float labelBaseline = centerY; 396 final String label = key.getLabel(); 397 if (label != null) { 398 paint.setTypeface(key.selectTypeface(params)); 399 paint.setTextSize(key.selectTextSize(params)); 400 final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint); 401 final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint); 402 403 // Vertical label text alignment. 404 labelBaseline = centerY + labelCharHeight / 2.0f; 405 406 // Horizontal label text alignment 407 if (key.isAlignLabelOffCenter()) { 408 // The label is placed off center of the key. Used mainly on "phone number" layout. 409 labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth; 410 paint.setTextAlign(Align.LEFT); 411 } else { 412 labelX = centerX; 413 paint.setTextAlign(Align.CENTER); 414 } 415 if (key.needsAutoXScale()) { 416 final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / 417 TypefaceUtils.getStringWidth(label, paint)); 418 if (key.needsAutoScale()) { 419 final float autoSize = paint.getTextSize() * ratio; 420 paint.setTextSize(autoSize); 421 } else { 422 paint.setTextScaleX(ratio); 423 } 424 } 425 426 if (key.isEnabled()) { 427 paint.setColor(key.selectTextColor(params)); 428 // Set a drop shadow for the text if the shadow radius is positive value. 429 if (mKeyTextShadowRadius > 0.0f) { 430 paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor); 431 } else { 432 paint.clearShadowLayer(); 433 } 434 } else { 435 // Make label invisible 436 paint.setColor(Color.TRANSPARENT); 437 paint.clearShadowLayer(); 438 } 439 blendAlpha(paint, params.mAnimAlpha); 440 canvas.drawText(label, 0, label.length(), labelX, labelBaseline, paint); 441 // Turn off drop shadow and reset x-scale. 442 paint.clearShadowLayer(); 443 paint.setTextScaleX(1.0f); 444 } 445 446 // Draw hint label. 447 final String hintLabel = key.getHintLabel(); 448 if (hintLabel != null) { 449 paint.setTextSize(key.selectHintTextSize(params)); 450 paint.setColor(key.selectHintTextColor(params)); 451 // TODO: Should add a way to specify type face for hint letters 452 paint.setTypeface(Typeface.DEFAULT_BOLD); 453 blendAlpha(paint, params.mAnimAlpha); 454 final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint); 455 final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint); 456 final float hintX, hintBaseline; 457 if (key.hasHintLabel()) { 458 // The hint label is placed just right of the key label. Used mainly on 459 // "phone number" layout. 460 hintX = labelX + params.mHintLabelOffCenterRatio * labelCharWidth; 461 if (key.isAlignHintLabelToBottom(mDefaultKeyLabelFlags)) { 462 hintBaseline = labelBaseline; 463 } else { 464 hintBaseline = centerY + labelCharHeight / 2.0f; 465 } 466 paint.setTextAlign(Align.LEFT); 467 } else if (key.hasShiftedLetterHint()) { 468 // The hint label is placed at top-right corner of the key. Used mainly on tablet. 469 hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f; 470 paint.getFontMetrics(mFontMetrics); 471 hintBaseline = -mFontMetrics.top; 472 paint.setTextAlign(Align.CENTER); 473 } else { // key.hasHintLetter() 474 // The hint letter is placed at top-right corner of the key. Used mainly on phone. 475 final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint); 476 final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint); 477 hintX = keyWidth - mKeyHintLetterPadding 478 - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f; 479 hintBaseline = -paint.ascent(); 480 paint.setTextAlign(Align.CENTER); 481 } 482 final float adjustmentY = params.mHintLabelVerticalAdjustment * labelCharHeight; 483 canvas.drawText( 484 hintLabel, 0, hintLabel.length(), hintX, hintBaseline + adjustmentY, paint); 485 } 486 487 // Draw key icon. 488 if (label == null && icon != null) { 489 final int iconWidth; 490 if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) { 491 iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio); 492 } else { 493 iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth); 494 } 495 final int iconHeight = icon.getIntrinsicHeight(); 496 final int iconY; 497 if (key.isAlignIconToBottom()) { 498 iconY = keyHeight - iconHeight; 499 } else { 500 iconY = (keyHeight - iconHeight) / 2; // Align vertically center. 501 } 502 final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center. 503 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 504 } 505 506 if (key.hasPopupHint() && key.getMoreKeys() != null) { 507 drawKeyPopupHint(key, canvas, paint, params); 508 } 509 } 510 511 // Draw popup hint "..." at the bottom right corner of the key. drawKeyPopupHint(@onnull final Key key, @Nonnull final Canvas canvas, @Nonnull final Paint paint, @Nonnull final KeyDrawParams params)512 protected void drawKeyPopupHint(@Nonnull final Key key, @Nonnull final Canvas canvas, 513 @Nonnull final Paint paint, @Nonnull final KeyDrawParams params) { 514 if (TextUtils.isEmpty(mKeyPopupHintLetter)) { 515 return; 516 } 517 final int keyWidth = key.getDrawWidth(); 518 final int keyHeight = key.getHeight(); 519 520 paint.setTypeface(params.mTypeface); 521 paint.setTextSize(params.mHintLetterSize); 522 paint.setColor(params.mHintLabelColor); 523 paint.setTextAlign(Align.CENTER); 524 final float hintX = keyWidth - mKeyHintLetterPadding 525 - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f; 526 final float hintY = keyHeight - mKeyPopupHintLetterPadding; 527 canvas.drawText(mKeyPopupHintLetter, hintX, hintY, paint); 528 } 529 drawIcon(@onnull final Canvas canvas,@Nonnull final Drawable icon, final int x, final int y, final int width, final int height)530 protected static void drawIcon(@Nonnull final Canvas canvas,@Nonnull final Drawable icon, 531 final int x, final int y, final int width, final int height) { 532 canvas.translate(x, y); 533 icon.setBounds(0, 0, width, height); 534 icon.draw(canvas); 535 canvas.translate(-x, -y); 536 } 537 newLabelPaint(@ullable final Key key)538 public Paint newLabelPaint(@Nullable final Key key) { 539 final Paint paint = new Paint(); 540 paint.setAntiAlias(true); 541 if (key == null) { 542 paint.setTypeface(mKeyDrawParams.mTypeface); 543 paint.setTextSize(mKeyDrawParams.mLabelSize); 544 } else { 545 paint.setColor(key.selectTextColor(mKeyDrawParams)); 546 paint.setTypeface(key.selectTypeface(mKeyDrawParams)); 547 paint.setTextSize(key.selectTextSize(mKeyDrawParams)); 548 } 549 return paint; 550 } 551 552 /** 553 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 554 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 555 * draws the cached buffer. 556 * @see #invalidateKey(Key) 557 */ invalidateAllKeys()558 public void invalidateAllKeys() { 559 mInvalidatedKeys.clear(); 560 mInvalidateAllKeys = true; 561 invalidate(); 562 } 563 564 /** 565 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 566 * one key is changing it's content. Any changes that affect the position or size of the key 567 * may not be honored. 568 * @param key key in the attached {@link Keyboard}. 569 * @see #invalidateAllKeys 570 */ invalidateKey(@ullable final Key key)571 public void invalidateKey(@Nullable final Key key) { 572 if (mInvalidateAllKeys || key == null) { 573 return; 574 } 575 mInvalidatedKeys.add(key); 576 final int x = key.getX() + getPaddingLeft(); 577 final int y = key.getY() + getPaddingTop(); 578 invalidate(x, y, x + key.getWidth(), y + key.getHeight()); 579 } 580 581 @Override onDetachedFromWindow()582 protected void onDetachedFromWindow() { 583 super.onDetachedFromWindow(); 584 freeOffscreenBuffer(); 585 } 586 deallocateMemory()587 public void deallocateMemory() { 588 freeOffscreenBuffer(); 589 } 590 } 591