container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
container.put(mID, state);
}
}
}
/**
* Hook allowing a view to generate a representation of its internal state
* that can later be used to create a new instance with that same state.
* This state should only contain information that is not persistent or can
* not be reconstructed later. For example, you will never store your
* current position on screen because that will be computed again when a
* new instance of the view is placed in its view hierarchy.
*
* Some examples of things you may store here: the current cursor position
* in a text view (but usually not the text itself since that is stored in a
* content provider or other persistent storage), the currently selected
* item in a list view.
*
* @return Returns a Parcelable object containing the view's current dynamic
* state, or null if there is nothing interesting to save. The
* default implementation returns null.
* @see #onRestoreInstanceState(android.os.Parcelable)
* @see #saveHierarchyState(android.util.SparseArray)
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #setSaveEnabled(boolean)
*/
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= SAVE_STATE_CALLED;
return BaseSavedState.EMPTY_STATE;
}
/**
* Restore this view hierarchy's frozen state from the given container.
*
* @param container The SparseArray which holds previously frozen states.
*
* @see #saveHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
public void restoreHierarchyState(SparseArray container) {
dispatchRestoreInstanceState(container);
}
/**
* Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
* state for this view and its children. May be overridden to modify how restoring
* happens to a view's children; for example, some views may want to not store state
* for their children.
*
* @param container The SparseArray which holds previously saved state.
*
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
protected void dispatchRestoreInstanceState(SparseArray container) {
if (mID != NO_ID) {
Parcelable state = container.get(mID);
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~SAVE_STATE_CALLED;
onRestoreInstanceState(state);
if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
}
/**
* Hook allowing a view to re-apply a representation of its internal state that had previously
* been generated by {@link #onSaveInstanceState}. This function will never be called with a
* null state.
*
* @param state The frozen state that had previously been returned by
* {@link #onSaveInstanceState}.
*
* @see #onSaveInstanceState()
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
*/
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= SAVE_STATE_CALLED;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
+ "when two views of different type have the same id in the same hierarchy. "
+ "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ "other views do not use the same id.");
}
}
/**
* Return the time at which the drawing of the view hierarchy started.
*
* @return the drawing start time in milliseconds
*/
public long getDrawingTime() {
return mAttachInfo != null ? mAttachInfo.mDrawingTime : 0;
}
/**
* Enables or disables the duplication of the parent's state into this view. When
* duplication is enabled, this view gets its drawable state from its parent rather
* than from its own internal properties.
*
* Note: in the current implementation, setting this property to true after the
* view was added to a ViewGroup might have no effect at all. This property should
* always be used from XML or set to true before adding this view to a ViewGroup.
*
* Note: if this view's parent addStateFromChildren property is enabled and this
* property is enabled, an exception will be thrown.
*
* Note: if the child view uses and updates additionnal states which are unknown to the
* parent, these states should not be affected by this method.
*
* @param enabled True to enable duplication of the parent's drawable state, false
* to disable it.
*
* @see #getDrawableState()
* @see #isDuplicateParentStateEnabled()
*/
public void setDuplicateParentStateEnabled(boolean enabled) {
setFlags(enabled ? DUPLICATE_PARENT_STATE : 0, DUPLICATE_PARENT_STATE);
}
/**
* Indicates whether this duplicates its drawable state from its parent.
*
* @return True if this view's drawable state is duplicated from the parent,
* false otherwise
*
* @see #getDrawableState()
* @see #setDuplicateParentStateEnabled(boolean)
*/
public boolean isDuplicateParentStateEnabled() {
return (mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE;
}
/**
* Specifies the type of layer backing this view. The layer can be
* {@link #LAYER_TYPE_NONE disabled}, {@link #LAYER_TYPE_SOFTWARE software} or
* {@link #LAYER_TYPE_HARDWARE hardware}.
*
* A layer is associated with an optional {@link android.graphics.Paint}
* instance that controls how the layer is composed on screen. The following
* properties of the paint are taken into account when composing the layer:
*
* {@link android.graphics.Paint#getAlpha() Translucency (alpha)}
* {@link android.graphics.Paint#getXfermode() Blending mode}
* {@link android.graphics.Paint#getColorFilter() Color filter}
*
*
* If this view has an alpha value set to < 1.0 by calling
* {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by
* this view's alpha value. Calling {@link #setAlpha(float)} is therefore
* equivalent to setting a hardware layer on this view and providing a paint with
* the desired alpha value.
*
*
Refer to the documentation of {@link #LAYER_TYPE_NONE disabled},
* {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware}
* for more information on when and how to use layers.
*
* @param layerType The ype of layer to use with this view, must be one of
* {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
* {@link #LAYER_TYPE_HARDWARE}
* @param paint The paint used to compose the layer. This argument is optional
* and can be null. It is ignored when the layer type is
* {@link #LAYER_TYPE_NONE}
*
* @see #getLayerType()
* @see #LAYER_TYPE_NONE
* @see #LAYER_TYPE_SOFTWARE
* @see #LAYER_TYPE_HARDWARE
* @see #setAlpha(float)
*
* @attr ref android.R.styleable#View_layerType
*/
public void setLayerType(int layerType, Paint paint) {
if (layerType < LAYER_TYPE_NONE || layerType > LAYER_TYPE_HARDWARE) {
throw new IllegalArgumentException("Layer type can only be one of: LAYER_TYPE_NONE, "
+ "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
}
if (layerType == mLayerType) {
if (layerType != LAYER_TYPE_NONE && paint != mLayerPaint) {
mLayerPaint = paint == null ? new Paint() : paint;
invalidateParentCaches();
invalidate(true);
}
return;
}
// Destroy any previous software drawing cache if needed
switch (mLayerType) {
case LAYER_TYPE_HARDWARE:
destroyLayer();
// fall through - non-accelerated views may use software layer mechanism instead
case LAYER_TYPE_SOFTWARE:
destroyDrawingCache();
break;
default:
break;
}
mLayerType = layerType;
final boolean layerDisabled = mLayerType == LAYER_TYPE_NONE;
mLayerPaint = layerDisabled ? null : (paint == null ? new Paint() : paint);
mLocalDirtyRect = layerDisabled ? null : new Rect();
invalidateParentCaches();
invalidate(true);
}
/**
* Indicates whether this view has a static layer. A view with layer type
* {@link #LAYER_TYPE_NONE} is a static layer. Other types of layers are
* dynamic.
*/
boolean hasStaticLayer() {
return mLayerType == LAYER_TYPE_NONE;
}
/**
* Indicates what type of layer is currently associated with this view. By default
* a view does not have a layer, and the layer type is {@link #LAYER_TYPE_NONE}.
* Refer to the documentation of {@link #setLayerType(int, android.graphics.Paint)}
* for more information on the different types of layers.
*
* @return {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
* {@link #LAYER_TYPE_HARDWARE}
*
* @see #setLayerType(int, android.graphics.Paint)
* @see #buildLayer()
* @see #LAYER_TYPE_NONE
* @see #LAYER_TYPE_SOFTWARE
* @see #LAYER_TYPE_HARDWARE
*/
public int getLayerType() {
return mLayerType;
}
/**
* Forces this view's layer to be created and this view to be rendered
* into its layer. If this view's layer type is set to {@link #LAYER_TYPE_NONE},
* invoking this method will have no effect.
*
* This method can for instance be used to render a view into its layer before
* starting an animation. If this view is complex, rendering into the layer
* before starting the animation will avoid skipping frames.
*
* @throws IllegalStateException If this view is not attached to a window
*
* @see #setLayerType(int, android.graphics.Paint)
*/
public void buildLayer() {
if (mLayerType == LAYER_TYPE_NONE) return;
if (mAttachInfo == null) {
throw new IllegalStateException("This view must be attached to a window first");
}
switch (mLayerType) {
case LAYER_TYPE_HARDWARE:
if (mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.validate()) {
getHardwareLayer();
}
break;
case LAYER_TYPE_SOFTWARE:
buildDrawingCache(true);
break;
}
}
// Make sure the HardwareRenderer.validate() was invoked before calling this method
void flushLayer() {
if (mLayerType == LAYER_TYPE_HARDWARE && mHardwareLayer != null) {
mHardwareLayer.flush();
}
}
/**
* Returns a hardware layer that can be used to draw this view again
* without executing its draw method.
*
* @return A HardwareLayer ready to render, or null if an error occurred.
*/
HardwareLayer getHardwareLayer() {
if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null ||
!mAttachInfo.mHardwareRenderer.isEnabled()) {
return null;
}
if (!mAttachInfo.mHardwareRenderer.validate()) return null;
final int width = mRight - mLeft;
final int height = mBottom - mTop;
if (width == 0 || height == 0) {
return null;
}
if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
if (mHardwareLayer == null) {
mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
width, height, isOpaque());
mLocalDirtyRect.setEmpty();
} else if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
mHardwareLayer.resize(width, height);
mLocalDirtyRect.setEmpty();
}
// The layer is not valid if the underlying GPU resources cannot be allocated
if (!mHardwareLayer.isValid()) {
return null;
}
HardwareCanvas currentCanvas = mAttachInfo.mHardwareCanvas;
final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas);
// Make sure all the GPU resources have been properly allocated
if (canvas == null) {
mHardwareLayer.end(currentCanvas);
return null;
}
mAttachInfo.mHardwareCanvas = canvas;
try {
canvas.setViewport(width, height);
canvas.onPreDraw(mLocalDirtyRect);
mLocalDirtyRect.setEmpty();
final int restoreCount = canvas.save();
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
mPrivateFlags &= ~DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
canvas.restoreToCount(restoreCount);
} finally {
canvas.onPostDraw();
mHardwareLayer.end(currentCanvas);
mAttachInfo.mHardwareCanvas = currentCanvas;
}
}
return mHardwareLayer;
}
/**
* Destroys this View's hardware layer if possible.
*
* @return True if the layer was destroyed, false otherwise.
*
* @see #setLayerType(int, android.graphics.Paint)
* @see #LAYER_TYPE_HARDWARE
*/
boolean destroyLayer() {
if (mHardwareLayer != null) {
AttachInfo info = mAttachInfo;
if (info != null && info.mHardwareRenderer != null &&
info.mHardwareRenderer.isEnabled() && info.mHardwareRenderer.validate()) {
mHardwareLayer.destroy();
mHardwareLayer = null;
invalidate(true);
invalidateParentCaches();
}
return true;
}
return false;
}
/**
* Destroys all hardware rendering resources. This method is invoked
* when the system needs to reclaim resources. Upon execution of this
* method, you should free any OpenGL resources created by the view.
*
* Note: you must call
* super.destroyHardwareResources()
when overriding
* this method.
*
* @hide
*/
protected void destroyHardwareResources() {
destroyLayer();
}
/**
* Enables or disables the drawing cache. When the drawing cache is enabled, the next call
* to {@link #getDrawingCache()} or {@link #buildDrawingCache()} will draw the view in a
* bitmap. Calling {@link #draw(android.graphics.Canvas)} will not draw from the cache when
* the cache is enabled. To benefit from the cache, you must request the drawing cache by
* calling {@link #getDrawingCache()} and draw it on screen if the returned bitmap is not
* null.
*
* Enabling the drawing cache is similar to
* {@link #setLayerType(int, android.graphics.Paint) setting a layer} when hardware
* acceleration is turned off. When hardware acceleration is turned on, enabling the
* drawing cache has no effect on rendering because the system uses a different mechanism
* for acceleration which ignores the flag. If you want to use a Bitmap for the view, even
* when hardware acceleration is enabled, see {@link #setLayerType(int, android.graphics.Paint)}
* for information on how to enable software and hardware layers.
*
* This API can be used to manually generate
* a bitmap copy of this view, by setting the flag to true
and calling
* {@link #getDrawingCache()}.
*
* @param enabled true to enable the drawing cache, false otherwise
*
* @see #isDrawingCacheEnabled()
* @see #getDrawingCache()
* @see #buildDrawingCache()
* @see #setLayerType(int, android.graphics.Paint)
*/
public void setDrawingCacheEnabled(boolean enabled) {
mCachingFailed = false;
setFlags(enabled ? DRAWING_CACHE_ENABLED : 0, DRAWING_CACHE_ENABLED);
}
/**
* Indicates whether the drawing cache is enabled for this view.
*
* @return true if the drawing cache is enabled
*
* @see #setDrawingCacheEnabled(boolean)
* @see #getDrawingCache()
*/
@ViewDebug.ExportedProperty(category = "drawing")
public boolean isDrawingCacheEnabled() {
return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
}
/**
* Debugging utility which recursively outputs the dirty state of a view and its
* descendants.
*
* @hide
*/
@SuppressWarnings({"UnusedDeclaration"})
public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.DIRTY_MASK) +
") DRAWN(" + (mPrivateFlags & DRAWN) + ")" + " CACHE_VALID(" +
(mPrivateFlags & View.DRAWING_CACHE_VALID) +
") INVALIDATED(" + (mPrivateFlags & INVALIDATED) + ")");
if (clear) {
mPrivateFlags &= clearMask;
}
if (this instanceof ViewGroup) {
ViewGroup parent = (ViewGroup) this;
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
final View child = parent.getChildAt(i);
child.outputDirtyFlags(indent + " ", clear, clearMask);
}
}
}
/**
* This method is used by ViewGroup to cause its children to restore or recreate their
* display lists. It is called by getDisplayList() when the parent ViewGroup does not need
* to recreate its own display list, which would happen if it went through the normal
* draw/dispatchDraw mechanisms.
*
* @hide
*/
protected void dispatchGetDisplayList() {}
/**
* A view that is not attached or hardware accelerated cannot create a display list.
* This method checks these conditions and returns the appropriate result.
*
* @return true if view has the ability to create a display list, false otherwise.
*
* @hide
*/
public boolean canHaveDisplayList() {
return !(mAttachInfo == null || mAttachInfo.mHardwareRenderer == null);
}
/**
* @return The HardwareRenderer associated with that view or null if hardware rendering
* is not supported or this this has not been attached to a window.
*
* @hide
*/
public HardwareRenderer getHardwareRenderer() {
if (mAttachInfo != null) {
return mAttachInfo.mHardwareRenderer;
}
return null;
}
/**
* Returns a display list that can be used to draw this view again
* without executing its draw method.
*
* @return A DisplayList ready to replay, or null if caching is not enabled.
*
* @hide
*/
public DisplayList getDisplayList() {
if (!canHaveDisplayList()) {
return null;
}
if (((mPrivateFlags & DRAWING_CACHE_VALID) == 0 ||
mDisplayList == null || !mDisplayList.isValid() ||
mRecreateDisplayList)) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
if (mDisplayList != null && mDisplayList.isValid() &&
!mRecreateDisplayList) {
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
mPrivateFlags &= ~DIRTY_MASK;
dispatchGetDisplayList();
return mDisplayList;
}
// If we got here, we're recreating it. Mark it as such to ensure that
// we copy in child display lists into ours in drawChild()
mRecreateDisplayList = true;
if (mDisplayList == null) {
final String name = getClass().getSimpleName();
mDisplayList = mAttachInfo.mHardwareRenderer.createDisplayList(name);
// If we're creating a new display list, make sure our parent gets invalidated
// since they will need to recreate their display list to account for this
// new child display list.
invalidateParentCaches();
}
final HardwareCanvas canvas = mDisplayList.start();
int restoreCount = 0;
try {
int width = mRight - mLeft;
int height = mBottom - mTop;
canvas.setViewport(width, height);
// The dirty rect should always be null for a display list
canvas.onPreDraw(null);
computeScroll();
restoreCount = canvas.save();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
mPrivateFlags &= ~DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
dispatchDraw(canvas);
} else {
draw(canvas);
}
} finally {
canvas.restoreToCount(restoreCount);
canvas.onPostDraw();
mDisplayList.end();
}
} else {
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
mPrivateFlags &= ~DIRTY_MASK;
}
return mDisplayList;
}
/**
* Calling this method is equivalent to calling getDrawingCache(false)
.
*
* @return A non-scaled bitmap representing this view or null if cache is disabled.
*
* @see #getDrawingCache(boolean)
*/
public Bitmap getDrawingCache() {
return getDrawingCache(false);
}
/**
* Returns the bitmap in which this view drawing is cached. The returned bitmap
* is null when caching is disabled. If caching is enabled and the cache is not ready,
* this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not
* draw from the cache when the cache is enabled. To benefit from the cache, you must
* request the drawing cache by calling this method and draw it on screen if the
* returned bitmap is not null.
*
* Note about auto scaling in compatibility mode: When auto scaling is not enabled,
* this method will create a bitmap of the same size as this view. Because this bitmap
* will be drawn scaled by the parent ViewGroup, the result on screen might show
* scaling artifacts. To avoid such artifacts, you should call this method by setting
* the auto scaling to true. Doing so, however, will generate a bitmap of a different
* size than the view. This implies that your application must be able to handle this
* size.
*
* @param autoScale Indicates whether the generated bitmap should be scaled based on
* the current density of the screen when the application is in compatibility
* mode.
*
* @return A bitmap representing this view or null if cache is disabled.
*
* @see #setDrawingCacheEnabled(boolean)
* @see #isDrawingCacheEnabled()
* @see #buildDrawingCache(boolean)
* @see #destroyDrawingCache()
*/
public Bitmap getDrawingCache(boolean autoScale) {
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
}
if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
buildDrawingCache(autoScale);
}
return autoScale ? mDrawingCache : mUnscaledDrawingCache;
}
/**
* Frees the resources used by the drawing cache. If you call
* {@link #buildDrawingCache()} manually without calling
* {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
* should cleanup the cache with this method afterwards.
*
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
* @see #getDrawingCache()
*/
public void destroyDrawingCache() {
if (mDrawingCache != null) {
mDrawingCache.recycle();
mDrawingCache = null;
}
if (mUnscaledDrawingCache != null) {
mUnscaledDrawingCache.recycle();
mUnscaledDrawingCache = null;
}
}
/**
* Setting a solid background color for the drawing cache's bitmaps will improve
* performance and memory usage. Note, though that this should only be used if this
* view will always be drawn on top of a solid color.
*
* @param color The background color to use for the drawing cache's bitmap
*
* @see #setDrawingCacheEnabled(boolean)
* @see #buildDrawingCache()
* @see #getDrawingCache()
*/
public void setDrawingCacheBackgroundColor(int color) {
if (color != mDrawingCacheBackgroundColor) {
mDrawingCacheBackgroundColor = color;
mPrivateFlags &= ~DRAWING_CACHE_VALID;
}
}
/**
* @see #setDrawingCacheBackgroundColor(int)
*
* @return The background color to used for the drawing cache's bitmap
*/
public int getDrawingCacheBackgroundColor() {
return mDrawingCacheBackgroundColor;
}
/**
* Calling this method is equivalent to calling buildDrawingCache(false)
.
*
* @see #buildDrawingCache(boolean)
*/
public void buildDrawingCache() {
buildDrawingCache(false);
}
/**
* Forces the drawing cache to be built if the drawing cache is invalid.
*
* If you call {@link #buildDrawingCache()} manually without calling
* {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
* should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.
*
* Note about auto scaling in compatibility mode: When auto scaling is not enabled,
* this method will create a bitmap of the same size as this view. Because this bitmap
* will be drawn scaled by the parent ViewGroup, the result on screen might show
* scaling artifacts. To avoid such artifacts, you should call this method by setting
* the auto scaling to true. Doing so, however, will generate a bitmap of a different
* size than the view. This implies that your application must be able to handle this
* size.
*
* You should avoid calling this method when hardware acceleration is enabled. If
* you do not need the drawing cache bitmap, calling this method will increase memory
* usage and cause the view to be rendered in software once, thus negatively impacting
* performance.
*
* @see #getDrawingCache()
* @see #destroyDrawingCache()
*/
public void buildDrawingCache(boolean autoScale) {
if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
mCachingFailed = false;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
}
int width = mRight - mLeft;
int height = mBottom - mTop;
final AttachInfo attachInfo = mAttachInfo;
final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
if (autoScale && scalingRequired) {
width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
}
final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
if (width <= 0 || height <= 0 ||
// Projected bitmap size in bytes
(width * height * (opaque && !use32BitCache ? 2 : 4) >
ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
destroyDrawingCache();
mCachingFailed = true;
return;
}
boolean clear = true;
Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;
if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
Bitmap.Config quality;
if (!opaque) {
// Never pick ARGB_4444 because it looks awful
// Keep the DRAWING_CACHE_QUALITY_LOW flag just in case
switch (mViewFlags & DRAWING_CACHE_QUALITY_MASK) {
case DRAWING_CACHE_QUALITY_AUTO:
quality = Bitmap.Config.ARGB_8888;
break;
case DRAWING_CACHE_QUALITY_LOW:
quality = Bitmap.Config.ARGB_8888;
break;
case DRAWING_CACHE_QUALITY_HIGH:
quality = Bitmap.Config.ARGB_8888;
break;
default:
quality = Bitmap.Config.ARGB_8888;
break;
}
} else {
// Optimization for translucent windows
// If the window is translucent, use a 32 bits bitmap to benefit from memcpy()
quality = use32BitCache ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
}
// Try to cleanup memory
if (bitmap != null) bitmap.recycle();
try {
bitmap = Bitmap.createBitmap(width, height, quality);
bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
if (autoScale) {
mDrawingCache = bitmap;
} else {
mUnscaledDrawingCache = bitmap;
}
if (opaque && use32BitCache) bitmap.setHasAlpha(false);
} catch (OutOfMemoryError e) {
// If there is not enough memory to create the bitmap cache, just
// ignore the issue as bitmap caches are not required to draw the
// view hierarchy
if (autoScale) {
mDrawingCache = null;
} else {
mUnscaledDrawingCache = null;
}
mCachingFailed = true;
return;
}
clear = drawingCacheBackgroundColor != 0;
}
Canvas canvas;
if (attachInfo != null) {
canvas = attachInfo.mCanvas;
if (canvas == null) {
canvas = new Canvas();
}
canvas.setBitmap(bitmap);
// Temporarily clobber the cached Canvas in case one of our children
// is also using a drawing cache. Without this, the children would
// steal the canvas by attaching their own bitmap to it and bad, bad
// thing would happen (invisible views, corrupted drawings, etc.)
attachInfo.mCanvas = null;
} else {
// This case should hopefully never or seldom happen
canvas = new Canvas(bitmap);
}
if (clear) {
bitmap.eraseColor(drawingCacheBackgroundColor);
}
computeScroll();
final int restoreCount = canvas.save();
if (autoScale && scalingRequired) {
final float scale = attachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= DRAWN;
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= DRAWING_CACHE_VALID;
}
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
mPrivateFlags &= ~DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);
if (attachInfo != null) {
// Restore the cached Canvas for our siblings
attachInfo.mCanvas = canvas;
}
}
}
/**
* Create a snapshot of the view into a bitmap. We should probably make
* some form of this public, but should think about the API.
*/
Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) {
int width = mRight - mLeft;
int height = mBottom - mTop;
final AttachInfo attachInfo = mAttachInfo;
final float scale = attachInfo != null ? attachInfo.mApplicationScale : 1.0f;
width = (int) ((width * scale) + 0.5f);
height = (int) ((height * scale) + 0.5f);
Bitmap bitmap = Bitmap.createBitmap(width > 0 ? width : 1, height > 0 ? height : 1, quality);
if (bitmap == null) {
throw new OutOfMemoryError();
}
Resources resources = getResources();
if (resources != null) {
bitmap.setDensity(resources.getDisplayMetrics().densityDpi);
}
Canvas canvas;
if (attachInfo != null) {
canvas = attachInfo.mCanvas;
if (canvas == null) {
canvas = new Canvas();
}
canvas.setBitmap(bitmap);
// Temporarily clobber the cached Canvas in case one of our children
// is also using a drawing cache. Without this, the children would
// steal the canvas by attaching their own bitmap to it and bad, bad
// things would happen (invisible views, corrupted drawings, etc.)
attachInfo.mCanvas = null;
} else {
// This case should hopefully never or seldom happen
canvas = new Canvas(bitmap);
}
if ((backgroundColor & 0xff000000) != 0) {
bitmap.eraseColor(backgroundColor);
}
computeScroll();
final int restoreCount = canvas.save();
canvas.scale(scale, scale);
canvas.translate(-mScrollX, -mScrollY);
// Temporarily remove the dirty mask
int flags = mPrivateFlags;
mPrivateFlags &= ~DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
dispatchDraw(canvas);
} else {
draw(canvas);
}
mPrivateFlags = flags;
canvas.restoreToCount(restoreCount);
canvas.setBitmap(null);
if (attachInfo != null) {
// Restore the cached Canvas for our siblings
attachInfo.mCanvas = canvas;
}
return bitmap;
}
/**
* Indicates whether this View is currently in edit mode. A View is usually
* in edit mode when displayed within a developer tool. For instance, if
* this View is being drawn by a visual user interface builder, this method
* should return true.
*
* Subclasses should check the return value of this method to provide
* different behaviors if their normal behavior might interfere with the
* host environment. For instance: the class spawns a thread in its
* constructor, the drawing code relies on device-specific features, etc.
*
* This method is usually checked in the drawing code of custom widgets.
*
* @return True if this View is in edit mode, false otherwise.
*/
public boolean isInEditMode() {
return false;
}
/**
* If the View draws content inside its padding and enables fading edges,
* it needs to support padding offsets. Padding offsets are added to the
* fading edges to extend the length of the fade so that it covers pixels
* drawn inside the padding.
*
* Subclasses of this class should override this method if they need
* to draw content inside the padding.
*
* @return True if padding offset must be applied, false otherwise.
*
* @see #getLeftPaddingOffset()
* @see #getRightPaddingOffset()
* @see #getTopPaddingOffset()
* @see #getBottomPaddingOffset()
*
* @since CURRENT
*/
protected boolean isPaddingOffsetRequired() {
return false;
}
/**
* Amount by which to extend the left fading region. Called only when
* {@link #isPaddingOffsetRequired()} returns true.
*
* @return The left padding offset in pixels.
*
* @see #isPaddingOffsetRequired()
*
* @since CURRENT
*/
protected int getLeftPaddingOffset() {
return 0;
}
/**
* Amount by which to extend the right fading region. Called only when
* {@link #isPaddingOffsetRequired()} returns true.
*
* @return The right padding offset in pixels.
*
* @see #isPaddingOffsetRequired()
*
* @since CURRENT
*/
protected int getRightPaddingOffset() {
return 0;
}
/**
* Amount by which to extend the top fading region. Called only when
* {@link #isPaddingOffsetRequired()} returns true.
*
* @return The top padding offset in pixels.
*
* @see #isPaddingOffsetRequired()
*
* @since CURRENT
*/
protected int getTopPaddingOffset() {
return 0;
}
/**
* Amount by which to extend the bottom fading region. Called only when
* {@link #isPaddingOffsetRequired()} returns true.
*
* @return The bottom padding offset in pixels.
*
* @see #isPaddingOffsetRequired()
*
* @since CURRENT
*/
protected int getBottomPaddingOffset() {
return 0;
}
/**
* @hide
* @param offsetRequired
*/
protected int getFadeTop(boolean offsetRequired) {
int top = mPaddingTop;
if (offsetRequired) top += getTopPaddingOffset();
return top;
}
/**
* @hide
* @param offsetRequired
*/
protected int getFadeHeight(boolean offsetRequired) {
int padding = mPaddingTop;
if (offsetRequired) padding += getTopPaddingOffset();
return mBottom - mTop - mPaddingBottom - padding;
}
/**
* Indicates whether this view is attached to an hardware accelerated
* window or not.
*
* Even if this method returns true, it does not mean that every call
* to {@link #draw(android.graphics.Canvas)} will be made with an hardware
* accelerated {@link android.graphics.Canvas}. For instance, if this view
* is drawn onto an offscren {@link android.graphics.Bitmap} and its
* window is hardware accelerated,
* {@link android.graphics.Canvas#isHardwareAccelerated()} will likely
* return false, and this method will return true.
*
* @return True if the view is attached to a window and the window is
* hardware accelerated; false in any other case.
*/
public boolean isHardwareAccelerated() {
return mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
}
/**
* Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
* case of an active Animation being run on the view.
*/
private boolean drawAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
a.initialize(mRight - mLeft, mBottom - mTop, getWidth(), getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
onAnimationStart();
}
boolean more = a.getTransformation(drawingTime, parent.mChildTransformation, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = parent.mChildTransformation;
}
if (more) {
if (!a.willChangeBounds()) {
if ((flags & (parent.FLAG_OPTIMIZE_INVALIDATE | parent.FLAG_ANIMATION_DONE)) ==
parent.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= parent.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & parent.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
* This draw() method is an implementation detail and is not intended to be overridden or
* to be called from anywhere else other than ViewGroup.drawChild().
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
final int flags = parent.mGroupFlags;
if ((flags & parent.FLAG_CLEAR_TRANSFORMATION) == parent.FLAG_CLEAR_TRANSFORMATION) {
parent.mChildTransformation.clear();
parent.mGroupFlags &= ~parent.FLAG_CLEAR_TRANSFORMATION;
}
Transformation transformToApply = null;
boolean concatMatrix = false;
boolean scalingRequired = false;
boolean caching;
int layerType = parent.mDrawLayers ? getLayerType() : LAYER_TYPE_NONE;
final boolean hardwareAccelerated = canvas.isHardwareAccelerated();
if ((flags & parent.FLAG_CHILDREN_DRAWN_WITH_CACHE) == parent.FLAG_CHILDREN_DRAWN_WITH_CACHE ||
(flags & parent.FLAG_ALWAYS_DRAWN_WITH_CACHE) == parent.FLAG_ALWAYS_DRAWN_WITH_CACHE) {
caching = true;
if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
} else {
caching = (layerType != LAYER_TYPE_NONE) || hardwareAccelerated;
}
final Animation a = getAnimation();
if (a != null) {
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
transformToApply = parent.mChildTransformation;
} else if ((flags & parent.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ==
parent.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) {
final boolean hasTransform =
parent.getChildStaticTransformation(this, parent.mChildTransformation);
if (hasTransform) {
final int transformType = parent.mChildTransformation.getTransformationType();
transformToApply = transformType != Transformation.TYPE_IDENTITY ?
parent.mChildTransformation : null;
concatMatrix = (transformType & Transformation.TYPE_MATRIX) != 0;
}
}
concatMatrix |= !childHasIdentityMatrix;
// Sets the flag as early as possible to allow draw() implementations
// to call invalidate() successfully when doing animations
mPrivateFlags |= DRAWN;
if (!concatMatrix && canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
(mPrivateFlags & DRAW_ANIMATION) == 0) {
return more;
}
if (hardwareAccelerated) {
// Clear INVALIDATED flag to allow invalidation to occur during rendering, but
// retain the flag's value temporarily in the mRecreateDisplayList flag
mRecreateDisplayList = (mPrivateFlags & INVALIDATED) == INVALIDATED;
mPrivateFlags &= ~INVALIDATED;
}
computeScroll();
final int sx = mScrollX;
final int sy = mScrollY;
DisplayList displayList = null;
Bitmap cache = null;
boolean hasDisplayList = false;
if (caching) {
if (!hardwareAccelerated) {
if (layerType != LAYER_TYPE_NONE) {
layerType = LAYER_TYPE_SOFTWARE;
buildDrawingCache(true);
}
cache = getDrawingCache(true);
} else {
switch (layerType) {
case LAYER_TYPE_SOFTWARE:
buildDrawingCache(true);
cache = getDrawingCache(true);
break;
case LAYER_TYPE_NONE:
// Delay getting the display list until animation-driven alpha values are
// set up and possibly passed on to the view
hasDisplayList = canHaveDisplayList();
break;
}
}
}
final boolean hasNoCache = cache == null || hasDisplayList;
final boolean offsetForScroll = cache == null && !hasDisplayList &&
layerType != LAYER_TYPE_HARDWARE;
final int restoreTo = canvas.save();
if (offsetForScroll) {
canvas.translate(mLeft - sx, mTop - sy);
} else {
canvas.translate(mLeft, mTop);
if (scalingRequired) {
// mAttachInfo cannot be null, otherwise scalingRequired == false
final float scale = 1.0f / mAttachInfo.mApplicationScale;
canvas.scale(scale, scale);
}
}
float alpha = getAlpha();
if (transformToApply != null || alpha < 1.0f || !hasIdentityMatrix()) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
if (transformToApply != null) {
if (concatMatrix) {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
parent.mGroupFlags |= parent.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1.0f) {
alpha *= transformToApply.getAlpha();
parent.mGroupFlags |= parent.FLAG_CLEAR_TRANSFORMATION;
}
}
if (!childHasIdentityMatrix) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
if (alpha < 1.0f) {
parent.mGroupFlags |= parent.FLAG_CLEAR_TRANSFORMATION;
if (hasNoCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!onSetAlpha(multipliedAlpha)) {
int layerFlags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if ((flags & parent.FLAG_CLIP_CHILDREN) == parent.FLAG_CLIP_CHILDREN ||
layerType != LAYER_TYPE_NONE) {
layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG;
}
if (layerType == LAYER_TYPE_NONE) {
final int scrollX = hasDisplayList ? 0 : sx;
final int scrollY = hasDisplayList ? 0 : sy;
canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft,
scrollY + mBottom - mTop, multipliedAlpha, layerFlags);
}
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
mPrivateFlags |= ALPHA_SET;
}
}
}
} else if ((mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
onSetAlpha(255);
mPrivateFlags &= ~ALPHA_SET;
}
if ((flags & parent.FLAG_CLIP_CHILDREN) == parent.FLAG_CLIP_CHILDREN) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
} else {
if (!scalingRequired || cache == null) {
canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
} else {
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
}
}
}
if (hasDisplayList) {
displayList = getDisplayList();
if (!displayList.isValid()) {
// Uncommon, but possible. If a view is removed from the hierarchy during the call
// to getDisplayList(), the display list will be marked invalid and we should not
// try to use it again.
displayList = null;
hasDisplayList = false;
}
}
if (hasNoCache) {
boolean layerRendered = false;
if (layerType == LAYER_TYPE_HARDWARE) {
final HardwareLayer layer = getHardwareLayer();
if (layer != null && layer.isValid()) {
mLayerPaint.setAlpha((int) (alpha * 255));
((HardwareCanvas) canvas).drawHardwareLayer(layer, 0, 0, mLayerPaint);
layerRendered = true;
} else {
final int scrollX = hasDisplayList ? 0 : sx;
final int scrollY = hasDisplayList ? 0 : sy;
canvas.saveLayer(scrollX, scrollY,
scrollX + mRight - mLeft, scrollY + mBottom - mTop, mLayerPaint,
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
}
}
if (!layerRendered) {
if (!hasDisplayList) {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(parent, ViewDebug.HierarchyTraceType.DRAW);
}
mPrivateFlags &= ~DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
} else {
mPrivateFlags &= ~DIRTY_MASK;
((HardwareCanvas) canvas).drawDisplayList(displayList,
mRight - mLeft, mBottom - mTop, null, flags);
}
}
} else if (cache != null) {
mPrivateFlags &= ~DIRTY_MASK;
Paint cachePaint;
if (layerType == LAYER_TYPE_NONE) {
cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
if (alpha < 1.0f) {
cachePaint.setAlpha((int) (alpha * 255));
parent.mGroupFlags |= parent.FLAG_ALPHA_LOWER_THAN_ONE;
} else if ((flags & parent.FLAG_ALPHA_LOWER_THAN_ONE) ==
parent.FLAG_ALPHA_LOWER_THAN_ONE) {
cachePaint.setAlpha(255);
parent.mGroupFlags &= ~parent.FLAG_ALPHA_LOWER_THAN_ONE;
}
} else {
cachePaint = mLayerPaint;
cachePaint.setAlpha((int) (alpha * 255));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
}
canvas.restoreToCount(restoreTo);
if (a != null && !more) {
if (!hardwareAccelerated && !a.getFillAfter()) {
onSetAlpha(255);
}
parent.finishAnimatingView(this, a);
}
if (more && hardwareAccelerated) {
// invalidation is the trigger to recreate display lists, so if we're using
// display lists to render, force an invalidate to allow the animation to
// continue drawing another frame
parent.invalidate(true);
if (a.hasAlpha() && (mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
// alpha animations should cause the child to recreate its display list
invalidate(true);
}
}
mRecreateDisplayList = false;
return more;
}
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
public void draw(Canvas canvas) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}
/**
* Override this if your view is known to always be drawn on top of a solid color background,
* and needs to draw fading edges. Returning a non-zero color enables the view system to
* optimize the drawing of the fading edges. If you do return a non-zero color, the alpha
* should be set to 0xFF.
*
* @see #setVerticalFadingEdgeEnabled(boolean)
* @see #setHorizontalFadingEdgeEnabled(boolean)
*
* @return The known solid color background for this view, or 0 if the color may vary
*/
@ViewDebug.ExportedProperty(category = "drawing")
public int getSolidColor() {
return 0;
}
/**
* Build a human readable string representation of the specified view flags.
*
* @param flags the view flags to convert to a string
* @return a String representing the supplied flags
*/
private static String printFlags(int flags) {
String output = "";
int numFlags = 0;
if ((flags & FOCUSABLE_MASK) == FOCUSABLE) {
output += "TAKES_FOCUS";
numFlags++;
}
switch (flags & VISIBILITY_MASK) {
case INVISIBLE:
if (numFlags > 0) {
output += " ";
}
output += "INVISIBLE";
// USELESS HERE numFlags++;
break;
case GONE:
if (numFlags > 0) {
output += " ";
}
output += "GONE";
// USELESS HERE numFlags++;
break;
default:
break;
}
return output;
}
/**
* Build a human readable string representation of the specified private
* view flags.
*
* @param privateFlags the private view flags to convert to a string
* @return a String representing the supplied flags
*/
private static String printPrivateFlags(int privateFlags) {
String output = "";
int numFlags = 0;
if ((privateFlags & WANTS_FOCUS) == WANTS_FOCUS) {
output += "WANTS_FOCUS";
numFlags++;
}
if ((privateFlags & FOCUSED) == FOCUSED) {
if (numFlags > 0) {
output += " ";
}
output += "FOCUSED";
numFlags++;
}
if ((privateFlags & SELECTED) == SELECTED) {
if (numFlags > 0) {
output += " ";
}
output += "SELECTED";
numFlags++;
}
if ((privateFlags & IS_ROOT_NAMESPACE) == IS_ROOT_NAMESPACE) {
if (numFlags > 0) {
output += " ";
}
output += "IS_ROOT_NAMESPACE";
numFlags++;
}
if ((privateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
if (numFlags > 0) {
output += " ";
}
output += "HAS_BOUNDS";
numFlags++;
}
if ((privateFlags & DRAWN) == DRAWN) {
if (numFlags > 0) {
output += " ";
}
output += "DRAWN";
// USELESS HERE numFlags++;
}
return output;
}
/**
* Indicates whether or not this view's layout will be requested during
* the next hierarchy layout pass.
*
* @return true if the layout will be forced during next layout pass
*/
public boolean isLayoutRequested() {
return (mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT;
}
/**
* Assign a size and position to a view and all of its
* descendants
*
* This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().
*
* Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList listenersCopy =
(ArrayList)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~FORCE_LAYOUT;
}
/**
* Called from layout when this view should
* assign a size and position to each of its children.
*
* Derived classes with children should override
* this method and call layout on each of
* their children.
* @param changed This is a new size or position for this view
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
/**
* Assign a size and position to this view.
*
* This is called from layout.
*
* @param left Left position, relative to parent
* @param top Top position, relative to parent
* @param right Right position, relative to parent
* @param bottom Bottom position, relative to parent
* @return true if the new size and position are different than the
* previous ones
* {@hide}
*/
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mPrivateFlags |= HAS_BOUNDS;
if (sizeChanged) {
if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
if (mTransformationInfo != null) {
mTransformationInfo.mMatrixDirty = true;
}
}
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
mPrivateFlags |= DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
}
return changed;
}
/**
* Finalize inflating a view from XML. This is called as the last phase
* of inflation, after all child views have been added.
*
* Even if the subclass overrides onFinishInflate, they should always be
* sure to call the super method, so that we get called.
*/
protected void onFinishInflate() {
}
/**
* Returns the resources associated with this view.
*
* @return Resources object.
*/
public Resources getResources() {
return mResources;
}
/**
* Invalidates the specified Drawable.
*
* @param drawable the drawable to invalidate
*/
public void invalidateDrawable(Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
}
}
/**
* Schedules an action on a drawable to occur at a specified time.
*
* @param who the recipient of the action
* @param what the action to run on the drawable
* @param when the time at which the action must occur. Uses the
* {@link SystemClock#uptimeMillis} timebase.
*/
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (verifyDrawable(who) && what != null) {
if (mAttachInfo != null) {
mAttachInfo.mHandler.postAtTime(what, who, when);
} else {
ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis());
}
}
}
/**
* Cancels a scheduled action on a drawable.
*
* @param who the recipient of the action
* @param what the action to cancel
*/
public void unscheduleDrawable(Drawable who, Runnable what) {
if (verifyDrawable(who) && what != null) {
if (mAttachInfo != null) {
mAttachInfo.mHandler.removeCallbacks(what, who);
} else {
ViewRootImpl.getRunQueue().removeCallbacks(what);
}
}
}
/**
* Unschedule any events associated with the given Drawable. This can be
* used when selecting a new Drawable into a view, so that the previous
* one is completely unscheduled.
*
* @param who The Drawable to unschedule.
*
* @see #drawableStateChanged
*/
public void unscheduleDrawable(Drawable who) {
if (mAttachInfo != null) {
mAttachInfo.mHandler.removeCallbacksAndMessages(who);
}
}
/**
* Return the layout direction of a given Drawable.
*
* @param who the Drawable to query
*
* @hide
*/
public int getResolvedLayoutDirection(Drawable who) {
return (who == mBGDrawable) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT;
}
/**
* If your view subclass is displaying its own Drawable objects, it should
* override this function and return true for any Drawable it is
* displaying. This allows animations for those drawables to be
* scheduled.
*
*
Be sure to call through to the super class when overriding this
* function.
*
* @param who The Drawable to verify. Return true if it is one you are
* displaying, else return the result of calling through to the
* super class.
*
* @return boolean If true than the Drawable is being displayed in the
* view; else false and it is not allowed to animate.
*
* @see #unscheduleDrawable(android.graphics.drawable.Drawable)
* @see #drawableStateChanged()
*/
protected boolean verifyDrawable(Drawable who) {
return who == mBGDrawable;
}
/**
* This function is called whenever the state of the view changes in such
* a way that it impacts the state of drawables being shown.
*
*
Be sure to call through to the superclass when overriding this
* function.
*
* @see Drawable#setState(int[])
*/
protected void drawableStateChanged() {
Drawable d = mBGDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
/**
* Call this to force a view to update its drawable state. This will cause
* drawableStateChanged to be called on this view. Views that are interested
* in the new state should call getDrawableState.
*
* @see #drawableStateChanged
* @see #getDrawableState
*/
public void refreshDrawableState() {
mPrivateFlags |= DRAWABLE_STATE_DIRTY;
drawableStateChanged();
ViewParent parent = mParent;
if (parent != null) {
parent.childDrawableStateChanged(this);
}
}
/**
* Return an array of resource IDs of the drawable states representing the
* current state of the view.
*
* @return The current drawable state
*
* @see Drawable#setState(int[])
* @see #drawableStateChanged()
* @see #onCreateDrawableState(int)
*/
public final int[] getDrawableState() {
if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
mDrawableState = onCreateDrawableState(0);
mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
/**
* Generate the new {@link android.graphics.drawable.Drawable} state for
* this view. This is called by the view
* system when the cached Drawable state is determined to be invalid. To
* retrieve the current state, you should use {@link #getDrawableState}.
*
* @param extraSpace if non-zero, this is the number of extra entries you
* would like in the returned array in which you can place your own
* states.
*
* @return Returns an array holding the current {@link Drawable} state of
* the view.
*
* @see #mergeDrawableStates(int[], int[])
*/
protected int[] onCreateDrawableState(int extraSpace) {
if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
mParent instanceof View) {
return ((View) mParent).onCreateDrawableState(extraSpace);
}
int[] drawableState;
int privateFlags = mPrivateFlags;
int viewStateIndex = 0;
if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
// process doesn't allow it. This is just to allow app preview
// windows to better match their app.
viewStateIndex |= VIEW_STATE_ACCELERATED;
}
if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
final int privateFlags2 = mPrivateFlags2;
if ((privateFlags2 & DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
if ((privateFlags2 & DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
drawableState = VIEW_STATE_SETS[viewStateIndex];
//noinspection ConstantIfStatement
if (false) {
Log.i("View", "drawableStateIndex=" + viewStateIndex);
Log.i("View", toString()
+ " pressed=" + ((privateFlags & PRESSED) != 0)
+ " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+ " fo=" + hasFocus()
+ " sl=" + ((privateFlags & SELECTED) != 0)
+ " wf=" + hasWindowFocus()
+ ": " + Arrays.toString(drawableState));
}
if (extraSpace == 0) {
return drawableState;
}
final int[] fullState;
if (drawableState != null) {
fullState = new int[drawableState.length + extraSpace];
System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
} else {
fullState = new int[extraSpace];
}
return fullState;
}
/**
* Merge your own state values in additionalState into the base
* state values baseState that were returned by
* {@link #onCreateDrawableState(int)}.
*
* @param baseState The base state values returned by
* {@link #onCreateDrawableState(int)}, which will be modified to also hold your
* own additional state values.
*
* @param additionalState The additional state values you would like
* added to baseState ; this array is not modified.
*
* @return As a convenience, the baseState array you originally
* passed into the function is returned.
*
* @see #onCreateDrawableState(int)
*/
protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) {
final int N = baseState.length;
int i = N - 1;
while (i >= 0 && baseState[i] == 0) {
i--;
}
System.arraycopy(additionalState, 0, baseState, i + 1, additionalState.length);
return baseState;
}
/**
* Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}
* on all Drawable objects associated with this view.
*/
public void jumpDrawablesToCurrentState() {
if (mBGDrawable != null) {
mBGDrawable.jumpToCurrentState();
}
}
/**
* Sets the background color for this view.
* @param color the color of the background
*/
@RemotableViewMethod
public void setBackgroundColor(int color) {
if (mBGDrawable instanceof ColorDrawable) {
((ColorDrawable) mBGDrawable).setColor(color);
} else {
setBackgroundDrawable(new ColorDrawable(color));
}
}
/**
* Set the background to a given resource. The resource should refer to
* a Drawable object or 0 to remove the background.
* @param resid The identifier of the resource.
* @attr ref android.R.styleable#View_background
*/
@RemotableViewMethod
public void setBackgroundResource(int resid) {
if (resid != 0 && resid == mBackgroundResource) {
return;
}
Drawable d= null;
if (resid != 0) {
d = mResources.getDrawable(resid);
}
setBackgroundDrawable(d);
mBackgroundResource = resid;
}
/**
* Set the background to a given Drawable, or remove the background. If the
* background has padding, this View's padding is set to the background's
* padding. However, when a background is removed, this View's padding isn't
* touched. If setting the padding is desired, please use
* {@link #setPadding(int, int, int, int)}.
*
* @param d The Drawable to use as the background, or null to remove the
* background
*/
public void setBackgroundDrawable(Drawable d) {
if (d == mBGDrawable) {
return;
}
boolean requestLayout = false;
mBackgroundResource = 0;
/*
* Regardless of whether we're setting a new background or not, we want
* to clear the previous drawable.
*/
if (mBGDrawable != null) {
mBGDrawable.setCallback(null);
unscheduleDrawable(mBGDrawable);
}
if (d != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
if (d.getPadding(padding)) {
switch (d.getResolvedLayoutDirectionSelf()) {
case LAYOUT_DIRECTION_RTL:
setPadding(padding.right, padding.top, padding.left, padding.bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
}
// Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
// if it has a different minimum size, we should layout again
if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() ||
mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) {
requestLayout = true;
}
d.setCallback(this);
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, false);
mBGDrawable = d;
if ((mPrivateFlags & SKIP_DRAW) != 0) {
mPrivateFlags &= ~SKIP_DRAW;
mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
requestLayout = true;
}
} else {
/* Remove the background */
mBGDrawable = null;
if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) {
/*
* This view ONLY drew the background before and we're removing
* the background, so now it won't draw anything
* (hence we SKIP_DRAW)
*/
mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND;
mPrivateFlags |= SKIP_DRAW;
}
/*
* When the background is set, we try to apply its padding to this
* View. When the background is removed, we don't touch this View's
* padding. This is noted in the Javadocs. Hence, we don't need to
* requestLayout(), the invalidate() below is sufficient.
*/
// The old background's minimum size could have affected this
// View's layout, so let's requestLayout
requestLayout = true;
}
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
invalidate(true);
}
/**
* Gets the background drawable
* @return The drawable used as the background for this view, if any.
*/
public Drawable getBackground() {
return mBGDrawable;
}
/**
* Sets the padding. The view may add on the space required to display
* the scrollbars, depending on the style and visibility of the scrollbars.
* So the values returned from {@link #getPaddingLeft}, {@link #getPaddingTop},
* {@link #getPaddingRight} and {@link #getPaddingBottom} may be different
* from the values set in this call.
*
* @attr ref android.R.styleable#View_padding
* @attr ref android.R.styleable#View_paddingBottom
* @attr ref android.R.styleable#View_paddingLeft
* @attr ref android.R.styleable#View_paddingRight
* @attr ref android.R.styleable#View_paddingTop
* @param left the left padding in pixels
* @param top the top padding in pixels
* @param right the right padding in pixels
* @param bottom the bottom padding in pixels
*/
public void setPadding(int left, int top, int right, int bottom) {
boolean changed = false;
mUserPaddingRelative = false;
mUserPaddingLeft = left;
mUserPaddingRight = right;
mUserPaddingBottom = bottom;
final int viewFlags = mViewFlags;
// Common case is there are no scroll bars.
if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
if ((viewFlags & SCROLLBARS_VERTICAL) != 0) {
final int offset = (viewFlags & SCROLLBARS_INSET_MASK) == 0
? 0 : getVerticalScrollbarWidth();
switch (mVerticalScrollbarPosition) {
case SCROLLBAR_POSITION_DEFAULT:
if (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) {
left += offset;
} else {
right += offset;
}
break;
case SCROLLBAR_POSITION_RIGHT:
right += offset;
break;
case SCROLLBAR_POSITION_LEFT:
left += offset;
break;
}
}
if ((viewFlags & SCROLLBARS_HORIZONTAL) != 0) {
bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0
? 0 : getHorizontalScrollbarHeight();
}
}
if (mPaddingLeft != left) {
changed = true;
mPaddingLeft = left;
}
if (mPaddingTop != top) {
changed = true;
mPaddingTop = top;
}
if (mPaddingRight != right) {
changed = true;
mPaddingRight = right;
}
if (mPaddingBottom != bottom) {
changed = true;
mPaddingBottom = bottom;
}
if (changed) {
requestLayout();
}
}
/**
* Sets the relative padding. The view may add on the space required to display
* the scrollbars, depending on the style and visibility of the scrollbars.
* So the values returned from {@link #getPaddingStart}, {@link #getPaddingTop},
* {@link #getPaddingEnd} and {@link #getPaddingBottom} may be different
* from the values set in this call.
*
* @attr ref android.R.styleable#View_padding
* @attr ref android.R.styleable#View_paddingBottom
* @attr ref android.R.styleable#View_paddingStart
* @attr ref android.R.styleable#View_paddingEnd
* @attr ref android.R.styleable#View_paddingTop
* @param start the start padding in pixels
* @param top the top padding in pixels
* @param end the end padding in pixels
* @param bottom the bottom padding in pixels
*/
public void setPaddingRelative(int start, int top, int end, int bottom) {
mUserPaddingRelative = true;
mUserPaddingStart = start;
mUserPaddingEnd = end;
switch(getResolvedLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
setPadding(end, top, start, bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
setPadding(start, top, end, bottom);
}
}
/**
* Returns the top padding of this view.
*
* @return the top padding in pixels
*/
public int getPaddingTop() {
return mPaddingTop;
}
/**
* Returns the bottom padding of this view. If there are inset and enabled
* scrollbars, this value may include the space required to display the
* scrollbars as well.
*
* @return the bottom padding in pixels
*/
public int getPaddingBottom() {
return mPaddingBottom;
}
/**
* Returns the left padding of this view. If there are inset and enabled
* scrollbars, this value may include the space required to display the
* scrollbars as well.
*
* @return the left padding in pixels
*/
public int getPaddingLeft() {
return mPaddingLeft;
}
/**
* Returns the start padding of this view. If there are inset and enabled
* scrollbars, this value may include the space required to display the
* scrollbars as well.
*
* @return the start padding in pixels
*/
public int getPaddingStart() {
return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
mPaddingRight : mPaddingLeft;
}
/**
* Returns the right padding of this view. If there are inset and enabled
* scrollbars, this value may include the space required to display the
* scrollbars as well.
*
* @return the right padding in pixels
*/
public int getPaddingRight() {
return mPaddingRight;
}
/**
* Returns the end padding of this view. If there are inset and enabled
* scrollbars, this value may include the space required to display the
* scrollbars as well.
*
* @return the end padding in pixels
*/
public int getPaddingEnd() {
return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
mPaddingLeft : mPaddingRight;
}
/**
* Return if the padding as been set thru relative values
* {@link #setPaddingRelative(int, int, int, int)} or thru
* @attr ref android.R.styleable#View_paddingStart or
* @attr ref android.R.styleable#View_paddingEnd
*
* @return true if the padding is relative or false if it is not.
*/
public boolean isPaddingRelative() {
return mUserPaddingRelative;
}
/**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
* the selected view is the view that is highlighted.
*
* @param selected true if the view must be selected, false otherwise
*/
public void setSelected(boolean selected) {
if (((mPrivateFlags & SELECTED) != 0) != selected) {
mPrivateFlags = (mPrivateFlags & ~SELECTED) | (selected ? SELECTED : 0);
if (!selected) resetPressedState();
invalidate(true);
refreshDrawableState();
dispatchSetSelected(selected);
}
}
/**
* Dispatch setSelected to all of this View's children.
*
* @see #setSelected(boolean)
*
* @param selected The new selected state
*/
protected void dispatchSetSelected(boolean selected) {
}
/**
* Indicates the selection state of this view.
*
* @return true if the view is selected, false otherwise
*/
@ViewDebug.ExportedProperty
public boolean isSelected() {
return (mPrivateFlags & SELECTED) != 0;
}
/**
* Changes the activated state of this view. A view can be activated or not.
* Note that activation is not the same as selection. Selection is
* a transient property, representing the view (hierarchy) the user is
* currently interacting with. Activation is a longer-term state that the
* user can move views in and out of. For example, in a list view with
* single or multiple selection enabled, the views in the current selection
* set are activated. (Um, yeah, we are deeply sorry about the terminology
* here.) The activated state is propagated down to children of the view it
* is set on.
*
* @param activated true if the view must be activated, false otherwise
*/
public void setActivated(boolean activated) {
if (((mPrivateFlags & ACTIVATED) != 0) != activated) {
mPrivateFlags = (mPrivateFlags & ~ACTIVATED) | (activated ? ACTIVATED : 0);
invalidate(true);
refreshDrawableState();
dispatchSetActivated(activated);
}
}
/**
* Dispatch setActivated to all of this View's children.
*
* @see #setActivated(boolean)
*
* @param activated The new activated state
*/
protected void dispatchSetActivated(boolean activated) {
}
/**
* Indicates the activation state of this view.
*
* @return true if the view is activated, false otherwise
*/
@ViewDebug.ExportedProperty
public boolean isActivated() {
return (mPrivateFlags & ACTIVATED) != 0;
}
/**
* Returns the ViewTreeObserver for this view's hierarchy. The view tree
* observer can be used to get notifications when global events, like
* layout, happen.
*
* The returned ViewTreeObserver observer is not guaranteed to remain
* valid for the lifetime of this View. If the caller of this method keeps
* a long-lived reference to ViewTreeObserver, it should always check for
* the return value of {@link ViewTreeObserver#isAlive()}.
*
* @return The ViewTreeObserver for this view's hierarchy.
*/
public ViewTreeObserver getViewTreeObserver() {
if (mAttachInfo != null) {
return mAttachInfo.mTreeObserver;
}
if (mFloatingTreeObserver == null) {
mFloatingTreeObserver = new ViewTreeObserver();
}
return mFloatingTreeObserver;
}
/**
*
Finds the topmost view in the current view hierarchy.
*
* @return the topmost view containing this view
*/
public View getRootView() {
if (mAttachInfo != null) {
final View v = mAttachInfo.mRootView;
if (v != null) {
return v;
}
}
View parent = this;
while (parent.mParent != null && parent.mParent instanceof View) {
parent = (View) parent.mParent;
}
return parent;
}
/**
* Computes the coordinates of this view on the screen. The argument
* must be an array of two integers. After the method returns, the array
* contains the x and y location in that order.
*
* @param location an array of two integers in which to hold the coordinates
*/
public void getLocationOnScreen(int[] location) {
getLocationInWindow(location);
final AttachInfo info = mAttachInfo;
if (info != null) {
location[0] += info.mWindowLeft;
location[1] += info.mWindowTop;
}
}
/**
* Computes the coordinates of this view in its window. The argument
* must be an array of two integers. After the method returns, the array
* contains the x and y location in that order.
*
* @param location an array of two integers in which to hold the coordinates
*/
public void getLocationInWindow(int[] location) {
if (location == null || location.length < 2) {
throw new IllegalArgumentException("location must be an array of two integers");
}
if (mAttachInfo == null) {
// When the view is not attached to a window, this method does not make sense
location[0] = location[1] = 0;
return;
}
float[] position = mAttachInfo.mTmpTransformLocation;
position[0] = position[1] = 0.0f;
if (!hasIdentityMatrix()) {
getMatrix().mapPoints(position);
}
position[0] += mLeft;
position[1] += mTop;
ViewParent viewParent = mParent;
while (viewParent instanceof View) {
final View view = (View) viewParent;
position[0] -= view.mScrollX;
position[1] -= view.mScrollY;
if (!view.hasIdentityMatrix()) {
view.getMatrix().mapPoints(position);
}
position[0] += view.mLeft;
position[1] += view.mTop;
viewParent = view.mParent;
}
if (viewParent instanceof ViewRootImpl) {
// *cough*
final ViewRootImpl vr = (ViewRootImpl) viewParent;
position[1] -= vr.mCurScrollY;
}
location[0] = (int) (position[0] + 0.5f);
location[1] = (int) (position[1] + 0.5f);
}
/**
* {@hide}
* @param id the id of the view to be found
* @return the view of the specified id, null if cannot be found
*/
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
/**
* {@hide}
* @param tag the tag of the view to be found
* @return the view of specified tag, null if cannot be found
*/
protected View findViewWithTagTraversal(Object tag) {
if (tag != null && tag.equals(mTag)) {
return this;
}
return null;
}
/**
* {@hide}
* @param predicate The predicate to evaluate.
* @param childToSkip If not null, ignores this child during the recursive traversal.
* @return The first view that matches the predicate or null.
*/
protected View findViewByPredicateTraversal(Predicate predicate, View childToSkip) {
if (predicate.apply(this)) {
return this;
}
return null;
}
/**
* Look for a child view with the given id. If this view has the given
* id, return this view.
*
* @param id The id to search for.
* @return The view that has the given id in the hierarchy or null
*/
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
/**
* Finds a view by its unuque and stable accessibility id.
*
* @param accessibilityId The searched accessibility id.
* @return The found view.
*/
final View findViewByAccessibilityId(int accessibilityId) {
if (accessibilityId < 0) {
return null;
}
return findViewByAccessibilityIdTraversal(accessibilityId);
}
/**
* Performs the traversal to find a view by its unuque and stable accessibility id.
*
* Note: This method does not stop at the root namespace
* boundary since the user can touch the screen at an arbitrary location
* potentially crossing the root namespace bounday which will send an
* accessibility event to accessibility services and they should be able
* to obtain the event source. Also accessibility ids are guaranteed to be
* unique in the window.
*
* @param accessibilityId The accessibility id.
* @return The found view.
*/
View findViewByAccessibilityIdTraversal(int accessibilityId) {
if (getAccessibilityViewId() == accessibilityId) {
return this;
}
return null;
}
/**
* Look for a child view with the given tag. If this view has the given
* tag, return this view.
*
* @param tag The tag to search for, using "tag.equals(getTag())".
* @return The View that has the given tag in the hierarchy or null
*/
public final View findViewWithTag(Object tag) {
if (tag == null) {
return null;
}
return findViewWithTagTraversal(tag);
}
/**
* {@hide}
* Look for a child view that matches the specified predicate.
* If this view matches the predicate, return this view.
*
* @param predicate The predicate to evaluate.
* @return The first view that matches the predicate or null.
*/
public final View findViewByPredicate(Predicate predicate) {
return findViewByPredicateTraversal(predicate, null);
}
/**
* {@hide}
* Look for a child view that matches the specified predicate,
* starting with the specified view and its descendents and then
* recusively searching the ancestors and siblings of that view
* until this view is reached.
*
* This method is useful in cases where the predicate does not match
* a single unique view (perhaps multiple views use the same id)
* and we are trying to find the view that is "closest" in scope to the
* starting view.
*
* @param start The view to start from.
* @param predicate The predicate to evaluate.
* @return The first view that matches the predicate or null.
*/
public final View findViewByPredicateInsideOut(View start, Predicate predicate) {
View childToSkip = null;
for (;;) {
View view = start.findViewByPredicateTraversal(predicate, childToSkip);
if (view != null || start == this) {
return view;
}
ViewParent parent = start.getParent();
if (parent == null || !(parent instanceof View)) {
return null;
}
childToSkip = start;
start = (View) parent;
}
}
/**
* Sets the identifier for this view. The identifier does not have to be
* unique in this view's hierarchy. The identifier should be a positive
* number.
*
* @see #NO_ID
* @see #getId()
* @see #findViewById(int)
*
* @param id a number used to identify the view
*
* @attr ref android.R.styleable#View_id
*/
public void setId(int id) {
mID = id;
}
/**
* {@hide}
*
* @param isRoot true if the view belongs to the root namespace, false
* otherwise
*/
public void setIsRootNamespace(boolean isRoot) {
if (isRoot) {
mPrivateFlags |= IS_ROOT_NAMESPACE;
} else {
mPrivateFlags &= ~IS_ROOT_NAMESPACE;
}
}
/**
* {@hide}
*
* @return true if the view belongs to the root namespace, false otherwise
*/
public boolean isRootNamespace() {
return (mPrivateFlags&IS_ROOT_NAMESPACE) != 0;
}
/**
* Returns this view's identifier.
*
* @return a positive integer used to identify the view or {@link #NO_ID}
* if the view has no ID
*
* @see #setId(int)
* @see #findViewById(int)
* @attr ref android.R.styleable#View_id
*/
@ViewDebug.CapturedViewProperty
public int getId() {
return mID;
}
/**
* Returns this view's tag.
*
* @return the Object stored in this view as a tag
*
* @see #setTag(Object)
* @see #getTag(int)
*/
@ViewDebug.ExportedProperty
public Object getTag() {
return mTag;
}
/**
* Sets the tag associated with this view. A tag can be used to mark
* a view in its hierarchy and does not have to be unique within the
* hierarchy. Tags can also be used to store data within a view without
* resorting to another data structure.
*
* @param tag an Object to tag the view with
*
* @see #getTag()
* @see #setTag(int, Object)
*/
public void setTag(final Object tag) {
mTag = tag;
}
/**
* Returns the tag associated with this view and the specified key.
*
* @param key The key identifying the tag
*
* @return the Object stored in this view as a tag
*
* @see #setTag(int, Object)
* @see #getTag()
*/
public Object getTag(int key) {
if (mKeyedTags != null) return mKeyedTags.get(key);
return null;
}
/**
* Sets a tag associated with this view and a key. A tag can be used
* to mark a view in its hierarchy and does not have to be unique within
* the hierarchy. Tags can also be used to store data within a view
* without resorting to another data structure.
*
* The specified key should be an id declared in the resources of the
* application to ensure it is unique (see the ID resource type ).
* Keys identified as belonging to
* the Android framework or not associated with any package will cause
* an {@link IllegalArgumentException} to be thrown.
*
* @param key The key identifying the tag
* @param tag An Object to tag the view with
*
* @throws IllegalArgumentException If they specified key is not valid
*
* @see #setTag(Object)
* @see #getTag(int)
*/
public void setTag(int key, final Object tag) {
// If the package id is 0x00 or 0x01, it's either an undefined package
// or a framework id
if ((key >>> 24) < 2) {
throw new IllegalArgumentException("The key must be an application-specific "
+ "resource id.");
}
setKeyedTag(key, tag);
}
/**
* Variation of {@link #setTag(int, Object)} that enforces the key to be a
* framework id.
*
* @hide
*/
public void setTagInternal(int key, Object tag) {
if ((key >>> 24) != 0x1) {
throw new IllegalArgumentException("The key must be a framework-specific "
+ "resource id.");
}
setKeyedTag(key, tag);
}
private void setKeyedTag(int key, Object tag) {
if (mKeyedTags == null) {
mKeyedTags = new SparseArray();
}
mKeyedTags.put(key, tag);
}
/**
* @param consistency The type of consistency. See ViewDebug for more information.
*
* @hide
*/
protected boolean dispatchConsistencyCheck(int consistency) {
return onConsistencyCheck(consistency);
}
/**
* Method that subclasses should implement to check their consistency. The type of
* consistency check is indicated by the bit field passed as a parameter.
*
* @param consistency The type of consistency. See ViewDebug for more information.
*
* @throws IllegalStateException if the view is in an inconsistent state.
*
* @hide
*/
protected boolean onConsistencyCheck(int consistency) {
boolean result = true;
final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0;
final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0;
if (checkLayout) {
if (getParent() == null) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + this + " does not have a parent.");
}
if (mAttachInfo == null) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + this + " is not attached to a window.");
}
}
if (checkDrawing) {
// Do not check the DIRTY/DRAWN flags because views can call invalidate()
// from their draw() method
if ((mPrivateFlags & DRAWN) != DRAWN &&
(mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
result = false;
android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
"View " + this + " was invalidated but its drawing cache is valid.");
}
}
return result;
}
/**
* Prints information about this view in the log output, with the tag
* {@link #VIEW_LOG_TAG}.
*
* @hide
*/
public void debug() {
debug(0);
}
/**
* Prints information about this view in the log output, with the tag
* {@link #VIEW_LOG_TAG}. Each line in the output is preceded with an
* indentation defined by the depth
.
*
* @param depth the indentation level
*
* @hide
*/
protected void debug(int depth) {
String output = debugIndent(depth - 1);
output += "+ " + this;
int id = getId();
if (id != -1) {
output += " (id=" + id + ")";
}
Object tag = getTag();
if (tag != null) {
output += " (tag=" + tag + ")";
}
Log.d(VIEW_LOG_TAG, output);
if ((mPrivateFlags & FOCUSED) != 0) {
output = debugIndent(depth) + " FOCUSED";
Log.d(VIEW_LOG_TAG, output);
}
output = debugIndent(depth);
output += "frame={" + mLeft + ", " + mTop + ", " + mRight
+ ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
+ "} ";
Log.d(VIEW_LOG_TAG, output);
if (mPaddingLeft != 0 || mPaddingTop != 0 || mPaddingRight != 0
|| mPaddingBottom != 0) {
output = debugIndent(depth);
output += "padding={" + mPaddingLeft + ", " + mPaddingTop
+ ", " + mPaddingRight + ", " + mPaddingBottom + "}";
Log.d(VIEW_LOG_TAG, output);
}
output = debugIndent(depth);
output += "mMeasureWidth=" + mMeasuredWidth +
" mMeasureHeight=" + mMeasuredHeight;
Log.d(VIEW_LOG_TAG, output);
output = debugIndent(depth);
if (mLayoutParams == null) {
output += "BAD! no layout params";
} else {
output = mLayoutParams.debug(output);
}
Log.d(VIEW_LOG_TAG, output);
output = debugIndent(depth);
output += "flags={";
output += View.printFlags(mViewFlags);
output += "}";
Log.d(VIEW_LOG_TAG, output);
output = debugIndent(depth);
output += "privateFlags={";
output += View.printPrivateFlags(mPrivateFlags);
output += "}";
Log.d(VIEW_LOG_TAG, output);
}
/**
* Creates an string of whitespaces used for indentation.
*
* @param depth the indentation level
* @return a String containing (depth * 2 + 3) * 2 white spaces
*
* @hide
*/
protected static String debugIndent(int depth) {
StringBuilder spaces = new StringBuilder((depth * 2 + 3) * 2);
for (int i = 0; i < (depth * 2) + 3; i++) {
spaces.append(' ').append(' ');
}
return spaces.toString();
}
/**
* Return the offset of the widget's text baseline from the widget's top
* boundary. If this widget does not support baseline alignment, this
* method returns -1.
*
* @return the offset of the baseline within the widget's bounds or -1
* if baseline alignment is not supported
*/
@ViewDebug.ExportedProperty(category = "layout")
public int getBaseline() {
return -1;
}
/**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree.
*/
public void requestLayout() {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
}
if (getAccessibilityNodeProvider() != null) {
throw new IllegalStateException("Views with AccessibilityNodeProvider"
+ " can't have children.");
}
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
if (mParent != null) {
if (mLayoutParams != null) {
mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
}
if (!mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
}
/**
* Forces this view to be laid out during the next layout pass.
* This method does not call requestLayout() or forceLayout()
* on the parent.
*/
public void forceLayout() {
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
}
/**
*
* This is called to find out how big a view should be. The parent
* supplies constraint information in the width and height parameters.
*
*
*
* The actual measurement work of a view is performed in
* {@link #onMeasure(int, int)}, called by this method. Therefore, only
* {@link #onMeasure(int, int)} can and must be overridden by subclasses.
*
*
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the
* parent
* @param heightMeasureSpec Vertical space requirements as imposed by the
* parent
*
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~MEASURED_DIMENSION_SET;
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
}
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
/**
*
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
* should be overriden by subclasses to provide accurate and efficient
* measurement of their contents.
*
*
*
* CONTRACT: When overriding this method, you
* must call {@link #setMeasuredDimension(int, int)} to store the
* measured width and height of this view. Failure to do so will trigger an
* IllegalStateException
, thrown by
* {@link #measure(int, int)}. Calling the superclass'
* {@link #onMeasure(int, int)} is a valid use.
*
*
*
* The base class implementation of measure defaults to the background size,
* unless a larger size is allowed by the MeasureSpec. Subclasses should
* override {@link #onMeasure(int, int)} to provide better measurements of
* their content.
*
*
*
* If this method is overridden, it is the subclass's responsibility to make
* sure the measured height and width are at least the view's minimum height
* and width ({@link #getSuggestedMinimumHeight()} and
* {@link #getSuggestedMinimumWidth()}).
*
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
* @param heightMeasureSpec vertical space requirements as imposed by the parent.
* The requirements are encoded with
* {@link android.view.View.MeasureSpec}.
*
* @see #getMeasuredWidth()
* @see #getMeasuredHeight()
* @see #setMeasuredDimension(int, int)
* @see #getSuggestedMinimumHeight()
* @see #getSuggestedMinimumWidth()
* @see android.view.View.MeasureSpec#getMode(int)
* @see android.view.View.MeasureSpec#getSize(int)
*/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* This mehod must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.
*
* @param measuredWidth The measured width of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
* @param measuredHeight The measured height of this view. May be a complex
* bit mask as defined by {@link #MEASURED_SIZE_MASK} and
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= MEASURED_DIMENSION_SET;
}
/**
* Merge two states as returned by {@link #getMeasuredState()}.
* @param curState The current state as returned from a view or the result
* of combining multiple views.
* @param newState The new view state to combine.
* @return Returns a new integer reflecting the combination of the two
* states.
*/
public static int combineMeasuredStates(int curState, int newState) {
return curState | newState;
}
/**
* Version of {@link #resolveSizeAndState(int, int, int)}
* returning only the {@link #MEASURED_SIZE_MASK} bits of the result.
*/
public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
/**
* Utility to reconcile a desired size and state, with constraints imposed
* by a MeasureSpec. Will take the desired size, unless a different size
* is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
* optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
* size is smaller than the size the view wants to be.
*
* @param size How big the view wants to be
* @param measureSpec Constraints imposed by the parent
* @return Size information bit mask as defined by
* {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result | (childMeasuredState&MEASURED_STATE_MASK);
}
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
/**
* Returns the suggested minimum height that the view should use. This
* returns the maximum of the view's minimum height
* and the background's minimum height
* ({@link android.graphics.drawable.Drawable#getMinimumHeight()}).
*
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned height is within the requirements of the parent.
*
* @return The suggested minimum height of the view.
*/
protected int getSuggestedMinimumHeight() {
int suggestedMinHeight = mMinHeight;
if (mBGDrawable != null) {
final int bgMinHeight = mBGDrawable.getMinimumHeight();
if (suggestedMinHeight < bgMinHeight) {
suggestedMinHeight = bgMinHeight;
}
}
return suggestedMinHeight;
}
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width)
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
*
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
protected int getSuggestedMinimumWidth() {
int suggestedMinWidth = mMinWidth;
if (mBGDrawable != null) {
final int bgMinWidth = mBGDrawable.getMinimumWidth();
if (suggestedMinWidth < bgMinWidth) {
suggestedMinWidth = bgMinWidth;
}
}
return suggestedMinWidth;
}
/**
* Sets the minimum height of the view. It is not guaranteed the view will
* be able to achieve this minimum height (for example, if its parent layout
* constrains it with less available height).
*
* @param minHeight The minimum height the view will try to be.
*/
public void setMinimumHeight(int minHeight) {
mMinHeight = minHeight;
}
/**
* Sets the minimum width of the view. It is not guaranteed the view will
* be able to achieve this minimum width (for example, if its parent layout
* constrains it with less available width).
*
* @param minWidth The minimum width the view will try to be.
*/
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
}
/**
* Get the animation currently associated with this view.
*
* @return The animation that is currently playing or
* scheduled to play for this view.
*/
public Animation getAnimation() {
return mCurrentAnimation;
}
/**
* Start the specified animation now.
*
* @param animation the animation to start now
*/
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
/**
* Cancels any animations for this view.
*/
public void clearAnimation() {
if (mCurrentAnimation != null) {
mCurrentAnimation.detach();
}
mCurrentAnimation = null;
invalidateParentIfNeeded();
}
/**
* Sets the next animation to play for this view.
* If you want the animation to play immediately, use
* startAnimation. This method provides allows fine-grained
* control over the start time and invalidation, but you
* must make sure that 1) the animation has a start time set, and
* 2) the view will be invalidated when the animation is supposed to
* start.
*
* @param animation The next animation, or null.
*/
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
if (animation != null) {
animation.reset();
}
}
/**
* Invoked by a parent ViewGroup to notify the start of the animation
* currently associated with this view. If you override this method,
* always call super.onAnimationStart();
*
* @see #setAnimation(android.view.animation.Animation)
* @see #getAnimation()
*/
protected void onAnimationStart() {
mPrivateFlags |= ANIMATION_STARTED;
}
/**
* Invoked by a parent ViewGroup to notify the end of the animation
* currently associated with this view. If you override this method,
* always call super.onAnimationEnd();
*
* @see #setAnimation(android.view.animation.Animation)
* @see #getAnimation()
*/
protected void onAnimationEnd() {
mPrivateFlags &= ~ANIMATION_STARTED;
}
/**
* Invoked if there is a Transform that involves alpha. Subclass that can
* draw themselves with the specified alpha should return true, and then
* respect that alpha when their onDraw() is called. If this returns false
* then the view may be redirected to draw into an offscreen buffer to
* fulfill the request, which will look fine, but may be slower than if the
* subclass handles it internally. The default implementation returns false.
*
* @param alpha The alpha (0..255) to apply to the view's drawing
* @return true if the view can draw with the specified alpha.
*/
protected boolean onSetAlpha(int alpha) {
return false;
}
/**
* This is used by the RootView to perform an optimization when
* the view hierarchy contains one or several SurfaceView.
* SurfaceView is always considered transparent, but its children are not,
* therefore all View objects remove themselves from the global transparent
* region (passed as a parameter to this function).
*
* @param region The transparent region for this ViewAncestor (window).
*
* @return Returns true if the effective visibility of the view at this
* point is opaque, regardless of the transparent region; returns false
* if it is possible for underlying windows to be seen behind the view.
*
* {@hide}
*/
public boolean gatherTransparentRegion(Region region) {
final AttachInfo attachInfo = mAttachInfo;
if (region != null && attachInfo != null) {
final int pflags = mPrivateFlags;
if ((pflags & SKIP_DRAW) == 0) {
// The SKIP_DRAW flag IS NOT set, so this view draws. We need to
// remove it from the transparent region.
final int[] location = attachInfo.mTransparentLocation;
getLocationInWindow(location);
region.op(location[0], location[1], location[0] + mRight - mLeft,
location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
} else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {
// The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
// exists, so we remove the background drawable's non-transparent
// parts from this transparent region.
applyDrawableToTransparentRegion(mBGDrawable, region);
}
}
return true;
}
/**
* Play a sound effect for this view.
*
*
The framework will play sound effects for some built in actions, such as
* clicking, but you may wish to play these effects in your widget,
* for instance, for internal navigation.
*
*
The sound effect will only be played if sound effects are enabled by the user, and
* {@link #isSoundEffectsEnabled()} is true.
*
* @param soundConstant One of the constants defined in {@link SoundEffectConstants}
*/
public void playSoundEffect(int soundConstant) {
if (mAttachInfo == null || mAttachInfo.mRootCallbacks == null || !isSoundEffectsEnabled()) {
return;
}
mAttachInfo.mRootCallbacks.playSoundEffect(soundConstant);
}
/**
* BZZZTT!!1!
*
*
Provide haptic feedback to the user for this view.
*
*
The framework will provide haptic feedback for some built in actions,
* such as long presses, but you may wish to provide feedback for your
* own widget.
*
*
The feedback will only be performed if
* {@link #isHapticFeedbackEnabled()} is true.
*
* @param feedbackConstant One of the constants defined in
* {@link HapticFeedbackConstants}
*/
public boolean performHapticFeedback(int feedbackConstant) {
return performHapticFeedback(feedbackConstant, 0);
}
/**
* BZZZTT!!1!
*
*
Like {@link #performHapticFeedback(int)}, with additional options.
*
* @param feedbackConstant One of the constants defined in
* {@link HapticFeedbackConstants}
* @param flags Additional flags as per {@link HapticFeedbackConstants}.
*/
public boolean performHapticFeedback(int feedbackConstant, int flags) {
if (mAttachInfo == null) {
return false;
}
//noinspection SimplifiableIfStatement
if ((flags & HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING) == 0
&& !isHapticFeedbackEnabled()) {
return false;
}
return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
(flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
}
/**
* Request that the visibility of the status bar be changed.
* @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}.
*/
public void setSystemUiVisibility(int visibility) {
if (visibility != mSystemUiVisibility) {
mSystemUiVisibility = visibility;
if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
mParent.recomputeViewAttributes(this);
}
}
}
/**
* Returns the status bar visibility that this view has requested.
* @return Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}.
*/
public int getSystemUiVisibility() {
return mSystemUiVisibility;
}
/**
* Set a listener to receive callbacks when the visibility of the system bar changes.
* @param l The {@link OnSystemUiVisibilityChangeListener} to receive callbacks.
*/
public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) {
getListenerInfo().mOnSystemUiVisibilityChangeListener = l;
if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
mParent.recomputeViewAttributes(this);
}
}
/**
* Dispatch callbacks to {@link #setOnSystemUiVisibilityChangeListener} down
* the view hierarchy.
*/
public void dispatchSystemUiVisibilityChanged(int visibility) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
li.mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange(
visibility & PUBLIC_STATUS_BAR_VISIBILITY_MASK);
}
}
void updateLocalSystemUiVisibility(int localValue, int localChanges) {
int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges);
if (val != mSystemUiVisibility) {
setSystemUiVisibility(val);
}
}
/**
* Creates an image that the system displays during the drag and drop
* operation. This is called a "drag shadow". The default implementation
* for a DragShadowBuilder based on a View returns an image that has exactly the same
* appearance as the given View. The default also positions the center of the drag shadow
* directly under the touch point. If no View is provided (the constructor with no parameters
* is used), and {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} and
* {@link #onDrawShadow(Canvas) onDrawShadow()} are not overriden, then the
* default is an invisible drag shadow.
*
* You are not required to use the View you provide to the constructor as the basis of the
* drag shadow. The {@link #onDrawShadow(Canvas) onDrawShadow()} method allows you to draw
* anything you want as the drag shadow.
*
*
* You pass a DragShadowBuilder object to the system when you start the drag. The system
* calls {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} to get the
* size and position of the drag shadow. It uses this data to construct a
* {@link android.graphics.Canvas} object, then it calls {@link #onDrawShadow(Canvas) onDrawShadow()}
* so that your application can draw the shadow image in the Canvas.
*
*
*
*
Developer Guides
*
For a guide to implementing drag and drop features, read the
* Drag and Drop developer guide.
*
*/
public static class DragShadowBuilder {
private final WeakReference mView;
/**
* Constructs a shadow image builder based on a View. By default, the resulting drag
* shadow will have the same appearance and dimensions as the View, with the touch point
* over the center of the View.
* @param view A View. Any View in scope can be used.
*/
public DragShadowBuilder(View view) {
mView = new WeakReference(view);
}
/**
* Construct a shadow builder object with no associated View. This
* constructor variant is only useful when the {@link #onProvideShadowMetrics(Point, Point)}
* and {@link #onDrawShadow(Canvas)} methods are also overridden in order
* to supply the drag shadow's dimensions and appearance without
* reference to any View object. If they are not overridden, then the result is an
* invisible drag shadow.
*/
public DragShadowBuilder() {
mView = new WeakReference(null);
}
/**
* Returns the View object that had been passed to the
* {@link #View.DragShadowBuilder(View)}
* constructor. If that View parameter was {@code null} or if the
* {@link #View.DragShadowBuilder()}
* constructor was used to instantiate the builder object, this method will return
* null.
*
* @return The View object associate with this builder object.
*/
@SuppressWarnings({"JavadocReference"})
final public View getView() {
return mView.get();
}
/**
* Provides the metrics for the shadow image. These include the dimensions of
* the shadow image, and the point within that shadow that should
* be centered under the touch location while dragging.
*
* The default implementation sets the dimensions of the shadow to be the
* same as the dimensions of the View itself and centers the shadow under
* the touch point.
*
*
* @param shadowSize A {@link android.graphics.Point} containing the width and height
* of the shadow image. Your application must set {@link android.graphics.Point#x} to the
* desired width and must set {@link android.graphics.Point#y} to the desired height of the
* image.
*
* @param shadowTouchPoint A {@link android.graphics.Point} for the position within the
* shadow image that should be underneath the touch point during the drag and drop
* operation. Your application must set {@link android.graphics.Point#x} to the
* X coordinate and {@link android.graphics.Point#y} to the Y coordinate of this position.
*/
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
final View view = mView.get();
if (view != null) {
shadowSize.set(view.getWidth(), view.getHeight());
shadowTouchPoint.set(shadowSize.x / 2, shadowSize.y / 2);
} else {
Log.e(View.VIEW_LOG_TAG, "Asked for drag thumb metrics but no view");
}
}
/**
* Draws the shadow image. The system creates the {@link android.graphics.Canvas} object
* based on the dimensions it received from the
* {@link #onProvideShadowMetrics(Point, Point)} callback.
*
* @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
*/
public void onDrawShadow(Canvas canvas) {
final View view = mView.get();
if (view != null) {
view.draw(canvas);
} else {
Log.e(View.VIEW_LOG_TAG, "Asked to draw drag shadow but no view");
}
}
}
/**
* Starts a drag and drop operation. When your application calls this method, it passes a
* {@link android.view.View.DragShadowBuilder} object to the system. The
* system calls this object's {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)}
* to get metrics for the drag shadow, and then calls the object's
* {@link DragShadowBuilder#onDrawShadow(Canvas)} to draw the drag shadow itself.
*
* Once the system has the drag shadow, it begins the drag and drop operation by sending
* drag events to all the View objects in your application that are currently visible. It does
* this either by calling the View object's drag listener (an implementation of
* {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} or by calling the
* View object's {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} method.
* Both are passed a {@link android.view.DragEvent} object that has a
* {@link android.view.DragEvent#getAction()} value of
* {@link android.view.DragEvent#ACTION_DRAG_STARTED}.
*
*
* Your application can invoke startDrag() on any attached View object. The View object does not
* need to be the one used in {@link android.view.View.DragShadowBuilder}, nor does it need to
* be related to the View the user selected for dragging.
*
* @param data A {@link android.content.ClipData} object pointing to the data to be
* transferred by the drag and drop operation.
* @param shadowBuilder A {@link android.view.View.DragShadowBuilder} object for building the
* drag shadow.
* @param myLocalState An {@link java.lang.Object} containing local data about the drag and
* drop operation. This Object is put into every DragEvent object sent by the system during the
* current drag.
*
* myLocalState is a lightweight mechanism for the sending information from the dragged View
* to the target Views. For example, it can contain flags that differentiate between a
* a copy operation and a move operation.
*
* @param flags Flags that control the drag and drop operation. No flags are currently defined,
* so the parameter should be set to 0.
* @return {@code true} if the method completes successfully, or
* {@code false} if it fails anywhere. Returning {@code false} means the system was unable to
* do a drag, and so no drag operation is in progress.
*/
public final boolean startDrag(ClipData data, DragShadowBuilder shadowBuilder,
Object myLocalState, int flags) {
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "startDrag: data=" + data + " flags=" + flags);
}
boolean okay = false;
Point shadowSize = new Point();
Point shadowTouchPoint = new Point();
shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
if ((shadowSize.x < 0) || (shadowSize.y < 0) ||
(shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
throw new IllegalStateException("Drag shadow dimensions must not be negative");
}
if (ViewDebug.DEBUG_DRAG) {
Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
+ " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
}
Surface surface = new Surface();
try {
IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
flags, shadowSize.x, shadowSize.y, surface);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+ " surface=" + surface);
if (token != null) {
Canvas canvas = surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
shadowBuilder.onDrawShadow(canvas);
} finally {
surface.unlockCanvasAndPost(canvas);
}
final ViewRootImpl root = getViewRootImpl();
// Cache the local state object for delivery with DragEvents
root.setLocalDragState(myLocalState);
// repurpose 'shadowSize' for the last touch point
root.getLastTouchPoint(shadowSize);
okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
shadowSize.x, shadowSize.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
// Off and running! Release our local surface instance; the drag
// shadow surface is now managed by the system process.
surface.release();
}
} catch (Exception e) {
Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
surface.destroy();
}
return okay;
}
/**
* Handles drag events sent by the system following a call to
* {@link android.view.View#startDrag(ClipData,DragShadowBuilder,Object,int) startDrag()}.
*
* When the system calls this method, it passes a
* {@link android.view.DragEvent} object. A call to
* {@link android.view.DragEvent#getAction()} returns one of the action type constants defined
* in DragEvent. The method uses these to determine what is happening in the drag and drop
* operation.
* @param event The {@link android.view.DragEvent} sent by the system.
* The {@link android.view.DragEvent#getAction()} method returns an action type constant defined
* in DragEvent, indicating the type of drag event represented by this object.
* @return {@code true} if the method was successful, otherwise {@code false}.
*
* The method should return {@code true} in response to an action type of
* {@link android.view.DragEvent#ACTION_DRAG_STARTED} to receive drag events for the current
* operation.
*
*
* The method should also return {@code true} in response to an action type of
* {@link android.view.DragEvent#ACTION_DROP} if it consumed the drop, or
* {@code false} if it didn't.
*
*/
public boolean onDragEvent(DragEvent event) {
return false;
}
/**
* Detects if this View is enabled and has a drag event listener.
* If both are true, then it calls the drag event listener with the
* {@link android.view.DragEvent} it received. If the drag event listener returns
* {@code true}, then dispatchDragEvent() returns {@code true}.
*
* For all other cases, the method calls the
* {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} drag event handler
* method and returns its result.
*
*
* This ensures that a drag event is always consumed, even if the View does not have a drag
* event listener. However, if the View has a listener and the listener returns true, then
* onDragEvent() is not called.
*
*/
public boolean dispatchDragEvent(DragEvent event) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnDragListener.onDrag(this, event)) {
return true;
}
return onDragEvent(event);
}
boolean canAcceptDrag() {
return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
}
/**
* This needs to be a better API (NOT ON VIEW) before it is exposed. If
* it is ever exposed at all.
* @hide
*/
public void onCloseSystemDialogs(String reason) {
}
/**
* Given a Drawable whose bounds have been set to draw into this view,
* update a Region being computed for
* {@link #gatherTransparentRegion(android.graphics.Region)} so
* that any non-transparent parts of the Drawable are removed from the
* given transparent region.
*
* @param dr The Drawable whose transparency is to be applied to the region.
* @param region A Region holding the current transparency information,
* where any parts of the region that are set are considered to be
* transparent. On return, this region will be modified to have the
* transparency information reduced by the corresponding parts of the
* Drawable that are not transparent.
* {@hide}
*/
public void applyDrawableToTransparentRegion(Drawable dr, Region region) {
if (DBG) {
Log.i("View", "Getting transparent region for: " + this);
}
final Region r = dr.getTransparentRegion();
final Rect db = dr.getBounds();
final AttachInfo attachInfo = mAttachInfo;
if (r != null && attachInfo != null) {
final int w = getRight()-getLeft();
final int h = getBottom()-getTop();
if (db.left > 0) {
//Log.i("VIEW", "Drawable left " + db.left + " > view 0");
r.op(0, 0, db.left, h, Region.Op.UNION);
}
if (db.right < w) {
//Log.i("VIEW", "Drawable right " + db.right + " < view " + w);
r.op(db.right, 0, w, h, Region.Op.UNION);
}
if (db.top > 0) {
//Log.i("VIEW", "Drawable top " + db.top + " > view 0");
r.op(0, 0, w, db.top, Region.Op.UNION);
}
if (db.bottom < h) {
//Log.i("VIEW", "Drawable bottom " + db.bottom + " < view " + h);
r.op(0, db.bottom, w, h, Region.Op.UNION);
}
final int[] location = attachInfo.mTransparentLocation;
getLocationInWindow(location);
r.translate(location[0], location[1]);
region.op(r, Region.Op.INTERSECT);
} else {
region.op(db, Region.Op.DIFFERENCE);
}
}
private void checkForLongClick(int delayOffset) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.rememberWindowAttachCount();
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
*
* @param context The Context object for your activity or application.
* @param resource The resource ID to inflate
* @param root A view group that will be the parent. Used to properly inflate the
* layout_* parameters.
* @see LayoutInflater
*/
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
/**
* Scroll the view with standard behavior for scrolling beyond the normal
* content boundaries. Views that call this method should override
* {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
* results of an over-scroll operation.
*
* Views can use this method to handle any touch or fling-based scrolling.
*
* @param deltaX Change in X in pixels
* @param deltaY Change in Y in pixels
* @param scrollX Current X scroll value in pixels before applying deltaX
* @param scrollY Current Y scroll value in pixels before applying deltaY
* @param scrollRangeX Maximum content scroll range along the X axis
* @param scrollRangeY Maximum content scroll range along the Y axis
* @param maxOverScrollX Number of pixels to overscroll by in either direction
* along the X axis.
* @param maxOverScrollY Number of pixels to overscroll by in either direction
* along the Y axis.
* @param isTouchEvent true if this scroll operation is the result of a touch event.
* @return true if scrolling was clamped to an over-scroll boundary along either
* axis, false otherwise.
*/
@SuppressWarnings({"UnusedParameters"})
protected boolean overScrollBy(int deltaX, int deltaY,
int scrollX, int scrollY,
int scrollRangeX, int scrollRangeY,
int maxOverScrollX, int maxOverScrollY,
boolean isTouchEvent) {
final int overScrollMode = mOverScrollMode;
final boolean canScrollHorizontal =
computeHorizontalScrollRange() > computeHorizontalScrollExtent();
final boolean canScrollVertical =
computeVerticalScrollRange() > computeVerticalScrollExtent();
final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
(overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
(overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
int newScrollX = scrollX + deltaX;
if (!overScrollHorizontal) {
maxOverScrollX = 0;
}
int newScrollY = scrollY + deltaY;
if (!overScrollVertical) {
maxOverScrollY = 0;
}
// Clamp values if at the limits and record
final int left = -maxOverScrollX;
final int right = maxOverScrollX + scrollRangeX;
final int top = -maxOverScrollY;
final int bottom = maxOverScrollY + scrollRangeY;
boolean clampedX = false;
if (newScrollX > right) {
newScrollX = right;
clampedX = true;
} else if (newScrollX < left) {
newScrollX = left;
clampedX = true;
}
boolean clampedY = false;
if (newScrollY > bottom) {
newScrollY = bottom;
clampedY = true;
} else if (newScrollY < top) {
newScrollY = top;
clampedY = true;
}
onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
return clampedX || clampedY;
}
/**
* Called by {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)} to
* respond to the results of an over-scroll operation.
*
* @param scrollX New X scroll value in pixels
* @param scrollY New Y scroll value in pixels
* @param clampedX True if scrollX was clamped to an over-scroll boundary
* @param clampedY True if scrollY was clamped to an over-scroll boundary
*/
protected void onOverScrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
// Intentionally empty.
}
/**
* Returns the over-scroll mode for this view. The result will be
* one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
* @return This view's over-scroll mode.
*/
public int getOverScrollMode() {
return mOverScrollMode;
}
/**
* Set the over-scroll mode for this view. Valid over-scroll modes are
* {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
* (allow over-scrolling only if the view content is larger than the container),
* or {@link #OVER_SCROLL_NEVER}.
*
* Setting the over-scroll mode of a view will have an effect only if the
* view is capable of scrolling.
*
* @param overScrollMode The new over-scroll mode for this view.
*/
public void setOverScrollMode(int overScrollMode) {
if (overScrollMode != OVER_SCROLL_ALWAYS &&
overScrollMode != OVER_SCROLL_IF_CONTENT_SCROLLS &&
overScrollMode != OVER_SCROLL_NEVER) {
throw new IllegalArgumentException("Invalid overscroll mode " + overScrollMode);
}
mOverScrollMode = overScrollMode;
}
/**
* Gets a scale factor that determines the distance the view should scroll
* vertically in response to {@link MotionEvent#ACTION_SCROLL}.
* @return The vertical scroll scale factor.
* @hide
*/
protected float getVerticalScrollFactor() {
if (mVerticalScrollFactor == 0) {
TypedValue outValue = new TypedValue();
if (!mContext.getTheme().resolveAttribute(
com.android.internal.R.attr.listPreferredItemHeight, outValue, true)) {
throw new IllegalStateException(
"Expected theme to define listPreferredItemHeight.");
}
mVerticalScrollFactor = outValue.getDimension(
mContext.getResources().getDisplayMetrics());
}
return mVerticalScrollFactor;
}
/**
* Gets a scale factor that determines the distance the view should scroll
* horizontally in response to {@link MotionEvent#ACTION_SCROLL}.
* @return The horizontal scroll scale factor.
* @hide
*/
protected float getHorizontalScrollFactor() {
// TODO: Should use something else.
return getVerticalScrollFactor();
}
/**
* Return the value specifying the text direction or policy that was set with
* {@link #setTextDirection(int)}.
*
* @return the defined text direction. It can be one of:
*
* {@link #TEXT_DIRECTION_INHERIT},
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
*/
public int getTextDirection() {
return mTextDirection;
}
/**
* Set the text direction.
*
* @param textDirection the direction to set. Should be one of:
*
* {@link #TEXT_DIRECTION_INHERIT},
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
*/
public void setTextDirection(int textDirection) {
if (textDirection != mTextDirection) {
mTextDirection = textDirection;
resetResolvedTextDirection();
requestLayout();
}
}
/**
* Return the resolved text direction.
*
* @return the resolved text direction. Return one of:
*
* {@link #TEXT_DIRECTION_FIRST_STRONG}
* {@link #TEXT_DIRECTION_ANY_RTL},
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE},
*/
public int getResolvedTextDirection() {
if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) {
resolveTextDirection();
}
return mResolvedTextDirection;
}
/**
* Resolve the text direction. Will call {@link View#onResolveTextDirection()} when resolution
* is done.
*/
public void resolveTextDirection() {
if (mResolvedTextDirection != TEXT_DIRECTION_INHERIT) {
// Resolution has already been done.
return;
}
if (mTextDirection != TEXT_DIRECTION_INHERIT) {
mResolvedTextDirection = mTextDirection;
} else if (mParent != null && mParent instanceof ViewGroup) {
mResolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection();
} else {
mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG;
}
onResolveTextDirection();
}
/**
* Called when text direction has been resolved. Subclasses that care about text direction
* resolution should override this method. The default implementation does nothing.
*/
public void onResolveTextDirection() {
}
/**
* Reset resolved text direction. Text direction can be resolved with a call to
* getResolvedTextDirection(). Will call {@link View#onResetResolvedTextDirection()} when
* reset is done.
*/
public void resetResolvedTextDirection() {
mResolvedTextDirection = TEXT_DIRECTION_INHERIT;
onResetResolvedTextDirection();
}
/**
* Called when text direction is reset. Subclasses that care about text direction reset should
* override this method and do a reset of the text direction of their children. The default
* implementation does nothing.
*/
public void onResetResolvedTextDirection() {
}
//
// Properties
//
/**
* A Property wrapper around the alpha
functionality handled by the
* {@link View#setAlpha(float)} and {@link View#getAlpha()} methods.
*/
public static final Property ALPHA = new FloatProperty("alpha") {
@Override
public void setValue(View object, float value) {
object.setAlpha(value);
}
@Override
public Float get(View object) {
return object.getAlpha();
}
};
/**
* A Property wrapper around the translationX
functionality handled by the
* {@link View#setTranslationX(float)} and {@link View#getTranslationX()} methods.
*/
public static final Property TRANSLATION_X = new FloatProperty("translationX") {
@Override
public void setValue(View object, float value) {
object.setTranslationX(value);
}
@Override
public Float get(View object) {
return object.getTranslationX();
}
};
/**
* A Property wrapper around the translationY
functionality handled by the
* {@link View#setTranslationY(float)} and {@link View#getTranslationY()} methods.
*/
public static final Property TRANSLATION_Y = new FloatProperty("translationY") {
@Override
public void setValue(View object, float value) {
object.setTranslationY(value);
}
@Override
public Float get(View object) {
return object.getTranslationY();
}
};
/**
* A Property wrapper around the x
functionality handled by the
* {@link View#setX(float)} and {@link View#getX()} methods.
*/
public static final Property X = new FloatProperty("x") {
@Override
public void setValue(View object, float value) {
object.setX(value);
}
@Override
public Float get(View object) {
return object.getX();
}
};
/**
* A Property wrapper around the y
functionality handled by the
* {@link View#setY(float)} and {@link View#getY()} methods.
*/
public static final Property Y = new FloatProperty("y") {
@Override
public void setValue(View object, float value) {
object.setY(value);
}
@Override
public Float get(View object) {
return object.getY();
}
};
/**
* A Property wrapper around the rotation
functionality handled by the
* {@link View#setRotation(float)} and {@link View#getRotation()} methods.
*/
public static final Property ROTATION = new FloatProperty("rotation") {
@Override
public void setValue(View object, float value) {
object.setRotation(value);
}
@Override
public Float get(View object) {
return object.getRotation();
}
};
/**
* A Property wrapper around the rotationX
functionality handled by the
* {@link View#setRotationX(float)} and {@link View#getRotationX()} methods.
*/
public static final Property ROTATION_X = new FloatProperty("rotationX") {
@Override
public void setValue(View object, float value) {
object.setRotationX(value);
}
@Override
public Float get(View object) {
return object.getRotationX();
}
};
/**
* A Property wrapper around the rotationY
functionality handled by the
* {@link View#setRotationY(float)} and {@link View#getRotationY()} methods.
*/
public static final Property ROTATION_Y = new FloatProperty("rotationY") {
@Override
public void setValue(View object, float value) {
object.setRotationY(value);
}
@Override
public Float get(View object) {
return object.getRotationY();
}
};
/**
* A Property wrapper around the scaleX
functionality handled by the
* {@link View#setScaleX(float)} and {@link View#getScaleX()} methods.
*/
public static final Property SCALE_X = new FloatProperty("scaleX") {
@Override
public void setValue(View object, float value) {
object.setScaleX(value);
}
@Override
public Float get(View object) {
return object.getScaleX();
}
};
/**
* A Property wrapper around the scaleY
functionality handled by the
* {@link View#setScaleY(float)} and {@link View#getScaleY()} methods.
*/
public static final Property SCALE_Y = new FloatProperty("scaleY") {
@Override
public void setValue(View object, float value) {
object.setScaleY(value);
}
@Override
public Float get(View object) {
return object.getScaleY();
}
};
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode. There are three possible
* modes:
*
* UNSPECIFIED
*
* The parent has not imposed any constraint on the child. It can be whatever size
* it wants.
*
*
* EXACTLY
*
* The parent has determined an exact size for the child. The child is going to be
* given those bounds regardless of how big it wants to be.
*
*
* AT_MOST
*
* The child can be as large as it wants up to the specified size.
*
*
*
* MeasureSpecs are implemented as ints to reduce object allocation. This class
* is provided to pack and unpack the <size, mode> tuple into the int.
*/
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
*
* {@link android.view.View.MeasureSpec#UNSPECIFIED}
* {@link android.view.View.MeasureSpec#EXACTLY}
* {@link android.view.View.MeasureSpec#AT_MOST}
*
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
}
private final class CheckForTap implements Runnable {
public void run() {
mPrivateFlags &= ~PREPRESSED;
mPrivateFlags |= PRESSED;
refreshDrawableState();
checkForLongClick(ViewConfiguration.getTapTimeout());
}
}
private final class PerformClick implements Runnable {
public void run() {
performClick();
}
}
/** @hide */
public void hackTurnOffWindowResizeAnim(boolean off) {
mAttachInfo.mTurnOffWindowResizeAnim = off;
}
/**
* This method returns a ViewPropertyAnimator object, which can be used to animate
* specific properties on this View.
*
* @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
*/
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
/**
* Interface definition for a callback to be invoked when a key event is
* dispatched to this view. The callback will be invoked before the key
* event is given to the view.
*/
public interface OnKeyListener {
/**
* Called when a key is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the key has been dispatched to.
* @param keyCode The code for the physical key that was pressed
* @param event The KeyEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onKey(View v, int keyCode, KeyEvent event);
}
/**
* Interface definition for a callback to be invoked when a touch event is
* dispatched to this view. The callback will be invoked before the touch
* event is given to the view.
*/
public interface OnTouchListener {
/**
* Called when a touch event is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the touch event has been dispatched to.
* @param event The MotionEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onTouch(View v, MotionEvent event);
}
/**
* Interface definition for a callback to be invoked when a hover event is
* dispatched to this view. The callback will be invoked before the hover
* event is given to the view.
*/
public interface OnHoverListener {
/**
* Called when a hover event is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the hover event has been dispatched to.
* @param event The MotionEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onHover(View v, MotionEvent event);
}
/**
* Interface definition for a callback to be invoked when a generic motion event is
* dispatched to this view. The callback will be invoked before the generic motion
* event is given to the view.
*/
public interface OnGenericMotionListener {
/**
* Called when a generic motion event is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the generic motion event has been dispatched to.
* @param event The MotionEvent object containing full information about
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onGenericMotion(View v, MotionEvent event);
}
/**
* Interface definition for a callback to be invoked when a view has been clicked and held.
*/
public interface OnLongClickListener {
/**
* Called when a view has been clicked and held.
*
* @param v The view that was clicked and held.
*
* @return true if the callback consumed the long click, false otherwise.
*/
boolean onLongClick(View v);
}
/**
* Interface definition for a callback to be invoked when a drag is being dispatched
* to this view. The callback will be invoked before the hosting view's own
* onDrag(event) method. If the listener wants to fall back to the hosting view's
* onDrag(event) behavior, it should return 'false' from this callback.
*
*
*
Developer Guides
*
For a guide to implementing drag and drop features, read the
* Drag and Drop developer guide.
*
*/
public interface OnDragListener {
/**
* Called when a drag event is dispatched to a view. This allows listeners
* to get a chance to override base View behavior.
*
* @param v The View that received the drag event.
* @param event The {@link android.view.DragEvent} object for the drag event.
* @return {@code true} if the drag event was handled successfully, or {@code false}
* if the drag event was not handled. Note that {@code false} will trigger the View
* to call its {@link #onDragEvent(DragEvent) onDragEvent()} handler.
*/
boolean onDrag(View v, DragEvent event);
}
/**
* Interface definition for a callback to be invoked when the focus state of
* a view changed.
*/
public interface OnFocusChangeListener {
/**
* Called when the focus state of a view has changed.
*
* @param v The view whose state has changed.
* @param hasFocus The new focus state of v.
*/
void onFocusChange(View v, boolean hasFocus);
}
/**
* Interface definition for a callback to be invoked when a view is clicked.
*/
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
/**
* Interface definition for a callback to be invoked when the context menu
* for this view is being built.
*/
public interface OnCreateContextMenuListener {
/**
* Called when the context menu for this view is being built. It is not
* safe to hold onto the menu after this method returns.
*
* @param menu The context menu that is being built
* @param v The view for which the context menu is being built
* @param menuInfo Extra information about the item for which the
* context menu should be shown. This information will vary
* depending on the class of v.
*/
void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo);
}
/**
* Interface definition for a callback to be invoked when the status bar changes
* visibility. This reports global changes to the system UI
* state, not just what the application is requesting.
*
* @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
*/
public interface OnSystemUiVisibilityChangeListener {
/**
* Called when the status bar changes visibility because of a call to
* {@link View#setSystemUiVisibility(int)}.
*
* @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. This tells you the
* global state of the UI visibility flags, not what your
* app is currently applying.
*/
public void onSystemUiVisibilityChange(int visibility);
}
/**
* Interface definition for a callback to be invoked when this view is attached
* or detached from its window.
*/
public interface OnAttachStateChangeListener {
/**
* Called when the view is attached to a window.
* @param v The view that was attached
*/
public void onViewAttachedToWindow(View v);
/**
* Called when the view is detached from a window.
* @param v The view that was detached
*/
public void onViewDetachedFromWindow(View v);
}
private final class UnsetPressedState implements Runnable {
public void run() {
setPressed(false);
}
}
/**
* Base class for derived classes that want to save and restore their own
* state in {@link android.view.View#onSaveInstanceState()}.
*/
public static class BaseSavedState extends AbsSavedState {
/**
* Constructor used when reading from a parcel. Reads the state of the superclass.
*
* @param source
*/
public BaseSavedState(Parcel source) {
super(source);
}
/**
* Constructor called by derived classes when creating their SavedState objects
*
* @param superState The state of the superclass of this view
*/
public BaseSavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
public BaseSavedState createFromParcel(Parcel in) {
return new BaseSavedState(in);
}
public BaseSavedState[] newArray(int size) {
return new BaseSavedState[size];
}
};
}
/**
* A set of information given to a view when it is attached to its parent
* window.
*/
static class AttachInfo {
interface Callbacks {
void playSoundEffect(int effectId);
boolean performHapticFeedback(int effectId, boolean always);
}
/**
* InvalidateInfo is used to post invalidate(int, int, int, int) messages
* to a Handler. This class contains the target (View) to invalidate and
* the coordinates of the dirty rectangle.
*
* For performance purposes, this class also implements a pool of up to
* POOL_LIMIT objects that get reused. This reduces memory allocations
* whenever possible.
*/
static class InvalidateInfo implements Poolable {
private static final int POOL_LIMIT = 10;
private static final Pool sPool = Pools.synchronizedPool(
Pools.finitePool(new PoolableManager() {
public InvalidateInfo newInstance() {
return new InvalidateInfo();
}
public void onAcquired(InvalidateInfo element) {
}
public void onReleased(InvalidateInfo element) {
element.target = null;
}
}, POOL_LIMIT)
);
private InvalidateInfo mNext;
private boolean mIsPooled;
View target;
int left;
int top;
int right;
int bottom;
public void setNextPoolable(InvalidateInfo element) {
mNext = element;
}
public InvalidateInfo getNextPoolable() {
return mNext;
}
static InvalidateInfo acquire() {
return sPool.acquire();
}
void release() {
sPool.release(this);
}
public boolean isPooled() {
return mIsPooled;
}
public void setPooled(boolean isPooled) {
mIsPooled = isPooled;
}
}
final IWindowSession mSession;
final IWindow mWindow;
final IBinder mWindowToken;
final Callbacks mRootCallbacks;
HardwareCanvas mHardwareCanvas;
/**
* The top view of the hierarchy.
*/
View mRootView;
IBinder mPanelParentWindowToken;
Surface mSurface;
boolean mHardwareAccelerated;
boolean mHardwareAccelerationRequested;
HardwareRenderer mHardwareRenderer;
/**
* Scale factor used by the compatibility mode
*/
float mApplicationScale;
/**
* Indicates whether the application is in compatibility mode
*/
boolean mScalingRequired;
/**
* If set, ViewAncestor doesn't use its lame animation for when the window resizes.
*/
boolean mTurnOffWindowResizeAnim;
/**
* Left position of this view's window
*/
int mWindowLeft;
/**
* Top position of this view's window
*/
int mWindowTop;
/**
* Indicates whether views need to use 32-bit drawing caches
*/
boolean mUse32BitDrawingCache;
/**
* For windows that are full-screen but using insets to layout inside
* of the screen decorations, these are the current insets for the
* content of the window.
*/
final Rect mContentInsets = new Rect();
/**
* For windows that are full-screen but using insets to layout inside
* of the screen decorations, these are the current insets for the
* actual visible parts of the window.
*/
final Rect mVisibleInsets = new Rect();
/**
* The internal insets given by this window. This value is
* supplied by the client (through
* {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will
* be given to the window manager when changed to be used in laying
* out windows behind it.
*/
final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets
= new ViewTreeObserver.InternalInsetsInfo();
/**
* All views in the window's hierarchy that serve as scroll containers,
* used to determine if the window can be resized or must be panned
* to adjust for a soft input area.
*/
final ArrayList mScrollContainers = new ArrayList();
final KeyEvent.DispatcherState mKeyDispatchState
= new KeyEvent.DispatcherState();
/**
* Indicates whether the view's window currently has the focus.
*/
boolean mHasWindowFocus;
/**
* The current visibility of the window.
*/
int mWindowVisibility;
/**
* Indicates the time at which drawing started to occur.
*/
long mDrawingTime;
/**
* Indicates whether or not ignoring the DIRTY_MASK flags.
*/
boolean mIgnoreDirtyState;
/**
* This flag tracks when the mIgnoreDirtyState flag is set during draw(),
* to avoid clearing that flag prematurely.
*/
boolean mSetIgnoreDirtyState = false;
/**
* Indicates whether the view's window is currently in touch mode.
*/
boolean mInTouchMode;
/**
* Indicates that ViewAncestor should trigger a global layout change
* the next time it performs a traversal
*/
boolean mRecomputeGlobalAttributes;
/**
* Always report new attributes at next traversal.
*/
boolean mForceReportNewAttributes;
/**
* Set during a traveral if any views want to keep the screen on.
*/
boolean mKeepScreenOn;
/**
* Bitwise-or of all of the values that views have passed to setSystemUiVisibility().
*/
int mSystemUiVisibility;
/**
* True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener
* attached.
*/
boolean mHasSystemUiListeners;
/**
* Set if the visibility of any views has changed.
*/
boolean mViewVisibilityChanged;
/**
* Set to true if a view has been scrolled.
*/
boolean mViewScrollChanged;
/**
* Global to the view hierarchy used as a temporary for dealing with
* x/y points in the transparent region computations.
*/
final int[] mTransparentLocation = new int[2];
/**
* Global to the view hierarchy used as a temporary for dealing with
* x/y points in the ViewGroup.invalidateChild implementation.
*/
final int[] mInvalidateChildLocation = new int[2];
/**
* Global to the view hierarchy used as a temporary for dealing with
* x/y location when view is transformed.
*/
final float[] mTmpTransformLocation = new float[2];
/**
* The view tree observer used to dispatch global events like
* layout, pre-draw, touch mode change, etc.
*/
final ViewTreeObserver mTreeObserver = new ViewTreeObserver();
/**
* A Canvas used by the view hierarchy to perform bitmap caching.
*/
Canvas mCanvas;
/**
* The view root impl.
*/
final ViewRootImpl mViewRootImpl;
/**
* A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
* handler can be used to pump events in the UI events queue.
*/
final Handler mHandler;
/**
* Temporary for use in computing invalidate rectangles while
* calling up the hierarchy.
*/
final Rect mTmpInvalRect = new Rect();
/**
* Temporary for use in computing hit areas with transformed views
*/
final RectF mTmpTransformRect = new RectF();
/**
* Temporary list for use in collecting focusable descendents of a view.
*/
final ArrayList mFocusablesTempList = new ArrayList(24);
/**
* The id of the window for accessibility purposes.
*/
int mAccessibilityWindowId = View.NO_ID;
/**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
* @param handler the events handler the view must use
*/
AttachInfo(IWindowSession session, IWindow window,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
}
}
/**
* ScrollabilityCache holds various fields used by a View when scrolling
* is supported. This avoids keeping too many unused fields in most
* instances of View.
*/
private static class ScrollabilityCache implements Runnable {
/**
* Scrollbars are not visible
*/
public static final int OFF = 0;
/**
* Scrollbars are visible
*/
public static final int ON = 1;
/**
* Scrollbars are fading away
*/
public static final int FADING = 2;
public boolean fadeScrollBars;
public int fadingEdgeLength;
public int scrollBarDefaultDelayBeforeFade;
public int scrollBarFadeDuration;
public int scrollBarSize;
public ScrollBarDrawable scrollBar;
public float[] interpolatorValues;
public View host;
public final Paint paint;
public final Matrix matrix;
public Shader shader;
public final Interpolator scrollBarInterpolator = new Interpolator(1, 2);
private static final float[] OPAQUE = { 255 };
private static final float[] TRANSPARENT = { 0.0f };
/**
* When fading should start. This time moves into the future every time
* a new scroll happens. Measured based on SystemClock.uptimeMillis()
*/
public long fadeStartTime;
/**
* The current state of the scrollbars: ON, OFF, or FADING
*/
public int state = OFF;
private int mLastColor;
public ScrollabilityCache(ViewConfiguration configuration, View host) {
fadingEdgeLength = configuration.getScaledFadingEdgeLength();
scrollBarSize = configuration.getScaledScrollBarSize();
scrollBarDefaultDelayBeforeFade = ViewConfiguration.getScrollDefaultDelay();
scrollBarFadeDuration = ViewConfiguration.getScrollBarFadeDuration();
paint = new Paint();
matrix = new Matrix();
// use use a height of 1, and then wack the matrix each time we
// actually use it.
shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
this.host = host;
}
public void setFadeColor(int color) {
if (color != 0 && color != mLastColor) {
mLastColor = color;
color |= 0xFF000000;
shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
color & 0x00FFFFFF, Shader.TileMode.CLAMP);
paint.setShader(shader);
// Restore the default transfer mode (src_over)
paint.setXfermode(null);
}
}
public void run() {
long now = AnimationUtils.currentAnimationTimeMillis();
if (now >= fadeStartTime) {
// the animation fades the scrollbars out by changing
// the opacity (alpha) from fully opaque to fully
// transparent
int nextFrame = (int) now;
int framesCount = 0;
Interpolator interpolator = scrollBarInterpolator;
// Start opaque
interpolator.setKeyFrame(framesCount++, nextFrame, OPAQUE);
// End transparent
nextFrame += scrollBarFadeDuration;
interpolator.setKeyFrame(framesCount, nextFrame, TRANSPARENT);
state = FADING;
// Kick off the fade animation
host.invalidate(true);
}
}
}
/**
* Resuable callback for sending
* {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} accessibility event.
*/
private class SendViewScrolledAccessibilityEvent implements Runnable {
public volatile boolean mIsPending;
public void run() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
mIsPending = false;
}
}
/**
*
* This class represents a delegate that can be registered in a {@link View}
* to enhance accessibility support via composition rather via inheritance.
* It is specifically targeted to widget developers that extend basic View
* classes i.e. classes in package android.view, that would like their
* applications to be backwards compatible.
*
*
* A scenario in which a developer would like to use an accessibility delegate
* is overriding a method introduced in a later API version then the minimal API
* version supported by the application. For example, the method
* {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} is not available
* in API version 4 when the accessibility APIs were first introduced. If a
* developer would like his application to run on API version 4 devices (assuming
* all other APIs used by the application are version 4 or lower) and take advantage
* of this method, instead of overriding the method which would break the application's
* backwards compatibility, he can override the corresponding method in this
* delegate and register the delegate in the target View if the API version of
* the system is high enough i.e. the API version is same or higher to the API
* version that introduced
* {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)}.
*
*
* Here is an example implementation:
*
*
* if (Build.VERSION.SDK_INT >= 14) {
* // If the API version is equal of higher than the version in
* // which onInitializeAccessibilityNodeInfo was introduced we
* // register a delegate with a customized implementation.
* View view = findViewById(R.id.view_id);
* view.setAccessibilityDelegate(new AccessibilityDelegate() {
* public void onInitializeAccessibilityNodeInfo(View host,
* AccessibilityNodeInfo info) {
* // Let the default implementation populate the info.
* super.onInitializeAccessibilityNodeInfo(host, info);
* // Set some other information.
* info.setEnabled(host.isEnabled());
* }
* });
* }
*
*
* This delegate contains methods that correspond to the accessibility methods
* in View. If a delegate has been specified the implementation in View hands
* off handling to the corresponding method in this delegate. The default
* implementation the delegate methods behaves exactly as the corresponding
* method in View for the case of no accessibility delegate been set. Hence,
* to customize the behavior of a View method, clients can override only the
* corresponding delegate method without altering the behavior of the rest
* accessibility related methods of the host view.
*
*/
public static class AccessibilityDelegate {
/**
* Sends an accessibility event of the given type. If accessibility is not
* enabled this method has no effect.
*
* The default implementation behaves as {@link View#sendAccessibilityEvent(int)
* View#sendAccessibilityEvent(int)} for the case of no accessibility delegate
* been set.
*
*
* @param host The View hosting the delegate.
* @param eventType The type of the event to send.
*
* @see View#sendAccessibilityEvent(int) View#sendAccessibilityEvent(int)
*/
public void sendAccessibilityEvent(View host, int eventType) {
host.sendAccessibilityEventInternal(eventType);
}
/**
* Sends an accessibility event. This method behaves exactly as
* {@link #sendAccessibilityEvent(View, int)} but takes as an argument an
* empty {@link AccessibilityEvent} and does not perform a check whether
* accessibility is enabled.
*
* The default implementation behaves as
* {@link View#sendAccessibilityEventUnchecked(AccessibilityEvent)
* View#sendAccessibilityEventUnchecked(AccessibilityEvent)} for
* the case of no accessibility delegate been set.
*
*
* @param host The View hosting the delegate.
* @param event The event to send.
*
* @see View#sendAccessibilityEventUnchecked(AccessibilityEvent)
* View#sendAccessibilityEventUnchecked(AccessibilityEvent)
*/
public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
host.sendAccessibilityEventUncheckedInternal(event);
}
/**
* Dispatches an {@link AccessibilityEvent} to the host {@link View} first and then
* to its children for adding their text content to the event.
*
* The default implementation behaves as
* {@link View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
* View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)} for
* the case of no accessibility delegate been set.
*
*
* @param host The View hosting the delegate.
* @param event The event.
* @return True if the event population was completed.
*
* @see View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
* View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*/
public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
return host.dispatchPopulateAccessibilityEventInternal(event);
}
/**
* Gives a chance to the host View to populate the accessibility event with its
* text content.
*
* The default implementation behaves as
* {@link View#onPopulateAccessibilityEvent(AccessibilityEvent)
* View#onPopulateAccessibilityEvent(AccessibilityEvent)} for
* the case of no accessibility delegate been set.
*
*
* @param host The View hosting the delegate.
* @param event The accessibility event which to populate.
*
* @see View#onPopulateAccessibilityEvent(AccessibilityEvent)
* View#onPopulateAccessibilityEvent(AccessibilityEvent)
*/
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
host.onPopulateAccessibilityEventInternal(event);
}
/**
* Initializes an {@link AccessibilityEvent} with information about the
* the host View which is the event source.
*
* The default implementation behaves as
* {@link View#onInitializeAccessibilityEvent(AccessibilityEvent)
* View#onInitializeAccessibilityEvent(AccessibilityEvent)} for
* the case of no accessibility delegate been set.
*
*
* @param host The View hosting the delegate.
* @param event The event to initialize.
*
* @see View#onInitializeAccessibilityEvent(AccessibilityEvent)
* View#onInitializeAccessibilityEvent(AccessibilityEvent)
*/
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
host.onInitializeAccessibilityEventInternal(event);
}
/**
* Initializes an {@link AccessibilityNodeInfo} with information about the host view.
*
* The default implementation behaves as
* {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
* View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} for
* the case of no accessibility delegate been set.
*
*
* @param host The View hosting the delegate.
* @param info The instance to initialize.
*
* @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
* View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*/
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
host.onInitializeAccessibilityNodeInfoInternal(info);
}
/**
* Called when a child of the host View has requested sending an
* {@link AccessibilityEvent} and gives an opportunity to the parent (the host)
* to augment the event.
*
* The default implementation behaves as
* {@link ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
* ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)} for
* the case of no accessibility delegate been set.
*
*
* @param host The View hosting the delegate.
* @param child The child which requests sending the event.
* @param event The event to be sent.
* @return True if the event should be sent
*
* @see ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
* ViewGroup#onRequestSendAccessibilityEvent(View, AccessibilityEvent)
*/
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
AccessibilityEvent event) {
return host.onRequestSendAccessibilityEventInternal(child, event);
}
/**
* Gets the provider for managing a virtual view hierarchy rooted at this View
* and reported to {@link android.accessibilityservice.AccessibilityService}s
* that explore the window content.
*
* The default implementation behaves as
* {@link View#getAccessibilityNodeProvider() View#getAccessibilityNodeProvider()} for
* the case of no accessibility delegate been set.
*
*
* @return The provider.
*
* @see AccessibilityNodeProvider
*/
public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
return null;
}
}
}