1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.transition; 17 18 import com.android.internal.R; 19 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Matrix; 23 import android.graphics.Path; 24 import android.graphics.PathMeasure; 25 import android.util.AttributeSet; 26 import android.util.FloatMath; 27 import android.util.PathParser; 28 29 /** 30 * A PathMotion that takes a Path pattern and applies it to the separation between two points. 31 * The starting point of the Path will be moved to the origin and the end point will be scaled 32 * and rotated so that it matches with the target end point. 33 * <p>This may be used in XML as an element inside a transition.</p> 34 * <pre> 35 * {@code 36 * <changeBounds> 37 * <patternPathMotion android:patternPathData="M0 0 L0 100 L100 100"/> 38 * </changeBounds>} 39 * </pre> 40 */ 41 public class PatternPathMotion extends PathMotion { 42 43 private Path mOriginalPatternPath; 44 45 private final Path mPatternPath = new Path(); 46 47 private final Matrix mTempMatrix = new Matrix(); 48 49 /** 50 * Constructs a PatternPathMotion with a straight-line pattern. 51 */ 52 public PatternPathMotion() { 53 mPatternPath.lineTo(1, 0); 54 mOriginalPatternPath = mPatternPath; 55 } 56 57 public PatternPathMotion(Context context, AttributeSet attrs) { 58 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PatternPathMotion); 59 try { 60 String pathData = a.getString(R.styleable.PatternPathMotion_patternPathData); 61 if (pathData == null) { 62 throw new RuntimeException("pathData must be supplied for patternPathMotion"); 63 } 64 Path pattern = PathParser.createPathFromPathData(pathData); 65 setPatternPath(pattern); 66 } finally { 67 a.recycle(); 68 } 69 70 } 71 72 /** 73 * Creates a PatternPathMotion with the Path defining a pattern of motion between two 74 * coordinates. The pattern will be translated, rotated, and scaled to fit between the start 75 * and end points. The pattern must not be empty and must have the end point differ from the 76 * start point. 77 * 78 * @param patternPath A Path to be used as a pattern for two-dimensional motion. 79 */ 80 public PatternPathMotion(Path patternPath) { 81 setPatternPath(patternPath); 82 } 83 84 /** 85 * Returns the Path defining a pattern of motion between two coordinates. 86 * The pattern will be translated, rotated, and scaled to fit between the start and end points. 87 * The pattern must not be empty and must have the end point differ from the start point. 88 * 89 * @return the Path defining a pattern of motion between two coordinates. 90 * @attr ref android.R.styleable#PatternPathMotion_patternPathData 91 */ 92 public Path getPatternPath() { 93 return mOriginalPatternPath; 94 } 95 96 /** 97 * Sets the Path defining a pattern of motion between two coordinates. 98 * The pattern will be translated, rotated, and scaled to fit between the start and end points. 99 * The pattern must not be empty and must have the end point differ from the start point. 100 * 101 * @param patternPath A Path to be used as a pattern for two-dimensional motion. 102 * @attr ref android.R.styleable#PatternPathMotion_patternPathData 103 */ 104 public void setPatternPath(Path patternPath) { 105 PathMeasure pathMeasure = new PathMeasure(patternPath, false); 106 float length = pathMeasure.getLength(); 107 float[] pos = new float[2]; 108 pathMeasure.getPosTan(length, pos, null); 109 float endX = pos[0]; 110 float endY = pos[1]; 111 pathMeasure.getPosTan(0, pos, null); 112 float startX = pos[0]; 113 float startY = pos[1]; 114 115 if (startX == endX && startY == endY) { 116 throw new IllegalArgumentException("pattern must not end at the starting point"); 117 } 118 119 mTempMatrix.setTranslate(-startX, -startY); 120 float dx = endX - startX; 121 float dy = endY - startY; 122 float distance = distance(dx, dy); 123 float scale = 1 / distance; 124 mTempMatrix.postScale(scale, scale); 125 double angle = Math.atan2(dy, dx); 126 mTempMatrix.postRotate((float) Math.toDegrees(-angle)); 127 patternPath.transform(mTempMatrix, mPatternPath); 128 mOriginalPatternPath = patternPath; 129 } 130 131 @Override 132 public Path getPath(float startX, float startY, float endX, float endY) { 133 float dx = endX - startX; 134 float dy = endY - startY; 135 float length = distance(dx, dy); 136 double angle = Math.atan2(dy, dx); 137 138 mTempMatrix.setScale(length, length); 139 mTempMatrix.postRotate((float) Math.toDegrees(angle)); 140 mTempMatrix.postTranslate(startX, startY); 141 Path path = new Path(); 142 mPatternPath.transform(mTempMatrix, path); 143 return path; 144 } 145 146 private static float distance(float x, float y) { 147 return FloatMath.sqrt((x * x) + (y * y)); 148 } 149 } 150