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