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