Loading core/res/res/values/attrs.xml +2 −0 Original line number Original line Diff line number Diff line Loading @@ -4798,6 +4798,8 @@ <attr name="scaleX" /> <attr name="scaleX" /> <!-- The amount to scale the group on X coordinate --> <!-- The amount to scale the group on X coordinate --> <attr name="scaleY" /> <attr name="scaleY" /> <!-- The alpha of the group (0 is transparent and 1 is opaque) --> <attr name="alpha" /> </declare-styleable> </declare-styleable> <!-- Defines the path used in Vector Drawables. --> <!-- Defines the path used in Vector Drawables. --> Loading graphics/java/android/graphics/drawable/VectorDrawable.java +175 −58 Original line number Original line Diff line number Diff line Loading @@ -40,6 +40,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.HashMap; import java.util.HashMap; import java.util.Stack; /** /** * This lets you create a drawable based on an XML vector graphic It can be * This lets you create a drawable based on an XML vector graphic It can be Loading Loading @@ -129,6 +130,8 @@ public class VectorDrawable extends Drawable { private static final int LINEJOIN_ROUND = 1; private static final int LINEJOIN_ROUND = 1; private static final int LINEJOIN_BEVEL = 2; private static final int LINEJOIN_BEVEL = 2; private static final boolean DBG_VECTOR_DRAWABLE = false; private final VectorDrawableState mVectorState; private final VectorDrawableState mVectorState; private int mAlpha = 0xFF; private int mAlpha = 0xFF; Loading Loading @@ -279,12 +282,17 @@ public class VectorDrawable extends Drawable { boolean noGroupTag = true; boolean noGroupTag = true; boolean noPathTag = true; boolean noPathTag = true; VGroup currentGroup = new VGroup(); // Use a stack to help to build the group tree. // The top of the stack is always the current group. final Stack<VGroup> groupStack = new Stack<VGroup>(); groupStack.push(pathRenderer.mRootGroup); int eventType = parser.getEventType(); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); final String tagName = parser.getName(); final VGroup currentGroup = groupStack.peek(); if (SHAPE_PATH.equals(tagName)) { if (SHAPE_PATH.equals(tagName)) { final VPath path = new VPath(); final VPath path = new VPath(); path.inflate(res, attrs, theme); path.inflate(res, attrs, theme); Loading @@ -297,18 +305,24 @@ public class VectorDrawable extends Drawable { pathRenderer.parseViewport(res, attrs); pathRenderer.parseViewport(res, attrs); noViewportTag = false; noViewportTag = false; } else if (SHAPE_GROUP.equals(tagName)) { } else if (SHAPE_GROUP.equals(tagName)) { currentGroup = new VGroup(); VGroup newChildGroup = new VGroup(); currentGroup.inflate(res, attrs, theme); newChildGroup.inflate(res, attrs, theme); pathRenderer.mGroupList.add(currentGroup); currentGroup.mChildGroupList.add(newChildGroup); groupStack.push(newChildGroup); noGroupTag = false; noGroupTag = false; } } } else if (eventType == XmlPullParser.END_TAG) { final String tagName = parser.getName(); if (SHAPE_GROUP.equals(tagName)) { groupStack.pop(); } } } eventType = parser.next(); eventType = parser.next(); } } if (noGroupTag && !noPathTag) { // Print the tree out for debug. pathRenderer.mGroupList.add(currentGroup); if (DBG_VECTOR_DRAWABLE) { printGroupTree(pathRenderer.mRootGroup, 0); } } if (noSizeTag || noViewportTag || noPathTag) { if (noSizeTag || noViewportTag || noPathTag) { Loading Loading @@ -338,6 +352,21 @@ public class VectorDrawable extends Drawable { return pathRenderer; return pathRenderer; } } private void printGroupTree(VGroup currentGroup, int level) { String indent = ""; for (int i = 0 ; i < level ; i++) { indent += " "; } // Print the current node Log.v(LOGTAG, indent + "current group is :" + currentGroup.getName() + " rotation is " + currentGroup.mRotate); Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); // Then print all the children for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { printGroupTree(currentGroup.mChildGroupList.get(i), level + 1); } } private void setPathRenderer(VPathRenderer pathRenderer) { private void setPathRenderer(VPathRenderer pathRenderer) { mVectorState.mVPathRenderer = pathRenderer; mVectorState.mVPathRenderer = pathRenderer; } } Loading @@ -350,6 +379,7 @@ public class VectorDrawable extends Drawable { public VectorDrawableState(VectorDrawableState copy) { public VectorDrawableState(VectorDrawableState copy) { if (copy != null) { if (copy != null) { mChangingConfigurations = copy.mChangingConfigurations; mChangingConfigurations = copy.mChangingConfigurations; // TODO: Make sure the constant state are handled correctly. mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); mPadding = new Rect(copy.mPadding); mPadding = new Rect(copy.mPadding); } } Loading Loading @@ -377,28 +407,42 @@ public class VectorDrawable extends Drawable { } } private static class VPathRenderer { private static class VPathRenderer { /* Right now the internal data structure is organized as a tree. * Each node can be a group node, or a path. * A group node can have groups or paths as children, but a path node has * no children. * One example can be: * Root Group * / | \ * Group Path Group * / \ | * Path Path Path * */ private final VGroup mRootGroup; private final Path mPath = new Path(); private final Path mPath = new Path(); private final Path mRenderPath = new Path(); private final Path mRenderPath = new Path(); private final Matrix mMatrix = new Matrix(); private static final Matrix IDENTITY_MATRIX = new Matrix(); private Paint mStrokePaint; private Paint mStrokePaint; private Paint mFillPaint; private Paint mFillPaint; private ColorFilter mColorFilter; private ColorFilter mColorFilter; private PathMeasure mPathMeasure; private PathMeasure mPathMeasure; final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>(); private float mBaseWidth = 0; private float mBaseHeight = 0; private float mViewportWidth = 0; private float mViewportHeight = 0; float mBaseWidth = 0; private final Matrix mFinalPathMatrix = new Matrix(); float mBaseHeight = 0; float mViewportWidth = 0; float mViewportHeight = 0; public VPathRenderer() { public VPathRenderer() { mRootGroup = new VGroup(); } } public VPathRenderer(VPathRenderer copy) { public VPathRenderer(VPathRenderer copy) { mGroupList.addAll(copy.mGroupList); mRootGroup = copy.mRootGroup; mBaseWidth = copy.mBaseWidth; mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; mBaseHeight = copy.mBaseHeight; mViewportWidth = copy.mViewportHeight; mViewportWidth = copy.mViewportHeight; Loading @@ -406,33 +450,59 @@ public class VectorDrawable extends Drawable { } } public boolean canApplyTheme() { public boolean canApplyTheme() { final ArrayList<VGroup> groups = mGroupList; // If one of the paths can apply theme, then return true; for (int i = groups.size() - 1; i >= 0; i--) { return recursiveCanApplyTheme(mRootGroup); final ArrayList<VPath> paths = groups.get(i).mVGList; } private boolean recursiveCanApplyTheme(VGroup currentGroup) { // We can do a tree traverse here, if there is one path return true, // then we return true for the whole tree. final ArrayList<VPath> paths = currentGroup.mPathList; for (int j = paths.size() - 1; j >= 0; j--) { for (int j = paths.size() - 1; j >= 0; j--) { final VPath path = paths.get(j); final VPath path = paths.get(j); if (path.canApplyTheme()) { if (path.canApplyTheme()) { return true; return true; } } } } } final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; for (int i = 0; i < childGroups.size(); i++) { VGroup childGroup = childGroups.get(i); if (childGroup.canApplyTheme() || recursiveCanApplyTheme(childGroup)) { return true; } } return false; return false; } } public void applyTheme(Theme t) { public void applyTheme(Theme t) { final ArrayList<VGroup> groups = mGroupList; // Apply theme to every path of the tree. for (int i = groups.size() - 1; i >= 0; i--) { recursiveApplyTheme(mRootGroup, t); VGroup currentGroup = groups.get(i); } currentGroup.applyTheme(t); final ArrayList<VPath> paths = currentGroup.mVGList; private void recursiveApplyTheme(VGroup currentGroup, Theme t) { // We can do a tree traverse here, apply theme to all paths which // can apply theme. final ArrayList<VPath> paths = currentGroup.mPathList; for (int j = paths.size() - 1; j >= 0; j--) { for (int j = paths.size() - 1; j >= 0; j--) { final VPath path = paths.get(j); final VPath path = paths.get(j); if (path.canApplyTheme()) { if (path.canApplyTheme()) { path.applyTheme(t); path.applyTheme(t); } } } } final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; for (int i = 0; i < childGroups.size(); i++) { VGroup childGroup = childGroups.get(i); if (childGroup.canApplyTheme()) { childGroup.applyTheme(t); } } recursiveApplyTheme(childGroup, t); } } } public void setColorFilter(ColorFilter colorFilter) { public void setColorFilter(ColorFilter colorFilter) { Loading @@ -448,34 +518,35 @@ public class VectorDrawable extends Drawable { } } public void draw(Canvas canvas, int w, int h) { private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, if (mGroupList == null || mGroupList.size() == 0) { Canvas canvas, int w, int h) { Log.e(LOGTAG,"There is no group to draw"); // Calculate current group's matrix by preConcat the parent's and return; // and the current one on the top of the stack. } // Basically the Mfinal = Mviewport * M0 * M1 * M2; // Mi the local matrix at level i of the group tree. currentGroup.mStackedMatrix.set(currentMatrix); currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); for (int i = 0; i < mGroupList.size(); i++) { VGroup currentGroup = mGroupList.get(i); if (currentGroup != null) { drawPath(currentGroup, canvas, w, h); drawPath(currentGroup, canvas, w, h); // Draw the group tree in post order. for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { drawGroupTree(currentGroup.mChildGroupList.get(i), currentGroup.mStackedMatrix, canvas, w, h); } } } } public void draw(Canvas canvas, int w, int h) { // Travese the tree in pre-order to draw. drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h); } } private void drawPath(VGroup vGroup, Canvas canvas, int w, int h) { private void drawPath(VGroup vGroup, Canvas canvas, int w, int h) { final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); mMatrix.reset(); mFinalPathMatrix.set(vGroup.mStackedMatrix); mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); // The order we apply is the same as the mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); // RenderNode.cpp::applyViewPropertyTransforms(). mMatrix.postTranslate(-vGroup.mPivotX, -vGroup.mPivotY); mMatrix.postScale(vGroup.mScaleX, vGroup.mScaleY); mMatrix.postRotate(vGroup.mRotate, 0, 0); mMatrix.postTranslate(vGroup.mTranslateX + vGroup.mPivotX, vGroup.mTranslateY + vGroup.mPivotY); mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); ArrayList<VPath> paths = vGroup.getPaths(); ArrayList<VPath> paths = vGroup.getPaths(); for (int i = 0; i < paths.size(); i++) { for (int i = 0; i < paths.size(); i++) { Loading Loading @@ -507,7 +578,7 @@ public class VectorDrawable extends Drawable { mRenderPath.reset(); mRenderPath.reset(); mRenderPath.addPath(path, mMatrix); mRenderPath.addPath(path, mFinalPathMatrix); if (vPath.mClip) { if (vPath.mClip) { canvas.clipPath(mRenderPath, Region.Op.REPLACE); canvas.clipPath(mRenderPath, Region.Op.REPLACE); Loading Loading @@ -588,7 +659,8 @@ public class VectorDrawable extends Drawable { private static class VGroup { private static class VGroup { private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>(); private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>(); private final ArrayList<VPath> mVGList = new ArrayList<VPath>(); private final ArrayList<VPath> mPathList = new ArrayList<VPath>(); private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>(); private float mRotate = 0; private float mRotate = 0; private float mPivotX = 0; private float mPivotX = 0; Loading @@ -597,13 +669,34 @@ public class VectorDrawable extends Drawable { private float mScaleY = 1; private float mScaleY = 1; private float mTranslateX = 0; private float mTranslateX = 0; private float mTranslateY = 0; private float mTranslateY = 0; private float mAlpha = 1; // mLocalMatrix is parsed from the XML. private final Matrix mLocalMatrix = new Matrix(); // mStackedMatrix is only used when drawing, it combines all the // parents' local matrices with the current one. private final Matrix mStackedMatrix = new Matrix(); private int[] mThemeAttrs; private int[] mThemeAttrs; private String mName = null; public String getName() { return mName; } public Matrix getLocalMatrix() { return mLocalMatrix; } public void add(VPath path) { public void add(VPath path) { String id = path.getID(); String id = path.getID(); mVGPathMap.put(id, path); mVGPathMap.put(id, path); mVGList.add(path); mPathList.add(path); } public boolean canApplyTheme() { return mThemeAttrs != null; } } public void applyTheme(Theme t) { public void applyTheme(Theme t) { Loading @@ -621,6 +714,11 @@ public class VectorDrawable extends Drawable { mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); mAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mAlpha); updateLocalMatrix(); if (a.hasValue(R.styleable.VectorDrawableGroup_name)) { mName = a.getString(R.styleable.VectorDrawableGroup_name); } a.recycle(); a.recycle(); } } Loading Loading @@ -660,15 +758,34 @@ public class VectorDrawable extends Drawable { mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); } } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) { mName = a.getString(R.styleable.VectorDrawableGroup_name); } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) { mAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mAlpha); } updateLocalMatrix(); a.recycle(); a.recycle(); } } private void updateLocalMatrix() { // The order we apply is the same as the // RenderNode.cpp::applyViewPropertyTransforms(). mLocalMatrix.reset(); mLocalMatrix.postTranslate(-mPivotX, -mPivotY); mLocalMatrix.postScale(mScaleX, mScaleY); mLocalMatrix.postRotate(mRotate, 0, 0); mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); } /** /** * Must return in order of adding * Must return in order of adding * @return ordered list of paths * @return ordered list of paths */ */ public ArrayList<VPath> getPaths() { public ArrayList<VPath> getPaths() { return mVGList; return mPathList; } } } } Loading tests/VectorDrawableTest/res/drawable/vector_drawable22.xml 0 → 100644 +72 −0 Original line number Original line Diff line number Diff line <!-- Copyright (C) 2014 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. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size android:height="64dp" android:width="64dp" /> <viewport android:viewportHeight="400" android:viewportWidth="400" /> <group android:name="backgroundGroup" > <path android:name="background1" android:fill="#80000000" android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> <path android:name="background2" android:fill="#80000000" android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> </group> <group android:name="translateToCenterGroup" android:translateX="50.0" android:translateY="90.0" > <path android:name="twoLines" android:pathData="M 0,0 v 100 M 0,0 h 100" android:stroke="#FFFF0000" android:strokeWidth="20" /> <group android:name="rotationGroup" android:pivotX="0.0" android:pivotY="0.0" android:rotation="-45.0" > <path android:name="twoLines1" android:pathData="M 0,0 v 100 M 0,0 h 100" android:stroke="#FF00FF00" android:strokeWidth="20" /> <group android:name="translateGroup" android:translateX="130.0" android:translateY="160.0" > <group android:name="scaleGroup" > <path android:name="twoLines2" android:pathData="M 0,0 v 100 M 0,0 h 100" android:stroke="#FF0000FF" android:strokeWidth="20" /> </group> </group> </group> </group> </vector> No newline at end of file tests/VectorDrawableTest/res/drawable/vector_drawable23.xml 0 → 100644 +86 −0 Original line number Original line Diff line number Diff line <!-- Copyright (C) 2014 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. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size android:height="64dp" android:width="64dp" /> <viewport android:viewportHeight="400" android:viewportWidth="400" /> <group android:name="backgroundGroup" > <path android:name="background1" android:fill="#80000000" android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> <path android:name="background2" android:fill="#80000000" android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> </group> <group android:name="translateToCenterGroup" android:translateX="50.0" android:translateY="90.0" > <path android:name="twoLines" android:pathData="@string/twoLinePathData" android:stroke="#FFFF0000" android:strokeWidth="20" /> <group android:name="rotationGroup" android:pivotX="0.0" android:pivotY="0.0" android:rotation="-45.0" > <path android:name="twoLines1" android:pathData="@string/twoLinePathData" android:stroke="#FF00FF00" android:strokeWidth="20" /> <group android:name="translateGroup" android:translateX="130.0" android:translateY="160.0" > <group android:name="scaleGroup" > <path android:name="twoLines3" android:pathData="@string/twoLinePathData" android:stroke="#FF0000FF" android:strokeWidth="20" /> </group> </group> <group android:name="translateGroupHalf" android:translateX="65.0" android:translateY="80.0" > <group android:name="scaleGroup" > <path android:name="twoLines2" android:pathData="@string/twoLinePathData" android:fill="?android:attr/colorForeground" android:stroke="?android:attr/colorForeground" android:strokeWidth="20" /> </group> </group> </group> </group> </vector> No newline at end of file tests/VectorDrawableTest/res/values/strings.xml +1 −0 Original line number Original line Diff line number Diff line Loading @@ -15,4 +15,5 @@ --> --> <resources> <resources> <string name="twoLinePathData" >"M 0,0 v 100 M 0,0 h 100"</string> </resources> </resources> Loading
core/res/res/values/attrs.xml +2 −0 Original line number Original line Diff line number Diff line Loading @@ -4798,6 +4798,8 @@ <attr name="scaleX" /> <attr name="scaleX" /> <!-- The amount to scale the group on X coordinate --> <!-- The amount to scale the group on X coordinate --> <attr name="scaleY" /> <attr name="scaleY" /> <!-- The alpha of the group (0 is transparent and 1 is opaque) --> <attr name="alpha" /> </declare-styleable> </declare-styleable> <!-- Defines the path used in Vector Drawables. --> <!-- Defines the path used in Vector Drawables. --> Loading
graphics/java/android/graphics/drawable/VectorDrawable.java +175 −58 Original line number Original line Diff line number Diff line Loading @@ -40,6 +40,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.HashMap; import java.util.HashMap; import java.util.Stack; /** /** * This lets you create a drawable based on an XML vector graphic It can be * This lets you create a drawable based on an XML vector graphic It can be Loading Loading @@ -129,6 +130,8 @@ public class VectorDrawable extends Drawable { private static final int LINEJOIN_ROUND = 1; private static final int LINEJOIN_ROUND = 1; private static final int LINEJOIN_BEVEL = 2; private static final int LINEJOIN_BEVEL = 2; private static final boolean DBG_VECTOR_DRAWABLE = false; private final VectorDrawableState mVectorState; private final VectorDrawableState mVectorState; private int mAlpha = 0xFF; private int mAlpha = 0xFF; Loading Loading @@ -279,12 +282,17 @@ public class VectorDrawable extends Drawable { boolean noGroupTag = true; boolean noGroupTag = true; boolean noPathTag = true; boolean noPathTag = true; VGroup currentGroup = new VGroup(); // Use a stack to help to build the group tree. // The top of the stack is always the current group. final Stack<VGroup> groupStack = new Stack<VGroup>(); groupStack.push(pathRenderer.mRootGroup); int eventType = parser.getEventType(); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { while (eventType != XmlPullParser.END_DOCUMENT) { if (eventType == XmlPullParser.START_TAG) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); final String tagName = parser.getName(); final VGroup currentGroup = groupStack.peek(); if (SHAPE_PATH.equals(tagName)) { if (SHAPE_PATH.equals(tagName)) { final VPath path = new VPath(); final VPath path = new VPath(); path.inflate(res, attrs, theme); path.inflate(res, attrs, theme); Loading @@ -297,18 +305,24 @@ public class VectorDrawable extends Drawable { pathRenderer.parseViewport(res, attrs); pathRenderer.parseViewport(res, attrs); noViewportTag = false; noViewportTag = false; } else if (SHAPE_GROUP.equals(tagName)) { } else if (SHAPE_GROUP.equals(tagName)) { currentGroup = new VGroup(); VGroup newChildGroup = new VGroup(); currentGroup.inflate(res, attrs, theme); newChildGroup.inflate(res, attrs, theme); pathRenderer.mGroupList.add(currentGroup); currentGroup.mChildGroupList.add(newChildGroup); groupStack.push(newChildGroup); noGroupTag = false; noGroupTag = false; } } } else if (eventType == XmlPullParser.END_TAG) { final String tagName = parser.getName(); if (SHAPE_GROUP.equals(tagName)) { groupStack.pop(); } } } eventType = parser.next(); eventType = parser.next(); } } if (noGroupTag && !noPathTag) { // Print the tree out for debug. pathRenderer.mGroupList.add(currentGroup); if (DBG_VECTOR_DRAWABLE) { printGroupTree(pathRenderer.mRootGroup, 0); } } if (noSizeTag || noViewportTag || noPathTag) { if (noSizeTag || noViewportTag || noPathTag) { Loading Loading @@ -338,6 +352,21 @@ public class VectorDrawable extends Drawable { return pathRenderer; return pathRenderer; } } private void printGroupTree(VGroup currentGroup, int level) { String indent = ""; for (int i = 0 ; i < level ; i++) { indent += " "; } // Print the current node Log.v(LOGTAG, indent + "current group is :" + currentGroup.getName() + " rotation is " + currentGroup.mRotate); Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString()); // Then print all the children for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { printGroupTree(currentGroup.mChildGroupList.get(i), level + 1); } } private void setPathRenderer(VPathRenderer pathRenderer) { private void setPathRenderer(VPathRenderer pathRenderer) { mVectorState.mVPathRenderer = pathRenderer; mVectorState.mVPathRenderer = pathRenderer; } } Loading @@ -350,6 +379,7 @@ public class VectorDrawable extends Drawable { public VectorDrawableState(VectorDrawableState copy) { public VectorDrawableState(VectorDrawableState copy) { if (copy != null) { if (copy != null) { mChangingConfigurations = copy.mChangingConfigurations; mChangingConfigurations = copy.mChangingConfigurations; // TODO: Make sure the constant state are handled correctly. mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); mVPathRenderer = new VPathRenderer(copy.mVPathRenderer); mPadding = new Rect(copy.mPadding); mPadding = new Rect(copy.mPadding); } } Loading Loading @@ -377,28 +407,42 @@ public class VectorDrawable extends Drawable { } } private static class VPathRenderer { private static class VPathRenderer { /* Right now the internal data structure is organized as a tree. * Each node can be a group node, or a path. * A group node can have groups or paths as children, but a path node has * no children. * One example can be: * Root Group * / | \ * Group Path Group * / \ | * Path Path Path * */ private final VGroup mRootGroup; private final Path mPath = new Path(); private final Path mPath = new Path(); private final Path mRenderPath = new Path(); private final Path mRenderPath = new Path(); private final Matrix mMatrix = new Matrix(); private static final Matrix IDENTITY_MATRIX = new Matrix(); private Paint mStrokePaint; private Paint mStrokePaint; private Paint mFillPaint; private Paint mFillPaint; private ColorFilter mColorFilter; private ColorFilter mColorFilter; private PathMeasure mPathMeasure; private PathMeasure mPathMeasure; final ArrayList<VGroup> mGroupList = new ArrayList<VGroup>(); private float mBaseWidth = 0; private float mBaseHeight = 0; private float mViewportWidth = 0; private float mViewportHeight = 0; float mBaseWidth = 0; private final Matrix mFinalPathMatrix = new Matrix(); float mBaseHeight = 0; float mViewportWidth = 0; float mViewportHeight = 0; public VPathRenderer() { public VPathRenderer() { mRootGroup = new VGroup(); } } public VPathRenderer(VPathRenderer copy) { public VPathRenderer(VPathRenderer copy) { mGroupList.addAll(copy.mGroupList); mRootGroup = copy.mRootGroup; mBaseWidth = copy.mBaseWidth; mBaseWidth = copy.mBaseWidth; mBaseHeight = copy.mBaseHeight; mBaseHeight = copy.mBaseHeight; mViewportWidth = copy.mViewportHeight; mViewportWidth = copy.mViewportHeight; Loading @@ -406,33 +450,59 @@ public class VectorDrawable extends Drawable { } } public boolean canApplyTheme() { public boolean canApplyTheme() { final ArrayList<VGroup> groups = mGroupList; // If one of the paths can apply theme, then return true; for (int i = groups.size() - 1; i >= 0; i--) { return recursiveCanApplyTheme(mRootGroup); final ArrayList<VPath> paths = groups.get(i).mVGList; } private boolean recursiveCanApplyTheme(VGroup currentGroup) { // We can do a tree traverse here, if there is one path return true, // then we return true for the whole tree. final ArrayList<VPath> paths = currentGroup.mPathList; for (int j = paths.size() - 1; j >= 0; j--) { for (int j = paths.size() - 1; j >= 0; j--) { final VPath path = paths.get(j); final VPath path = paths.get(j); if (path.canApplyTheme()) { if (path.canApplyTheme()) { return true; return true; } } } } } final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; for (int i = 0; i < childGroups.size(); i++) { VGroup childGroup = childGroups.get(i); if (childGroup.canApplyTheme() || recursiveCanApplyTheme(childGroup)) { return true; } } return false; return false; } } public void applyTheme(Theme t) { public void applyTheme(Theme t) { final ArrayList<VGroup> groups = mGroupList; // Apply theme to every path of the tree. for (int i = groups.size() - 1; i >= 0; i--) { recursiveApplyTheme(mRootGroup, t); VGroup currentGroup = groups.get(i); } currentGroup.applyTheme(t); final ArrayList<VPath> paths = currentGroup.mVGList; private void recursiveApplyTheme(VGroup currentGroup, Theme t) { // We can do a tree traverse here, apply theme to all paths which // can apply theme. final ArrayList<VPath> paths = currentGroup.mPathList; for (int j = paths.size() - 1; j >= 0; j--) { for (int j = paths.size() - 1; j >= 0; j--) { final VPath path = paths.get(j); final VPath path = paths.get(j); if (path.canApplyTheme()) { if (path.canApplyTheme()) { path.applyTheme(t); path.applyTheme(t); } } } } final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList; for (int i = 0; i < childGroups.size(); i++) { VGroup childGroup = childGroups.get(i); if (childGroup.canApplyTheme()) { childGroup.applyTheme(t); } } recursiveApplyTheme(childGroup, t); } } } public void setColorFilter(ColorFilter colorFilter) { public void setColorFilter(ColorFilter colorFilter) { Loading @@ -448,34 +518,35 @@ public class VectorDrawable extends Drawable { } } public void draw(Canvas canvas, int w, int h) { private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix, if (mGroupList == null || mGroupList.size() == 0) { Canvas canvas, int w, int h) { Log.e(LOGTAG,"There is no group to draw"); // Calculate current group's matrix by preConcat the parent's and return; // and the current one on the top of the stack. } // Basically the Mfinal = Mviewport * M0 * M1 * M2; // Mi the local matrix at level i of the group tree. currentGroup.mStackedMatrix.set(currentMatrix); currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix); for (int i = 0; i < mGroupList.size(); i++) { VGroup currentGroup = mGroupList.get(i); if (currentGroup != null) { drawPath(currentGroup, canvas, w, h); drawPath(currentGroup, canvas, w, h); // Draw the group tree in post order. for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) { drawGroupTree(currentGroup.mChildGroupList.get(i), currentGroup.mStackedMatrix, canvas, w, h); } } } } public void draw(Canvas canvas, int w, int h) { // Travese the tree in pre-order to draw. drawGroupTree(mRootGroup, IDENTITY_MATRIX, canvas, w, h); } } private void drawPath(VGroup vGroup, Canvas canvas, int w, int h) { private void drawPath(VGroup vGroup, Canvas canvas, int w, int h) { final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); final float scale = Math.min(h / mViewportHeight, w / mViewportWidth); mMatrix.reset(); mFinalPathMatrix.set(vGroup.mStackedMatrix); mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); // The order we apply is the same as the mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); // RenderNode.cpp::applyViewPropertyTransforms(). mMatrix.postTranslate(-vGroup.mPivotX, -vGroup.mPivotY); mMatrix.postScale(vGroup.mScaleX, vGroup.mScaleY); mMatrix.postRotate(vGroup.mRotate, 0, 0); mMatrix.postTranslate(vGroup.mTranslateX + vGroup.mPivotX, vGroup.mTranslateY + vGroup.mPivotY); mMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f); mMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f); ArrayList<VPath> paths = vGroup.getPaths(); ArrayList<VPath> paths = vGroup.getPaths(); for (int i = 0; i < paths.size(); i++) { for (int i = 0; i < paths.size(); i++) { Loading Loading @@ -507,7 +578,7 @@ public class VectorDrawable extends Drawable { mRenderPath.reset(); mRenderPath.reset(); mRenderPath.addPath(path, mMatrix); mRenderPath.addPath(path, mFinalPathMatrix); if (vPath.mClip) { if (vPath.mClip) { canvas.clipPath(mRenderPath, Region.Op.REPLACE); canvas.clipPath(mRenderPath, Region.Op.REPLACE); Loading Loading @@ -588,7 +659,8 @@ public class VectorDrawable extends Drawable { private static class VGroup { private static class VGroup { private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>(); private final HashMap<String, VPath> mVGPathMap = new HashMap<String, VPath>(); private final ArrayList<VPath> mVGList = new ArrayList<VPath>(); private final ArrayList<VPath> mPathList = new ArrayList<VPath>(); private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>(); private float mRotate = 0; private float mRotate = 0; private float mPivotX = 0; private float mPivotX = 0; Loading @@ -597,13 +669,34 @@ public class VectorDrawable extends Drawable { private float mScaleY = 1; private float mScaleY = 1; private float mTranslateX = 0; private float mTranslateX = 0; private float mTranslateY = 0; private float mTranslateY = 0; private float mAlpha = 1; // mLocalMatrix is parsed from the XML. private final Matrix mLocalMatrix = new Matrix(); // mStackedMatrix is only used when drawing, it combines all the // parents' local matrices with the current one. private final Matrix mStackedMatrix = new Matrix(); private int[] mThemeAttrs; private int[] mThemeAttrs; private String mName = null; public String getName() { return mName; } public Matrix getLocalMatrix() { return mLocalMatrix; } public void add(VPath path) { public void add(VPath path) { String id = path.getID(); String id = path.getID(); mVGPathMap.put(id, path); mVGPathMap.put(id, path); mVGList.add(path); mPathList.add(path); } public boolean canApplyTheme() { return mThemeAttrs != null; } } public void applyTheme(Theme t) { public void applyTheme(Theme t) { Loading @@ -621,6 +714,11 @@ public class VectorDrawable extends Drawable { mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY); mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX); mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); mAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mAlpha); updateLocalMatrix(); if (a.hasValue(R.styleable.VectorDrawableGroup_name)) { mName = a.getString(R.styleable.VectorDrawableGroup_name); } a.recycle(); a.recycle(); } } Loading Loading @@ -660,15 +758,34 @@ public class VectorDrawable extends Drawable { mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY); } } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) { mName = a.getString(R.styleable.VectorDrawableGroup_name); } if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) { mAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mAlpha); } updateLocalMatrix(); a.recycle(); a.recycle(); } } private void updateLocalMatrix() { // The order we apply is the same as the // RenderNode.cpp::applyViewPropertyTransforms(). mLocalMatrix.reset(); mLocalMatrix.postTranslate(-mPivotX, -mPivotY); mLocalMatrix.postScale(mScaleX, mScaleY); mLocalMatrix.postRotate(mRotate, 0, 0); mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); } /** /** * Must return in order of adding * Must return in order of adding * @return ordered list of paths * @return ordered list of paths */ */ public ArrayList<VPath> getPaths() { public ArrayList<VPath> getPaths() { return mVGList; return mPathList; } } } } Loading
tests/VectorDrawableTest/res/drawable/vector_drawable22.xml 0 → 100644 +72 −0 Original line number Original line Diff line number Diff line <!-- Copyright (C) 2014 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. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size android:height="64dp" android:width="64dp" /> <viewport android:viewportHeight="400" android:viewportWidth="400" /> <group android:name="backgroundGroup" > <path android:name="background1" android:fill="#80000000" android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> <path android:name="background2" android:fill="#80000000" android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> </group> <group android:name="translateToCenterGroup" android:translateX="50.0" android:translateY="90.0" > <path android:name="twoLines" android:pathData="M 0,0 v 100 M 0,0 h 100" android:stroke="#FFFF0000" android:strokeWidth="20" /> <group android:name="rotationGroup" android:pivotX="0.0" android:pivotY="0.0" android:rotation="-45.0" > <path android:name="twoLines1" android:pathData="M 0,0 v 100 M 0,0 h 100" android:stroke="#FF00FF00" android:strokeWidth="20" /> <group android:name="translateGroup" android:translateX="130.0" android:translateY="160.0" > <group android:name="scaleGroup" > <path android:name="twoLines2" android:pathData="M 0,0 v 100 M 0,0 h 100" android:stroke="#FF0000FF" android:strokeWidth="20" /> </group> </group> </group> </group> </vector> No newline at end of file
tests/VectorDrawableTest/res/drawable/vector_drawable23.xml 0 → 100644 +86 −0 Original line number Original line Diff line number Diff line <!-- Copyright (C) 2014 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. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" > <size android:height="64dp" android:width="64dp" /> <viewport android:viewportHeight="400" android:viewportWidth="400" /> <group android:name="backgroundGroup" > <path android:name="background1" android:fill="#80000000" android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" /> <path android:name="background2" android:fill="#80000000" android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" /> </group> <group android:name="translateToCenterGroup" android:translateX="50.0" android:translateY="90.0" > <path android:name="twoLines" android:pathData="@string/twoLinePathData" android:stroke="#FFFF0000" android:strokeWidth="20" /> <group android:name="rotationGroup" android:pivotX="0.0" android:pivotY="0.0" android:rotation="-45.0" > <path android:name="twoLines1" android:pathData="@string/twoLinePathData" android:stroke="#FF00FF00" android:strokeWidth="20" /> <group android:name="translateGroup" android:translateX="130.0" android:translateY="160.0" > <group android:name="scaleGroup" > <path android:name="twoLines3" android:pathData="@string/twoLinePathData" android:stroke="#FF0000FF" android:strokeWidth="20" /> </group> </group> <group android:name="translateGroupHalf" android:translateX="65.0" android:translateY="80.0" > <group android:name="scaleGroup" > <path android:name="twoLines2" android:pathData="@string/twoLinePathData" android:fill="?android:attr/colorForeground" android:stroke="?android:attr/colorForeground" android:strokeWidth="20" /> </group> </group> </group> </group> </vector> No newline at end of file
tests/VectorDrawableTest/res/values/strings.xml +1 −0 Original line number Original line Diff line number Diff line Loading @@ -15,4 +15,5 @@ --> --> <resources> <resources> <string name="twoLinePathData" >"M 0,0 v 100 M 0,0 h 100"</string> </resources> </resources>