/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher2; import com.android.launcher.R; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Bitmap.Config; import android.graphics.PorterDuff.Mode; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; // This class caches the drawing of this View's children in a bitmap when the scale factor // falls below a certain size. Only used by CellLayout, but in a separate class to keep cache // logic separate from the other logic in CellLayout public class CachedViewGroup extends ViewGroup implements VisibilityChangedListener { static final String TAG = "CachedViewGroup"; private Bitmap mCache; private Canvas mCacheCanvas; private Rect mCacheRect; private Paint mCachePaint; private boolean mIsCacheEnabled = true; private boolean mDisableCacheUpdates = false; private boolean mForceCacheUpdate = false; private boolean isUpdatingCache = false; private boolean mIsCacheDirty = true; private float mBitmapCacheScale; private float mMaxScaleForUsingBitmapCache; private Rect mBackgroundRect; public CachedViewGroup(Context context) { super(context); init(); } public CachedViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mBackgroundRect = new Rect(); mCacheRect = new Rect(); final Resources res = getResources(); mBitmapCacheScale = res.getInteger(R.integer.config_workspaceScreenBitmapCacheScale) / 100.0f; mMaxScaleForUsingBitmapCache = res.getInteger(R.integer.config_maxScaleForUsingWorkspaceScreenBitmapCache) / 100.0f; mCacheCanvas = new Canvas(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // sub-classes (namely CellLayout) will need to implement this prepareCacheBitmap(); invalidateCache(); } private void invalidateIfNeeded() { if (mIsCacheDirty) { // Force a redraw to update the cache if it's dirty invalidate(); } } public void enableCache() { mIsCacheEnabled = true; invalidateIfNeeded(); } public void disableCache() { mIsCacheEnabled = false; } public void disableCacheUpdates() { mDisableCacheUpdates = true; // Force just one update before we enter a period of no cache updates mForceCacheUpdate = true; } public void enableCacheUpdates() { mDisableCacheUpdates = false; invalidateIfNeeded(); } private void invalidateCache() { mIsCacheDirty = true; invalidate(); } public void receiveVisibilityChangedMessage(View v) { invalidateCache(); } private void prepareCacheBitmap() { if (mCache == null) { mCache = Bitmap.createBitmap((int) (getWidth() * mBitmapCacheScale), (int) (getHeight() * mBitmapCacheScale), Config.ARGB_8888); mCachePaint = new Paint(); mCachePaint.setFilterBitmap(true); mCacheCanvas.setBitmap(mCache); mCacheCanvas.scale(mBitmapCacheScale, mBitmapCacheScale); } } public void updateCache() { mCacheCanvas.drawColor(0, Mode.CLEAR); float alpha = getAlpha(); setAlpha(1.0f); isUpdatingCache = true; drawChildren(mCacheCanvas); isUpdatingCache = false; setAlpha(alpha); mIsCacheDirty = false; } public void drawChildren(Canvas canvas) { super.dispatchDraw(canvas); } @Override public void removeAllViews() { super.removeAllViews(); invalidateCache(); } @Override public void removeAllViewsInLayout() { super.removeAllViewsInLayout(); invalidateCache(); } @Override public void removeView(View view) { super.removeView(view); invalidateCache(); } @Override public void removeViewAt(int index) { super.removeViewAt(index); invalidateCache(); } @Override public void removeViewInLayout(View view) { super.removeViewInLayout(view); invalidateCache(); } @Override public void removeViews(int start, int count) { super.removeViews(start, count); invalidateCache(); } @Override public void removeViewsInLayout(int start, int count) { super.removeViewsInLayout(start, count); invalidateCache(); } @Override public void dispatchDraw(Canvas canvas) { final int count = getChildCount(); boolean useBitmapCache = false; if (!isUpdatingCache) { if (!mIsCacheDirty) { // Check if one of the children (an icon or widget) is dirty for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.isDirty()) { mIsCacheDirty = true; break; } } } useBitmapCache = mIsCacheEnabled && getScaleX() < mMaxScaleForUsingBitmapCache; if (mForceCacheUpdate || (useBitmapCache && !mDisableCacheUpdates)) { // Sometimes we force a cache update-- this is used to make sure the cache will look as // up-to-date as possible right when we disable cache updates if (mIsCacheDirty) { updateCache(); } mForceCacheUpdate = false; } } if (useBitmapCache) { mCachePaint.setAlpha((int)(255*getAlpha())); canvas.drawBitmap(mCache, mCacheRect, mBackgroundRect, mCachePaint); } else { super.dispatchDraw(canvas); } } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); // invalidate the cache to have it reflect the new item invalidateCache(); if (child instanceof VisibilityChangedBroadcaster) { VisibilityChangedBroadcaster v = (VisibilityChangedBroadcaster) child; v.setVisibilityChangedListener(this); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mBackgroundRect.set(0, 0, w, h); mCacheRect.set(0, 0, (int) (mBitmapCacheScale * w), (int) (mBitmapCacheScale * h)); mCache = null; prepareCacheBitmap(); invalidateCache(); } } //Custom interfaces used to listen to "visibility changed" events of *children* of Views. Avoided //using "onVisibilityChanged" in the names because there's a method of that name in framework //(which can only can be used to listen to ancestors' "visibility changed" events) interface VisibilityChangedBroadcaster { public void setVisibilityChangedListener(VisibilityChangedListener listener); } interface VisibilityChangedListener { public void receiveVisibilityChangedMessage(View v); }