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