2012年2月25日土曜日

入力ボックス(Androidアプリ)

Androidアプリ:入力ボックス
package:com.blogspot.tsukinihinikeni.InputBox
対象:Android2.2+ (FROYO以上)

"入力ボックス"はマッシュルームアプリです。
Simejiのマッシュルーム呼び出し機能、ATOKのATOKダイレクトなどから呼び出してご使用下さい。

・アプリの概要
"入力ボックス"をマッシュルームとして呼び出すと、文字入力用のボックスが現れます。

・どんな時に役に立つか
1.Gmailアプリの宛先入力時

現在、Galaxy NEXUSでは、標準のGmailアプリの宛先欄にうまく日本語が入力できません。
2012年4月8日追記:2012年4月4日のGmailアプリのアップデートで、この件の不具合は解消されたようです。


(ATOKの場合1)一文字ごとに確定してしまう (ATOKの場合2)文字が2つ入力されてしまう

入力一文字ごとに電話帳の検索が入るためのバグと思いますが、漢字変換等もできないので、日本人名の入力ができない状態です。


このアプリを使えば、漢字変換等を行った後の文字を宛先欄に入力することができます。

入力ボックスに文字を入力

宛先欄に反映



 2.入力文字が隠されてしまう時

パスワードの入力フォームなど、入力文字が***や・・・のようにマスクされてしまい、何を入力したか分からなくなる時があります。
また、入力フォームのフォントが小さすぎて読みづらい、入力フォームが1行分しか表示されなくて入力した文字が表示されきらない、といったこともあるかもしれません。

このアプリを使えば、入力文字を確認後にフォームに反映させることができます。


パスワードを入力

反映
スクリーンショットをとっておけば、パスワードとして何を設定したかの簡易なメモにもなります。


3.アクションボタン

ver.2.0(2012/8/5-)にて、アクションボタンの機能を追加しました。


ボリュームダウンキーを押すと、入力ボックスの下に4つのアイコンが表示されます。

ボタンの機能は、左から、
・入力した番号に電話をかけます
・入力したアドレス宛にメールを作成します
・入力した内容を共有します
・入力ボックスの表示位置を移動します

ボリュームアップキーを押すとアクションボタンの表示をやめます。
また、メニューキーによってもアクションボタンの表示/非表示を切り替えることができます。

この機能によって、(マッシュルームの本来の使い方からは外れますが、)ブラウジング中でもメール作成中でも、IMEさえ立ち上げられる(あるいはマッシュルームを起動できる)状態であれば、画面を見ながら電話番号やメールアドレスを入力することができます。


・その他
アプリのダウンロード(Google play)
https://play.google.com/store/apps/details?id=com.blogspot.tsukinihinikeni.InputBox

更新
(2012/8/5 ver.2.0)
アクションボタン機能を追加しました。


(2012/2/27 ver.1.1)
対象OSを「Android4.0+(Ice Cream Sandwich以上)」から「Android2.2以上(FROYO以上)」に広げました。

(2012/2/29 ver.1.2)
入力ボックスが複数行を表示するように修正しました。


連絡先
何かありましたら、この記事のコメント欄、あるいはメール( d.kondo.android@gmail.com )でお問い合わせください。


2012年2月9日木曜日

BouncingBalls

API Demosに入っているBouncingBalls (http://goo.gl/HuQSf)を自分でも書いてみた。

ただし、勉強のため

・背景色は静的なものとし、AnimatorUpdateListenerによってinvalidateの処理を行うこととした。(オリジナルのBouncingBallsは背景色を変化させることで自動的にinvalidateの処理をおこなっている)

・オリジナルにあるShapeHolderクラスを使用しない。つまり、メインのBouncingBallsクラスの中で全て完結させる。また、ObjectAnimatorは使用せず、全てValueAnimatorで処理する。

という制限をつけて行った。

上手いコードにはなっていないが、とりあえずできたので貼りつけておく。



import java.util.ArrayList;

import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator.AnimatorUpdateListener;

public class BouncingBallsActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((LinearLayout) findViewById(R.id.container)).addView(new MyView(this));
}

public class MyView extends View implements AnimatorUpdateListener {

int i = 0;
ArrayList<Integer> rgb = new ArrayList<Integer>();
ArrayList<AnimatorSet> animatorSet = new ArrayList<AnimatorSet>();

ArrayList<ValueAnimator> animLeft1 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animTop1 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animRight1 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animBottom1 = new ArrayList<ValueAnimator>();
ArrayList<AnimatorSet> animatorSet1 = new ArrayList<AnimatorSet>();

ArrayList<ValueAnimator> animLeft2 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animTop2 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animRight2 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animBottom2 = new ArrayList<ValueAnimator>();
ArrayList<AnimatorSet> animatorSet2 = new ArrayList<AnimatorSet>();

ArrayList<ValueAnimator> animLeft3 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animTop3 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animRight3 = new ArrayList<ValueAnimator>();
ArrayList<ValueAnimator> animBottom3 = new ArrayList<ValueAnimator>();
ArrayList<AnimatorSet> animatorSet3 = new ArrayList<AnimatorSet>();

public MyView(Context context) {
super(context);
}

@Override
public void onAnimationUpdate(ValueAnimator anim) {
invalidate();
}

@Override
public boolean onTouchEvent(MotionEvent event) {

if (event.getAction() != MotionEvent.ACTION_DOWN
&& event.getAction() != MotionEvent.ACTION_MOVE) {
return false;
}

float h = (float) getHeight();
float endY = h - 50f;
float X = event.getX();
float startY = event.getY();
int duration = (int) (500 * ((h - startY) / h));

// color
rgb.add(i, Color.rgb((int) (Math.random() * 255),
(int) (Math.random() * 255), (int) (Math.random() * 255)));

// down
animLeft1.add(i, ValueAnimator.ofFloat(X - 25f, X - 25f));
animLeft1.get(i).setDuration(duration);

animRight1.add(i, ValueAnimator.ofFloat(X + 25f, X + 25f));
animRight1.get(i).setDuration(duration);

animTop1.add(i, ValueAnimator.ofFloat(startY - 25f, endY - 25f));
animTop1.get(i).setDuration(duration);
animTop1.get(i).setInterpolator(new AccelerateInterpolator());
animTop1.get(i).addUpdateListener(this);

animBottom1.add(i, ValueAnimator.ofFloat(startY + 25f, endY + 25f));
animBottom1.get(i).setDuration(duration);
animBottom1.get(i).setInterpolator(new AccelerateInterpolator());

animatorSet1.add(i, new AnimatorSet());
animatorSet1.get(i).play(animTop1.get(i)).with(animBottom1.get(i));
animatorSet1.get(i).play(animTop1.get(i)).with(animLeft1.get(i));
animatorSet1.get(i).play(animTop1.get(i)).with(animRight1.get(i));

// skew
animLeft2.add(i, ValueAnimator.ofFloat(X - 25f, X - 25f * 2f));
animLeft2.get(i).setDuration(duration / 4);
animLeft2.get(i).setInterpolator(new DecelerateInterpolator());
animLeft2.get(i).setRepeatCount(1);
animLeft2.get(i).setRepeatMode(ValueAnimator.REVERSE);

animRight2.add(i, ValueAnimator.ofFloat(X + 25f, X + 25f * 2f));
animRight2.get(i).setDuration(duration / 4);
animRight2.get(i).setInterpolator(new DecelerateInterpolator());
animRight2.get(i).setRepeatCount(1);
animRight2.get(i).setRepeatMode(ValueAnimator.REVERSE);

animTop2.add(i, ValueAnimator.ofFloat(endY - 25f, endY - 25f * 0f));
animTop2.get(i).setDuration(duration / 4);
animTop2.get(i).setInterpolator(new DecelerateInterpolator());
animTop2.get(i).setRepeatCount(1);
animTop2.get(i).setRepeatMode(ValueAnimator.REVERSE);
animTop2.get(i).addUpdateListener(this);

animBottom2.add(i, ValueAnimator.ofFloat(endY + 25f, endY + 25f));
animBottom2.get(i).setDuration(duration / 4);
animBottom2.get(i).setRepeatCount(1);
animBottom2.get(i).setRepeatMode(ValueAnimator.REVERSE);

animatorSet2.add(i, new AnimatorSet());
animatorSet2.get(i).play(animTop2.get(i)).with(animBottom2.get(i));
animatorSet2.get(i).play(animTop2.get(i)).with(animLeft2.get(i));
animatorSet2.get(i).play(animTop2.get(i)).with(animRight2.get(i));

// up
animLeft3.add(i, ValueAnimator.ofFloat(X - 25f, X - 25f));
animLeft3.get(i).setDuration(duration);

animRight3.add(i, ValueAnimator.ofFloat(X + 25f, X + 25f));
animRight3.get(i).setDuration(duration);

animTop3.add(i, ValueAnimator.ofFloat(endY - 25f, startY - 25f));
animTop3.get(i).setDuration(duration);
animTop3.get(i).setInterpolator(new DecelerateInterpolator());
animTop3.get(i).addUpdateListener(this);

animBottom3.add(i, ValueAnimator.ofFloat(endY + 25f, startY + 25f));
animBottom3.get(i).setDuration(duration);
animBottom3.get(i).setInterpolator(new DecelerateInterpolator());

animatorSet3.add(i, new AnimatorSet());
animatorSet3.get(i).play(animTop3.get(i)).with(animBottom3.get(i));
animatorSet3.get(i).play(animTop3.get(i)).with(animLeft3.get(i));
animatorSet3.get(i).play(animTop3.get(i)).with(animRight3.get(i));

animatorSet.add(i, new AnimatorSet());
animatorSet.get(i).play(animatorSet2.get(i))
.after(animatorSet1.get(i));
animatorSet.get(i).play(animatorSet3.get(i))
.after(animatorSet2.get(i));
animatorSet.get(i).start();

i++;

return true;
}

@Override
public void onDraw(Canvas canvas) {
if (i > 0) {
for (int j = 0; j < animatorSet.size(); ++j) {
if (animatorSet1.get(j) != null
&& animatorSet1.get(j).isRunning()) {
Paint paint = new Paint();
paint.setColor(rgb.get(j));
paint.setStyle(Paint.Style.FILL);
RectF oval = new RectF((Float) animLeft1.get(j)
.getAnimatedValue(), (Float) animTop1.get(j)
.getAnimatedValue(), (Float) animRight1.get(j)
.getAnimatedValue(), (Float) animBottom1.get(j)
.getAnimatedValue());
canvas.drawOval(oval, paint);
} else if (animatorSet2.get(j) != null
&& animatorSet2.get(j).isRunning()) {
Paint paint = new Paint();
paint.setColor(rgb.get(j));
paint.setStyle(Paint.Style.FILL);
RectF oval = new RectF((Float) animLeft2.get(j)
.getAnimatedValue(), (Float) animTop2.get(j)
.getAnimatedValue(), (Float) animRight2.get(j)
.getAnimatedValue(), (Float) animBottom2.get(j)
.getAnimatedValue());
canvas.drawOval(oval, paint);
} else if (animatorSet3.get(j) != null
&& animatorSet3.get(j).isRunning()) {
Paint paint = new Paint();
paint.setColor(rgb.get(j));
paint.setStyle(Paint.Style.FILL);
RectF oval = new RectF((Float) animLeft3.get(j)
.getAnimatedValue(), (Float) animTop3.get(j)
.getAnimatedValue(), (Float) animRight3.get(j)
.getAnimatedValue(), (Float) animBottom3.get(j)
.getAnimatedValue());
canvas.drawOval(oval, paint);
}
}
}
}
}
}



なお、R.layout.mainは以下のようになっている。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/container"
    android:background="#ffffffff">
</LinearLayout>

android:id="@+id/container"を定義している以外は、特に何もしていない。

2012年2月6日月曜日

(Animator)図形を動かす

図形を動かすには、OS3.0から導入された"Property Animation"という方法と、以前からある"View Animation"という方法がある。
Property Animationの方が汎用性が高く推奨される方法とのこと。
(http://developer.android.com/intl/ja/guide/topics/graphics/animation.html)

現在の端末は2.xが主流であるため、あまり使用されていないようだが、この記事の内容はProperty Animationについてである。


Property Animationでは、動きの描写は、ValueAnimatorオブジェクト、および、このオブジェクトに関するリスナーを使って記述される。

ValueAnimatorオブジェクト(android.animation.ValueAnimator)は、単にタイマー的な計算機であり、
「値Aから値BまでをT秒かけて変化していく」
という動作を基本とする。
この値の変化について、一定のスピードで変化するのか、あるいは後半加速するのかといった補間の仕方(InterPoletor)や、この動作を一度でやめるのか繰り返すのかなどといった、様々な設定を施すことができる。

ValueAnimatorの値の変化はAnimatorUpdateListener(android.animation.ValueAnimator.AnimatorUpdateListener)で感知することができる。
このクラスのonAnimationUpdateメソッドをオーバーライドしてinvalidateメソッドを実行することで、ValueAnimatorに連動して、適時onDrawを呼び出すことができる。

最後に、図形を描画するCanvas側では、図形の位置や大きさなどのプロパティをValueAnimatorの計算値から取得するようにする。

以下のコードは、円が画面上部から下方へ並行移動する。

import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import android.animation.ValueAnimator.AnimatorUpdateListener;

public class CanvasSampleActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((LinearLayout) findViewById(R.id.container)).addView(new MyView(this));
}

public class MyView extends View implements AnimatorUpdateListener {

ValueAnimator anim = null;

public MyView(Context context) {
super(context);
anim = ValueAnimator.ofFloat(100f, 500f);
anim.setDuration(3000);
anim.addUpdateListener(this);
anim.start();
}

@Override
public void onAnimationUpdate(ValueAnimator anim) {
invalidate();
}

@Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(0xff0000ff);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200f, (Float) anim.getAnimatedValue(), 50f, paint);
}

}

}


なお、R.layout.mainは以下のようになっている。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/container"
    android:background="#ffffffff">
</LinearLayout>

android:id="@+id/container"を定義している以外は、特に何もしていない。

2012年2月5日日曜日

(Canvas)三角形を描画する

Canvasの基本的な使い方については(Canvas)円周を描画するを参照。

多角形を描画するには、Pathオブジェクトを使用する。
Paintオブジェクトに書式の情報、Pathオブジェクトに経路の情報をセットして、
canvas.drawPath(path,paint)
によって多角形を描画できる。

単純に正三角形を描画するコードは以下。

Paint paint = new Paint();
paint.setColor(0xff0000ff);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(150f, 50f);
path.lineTo(150f-58f,150f);
path.lineTo(150f+58f, 150f);
path.close();
canvas.drawPath(path,paint);


上ではPaint.StyleのプロパティをSTROKEにしているが、これをFILLにした場合、囲まれた領域が塗りつぶされる。

(図の①) STROKE、閉曲線(上記の正三角形)

(図の②) STROKE、非閉曲線
Paint paint = new Paint();
paint.setColor(0xff0000ff);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
path.moveTo(150f, 50f);
path.lineTo(150f-58f,150f);
path.lineTo(150f+58f, 150f);
canvas.drawPath(path,paint);

(図の③) FILL、閉曲線
Paint paint = new Paint();
paint.setColor(0xff0000ff);
paint.setStyle(Paint.Style.FILL);
Path path = new Path();
path.moveTo(150f, 50f);
path.lineTo(150f-58f,150f);
path.lineTo(150f+58f, 150f);
path.close();
canvas.drawPath(path,paint);

(図の④) FILL、非閉曲線
Paint paint = new Paint();
paint.setColor(0xff0000ff);
paint.setStyle(Paint.Style.FILL);
Path path = new Path();
path.moveTo(150f, 50f);
path.lineTo(150f-58f,150f);
path.lineTo(150f+58f, 150f);
canvas.drawPath(path,paint);



FILLで塗りつぶす場合は、Pathが必ずしも閉じている必要は無いようだ。

ちなみに、
Paint paint = new Paint();
paint.setColor(0xff0000ff);
Path path = new Path();
path.moveTo(150f, 50f);
path.lineTo(150f-58f,150f);
canvas.drawPath(path,paint);
のように経路が単に線分の場合、Paint.StyleをSTROKEにしていると線分が描画されるが、FILLにしていると何も描画されない。

2012年2月4日土曜日

(Canvas)円周を描画する

android.graphics.* android.animation.* のあたりにも手を出してみたい、とようやく考え始め。
まずはシンプルに円を描くコードを作成。

下記のコードは、青色の線で円を描きます。


import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;

public class CanvasSampleActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
((LinearLayout) findViewById(R.id.container)).addView(new MyView(this));
}

public class MyView extends View {

public MyView(Context context) {
super(context);
}

@Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(0xff0000ff);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200f, 200f, 100f, paint);
}

}

}

なお、R.layout.mainは以下のようになっています。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/container"
    android:background="#ffffffff">
</LinearLayout>

android:id="@+id/container"を定義している以外は、特に何もしていません。


基本的なポイントとしては、
・Viewを継承したクラスを作成し、その中でonDrawメソッドをオーバーライドして描画プログラムを書く。
・onDrawメソッドは最初の描画時や、invalidate()が呼び出された時などに自動的に呼び出される。
・Paintオブジェクトには色やエフェクト等書式的な情報がセットできる。
・Canvasオブジェクトが「丸を書く」「四角を書く」「線を書く」などの描画メソッドを持っている。描画メソッドは基本的にPaintオブジェクトを引数に取るようになっている。

また、Paint.StyleプロパティはデフォルトではFILLになっており、円や四角を描いた場合、中身はすべて塗りつぶされます。STROKEに変更すると周のみを描くことができます。