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