Android アプリ開発 「MATRIX」

Androidアプリの開発に役立つサンプル集



音声を認識して結果をテキスト表示する方法(音声認識アプリ)

簡単な音声認識アプリを作る

今回は、グーグルが提供している音声認識システムを使用した簡単な音声認識アプリを作ってみたいと思います。 詳細についてはコード内にコメントで記載してありますので、そちらをご覧ください

サンプルコード①(MainActivity.java) 

ポイントは、音声認識が完了した後の処理をする「onActivityResult」をオーバーライドするところです。これがないと音声認識の結果を受け取ることができませんので、必ず追加してください。 

import android.content.Intent;
import android.speech.RecognizerIntent;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

private TextView textView, textView2;
private Button button;
//任意の識別番号
private static final int REQUEST_CODE = 12345;

//アプリが開始されると最初に処理されるメソッド
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//このアプリで使用するレイアウトファイルをセット
setContentView(R.layout.activity_main);

//画面の上にあるアクションバーを隠す(隠さなくても良い)
ActionBar actionBar = getSupportActionBar();
actionBar.hide();

//レイアウトにある2つのテキストビューを取得
textView = (TextView)findViewById(R.id.textView);
textView2 = (TextView)findViewById(R.id.textView2);

//レイアウトにあるボタンを取得
button = (Button)findViewById(R.id.button);

//上で取得したボタンにクリックイベントを実装
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

//音声認識用のインテントを作成
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
//認識する言語を指定(この場合は日本語)
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPANESE.toString());
//認識する候補数の指定
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 10);
//音声認識時に表示する案内を設定
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "話してください");
//音声認識を開始
startActivityForResult(intent, REQUEST_CODE);
}
});
}

//音声認識が終わると自動で呼び出されるメソッド
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if(requestCode == REQUEST_CODE && resultCode == RESULT_OK) {

//data から音声認識の結果を取り出す(リスト形式で)
ArrayList<String> kekka = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);

//認識結果が一つ以上ある場合はテキストビューに結果を表示する
if (kekka.size() > 0) {
//一番最初にある認識結果を表示する
textView2.setText(kekka.get(0));
} else {
//何らかの原因で音声認識に失敗した場合はエラーメッセージを表示
textView2.setText("音声の認識に失敗しました…");
}
}
}
}

 

サンプルコード②(activity_main.xml) 

テキストビュー2つとボタンが1つあるだけのレイアウトで、特に注意する点はありません。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
tools:context="test.MainActivity">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="認識結果"
android:textSize="18sp" />

<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@+id/textView"
android:text="結果"
android:textSize="18sp" />

<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="音声認識スタート" />
</RelativeLayout

実行結果 

① 起動直後の画面です。

f:id:vw-dsg:20180327114021p:plain

② 「音声認識スタート」ボタンを押すと音声認識が開始されます。音声認識の小画面にはプログラムコード内で設定した「話してください」という案内が表示されます。

f:id:vw-dsg:20180327114111p:plain

③ 適当な言葉を話しかけると「認識結果」のすぐ下に話しかけた内容が表示されます。

f:id:vw-dsg:20180327114414p:plain

まとめ

音声認識機能はポイントさえ押さえてしまえば意外と簡単に実装できますので、これから音声認識機能を使ったアプリを作ろうと考えている方は、ぜひ今回のサンプルを参考にしてください。 

END

最初からバナー広告の表示スペースを確保した新規プロジェクトを作る方法

広告の後付けでひと苦労してしまう・・・

アプリに表示するバナー広告は、過去の記事にあるようにアプリを作ってからでも「後付け」することができますが、広告用のアドオンを追加したり、マニフェストファイルを変更したり、コードを追加する必要があるため少し面倒です。

また、アプリによっては、すんなりとバナー広告を表示するスペースを確保することができず、デザインの見直しで不要な苦労してしまう場合があります。

完成間近になってからのひと仕事ほどつらいものはありません。

後から広告で苦労しないために

この苦労を回避するのは簡単で、プロジェクトを新規作成する際に、バナー広告を含んだアクティビティ「Google AdMob Ads Activity」を選択し、あらかじめバナー広告のスペースが確保されたレイアウトを自動生成して、それからアプリの中身を作り始めると良いでしょう。

f:id:vw-dsg:20180302094552p:plain
新規プロジェクトを作成するときに広告を含んだアクティビティを選択します。

f:id:vw-dsg:20180302094610p:plain
設定項目をスクロールさせると広告の種類を選択するドロップダウンメニュー「Ad Format」が現れるので、そこで「Banner」を選択します。

f:id:vw-dsg:20180302095308p:plain
新規作成したプロジェクトのレイアウトファイルには、バナー広告のスペースが確保されています。

f:id:vw-dsg:20180302095752p:plain
プロジェクトを実行すると、無事画面の下にインターネット経由で AdMob から取得されたサンプルのバナー広告が表示されました。

まとめ

最初に読んだ Android アプリの参考書でバナー広告を後付けする方法を解説していたこともあって、自分も以前はアプリを作ってからコードを書いてバナー広告を後付けするという方法をとっていましたが、Android Studio で自動生成できることが分かってからはこの方法を使って楽をしています。

最後になってしまいましたが、バナー広告をサンプルから正式なものへ変更する場合は「res/values/strings.xml」の「banner_ad_unit_id」の値(ca-app-pub-3940256099942544/6300978111の部分)を正式に取得した広告IDに変更してください。

 

END

 

Android Developers サイトでキーワードの「検索」ができなくなってしまった時の対処方法

突然「検索機能」が使えなくなる?

アンドロイドの開発者向けサイト(Android Developers)にはキーワードからリファレンスマニュアルを検索する便利な機能がありますが、何かの原因でまったく反応しなくなり検索ができなくなってしまう場合があります。

リファレンスマニュアルは膨大な量ですので、この検索機能が使えなくなってしまうと大きな時間のロスを生み出してしまうでしょう。

※このトラブルはグーグルの「chrome」とマイクロソフトの「Edge」の両方で確認できました。

f:id:vw-dsg:20180226153453j:plain
この検索窓にキーワードを入力しても全く反応しなくなる

対処方法

検索ができなくなってしまった時は、ブラウザの設定から「閲覧履歴」や「Cookieと他のサイトデータ」や「キャッシュされた画像とファイル」などを消去してみてください。

トラブルの原因がここにあれば(というか、通常はここしかない)、検索機能が無事に復活するはずです。

f:id:vw-dsg:20180226155458j:plain
ロームの例。右端のメニューから設定を選択し、設定画面の一番したの「詳細設定」を開きます。その中に「閲覧履歴データを消去する」という項目があるので、それをクリック。

f:id:vw-dsg:20180226155540j:plain
消去するデータの選択画面で、「基本」項目の3つのデータを「全期間」消去します。

f:id:vw-dsg:20180226155603j:plain
その後、アンドロイド開発者サイト(Android Developers)のタブだけ更新すれば、今度は検索窓のキーワード入力に反応するようになります。

f:id:vw-dsg:20180226155620j:plain
これで無事に「検索機能」が復活しました。

このトラブルの原因は?

chrome」と「Edge」どちらのブラウザもキャッシュの消去によって復旧していますので、おそらく、サイトを制御している既存スクリプトの中身が部分的に変更されたことが原因になっていると思いますが、もしかしたら違うかもしれません。

END

 

「通知」をタップした時に任意の処理を実行する方法

通知をタップして処理を実行

今回は「通知」をタップした時に画面(Activity)を表示するのではなく、「任意の処理を実行する」というサンプルを作ってみたいと思います。基本的には以下のようなフローになっています。

<流れ>

① テキストボタンを持った「通知」を作成して表示する
        ↓
② 通知のテキストをタップした時に Intent を発信する
        ↓
③ 待機していたサービスが Intent を受け取りブロードキャストレシーバーに送信
        ↓
④ ブロードキャストレシーバーがフィルターを判別して任意の処理を実行 

 

サンプルコード①(Main.activity)

ここには通知を発行する「onCreate」だけでなく、通知から Intent を受け取りブロードキャストレシーバーに処理を依頼する「サービス」と、サービスからの依頼を受け取り処理を実行する「ブロードキャストレシーバー」など、必要なものがすべて含まれています。※詳細はコード内のコメントを参考にしてください。 

public class MainActivity extends AppCompatActivity {

private NotificationCompat.Builder builder;
private NotificationManager manager;
private static TextView textView;

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////メイン///////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//レイアウトXMLをセット
setContentView(R.layout.activity_main);

//表示用のテキストビューを操作するための準備
textView = (TextView)findViewById(R.id.text);

//通知の準備
builder = new NotificationCompat.Builder(getBaseContext(), "c1"); //通知ビルダーのインスタンスを生成
builder.setSmallIcon(R.drawable.bar_icon); //バーに表示するアイコンの指定
builder.setContentTitle("Sample Title"); //通知タイトル
builder.setContentText("Sample Text"); //通知する内容
builder.setWhen(System.currentTimeMillis()); //通知時間
builder.setAutoCancel(true); //オートキャンセル(これだけでは動作しない)

//通知にタップで反応するテキストを追加
Intent test_intent = new Intent(); //空のインテントを準備
test_intent.setAction("test_action"); //ブロードキャストが受信した時に識別する「タグ」のようなもの
test_intent.putExtra("sample_name", "sample value"); //ブロードキャストするデータをセット
PendingIntent test_pendingIntent = PendingIntent.getBroadcast(getBaseContext(), 0, test_intent, PendingIntent.FLAG_UPDATE_CURRENT); //インテントペンディングインテントに組み込む
builder.addAction(R.drawable.bar_icon, "ここを押すとブロードキャストする", test_pendingIntent); //上で作ったペンディングインテントActionとして追加する

manager = (NotificationManager)getBaseContext().getSystemService(Service.NOTIFICATION_SERVICE); //通知マネージャーを準備
manager.notify(1, builder.build()); //通知開始

Intent intent = new Intent(getBaseContext(), MyService.class); //サービス開始用のインテントを生成
startService(intent); //サービスを開始して待機
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////サンプル処理/////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

public static void sample() {
textView.setText("完了しました!"); //テキストを変更する
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////サービス////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

public static class MyService extends Service {

//自動作成される
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

//最初に実行される
@Override
public int onStartCommand(Intent intent, int flags, int startId) {

IntentFilter filter = new IntentFilter(); //空のインテントフィルターを準備
filter.addAction("test_action"); //受信するActionを指定("test_action"というアクションを受信してブロードキャストレシーバーに送る)
registerReceiver(MyReceiver, filter); //レシーバーとフィルターを登録してサービス開始

return super.onStartCommand(intent, flags, startId); //自動生成のまま
}
}

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////ブロードキャストレシーバー////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

public static BroadcastReceiver MyReceiver = new BroadcastReceiver() {

//サービスから処理のオーダーを受け取る
@Override
public void onReceive(Context context, Intent intent) {

if(intent.getAction().equals("test_action")) { //"test_action"というインテントフィルターが付いた依頼を処理

//ここは別スレッドなのでHandlerを利用してUIスレッドにアクセスしなければならない
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
sample(); //任意の処理(この場合はサンプルメソッドを実行
}
});
}
}
};
}

 

サンプルコード②(AndroidManifest.xml) 

マニフェストファイルには「サービス」と「ブロードキャストレシーバー」の情報を <application></application> タグ内に追加する必要があります。これを書かないと正しく動かないので注意してください。※赤文字の2行。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="htt p://schemas.android.com/apk/res/android"
package="sample.test.co.jp">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- 追記した部分 -->
<service android:name=".MainActivity$MyService" />
<receiver android:name=".MyReceiver" />

</application>

</manifest>

 

サンプルコード③(activity_main.xml)※抜粋

レイアウトファイルにはテキストを表示する「TextView」があるだけです。通知のテキストをタップした時に、この TextView のテキストを「Hello World!」から「完了しました!」に変更します。 

<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

 

サンプルの実行結果

① 起動すると画面の中央に「Hello World!」が表示され、すでに通知が届いている状態です。

f:id:vw-dsg:20180124111937j:plain

 

② 通知を下に引き出して確認すると、下側にタップ可能のテキスト(「ここを押すとブロードキャストする」)が表示されます。 

f:id:vw-dsg:20180124112152j:plain

 

③ 通知を収納すると画面中央のテキストが「完了しました!」に変更されています。

f:id:vw-dsg:20180124112207j:plain 

まとめ

ネット上には、通知をタップした時に「画面(Activity)を起動して表示する」という処理をもったサンプルは数多く存在しますが、「任意の処理を実行する」といったものは意外に少ないので、もし似たような処理を実現しようといろいろ模索している場合は、ぜひ今回のサンプルを参考にしてください。

END

Androido 端末で使用可能なセンサーの一覧(2017年12月26日現在)

Android 端末のセンサー一覧(2017年12月26日現在)

Sensor | Android Developers より抜粋した Android 端末で使用可能なセンサー(定数)の一覧です。各センサー下の説明は「Google 翻訳」で翻訳した後、若干の修正を加えたものとなっています。

全部で29種類のセンサーがありますが、この中のどのセンサーが実際に使用できるのかというのは、機種によってそれぞれ違いますのでアプリを作る前に、一度調べておく必要があるでしょう。

1.TYPE_ACCELEROMETER
加速度センサー

2.TYPE_ACCELEROMETER_UNCALIBRATED
較正されていない加速度センサー

3.TYPE_ALL
すべてのセンサー

4.TYPE_AMBIENT_TEMPERATURE
周囲温度センサー

5.TYPE_DEVICE_PRIVATE_BASE
ベンダーによって定義されたセンサー

6.TYPE_GAME_ROTATION_VECTOR
較正されていない回転ベクトルセンサー

7.TYPE_GEOMAGNETIC_ROTATION_VECTOR
地磁気の回転ベクトル

8.TYPE_GRAVITY
重力センサー

9.TYPE_GYROSCOPE
ジャイロセンサー

10.TYPE_GYROSCOPE_UNCALIBRATED
較正されていないジャイロセンサー

11.TYPE_HEART_BEAT
動き検出センサー

12.TYPE_HEART_RATE
心拍数センサー

13.TYPE_LIGHT
光センサー

14.TYPE_LINEAR_ACCELERATION
線形加速度センサー

15.TYPE_LOW_LATENCY_OFFBODY_DETECT
低遅延のオフボディ検出センサー

16.TYPE_MAGNETIC_FIELD
磁界センサー

17.TYPE_MAGNETIC_FIELD_UNCALIBRATED
較正されていない磁界センサー

18.TYPE_MOTION_DETECT
動き検出センサー

19.TYPE_ORIENTATION
方位センサー。この定数は、APIレベル8で廃止されました。代わりにSensorManager.getOrientation()を使用してください。

20.TYPE_POSE_6DOF
姿勢センサー

21.TYPE_PRESSURE
圧力センサー

22.TYPE_PROXIMITY
近接センサー

23.TYPE_RELATIVE_HUMIDITY
相対湿度センサー

24.TYPE_ROTATION_VECTOR
回転ベクトルセンサー

25.TYPE_SIGNIFICANT_MOTION
モーショントリガーセンサー

26.TYPE_STATIONARY_DETECT
固定検出センサー

27.TYPE_STEP_COUNTER
ステップカウンターセンサー

28.TYPE_STEP_DETECTOR
ステップ検出器センサーを記述する定数。

29.TYPE_TEMPERATURE
温度センサー。この定数はAPIレベル14では廃止されました。代わりにSensor.TYPE_AMBIENT_TEMPERATUREを使用してください。

備考

センサーからの値の取得については、「SensorEvent(SensorEvent | Android Developers)」を参考にしてください。

END

レイアウトXML に配置した SurfaceView 内でカウンターを動かしてみよう

今回は SurfaceView のサンプル

今回はレイアウトXML の一部に SurfaceView を配置し、その SurfaceView の中でカンターを動かしてみたいと思います。MainActivity と SurfaceView の関係が若干わかりにくいかもしれませんが、 一度覚えてしまえば楽に実装できるようになると思います。

サンプルコード①(activity_main.xml

使用するレイアウトは、Android Stuido が自動で生成したレイアウトXMLに SurfaceView を追加しただけのシンプルなものです。高さと幅は自由に確保してください。

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="sample.mysurfacetest.MainActivity">

<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="textView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

※デザインビューで見るとこんな感じになります。

f:id:vw-dsg:20171222093106p:plain

サンプルコード②(MainActivity.class)

レイアウト(activity_main)は普通に読み込みます。その後、レイアウトXML に配置した SurfaceView を取得して、 それを context と一緒に MySurfaceView.class に渡します。

public class MainActivity extends AppCompatActivity {

//オブジェクト・変数を用意
private TextView textView;
private SurfaceView mSurfaceView;
private MySurfaceView mSfV;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//レイアウトxmlをセット
setContentView(R.layout.activity_main);

//レイアウトXMLから textView を取得する
textView = (TextView)findViewById(R.id.textView);
//文字色をグリーンに指定
textView.setTextColor(Color.BLUE);
//文字サイズを50に指定
textView.setTextSize(30);
//文字をセットして表示
textView.setText("SurfaceView カウンター");

//レイアウトXMLから SurfaceView を取得
mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView);
//MySurfaceView.java context mSurfaceView を送って実体化する
mSfV = new MySurfaceView(this, mSurfaceView);
}
}

サンプルコード③(MySurfaceView.class)

SurfaceView を継承し、SurfaceHolder のコールバックと Runnable のインターフェースを実装します。重要ポイイントは、コンストラクターが受け取る引数に「SurfaceHolder sv」を追加するところです。これを追加しないと MainActivity から送られてくる SurfaceView を受け取ることができず、レイアウトXMLに配置した SurfaceView にアクセスすることができません。

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

//オブジェクト・変数を用意
private SurfaceHolder holder1;
private Thread thread;
private Canvas canvas;
private Paint paint;
private int count;

//コンストラクターで初期化(ここが最初に実行される)
public MySurfaceView(Context context, SurfaceView sv) {
super(context);

//サーフェースフォルダーを取得
holder1 = sv.getHolder();
//取得したサーフェースホルダーにコールバック機能を追加
holder1.addCallback(this);
//別スレッドを用意
thread = new Thread(this);
//count という名前の変数に 0 をセット
count = 0;
}

@Override
public void surfaceCreated(SurfaceHolder holder2) {
//SurfaceView が作られると同時にスレッドをスタート
thread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder3, int format, int width, int height) {
//SurfaceView が変更されると実行されるメソッド
//今回はここを使用しませんがこれが無いとエラーになるためとりあえす存在している
}

@Override
public void surfaceDestroyed(SurfaceHolder holder4) {
//SurfaceView が破棄される時にスレッドを停止
thread = null;
}

@Override
public void run() { //run() はスレッドが開始されると実行される

//SurfaceView に表示する文字の各詳細を paint で設定している
paint = new Paint();
paint.setTextSize(80);
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);

//スレッドが存在する限りループする
while (thread != null) {

//ダブルバッファリングを準備
canvas = holder1.lockCanvas();
//用意しておいた canvas に文字色と表示文字を設定
canvas.drawColor(Color.RED);
canvas.drawText("カウント" + count + " 回",10,80,paint);
//ここで SurfaceView canvas を描画する
holder1.unlockCanvasAndPost(canvas);

//カウントを1アップ
count += 1;
}
}

<結果>

画面の一部に SurfaceView が表示され、その中でカウンターが動作しました。

f:id:vw-dsg:20171222090107p:plainEND 

 

Matrix の基本「Translate」の使い方を知ろう(その2)

Matrix の Translate を知る

Matrix 関数の中に 「Translate」 というメソッドがあります。Translate を利用すると画像を任意の場所に移動することができますが、その使い方や使い道がよくわかりませんので、今回からその「Translate」について少しずつ調べてみることにします。

Translate は3種類ある

Translate には「setTranslate」「preTranslate」「postTranslate」の3種類があり、「移動する」という基本動作は同じですが、それぞれ微妙に動きが違います。

  • setTranslate・・・指定場所に移動する
  • preTranslate・・・あらかじめ移動する
  • postTranslate・・・現状に続けて移動する

と、簡単に説明するとこんな感じになるかとは思います。 

サンプルコード(MyView)

今回使うサンプルコード(オリジナルのView)です。動きは「画像を読み込んで表示する」だけの単純なものです。このコードを MainActivity で読み込んで表示させますので、レイアウトXMLは使いません。※Matrix の命令を記述するのはコード中の赤文字の部分になります。

public class MyView extends View {

//各オブジェクト・変数を準備
private Bitmap bitmap;
private BitmapFactory.Options options;
private Matrix matrix;
private Resources resources;

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

//リソース(drawable にある画像)にアクセスするための準備
resources = context.getResources();

//ビットマップを読み込む際のオプションを準備
options = new BitmapFactory.Options();
//スケーリングを無効にセット(原寸大表示にする)
options.inScaled = false;

//リソース(drawable)から画像を読み込んで、同時にオプションを適用
bitmap = BitmapFactory.decodeResource(resources, R.drawable.image, options);

//背景色をライトグレーにする
setBackgroundColor(Color.LTGRAY);
}

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

//matrix に新規のマトリックスを準備
matrix = new Matrix();

//ここに matrix の命令を記述
matrix.postTranslate(100,100);

//matrix を適用し画像を表示
canvas.drawBitmap(bitmap, matrix, null);
}

サンプルコード(MainActivity)

セットする View をレイアウトXML(activity_main)からオリジナルの「MyView」に書き換えますが、この時必ず頭に new を付けなければなりません。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//オリジナルの ViewMyView.class)をセットして使用
setContentView(new MyView(getBaseContext()));
}
}

調査 「複合パターンの動作」

まずは「setTranslate」と「preTranslate」を組み合わせた場合の動作です。

matrix.setTranslate(100,100);
matrix.preTranslate(200,200);

<結果>

どん兵衛の画像が左上の角から X方向に300px、Y方向に300px 移動しました。「preTranslate」は、”あらかじめ移動する”という命令なので、preTranslate であらかじめ X方向に100px、Y方向に100px 移動した後、そこを基準点にして setTranslate が実行され、最終的に、X方向に300px、Y方向に300px の位置に移動したと考えられます。 

f:id:vw-dsg:20171218103513p:plain

次に「setTranslate」と「postTranslate」を組み合わせた場合の動作です。

matrix.setTranslate(100,100);
matrix.postTranslate(200,200);

<結果>

「setTranslate」と「preTranslate」の組み合わせと同じように、どん兵衛の画像が左上の角から X方向に300px、Y方向に300px 移動しました。

f:id:vw-dsg:20171218103513p:plain

次に「preTranslate」と「postTranslate」を組み合わせた場合の動作です

matrix.preTranslate(100,100);
matrix.postTranslate(200,200);

<結果>

こちらも前の例と同じ結果になりました。preTranslate と postTranslate の組み合わせは、どちらが先でも継続して移動するようです。

f:id:vw-dsg:20171218103513p:plain

次は「setTranslate」と「preTranslate」を逆の順番で組み合わせた場合の動作です。

matrix.preTranslate(100,100);
matrix.setTranslate(200,200);

<結果>

今度は、どん兵衛の画像が「setTranslate」で指定した左上の角から X方向に200px、Y方向に200px の位置に移動しました。これは、あらかじめ移動する preTranslate でX方向に100px、Y方向に100px 移動した後に、setTranslate で置き換えられてしまったと考えられます。

f:id:vw-dsg:20171220231133p:plain

次は「postTranslate」と「setTranslate」の組み合わせです。

matrix.postTranslate(100,100);
matrix.setTranslate(200,200); 

<結果>

結果は、前回同様「setTranslate」で指定した左上の角から X方向に200px、Y方向に200px の位置に移動しました。 

f:id:vw-dsg:20171220231133p:plain

最後は「postTranslate」と「preTranslate」の組み合わせです。

matrix.postTranslate(100,100);
matrix.preTranslate(200,200); 

<結果>

どん兵衛の画像が「postTranslate」と「preTranslate」の数値を足した、左上の角から X方向に300px、Y方向に300px の位置に移動しました。 preTranslate と postTranslate の組み合わせは、どちらが先でも継続して移動するようです。

f:id:vw-dsg:20171218103513p:plain

今回のまとめ

今回は「setTranslate」「preTranslate」「postTranslate」をそれぞれ一つずつ組み合わせてみましたが、これだけでは Matrix の正しい使い方や使い道を知ることはできないようです。

次回「その3」ではこの3つの命令をもう少し複雑に組み合わせて、その動きを調べてみたいと思います。

END