1 /* 2 * Copyright (C) 2018 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 17 package androidx.constraintlayout.motion.widget; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.content.res.TypedArray; 22 import android.content.res.XmlResourceParser; 23 import android.graphics.RectF; 24 import android.util.AttributeSet; 25 import android.util.Log; 26 import android.util.SparseArray; 27 import android.util.SparseIntArray; 28 import android.util.TypedValue; 29 import android.util.Xml; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.animation.AccelerateDecelerateInterpolator; 33 import android.view.animation.AccelerateInterpolator; 34 import android.view.animation.AnimationUtils; 35 import android.view.animation.AnticipateInterpolator; 36 import android.view.animation.BounceInterpolator; 37 import android.view.animation.DecelerateInterpolator; 38 import android.view.animation.Interpolator; 39 import android.view.animation.OvershootInterpolator; 40 41 import androidx.constraintlayout.core.motion.utils.Easing; 42 import androidx.constraintlayout.widget.ConstraintSet; 43 import androidx.constraintlayout.widget.R; 44 import androidx.constraintlayout.widget.StateSet; 45 46 import org.xmlpull.v1.XmlPullParser; 47 import org.xmlpull.v1.XmlPullParserException; 48 49 import java.io.IOException; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.HashMap; 53 import java.util.List; 54 import java.util.Map; 55 56 /** 57 * The information to transition between multiple ConstraintSets 58 * This Class is meant to be used from XML 59 * 60 */ 61 public class MotionScene { 62 private static final String TAG = "MotionScene"; 63 private static final boolean DEBUG = false; 64 private static final int MIN_DURATION = 8; 65 static final int TRANSITION_BACKWARD = 0; 66 static final int TRANSITION_FORWARD = 1; 67 private static final int SPLINE_STRING = -1; 68 private static final int INTERPOLATOR_REFERENCE_ID = -2; 69 public static final int UNSET = -1; 70 private final MotionLayout mMotionLayout; 71 StateSet mStateSet = null; 72 Transition mCurrentTransition = null; 73 private boolean mDisableAutoTransition = false; 74 private ArrayList<Transition> mTransitionList = new ArrayList<>(); 75 private Transition mDefaultTransition = null; 76 private ArrayList<Transition> mAbstractTransitionList = new ArrayList<>(); 77 78 private SparseArray<ConstraintSet> mConstraintSetMap = new SparseArray<>(); 79 private HashMap<String, Integer> mConstraintSetIdMap = new HashMap<>(); 80 private SparseIntArray mDeriveMap = new SparseIntArray(); 81 private static final boolean DEBUG_DESKTOP = false; 82 private int mDefaultDuration = 400; 83 private int mLayoutDuringTransition = 0; 84 public static final int LAYOUT_IGNORE_REQUEST = 0; 85 public static final int LAYOUT_HONOR_REQUEST = 1; 86 public static final int LAYOUT_CALL_MEASURE = 2; 87 88 private MotionEvent mLastTouchDown; 89 private boolean mIgnoreTouch = false; 90 private boolean mMotionOutsideRegion = false; 91 private MotionLayout.MotionTracker mVelocityTracker; // used to support fling 92 private boolean mRtl; 93 private static final String MOTIONSCENE_TAG = "MotionScene"; 94 private static final String TRANSITION_TAG = "Transition"; 95 private static final String ONSWIPE_TAG = "OnSwipe"; 96 private static final String ONCLICK_TAG = "OnClick"; 97 private static final String STATESET_TAG = "StateSet"; 98 private static final String INCLUDE_TAG_UC = "Include"; 99 private static final String INCLUDE_TAG = "include"; 100 private static final String KEYFRAMESET_TAG = "KeyFrameSet"; 101 private static final String CONSTRAINTSET_TAG = "ConstraintSet"; 102 private static final String VIEW_TRANSITION = "ViewTransition"; 103 final ViewTransitionController mViewTransitionController; 104 105 /** 106 * Set the transition between two constraint set / states. 107 * The transition will get created between the two sets 108 * if it doesn't exist already. 109 * 110 * @param beginId id of the start constraint set or state 111 * @param endId id of the end constraint set or state 112 */ setTransition(int beginId, int endId)113 void setTransition(int beginId, int endId) { 114 int start = beginId; 115 int end = endId; 116 if (mStateSet != null) { 117 int tmp = mStateSet.stateGetConstraintID(beginId, -1, -1); 118 if (tmp != -1) { 119 start = tmp; 120 } 121 tmp = mStateSet.stateGetConstraintID(endId, -1, -1); 122 if (tmp != -1) { 123 end = tmp; 124 } 125 } 126 if (DEBUG) { 127 Log.v(TAG, Debug.getLocation() + " setTransition " 128 + Debug.getName(mMotionLayout.getContext(), beginId) + " -> " 129 + Debug.getName(mMotionLayout.getContext(), endId)); 130 } 131 if (mCurrentTransition != null) { 132 if (mCurrentTransition.mConstraintSetEnd == endId 133 && mCurrentTransition.mConstraintSetStart == beginId) { 134 return; 135 } 136 } 137 for (Transition transition : mTransitionList) { 138 if ((transition.mConstraintSetEnd == end 139 && transition.mConstraintSetStart == start) 140 || (transition.mConstraintSetEnd == endId 141 && transition.mConstraintSetStart == beginId)) { 142 if (DEBUG) { 143 Log.v(TAG, Debug.getLocation() + " found transition " 144 + Debug.getName(mMotionLayout.getContext(), beginId) + " -> " 145 + Debug.getName(mMotionLayout.getContext(), endId)); 146 } 147 mCurrentTransition = transition; 148 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 149 mCurrentTransition.mTouchResponse.setRTL(mRtl); 150 } 151 return; 152 } 153 } 154 // No transition defined for this so we will create one? 155 Transition matchTransition = mDefaultTransition; 156 for (Transition transition : mAbstractTransitionList) { 157 if (transition.mConstraintSetEnd == endId) { 158 matchTransition = transition; 159 } 160 } 161 Transition t = new Transition(this, matchTransition); 162 163 t.mConstraintSetStart = start; 164 t.mConstraintSetEnd = end; 165 if (start != UNSET) { 166 mTransitionList.add(t); 167 } 168 mCurrentTransition = t; 169 } 170 171 /** 172 * Add a transition to the motion scene. If a transition with the same id already exists 173 * in the scene, the new transition will replace the existing one. 174 * 175 * @throws IllegalArgumentException if the transition does not have an id. 176 */ addTransition(Transition transition)177 public void addTransition(Transition transition) { 178 int index = getIndex(transition); 179 if (index == -1) { 180 mTransitionList.add(transition); 181 } else { 182 mTransitionList.set(index, transition); 183 } 184 } 185 186 /** 187 * Remove the transition with the matching id from the motion scene. If no matching transition 188 * is found, it does nothing. 189 * 190 * @throws IllegalArgumentException if the transition does not have an id. 191 */ removeTransition(Transition transition)192 public void removeTransition(Transition transition) { 193 int index = getIndex(transition); 194 if (index != -1) { 195 mTransitionList.remove(index); 196 } 197 } 198 199 /** 200 * @return the index in the transition list. -1 if transition wasn't found. 201 */ getIndex(Transition transition)202 private int getIndex(Transition transition) { 203 int id = transition.mId; 204 if (id == UNSET) { 205 throw new IllegalArgumentException("The transition must have an id"); 206 } 207 208 int index = 0; 209 for (; index < mTransitionList.size(); index++) { 210 if (mTransitionList.get(index).mId == id) { 211 return index; 212 } 213 } 214 215 return -1; 216 } 217 218 /** 219 * @return true if the layout is valid for the scene. False otherwise. Use it for the debugging 220 * purposes. 221 */ validateLayout(MotionLayout layout)222 public boolean validateLayout(MotionLayout layout) { 223 return (layout == mMotionLayout && layout.mScene == this); 224 } 225 226 /** 227 * Set the transition to be the current transition of the motion scene. 228 * 229 * @param transition a transition to be set. The transition must exist within the motion scene. 230 * (e.g. {@link #addTransition(Transition)}) 231 */ setTransition(Transition transition)232 public void setTransition(Transition transition) { 233 mCurrentTransition = transition; 234 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 235 mCurrentTransition.mTouchResponse.setRTL(mRtl); 236 } 237 } 238 getRealID(int stateId)239 private int getRealID(int stateId) { 240 if (mStateSet != null) { 241 int tmp = mStateSet.stateGetConstraintID(stateId, -1, -1); 242 if (tmp != -1) { 243 return tmp; 244 } 245 } 246 return stateId; 247 } 248 249 /** 250 * Get all transitions that include this state 251 * @param stateId 252 * @return 253 */ getTransitionsWithState(int stateId)254 public List<Transition> getTransitionsWithState(int stateId) { 255 stateId = getRealID(stateId); 256 ArrayList<Transition> ret = new ArrayList<>(); 257 for (Transition transition : mTransitionList) { 258 if (transition.mConstraintSetStart == stateId 259 || transition.mConstraintSetEnd == stateId) { 260 ret.add(transition); 261 } 262 263 } 264 return ret; 265 } 266 267 /** 268 * Add all on click listeners for the current state 269 * @param motionLayout 270 * @param currentState 271 */ addOnClickListeners(MotionLayout motionLayout, int currentState)272 public void addOnClickListeners(MotionLayout motionLayout, int currentState) { 273 // remove all on clicks listeners 274 for (Transition transition : mTransitionList) { 275 if (transition.mOnClicks.size() > 0) { 276 for (Transition.TransitionOnClick onClick : transition.mOnClicks) { 277 onClick.removeOnClickListeners(motionLayout); 278 } 279 } 280 } 281 for (Transition transition : mAbstractTransitionList) { 282 if (transition.mOnClicks.size() > 0) { 283 for (Transition.TransitionOnClick onClick : transition.mOnClicks) { 284 onClick.removeOnClickListeners(motionLayout); 285 } 286 } 287 } 288 // add back all the listeners that are needed 289 for (Transition transition : mTransitionList) { 290 if (transition.mOnClicks.size() > 0) { 291 for (Transition.TransitionOnClick onClick : transition.mOnClicks) { 292 onClick.addOnClickListeners(motionLayout, currentState, transition); 293 } 294 } 295 } 296 for (Transition transition : mAbstractTransitionList) { 297 if (transition.mOnClicks.size() > 0) { 298 for (Transition.TransitionOnClick onClick : transition.mOnClicks) { 299 onClick.addOnClickListeners(motionLayout, currentState, transition); 300 } 301 } 302 } 303 } 304 305 /** 306 * Find the best transition for the motion 307 * @param currentState 308 * @param dx drag delta x 309 * @param dy drag delta y 310 * @param lastTouchDown 311 * @return 312 */ bestTransitionFor(int currentState, float dx, float dy, MotionEvent lastTouchDown)313 public Transition bestTransitionFor(int currentState, 314 float dx, 315 float dy, 316 MotionEvent lastTouchDown) { 317 List<Transition> candidates = null; 318 if (currentState != -1) { 319 candidates = getTransitionsWithState(currentState); 320 float max = 0; 321 Transition best = null; 322 RectF cache = new RectF(); 323 for (Transition transition : candidates) { 324 if (transition.mDisable) { 325 continue; 326 } 327 if (transition.mTouchResponse != null) { 328 transition.mTouchResponse.setRTL(mRtl); 329 RectF region = transition.mTouchResponse.getTouchRegion(mMotionLayout, cache); 330 if (region != null && lastTouchDown != null 331 && !region.contains(lastTouchDown.getX(), lastTouchDown.getY())) { 332 continue; 333 } 334 region = transition.mTouchResponse.getLimitBoundsTo(mMotionLayout, cache); 335 if (region != null && lastTouchDown != null 336 && !region.contains(lastTouchDown.getX(), lastTouchDown.getY())) { 337 continue; 338 } 339 340 float val = transition.mTouchResponse.dot(dx, dy); 341 if (transition.mTouchResponse.mIsRotateMode && lastTouchDown != null) { 342 float startX = lastTouchDown.getX() 343 - transition.mTouchResponse.mRotateCenterX; 344 float startY = lastTouchDown.getY() 345 - transition.mTouchResponse.mRotateCenterY; 346 float endX = dx + startX; 347 float endY = dy + startY; 348 double endAngle = Math.atan2(endY, endX); 349 double startAngle = Math.atan2(startX, startY); 350 val = (float) (endAngle - startAngle) * 10; 351 } 352 if (transition.mConstraintSetEnd == currentState) { // flip because backwards 353 val *= -1; 354 } else { 355 val *= 1.1f; // slightly bias towards the transition which is start over end 356 } 357 358 if (val > max) { 359 max = val; 360 best = transition; 361 } 362 } 363 } 364 if (DEBUG) { 365 if (best != null) { 366 Log.v(TAG, Debug.getLocation() + " ### BEST ----- " 367 + best.debugString(mMotionLayout.getContext()) + " ----"); 368 } else { 369 Log.v(TAG, Debug.getLocation() + " ### BEST ----- " + null + " ----"); 370 371 } 372 } 373 return best; 374 } 375 return mCurrentTransition; 376 } 377 378 /** 379 * Get list of Transitions know to the system 380 * @return 381 */ getDefinedTransitions()382 public ArrayList<Transition> getDefinedTransitions() { 383 return mTransitionList; 384 } 385 386 /** 387 * Find the transition based on the id 388 * @param id 389 * @return 390 */ getTransitionById(int id)391 public Transition getTransitionById(int id) { 392 for (Transition transition : mTransitionList) { 393 if (transition.mId == id) { 394 return transition; 395 } 396 } 397 return null; 398 } 399 400 /** 401 * Get the list of all Constraint Sets Know to the system 402 * @return 403 */ getConstraintSetIds()404 public int[] getConstraintSetIds() { 405 int[] ids = new int[mConstraintSetMap.size()]; 406 for (int i = 0; i < ids.length; i++) { 407 ids[i] = mConstraintSetMap.keyAt(i); 408 } 409 return ids; 410 } 411 412 /** 413 * Get the id's of all constraintSets with the matching types 414 * @return 415 */ getMatchingStateLabels(String .... types)416 public int[] getMatchingStateLabels(String ... types) { 417 int[] ids = new int[mConstraintSetMap.size()]; 418 419 int count = 0; 420 421 for (int i = 0; i < ids.length; i++) { 422 ConstraintSet set = mConstraintSetMap.valueAt(i); 423 int id = mConstraintSetMap.keyAt(i); 424 if (set.matchesLabels(types)) { 425 @SuppressWarnings("unused") String[] s = set.getStateLabels(); 426 ids[count++] = id; 427 } 428 } 429 return Arrays.copyOf(ids, count); 430 } 431 432 /** 433 * This will launch a transition to another state if an autoTransition is enabled on 434 * a Transition that matches the current state. 435 * 436 * @param motionLayout 437 * @param currentState 438 * @return 439 * 440 */ autoTransition(MotionLayout motionLayout, int currentState)441 boolean autoTransition(MotionLayout motionLayout, int currentState) { 442 if (isProcessingTouch()) { 443 return false; 444 } 445 if (mDisableAutoTransition) { 446 return false; 447 } 448 449 for (Transition transition : mTransitionList) { 450 if (transition.mAutoTransition == Transition.AUTO_NONE) { 451 continue; 452 } 453 if (mCurrentTransition == transition 454 && mCurrentTransition.isTransitionFlag(Transition.TRANSITION_FLAG_INTRA_AUTO)) { 455 continue; 456 } 457 if (currentState == transition.mConstraintSetStart && ( 458 transition.mAutoTransition == Transition.AUTO_ANIMATE_TO_END 459 || transition.mAutoTransition == Transition.AUTO_JUMP_TO_END)) { 460 motionLayout.setState(MotionLayout.TransitionState.FINISHED); 461 motionLayout.setTransition(transition); 462 if (transition.mAutoTransition == Transition.AUTO_ANIMATE_TO_END) { 463 motionLayout.transitionToEnd(); 464 motionLayout.setState(MotionLayout.TransitionState.SETUP); 465 motionLayout.setState(MotionLayout.TransitionState.MOVING); 466 } else { 467 motionLayout.setProgress(1); 468 motionLayout.evaluate(true); 469 motionLayout.setState(MotionLayout.TransitionState.SETUP); 470 motionLayout.setState(MotionLayout.TransitionState.MOVING); 471 motionLayout.setState(MotionLayout.TransitionState.FINISHED); 472 motionLayout.onNewStateAttachHandlers(); 473 } 474 return true; 475 } 476 if (currentState == transition.mConstraintSetEnd && ( 477 transition.mAutoTransition == Transition.AUTO_ANIMATE_TO_START 478 || transition.mAutoTransition == Transition.AUTO_JUMP_TO_START)) { 479 motionLayout.setState(MotionLayout.TransitionState.FINISHED); 480 motionLayout.setTransition(transition); 481 if (transition.mAutoTransition == Transition.AUTO_ANIMATE_TO_START) { 482 motionLayout.transitionToStart(); 483 motionLayout.setState(MotionLayout.TransitionState.SETUP); 484 motionLayout.setState(MotionLayout.TransitionState.MOVING); 485 } else { 486 motionLayout.setProgress(0); 487 motionLayout.evaluate(true); 488 motionLayout.setState(MotionLayout.TransitionState.SETUP); 489 motionLayout.setState(MotionLayout.TransitionState.MOVING); 490 motionLayout.setState(MotionLayout.TransitionState.FINISHED); 491 motionLayout.onNewStateAttachHandlers(); 492 } 493 return true; 494 } 495 } 496 return false; 497 } 498 isProcessingTouch()499 private boolean isProcessingTouch() { 500 return (mVelocityTracker != null); 501 } 502 503 /** 504 * Set Right to left 505 * @param rtl 506 */ setRtl(boolean rtl)507 public void setRtl(boolean rtl) { 508 mRtl = rtl; 509 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 510 mCurrentTransition.mTouchResponse.setRTL(mRtl); 511 } 512 } 513 514 /** 515 * Apply the viewTransition on the list of views 516 * @param id 517 * @param view 518 */ viewTransition(int id, View... view)519 public void viewTransition(int id, View... view) { 520 mViewTransitionController.viewTransition(id, view); 521 } 522 523 /** 524 * Enable this viewTransition 525 * @param id of viewTransition 526 * @param enable 527 */ enableViewTransition(int id, boolean enable)528 public void enableViewTransition(int id, boolean enable) { 529 mViewTransitionController.enableViewTransition(id, enable); 530 } 531 532 /** 533 * Is this view transition enabled 534 * @param id of viewTransition 535 * @return 536 */ isViewTransitionEnabled(int id)537 public boolean isViewTransitionEnabled(int id) { 538 return mViewTransitionController.isViewTransitionEnabled(id); 539 } 540 541 /** 542 * Apply a view transition to the MotionController 543 * @param viewTransitionId of viewTransition 544 * @param motionController 545 * @return 546 */ applyViewTransition(int viewTransitionId, MotionController motionController)547 public boolean applyViewTransition(int viewTransitionId, MotionController motionController) { 548 return mViewTransitionController.applyViewTransition(viewTransitionId, motionController); 549 } 550 551 /////////////////////////////////////////////////////////////////////////////// 552 // ====================== Transition ========================================== 553 554 /** 555 * Transition defines the interaction from one state to another. 556 * With out a Transition object Transition between two stats involves strictly linear 557 * interpolation 558 */ 559 public static class Transition { 560 private int mId = UNSET; 561 private boolean mIsAbstract = false; 562 private int mConstraintSetEnd = -1; 563 private int mConstraintSetStart = -1; 564 private int mDefaultInterpolator = 0; 565 private String mDefaultInterpolatorString = null; 566 private int mDefaultInterpolatorID = -1; 567 private int mDuration = 400; 568 private float mStagger = 0.0f; 569 private final MotionScene mMotionScene; 570 private ArrayList<KeyFrames> mKeyFramesList = new ArrayList<>(); 571 private TouchResponse mTouchResponse = null; 572 private ArrayList<TransitionOnClick> mOnClicks = new ArrayList<>(); 573 private int mAutoTransition = 0; 574 public static final int AUTO_NONE = 0; 575 public static final int AUTO_JUMP_TO_START = 1; 576 public static final int AUTO_JUMP_TO_END = 2; 577 public static final int AUTO_ANIMATE_TO_START = 3; 578 public static final int AUTO_ANIMATE_TO_END = 4; 579 private boolean mDisable = false; 580 private int mPathMotionArc = UNSET; 581 private int mLayoutDuringTransition = 0; 582 private int mTransitionFlags = 0; 583 static final int TRANSITION_FLAG_FIRST_DRAW = 1; 584 static final int TRANSITION_FLAG_INTRA_AUTO = 2; 585 static final int TRANSITION_FLAG_INTERCEPT_TOUCH = 4; 586 587 public static final int INTERPOLATE_REFERENCE_ID = -2; 588 public static final int INTERPOLATE_SPLINE_STRING = -1; 589 public static final int INTERPOLATE_EASE_IN_OUT = 0; 590 public static final int INTERPOLATE_EASE_IN = 1; 591 public static final int INTERPOLATE_EASE_OUT = 2; 592 public static final int INTERPOLATE_LINEAR = 3; 593 public static final int INTERPOLATE_BOUNCE = 4; 594 public static final int INTERPOLATE_OVERSHOOT = 5; 595 public static final int INTERPOLATE_ANTICIPATE = 6; 596 597 /** 598 * Set the onSwipe for this Transition 599 * @param onSwipe 600 */ setOnSwipe(OnSwipe onSwipe)601 public void setOnSwipe(OnSwipe onSwipe) { 602 mTouchResponse = (onSwipe == null) ? null 603 : new TouchResponse(mMotionScene.mMotionLayout, onSwipe); 604 } 605 606 /** 607 * Add the onclick to this view 608 * @param id 609 * @param action 610 */ addOnClick(int id, int action)611 public void addOnClick(int id, int action) { 612 for (TransitionOnClick onClick : mOnClicks) { 613 if (onClick.mTargetId == id) { 614 onClick.mMode = action; 615 return; 616 } 617 } 618 TransitionOnClick click = new TransitionOnClick(this, id, action); 619 mOnClicks.add(click); 620 } 621 622 /** 623 * Remove the onclick added to this view 624 * @param id 625 */ removeOnClick(int id)626 public void removeOnClick(int id) { 627 TransitionOnClick toRemove = null; 628 for (TransitionOnClick onClick : mOnClicks) { 629 if (onClick.mTargetId == id) { 630 toRemove = onClick; 631 break; 632 } 633 } 634 if (toRemove != null) { 635 mOnClicks.remove(toRemove); 636 } 637 } 638 639 /** 640 * get the mode of layout during transition 641 * @return 642 */ getLayoutDuringTransition()643 public int getLayoutDuringTransition() { 644 return mLayoutDuringTransition; 645 } 646 647 /** 648 * set the mode of layout during transition 649 * @param mode 650 */ setLayoutDuringTransition(int mode)651 public void setLayoutDuringTransition(int mode) { 652 mLayoutDuringTransition = mode; 653 } 654 655 /** 656 * Add on Click support using the xml parser 657 * @param context 658 * @param parser 659 */ addOnClick(Context context, XmlPullParser parser)660 public void addOnClick(Context context, XmlPullParser parser) { 661 mOnClicks.add(new TransitionOnClick(context, this, parser)); 662 } 663 664 /** 665 * sets the autoTransitionType 666 * On reaching a state auto transitions may be run based on 667 * one of AUTO_NONE, AUTO_JUMP_TO_START, AUTO_JUMP_TO_END, AUTO_ANIMATE_TO_START, 668 * AUTO_ANIMATE_TO_END 669 * 670 * @param type 671 */ setAutoTransition(int type)672 public void setAutoTransition(int type) { 673 mAutoTransition = type; 674 } 675 676 /** 677 * return the autoTransitionType. 678 * one of AUTO_NONE, AUTO_JUMP_TO_START, AUTO_JUMP_TO_END, AUTO_ANIMATE_TO_START, 679 * AUTO_ANIMATE_TO_END 680 * 681 * @return 0=NONE, 1=JUMP_TO_START, 2=JUMP_TO_END, 3=ANIMATE_TO_START, 4=ANIMATE_TO_END 682 */ getAutoTransition()683 public int getAutoTransition() { 684 return mAutoTransition; 685 } 686 687 /** 688 * Transitions can be given and ID. If unset it returns UNSET (-1) 689 * 690 * @return The Id of the Transition set in the MotionScene File or UNSET (-1) 691 */ getId()692 public int getId() { 693 return mId; 694 } 695 696 /** 697 * Get the id of the constraint set to go to 698 * 699 * @return 700 */ getEndConstraintSetId()701 public int getEndConstraintSetId() { 702 return mConstraintSetEnd; 703 } 704 705 /** 706 * Gets the id of the starting constraint set 707 * 708 * @return 709 */ getStartConstraintSetId()710 public int getStartConstraintSetId() { 711 return mConstraintSetStart; 712 } 713 714 /** 715 * sets the duration of the transition 716 * if set to < 8 it will be set to 8 717 * 718 * @param duration in milliseconds (min is 8) 719 */ setDuration(int duration)720 public void setDuration(int duration) { 721 this.mDuration = Math.max(duration, MIN_DURATION); 722 } 723 724 /** 725 * gets the default transition duration 726 * 727 * @return duration int milliseconds 728 */ getDuration()729 public int getDuration() { 730 return mDuration; 731 } 732 733 /** 734 * Gets the stagger value. 735 * 736 * @return 737 */ getStagger()738 public float getStagger() { 739 return mStagger; 740 } 741 getKeyFrameList()742 public List<KeyFrames> getKeyFrameList() { 743 return mKeyFramesList; 744 } 745 746 /** 747 * add a keyframe to this motion scene 748 * @param keyFrames 749 */ addKeyFrame(KeyFrames keyFrames)750 public void addKeyFrame(KeyFrames keyFrames) { 751 mKeyFramesList.add(keyFrames); 752 } 753 754 /** 755 * Get the onClick handlers. 756 * 757 * @return list of on click handler 758 */ getOnClickList()759 public List<TransitionOnClick> getOnClickList() { 760 return mOnClicks; 761 } 762 763 /** 764 * Get the Touch response manager 765 * 766 * @return 767 */ 768 @SuppressWarnings("HiddenTypeParameter") getTouchResponse()769 public TouchResponse getTouchResponse() { 770 return mTouchResponse; 771 } 772 773 /** 774 * Sets the stagger value. 775 * A Stagger value of zero means no stagger. 776 * A Stagger value of 1 means the last view starts moving at .5 progress 777 * 778 * @param stagger 779 */ setStagger(float stagger)780 public void setStagger(float stagger) { 781 mStagger = stagger; 782 } 783 784 /** 785 * Sets the pathMotionArc for the all motions in this transition. 786 * if set to UNSET (default) it reverts to the setting of the constraintSet 787 * 788 * @param arcMode 789 */ setPathMotionArc(int arcMode)790 public void setPathMotionArc(int arcMode) { 791 mPathMotionArc = arcMode; 792 } 793 794 /** 795 * gets the pathMotionArc for the all motions in this transition. 796 * if set to UNSET (default) it reverts to the setting of the constraintSet 797 * 798 * @return arcMode 799 */ getPathMotionArc()800 public int getPathMotionArc() { 801 return mPathMotionArc; 802 } 803 804 /** 805 * Returns true if this Transition can be auto considered for transition 806 * Default is enabled 807 */ isEnabled()808 public boolean isEnabled() { 809 return !mDisable; 810 } 811 812 /** 813 * enable or disable the Transition. If a Transition is disabled it is not eligible 814 * for automatically switching to. 815 * 816 * @param enable 817 */ setEnabled(boolean enable)818 public void setEnabled(boolean enable) { 819 mDisable = !enable; 820 } 821 822 /** 823 * Print a debug string indicating the starting and ending state of the transition 824 * 825 * @param context 826 * @return 827 */ debugString(Context context)828 public String debugString(Context context) { 829 830 String ret; 831 if (mConstraintSetStart == UNSET) { 832 ret = "null"; 833 } else { 834 ret = context.getResources().getResourceEntryName(mConstraintSetStart); 835 } 836 if (mConstraintSetEnd == UNSET) { 837 ret += " -> " + "null"; 838 } else { 839 ret += " -> " + context.getResources().getResourceEntryName(mConstraintSetEnd); 840 } 841 return ret; 842 } 843 844 /** 845 * is the transition flag set 846 * @param flag 847 * @return 848 */ isTransitionFlag(int flag)849 public boolean isTransitionFlag(int flag) { 850 return 0 != (mTransitionFlags & flag); 851 } 852 setTransitionFlag(int flag)853 public void setTransitionFlag(int flag) { 854 mTransitionFlags = flag; 855 } 856 857 /** 858 * Set the on touch up mode 859 * @param touchUpMode 860 */ setOnTouchUp(int touchUpMode)861 public void setOnTouchUp(int touchUpMode) { 862 TouchResponse touchResponse = getTouchResponse(); 863 if (touchResponse != null) { 864 touchResponse.setTouchUpMode(touchUpMode); 865 } 866 } 867 868 public static class TransitionOnClick implements View.OnClickListener { 869 public static final int ANIM_TO_END = 0x0001; 870 public static final int ANIM_TOGGLE = 0x0011; 871 public static final int ANIM_TO_START = 0x0010; 872 public static final int JUMP_TO_END = 0x100; 873 public static final int JUMP_TO_START = 0x1000; 874 private final Transition mTransition; 875 int mTargetId = UNSET; 876 int mMode = 0x11; 877 TransitionOnClick(Context context, Transition transition, XmlPullParser parser)878 public TransitionOnClick(Context context, 879 Transition transition, 880 XmlPullParser parser) { 881 mTransition = transition; 882 TypedArray a = context.obtainStyledAttributes(Xml.asAttributeSet(parser), 883 R.styleable.OnClick); 884 final int count = a.getIndexCount(); 885 for (int i = 0; i < count; i++) { 886 int attr = a.getIndex(i); 887 if (attr == R.styleable.OnClick_targetId) { 888 mTargetId = a.getResourceId(attr, mTargetId); 889 } else if (attr == R.styleable.OnClick_clickAction) { 890 mMode = a.getInt(attr, mMode); 891 } 892 } 893 a.recycle(); 894 } 895 TransitionOnClick(Transition transition, int id, int action)896 public TransitionOnClick(Transition transition, int id, int action) { 897 mTransition = transition; 898 mTargetId = id; 899 mMode = action; 900 } 901 902 /** 903 * Add the on click listeners for the current state 904 * 905 * @param motionLayout 906 * @param currentState 907 * @param transition 908 */ addOnClickListeners(MotionLayout motionLayout, int currentState, Transition transition)909 public void addOnClickListeners(MotionLayout motionLayout, 910 int currentState, 911 Transition transition) { 912 View v = mTargetId == UNSET ? motionLayout : motionLayout.findViewById(mTargetId); 913 if (v == null) { 914 Log.e(TAG, "OnClick could not find id " + mTargetId); 915 return; 916 } 917 int start = transition.mConstraintSetStart; 918 int end = transition.mConstraintSetEnd; 919 if (start == UNSET) { // does not require a known end state 920 v.setOnClickListener(this); 921 return; 922 } 923 924 boolean listen = ((mMode & ANIM_TO_END) != 0) && currentState == start; 925 listen |= ((mMode & JUMP_TO_END) != 0) && currentState == start; 926 listen |= ((mMode & ANIM_TO_END) != 0) && currentState == start; 927 listen |= ((mMode & ANIM_TO_START) != 0) && currentState == end; 928 listen |= ((mMode & JUMP_TO_START) != 0) && currentState == end; 929 930 if (listen) { 931 v.setOnClickListener(this); 932 } 933 } 934 935 /** 936 * Remove the OnClickListeners 937 * (typically called because you are removing the transition) 938 * 939 * @param motionLayout 940 */ removeOnClickListeners(MotionLayout motionLayout)941 public void removeOnClickListeners(MotionLayout motionLayout) { 942 if (mTargetId == UNSET) { 943 return; 944 } 945 View v = motionLayout.findViewById(mTargetId); 946 if (v == null) { 947 Log.e(TAG, " (*) could not find id " + mTargetId); 948 return; 949 } 950 v.setOnClickListener(null); 951 } 952 isTransitionViable(Transition current, MotionLayout tl)953 boolean isTransitionViable(Transition current, MotionLayout tl) { 954 if (mTransition == current) { 955 return true; 956 } 957 int dest = mTransition.mConstraintSetEnd; 958 int from = mTransition.mConstraintSetStart; 959 if (from == UNSET) { 960 return tl.mCurrentState != dest; 961 } 962 return (tl.mCurrentState == from) || (tl.mCurrentState == dest); 963 964 } 965 966 @Override onClick(View view)967 public void onClick(View view) { 968 MotionLayout tl = mTransition.mMotionScene.mMotionLayout; 969 if (!tl.isInteractionEnabled()) { 970 return; 971 } 972 if (mTransition.mConstraintSetStart == UNSET) { 973 int currentState = tl.getCurrentState(); 974 if (currentState == UNSET) { 975 tl.transitionToState(mTransition.mConstraintSetEnd); 976 return; 977 } 978 Transition t = new Transition(mTransition.mMotionScene, mTransition); 979 t.mConstraintSetStart = currentState; 980 t.mConstraintSetEnd = mTransition.mConstraintSetEnd; 981 tl.setTransition(t); 982 tl.transitionToEnd(); 983 return; 984 } 985 Transition current = mTransition.mMotionScene.mCurrentTransition; 986 boolean forward = ((mMode & ANIM_TO_END) != 0 || (mMode & JUMP_TO_END) != 0); 987 boolean backward = ((mMode & ANIM_TO_START) != 0 || (mMode & JUMP_TO_START) != 0); 988 boolean bidirectional = forward && backward; 989 if (bidirectional) { 990 if (mTransition.mMotionScene.mCurrentTransition != mTransition) { 991 tl.setTransition(mTransition); 992 } 993 if (tl.getCurrentState() == tl.getEndState() || tl.getProgress() > 0.5f) { 994 forward = false; 995 } else { 996 backward = false; 997 } 998 } 999 if (isTransitionViable(current, tl)) { 1000 if (forward && (mMode & ANIM_TO_END) != 0) { 1001 tl.setTransition(mTransition); 1002 tl.transitionToEnd(); 1003 } else if (backward && (mMode & ANIM_TO_START) != 0) { 1004 tl.setTransition(mTransition); 1005 tl.transitionToStart(); 1006 } else if (forward && (mMode & JUMP_TO_END) != 0) { 1007 tl.setTransition(mTransition); 1008 tl.setProgress(1); 1009 } else if (backward && (mMode & JUMP_TO_START) != 0) { 1010 tl.setTransition(mTransition); 1011 tl.setProgress(0); 1012 } 1013 } 1014 } 1015 } 1016 Transition(MotionScene motionScene, Transition global)1017 Transition(MotionScene motionScene, Transition global) { 1018 mMotionScene = motionScene; 1019 mDuration = motionScene.mDefaultDuration; 1020 if (global != null) { 1021 mPathMotionArc = global.mPathMotionArc; 1022 mDefaultInterpolator = global.mDefaultInterpolator; 1023 mDefaultInterpolatorString = global.mDefaultInterpolatorString; 1024 mDefaultInterpolatorID = global.mDefaultInterpolatorID; 1025 mDuration = global.mDuration; 1026 mKeyFramesList = global.mKeyFramesList; 1027 mStagger = global.mStagger; 1028 mLayoutDuringTransition = global.mLayoutDuringTransition; 1029 } 1030 } 1031 1032 /** 1033 * Create a transition 1034 * 1035 * @param id a unique id to represent the transition. 1036 * @param motionScene the motion scene that the transition will be added to. 1037 * @param constraintSetStartId id of the ConstraintSet to be used for the start of 1038 * transition 1039 * @param constraintSetEndId id of the ConstraintSet to be used for the end of transition 1040 */ Transition( int id, MotionScene motionScene, int constraintSetStartId, int constraintSetEndId)1041 public Transition( 1042 int id, 1043 MotionScene motionScene, 1044 int constraintSetStartId, 1045 int constraintSetEndId) { 1046 mId = id; 1047 mMotionScene = motionScene; 1048 mConstraintSetStart = constraintSetStartId; 1049 mConstraintSetEnd = constraintSetEndId; 1050 mDuration = motionScene.mDefaultDuration; 1051 mLayoutDuringTransition = motionScene.mLayoutDuringTransition; 1052 } 1053 Transition(MotionScene motionScene, Context context, XmlPullParser parser)1054 Transition(MotionScene motionScene, Context context, XmlPullParser parser) { 1055 mDuration = motionScene.mDefaultDuration; 1056 mLayoutDuringTransition = motionScene.mLayoutDuringTransition; 1057 mMotionScene = motionScene; 1058 fillFromAttributeList(motionScene, context, Xml.asAttributeSet(parser)); 1059 } 1060 1061 /** 1062 * Sets the interpolation used for this transition. 1063 * <br> 1064 * The call support standard types EASE_IN_OUT etc.:<br> 1065 * setInterpolatorInfo(MotionScene.Transition.INTERPOLATE_EASE_IN_OUT, null, 0); 1066 * setInterpolatorInfo(MotionScene.Transition.INTERPOLATE_OVERSHOOT, null, 0); 1067 * <br> 1068 * Strings such as "cubic(...)" , "spline(...)"<br> 1069 * setInterpolatorInfo( 1070 * MotionScene.Transition.INTERPOLATE_SPLINE_STRING, "cubic(1,0,0,1)", 0); 1071 * <br> 1072 * Android interpolators in res/anim : <br> 1073 * setInterpolatorInfo( 1074 * MotionScene.Transition.INTERPOLATE_REFERENCE_ID, null, R.anim....); 1075 * <br> 1076 * @param interpolator sets the type of interpolation (MotionScene.Transition.INTERPOLATE_*) 1077 * @param interpolatorString sets a string based custom interpolation 1078 * @param interpolatorID sets the id of a Android Transition 1079 */ setInterpolatorInfo(int interpolator, String interpolatorString, int interpolatorID)1080 public void setInterpolatorInfo(int interpolator, 1081 String interpolatorString, 1082 int interpolatorID) { 1083 mDefaultInterpolator = interpolator; 1084 mDefaultInterpolatorString = interpolatorString; 1085 mDefaultInterpolatorID = interpolatorID; 1086 } 1087 fillFromAttributeList(MotionScene motionScene, Context context, AttributeSet attrs)1088 private void fillFromAttributeList(MotionScene motionScene, 1089 Context context, 1090 AttributeSet attrs) { 1091 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Transition); 1092 fill(motionScene, context, a); 1093 a.recycle(); 1094 } 1095 fill(MotionScene motionScene, Context context, TypedArray a)1096 private void fill(MotionScene motionScene, Context context, TypedArray a) { 1097 final int count = a.getIndexCount(); 1098 for (int i = 0; i < count; i++) { 1099 int attr = a.getIndex(i); 1100 if (attr == R.styleable.Transition_constraintSetEnd) { 1101 mConstraintSetEnd = a.getResourceId(attr, UNSET); 1102 String type = context.getResources().getResourceTypeName(mConstraintSetEnd); 1103 if ("layout".equals(type)) { 1104 ConstraintSet cSet = new ConstraintSet(); 1105 cSet.load(context, mConstraintSetEnd); 1106 motionScene.mConstraintSetMap.append(mConstraintSetEnd, cSet); 1107 if (DEBUG) { 1108 Log.v(TAG, " constraint Set end loaded from layout " 1109 + Debug.getName(context, mConstraintSetEnd)); 1110 } 1111 } else if ("xml".equals(type)) { 1112 int id = motionScene.parseInclude(context, mConstraintSetEnd); 1113 mConstraintSetEnd = id; 1114 } 1115 } else if (attr == R.styleable.Transition_constraintSetStart) { 1116 mConstraintSetStart = a.getResourceId(attr, mConstraintSetStart); 1117 String type = context.getResources().getResourceTypeName(mConstraintSetStart); 1118 if ("layout".equals(type)) { 1119 ConstraintSet cSet = new ConstraintSet(); 1120 cSet.load(context, mConstraintSetStart); 1121 motionScene.mConstraintSetMap.append(mConstraintSetStart, cSet); 1122 } else if ("xml".equals(type)) { 1123 int id = motionScene.parseInclude(context, mConstraintSetStart); 1124 mConstraintSetStart = id; 1125 } 1126 } else if (attr == R.styleable.Transition_motionInterpolator) { 1127 TypedValue type = a.peekValue(attr); 1128 1129 if (type.type == TypedValue.TYPE_REFERENCE) { 1130 mDefaultInterpolatorID = a.getResourceId(attr, -1); 1131 if (mDefaultInterpolatorID != -1) { 1132 mDefaultInterpolator = INTERPOLATOR_REFERENCE_ID; 1133 } 1134 } else if (type.type == TypedValue.TYPE_STRING) { 1135 mDefaultInterpolatorString = a.getString(attr); 1136 if (mDefaultInterpolatorString != null) { 1137 if (mDefaultInterpolatorString.indexOf("/") > 0) { 1138 mDefaultInterpolatorID = a.getResourceId(attr, -1); 1139 mDefaultInterpolator = INTERPOLATOR_REFERENCE_ID; 1140 } else { 1141 mDefaultInterpolator = SPLINE_STRING; 1142 } 1143 } 1144 } else { 1145 mDefaultInterpolator = a.getInteger(attr, mDefaultInterpolator); 1146 } 1147 1148 } else if (attr == R.styleable.Transition_duration) { 1149 mDuration = a.getInt(attr, mDuration); 1150 if (mDuration < MIN_DURATION) { 1151 mDuration = MIN_DURATION; 1152 } 1153 } else if (attr == R.styleable.Transition_staggered) { 1154 mStagger = a.getFloat(attr, mStagger); 1155 } else if (attr == R.styleable.Transition_autoTransition) { 1156 mAutoTransition = a.getInteger(attr, mAutoTransition); 1157 } else if (attr == R.styleable.Transition_android_id) { 1158 mId = a.getResourceId(attr, mId); 1159 } else if (attr == R.styleable.Transition_transitionDisable) { 1160 mDisable = a.getBoolean(attr, mDisable); 1161 } else if (attr == R.styleable.Transition_pathMotionArc) { 1162 mPathMotionArc = a.getInteger(attr, UNSET); 1163 } else if (attr == R.styleable.Transition_layoutDuringTransition) { 1164 mLayoutDuringTransition = a.getInteger(attr, 0); 1165 } else if (attr == R.styleable.Transition_transitionFlags) { 1166 mTransitionFlags = a.getInteger(attr, 0); 1167 } 1168 } 1169 if (mConstraintSetStart == UNSET) { 1170 mIsAbstract = true; 1171 } 1172 } 1173 1174 } 1175 1176 /** 1177 * Create a motion scene. 1178 * 1179 * @param layout Motion layout to which the scene will be set. 1180 */ MotionScene(MotionLayout layout)1181 public MotionScene(MotionLayout layout) { 1182 mMotionLayout = layout; 1183 mViewTransitionController = new ViewTransitionController(layout); 1184 } 1185 MotionScene(Context context, MotionLayout layout, int resourceID)1186 MotionScene(Context context, MotionLayout layout, int resourceID) { 1187 mMotionLayout = layout; 1188 mViewTransitionController = new ViewTransitionController(layout); 1189 1190 load(context, resourceID); 1191 mConstraintSetMap.put(R.id.motion_base, new ConstraintSet()); 1192 mConstraintSetIdMap.put("motion_base", R.id.motion_base); 1193 } 1194 1195 /** 1196 * Load a MotionScene from a MotionScene.xml file 1197 * 1198 * @param context the context for the inflation 1199 * @param resourceId id of xml file in res/xml/ 1200 */ load(Context context, int resourceId)1201 private void load(Context context, int resourceId) { 1202 Resources res = context.getResources(); 1203 XmlPullParser parser = res.getXml(resourceId); 1204 try { 1205 Transition transition = null; 1206 for (int eventType = parser.getEventType(); 1207 eventType != XmlResourceParser.END_DOCUMENT; 1208 eventType = parser.next()) { 1209 switch (eventType) { 1210 case XmlResourceParser.START_DOCUMENT: 1211 case XmlResourceParser.END_TAG: 1212 case XmlResourceParser.TEXT: 1213 break; 1214 case XmlResourceParser.START_TAG: 1215 String tagName = parser.getName(); 1216 if (DEBUG_DESKTOP) { 1217 System.out.println("parsing = " + tagName); 1218 } 1219 if (DEBUG) { 1220 Log.v(TAG, "MotionScene ----------- START_TAG " + tagName); 1221 } 1222 switch (tagName) { 1223 case MOTIONSCENE_TAG: 1224 parseMotionSceneTags(context, parser); 1225 break; 1226 case TRANSITION_TAG: 1227 mTransitionList.add(transition = 1228 new Transition(this, context, parser)); 1229 if (mCurrentTransition == null && !transition.mIsAbstract) { 1230 mCurrentTransition = transition; 1231 if (mCurrentTransition != null 1232 && mCurrentTransition.mTouchResponse != null) { 1233 mCurrentTransition.mTouchResponse.setRTL(mRtl); 1234 } 1235 } 1236 if (transition.mIsAbstract) { // global transition only one for now 1237 if (transition.mConstraintSetEnd == UNSET) { 1238 mDefaultTransition = transition; 1239 } else { 1240 mAbstractTransitionList.add(transition); 1241 } 1242 mTransitionList.remove(transition); 1243 } 1244 break; 1245 case ONSWIPE_TAG: 1246 if (DEBUG || transition == null) { 1247 String name = context.getResources() 1248 .getResourceEntryName(resourceId); 1249 int line = parser.getLineNumber(); 1250 Log.v(TAG, " OnSwipe (" + name + ".xml:" + line + ")"); 1251 } 1252 if (transition != null) { 1253 transition.mTouchResponse = 1254 new TouchResponse(context, mMotionLayout, parser); 1255 } 1256 break; 1257 case ONCLICK_TAG: 1258 if (transition != null) { 1259 if (!mMotionLayout.isInEditMode()) { 1260 transition.addOnClick(context, parser); 1261 } 1262 } 1263 break; 1264 case STATESET_TAG: 1265 mStateSet = new StateSet(context, parser); 1266 break; 1267 case CONSTRAINTSET_TAG: 1268 parseConstraintSet(context, parser); 1269 break; 1270 case INCLUDE_TAG: 1271 case INCLUDE_TAG_UC: 1272 parseInclude(context, parser); 1273 break; 1274 case KEYFRAMESET_TAG: 1275 KeyFrames keyFrames = new KeyFrames(context, parser); 1276 if (transition != null) { 1277 transition.mKeyFramesList.add(keyFrames); 1278 } 1279 break; 1280 case VIEW_TRANSITION: 1281 ViewTransition viewTransition = new ViewTransition(context, parser); 1282 mViewTransitionController.add(viewTransition); 1283 break; 1284 default: 1285 if (DEBUG) { 1286 Log.v(TAG, getLine(context, resourceId, parser) 1287 + "WARNING UNKNOWN ATTRIBUTE " + tagName); 1288 } 1289 break; 1290 } 1291 1292 break; 1293 } 1294 } 1295 } catch (XmlPullParserException e) { 1296 if (DEBUG) { 1297 Log.v(TAG, getLine(context, resourceId, parser) + " " + e.getMessage()); 1298 } 1299 Log.e(TAG, "Error parsing resource: " + resourceId, e); 1300 } catch (IOException e) { 1301 if (DEBUG) { 1302 Log.v(TAG, getLine(context, resourceId, parser) + " " + e.getMessage()); 1303 } 1304 Log.e(TAG, "Error parsing resource: " + resourceId, e); 1305 } 1306 } 1307 parseMotionSceneTags(Context context, XmlPullParser parser)1308 private void parseMotionSceneTags(Context context, XmlPullParser parser) { 1309 AttributeSet attrs = Xml.asAttributeSet(parser); 1310 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MotionScene); 1311 final int count = a.getIndexCount(); 1312 for (int i = 0; i < count; i++) { 1313 int attr = a.getIndex(i); 1314 if (attr == R.styleable.MotionScene_defaultDuration) { 1315 mDefaultDuration = a.getInt(attr, mDefaultDuration); 1316 if (mDefaultDuration < MIN_DURATION) { 1317 mDefaultDuration = MIN_DURATION; 1318 } 1319 } else if (attr == R.styleable.MotionScene_layoutDuringTransition) { 1320 mLayoutDuringTransition = a.getInteger(attr, LAYOUT_IGNORE_REQUEST); 1321 } 1322 } 1323 a.recycle(); 1324 } 1325 getId(Context context, String idString)1326 private int getId(Context context, String idString) { 1327 int id = UNSET; 1328 if (idString.contains("/")) { 1329 String tmp = idString.substring(idString.indexOf('/') + 1); 1330 id = context.getResources().getIdentifier(tmp, "id", context.getPackageName()); 1331 if (DEBUG_DESKTOP) { 1332 System.out.println("id getMap res = " + id); 1333 } 1334 } 1335 if (id == UNSET) { 1336 if (idString != null && idString.length() > 1) { 1337 id = Integer.parseInt(idString.substring(1)); 1338 } else { 1339 Log.e(TAG, "error in parsing id"); 1340 } 1341 } 1342 return id; 1343 } 1344 parseInclude(Context context, XmlPullParser mainParser)1345 private void parseInclude(Context context, XmlPullParser mainParser) { 1346 TypedArray a = context 1347 .obtainStyledAttributes(Xml.asAttributeSet(mainParser), R.styleable.include); 1348 final int count = a.getIndexCount(); 1349 for (int i = 0; i < count; i++) { 1350 int attr = a.getIndex(i); 1351 if (attr == R.styleable.include_constraintSet) { 1352 int resourceId = a.getResourceId(attr, UNSET); 1353 parseInclude(context, resourceId); 1354 } 1355 } 1356 a.recycle(); 1357 } 1358 parseInclude(Context context, int resourceId)1359 private int parseInclude(Context context, int resourceId) { 1360 Resources res = context.getResources(); 1361 XmlPullParser includeParser = res.getXml(resourceId); 1362 try { 1363 for (int eventType = includeParser.getEventType(); 1364 eventType != XmlResourceParser.END_DOCUMENT; 1365 eventType = includeParser.next()) { 1366 String tagName = includeParser.getName(); 1367 if (XmlResourceParser.START_TAG == eventType 1368 && CONSTRAINTSET_TAG.equals(tagName)) { 1369 return parseConstraintSet(context, includeParser); 1370 } 1371 } 1372 } catch (XmlPullParserException e) { 1373 if (DEBUG) { 1374 Log.v(TAG, getLine(context, resourceId, includeParser) 1375 + " " + e.getMessage()); 1376 } 1377 Log.e(TAG, "Error parsing resource: " + resourceId, e); 1378 } catch (IOException e) { 1379 if (DEBUG) { 1380 Log.v(TAG, getLine(context, resourceId, includeParser) 1381 + " " + e.getMessage()); 1382 } 1383 Log.e(TAG, "Error parsing resource: " + resourceId, e); 1384 } 1385 return UNSET; 1386 } 1387 parseConstraintSet(Context context, XmlPullParser parser)1388 private int parseConstraintSet(Context context, XmlPullParser parser) { 1389 ConstraintSet set = new ConstraintSet(); 1390 set.setForceId(false); 1391 int count = parser.getAttributeCount(); 1392 int id = UNSET; 1393 int derivedId = UNSET; 1394 for (int i = 0; i < count; i++) { 1395 String name = parser.getAttributeName(i); 1396 String value = parser.getAttributeValue(i); 1397 if (DEBUG_DESKTOP) { 1398 System.out.println("id string = " + value); 1399 } 1400 switch (name) { 1401 case "id": 1402 id = getId(context, value); 1403 mConstraintSetIdMap.put(stripID(value), id); 1404 set.mIdString = Debug.getName(context, id); 1405 break; 1406 case "deriveConstraintsFrom": 1407 derivedId = getId(context, value); 1408 break; 1409 case "stateLabels": 1410 set.setStateLabels(value); 1411 break; 1412 case "constraintRotate": 1413 try { 1414 set.mRotate = Integer.parseInt(value); 1415 } catch (NumberFormatException exception) { 1416 switch (value) { 1417 case "none": 1418 set.mRotate = 0; 1419 break; 1420 case "right": 1421 set.mRotate = 1; 1422 break; 1423 case "left": 1424 set.mRotate = 2; 1425 break; 1426 case "x_right": 1427 set.mRotate = 3; 1428 break; 1429 case "x_left": 1430 set.mRotate = 4; 1431 break; 1432 } 1433 } 1434 1435 1436 break; 1437 } 1438 } 1439 if (id != UNSET) { 1440 if (mMotionLayout.mDebugPath != 0) { 1441 set.setValidateOnParse(true); 1442 } 1443 set.load(context, parser); 1444 if (derivedId != UNSET) { 1445 mDeriveMap.put(id, derivedId); 1446 } 1447 mConstraintSetMap.put(id, set); 1448 } 1449 return id; 1450 } 1451 onLayout(boolean changed, int left, int top, int right, int bottom)1452 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1453 } 1454 1455 /** 1456 * Get the constraintSet given the id 1457 * @param context 1458 * @param id 1459 * @return 1460 */ getConstraintSet(Context context, String id)1461 public ConstraintSet getConstraintSet(Context context, String id) { 1462 if (DEBUG_DESKTOP) { 1463 System.out.println("id " + id); 1464 System.out.println("size " + mConstraintSetMap.size()); 1465 } 1466 for (int i = 0; i < mConstraintSetMap.size(); i++) { 1467 int key = mConstraintSetMap.keyAt(i); 1468 String IdAsString = context.getResources().getResourceName(key); 1469 if (DEBUG_DESKTOP) { 1470 System.out.println("Id for <" + i + "> is <" 1471 + IdAsString + "> looking for <" + id + ">"); 1472 } 1473 if (id.equals(IdAsString)) { 1474 return mConstraintSetMap.get(key); 1475 } 1476 } 1477 return null; 1478 } 1479 getConstraintSet(int id)1480 ConstraintSet getConstraintSet(int id) { 1481 return getConstraintSet(id, -1, -1); 1482 } 1483 getConstraintSet(int id, int width, int height)1484 ConstraintSet getConstraintSet(int id, int width, int height) { 1485 if (DEBUG_DESKTOP) { 1486 System.out.println("id " + id); 1487 System.out.println("size " + mConstraintSetMap.size()); 1488 } 1489 if (mStateSet != null) { 1490 int cid = mStateSet.stateGetConstraintID(id, width, height); 1491 if (cid != -1) { 1492 id = cid; 1493 } 1494 } 1495 if (mConstraintSetMap.get(id) == null) { 1496 Log.e(TAG, "Warning could not find ConstraintSet id/" 1497 + Debug.getName(mMotionLayout.getContext(), id) + " In MotionScene"); 1498 return mConstraintSetMap.get(mConstraintSetMap.keyAt(0)); 1499 } 1500 return mConstraintSetMap.get(id); 1501 } 1502 1503 /** 1504 * Maps the Constraint set to the id. 1505 * 1506 * @param id - unique id to represent the ConstraintSet 1507 * @param set - ConstraintSet to be represented with the id. 1508 */ setConstraintSet(int id, ConstraintSet set)1509 public void setConstraintSet(int id, ConstraintSet set) { 1510 mConstraintSetMap.put(id, set); 1511 } 1512 1513 /** 1514 * provides the key frames & CycleFrames to the motion view to 1515 * 1516 * @param motionController 1517 */ getKeyFrames(MotionController motionController)1518 public void getKeyFrames(MotionController motionController) { 1519 if (mCurrentTransition == null) { 1520 if (mDefaultTransition != null) { 1521 for (KeyFrames keyFrames : mDefaultTransition.mKeyFramesList) { 1522 keyFrames.addFrames(motionController); 1523 } 1524 } 1525 return; 1526 } 1527 for (KeyFrames keyFrames : mCurrentTransition.mKeyFramesList) { 1528 keyFrames.addFrames(motionController); 1529 } 1530 } 1531 1532 /** 1533 * get key frame 1534 * 1535 * @param context 1536 * @param type 1537 * @param target 1538 * @param position 1539 * @return Key Object 1540 */ getKeyFrame(Context context, int type, int target, int position)1541 Key getKeyFrame(Context context, int type, int target, int position) { 1542 if (mCurrentTransition == null) { 1543 return null; 1544 } 1545 for (KeyFrames keyFrames : mCurrentTransition.mKeyFramesList) { 1546 for (Integer integer : keyFrames.getKeys()) { 1547 if (target == integer) { 1548 ArrayList<Key> keys = keyFrames.getKeyFramesForView(integer); 1549 for (Key key : keys) { 1550 if (key.mFramePosition == position) { 1551 if (key.mType == type) { 1552 return key; 1553 } 1554 } 1555 } 1556 } 1557 } 1558 1559 } 1560 return null; 1561 } 1562 getTransitionDirection(int stateId)1563 int getTransitionDirection(int stateId) { 1564 for (Transition transition : mTransitionList) { 1565 if (transition.mConstraintSetStart == stateId) { 1566 return TRANSITION_BACKWARD; 1567 } 1568 } 1569 return TRANSITION_FORWARD; 1570 } 1571 1572 /** 1573 * Returns true if the view has a keyframe defined at the given position 1574 * 1575 * @param view 1576 * @param position 1577 * @return true if a keyframe exists, false otherwise 1578 */ hasKeyFramePosition(View view, int position)1579 boolean hasKeyFramePosition(View view, int position) { 1580 if (mCurrentTransition == null) { 1581 return false; 1582 } 1583 for (KeyFrames keyFrames : mCurrentTransition.mKeyFramesList) { 1584 ArrayList<Key> framePoints = keyFrames.getKeyFramesForView(view.getId()); 1585 for (Key framePoint : framePoints) { 1586 if (framePoint.mFramePosition == position) { 1587 return true; 1588 } 1589 } 1590 } 1591 return false; 1592 } 1593 1594 /** 1595 * Set a keyFrame on the current Transition 1596 * @param view 1597 * @param position 1598 * @param name 1599 * @param value 1600 */ setKeyframe(View view, int position, String name, Object value)1601 public void setKeyframe(View view, int position, String name, Object value) { 1602 if (DEBUG) { 1603 System.out.println("setKeyframe for pos " + position 1604 + " name <" + name + "> value: " + value); 1605 } 1606 if (mCurrentTransition == null) { 1607 return; 1608 } 1609 for (KeyFrames keyFrames : mCurrentTransition.mKeyFramesList) { 1610 if (DEBUG) { 1611 System.out.println("key frame " + keyFrames); 1612 } 1613 ArrayList<Key> framePoints = keyFrames.getKeyFramesForView(view.getId()); 1614 if (DEBUG) { 1615 System.out.println("key frame has " + framePoints.size() + " frame points"); 1616 } 1617 for (Key framePoint : framePoints) { 1618 if (DEBUG) { 1619 System.out.println("framePoint pos: " + framePoint.mFramePosition); 1620 } 1621 if (framePoint.mFramePosition == position) { 1622 float v = 0; 1623 if (value != null) { 1624 v = ((Float) value).floatValue(); 1625 if (DEBUG) { 1626 System.out.println("value: " + v); 1627 } 1628 } else { 1629 if (DEBUG) { 1630 System.out.println("value was null!!!"); 1631 } 1632 } 1633 if (v == 0) { 1634 v = 0.01f; 1635 } 1636 } 1637 } 1638 } 1639 } 1640 1641 /** 1642 * Get the path percent (Non functional currently) 1643 * @param view 1644 * @param position 1645 * @return 1646 */ getPathPercent(View view, int position)1647 public float getPathPercent(View view, int position) { 1648 return 0; 1649 } 1650 1651 ////////////////////////////////////////////////////////// 1652 // touch handling 1653 /////////////////////////////////////////////////////////// supportTouch()1654 boolean supportTouch() { 1655 for (Transition transition : mTransitionList) { 1656 if (transition.mTouchResponse != null) { 1657 return true; 1658 } 1659 } 1660 return mCurrentTransition != null && mCurrentTransition.mTouchResponse != null; 1661 } 1662 1663 float mLastTouchX, mLastTouchY; 1664 processTouchEvent(MotionEvent event, int currentState, MotionLayout motionLayout)1665 void processTouchEvent(MotionEvent event, int currentState, MotionLayout motionLayout) { 1666 if (DEBUG) { 1667 Log.v(TAG, Debug.getLocation() + " processTouchEvent"); 1668 } 1669 RectF cache = new RectF(); 1670 if (mVelocityTracker == null) { 1671 mVelocityTracker = mMotionLayout.obtainVelocityTracker(); 1672 } 1673 mVelocityTracker.addMovement(event); 1674 if (DEBUG) { 1675 float time = (event.getEventTime() % 100000) / 1000f; 1676 float x = event.getRawX(); 1677 float y = event.getRawY(); 1678 Log.v(TAG, " " + time + " processTouchEvent " 1679 + "state=" + Debug.getState(motionLayout, currentState) 1680 + " " + Debug.getActionType(event) + " " + x 1681 + ", " + y + " \t " + motionLayout.getProgress()); 1682 } 1683 1684 if (currentState != -1) { 1685 RectF region; 1686 switch (event.getAction()) { 1687 case MotionEvent.ACTION_DOWN: 1688 mLastTouchX = event.getRawX(); 1689 mLastTouchY = event.getRawY(); 1690 mLastTouchDown = event; 1691 mIgnoreTouch = false; 1692 if (mCurrentTransition.mTouchResponse != null) { 1693 region = mCurrentTransition.mTouchResponse 1694 .getLimitBoundsTo(mMotionLayout, cache); 1695 if (region != null 1696 && !region.contains(mLastTouchDown.getX(), mLastTouchDown.getY())) { 1697 mLastTouchDown = null; 1698 mIgnoreTouch = true; 1699 return; 1700 } 1701 region = mCurrentTransition.mTouchResponse 1702 .getTouchRegion(mMotionLayout, cache); 1703 if (region != null 1704 && !region.contains(mLastTouchDown.getX(), 1705 mLastTouchDown.getY())) { 1706 mMotionOutsideRegion = true; 1707 } else { 1708 mMotionOutsideRegion = false; 1709 } 1710 mCurrentTransition.mTouchResponse.setDown(mLastTouchX, mLastTouchY); 1711 } 1712 if (DEBUG) { 1713 Log.v(TAG, "----- ACTION_DOWN " + mLastTouchX + "," + mLastTouchY); 1714 } 1715 return; 1716 case MotionEvent.ACTION_MOVE: 1717 if (mIgnoreTouch) { 1718 break; 1719 } 1720 float dy = event.getRawY() - mLastTouchY; 1721 float dx = event.getRawX() - mLastTouchX; 1722 if (DEBUG) { 1723 Log.v(TAG, "----- ACTION_MOVE " + dx + "," + dy); 1724 } 1725 if ((dx == 0.0 && dy == 0.0) || mLastTouchDown == null) { 1726 return; 1727 } 1728 1729 Transition transition = 1730 bestTransitionFor(currentState, dx, dy, mLastTouchDown); 1731 if (DEBUG) { 1732 Log.v(TAG, Debug.getLocation() + " best Transition For " 1733 + dx + "," + dy + " " 1734 + ((transition == null) ? null 1735 : transition.debugString(mMotionLayout.getContext()))); 1736 } 1737 if (transition != null) { 1738 1739 motionLayout.setTransition(transition); 1740 region = mCurrentTransition 1741 .mTouchResponse.getTouchRegion(mMotionLayout, cache); 1742 mMotionOutsideRegion = region != null 1743 && !region.contains(mLastTouchDown.getX(), mLastTouchDown.getY()); 1744 mCurrentTransition.mTouchResponse.setUpTouchEvent(mLastTouchX, mLastTouchY); 1745 } 1746 } 1747 } 1748 if (mIgnoreTouch) { 1749 return; 1750 } 1751 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null 1752 && !mMotionOutsideRegion) { 1753 mCurrentTransition.mTouchResponse.processTouchEvent(event, 1754 mVelocityTracker, currentState, this); 1755 } 1756 1757 mLastTouchX = event.getRawX(); 1758 mLastTouchY = event.getRawY(); 1759 1760 if (event.getAction() == MotionEvent.ACTION_UP) { 1761 if (mVelocityTracker != null) { 1762 mVelocityTracker.recycle(); 1763 mVelocityTracker = null; 1764 if (motionLayout.mCurrentState != UNSET) { 1765 autoTransition(motionLayout, motionLayout.mCurrentState); 1766 } 1767 } 1768 } 1769 } 1770 processScrollMove(float dx, float dy)1771 void processScrollMove(float dx, float dy) { 1772 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1773 mCurrentTransition.mTouchResponse.scrollMove(dx, dy); 1774 } 1775 } 1776 processScrollUp(float dx, float dy)1777 void processScrollUp(float dx, float dy) { 1778 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1779 mCurrentTransition.mTouchResponse.scrollUp(dx, dy); 1780 } 1781 } 1782 1783 /** 1784 * Calculate if a drag in this direction results in an increase or decrease in progress. 1785 * 1786 * @param dx drag direction in x 1787 * @param dy drag direction in y 1788 * @return change in progress given that dx and dy 1789 */ getProgressDirection(float dx, float dy)1790 float getProgressDirection(float dx, float dy) { 1791 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1792 return mCurrentTransition.mTouchResponse.getProgressDirection(dx, dy); 1793 } 1794 return 0; 1795 } 1796 1797 ///////////////////////////////////////////////////////////// 1798 getStartId()1799 int getStartId() { 1800 if (mCurrentTransition == null) { 1801 return -1; 1802 } 1803 return mCurrentTransition.mConstraintSetStart; 1804 } 1805 getEndId()1806 int getEndId() { 1807 if (mCurrentTransition == null) { 1808 return -1; 1809 } 1810 return mCurrentTransition.mConstraintSetEnd; 1811 } 1812 1813 static final int EASE_IN_OUT = 0; 1814 static final int EASE_IN = 1; 1815 static final int EASE_OUT = 2; 1816 static final int LINEAR = 3; 1817 static final int BOUNCE = 4; 1818 static final int OVERSHOOT = 5; 1819 static final int ANTICIPATE = 6; 1820 1821 /** 1822 * Get the interpolator define for the current Transition 1823 * @return 1824 */ getInterpolator()1825 public Interpolator getInterpolator() { 1826 switch (mCurrentTransition.mDefaultInterpolator) { 1827 case SPLINE_STRING: 1828 final Easing easing = Easing 1829 .getInterpolator(mCurrentTransition.mDefaultInterpolatorString); 1830 return new Interpolator() { 1831 @Override 1832 public float getInterpolation(float v) { 1833 return (float) easing.get(v); 1834 } 1835 }; 1836 case INTERPOLATOR_REFERENCE_ID: 1837 return AnimationUtils.loadInterpolator(mMotionLayout.getContext(), 1838 mCurrentTransition.mDefaultInterpolatorID); 1839 case EASE_IN_OUT: 1840 return new AccelerateDecelerateInterpolator(); 1841 case EASE_IN: 1842 return new AccelerateInterpolator(); 1843 case EASE_OUT: 1844 return new DecelerateInterpolator(); 1845 case LINEAR: 1846 return null; 1847 case ANTICIPATE: 1848 return new AnticipateInterpolator(); 1849 case OVERSHOOT: 1850 return new OvershootInterpolator(); 1851 case BOUNCE: 1852 return new BounceInterpolator(); 1853 } 1854 return null; 1855 } 1856 1857 /** 1858 * Get Duration of the current transition. 1859 * 1860 * @return duration in milliseconds 1861 */ 1862 public int getDuration() { 1863 if (mCurrentTransition != null) { 1864 return mCurrentTransition.mDuration; 1865 } 1866 return mDefaultDuration; 1867 } 1868 1869 /** 1870 * Sets the duration of the current transition or the default if there is no current transition 1871 * 1872 * @param duration in milliseconds 1873 */ 1874 public void setDuration(int duration) { 1875 if (mCurrentTransition != null) { 1876 mCurrentTransition.setDuration(duration); 1877 } else { 1878 mDefaultDuration = duration; 1879 } 1880 } 1881 1882 /** 1883 * The transition arc path mode 1884 * @return 1885 */ 1886 public int gatPathMotionArc() { 1887 return (mCurrentTransition != null) ? mCurrentTransition.mPathMotionArc : UNSET; 1888 } 1889 1890 /** 1891 * Get the staggered value of the current transition. 1892 * Will default to 0 staggered if there is no current transition. 1893 * 1894 * @return 1895 */ 1896 public float getStaggered() { 1897 if (mCurrentTransition != null) { 1898 return mCurrentTransition.mStagger; 1899 } 1900 return 0; 1901 } 1902 1903 float getMaxAcceleration() { 1904 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1905 return mCurrentTransition.mTouchResponse.getMaxAcceleration(); 1906 } 1907 return 0; 1908 } 1909 1910 float getMaxVelocity() { 1911 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1912 return mCurrentTransition.mTouchResponse.getMaxVelocity(); 1913 } 1914 return 0; 1915 } 1916 1917 float getSpringStiffiness() { 1918 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1919 return mCurrentTransition.mTouchResponse.getSpringStiffness(); 1920 } 1921 return 0; 1922 } 1923 1924 float getSpringMass() { 1925 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1926 return mCurrentTransition.mTouchResponse.getSpringMass(); 1927 } 1928 return 0; 1929 } 1930 1931 float getSpringDamping() { 1932 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1933 return mCurrentTransition.mTouchResponse.getSpringDamping(); 1934 } 1935 return 0; 1936 } 1937 1938 float getSpringStopThreshold() { 1939 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1940 return mCurrentTransition.mTouchResponse.getSpringStopThreshold(); 1941 } 1942 return 0; 1943 } 1944 int getSpringBoundary() { 1945 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1946 return mCurrentTransition.mTouchResponse.getSpringBoundary(); 1947 } 1948 return 0; 1949 } 1950 int getAutoCompleteMode() { 1951 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1952 return mCurrentTransition.mTouchResponse.getAutoCompleteMode(); 1953 } 1954 return 0; 1955 } 1956 void setupTouch() { 1957 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1958 mCurrentTransition.mTouchResponse.setupTouch(); 1959 } 1960 } 1961 1962 boolean getMoveWhenScrollAtTop() { 1963 if (mCurrentTransition != null && mCurrentTransition.mTouchResponse != null) { 1964 return mCurrentTransition.mTouchResponse.getMoveWhenScrollAtTop(); 1965 } 1966 return false; 1967 } 1968 1969 /** 1970 * read the constraints from the inflation of the ConstraintLayout 1971 * If the constraintset does not contain information about a view this information is used 1972 * as a "fallback" position. 1973 * 1974 * @param motionLayout 1975 */ 1976 void readFallback(MotionLayout motionLayout) { 1977 1978 for (int i = 0; i < mConstraintSetMap.size(); i++) { 1979 int key = mConstraintSetMap.keyAt(i); 1980 if (hasCycleDependency(key)) { 1981 Log.e(TAG, "Cannot be derived from yourself"); 1982 return; 1983 } 1984 readConstraintChain(key, motionLayout); 1985 } 1986 } 1987 1988 /** 1989 * This is brute force but the number of ConstraintSets is typically very small (< 5) 1990 * 1991 * @param key 1992 * @return 1993 */ 1994 private boolean hasCycleDependency(int key) { 1995 int derived = mDeriveMap.get(key); 1996 int len = mDeriveMap.size(); 1997 while (derived > 0) { 1998 if (derived == key) { 1999 return true; 2000 } 2001 if (len-- < 0) { 2002 return true; 2003 } 2004 derived = mDeriveMap.get(derived); 2005 } 2006 return false; 2007 } 2008 2009 /** 2010 * Recursive descent of the deriveConstraintsFrom tree reading the motionLayout if 2011 * needed. 2012 * 2013 * @param key 2014 */ 2015 private void readConstraintChain(int key, MotionLayout motionLayout) { 2016 ConstraintSet cs = mConstraintSetMap.get(key); 2017 cs.derivedState = cs.mIdString; 2018 int derivedFromId = mDeriveMap.get(key); 2019 if (derivedFromId > 0) { 2020 readConstraintChain(derivedFromId, motionLayout); 2021 ConstraintSet derivedFrom = mConstraintSetMap.get(derivedFromId); 2022 if (derivedFrom == null) { 2023 Log.e(TAG, "ERROR! invalid deriveConstraintsFrom: @id/" 2024 + Debug.getName(mMotionLayout.getContext(), derivedFromId)); 2025 return; 2026 } 2027 cs.derivedState += "/" + derivedFrom.derivedState; 2028 cs.readFallback(derivedFrom); 2029 } else { 2030 cs.derivedState += " layout"; 2031 cs.readFallback(motionLayout); 2032 } 2033 cs.applyDeltaFrom(cs); 2034 } 2035 2036 /** 2037 * Utility to strip the @id/ from an id 2038 * @param id 2039 * @return 2040 */ 2041 public static String stripID(String id) { 2042 if (id == null) { 2043 return ""; 2044 } 2045 int index = id.indexOf('/'); 2046 if (index < 0) { 2047 return id; 2048 } 2049 return id.substring(index + 1); 2050 } 2051 2052 /** 2053 * Used at design time 2054 * 2055 * @param id 2056 * @return 2057 */ 2058 public int lookUpConstraintId(String id) { 2059 Integer boxed = mConstraintSetIdMap.get(id); 2060 if (boxed == null) { 2061 return 0; 2062 } else { 2063 return boxed; 2064 } 2065 } 2066 2067 /** 2068 * used at design time 2069 * 2070 * @return 2071 */ 2072 public String lookUpConstraintName(int id) { 2073 for (Map.Entry<String, Integer> entry : mConstraintSetIdMap.entrySet()) { 2074 Integer boxed = entry.getValue(); 2075 if (boxed == null) { 2076 continue; 2077 } 2078 2079 if (boxed == id) { 2080 return entry.getKey(); 2081 } 2082 } 2083 return null; 2084 } 2085 2086 /** 2087 * this allow disabling autoTransitions to prevent design surface from being in undefined states 2088 * 2089 * @param disable 2090 */ 2091 public void disableAutoTransition(boolean disable) { 2092 mDisableAutoTransition = disable; 2093 } 2094 2095 /** 2096 * Construct a user friendly error string 2097 * 2098 * @param context the context 2099 * @param resourceId the xml being parsed 2100 * @param pullParser the XML parser 2101 * @return 2102 */ 2103 static String getLine(Context context, int resourceId, XmlPullParser pullParser) { 2104 return ".(" + Debug.getName(context, resourceId) + ".xml:" + pullParser.getLineNumber() 2105 + ") \"" + pullParser.getName() + "\""; 2106 } 2107 } 2108