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