KeyboardView.java revision c499866948f725d14fb2ce95213f9c6f3a7da8b5
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, mKeyboard, canvas, mPaint, params, 447 isManualTemporaryUpperCase); 448 canvas.translate(-keyDrawX, -keyDrawY); 449 } else { 450 // Draw all keys. 451 for (final Key key : mKeyboard.getKeys()) { 452 final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft(); 453 final int keyDrawY = key.mY + getPaddingTop(); 454 canvas.translate(keyDrawX, keyDrawY); 455 onBufferDrawKey(key, mKeyboard, canvas, mPaint, params, isManualTemporaryUpperCase); 456 canvas.translate(-keyDrawX, -keyDrawY); 457 } 458 } 459 460 // Overlay a dark rectangle to dim the keyboard 461 if (needsToDimKeyboard()) { 462 mPaint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); 463 canvas.drawRect(0, 0, width, height, mPaint); 464 } 465 466 mInvalidatedKey = null; 467 mDirtyRect.setEmpty(); 468 } 469 470 protected boolean needsToDimKeyboard() { 471 return false; 472 } 473 474 private static void onBufferDrawKey(final Key key, final Keyboard keyboard, final Canvas canvas, 475 Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) { 476 final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG; 477 // Draw key background. 478 final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight 479 + params.mPadding.left + params.mPadding.right; 480 final int bgHeight = key.mHeight + params.mPadding.top + params.mPadding.bottom; 481 final int bgX = -params.mPadding.left; 482 final int bgY = -params.mPadding.top; 483 final int[] drawableState = key.getCurrentDrawableState(); 484 final Drawable background = params.mKeyBackground; 485 background.setState(drawableState); 486 final Rect bounds = background.getBounds(); 487 if (bgWidth != bounds.right || bgHeight != bounds.bottom) { 488 background.setBounds(0, 0, bgWidth, bgHeight); 489 } 490 canvas.translate(bgX, bgY); 491 background.draw(canvas); 492 if (debugShowAlign) { 493 drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint()); 494 } 495 canvas.translate(-bgX, -bgY); 496 497 // Draw key top visuals. 498 final int keyWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 499 final int keyHeight = key.mHeight; 500 final float centerX = keyWidth * 0.5f; 501 final float centerY = keyHeight * 0.5f; 502 503 if (debugShowAlign) { 504 drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint()); 505 } 506 507 // Draw key label. 508 float positionX = centerX; 509 if (key.mLabel != null) { 510 // Switch the character to uppercase if shift is pressed 511 final CharSequence label = keyboard.adjustLabelCase(key.mLabel); 512 // For characters, use large font. For labels like "Done", use smaller font. 513 paint.setTypeface(key.selectTypeface(params.mKeyTextStyle)); 514 final int labelSize = key.selectTextSize(params.mKeyLetterSize, 515 params.mKeyLargeLetterSize, params.mKeyLabelSize, params.mKeyHintLabelSize); 516 paint.setTextSize(labelSize); 517 final float labelCharHeight = getCharHeight(paint); 518 final float labelCharWidth = getCharWidth(paint); 519 520 // Vertical label text alignment. 521 final float baseline = centerY + labelCharHeight / 2; 522 523 // Horizontal label text alignment 524 if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_LEFT) != 0) { 525 positionX = (int)params.mKeyLabelHorizontalPadding; 526 paint.setTextAlign(Align.LEFT); 527 } else if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_RIGHT) != 0) { 528 positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding; 529 paint.setTextAlign(Align.RIGHT); 530 } else if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0) { 531 // TODO: Parameterise this? 532 positionX = centerX - labelCharWidth * 7 / 4; 533 paint.setTextAlign(Align.LEFT); 534 } else { 535 positionX = centerX; 536 paint.setTextAlign(Align.CENTER); 537 } 538 539 if (key.hasUppercaseLetter() && isManualTemporaryUpperCase) { 540 paint.setColor(params.mKeyTextInactivatedColor); 541 } else { 542 paint.setColor(params.mKeyTextColor); 543 } 544 if (key.isEnabled()) { 545 // Set a drop shadow for the text 546 paint.setShadowLayer(params.mShadowRadius, 0, 0, params.mShadowColor); 547 } else { 548 // Make label invisible 549 paint.setColor(Color.TRANSPARENT); 550 } 551 canvas.drawText(label, 0, label.length(), positionX, baseline, paint); 552 // Turn off drop shadow 553 paint.setShadowLayer(0, 0, 0, 0); 554 555 if (debugShowAlign) { 556 final Paint line = new Paint(); 557 drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line); 558 drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line); 559 } 560 } 561 562 // Draw hint label. 563 if (key.mHintLabel != null) { 564 final CharSequence hint = key.mHintLabel; 565 final int hintColor; 566 final int hintSize; 567 if (key.hasHintLabel()) { 568 hintColor = params.mKeyHintLabelColor; 569 hintSize = params.mKeyHintLabelSize; 570 paint.setTypeface(Typeface.DEFAULT); 571 } else if (key.hasUppercaseLetter()) { 572 hintColor = isManualTemporaryUpperCase 573 ? params.mKeyUppercaseLetterActivatedColor 574 : params.mKeyUppercaseLetterInactivatedColor; 575 hintSize = params.mKeyUppercaseLetterSize; 576 } else { // key.hasHintLetter() 577 hintColor = params.mKeyHintLetterColor; 578 hintSize = params.mKeyHintLetterSize; 579 } 580 paint.setColor(hintColor); 581 paint.setTextSize(hintSize); 582 final float hintCharWidth = getCharWidth(paint); 583 final float hintX, hintY; 584 if (key.hasHintLabel()) { 585 // TODO: Generalize the following calculations. 586 hintX = positionX + hintCharWidth * 2; 587 hintY = centerY + getCharHeight(paint) / 2; 588 paint.setTextAlign(Align.LEFT); 589 } else if (key.hasUppercaseLetter()) { 590 hintX = keyWidth - params.mKeyUppercaseLetterPadding - hintCharWidth / 2; 591 hintY = -paint.ascent() + params.mKeyUppercaseLetterPadding; 592 paint.setTextAlign(Align.CENTER); 593 } else { // key.hasHintLetter() 594 hintX = keyWidth - params.mKeyHintLetterPadding - hintCharWidth / 2; 595 hintY = -paint.ascent() + params.mKeyHintLetterPadding; 596 paint.setTextAlign(Align.CENTER); 597 } 598 canvas.drawText(hint, 0, hint.length(), hintX, hintY, paint); 599 600 if (debugShowAlign) { 601 final Paint line = new Paint(); 602 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); 603 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); 604 } 605 } 606 607 // Draw key icon. 608 final Drawable icon = key.getIcon(); 609 if (key.mLabel == null && icon != null) { 610 final int iconWidth = icon.getIntrinsicWidth(); 611 final int iconHeight = icon.getIntrinsicHeight(); 612 final int iconX, alignX; 613 final int iconY = (keyHeight - iconHeight) / 2; 614 if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_LEFT) != 0) { 615 iconX = (int)params.mKeyLabelHorizontalPadding; 616 alignX = iconX; 617 } else if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_RIGHT) != 0) { 618 iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth; 619 alignX = iconX + iconWidth; 620 } else { // Align center 621 iconX = (keyWidth - iconWidth) / 2; 622 alignX = iconX + iconWidth / 2; 623 } 624 drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); 625 626 if (debugShowAlign) { 627 final Paint line = new Paint(); 628 drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line); 629 drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line); 630 } 631 } 632 633 // Draw popup hint "..." at the bottom right corner of the key. 634 if (key.hasPopupHint() && key.mPopupCharacters != null && key.mPopupCharacters.length > 0) { 635 paint.setTextSize(params.mKeyHintLetterSize); 636 paint.setColor(params.mKeyHintLabelColor); 637 paint.setTextAlign(Align.CENTER); 638 final float hintX = keyWidth - params.mKeyHintLetterPadding - getCharWidth(paint) / 2; 639 final float hintY = keyHeight - params.mKeyHintLetterPadding; 640 canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint); 641 642 if (debugShowAlign) { 643 final Paint line = new Paint(); 644 drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); 645 drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); 646 } 647 } 648 } 649 650 // This method is currently being used only by MiniKeyboardBuilder 651 public int getDefaultLabelSizeAndSetPaint(Paint paint) { 652 // For characters, use large font. For labels like "Done", use small font. 653 final int labelSize = mKeyDrawParams.mKeyLabelSize; 654 paint.setTextSize(labelSize); 655 paint.setTypeface(mKeyDrawParams.mKeyTextStyle); 656 return labelSize; 657 } 658 659 private static final Rect sTextBounds = new Rect(); 660 661 private static float getCharHeight(Paint paint) { 662 final int labelSize = (int)paint.getTextSize(); 663 final Float cachedValue = sTextHeightCache.get(labelSize); 664 if (cachedValue != null) 665 return cachedValue; 666 667 paint.getTextBounds(KEY_LABEL_REFERENCE_CHAR, 0, 1, sTextBounds); 668 final float height = sTextBounds.height(); 669 sTextHeightCache.put(labelSize, height); 670 return height; 671 } 672 673 private static float getCharWidth(Paint paint) { 674 final int labelSize = (int)paint.getTextSize(); 675 final Typeface face = paint.getTypeface(); 676 final Integer key; 677 if (face == Typeface.DEFAULT) { 678 key = labelSize; 679 } else if (face == Typeface.DEFAULT_BOLD) { 680 key = labelSize + 1000; 681 } else if (face == Typeface.MONOSPACE) { 682 key = labelSize + 2000; 683 } else { 684 key = labelSize; 685 } 686 687 final Float cached = sTextWidthCache.get(key); 688 if (cached != null) 689 return cached; 690 691 paint.getTextBounds(KEY_LABEL_REFERENCE_CHAR, 0, 1, sTextBounds); 692 final float width = sTextBounds.width(); 693 sTextWidthCache.put(key, width); 694 return width; 695 } 696 697 private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width, 698 int height) { 699 canvas.translate(x, y); 700 icon.setBounds(0, 0, width, height); 701 icon.draw(canvas); 702 canvas.translate(-x, -y); 703 } 704 705 private static void drawHorizontalLine(Canvas canvas, float y, float w, int color, Paint paint) { 706 paint.setStyle(Paint.Style.STROKE); 707 paint.setStrokeWidth(1.0f); 708 paint.setColor(color); 709 canvas.drawLine(0, y, w, y, paint); 710 } 711 712 private static void drawVerticalLine(Canvas canvas, float x, float h, int color, Paint paint) { 713 paint.setStyle(Paint.Style.STROKE); 714 paint.setStrokeWidth(1.0f); 715 paint.setColor(color); 716 canvas.drawLine(x, 0, x, h, paint); 717 } 718 719 private static void drawRectangle(Canvas canvas, float x, float y, float w, float h, int color, 720 Paint paint) { 721 paint.setStyle(Paint.Style.STROKE); 722 paint.setStrokeWidth(1.0f); 723 paint.setColor(color); 724 canvas.translate(x, y); 725 canvas.drawRect(0, 0, w, h, paint); 726 canvas.translate(-x, -y); 727 } 728 729 public void cancelAllMessages() { 730 mDrawingHandler.cancelAllMessages(); 731 } 732 733 // Called by {@link PointerTracker} constructor to create a TextView. 734 @Override 735 public TextView inflateKeyPreviewText() { 736 final Context context = getContext(); 737 if (mKeyPreviewLayoutId != 0) { 738 return (TextView)LayoutInflater.from(context).inflate(mKeyPreviewLayoutId, null); 739 } else { 740 return new TextView(context); 741 } 742 } 743 744 @Override 745 public void showKeyPreview(int keyIndex, PointerTracker tracker) { 746 if (mShowKeyPreviewPopup) { 747 mDrawingHandler.showKeyPreview(mDelayBeforePreview, keyIndex, tracker); 748 } 749 } 750 751 @Override 752 public void cancelShowKeyPreview(PointerTracker tracker) { 753 mDrawingHandler.cancelShowKeyPreview(tracker); 754 } 755 756 @Override 757 public void dismissKeyPreview(PointerTracker tracker) { 758 mDrawingHandler.cancelShowKeyPreview(tracker); 759 mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker); 760 } 761 762 private void addKeyPreview(TextView keyPreview) { 763 if (mPreviewPlacer == null) { 764 mPreviewPlacer = FrameLayoutCompatUtils.getPlacer( 765 (ViewGroup)getRootView().findViewById(android.R.id.content)); 766 } 767 final ViewGroup placer = mPreviewPlacer; 768 placer.addView(keyPreview, FrameLayoutCompatUtils.newLayoutParam(placer, 0, 0)); 769 } 770 771 // TODO: Introduce minimum duration for displaying key previews 772 private void showKey(final int keyIndex, PointerTracker tracker) { 773 final TextView previewText = tracker.getKeyPreviewText(); 774 // If the key preview has no parent view yet, add it to the ViewGroup which can place 775 // key preview absolutely in SoftInputWindow. 776 if (previewText.getParent() == null) { 777 addKeyPreview(previewText); 778 } 779 780 mDrawingHandler.cancelDismissKeyPreview(tracker); 781 final Key key = tracker.getKey(keyIndex); 782 // If keyIndex is invalid or IME is already closed, we must not show key preview. 783 // Trying to show key preview while root window is closed causes 784 // WindowManager.BadTokenException. 785 if (key == null) 786 return; 787 788 final KeyPreviewDrawParams params = mKeyPreviewDrawParams; 789 final int keyDrawX = key.mX + key.mVisualInsetsLeft; 790 final int keyDrawWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight; 791 // What we show as preview should match what we show on key top in onBufferDraw(). 792 if (key.mLabel != null) { 793 // TODO Should take care of temporaryShiftLabel here. 794 previewText.setCompoundDrawables(null, null, null, null); 795 if (key.mLabel.length() > 1) { 796 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mKeyLetterSize); 797 previewText.setTypeface(Typeface.DEFAULT_BOLD); 798 } else { 799 previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize); 800 previewText.setTypeface(params.mKeyTextStyle); 801 } 802 previewText.setText(mKeyboard.adjustLabelCase(key.mLabel)); 803 } else { 804 final Drawable previewIcon = key.getPreviewIcon(); 805 previewText.setCompoundDrawables(null, null, null, 806 previewIcon != null ? previewIcon : key.getIcon()); 807 previewText.setText(null); 808 } 809 previewText.setBackgroundDrawable(params.mPreviewBackground); 810 811 previewText.measure(MEASURESPEC_UNSPECIFIED, MEASURESPEC_UNSPECIFIED); 812 final int previewWidth = Math.max(previewText.getMeasuredWidth(), keyDrawWidth 813 + previewText.getPaddingLeft() + previewText.getPaddingRight()); 814 final int previewHeight = params.mPreviewHeight; 815 getLocationInWindow(params.mCoordinates); 816 int previewX = keyDrawX - (previewWidth - keyDrawWidth) / 2 + params.mCoordinates[0]; 817 final int previewY = key.mY - previewHeight 818 + params.mCoordinates[1] + params.mPreviewOffset; 819 if (previewX < 0 && params.mPreviewLeftBackground != null) { 820 previewText.setBackgroundDrawable(params.mPreviewLeftBackground); 821 previewX = 0; 822 } else if (previewX + previewWidth > getWidth() && params.mPreviewRightBackground != null) { 823 previewText.setBackgroundDrawable(params.mPreviewRightBackground); 824 previewX = getWidth() - previewWidth; 825 } 826 827 // Set the preview background state 828 previewText.getBackground().setState( 829 key.mPopupCharacters != null ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); 830 previewText.setTextColor(params.mPreviewTextColor); 831 FrameLayoutCompatUtils.placeViewAt( 832 previewText, previewX, previewY, previewWidth, previewHeight); 833 previewText.setVisibility(VISIBLE); 834 } 835 836 /** 837 * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient 838 * because the keyboard renders the keys to an off-screen buffer and an invalidate() only 839 * draws the cached buffer. 840 * @see #invalidateKey(Key) 841 */ 842 public void invalidateAllKeys() { 843 mDirtyRect.union(0, 0, getWidth(), getHeight()); 844 mBufferNeedsUpdate = true; 845 invalidate(); 846 } 847 848 /** 849 * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only 850 * one key is changing it's content. Any changes that affect the position or size of the key 851 * may not be honored. 852 * @param key key in the attached {@link Keyboard}. 853 * @see #invalidateAllKeys 854 */ 855 @Override 856 public void invalidateKey(Key key) { 857 if (key == null) 858 return; 859 mInvalidatedKey = key; 860 final int x = key.mX + getPaddingLeft(); 861 final int y = key.mY + getPaddingTop(); 862 mInvalidatedKeyRect.set(x, y, x + key.mWidth, y + key.mHeight); 863 mDirtyRect.union(mInvalidatedKeyRect); 864 mBufferNeedsUpdate = true; 865 invalidate(mInvalidatedKeyRect); 866 } 867 868 public void closing() { 869 PointerTracker.dismissAllKeyPreviews(); 870 cancelAllMessages(); 871 872 mDirtyRect.union(0, 0, getWidth(), getHeight()); 873 requestLayout(); 874 } 875 876 @Override 877 public boolean dismissPopupPanel() { 878 return false; 879 } 880 881 public void purgeKeyboardAndClosing() { 882 mKeyboard = null; 883 closing(); 884 } 885 886 @Override 887 public void onDetachedFromWindow() { 888 super.onDetachedFromWindow(); 889 closing(); 890 } 891} 892