Android Open Source - AndroidUIExample Action View






From Project

Back to project page AndroidUIExample.

License

The source code is released under:

Apache License

If you think the Android project AndroidUIExample listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.example.androidui;
/*from ww w.  j av  a  2 s.c om*/
import action.Action;
import action.BackAction;
import action.CloseAction;
import action.DrawerAction;
import action.LineSegment;
import action.PlusAction;
import util.BakedBezierInterpolator;
import util.UiHelper;

import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;


/**
 * ActionView allows you to dynamically display action and automatically animate from one action to another.
 * <p/>
 * ActionView can be included in your layout xml quite easy:
 * <pre>
 * {@code
 * <at.markushi.ui.ActionView
 *      android:id="@+id/action"
 *      android:layout_width="56dip"
 *      android:layout_height="56dip"
 *      android:padding="16dip"
 *      app:av_color="@android:color/white"
 *      app:av_action="drawer"/>
 * }</pre>
 *
 * In order to start a transition to another {@link at.markushi.ui.action.Action} call {@link #setAction(at.markushi.ui.action.Action)}
 *
 * @see at.markushi.ui.action.BackAction
 * @see at.markushi.ui.action.CloseAction
 * @see at.markushi.ui.action.DrawerAction
 * @see at.markushi.ui.action.PlusAction
 */
public class ActionView extends View {

  public static final int ROTATE_CLOCKWISE = 0;
  public static final int ROTATE_COUNTER_CLOCKWISE = 1;
  private static final int STROKE_SIZE_DIP = 2;

  private int color;
  private Action oldAction;
  private Action currentAction;
  private Action animatedAction;

  private float animationProgress;
  private float scale;
  private int padding;
  private Path path;
  private Paint paint;

  private float centerX;
  private float centerY;
  private boolean ready = false;
  private boolean animateWhenReady = false;
  private int rotation = ROTATE_CLOCKWISE;
  private long animationDuration;
  private int size;

  public ActionView(Context context) {
    this(context, null);
  }

  public ActionView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public ActionView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    animationDuration = getResources().getInteger(R.integer.av_animationDuration);
    animatedAction = new Action(new float[Action.ACTION_SIZE], null);

    final float strokeSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, STROKE_SIZE_DIP, context.getResources().getDisplayMetrics());
    path = new Path();
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(0xDDFFFFFF);
    paint.setStrokeWidth(strokeSize);
    paint.setStyle(Paint.Style.STROKE);

    if (attrs == null) {
      return;
    }
    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionView);
    final int color = a.getColor(R.styleable.ActionView_av_color, 0xDDFFFFF);
    final int actionId = a.getInt(R.styleable.ActionView_av_action, 0);
    a.recycle();

    paint.setColor(color);
    final Action action = getActionFromEnum(actionId);
    setAction(action);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    this.centerX = w / 2;
    this.centerY = h / 2;
    padding = getPaddingLeft();
    size = Math.min(w, h);
    scale = Math.min(w, h) - (2 * padding);
    ready = true;
    transformActions();

    if (animateWhenReady) {
      animateWhenReady = false;
      startAnimation();
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (currentAction == null) {
      return;
    }

    if (oldAction == null) {
      updatePath(currentAction);
    } else {
      final float inverseProgress = 1f - animationProgress;
      final float[] current = currentAction.getLineData();
      final float[] old = oldAction.getLineData();
      final float[] animated = animatedAction.getLineData();
      for (int i = 0; i < animated.length; i++) {
        animated[i] = current[i] * animationProgress + old[i] * inverseProgress;
      }
      updatePath(animatedAction);
    }

    canvas.rotate((rotation == ROTATE_CLOCKWISE ? 180f : -180f) * animationProgress, centerX, centerY);
    canvas.drawPath(path, paint);
  }

  public float getAnimationProgress() {
    return animationProgress;
  }

  public void setAnimationProgress(float animationProgress) {
    this.animationProgress = animationProgress;
    UiHelper.postInvalidateOnAnimation(this);
  }

  /**
   * Set the color used for drawing an {@link at.markushi.ui.action.Action}.
   *
   * @param color
   */
  public void setColor(final int color) {
    this.color = color;
    paint.setColor(color);
    UiHelper.postInvalidateOnAnimation(this);
  }

  /**
   * @return the current action
   */
  public Action getAction() {
    return currentAction;
  }

  /**
   * Sets the new action. If an action was set before a transition will be started.
   *
   * @param action
   * @see #setAction(at.markushi.ui.action.Action, boolean)
   * @see #setAction(at.markushi.ui.action.Action, boolean, int)
   * @see #setAction(at.markushi.ui.action.Action, at.markushi.ui.action.Action, int, long)
   */
  public void setAction(final Action action) {
    setAction(action, true, ROTATE_CLOCKWISE);
  }

  /**
   * Sets the new action. If an action was set before a transition will be started.
   *
   * @param action
   * @param rotation Can be either {@link #ROTATE_CLOCKWISE} or {@link #ROTATE_COUNTER_CLOCKWISE}.
   */
  public void setAction(final Action action, final int rotation) {
    setAction(action, true, rotation);
  }

  /**
   * Sets the new action.
   *
   * @param action
   * @param animate If a prior action was set and {@code true} a transition will be started, otherwise not.
   */
  public void setAction(final Action action, final boolean animate) {
    setAction(action, animate, ROTATE_CLOCKWISE);
  }

  /**
   * Sets a new action transition.
   *
   * @param fromAction The initial action.
   * @param toAction   The target action.
   * @param rotation   The rotation direction used for the transition {@link #ROTATE_CLOCKWISE} or {@link #ROTATE_COUNTER_CLOCKWISE}.
   * @param delay      The delay in ms before the transition is started.
   */
  @TargetApi(Build.VERSION_CODES.KITKAT) public void setAction(final Action fromAction, final Action toAction, final int rotation, long delay) {
    setAction(fromAction, false, ROTATE_CLOCKWISE);
    postDelayed(new Runnable() {
      @Override
      public void run() {
        if (!isAttachedToWindow()) {
          return;
        }
        setAction(toAction, true, rotation);
      }
    }, delay);
  }

  /**
   * Set the animation duration used for Action transitions
   *
   * @param animationDuration the duration in ms
   */
  public void setAnimationDuration(long animationDuration) {
    this.animationDuration = animationDuration;
  }

  @Override
  protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    final SavedState ss = new SavedState(superState);
    ss.currentAction = currentAction;
    ss.color = color;
    return ss;
  }

  @Override
  protected void onRestoreInstanceState(Parcelable state) {
    SavedState ss = (SavedState) state;
    super.onRestoreInstanceState(ss.getSuperState());
    this.color = ss.color;
    this.currentAction = ss.currentAction;
    this.animationProgress = 1f;
  }

  private void setAction(Action action, boolean animate, int rotation) {
    if (action == null) {
      return;
    }

    this.rotation = rotation;
    if (currentAction == null) {
      currentAction = action;
      currentAction.flipHorizontally();
      animationProgress = 1f;
      UiHelper.postInvalidateOnAnimation(this);
      return;
    }

    if (currentAction.getClass().equals(action.getClass())) {
      return;
    }

    oldAction = currentAction;
    currentAction = action;

    if (animate) {
      animationProgress = 0f;
      if (ready) {
        startAnimation();
      } else {
        animateWhenReady = true;
      }
    } else {
      animationProgress = 1f;
      UiHelper.postInvalidateOnAnimation(this);
    }
  }

  private void updatePath(Action action) {
    path.reset();

    final float[] data = action.getLineData();
    // Once we're near the end of the animation we use the action segments to draw linked lines
    if (animationProgress > 0.95f && !action.getLineSegments().isEmpty()) {
      for (LineSegment s : action.getLineSegments()) {
        path.moveTo(data[s.getStartIdx() + 0], data[s.getStartIdx() + 1]);
        path.lineTo(data[s.getStartIdx() + 2], data[s.getStartIdx() + 3]);
        for (int i = 1; i < s.indexes.length; i++) {
          path.lineTo(data[s.indexes[i] + 0], data[s.indexes[i] + 1]);
          path.lineTo(data[s.indexes[i] + 2], data[s.indexes[i] + 3]);
        }
      }
    } else {
      for (int i = 0; i < data.length; i += 4) {
        path.moveTo(data[i + 0], data[i + 1]);
        path.lineTo(data[i + 2], data[i + 3]);
      }
    }
  }

  private void transformActions() {
    if (currentAction != null && !currentAction.isTransformed()) {
      currentAction.transform(padding, padding, scale, size);
    }

    if (oldAction != null && !oldAction.isTransformed()) {
      oldAction.transform(padding, padding, scale, size);
    }
  }

  @TargetApi(Build.VERSION_CODES.HONEYCOMB) private void startAnimation() {
    oldAction.flipHorizontally();
    currentAction.flipHorizontally();

    transformActions();

    animatedAction.setLineSegments(currentAction.getLineSegments());
    final ObjectAnimator animator = ObjectAnimator.ofFloat(ActionView.this, "animationProgress", 0f, 1f);
    animator.setInterpolator(BakedBezierInterpolator.getInstance());
    animator.setDuration(animationDuration).start();
  }

  private Action getActionFromEnum(int id) {
    switch (id) {
    case 0:
      return new DrawerAction();
    case 1:
      return new BackAction();
    case 2:
      return new CloseAction();
    case 3:
      return new PlusAction();
    }

    return null;
  }

  static class SavedState extends BaseSavedState {

    Action currentAction;
    int color;

    public SavedState(Parcelable superState) {
      super(superState);
    }

    public SavedState(Parcel source) {
      super(source);
      currentAction = source.readParcelable(getClass().getClassLoader());
      color = source.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
      super.writeToParcel(dest, flags);
      dest.writeParcelable(currentAction, 0);
      dest.writeInt(color);
    }

    public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {

      @Override
      public SavedState createFromParcel(Parcel parcel) {
        return new SavedState(parcel);
      }

      @Override
      public SavedState[] newArray(int size) {
        return new SavedState[size];
      }
    };
  }
}




Java Source Code List

action.Action.java
action.BackAction.java
action.CloseAction.java
action.DrawerAction.java
action.LineSegment.java
action.PlusAction.java
action.SymbolAction.java
com.example.androidui.ActionView.java
com.example.androidui.MyActivity.java
com.example.androidui.RevealColorView.java
util.BakedBezierInterpolator.java
util.UiHelper.java