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