docs: protected void createAnimators(@NonNull ViewGroup sceneRoot,
                                     @NonNull TransitionValuesMaps startValues,
                                     @NonNull TransitionValuesMaps endValues,
                                     @NonNull ArrayList<TransitionValues> startValuesList,
                                     @NonNull ArrayList<TransitionValues> endValuesList) {
          if (DBG) {
              Log.d(LOG_TAG, "createAnimators() for " + this);
          }
          ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
          long minStartDelay = Long.MAX_VALUE;
          int minAnimator = mAnimators.size();
          SparseArray<Long> startDelays = new SparseArray<Long>();
          int startValuesListCount = startValuesList.size();
          for (int i = 0; i < startValuesListCount; ++i) {
              TransitionValues start = startValuesList.get(i);
              TransitionValues end = endValuesList.get(i);
              if (start != null && !start.targetedTransitions.contains(this)) {
                  start = null;
              }
              if (end != null && !end.targetedTransitions.contains(this)) {
                  end = null;
              }
              if (start == null && end == null) {
                  continue;
              }
              // Only bother trying to animate with values that differ between start/end
              boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
              if (isChanged) {
                  if (DBG) {
                      View view = (end != null) ? end.view : start.view;
                      Log.d(LOG_TAG, "  differing start/end values for view " + view);
                      if (start == null || end == null) {
                          Log.d(LOG_TAG, "    " + ((start == null) ?
                                  "start null, end non-null" : "start non-null, end null"));
                      } else {
                          for (String key : start.values.keySet()) {
                              Object startValue = start.values.get(key);
                              Object endValue = end.values.get(key);
                              if (startValue != endValue && !startValue.equals(endValue)) {
                                  Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
                                          "), end(" + endValue + ")");
                              }
                          }
                      }
                  }
                  // TODO: what to do about targetIds and itemIds?
                  Animator animator = createAnimator(sceneRoot, start, end);
                  if (animator != null) {
                      // Save animation info for future cancellation purposes
                      View view;
                      TransitionValues infoValues = null;
                      if (end != null) {
                          view = end.view;
                          String[] properties = getTransitionProperties();
                          if (properties != null && properties.length > 0) {
                              infoValues = new TransitionValues(view);
                              TransitionValues newValues = endValues.viewValues.get(view);
                              if (newValues != null) {
                                  for (int j = 0; j < properties.length; ++j) {
                                      infoValues.values.put(properties[j],
                                              newValues.values.get(properties[j]));
                                  }
                              }
                              synchronized (sRunningAnimators) {
                                  int numExistingAnims = runningAnimators.size();
                                  for (int j = 0; j < numExistingAnims; ++j) {
                                      Animator anim = runningAnimators.keyAt(j);
                                      AnimationInfo info = runningAnimators.get(anim);
                                      if (info.values != null && info.view == view && info.name.equals(getName())) {
                                          if (info.values.equals(infoValues)) {
                                              // Favor the old animator
                                              animator = null;
                                              break;
                                          }
                                      }
                                  }
                              }
                          }
                      } else {
                          view = start.view;
                      }
                      if (animator != null) {
                          if (mPropagation != null) {
                              long delay = mPropagation
                                      .getStartDelay(sceneRoot, this, start, end);
                              startDelays.put(mAnimators.size(), delay);
                              minStartDelay = Math.min(delay, minStartDelay);
                          }
                          AnimationInfo info = new AnimationInfo(view, getName(), this,
                                  ViewUtils.getWindowId(sceneRoot), infoValues);
                          runningAnimators.put(animator, info);
                          mAnimators.add(animator);
                      }
                  }
              }
          }
          if (startDelays.size() != 0) {
              for (int i = 0; i < startDelays.size(); i++) {
                  int index = startDelays.keyAt(i);
                  Animator animator = mAnimators.get(index);
                  long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
                  animator.setStartDelay(delay);
              }
          }
      }