1317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki/* 2317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Copyright (C) 2017 The Android Open Source Project 3317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * 4317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Licensed under the Apache License, Version 2.0 (the "License"); 5317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * you may not use this file except in compliance with the License. 6317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * You may obtain a copy of the License at 7317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * 8317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * http://www.apache.org/licenses/LICENSE-2.0 9317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * 10317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Unless required by applicable law or agreed to in writing, software 11317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * distributed under the License is distributed on an "AS IS" BASIS, 12317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * See the License for the specific language governing permissions and 14317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * limitations under the License. 15317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki */ 16317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 17317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Arakipackage android.support.transition; 18317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 19142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport android.content.Context; 20142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport android.content.res.TypedArray; 21317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Arakiimport android.graphics.Matrix; 22317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Arakiimport android.graphics.Path; 23317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Arakiimport android.graphics.PathMeasure; 24142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport android.support.v4.content.res.TypedArrayUtils; 25142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport android.support.v4.graphics.PathParser; 26142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport android.util.AttributeSet; 27142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki 28142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Arakiimport org.xmlpull.v1.XmlPullParser; 29317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 30317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki/** 31317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * A PathMotion that takes a Path pattern and applies it to the separation between two points. 32317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * The starting point of the Path will be moved to the origin and the end point will be scaled 33317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * and rotated so that it matches with the target end point. 34b97d1415b97f0c695f4c2e79c935141d9954274eYuichi Araki * <p>This may be used in XML as an element inside a transition.</p> 35b97d1415b97f0c695f4c2e79c935141d9954274eYuichi Araki * <pre>{@code 36b97d1415b97f0c695f4c2e79c935141d9954274eYuichi Araki * <changeBounds> 37b97d1415b97f0c695f4c2e79c935141d9954274eYuichi Araki * <patternPathMotion android:patternPathData="M0 0 L0 100 L100 100"/> 38b97d1415b97f0c695f4c2e79c935141d9954274eYuichi Araki * </changeBounds>} 39b97d1415b97f0c695f4c2e79c935141d9954274eYuichi Araki * </pre> 40317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki */ 41317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Arakipublic class PatternPathMotion extends PathMotion { 42317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 43317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki private Path mOriginalPatternPath; 44317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 45317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki private final Path mPatternPath = new Path(); 46317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 47317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki private final Matrix mTempMatrix = new Matrix(); 48317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 49317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki /** 50317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Constructs a PatternPathMotion with a straight-line pattern. 51317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki */ 52317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki public PatternPathMotion() { 53317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mPatternPath.lineTo(1, 0); 54317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mOriginalPatternPath = mPatternPath; 55317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 56317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 57142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki public PatternPathMotion(Context context, AttributeSet attrs) { 58142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki TypedArray a = context.obtainStyledAttributes(attrs, Styleable.PATTERN_PATH_MOTION); 59142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki try { 60142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki String pathData = TypedArrayUtils.getNamedString(a, (XmlPullParser) attrs, 61142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki "patternPathData", Styleable.PatternPathMotion.PATTERN_PATH_DATA); 62142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki if (pathData == null) { 63142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki throw new RuntimeException("pathData must be supplied for patternPathMotion"); 64142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki } 65142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki Path pattern = PathParser.createPathFromPathData(pathData); 66142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki setPatternPath(pattern); 67142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki } finally { 68142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki a.recycle(); 69142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki } 70142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki } 71142a5654b5eff546b6fa1b9b938896356ff9b03bYuichi Araki 72317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki /** 73317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Creates a PatternPathMotion with the Path defining a pattern of motion between two 74317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * coordinates. The pattern will be translated, rotated, and scaled to fit between the start 75317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * and end points. The pattern must not be empty and must have the end point differ from the 76317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * start point. 77317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * 78317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * @param patternPath A Path to be used as a pattern for two-dimensional motion. 79317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki */ 80317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki public PatternPathMotion(Path patternPath) { 81317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki setPatternPath(patternPath); 82317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 83317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 84317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki /** 85317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Returns the Path defining a pattern of motion between two coordinates. 86317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * The pattern will be translated, rotated, and scaled to fit between the start and end points. 87317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * The pattern must not be empty and must have the end point differ from the start point. 88317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * 89317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * @return the Path defining a pattern of motion between two coordinates. 90317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki */ 91317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki public Path getPatternPath() { 92317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki return mOriginalPatternPath; 93317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 94317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 95317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki /** 96317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * Sets the Path defining a pattern of motion between two coordinates. 97317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * The pattern will be translated, rotated, and scaled to fit between the start and end points. 98317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * The pattern must not be empty and must have the end point differ from the start point. 99317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * 100317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki * @param patternPath A Path to be used as a pattern for two-dimensional motion. 101317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki */ 102317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki public void setPatternPath(Path patternPath) { 103317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki PathMeasure pathMeasure = new PathMeasure(patternPath, false); 104317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float length = pathMeasure.getLength(); 105317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float[] pos = new float[2]; 106317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki pathMeasure.getPosTan(length, pos, null); 107317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float endX = pos[0]; 108317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float endY = pos[1]; 109317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki pathMeasure.getPosTan(0, pos, null); 110317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float startX = pos[0]; 111317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float startY = pos[1]; 112317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 113317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki if (startX == endX && startY == endY) { 114317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki throw new IllegalArgumentException("pattern must not end at the starting point"); 115317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 116317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 117317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mTempMatrix.setTranslate(-startX, -startY); 118317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float dx = endX - startX; 119317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float dy = endY - startY; 120317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float distance = distance(dx, dy); 121317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float scale = 1 / distance; 122317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mTempMatrix.postScale(scale, scale); 123317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki double angle = Math.atan2(dy, dx); 124317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mTempMatrix.postRotate((float) Math.toDegrees(-angle)); 125317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki patternPath.transform(mTempMatrix, mPatternPath); 126317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mOriginalPatternPath = patternPath; 127317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 128317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 129317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki @Override 130317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki public Path getPath(float startX, float startY, float endX, float endY) { 131317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float dx = endX - startX; 132317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float dy = endY - startY; 133317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki float length = distance(dx, dy); 134317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki double angle = Math.atan2(dy, dx); 135317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 136317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mTempMatrix.setScale(length, length); 137317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mTempMatrix.postRotate((float) Math.toDegrees(angle)); 138317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mTempMatrix.postTranslate(startX, startY); 139317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki Path path = new Path(); 140317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki mPatternPath.transform(mTempMatrix, path); 141317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki return path; 142317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 143317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 144317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki private static float distance(float x, float y) { 145317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki return (float) Math.sqrt((x * x) + (y * y)); 146317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki } 147317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki 148317d4bb802dbd80c9ddf1382e037769e58dff919Yuichi Araki} 149