diff --git a/api/current.txt b/api/current.txt index ce554898a8f55..6c4143389e476 100644 --- a/api/current.txt +++ b/api/current.txt @@ -291,6 +291,7 @@ package android { field public static final int autoAdvanceViewId = 16843535; // 0x101030f field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b field public static final int autoLink = 16842928; // 0x10100b0 + field public static final int autoMirrored = 16843752; // 0x10103e8 field public static final int autoStart = 16843445; // 0x10102b5 field public static final deprecated int autoText = 16843114; // 0x101016a field public static final int autoUrlDetect = 16843404; // 0x101028c @@ -9891,6 +9892,7 @@ package android.graphics.drawable { method public android.graphics.Shader.TileMode getTileModeY(); method public boolean hasAntiAlias(); method public boolean hasMipMap(); + method public final boolean isAutoMirrored(); method public void setAlpha(int); method public void setAntiAlias(boolean); method public void setColorFilter(android.graphics.ColorFilter); @@ -9957,6 +9959,7 @@ package android.graphics.drawable { method public android.graphics.Region getTransparentRegion(); method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public void invalidateSelf(); + method public boolean isAutoMirrored(); method public boolean isStateful(); method public final boolean isVisible(); method public void jumpToCurrentState(); @@ -9967,6 +9970,7 @@ package android.graphics.drawable { method public static int resolveOpacity(int, int); method public void scheduleSelf(java.lang.Runnable, long); method public abstract void setAlpha(int); + method public void setAutoMirrored(boolean); method public void setBounds(int, int, int, int); method public void setBounds(android.graphics.Rect); method public final void setCallback(android.graphics.drawable.Drawable.Callback); @@ -25138,6 +25142,13 @@ package android.util { method public android.util.JsonWriter value(java.lang.Number) throws java.io.IOException; } + public abstract interface LayoutDirection { + field public static final int INHERIT = 2; // 0x2 + field public static final int LOCALE = 3; // 0x3 + field public static final int LTR = 0; // 0x0 + field public static final int RTL = 1; // 0x1 + } + public final class Log { method public static int d(java.lang.String, java.lang.String); method public static int d(java.lang.String, java.lang.String, java.lang.Throwable); diff --git a/core/java/android/util/LayoutDirection.java b/core/java/android/util/LayoutDirection.java new file mode 100644 index 0000000000000..e37d2f22f45ee --- /dev/null +++ b/core/java/android/util/LayoutDirection.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +/** + * An interface for defining layout directions. A layout direction can be left-to-right (LTR) + * or right-to-left (RTL). It can also be inherited (from a parent) or deduced from the default + * language script of a locale. + */ +public interface LayoutDirection { + /** + * Horizontal layout direction is from Left to Right. + */ + public static final int LTR = 0; + + /** + * Horizontal layout direction is from Right to Left. + */ + public static final int RTL = 1; + + /** + * Horizontal layout direction is inherited. + */ + public static final int INHERIT = 2; + + /** + * Horizontal layout direction is deduced from the default language script for the locale. + */ + public static final int LOCALE = 3; +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 299c4a230219b..7624b56dd4a43 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -51,6 +51,7 @@ import android.os.SystemProperties; import android.text.TextUtils; import android.util.AttributeSet; import android.util.FloatProperty; +import android.util.LayoutDirection; import android.util.Log; import android.util.LongSparseLongArray; import android.util.Pools.SynchronizedPool; @@ -1801,25 +1802,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Horizontal layout direction of this view is from Left to Right. * Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_LTR = 0; + public static final int LAYOUT_DIRECTION_LTR = LayoutDirection.LTR; /** * Horizontal layout direction of this view is from Right to Left. * Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_RTL = 1; + public static final int LAYOUT_DIRECTION_RTL = LayoutDirection.RTL; /** * Horizontal layout direction of this view is inherited from its parent. * Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_INHERIT = 2; + public static final int LAYOUT_DIRECTION_INHERIT = LayoutDirection.INHERIT; /** * Horizontal layout direction of this view is from deduced from the default language * script for the locale. Use with {@link #setLayoutDirection}. */ - public static final int LAYOUT_DIRECTION_LOCALE = 3; + public static final int LAYOUT_DIRECTION_LOCALE = LayoutDirection.LOCALE; /** * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 33fd8ce446ac3..3e53b91bd0b05 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -732,6 +732,15 @@ public class ImageView extends View { } } + @Override + public void onRtlPropertiesChanged(int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + + if (mDrawable != null) { + mDrawable.setLayoutDirection(layoutDirection); + } + } + private static final Matrix.ScaleToFit[] sS2FArray = { Matrix.ScaleToFit.FILL, Matrix.ScaleToFit.START, diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 816bb18dcd560..3181164e35285 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2042,6 +2042,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener dr.mDrawableRightInitial = right; } + resetResolvedDrawables(); + resolveDrawables(); invalidate(); requestLayout(); } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 2096f6635c760..67a32fd1357ed 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3905,6 +3905,10 @@ value is false. See {@link android.graphics.drawable.Drawable#setVisible}. --> + + + + @@ -4083,6 +4090,9 @@ + + @@ -4172,6 +4182,9 @@ {@link android.graphics.Bitmap#setHasMipMap(boolean)} for more information. Default value is false. --> + + @@ -4182,6 +4195,9 @@ same pixel configuration as the screen (for instance: a ARGB 8888 bitmap with an RGB 565 screen). --> + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 0eaab656cae01..80c91849103da 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2071,4 +2071,6 @@ + + diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index 8689261eef21a..5ceab36674df9 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -23,12 +23,14 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Shader; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.LayoutDirection; import android.view.Gravity; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -72,7 +74,10 @@ public class BitmapDrawable extends Drawable { // These are scaled to match the target density. private int mBitmapWidth; private int mBitmapHeight; - + + // Mirroring matrix for using with Shaders + private Matrix mMirrorMatrix; + /** * Create an empty drawable, not dealing with density. * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} @@ -398,15 +403,52 @@ public class BitmapDrawable extends Drawable { } } + @Override + public void setAutoMirrored(boolean mirrored) { + if (mBitmapState.mAutoMirrored != mirrored) { + mBitmapState.mAutoMirrored = mirrored; + invalidateSelf(); + } + } + + @Override + public final boolean isAutoMirrored() { + return mBitmapState.mAutoMirrored; + } + @Override public int getChangingConfigurations() { return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; } - + + private boolean needMirroring() { + return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; + } + + private void updateMirrorMatrix(float dx) { + if (mMirrorMatrix == null) { + mMirrorMatrix = new Matrix(); + } + mMirrorMatrix.setTranslate(dx, 0); + mMirrorMatrix.preScale(-1.0f, 1.0f); + } + @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); mApplyGravity = true; + Shader shader = mBitmapState.mPaint.getShader(); + if (shader != null) { + if (needMirroring()) { + updateMirrorMatrix(bounds.right - bounds.left); + shader.setLocalMatrix(mMirrorMatrix); + } else { + if (mMirrorMatrix != null) { + mMirrorMatrix = null; + shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); + } + } + } } @Override @@ -430,6 +472,7 @@ public class BitmapDrawable extends Drawable { } Shader shader = state.mPaint.getShader(); + final boolean needMirroring = needMirroring(); if (shader == null) { if (mApplyGravity) { final int layoutDirection = getLayoutDirection(); @@ -437,12 +480,31 @@ public class BitmapDrawable extends Drawable { getBounds(), mDstRect, layoutDirection); mApplyGravity = false; } + if (needMirroring) { + canvas.save(); + // Mirror the bitmap + canvas.translate(mDstRect.right - mDstRect.left, 0); + canvas.scale(-1.0f, 1.0f); + } canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); + if (needMirroring) { + canvas.restore(); + } } else { if (mApplyGravity) { copyBounds(mDstRect); mApplyGravity = false; } + if (needMirroring) { + // Mirror the bitmap + updateMirrorMatrix(mDstRect.right - mDstRect.left); + shader.setLocalMatrix(mMirrorMatrix); + } else { + if (mMirrorMatrix != null) { + mMirrorMatrix = null; + shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); + } + } canvas.drawRect(mDstRect, state.mPaint); } } @@ -505,6 +567,8 @@ public class BitmapDrawable extends Drawable { setTargetDensity(r.getDisplayMetrics()); setMipMap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_mipMap, bitmap.hasMipMap())); + setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_autoMirrored, + false)); final Paint paint = mBitmapState.mPaint; paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, @@ -567,6 +631,7 @@ public class BitmapDrawable extends Drawable { Shader.TileMode mTileModeY = null; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; boolean mRebuildShader; + boolean mAutoMirrored; BitmapState(Bitmap bitmap) { mBitmap = bitmap; @@ -581,6 +646,7 @@ public class BitmapDrawable extends Drawable { mTargetDensity = bitmapState.mTargetDensity; mPaint = new Paint(bitmapState.mPaint); mRebuildShader = bitmapState.mRebuildShader; + mAutoMirrored = bitmapState.mAutoMirrored; } @Override diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index c8fce9eb85354..8135716d41e5b 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -571,6 +571,25 @@ public abstract class Drawable { return mVisible; } + /** + * Set whether this Drawable is automatically mirrored when its layout direction is RTL + * (right-to left). See {@link android.util.LayoutDirection}. + * + * @param mirrored Set to true if the Drawable should be mirrored, false if not. + */ + public void setAutoMirrored(boolean mirrored) { + } + + /** + * Tells if this Drawable will be automatically mirrored when its layout direction is RTL + * right-to-left. See {@link android.util.LayoutDirection}. + * + * @return boolean Returns true if this Drawable will be automatically mirrored. + */ + public boolean isAutoMirrored() { + return false; + } + /** * Return the opacity/transparency of this Drawable. The returned value is * one of the abstract format constants in diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index f9cd4e261170e..e350e8d0c71c7 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -172,6 +172,19 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return mDrawableContainerState.isStateful(); } + @Override + public void setAutoMirrored(boolean mirrored) { + mDrawableContainerState.mAutoMirrored = mirrored; + if (mCurrDrawable != null) { + mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored); + } + } + + @Override + public boolean isAutoMirrored() { + return mDrawableContainerState.mAutoMirrored; + } + @Override public void jumpToCurrentState() { boolean changed = false; @@ -334,6 +347,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { d.setLevel(getLevel()); d.setBounds(getBounds()); d.setLayoutDirection(getLayoutDirection()); + d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); } } else { mCurrDrawable = null; @@ -471,6 +485,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { int mEnterFadeDuration; int mExitFadeDuration; + boolean mAutoMirrored; + DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res) { mOwner = owner; @@ -490,6 +506,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mLayoutDirection = orig.mLayoutDirection; mEnterFadeDuration = orig.mEnterFadeDuration; mExitFadeDuration = orig.mExitFadeDuration; + mAutoMirrored = orig.mAutoMirrored; // Cloning the following values may require creating futures. mConstantPadding = orig.getConstantPadding(); diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 206897bcb9067..81cc11b6bd7fb 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -119,6 +119,9 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); + setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.LayerDrawable_autoMirrored, + false)); + a.recycle(); final int innerDepth = parser.getDepth() + 1; @@ -200,6 +203,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { st.mChildren[i] = childDrawable; childDrawable.mId = id; childDrawable.mDrawable = layer; + childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); childDrawable.mInsetL = left; childDrawable.mInsetT = top; childDrawable.mInsetR = right; @@ -447,6 +451,21 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { return mLayerState.getOpacity(); } + @Override + public void setAutoMirrored(boolean mirrored) { + mLayerState.mAutoMirrored = mirrored; + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.mNum; + for (int i=0; i requires a valid 9-patch source image"); } + final boolean automirrored = a.getBoolean( + com.android.internal.R.styleable.NinePatchDrawable_autoMirrored, false); + setNinePatchState(new NinePatchState(new NinePatch(bitmap, bitmap.getNinePatchChunk()), - padding, opticalInsets, dither), r); + padding, opticalInsets, dither, automirrored), r); mNinePatchState.mTargetDensity = mTargetDensity; a.recycle(); @@ -407,20 +434,23 @@ public class NinePatchDrawable extends Drawable { final boolean mDither; int mChangingConfigurations; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; + boolean mAutoMirrored; NinePatchState(NinePatch ninePatch, Rect padding) { - this(ninePatch, padding, new Rect(), DEFAULT_DITHER); + this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false); } NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) { - this(ninePatch, padding, opticalInsets, DEFAULT_DITHER); + this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false); } - NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither) { + NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither, + boolean autoMirror) { mNinePatch = ninePatch; mPadding = rect; mOpticalInsets = Insets.of(opticalInsets); mDither = dither; + mAutoMirrored = autoMirror; } // Copy constructor @@ -434,6 +464,7 @@ public class NinePatchDrawable extends Drawable { mDither = state.mDither; mChangingConfigurations = state.mChangingConfigurations; mTargetDensity = state.mTargetDensity; + mAutoMirrored = state.mAutoMirrored; } @Override diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index e3c7bc5862886..48d66b7322695 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -132,6 +132,9 @@ public class StateListDrawable extends DrawableContainer { setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither, DEFAULT_DITHER)); + setAutoMirrored(a.getBoolean( + com.android.internal.R.styleable.StateListDrawable_autoMirrored, false)); + a.recycle(); int type;