• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.keyboard;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Color;
24 import android.graphics.Paint;
25 import android.graphics.Paint.Align;
26 import android.graphics.PorterDuff;
27 import android.graphics.Rect;
28 import android.graphics.Region;
29 import android.graphics.Typeface;
30 import android.graphics.drawable.Drawable;
31 import android.graphics.drawable.NinePatchDrawable;
32 import android.text.TextUtils;
33 import android.util.AttributeSet;
34 import android.view.View;
35 
36 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
37 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
38 import com.android.inputmethod.latin.Constants;
39 import com.android.inputmethod.latin.R;
40 import com.android.inputmethod.latin.utils.TypefaceUtils;
41 
42 import java.util.HashSet;
43 
44 /**
45  * A view that renders a virtual {@link Keyboard}.
46  *
47  * @attr ref R.styleable#KeyboardView_keyBackground
48  * @attr ref R.styleable#KeyboardView_functionalKeyBackground
49  * @attr ref R.styleable#KeyboardView_spacebarBackground
50  * @attr ref R.styleable#KeyboardView_spacebarIconWidthRatio
51  * @attr ref R.styleable#Keyboard_Key_keyLabelFlags
52  * @attr ref R.styleable#KeyboardView_keyHintLetterPadding
53  * @attr ref R.styleable#KeyboardView_keyPopupHintLetter
54  * @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
55  * @attr ref R.styleable#KeyboardView_keyShiftedLetterHintPadding
56  * @attr ref R.styleable#KeyboardView_keyTextShadowRadius
57  * @attr ref R.styleable#KeyboardView_verticalCorrection
58  * @attr ref R.styleable#Keyboard_Key_keyTypeface
59  * @attr ref R.styleable#Keyboard_Key_keyLetterSize
60  * @attr ref R.styleable#Keyboard_Key_keyLabelSize
61  * @attr ref R.styleable#Keyboard_Key_keyLargeLetterRatio
62  * @attr ref R.styleable#Keyboard_Key_keyLargeLabelRatio
63  * @attr ref R.styleable#Keyboard_Key_keyHintLetterRatio
64  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintRatio
65  * @attr ref R.styleable#Keyboard_Key_keyHintLabelRatio
66  * @attr ref R.styleable#Keyboard_Key_keyLabelOffCenterRatio
67  * @attr ref R.styleable#Keyboard_Key_keyHintLabelOffCenterRatio
68  * @attr ref R.styleable#Keyboard_Key_keyPreviewTextRatio
69  * @attr ref R.styleable#Keyboard_Key_keyTextColor
70  * @attr ref R.styleable#Keyboard_Key_keyTextColorDisabled
71  * @attr ref R.styleable#Keyboard_Key_keyTextShadowColor
72  * @attr ref R.styleable#Keyboard_Key_keyHintLetterColor
73  * @attr ref R.styleable#Keyboard_Key_keyHintLabelColor
74  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintInactivatedColor
75  * @attr ref R.styleable#Keyboard_Key_keyShiftedLetterHintActivatedColor
76  * @attr ref R.styleable#Keyboard_Key_keyPreviewTextColor
77  */
78 public class KeyboardView extends View {
79     // XML attributes
80     private final KeyVisualAttributes mKeyVisualAttributes;
81     // Default keyLabelFlags from {@link KeyboardTheme}.
82     // Currently only "alignHintLabelToBottom" is supported.
83     private final int mDefaultKeyLabelFlags;
84     private final float mKeyHintLetterPadding;
85     private final String mKeyPopupHintLetter;
86     private final float mKeyPopupHintLetterPadding;
87     private final float mKeyShiftedLetterHintPadding;
88     private final float mKeyTextShadowRadius;
89     private final float mVerticalCorrection;
90     private final Drawable mKeyBackground;
91     private final Drawable mFunctionalKeyBackground;
92     private final Drawable mSpacebarBackground;
93     private final float mSpacebarIconWidthRatio;
94     private final Rect mKeyBackgroundPadding = new Rect();
95     private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
96 
97     // The maximum key label width in the proportion to the key width.
98     private static final float MAX_LABEL_RATIO = 0.90f;
99 
100     // Main keyboard
101     private Keyboard mKeyboard;
102     protected final KeyDrawParams mKeyDrawParams = new KeyDrawParams();
103 
104     // Drawing
105     /** True if all keys should be drawn */
106     private boolean mInvalidateAllKeys;
107     /** The keys that should be drawn */
108     private final HashSet<Key> mInvalidatedKeys = new HashSet<>();
109     /** The working rectangle variable */
110     private final Rect mWorkingRect = new Rect();
111     /** The keyboard bitmap buffer for faster updates */
112     /** The clip region to draw keys */
113     private final Region mClipRegion = new Region();
114     private Bitmap mOffscreenBuffer;
115     /** The canvas for the above mutable keyboard bitmap */
116     private final Canvas mOffscreenCanvas = new Canvas();
117     private final Paint mPaint = new Paint();
118     private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
KeyboardView(final Context context, final AttributeSet attrs)119     public KeyboardView(final Context context, final AttributeSet attrs) {
120         this(context, attrs, R.attr.keyboardViewStyle);
121     }
122 
KeyboardView(final Context context, final AttributeSet attrs, final int defStyle)123     public KeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
124         super(context, attrs, defStyle);
125 
126         final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
127                 R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
128         mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
129         mKeyBackground.getPadding(mKeyBackgroundPadding);
130         final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable(
131                 R.styleable.KeyboardView_functionalKeyBackground);
132         mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground
133                 : mKeyBackground;
134         final Drawable spacebarBackground = keyboardViewAttr.getDrawable(
135                 R.styleable.KeyboardView_spacebarBackground);
136         mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground : mKeyBackground;
137         mSpacebarIconWidthRatio = keyboardViewAttr.getFloat(
138                 R.styleable.KeyboardView_spacebarIconWidthRatio, 1.0f);
139         mKeyHintLetterPadding = keyboardViewAttr.getDimension(
140                 R.styleable.KeyboardView_keyHintLetterPadding, 0.0f);
141         mKeyPopupHintLetter = keyboardViewAttr.getString(
142                 R.styleable.KeyboardView_keyPopupHintLetter);
143         mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
144                 R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f);
145         mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
146                 R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
147         mKeyTextShadowRadius = keyboardViewAttr.getFloat(
148                 R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED);
149         mVerticalCorrection = keyboardViewAttr.getDimension(
150                 R.styleable.KeyboardView_verticalCorrection, 0.0f);
151         keyboardViewAttr.recycle();
152 
153         final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
154                 R.styleable.Keyboard_Key, defStyle, R.style.KeyboardView);
155         mDefaultKeyLabelFlags = keyAttr.getInt(R.styleable.Keyboard_Key_keyLabelFlags, 0);
156         mKeyVisualAttributes = KeyVisualAttributes.newInstance(keyAttr);
157         keyAttr.recycle();
158 
159         mPaint.setAntiAlias(true);
160     }
161 
getKeyVisualAttribute()162     public KeyVisualAttributes getKeyVisualAttribute() {
163         return mKeyVisualAttributes;
164     }
165 
blendAlpha(final Paint paint, final int alpha)166     private static void blendAlpha(final Paint paint, final int alpha) {
167         final int color = paint.getColor();
168         paint.setARGB((paint.getAlpha() * alpha) / Constants.Color.ALPHA_OPAQUE,
169                 Color.red(color), Color.green(color), Color.blue(color));
170     }
171 
setHardwareAcceleratedDrawingEnabled(final boolean enabled)172     public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
173         if (!enabled) return;
174         // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
175         setLayerType(LAYER_TYPE_HARDWARE, null);
176     }
177 
178     /**
179      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
180      * view will re-layout itself to accommodate the keyboard.
181      * @see Keyboard
182      * @see #getKeyboard()
183      * @param keyboard the keyboard to display in this view
184      */
setKeyboard(final Keyboard keyboard)185     public void setKeyboard(final Keyboard keyboard) {
186         mKeyboard = keyboard;
187         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
188         mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
189         mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes);
190         invalidateAllKeys();
191         requestLayout();
192     }
193 
194     /**
195      * Returns the current keyboard being displayed by this view.
196      * @return the currently attached keyboard
197      * @see #setKeyboard(Keyboard)
198      */
getKeyboard()199     public Keyboard getKeyboard() {
200         return mKeyboard;
201     }
202 
getVerticalCorrection()203     protected float getVerticalCorrection() {
204         return mVerticalCorrection;
205     }
206 
updateKeyDrawParams(final int keyHeight)207     protected void updateKeyDrawParams(final int keyHeight) {
208         mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
209     }
210 
211     @Override
onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)212     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
213         if (mKeyboard == null) {
214             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
215             return;
216         }
217         // The main keyboard expands to the entire this {@link KeyboardView}.
218         final int width = mKeyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
219         final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
220         setMeasuredDimension(width, height);
221     }
222 
223     @Override
onDraw(final Canvas canvas)224     protected void onDraw(final Canvas canvas) {
225         super.onDraw(canvas);
226         if (canvas.isHardwareAccelerated()) {
227             onDrawKeyboard(canvas);
228             return;
229         }
230 
231         final boolean bufferNeedsUpdates = mInvalidateAllKeys || !mInvalidatedKeys.isEmpty();
232         if (bufferNeedsUpdates || mOffscreenBuffer == null) {
233             if (maybeAllocateOffscreenBuffer()) {
234                 mInvalidateAllKeys = true;
235                 // TODO: Stop using the offscreen canvas even when in software rendering
236                 mOffscreenCanvas.setBitmap(mOffscreenBuffer);
237             }
238             onDrawKeyboard(mOffscreenCanvas);
239         }
240         canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null);
241     }
242 
maybeAllocateOffscreenBuffer()243     private boolean maybeAllocateOffscreenBuffer() {
244         final int width = getWidth();
245         final int height = getHeight();
246         if (width == 0 || height == 0) {
247             return false;
248         }
249         if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == width
250                 && mOffscreenBuffer.getHeight() == height) {
251             return false;
252         }
253         freeOffscreenBuffer();
254         mOffscreenBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
255         return true;
256     }
257 
freeOffscreenBuffer()258     private void freeOffscreenBuffer() {
259         mOffscreenCanvas.setBitmap(null);
260         mOffscreenCanvas.setMatrix(null);
261         if (mOffscreenBuffer != null) {
262             mOffscreenBuffer.recycle();
263             mOffscreenBuffer = null;
264         }
265     }
266 
onDrawKeyboard(final Canvas canvas)267     private void onDrawKeyboard(final Canvas canvas) {
268         if (mKeyboard == null) return;
269 
270         final int width = getWidth();
271         final int height = getHeight();
272         final Paint paint = mPaint;
273 
274         // Calculate clip region and set.
275         final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
276         final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
277         // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
278         if (drawAllKeys || isHardwareAccelerated) {
279             mClipRegion.set(0, 0, width, height);
280         } else {
281             mClipRegion.setEmpty();
282             for (final Key key : mInvalidatedKeys) {
283                 if (mKeyboard.hasKey(key)) {
284                     final int x = key.getX() + getPaddingLeft();
285                     final int y = key.getY() + getPaddingTop();
286                     mWorkingRect.set(x, y, x + key.getWidth(), y + key.getHeight());
287                     mClipRegion.union(mWorkingRect);
288                 }
289             }
290         }
291         if (!isHardwareAccelerated) {
292             canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
293             // Draw keyboard background.
294             canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
295             final Drawable background = getBackground();
296             if (background != null) {
297                 background.draw(canvas);
298             }
299         }
300 
301         // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
302         if (drawAllKeys || isHardwareAccelerated) {
303             // Draw all keys.
304             for (final Key key : mKeyboard.getSortedKeys()) {
305                 onDrawKey(key, canvas, paint);
306             }
307         } else {
308             // Draw invalidated keys.
309             for (final Key key : mInvalidatedKeys) {
310                 if (mKeyboard.hasKey(key)) {
311                     onDrawKey(key, canvas, paint);
312                 }
313             }
314         }
315 
316         mInvalidatedKeys.clear();
317         mInvalidateAllKeys = false;
318     }
319 
onDrawKey(final Key key, final Canvas canvas, final Paint paint)320     private void onDrawKey(final Key key, final Canvas canvas, final Paint paint) {
321         final int keyDrawX = key.getDrawX() + getPaddingLeft();
322         final int keyDrawY = key.getY() + getPaddingTop();
323         canvas.translate(keyDrawX, keyDrawY);
324 
325         final int keyHeight = mKeyboard.mMostCommonKeyHeight - mKeyboard.mVerticalGap;
326         final KeyVisualAttributes attr = key.getVisualAttributes();
327         final KeyDrawParams params = mKeyDrawParams.mayCloneAndUpdateParams(keyHeight, attr);
328         params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
329 
330         if (!key.isSpacer()) {
331             final Drawable background = key.selectBackgroundDrawable(
332                     mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground);
333             onDrawKeyBackground(key, canvas, background);
334         }
335         onDrawKeyTopVisuals(key, canvas, paint, params);
336 
337         canvas.translate(-keyDrawX, -keyDrawY);
338     }
339 
340     // Draw key background.
onDrawKeyBackground(final Key key, final Canvas canvas, final Drawable background)341     protected void onDrawKeyBackground(final Key key, final Canvas canvas,
342             final Drawable background) {
343         final int keyWidth = key.getDrawWidth();
344         final int keyHeight = key.getHeight();
345         final int bgWidth, bgHeight, bgX, bgY;
346         if (key.needsToKeepBackgroundAspectRatio(mDefaultKeyLabelFlags)
347                 // HACK: To disable expanding normal/functional key background.
348                 && !key.hasCustomActionLabel()) {
349             final int intrinsicWidth = background.getIntrinsicWidth();
350             final int intrinsicHeight = background.getIntrinsicHeight();
351             final float minScale = Math.min(
352                     keyWidth / (float)intrinsicWidth, keyHeight / (float)intrinsicHeight);
353             bgWidth = (int)(intrinsicWidth * minScale);
354             bgHeight = (int)(intrinsicHeight * minScale);
355             bgX = (keyWidth - bgWidth) / 2;
356             bgY = (keyHeight - bgHeight) / 2;
357         } else {
358             final Rect padding = mKeyBackgroundPadding;
359             bgWidth = keyWidth + padding.left + padding.right;
360             bgHeight = keyHeight + padding.top + padding.bottom;
361             bgX = -padding.left;
362             bgY = -padding.top;
363         }
364         final Rect bounds = background.getBounds();
365         if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
366             background.setBounds(0, 0, bgWidth, bgHeight);
367         }
368         canvas.translate(bgX, bgY);
369         background.draw(canvas);
370         canvas.translate(-bgX, -bgY);
371     }
372 
373     // Draw key top visuals.
onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)374     protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
375             final KeyDrawParams params) {
376         final int keyWidth = key.getDrawWidth();
377         final int keyHeight = key.getHeight();
378         final float centerX = keyWidth * 0.5f;
379         final float centerY = keyHeight * 0.5f;
380 
381         // Draw key label.
382         final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha);
383         float labelX = centerX;
384         float labelBaseline = centerY;
385         final String label = key.getLabel();
386         if (label != null) {
387             paint.setTypeface(key.selectTypeface(params));
388             paint.setTextSize(key.selectTextSize(params));
389             final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
390             final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
391 
392             // Vertical label text alignment.
393             labelBaseline = centerY + labelCharHeight / 2.0f;
394 
395             // Horizontal label text alignment
396             if (key.isAlignLabelOffCenter()) {
397                 // The label is placed off center of the key. Used mainly on "phone number" layout.
398                 labelX = centerX + params.mLabelOffCenterRatio * labelCharWidth;
399                 paint.setTextAlign(Align.LEFT);
400             } else {
401                 labelX = centerX;
402                 paint.setTextAlign(Align.CENTER);
403             }
404             if (key.needsAutoXScale()) {
405                 final float ratio = Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) /
406                         TypefaceUtils.getStringWidth(label, paint));
407                 if (key.needsAutoScale()) {
408                     final float autoSize = paint.getTextSize() * ratio;
409                     paint.setTextSize(autoSize);
410                 } else {
411                     paint.setTextScaleX(ratio);
412                 }
413             }
414 
415             if (key.isEnabled()) {
416                 paint.setColor(key.selectTextColor(params));
417                 // Set a drop shadow for the text if the shadow radius is positive value.
418                 if (mKeyTextShadowRadius > 0.0f) {
419                     paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
420                 } else {
421                     paint.clearShadowLayer();
422                 }
423             } else {
424                 // Make label invisible
425                 paint.setColor(Color.TRANSPARENT);
426                 paint.clearShadowLayer();
427             }
428             blendAlpha(paint, params.mAnimAlpha);
429             canvas.drawText(label, 0, label.length(), labelX, labelBaseline, paint);
430             // Turn off drop shadow and reset x-scale.
431             paint.clearShadowLayer();
432             paint.setTextScaleX(1.0f);
433         }
434 
435         // Draw hint label.
436         final String hintLabel = key.getHintLabel();
437         if (hintLabel != null) {
438             paint.setTextSize(key.selectHintTextSize(params));
439             paint.setColor(key.selectHintTextColor(params));
440             // TODO: Should add a way to specify type face for hint letters
441             paint.setTypeface(Typeface.DEFAULT_BOLD);
442             blendAlpha(paint, params.mAnimAlpha);
443             final float labelCharHeight = TypefaceUtils.getReferenceCharHeight(paint);
444             final float labelCharWidth = TypefaceUtils.getReferenceCharWidth(paint);
445             final float hintX, hintBaseline;
446             if (key.hasHintLabel()) {
447                 // The hint label is placed just right of the key label. Used mainly on
448                 // "phone number" layout.
449                 hintX = labelX + params.mHintLabelOffCenterRatio * labelCharWidth;
450                 if (key.isAlignHintLabelToBottom(mDefaultKeyLabelFlags)) {
451                     hintBaseline = labelBaseline;
452                 } else {
453                     hintBaseline = centerY + labelCharHeight / 2.0f;
454                 }
455                 paint.setTextAlign(Align.LEFT);
456             } else if (key.hasShiftedLetterHint()) {
457                 // The hint label is placed at top-right corner of the key. Used mainly on tablet.
458                 hintX = keyWidth - mKeyShiftedLetterHintPadding - labelCharWidth / 2.0f;
459                 paint.getFontMetrics(mFontMetrics);
460                 hintBaseline = -mFontMetrics.top;
461                 paint.setTextAlign(Align.CENTER);
462             } else { // key.hasHintLetter()
463                 // The hint letter is placed at top-right corner of the key. Used mainly on phone.
464                 final float hintDigitWidth = TypefaceUtils.getReferenceDigitWidth(paint);
465                 final float hintLabelWidth = TypefaceUtils.getStringWidth(hintLabel, paint);
466                 hintX = keyWidth - mKeyHintLetterPadding
467                         - Math.max(hintDigitWidth, hintLabelWidth) / 2.0f;
468                 hintBaseline = -paint.ascent();
469                 paint.setTextAlign(Align.CENTER);
470             }
471             final float adjustmentY = params.mHintLabelVerticalAdjustment * labelCharHeight;
472             canvas.drawText(
473                     hintLabel, 0, hintLabel.length(), hintX, hintBaseline + adjustmentY, paint);
474         }
475 
476         // Draw key icon.
477         if (label == null && icon != null) {
478             final int iconWidth;
479             if (key.getCode() == Constants.CODE_SPACE && icon instanceof NinePatchDrawable) {
480                 iconWidth = (int)(keyWidth * mSpacebarIconWidthRatio);
481             } else {
482                 iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth);
483             }
484             final int iconHeight = icon.getIntrinsicHeight();
485             final int iconY;
486             if (key.isAlignIconToBottom()) {
487                 iconY = keyHeight - iconHeight;
488             } else {
489                 iconY = (keyHeight - iconHeight) / 2; // Align vertically center.
490             }
491             final int iconX = (keyWidth - iconWidth) / 2; // Align horizontally center.
492             drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
493         }
494 
495         if (key.hasPopupHint() && key.getMoreKeys() != null) {
496             drawKeyPopupHint(key, canvas, paint, params);
497         }
498     }
499 
500     // Draw popup hint "..." at the bottom right corner of the key.
drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint, final KeyDrawParams params)501     protected void drawKeyPopupHint(final Key key, final Canvas canvas, final Paint paint,
502             final KeyDrawParams params) {
503         if (TextUtils.isEmpty(mKeyPopupHintLetter)) {
504             return;
505         }
506         final int keyWidth = key.getDrawWidth();
507         final int keyHeight = key.getHeight();
508 
509         paint.setTypeface(params.mTypeface);
510         paint.setTextSize(params.mHintLetterSize);
511         paint.setColor(params.mHintLabelColor);
512         paint.setTextAlign(Align.CENTER);
513         final float hintX = keyWidth - mKeyHintLetterPadding
514                 - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f;
515         final float hintY = keyHeight - mKeyPopupHintLetterPadding;
516         canvas.drawText(mKeyPopupHintLetter, hintX, hintY, paint);
517     }
518 
drawIcon(final Canvas canvas, final Drawable icon, final int x, final int y, final int width, final int height)519     protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
520             final int y, final int width, final int height) {
521         canvas.translate(x, y);
522         icon.setBounds(0, 0, width, height);
523         icon.draw(canvas);
524         canvas.translate(-x, -y);
525     }
526 
newLabelPaint(final Key key)527     public Paint newLabelPaint(final Key key) {
528         final Paint paint = new Paint();
529         paint.setAntiAlias(true);
530         if (key == null) {
531             paint.setTypeface(mKeyDrawParams.mTypeface);
532             paint.setTextSize(mKeyDrawParams.mLabelSize);
533         } else {
534             paint.setColor(key.selectTextColor(mKeyDrawParams));
535             paint.setTypeface(key.selectTypeface(mKeyDrawParams));
536             paint.setTextSize(key.selectTextSize(mKeyDrawParams));
537         }
538         return paint;
539     }
540 
541     /**
542      * Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
543      * because the keyboard renders the keys to an off-screen buffer and an invalidate() only
544      * draws the cached buffer.
545      * @see #invalidateKey(Key)
546      */
invalidateAllKeys()547     public void invalidateAllKeys() {
548         mInvalidatedKeys.clear();
549         mInvalidateAllKeys = true;
550         invalidate();
551     }
552 
553     /**
554      * Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
555      * one key is changing it's content. Any changes that affect the position or size of the key
556      * may not be honored.
557      * @param key key in the attached {@link Keyboard}.
558      * @see #invalidateAllKeys
559      */
invalidateKey(final Key key)560     public void invalidateKey(final Key key) {
561         if (mInvalidateAllKeys) return;
562         if (key == null) return;
563         mInvalidatedKeys.add(key);
564         final int x = key.getX() + getPaddingLeft();
565         final int y = key.getY() + getPaddingTop();
566         invalidate(x, y, x + key.getWidth(), y + key.getHeight());
567     }
568 
569     @Override
onDetachedFromWindow()570     protected void onDetachedFromWindow() {
571         super.onDetachedFromWindow();
572         freeOffscreenBuffer();
573     }
574 
deallocateMemory()575     public void deallocateMemory() {
576         freeOffscreenBuffer();
577     }
578 }
579