Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/particles view update #375

Merged
merged 7 commits into from Feb 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle
Expand Up @@ -226,6 +226,9 @@ dependencies {
debugCompile 'com.tomoima.debot:debot:2.0.2'
releaseCompile 'com.tomoima.debot:debot-no-op:2.0.2'

// fps, track the frame rate of animation view.
compile 'jp.wasabeef:takt:1.0.3'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🆒


// Test
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:3.2.2"
Expand Down
4 changes: 4 additions & 0 deletions app/licenses.yml
Expand Up @@ -153,3 +153,7 @@
name: debot
copyrightHolder: Tomoaki Imai
license: The Apache License, Version 2.0
- artifact: jp.wasabeef:takt:+
name: takt
copyrightHolder: Daichi Furiya (Wasabeef)
license: The Apache License, Version 2.0
Expand Up @@ -19,6 +19,8 @@
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import jp.wasabeef.takt.Seat;
import jp.wasabeef.takt.Takt;
import timber.log.Timber;

public class SplashActivity extends BaseActivity {
Expand Down Expand Up @@ -52,12 +54,22 @@ protected void onCreate(Bundle savedInstanceState) {
protected void onStart() {
super.onStart();
loadSessionsForCache();

// Starting new Activity normally will not destroy this Activity, so set this up in start/stop cycle
Takt.stock(getApplication())
.seat(Seat.BOTTOM_RIGHT)
.interval(250)
.listener(fps -> Timber.i("heartbeat() called with: fps = [ %1$.3f ms ]", fps))
.play();
}

@Override
protected void onStop() {
super.onStop();
compositeDisposable.dispose();

// Stop tracking the frame rate.
Takt.finish();
}

private void loadSessionsForCache() {
Expand Down
Expand Up @@ -13,11 +13,10 @@
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.support.v4.content.ContextCompat;
import android.support.v4.util.Pair;
import android.util.AttributeSet;
import android.view.View;

import com.annimon.stream.Stream;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
Expand All @@ -27,13 +26,22 @@

public class ParticlesAnimationView extends View {

@SuppressWarnings("unused")
private static final String TAG = ParticlesAnimationView.class.getSimpleName();

// Use a single static Random generator to ensure the randomness. Also save memory.
private static final Random random = new Random();

private static final int MAX_HEXAGONS = 40;

private static final int LINK_HEXAGON_DISTANCE = 600;

private final Paint paint = new Paint();

private final List<Particle> particles = new ArrayList<>();

private final List<Line> lines = new ArrayList<>();

public ParticlesAnimationView(Context context) {
this(context, null);
}
Expand All @@ -54,18 +62,32 @@ public void onWindowFocusChanged(boolean hasWindowFocus) {
if (hasWindowFocus) {
particles.clear();
particles.addAll(createParticles(MAX_HEXAGONS));

lines.clear();
for (int i = 0; i < particles.size() - 1; i++) {
Particle particle = particles.get(i);
// So there are exactly C(particles.size(), 2) (Mathematical Combination) number of lines, which makes more sense.
for (int j = i + 1; j < particles.size(); j++) {
lines.add(new Line(particle, particles.get(j)));
}
}
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
lines.clear();
particles.clear();
}

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

for (int i = 0, size = particles.size(); i < size; i++) {
particles.get(i).draw(canvas, paint);
}

List<Line> lines = createLines(particles);
for (int i = 0, size = lines.size(); i < size; i++) {
lines.get(i).draw(canvas, paint);
}
Expand Down Expand Up @@ -100,68 +122,43 @@ public Shader resize(int width, int height) {
private List<Particle> createParticles(int count) {
List<Particle> particles = new ArrayList<>();
for (int i = 0; i < count; i++) {
particles.add(new Particle(getWidth(), getHeight()));
particles.add(new Particle(getWidth(), getHeight(), this));
}
return particles;
}

private List<Line> createLines(List<Particle> particles) {
final List<Line> lines = new ArrayList<>();
Stream.of(particles)
.forEach(particle -> Stream.of(particles)
.filter(targetParticle ->
particle.isShouldLinked(LINK_HEXAGON_DISTANCE, targetParticle)
)
.map(linkParticle -> new Line(new float[]{
particle.center.x,
particle.center.y,
linkParticle.center.x,
linkParticle.center.y})
)
.forEach(line -> {
if (!lines.contains(line)) lines.add(line);
})
);
return lines;
}

private class Line {
/**
* A Pair of 'Particles' whose centers can be 'linked to each other ... Called 'Line'.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! 👍

*/
private static class Line extends Pair<Particle, Particle> {

private static final int MAX_ALPHA = 172;
private float[] point;

public Line(float[] point) {
this.point = point;
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
Line(Particle first, Particle second) {
super(first, second);
}

public void draw(Canvas canvas, Paint paint) {
final double distance = Math.floor(Math.sqrt(
(point[2] - point[0]) * (point[2] - point[0])
+ (point[3] - point[1]) * (point[3] - point[1])
));
void draw(Canvas canvas, Paint paint) {
if (!first.shouldBeLinked(LINK_HEXAGON_DISTANCE, second)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, shouldBeLinked is not necessary if first particle compare second particle in draw.

Copy link
Contributor Author

@eneim eneim Feb 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roana0229 not really get your full point, but do you mean to call shouldBeLinked from ParticlesAnimationView#onDraw instead of here? It will end up removing the need of using "Line" object (a Line is actually just a bridge between 2 particles :D).

Yesterday I thought about something like:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0, size = particles.size(); i < size; i++) {
            particles.get(i).draw(canvas, paint);
        }

        // for (int i = 0, size = lines.size(); i < size; i++) {
        //    lines.get(i).draw(canvas, paint);
        //}

        for (int i = 0; i < particles.size() - 1; i++) {
            for (int j = i + 1; j < particles.size(); j++) {
                if (particles.get(i).shouldBeLinked(LINK_HEXAGON_DISTANCE, particles.get(j))) {
                    // TODO draw, use the code from Line
                }
            }
        }
    }

I think what you mean is the following:

    // ParticlesAnimationView
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0, size = particles.size(); i < size; i++) {
            particles.get(i).draw(canvas, paint);
        }

         for (int i = 0, size = lines.size(); i < size; i++) {
             if (lines.get(i).shouldDraw()) {
                 lines.get(i).draw(canvas, paint);
             }
        }
    }

    // ParticlesAnimationView$Line
    ///// shouldDraw simply returns the comparison
    boolean shouldDraw() {
        return first.shouldBeLinked(LINK_HEXAGON_DISTANCE, second);
    }

Neither of above gain any huge impact (but the later looks clean and better, in fact :D). For now I would like to keep current version (this PR is pretty huge now) and maybe another PR from you if you really want to :p. So it is up to you ^^.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that the following code was good.

For now I would like to keep current version (this PR is pretty huge now) and maybe another PR from you

OK! 😄

return;
}

final double distance = Math.sqrt(
Math.pow(second.center.x - first.center.x, 2) + Math.pow((second.center.y - first.center.y), 2)
);
final int alpha = MAX_ALPHA - (int) Math.floor(distance * MAX_ALPHA / LINK_HEXAGON_DISTANCE);
paint.setAlpha(alpha);
canvas.drawLines(point, paint);
}

@Override
public boolean equals(Object obj) {
final float[] targetPoint = ((Line) obj).point;
// return true if same point or reverse point
if (point[0] == targetPoint[0] && point[1] == targetPoint[1]
&& point[2] == targetPoint[2] && point[3] == targetPoint[3]) {
return true;
} else if (point[0] == targetPoint[2] && point[1] == targetPoint[3]
&& point[2] == targetPoint[0] && point[3] == targetPoint[1]) {
return true;
} else {
return super.equals(obj);
}
canvas.drawLine(first.center.x, first.center.y, second.center.x, second.center.y, paint);
}

}

private class Particle {
private static class Particle {

private static final int MAX_ALPHA = 128;
private static final float BASE_RADIUS = 100f;
Expand All @@ -173,27 +170,37 @@ private class Particle {
private Point center;
private Point vector;

public Particle(int maxWidth, int maxHeight) {
final Path path = new Path();

Particle(int maxWidth, int maxHeight, View view) {
this(maxWidth, maxHeight, view.getWidth(), view.getHeight());
}

Particle(int maxWidth, int maxHeight, int hostWidth, int hostHeight) {
center = new Point();
vector = new Point();
reset(maxWidth, maxHeight);
Random random = new Random();
center.x = (int) (getWidth() - getWidth() * random.nextFloat());
center.y = (int) (getHeight() - getHeight() * random.nextFloat());
center.x = (int) (hostWidth - hostWidth * random.nextFloat());
center.y = (int) (hostHeight - hostHeight * random.nextFloat());
}

public boolean isShouldLinked(int linkedDistance, Particle particle) {
if (this.equals(particle)) return false;
boolean shouldBeLinked(int linkedDistance, Particle particle) {
if (this.equals(particle)) {
return false;
}
// Math.pow(x, 2) and x * x stuff, for your information and my curiosity.
// ref: http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/5755b2aee8e8/src/share/vm/opto/library_call.cpp#l1799
final double distance = Math.sqrt(
Math.pow(particle.center.x - this.center.x, 2) + Math.pow(particle.center.y - this.center.y, 2)
);
return distance < linkedDistance;
}

public void draw(Canvas canvas, Paint paint) {
void draw(Canvas canvas, Paint paint) {
move(canvas.getWidth(), canvas.getHeight());
paint.setAlpha(alpha);
canvas.drawPath(getHexagonPath(center.x, center.y, scale), paint);
createHexagonPathOrUpdate(center.x, center.y, scale);
canvas.drawPath(this.path, paint);
}

private void move(int maxWidth, int maxHeight) {
Expand All @@ -216,7 +223,6 @@ private void move(int maxWidth, int maxHeight) {
}

private void reset(int maxWidth, int maxHeight) {
Random random = new Random();
scale = random.nextFloat() + random.nextFloat();
alpha = random.nextInt(MAX_ALPHA + 1);
moveSpeed = random.nextFloat() + random.nextFloat() + 0.5f;
Expand Down Expand Up @@ -247,8 +253,8 @@ private void reset(int maxWidth, int maxHeight) {
}
}

private Path getHexagonPath(float centerX, float centerY, float scale) {
final Path path = new Path();
private void createHexagonPathOrUpdate(float centerX, float centerY, float scale) {
path.reset();
final int radius = (int) (BASE_RADIUS * scale);
for (int i = 0; i < 6; i++) {
float x = (float) (centerX + radius * (Math.cos(2.0 * i * Math.PI / 6.0 + Math.PI)));
Expand All @@ -260,7 +266,6 @@ private Path getHexagonPath(float centerX, float centerY, float scale) {
}
}
path.close();
return path;
}

}
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Expand Up @@ -4,8 +4,9 @@ buildscript {
mavenCentral()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:2.3.0-rc1'
classpath 'com.google.gms:google-services:3.0.0'
classpath 'me.tatarka:gradle-retrolambda:3.5.0'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
Expand Down