@Override
  public void paint(Graphics g) {
    super.paint(g);
    paint_count++;
    long time = System.currentTimeMillis();
    int w = getWidth();
    int h = getHeight();
    float buttonCX = w / 2.0f;
    float buttonCY = h / 2.0f;
    float startX = buttonCX;
    float startY = buttonCY;
    float endX = buttonCX;
    float endY = buttonCY;
    if (myMoveObject != 0) {
      float dx = movement[myMoveObject][0];
      float dy = movement[myMoveObject][1];
      startX = buttonCX - (dx * w) / 3.0f;
      startY = buttonCY - (dy * h) / 3.0f;
      endX = buttonCX + (dx * w) / 3.0f;
      endY = buttonCY + (dy * h) / 3.0f;
      buttonCX = startX + myEasedPercent * (endX - startX);
      buttonCY = startY + myEasedPercent * (endY - startY);

    }
    Graphics2D g2d = (Graphics2D) g.create();
    AffineTransform at;
    Stroke old = g2d.getStroke();
    g2d.setColor(Color.RED);
    g2d.setStroke(DASH_STROKE);
    g2d.drawLine((int) startX, (int) startY, (int) endX, (int) endY);
    g2d.setStroke(old);
    Color background = Color.LIGHT_GRAY;
    Color border = Color.DARK_GRAY;
    Color text = Color.BLACK;
    if (myAttributeMask != 0) {

      if ((myAttributeMask & (1 << CycleView.Prop.PATH_ROTATE.ordinal())) != 0) {
        at = new AffineTransform();
        at.rotate(Math.toRadians(myCycleModel.getValue(CycleView.Prop.PATH_ROTATE, myEasedPercent)), buttonCX,
            buttonCY);
        g2d.transform(at);
      }
      if ((myAttributeMask & (1 << CycleView.Prop.ALPHA.ordinal())) != 0) {
        int alpha = Math
            .max(0, Math.min(255, (int) (myCycleModel.getValue(CycleView.Prop.ALPHA, myEasedPercent) * 255)));
        background = new Color(background.getRed(), background.getGreen(), background.getBlue(),
            alpha);
        border = new Color(border.getRed(), border.getGreen(), border.getBlue(), alpha);
        text = new Color(text.getRed(), text.getGreen(), text.getBlue(), alpha);
      }

      if ((myAttributeMask & (1 << CycleView.Prop.SCALE_X.ordinal())) != 0) {
        at = new AffineTransform();
        at.translate(w / 2.0, buttonCY);
        at.scale(myCycleModel.getValue(CycleView.Prop.SCALE_X, myEasedPercent), 1);
        at.translate(-w / 2.0, -buttonCY);

        g2d.transform(at);
      }

      if ((myAttributeMask & (1 << CycleView.Prop.SCALE_Y.ordinal())) != 0) {

        at = new AffineTransform();
        at.translate(buttonCX, buttonCY);
        at.scale(1, myCycleModel.getValue(CycleView.Prop.SCALE_Y, myEasedPercent));
        at.translate(-buttonCX, -buttonCY);
        g2d.transform(at);
      }

      if ((myAttributeMask & (1 << CycleView.Prop.TRANSLATION_X.ordinal())) != 0) {

        at = new AffineTransform();
        at.translate(myCycleModel.getValue(CycleView.Prop.TRANSLATION_X, myEasedPercent), 0);
        g2d.transform(at);
      }

      if ((myAttributeMask & (1 << CycleView.Prop.TRANSLATION_Y.ordinal())) != 0) {

        at = new AffineTransform();
        at.translate(0, myCycleModel.getValue(CycleView.Prop.TRANSLATION_Y, myEasedPercent));
        g2d.transform(at);
      }
      if ((myAttributeMask & (1 << CycleView.Prop.ROTATION.ordinal())) != 0) {
        at = new AffineTransform();
        at.rotate(Math.toRadians(myCycleModel.getValue(CycleView.Prop.ROTATION, myEasedPercent)), buttonCX,
            buttonCY);
        g2d.transform(at);
      }

    }

    int px = (int) (0.5 + buttonCX - BUTTON_WIDTH / 2.0f);
    int py = (int) (0.5 + buttonCY - BUTTON_HEIGHT / 2.0f);

    g2d.setColor(background);
    g2d.fillRoundRect(px, py, BUTTON_WIDTH, BUTTON_HEIGHT, 5, 5);
    g2d.setColor(border);
    g2d.drawRoundRect(px, py, BUTTON_WIDTH, BUTTON_HEIGHT, 5, 5);
    int sw = g.getFontMetrics().stringWidth(myTitle);
    int fh = g.getFontMetrics().getHeight();
    int fa = g.getFontMetrics().getAscent();
    g2d.setColor(text);
    g2d.drawString(myTitle, px + BUTTON_WIDTH / 2 - sw / 2, py + BUTTON_HEIGHT / 2 + fa - fh / 2);
    if (time - last_update > 1000) {
      if (RENDERING_STATS) {

        System.out.println("render" + 1000 * call_count / (time - last_update) + "fps paint "
            + 1000 * paint_count / (time - last_update) + "fps");
      }
      last_update = time;
      paint_count = 0;
      call_count = 0;
    }
  }
This panel simulates and Android view parameter for controlling a button
ConstraintLayoutExamples/CycleEditor/src/com/google/androidstudio/motionlayoutcycles/AnimationPanel.java