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