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.PathParser; 27 28 /** 29 * A PathMotion that takes a Path pattern and applies it to the separation between two points. 30 * The starting point of the Path will be moved to the origin and the end point will be scaled 31 * and rotated so that it matches with the target end point. 32 * <p>This may be used in XML as an element inside a transition.</p> 33 * <pre> 34 * {@code 35 * <changeBounds> 36 * <patternPathMotion android:patternPathData="M0 0 L0 100 L100 100"/> 37 * </changeBounds>} 38 * </pre> 39 */ 40 public class PatternPathMotion extends PathMotion { 41 42 private Path mOriginalPatternPath; 43 44 private final Path mPatternPath = new Path(); 45 46 private final Matrix mTempMatrix = new Matrix(); 47 48 /** 49 * Constructs a PatternPathMotion with a straight-line pattern. 50 */ PatternPathMotion()51 public PatternPathMotion() { 52 mPatternPath.lineTo(1, 0); 53 mOriginalPatternPath = mPatternPath; 54 } 55 PatternPathMotion(Context context, AttributeSet attrs)56 public PatternPathMotion(Context context, AttributeSet attrs) { 57 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PatternPathMotion); 58 try { 59 String pathData = a.getString(R.styleable.PatternPathMotion_patternPathData); 60 if (pathData == null) { 61 throw new RuntimeException("pathData must be supplied for patternPathMotion"); 62 } 63 Path pattern = PathParser.createPathFromPathData(pathData); 64 setPatternPath(pattern); 65 } finally { 66 a.recycle(); 67 } 68 69 } 70 71 /** 72 * Creates a PatternPathMotion with the Path defining a pattern of motion between two 73 * coordinates. The pattern will be translated, rotated, and scaled to fit between the start 74 * and end points. The pattern must not be empty and must have the end point differ from the 75 * start point. 76 * 77 * @param patternPath A Path to be used as a pattern for two-dimensional motion. 78 */ PatternPathMotion(Path patternPath)79 public PatternPathMotion(Path patternPath) { 80 setPatternPath(patternPath); 81 } 82 83 /** 84 * Returns the Path defining a pattern of motion between two coordinates. 85 * The pattern will be translated, rotated, and scaled to fit between the start and end points. 86 * The pattern must not be empty and must have the end point differ from the start point. 87 * 88 * @return the Path defining a pattern of motion between two coordinates. 89 * @attr ref android.R.styleable#PatternPathMotion_patternPathData 90 */ getPatternPath()91 public Path getPatternPath() { 92 return mOriginalPatternPath; 93 } 94 95 /** 96 * Sets the Path defining a pattern of motion between two coordinates. 97 * The pattern will be translated, rotated, and scaled to fit between the start and end points. 98 * The pattern must not be empty and must have the end point differ from the start point. 99 * 100 * @param patternPath A Path to be used as a pattern for two-dimensional motion. 101 * @attr ref android.R.styleable#PatternPathMotion_patternPathData 102 */ setPatternPath(Path patternPath)103 public void setPatternPath(Path patternPath) { 104 PathMeasure pathMeasure = new PathMeasure(patternPath, false); 105 float length = pathMeasure.getLength(); 106 float[] pos = new float[2]; 107 pathMeasure.getPosTan(length, pos, null); 108 float endX = pos[0]; 109 float endY = pos[1]; 110 pathMeasure.getPosTan(0, pos, null); 111 float startX = pos[0]; 112 float startY = pos[1]; 113 114 if (startX == endX && startY == endY) { 115 throw new IllegalArgumentException("pattern must not end at the starting point"); 116 } 117 118 mTempMatrix.setTranslate(-startX, -startY); 119 float dx = endX - startX; 120 float dy = endY - startY; 121 float distance = (float) Math.hypot(dx, dy); 122 float scale = 1 / distance; 123 mTempMatrix.postScale(scale, scale); 124 double angle = Math.atan2(dy, dx); 125 mTempMatrix.postRotate((float) Math.toDegrees(-angle)); 126 patternPath.transform(mTempMatrix, mPatternPath); 127 mOriginalPatternPath = patternPath; 128 } 129 130 @Override getPath(float startX, float startY, float endX, float endY)131 public Path getPath(float startX, float startY, float endX, float endY) { 132 double dx = endX - startX; 133 double dy = endY - startY; 134 float length = (float) Math.hypot(dx, dy); 135 double angle = Math.atan2(dy, dx); 136 137 mTempMatrix.setScale(length, length); 138 mTempMatrix.postRotate((float) Math.toDegrees(angle)); 139 mTempMatrix.postTranslate(startX, startY); 140 Path path = new Path(); 141 mPatternPath.transform(mTempMatrix, path); 142 return path; 143 } 144 145 } 146