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.Region; 29 import android.graphics.Typeface; 30 import android.graphics.drawable.Drawable; 31 import android.util.AttributeSet; 32 import android.view.View; 33 34 import com.android.inputmethod.keyboard.internal.KeyDrawParams; 35 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; 36 import com.android.inputmethod.latin.Constants; 37 import com.android.inputmethod.latin.LatinImeLogger; 38 import com.android.inputmethod.latin.R; 39 import com.android.inputmethod.latin.define.ProductionFlag; 40 import com.android.inputmethod.latin.utils.CollectionUtils; 41 import com.android.inputmethod.latin.utils.TypefaceUtils; 42 import com.android.inputmethod.research.ResearchLogger; 43 44 import java.util.HashSet; 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_keyLabelHorizontalPadding 51 * @attr ref R.styleable#KeyboardView_keyHintLetterPadding 52 * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding 53 * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding 54 * @attr ref R.styleable#KeyboardView_keyTextShadowRadius 55 * @attr ref R.styleable#KeyboardView_verticalCorrection 56 * @attr ref R.styleable#Keyboard_Key_keyTypeface 57 * @attr ref R.styleable#Keyboard_Key_keyLetterSize 58 * @attr ref R.styleable#Keyboard_Key_keyLabelSize 59 * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio 60 * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio 61 * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio 62 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio 63 * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio 64 * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio 65 * @attr ref R.styleable#Keyboard_Key_keyTextColor 66 * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled 67 * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor 68 * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor 69 * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor 70 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor 71 * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor 72 * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor 73 */ 74 public class KeyboardView extends View { 75 // XML attributes 76 private final KeyVisualAttributes mKeyVisualAttributes; 77 private final int mKeyLabelHorizontalPadding; 78 private final float mKeyHintLetterPadding; 79 private final float mKeyPopupHintLetterPadding; 80 private final float mKeyShiftedLetterHintPadding; 81 private final float mKeyTextShadowRadius; 82 private final float mVerticalCorrection; 83 private final Drawable mKeyBackground; 84 private final Rect mKeyBackgroundPadding = new Rect(); 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 protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams(); 100 101 // Drawing 102 /** True if all keys should be drawn */ 103 private boolean mInvalidateAllKeys; 104 /** The keys that should be drawn */ 105 private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet(); 106 /** The working rectangle variable */ 107 private final Rect mWorkingRect = new Rect(); 108 /** The keyboard bitmap buffer for faster updates */ 109 /** The clip region to draw keys */ 110 private final Region mClipRegion = new Region(); 111 private Bitmap mOffscreenBuffer; 112 /** The canvas for the above mutable keyboard bitmap */ 113 private final Canvas mOffscreenCanvas = new Canvas(); 114 private final Paint mPaint = new Paint(); 115 private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); 116 private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' }; 117 private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' }; 118 119 public KeyboardView(final Context context, final AttributeSet attrs) { 120 this(context, attrs, R.attr.keyboardViewStyle); 121 } 122 123 public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { 124 super(context, attrs, defStyle); 125 126 final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs, 127 R.styleable.KeyboardView, defStyle, R.style.KeyboardView); 128 mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground); 129 mKeyBackground.getPadding(mKeyBackgroundPadding); 130 mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset( 131 R.styleable.KeyboardView_keyLabelHorizontalPadding, 0); 132 mKeyHintLetterPadding = keyboardViewAttr.getDimension( 133 R.styleable.KeyboardView_keyHintLetterPadding, 0.0f); 134 mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension( 135 R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f); 136 mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension( 137 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f); 138 mKeyTextShadowRadius = keyboardViewAttr.getFloat( 139 R.styleable.KeyboardView_keyTextShadowRadius, 0.0f); 140 mVerticalCorrection = keyboardViewAttr.getDimension( 141 R.styleable.KeyboardView_verticalCorrection, 0.0f); 142 keyboardViewAttr.recycle(); 143 144 final TypedArray keyAttr = context.obtainStyledAttributes(attrs, 145 R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView); 146 mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr); 147 keyAttr.recycle(); 148 149 mPaint.setAntiAlias(true); 150 } 151 152 private static void blendAlpha(final Paint paint, final int alpha) { 153 final int color = paint.getColor(); 154 paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE, 155 Color.red(color), Color.green(color), Color.blue(color)); 156 } 157 158 public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) { 159 if (!enabled) return; 160 // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? 161 setLayerType(LAYER_TYPE_HARDWARE, null); 162 } 163 164 /** 165 * Attaches a keyboard to this view. The keyboard can be switched at any time and the 166 * view will re-layout itself to accommodate the keyboard. 167 * @see Keyboard 168 * @see #getKeyboard() 169 * @param keyboard the keyboard to display in this view 170 */ 171 public void setKeyboard(final Keyboard keyboard) { 172 mKeyboard = keyboard; 173 LatinImeLogger.onSetKeyboard(keyboard); 174 final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; 175 mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); 176 mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes); 177 invalidateAllKeys(); 178 requestLayout(); 179 } 180 181 /** 182 * Returns the current keyboard being displayed by this view. 183 * @return the currently attached keyboard 184 * @see #setKeyboard(Keyboard) 185 */ 186 public Keyboard getKeyboard() { 187 return mKeyboard; 188 } 189 190 protected float getVerticalCorrection() { 191 return mVerticalCorrection; 192 } 193 194 protected void updateKeyDrawParams(final int keyHeight) { 195 mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); 196 } 197 198 @Override 199 protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { 200 if (mKeyboard == null) { 201 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 202 return; 203 } 204 // The main keyboard expands to the entire this {@link KeyboardView}. 205 final int width = mKeyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); 206 final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom(); 207 setMeasuredDimension(width, height); 208 } 209 210 @Override 211 protected void onDraw(final Canvas canvas) { 212 super.onDraw(canvas); 213 if (canvas.isHardwareAccelerated()) { 214 onDrawKeyboard(canvas); 215 return; 216 } 217 218 final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty(); 219 if (bufferNeedsUpdates || mOffscreenBuffer == null) { 220 if (maybeAllocateOffscreenBuffer()) { 221 mInvalidateAllKeys = true; 222 // TODO: Stop using the offscreen canvas even when in software rendering 223 mOffscreenCanvas.setBitmap(mOffscreenBuffer); 224 } 225 onDrawKeyboard(mOffscreenCanvas); 226 } 227 canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null); 228 } 229 230 private boolean maybeAllocateOffscreenBuffer() { 231 final int width = getWidth(); 232 final int height = getHeight(); 233 if (width == 0 || height == 0) { 234 return false; 235 } 236 if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width 237 && mOffscreenBuffer.getHeight() == height) { 238 return false; 239 } 240 freeOffscreenBuffer(); 241 mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 242 return true; 243 } 244 245 private void freeOffscreenBuffer() { 246 mOffscreenCanvas.setBitmap(null); 247 mOffscreenCanvas.setMatrix(null); 248 if (mOffscreenBuffer != null) { 249 mOffscreenBuffer.recycle(); 250 mOffscreenBuffer = null; 251 } 252 } 253 254 private void onDrawKeyboard(final Canvas canvas) { 255 if (mKeyboard == null) return; 256 257 final int width = getWidth(); 258 final int height = getHeight(); 259 final Paint paint = mPaint; 260 261 // Calculate clip region and set. 262 final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty(); 263 final boolean isHardwareAccelerated = canvas.isHardwareAccelerated(); 264 // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on. 265 if (drawAllKeys || isHardwareAccelerated) { 266 mClipRegion.set(0, 0, width, height); 267 } else { 268 mClipRegion.setEmpty(); 269 for (final Key key : mInvalidatedKeys) { 270 if (mKeyboard.hasKey(key)) { 271 final int x = key.getX() + getPaddingLeft(); 272 final int y = key.getY() + getPaddingTop(); 273 mWorkingRect.set(x, y, x + key.getWidth(), y + key.getHeight()); 274 mClipRegion.union(mWorkingRect); 275 } 276 } 277 } 278 if (!isHardwareAccelerated) { 279 canvas.clipRegion(mClipRegion, Region.Op.REPLACE); 280 // Draw keyboard background. 281 canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR); 282 final Drawable background = getBackground(); 283 if (background != null) { 284 background.draw(canvas); 285 } 286 } 287 288 // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on. 289 if (drawAllKeys || isHardwareAccelerated) { 290 // Draw all keys. 291 for (final Key key : mKeyboard.getKeys()) { 292 onDrawKey(key, canvas, paint); 293 } 294 } else { 295 // Draw invalidated keys. 296 for (final Key key : mInvalidatedKeys) { 297 if (mKeyboard.hasKey(key)) { 298 onDrawKey(key, canvas, paint); 299 } 300 } 301 } 302 303 // Research Logging (Development Only Diagnostics) indicator. 304 // TODO: Reimplement using a keyboard background image specific to the ResearchLogger, 305 // and remove this call. 306 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 307 ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height); 308 } 309 310 mInvalidatedKeys.clear(); 311 mInvalidateAllKeys = false; 312 } 313 314 private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) { 315 final int keyDrawX = key.getDrawX() + getPaddingLeft(); 316 final int keyDrawY = key.getY() + getPaddingTop(); 317 canvas.translate(keyDrawX, keyDrawY); 318 319 final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap; 320 final KeyVisualAttributes attr = key.getVisualAttributes(); 321 final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr); 322 params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE; 323 324 if (!key.isSpacer()) { 325 onDrawKeyBackground(key, canvas); 326 } 327 onDrawKeyTopVisuals(key, canvas, paint, params); 328 329 canvas.translate(-keyDrawX, -keyDrawY); 330 } 331 332 // Draw key background. 333 protected void onDrawKeyBackground(final Key key, final Canvas canvas) { 334 final Rect padding = mKeyBackgroundPadding; 335 final int bgWidth = key.getDrawWidth() + padding.left + padding.right; 336 final int bgHeight = key.getHeight() + padding.top + padding.bottom; 337 final int bgX = -padding.left; 338 final int bgY = -padding.top; 339 final int[] drawableState = key.getCurrentDrawableState(); 340 final Drawable background = mKeyBackground; 341 background.setState(drawableState); 342 final Rect bounds = background.getBounds(); 343 if (bgWidth != bounds.right || bgHeight != bounds.bottom) { 344 background.setBounds(0, 0, bgWidth, bgHeight); 345 } 346 canvas.translate(bgX, bgY); 347 background.draw(canvas); 348 if (LatinImeLogger.sVISUALDEBUG) { 349 drawRectangle(canvas, 0.0f, 0.0f, bgWidth, bgHeight, 0x80c00000, new Paint()); 350 } 351 canvas.translate(-bgX, -bgY); 352 } 353 354 // Draw key top visuals. 355 protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, 356 final KeyDrawParams params) { 357 final int keyWidth = key.getDrawWidth(); 358 final int keyHeight = key.getHeight(); 359 final float centerX = keyWidth * 0.5f; 360 final float centerY = keyHeight * 0.5f; 361 362 if (LatinImeLogger.sVISUALDEBUG) { 363 drawRectangle(canvas, 0.0f, 0.0f, keyWidth, keyHeight, 0x800000c0, new Paint()); 364 } 365 366 // Draw key label. 367 final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha); 368 float positionX = centerX; 369 final String label = key.getLabel(); 370 if (label != null) { 371 paint.setTypeface(key.selectTypeface(params)); 372 paint.setTextSize(key.selectTextSize(params)); 373 final float labelCharHeight = TypefaceUtils.getCharHeight( 374 KEY_LABEL_REFERENCE_CHAR, paint); 375 final float labelCharWidth = TypefaceUtils.getCharWidth( 376 KEY_LABEL_REFERENCE_CHAR, paint); 377 378 // Vertical label text alignment. 379 final float baseline = centerY + labelCharHeight / 2.0f; 380 381 // Horizontal label text alignment 382 float labelWidth = 0.0f; 383 if (key.isAlignLeft()) { 384 positionX = mKeyLabelHorizontalPadding; 385 paint.setTextAlign(Align.LEFT); 386 } else if (key.isAlignRight()) { 387 positionX = keyWidth - mKeyLabelHorizontalPadding; 388 paint.setTextAlign(Align.RIGHT); 389 } else if (key.isAlignLeftOfCenter()) { 390 // TODO: Parameterise this? 391 positionX = centerX - labelCharWidth * 7.0f / 4.0f; 392 paint.setTextAlign(Align.LEFT); 393 } else if (key.hasLabelWithIconLeft() && icon != null) { 394 labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth() 395 + LABEL_ICON_MARGIN * keyWidth; 396 positionX = centerX + labelWidth / 2.0f; 397 paint.setTextAlign(Align.RIGHT); 398 } else if (key.hasLabelWithIconRight() && icon != null) { 399 labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth() 400 + LABEL_ICON_MARGIN * keyWidth; 401 positionX = centerX - labelWidth / 2.0f; 402 paint.setTextAlign(Align.LEFT); 403 } else { 404 positionX = centerX; 405 paint.setTextAlign(Align.CENTER); 406 } 407 if (key.needsXScale()) { 408 paint.setTextScaleX(Math.min(1.0f, 409 (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getLabelWidth(label, paint))); 410 } 411 412 paint.setColor(key.selectTextColor(params)); 413 if (key.isEnabled()) { 414 // Set a drop shadow for the text 415 paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor); 416 } else { 417 // Make label invisible 418 paint.setColor(Color.TRANSPARENT); 419 } 420 blendAlpha(paint, params.mAnimAlpha); 421 canvas.drawText(label, 0, label.length(), positionX, baseline, paint); 422 // Turn off drop shadow and reset x-scale. 423 paint.setShadowLayer(0.0f, 0.0f, 0.0f, Color.TRANSPARENT); 424 paint.setTextScaleX(1.0f); 425 426 if (icon != null) { 427 final int iconWidth = icon.getIntrinsicWidth(); 428 final int iconHeight = icon.getIntrinsicHeight(); 429 final int iconY = (keyHeight - iconHeight) / 2; 430 if (key.hasLabelWithIconLeft()) { 431 final int iconX = (int)(centerX - labelWidth / 2.0f); 432 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 433 } else if (key.hasLabelWithIconRight()) { 434 final int iconX = (int)(centerX + labelWidth / 2.0f - iconWidth); 435 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 436 } 437 } 438 439 if (LatinImeLogger.sVISUALDEBUG) { 440 final Paint line = new Paint(); 441 drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line); 442 drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line); 443 } 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 hintX, hintY; 455 if (key.hasHintLabel()) { 456 // The hint label is placed just right of the key label. Used mainly on 457 // "phone number" layout. 458 // TODO: Generalize the following calculations. 459 hintX = positionX 460 + TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2.0f; 461 hintY = centerY 462 + TypefaceUtils.getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f; 463 paint.setTextAlign(Align.LEFT); 464 } else if (key.hasShiftedLetterHint()) { 465 // The hint label is placed at top-right corner of the key. Used mainly on tablet. 466 hintX = keyWidth - mKeyShiftedLetterHintPadding 467 - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f; 468 paint.getFontMetrics(mFontMetrics); 469 hintY = -mFontMetrics.top; 470 paint.setTextAlign(Align.CENTER); 471 } else { // key.hasHintLetter() 472 // The hint letter is placed at top-right corner of the key. Used mainly on phone. 473 final float keyNumericHintLabelReferenceCharWidth = 474 TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint); 475 final float keyHintLabelStringWidth = 476 TypefaceUtils.getStringWidth(hintLabel, paint); 477 hintX = keyWidth - mKeyHintLetterPadding 478 - Math.max(keyNumericHintLabelReferenceCharWidth, keyHintLabelStringWidth) 479 / 2.0f; 480 hintY = -paint.ascent(); 481 paint.setTextAlign(Align.CENTER); 482 } 483 canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY, paint); 484 485 if (LatinImeLogger.sVISUALDEBUG) { 486 final Paint line = new Paint(); 487 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); 488 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); 489 } 490 } 491 492 // Draw key icon. 493 if (label == null && icon != null) { 494 final int iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth); 495 final int iconHeight = icon.getIntrinsicHeight(); 496 final int iconX, alignX; 497 final int iconY = (keyHeight - iconHeight) / 2; 498 if (key.isAlignLeft()) { 499 iconX = mKeyLabelHorizontalPadding; 500 alignX = iconX; 501 } else if (key.isAlignRight()) { 502 iconX = keyWidth - mKeyLabelHorizontalPadding - iconWidth; 503 alignX = iconX + iconWidth; 504 } else { // Align center 505 iconX = (keyWidth - iconWidth) / 2; 506 alignX = iconX + iconWidth / 2; 507 } 508 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 509 510 if (LatinImeLogger.sVISUALDEBUG) { 511 final Paint line = new Paint(); 512 drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line); 513 drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line); 514 } 515 } 516 517 if (key.hasPopupHint() && key.getMoreKeys() != null) { 518 drawKeyPopupHint(key, canvas, paint, params); 519 } 520 } 521 522 // Draw popup hint "..." at the bottom right corner of the key. 523 protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint, 524 final KeyDrawParams params) { 525 final int keyWidth = key.getDrawWidth(); 526 final int keyHeight = key.getHeight(); 527 528 paint.setTypeface(params.mTypeface); 529 paint.setTextSize(params.mHintLetterSize); 530 paint.setColor(params.mHintLabelColor); 531 paint.setTextAlign(Align.CENTER); 532 final float hintX = keyWidth - mKeyHintLetterPadding 533 - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f; 534 final float hintY = keyHeight - mKeyPopupHintLetterPadding; 535 canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint); 536 537 if (LatinImeLogger.sVISUALDEBUG) { 538 final Paint line = new Paint(); 539 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); 540 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); 541 } 542 } 543 544 protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x, 545 final int y, final int width, final int height) { 546 canvas.translate(x, y); 547 icon.setBounds(0, 0, width, height); 548 icon.draw(canvas); 549 canvas.translate(-x, -y); 550 } 551 552 private static void drawHorizontalLine(final Canvas canvas, final float y, final float w, 553 final int color, final Paint paint) { 554 paint.setStyle(Paint.Style.STROKE); 555 paint.setStrokeWidth(1.0f); 556 paint.setColor(color); 557 canvas.drawLine(0.0f, y, w, y, paint); 558 } 559 560 private static void drawVerticalLine(final Canvas canvas, final float x, final float h, 561 final int color, final Paint paint) { 562 paint.setStyle(Paint.Style.STROKE); 563 paint.setStrokeWidth(1.0f); 564 paint.setColor(color); 565 canvas.drawLine(x, 0.0f, x, h, paint); 566 } 567 568 private static void drawRectangle(final Canvas canvas, final float x, final float y, 569 final float w, final float h, final int color, final Paint paint) { 570 paint.setStyle(Paint.Style.STROKE); 571 paint.setStrokeWidth(1.0f); 572 paint.setColor(color); 573 canvas.translate(x, y); 574 canvas.drawRect(0.0f, 0.0f, w, h, paint); 575 canvas.translate(-x, -y); 576 } 577 578 public Paint newLabelPaint(final Key key) { 579 final Paint paint = new Paint(); 580 paint.setAntiAlias(true); 581 if (key == null) { 582 paint.setTypeface(mKeyDrawParams.mTypeface); 583 paint.setTextSize(mKeyDrawParams.mLabelSize); 584 } else { 585 paint.setTypeface(key.selectTypeface(mKeyDrawParams)); 586 paint.setTextSize(key.selectTextSize(mKeyDrawParams)); 587 } 588 return paint; 589 } 590 591 /** 592 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 593 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 594 * draws the cached buffer. 595 * @see #invalidateKey(Key) 596 */ 597 public void invalidateAllKeys() { 598 mInvalidatedKeys.clear(); 599 mInvalidateAllKeys = true; 600 invalidate(); 601 } 602 603 /** 604 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 605 * one key is changing it's content. Any changes that affect the position or size of the key 606 * may not be honored. 607 * @param key key in the attached {@link Keyboard}. 608 * @see #invalidateAllKeys 609 */ 610 public void invalidateKey(final Key key) { 611 if (mInvalidateAllKeys) return; 612 if (key == null) return; 613 mInvalidatedKeys.add(key); 614 final int x = key.getX() + getPaddingLeft(); 615 final int y = key.getY() + getPaddingTop(); 616 invalidate(x, y, x + key.getWidth(), y + key.getHeight()); 617 } 618 619 @Override 620 protected void onDetachedFromWindow() { 621 super.onDetachedFromWindow(); 622 freeOffscreenBuffer(); 623 } 624 625 public void deallocateMemory() { 626 freeOffscreenBuffer(); 627 } 628 } 629