diff --git a/app/build.gradle b/app/build.gradle index 4638cbb..afc23aa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" defaultConfig { applicationId "com.hitherejoe.animate" @@ -20,13 +20,14 @@ android { } dependencies { - final SUPPORT_LIBRARY_VERSION = '23.1.1' + final SUPPORT_LIBRARY_VERSION = '23.4.0' compile fileTree(dir: 'libs', include: ['*.jar']) compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION" compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION" compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION" + compile 'com.jjoe64:graphview:4.0.1' compile 'com.jakewharton:butterknife:7.0.1' testCompile 'junit:junit:4.12' diff --git a/app/src/main/java/com/hitherejoe/animate/ui/activity/InterpolatorActivity.java b/app/src/main/java/com/hitherejoe/animate/ui/activity/InterpolatorActivity.java index f52586d..20eba7f 100644 --- a/app/src/main/java/com/hitherejoe/animate/ui/activity/InterpolatorActivity.java +++ b/app/src/main/java/com/hitherejoe/animate/ui/activity/InterpolatorActivity.java @@ -1,128 +1,168 @@ package com.hitherejoe.animate.ui.activity; -import android.animation.Animator; +import android.graphics.Color; +import android.graphics.Path; import android.os.Bundle; -import android.support.design.widget.FloatingActionButton; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.support.v7.app.ActionBar; +import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnticipateInterpolator; import android.view.animation.AnticipateOvershootInterpolator; import android.view.animation.BounceInterpolator; import android.view.animation.DecelerateInterpolator; -import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.OvershootInterpolator; +import android.view.animation.PathInterpolator; +import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.RelativeLayout; import android.widget.Spinner; +import android.widget.TextView; import com.hitherejoe.animate.R; +import com.hitherejoe.animate.ui.widget.InterpolatorGraphView; -import butterknife.Bind; -import butterknife.ButterKnife; -import butterknife.OnClick; - -public class InterpolatorActivity extends BaseActivity { - - @Bind(R.id.text_animate) - Button mAnimateText; - - @Bind(R.id.layout_root) - RelativeLayout mLayoutRoot; - - @Bind(R.id.spinner_interpolators) - Spinner mInterpolatorSpinner; - - @Bind(R.id.fab_interpolator) - FloatingActionButton mFloatingActionButton; - - private boolean mIsButtonAtTop; +public class InterpolatorActivity extends BaseActivity implements AdapterView.OnItemSelectedListener { + private InterpolatorGraphView graph; + private View robotTest; + private View robotControl; + private boolean animateToEnd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_interpolator); - ButterKnife.bind(this); - mIsButtonAtTop = true; - setupSpinnerAdapter(); - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); - } - - private void setupSpinnerAdapter() { - ArrayAdapter adapter = new ArrayAdapter<>( - this, R.layout.item_spinner, getResources().getStringArray(R.array.interpolators)); - adapter.setDropDownViewResource(R.layout.item_spinner_dropdown); - mInterpolatorSpinner.setAdapter(adapter); + graph = (InterpolatorGraphView) findViewById(R.id.interpolator_activity_graph); + Spinner spinner = (Spinner) findViewById(R.id.interpolator_activity_spinner); + robotTest = findViewById(R.id.interpolator_activity_robot_test); + robotControl = findViewById(R.id.interpolator_activity_robot_control); + + assert spinner != null; + assert graph != null; + + spinner.setSelection(0); + spinner.setOnItemSelectedListener(this); + ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.interpolators, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + graph.applyInterpolator(new OvershootInterpolator()); + setupToolbar(); } - @OnClick(R.id.text_animate) - public void animate() { - int padding = - mFloatingActionButton.getPaddingBottom() + mFloatingActionButton.getPaddingTop(); - int height = mLayoutRoot.getHeight() - padding; - + private void setupToolbar() { ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) height -= actionBar.getHeight(); - - if (!mIsButtonAtTop) height = -height; - mIsButtonAtTop = !mIsButtonAtTop; - mFloatingActionButton.animate().setInterpolator(getSelectedInterpolator()) - .setDuration(500) - .setStartDelay(200) - .translationYBy(height) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - mAnimateText.setEnabled(false); - } - - @Override - public void onAnimationEnd(Animator animation) { - mAnimateText.setEnabled(true); - } - - @Override - public void onAnimationCancel(Animator animation) { } - - @Override - public void onAnimationRepeat(Animator animation) { } - }) - .start(); + if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); } - private Interpolator getSelectedInterpolator() { - switch (mInterpolatorSpinner.getSelectedItemPosition()) { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + // 0: LinearInterpolator + // 1: DecelerateInterpolator + // 2: AccelerateInterpolator + // 3: AccelerateDecelerateInterpolator + // 4: FastOutLinearInInterpolator + // 5: FastOutSlowInInterpolator + // 6: LinearOutSlowInInterpolator + // 7: OvershootInterpolator + // 8: AnticipateInterpolator + // 9: AnticipateOvershootInterpolator + // 10: BounceInterpolator + // 11: PathInterpolator + + ((TextView) parent.getChildAt(0)).setTextColor(Color.BLACK); + + switch (position) { + case 0: + graph.applyInterpolator(new LinearInterpolator()); + break; case 1: - return new FastOutLinearInInterpolator(); + graph.applyInterpolator( + new InterpolatorGraphView.GraphicInterpolator(new DecelerateInterpolator(1f), "Factor: 1 (default)", Color.RED), + new InterpolatorGraphView.GraphicInterpolator(new DecelerateInterpolator(1.4f), "Factor: 1.4", Color.BLUE), + new InterpolatorGraphView.GraphicInterpolator(new DecelerateInterpolator(0.6f), "Factor: 0.6", Color.DKGRAY) + ); + break; case 2: - return new FastOutSlowInInterpolator(); + graph.applyInterpolator( + new InterpolatorGraphView.GraphicInterpolator(new AccelerateInterpolator(1f), "Factor: 1 (default)", Color.RED), + new InterpolatorGraphView.GraphicInterpolator(new AccelerateInterpolator(1.4f), "Factor: 1.4", Color.BLUE), + new InterpolatorGraphView.GraphicInterpolator(new AccelerateInterpolator(0.6f), "Factor: 0.6", Color.DKGRAY) + ); + break; case 3: - return new LinearOutSlowInInterpolator(); + graph.applyInterpolator(new AccelerateDecelerateInterpolator()); + break; case 4: - return new AccelerateDecelerateInterpolator(); + graph.applyInterpolator(new FastOutLinearInInterpolator()); + break; case 5: - return new AccelerateInterpolator(); + graph.applyInterpolator(new FastOutSlowInInterpolator()); + break; case 6: - return new DecelerateInterpolator(); + graph.applyInterpolator(new LinearOutSlowInInterpolator()); + break; case 7: - return new AnticipateInterpolator(); + graph.applyInterpolator( + new InterpolatorGraphView.GraphicInterpolator(new OvershootInterpolator(2.0f), "Tension = 2 (default)", Color.RED), + new InterpolatorGraphView.GraphicInterpolator(new OvershootInterpolator(2.4f), "Tension = 2.4", Color.BLUE), + new InterpolatorGraphView.GraphicInterpolator(new OvershootInterpolator(1.6f), "Tension = 1.6", Color.DKGRAY) + ); + break; case 8: - return new AnticipateOvershootInterpolator(); + graph.applyInterpolator( + new InterpolatorGraphView.GraphicInterpolator(new AnticipateInterpolator(2.0f), "Tension = 2 (default)", Color.RED), + new InterpolatorGraphView.GraphicInterpolator(new AnticipateInterpolator(2.4f), "Tension = 2.4", Color.BLUE), + new InterpolatorGraphView.GraphicInterpolator(new AnticipateInterpolator(1.6f), "Tension = 1.6", Color.DKGRAY) + ); + break; case 9: - return new BounceInterpolator(); + graph.applyInterpolator( + new InterpolatorGraphView.GraphicInterpolator(new AnticipateOvershootInterpolator(3.0f), "Tension = 3 (default)", Color.RED), + new InterpolatorGraphView.GraphicInterpolator(new AnticipateOvershootInterpolator(3.4f), "Tension = 3.4", Color.BLUE), + new InterpolatorGraphView.GraphicInterpolator(new AnticipateOvershootInterpolator(2.6f), "Tension = 2.6", Color.DKGRAY) + ); + break; case 10: - return new LinearInterpolator(); + graph.applyInterpolator(new BounceInterpolator()); + break; case 11: - return new OvershootInterpolator(); - default: - return null; + Path p = new Path(); + p.moveTo(0, 0); + p.lineTo(0.8f, 0.2f); + p.lineTo(1, 1); + graph.applyInterpolator( + // http://cubic-bezier.com/#.55,.45,.86,-0.89 + new InterpolatorGraphView.GraphicInterpolator(new PathInterpolator(0.55f, 0.45f, 0.86f, -0.89f), "Cubic (0.55, 0.45, 0.86, -0.89)", Color.RED), + // https://www.jasondavies.com/animated-bezier/ + new InterpolatorGraphView.GraphicInterpolator(new PathInterpolator(0.6f, 0.2f), "Quadratic (0.6, 0.2)", Color.BLUE), + new InterpolatorGraphView.GraphicInterpolator(new PathInterpolator(p), "Path (0, 0; 0.8, 0.2; 1, 1)", Color.DKGRAY) + ); + break; } } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + + public void activateAnimation() { + if (!animateToEnd) { + robotControl.animate().translationX(graph.getWidth() - robotControl.getWidth()).setInterpolator(graph.getInterpolator()).setDuration(1000); + robotTest.animate().translationX(graph.getWidth() - robotControl.getWidth()).setInterpolator(new LinearInterpolator()).setDuration(1000); + animateToEnd = true; + } else { + robotControl.animate().translationX(0).setInterpolator(graph.getInterpolator()).setDuration(1000); + robotTest.animate().translationX(0).setInterpolator(new LinearInterpolator()).setDuration(1000); + animateToEnd = false; + } + } + + public void onAnimate(View view) { + activateAnimation(); + } } diff --git a/app/src/main/java/com/hitherejoe/animate/ui/widget/InterpolatorGraphView.java b/app/src/main/java/com/hitherejoe/animate/ui/widget/InterpolatorGraphView.java new file mode 100644 index 0000000..bbc3d7d --- /dev/null +++ b/app/src/main/java/com/hitherejoe/animate/ui/widget/InterpolatorGraphView.java @@ -0,0 +1,135 @@ +package com.hitherejoe.animate.ui.widget; + +import android.animation.TimeInterpolator; +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.animation.Interpolator; + +import com.hitherejoe.animate.R; +import com.jjoe64.graphview.GraphView; +import com.jjoe64.graphview.helper.StaticLabelsFormatter; +import com.jjoe64.graphview.series.DataPoint; +import com.jjoe64.graphview.series.LineGraphSeries; + +/** + * Created on 21/05/2016. + */ +public class InterpolatorGraphView extends GraphView { + + private float accuracy; + private Interpolator interpolator; + + public InterpolatorGraphView(Context context) { + super(context); + } + + public InterpolatorGraphView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public InterpolatorGraphView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void init() { + super.init(); + + getViewport().setXAxisBoundsManual(true); + getViewport().setYAxisBoundsManual(true); + getViewport().setMinX(0); + getViewport().setMaxX(100); + getViewport().setMinY(-20); + getViewport().setMaxY(120); + + getGridLabelRenderer().setHorizontalAxisTitle(getContext().getString(R.string.time)); + getGridLabelRenderer().setVerticalAxisTitle(getContext().getString(R.string.progression)); + StaticLabelsFormatter staticLabelsFormatter = new StaticLabelsFormatter(this); + staticLabelsFormatter.setHorizontalLabels(new String[] {"0%","", "", "", "", "100%"}); + staticLabelsFormatter.setVerticalLabels(new String[] {"","0%","", "", "", "", "100%" ,""}); + getGridLabelRenderer().setLabelFormatter(staticLabelsFormatter); + getGridLabelRenderer().setHorizontalAxisTitleColor(Color.BLACK); + getGridLabelRenderer().setVerticalAxisTitleColor(Color.BLACK); + getGridLabelRenderer().setHorizontalLabelsColor(Color.BLACK); + getGridLabelRenderer().setVerticalLabelsColor(Color.BLACK); + + accuracy = 0.5f; + } + + /** + * Set the accuracy of the graph + * @param accuracy - Lower is better, this graph will draw a point every x axis value + */ + public void setAccuracy(float accuracy) { + this.accuracy = accuracy; + } + + /** + * Draw over a graphView an interpolators graph + * @param interpolators - The interpolators to show + */ + public void applyInterpolator(@NonNull TimeInterpolator... interpolators) { + removeAllSeries(); + + boolean firstRound = true; + for (TimeInterpolator interpolator : interpolators) { + LineGraphSeries series = new LineGraphSeries<>(); + for (float i = 0; i <= 100; i += accuracy) { + series.appendData(new DataPoint(i, 100 * interpolator.getInterpolation((i / 100f))), true, (int) (101f / accuracy)); + } + + if (interpolator instanceof GraphicInterpolator) { + GraphicInterpolator graphicInterpolator = (GraphicInterpolator) interpolator; + series.setColor(graphicInterpolator.getColor()); + series.setTitle(graphicInterpolator.getTitle()); + if (firstRound) { + this.interpolator = graphicInterpolator.interpolatorInstance; + } + } else { + series.setColor(Color.RED); + if (firstRound) { + this.interpolator = (Interpolator) interpolator; + } + } + + firstRound = false; + addSeries(series); + } + + getLegendRenderer().setVisible((interpolators.length > 1)); + getLegendRenderer().setTextColor(Color.BLACK); + getLegendRenderer().setBackgroundColor(Color.TRANSPARENT); + getLegendRenderer().setFixedPosition(0, 0); + } + + public Interpolator getInterpolator() { + return interpolator; + } + + static public class GraphicInterpolator implements TimeInterpolator { + private Interpolator interpolatorInstance; + private String title; + private int color; + + public GraphicInterpolator(Interpolator interpolatorInstance, String title, int color) { + this.interpolatorInstance = interpolatorInstance; + this.title = title; + this.color = color; + } + + public String getTitle() { + return title; + } + + public int getColor() { + return color; + } + + @Override + public float getInterpolation(float input) { + return interpolatorInstance.getInterpolation(input); + } + } +} diff --git a/app/src/main/res/layout/activity_interpolator.xml b/app/src/main/res/layout/activity_interpolator.xml index 6b3b40e..389a866 100644 --- a/app/src/main/res/layout/activity_interpolator.xml +++ b/app/src/main/res/layout/activity_interpolator.xml @@ -1,47 +1,64 @@ - - - + + + + + + + tools:ignore="ContentDescription"/> + android:layout_width="wrap_content" + android:layout_gravity="center" + android:textColor="@android:color/black" + android:text="@string/control_linearinterpolator" + android:layout_height="wrap_content"/> - + tools:ignore="ContentDescription"/> + +