Android でラインアートを表示するアプリを書いてみた。

Android でラインアート

Android端末にインストール可能なapkファイル(たぶんAndroid4.0.3以降じゃないとインストールできない): Lineart.apk

開発環境は MacBook Air + Mac OS X Lion + Eclipse 4.2 Juno.

3つのクラスを作成。

  • MainActivity: アプリのエントリポイント的なアクティビティ クラス。
  • LineartView クラス: ラインアートの表示をするビュークラス。
  • Lineart クラス: ラインアートの動きを制御するクラス。

MainActivity クラス

アプリのエントリポイント的なアクティビティ クラス。


package info.nilab.lineart;
 
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
 
public class MainActivity extends Activity {
   
  private LineartView lineartView;
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // layout
    LinearLayout layout = new LinearLayout(this);
    layout.setOrientation(LinearLayout.VERTICAL);
    setContentView(layout);
    // speed up button
    Button speedUpButton = new Button(this);
    speedUpButton.setText("speed up↑");
    speedUpButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        MainActivity.this.lineartView.upSpeed();
      }
    });
    layout.addView(speedUpButton);
    // speed down button
    Button speedDownButton = new Button(this);
    speedDownButton.setText("speed down↓");
    speedDownButton.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        MainActivity.this.lineartView.downSpeed();
      }
    });
    layout.addView(speedDownButton);
    // lineart view
    lineartView = new LineartView(this);
    layout.addView(lineartView);
    //Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();
  }
  
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    //Toast.makeText(this, "onCreateOptionsMenu", Toast.LENGTH_SHORT).show();
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
  }
 
  @Override
  protected void onStart() {
    super.onStart();
    //Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show();
  }
 
  @Override
  protected void onRestart() {
    super.onRestart();
    //Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show();
  }
 
  @Override
  protected void onResume() {
    super.onResume();
    //Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();
  } 
 
  @Override
  protected void onPause() {
    super.onPause();
    //Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show();
  }
 
  @Override
  protected void onStop() {
    super.onStop();
    //Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show();
  }
 
  @Override
  protected void onDestroy() {
    super.onDestroy();
    //Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
  }
}

LineartView クラス

ラインアートの表示をするビュークラス。


package info.nilab.lineart;
 
import java.lang.ref.WeakReference;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
 
public class LineartView extends View {
   
  private boolean initialized = false;
  private Lineart lineart;
  private IntervalDrawHandler handler;
 
  public LineartView(Context context) {
    super(context);
  }
   
  private void init(){
    lineart = new Lineart(5, getWidth(), getHeight());
    handler = new IntervalDrawHandler(this);
    handler.sendEmptyMessageDelayed(0, handler.getDelayMillis());
  }
  
  private static class IntervalDrawHandler extends Handler{
    private final WeakReference<View> viewReference;
    private long delayMillis = 100L;
    IntervalDrawHandler(View view){
      viewReference = new WeakReference<View>(view);
    }
    long getDelayMillis(){
      return delayMillis;
    }
    void setDelayMillis(long delayMillis){
      this.delayMillis = delayMillis;
    }
    @Override
    public void handleMessage(Message msg) {
      View view = viewReference.get();
      if(view != null){
        view.invalidate();
        sendEmptyMessageDelayed(0, delayMillis);
      }
    }
  }
  
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if(!initialized){
      initialized = true;
      init();
    }
    lineart.next();
    // 背景を塗りつぶす
    canvas.drawColor(Color.WHITE);
    // ラインアートを描画する
    lineart.draw(canvas);
  }
  
  private Float lastTouchX = null;
  private Float lastTouchY = null;
  private long lastChangeTime = System.currentTimeMillis();
  
  @Override
  public boolean onTouchEvent(MotionEvent event) {
    // ラインアートの移動速度を変える
    switch(event.getAction()){
    case MotionEvent.ACTION_UP:
      lastTouchX = null;
      lastTouchY = null;
      break;
    case MotionEvent.ACTION_MOVE:
      float x = event.getX();
      float y = event.getY();
      // 移動速度を変えてから0.5秒以内なら、速度を変えない
      if(System.currentTimeMillis() - lastChangeTime > 500){
        if(lastTouchY != null && x < lastTouchY){
          // 上方向に指を動かしたら加速
          upSpeed();
          lastChangeTime = System.currentTimeMillis();
        }else if(lastTouchY != null && x > lastTouchY){
          // 下方向に指を動かしたら減速
          downSpeed();
          lastChangeTime = System.currentTimeMillis();
        }
      }
      lastTouchX = x;
      lastTouchY = y;
      break;
    }
    return true;
  }
  
  public void upSpeed(){
    long delayMillis = (long)(handler.getDelayMillis() / 1.5);
    if(delayMillis < 10){
      delayMillis = 10;
    }
    handler.setDelayMillis(delayMillis);
  }
  
  public void downSpeed(){
    long delayMillis = (long)(handler.getDelayMillis() * 1.5);
    if(delayMillis > 10000){
      delayMillis = 10000;
    }
    handler.setDelayMillis(delayMillis);
  }
}

Lineart クラス

ラインアートの動きを制御するクラス。


package info.nilab.lineart;
 
import java.util.*;
import android.graphics.*;
 
public class Lineart {
 
  private final ArrayList<History<MovingPoint>> point_histories = new ArrayList<History<MovingPoint>>();
  private final History<ChangingColor> color_history;
  
  private static class ChangingColor{
    private static final Random r = new Random();
    private final int value;
    ChangingColor(int color){
      this.value = color;
    }
    int value(){
      return value;
    }
    ChangingColor next(){
      return new ChangingColor(Color.argb(r.nextInt(256), r.nextInt(256), r.nextInt(256), r.nextInt(256)));
    }
  }
  
  private static class History<T> {
    private final LinkedList<T> list;
    private final int max;
    History(int max){
      this.max = max;
      this.list = new LinkedList<T>();
    }
    void add(T t){
      list.add(t);
      if(list.size() > max){
        list.remove(0); // 先頭を削除
      }
    }
    T get(int location){
      return list.get(location);
    }
    T last(){
      return list.get(list.size()-1);
    }
    int size(){
      return list.size();
    }
  }
  
  private static class MovingPoint{
    
    private static Random random = new Random();
    
    private float x;
    private float y;
    private float a;
    private float b;
    private float width;
    private float height;
    
    MovingPoint(float x, float y, float a, float b, float width, float height){
      this.x = x;
      this.y = y;
      this.a = a;
      this.b = b;
      this.width = width;
      this.height = height;
    }
    
    void random(){
      this.x = random.nextFloat() * width;
      this.y = random.nextFloat() * height;
      this.a = random.nextFloat() < 0.5 ? -1 : 1;
      this.b = random.nextFloat() < 0.5 ? -1 : 1;
    }
    
    MovingPoint next(float dist){
      float x = this.x + this.a * dist;
      float y = this.y + this.b * dist;
      float a = this.a;
      float b = this.b;
      if(x < 0){
        x = 0;
        a = 1;
      }
      if(x > this.width){
        x = this.width;
        a = -1;
      }
      if(y < 0){
        y = 0;
        b = 1;
      }
      if(y > this.height){
        y = this.height;
        b = -1;
      }
      return new MovingPoint(x, y, a, b, this.width, this.height);
    }
  }
  
  public Lineart(int historySize, float canvasWidth, float canvasHeight){
    // points
    for(int i=0; i<historySize; i++){
      History<MovingPoint> h = new History<MovingPoint>(historySize);
      MovingPoint mp = new MovingPoint(0, 0, 0, 0, canvasWidth, canvasHeight);
      mp.random();
      h.add(mp);
      point_histories.add(h);
    }
    // color
    color_history = new History<ChangingColor>(historySize);
    color_history.add(new ChangingColor(Color.argb(255, 255, 0, 0)));
  }
    
  public void next(){

    // next points
    float dist = 10;
    for(int i=0; i<point_histories.size(); i++){
      MovingPoint nextp = point_histories.get(i).last().next(dist);
      point_histories.get(i).add(nextp);
    }
    
    // next color
    color_history.add(color_history.last().next());
  }
  
  public void draw(Canvas canvas){
    // drawing lines
    for(int i=0; i<point_histories.size()-1; i++){
      History<MovingPoint> h1 = point_histories.get(i);
      History<MovingPoint> h2 = point_histories.get(i+1);
      int size = color_history.size();
      for(int j=0; j<size; j++){
        MovingPoint p1 = h1.get(j);
        MovingPoint p2 = h2.get(j);
        Paint paint = new Paint();
        paint.setColor(color_history.get(j).value());
        paint.setStrokeWidth(4.0f);
        paint.setAntiAlias(true);
        canvas.drawLine(p1.x, p1.y, p2.x, p2.y, paint);
      }
    }
  }
}

Ref.

tags: android

Posted by NI-Lab. (@nilab)