Android アプリ開発「MATRIX」

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


【暗黙の仕様】AlarmManager のリピート間隔を「1分以下」にすることは不可能

AlarmManagerクラスのリピート間隔

AlarmManagerクラスには、指定した間隔で繰り返しアラームを発報することができる「setRepeating」「setInexactRepeating」というパブリックメソッドがありますが、この2つのメソッドのリピート間隔を1分以下にしようとしても無視され、プログラムコード上は1分以下になっていても内部的に1分間隔に修正されてしまうようです。

以下のような短い間隔での発報は不可能…

MyAlarmmanager.setRepeating(AlarmManager.RTC,SystemClock.currentThreadTimeMillis(),5000,pendingIntent);
MyAlarmmanager.setInexactRepeating(AlarmManager.RTC,SystemClock.currentThreadTimeMillis(),5000,pendingIntent);

備考・その他

過去には数秒という短い間隔でアラームの発報ができたようですが、仕様が変わったのか現在はそれができないようです。1分以下の短い間隔で処理を繰り返したい場合は他の方法を使用する必要があります。

END

【Android 9】requires android.permission.FOREGROUND_SERVICE エラーが発生する場合の対処方法

Android 9「フォアグラウンドサービス」の変更点

Android 9 以降は「フォアグラウンドサービス」を使用する際に、マニフェストファイルでパーミッションを要求しなければなりません。パーミッションを要求しないままアプリを起動すると「requires android.permission.FOREGROUND_SERVICE」エラーが発生してアプリが強制終了します。

フォアグラウンド サービス

Android 9 以降をターゲットにするアプリは、フォアグラウンド サービスを使用する際に FOREGROUND_SERVICE パーミッションをリクエストする必要があります。 これは Normal パーミッションなので、リクエストしたアプリに自動で付与されます。

Android 9 以降をターゲットにするアプリが FOREGROUND_SERVICE をリクエストせずに、フォアグラウンド サービスの作成を試みると、システムにより、SecurityException がスローされます。

※公式開発サイトより抜粋

このエラーの対処方法

マニフェストファイルで「FOREGROUND_SERVICE」パーミッションを要求してください。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

これでエラーによる強制終了を回避することができます。

END

ブロードキャストレシーバーに「has no zero argument constructor」というエラーが出た場合の対処方法

ブロードキャストレシーバーのエラー

ブロードキャストレシーバー(BroadcastReceiver)を使ったアプリを開発している時に「has no zero argument constructor」というエラーが発生してブロードキャストレシーバーが正常に動作しない場合があります。

対処方法

このエラーの対処方法は以下になります。

public class MyBroadcastReceiver extends BroadcastReceiver {

「public」と「class」の間に「static」を追加する

public static class MyBroadcastReceiver extends BroadcastReceiver {

備考・その他

static」はブロードキャストレシーバーを別ファイルにしなかった場合(他のクラス内に記述した場合)に追加する必要があるようです。

END

新しいアクティビティを開始する

新しいアクティビティを開始する

新しいアクティビティを開始するには「startActivity()」メソッドを使います。

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

※詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Viewにレイアウトファイルをセット
setContentView(R.layout.activity_main);
// レイアウトファイルのボタンを取得
Button button = (Button)findViewById(R.id.button);
// ボタンにクリックイベントをセット
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// インテントを作成
Intent intent = new Intent(getBaseContext(),NewActivity.class);
// アクティビティを開始する
startActivity(intent);
}
});
}
}

備考・その他

「finishActivity()」メソッドで終了するアクティビティを開始する場合は「startActivityForResult()」メソッドを使用します。

END

現在のアクティビティを終了する

現在のアクティビティを終了する

現在のアクティビティを終了するには「finish()」メソッドを呼び出します。

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

※詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Viewにレイアウトファイルをセット
setContentView(R.layout.activity_main);
// レイアウトファイルのボタンを取得
Button button = (Button)findViewById(R.id.button);
// ボタンにクリックイベントをセット
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// ボタンがクリックされたら現在のアクティビティを終了する
finish();
}
});
}
}

備考・その他

以前開始した別のアクティビティを終了する場合は「finishActivity()」メソッドを使用します。

END

「通知」を発行する方法(APIレベル26以上にも対応)

「通知」を発行する方法

今回は、画面の上部にある通知領域に「通知」を発行して表示する方法です。APIレベル26以上とそれ未満では発行の方法が少し違っているので、「if文」を使って振り分けています。

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

APIレベル26からは通知の発行に少し手間がかかるようになりました。通知マネージャー「NotificationManager」に通知チャンネル「NotificationChannel」を作る必要があるようです。※サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends AppCompatActivity {

//通知オブジェクトの用意と初期化
Notification notification = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//レイアウトファイルをコンテントビューとしてセット
setContentView(R.layout.activity_main);
//システムから通知マネージャー取得
NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
//アプリ名をチャンネルIDとして利用
String chID = getString(R.string.app_name);

//アンドロイドのバージョンで振り分け
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { //APIが「26」以上の場合

//通知チャンネルIDを生成してインスタンス化
NotificationChannel notificationChannel = new NotificationChannel(chID, chID, NotificationManager.IMPORTANCE_DEFAULT);
//通知の説明のセット
notificationChannel.setDescription(chID);
//通知チャンネルの作成
notificationManager.createNotificationChannel(notificationChannel);
//通知の生成と設定とビルド
notification = new Notification.Builder(this, chID)
.setContentTitle(getString(R.string.app_name)) //通知タイトル
.setContentText("アプリ通知テスト26以上") //通知内容
.setSmallIcon(R.drawable.icon) //通知用アイコン
.build(); //通知のビルド
} else {
//APIが「25」以下の場合
//通知の生成と設定とビルド
notification = new Notification.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("アプリ通知テスト25まで")
.setSmallIcon(R.drawable.icon)
.build();
}
//通知の発行
notificationManager.notify(1, notification);
}
}

 

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

マニフェストファイルに追記する必要はありません。

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

今回は通知領域だけのサンプルなので、レイアウトファイルにオブジェクトを配置する必要はありません。

実行結果

① 起動すると画面上部の通知領域に通知を知らせるアイコンが表示されます。

アンドロイド開発「通知の発行方法」①

② 通知領域を下にスライドさせると、通知一覧に今回作成した「通知」が表示されます。(APIレベル26以上)

アンドロイド開発「通知の発行方法」②

③ APIレベル25以下の端末エミュレーターでテストしても同じように通知領域にアイコンが表示されます。

アンドロイド開発「通知の発行方法」③

④ 通知領域を下にスライドさせると、通知一覧にサンプルコードで作成した「通知」が表示されます。(APIレベル25以下)

アンドロイド開発「通知の発行方法」④

備考・その他

「通知」には必須コンテンツというのが3つほどあります。

 ・「setSmallIcon」で設定する小サイズのアイコン
 ・「setContentTitle」で設定するタイトル
 ・「setContentText」で設定する詳細テキスト

以上の3つは必ず設定しなければなりませんが、それ以外は省略が可能になっているようです。なお、詳細についてはアンドロイド公式開発サイトで確認することができます。

「Notifications Overview  |  Android Developers」

END

アクティビティを持たないウィジェットを実行(インストール)できるようにする設定手順

アクティビティを持たないウィジェット

アクティビティを持たないウィジェットは、そのままでは実行やインストールができませんので、実行時の設定を変更する必要があります。

~ 実行設定の変更手順 ~

1.まず、Android Studio の「実行」ボタンの左にある「app」と表示されたボタンをクリックします。「app」ボタンには赤い「×」マークが付いています。

アプリの実行設定の変更手順1

2.ボタンの下にメニューが表示されるので、その中から「Edit Configurations…」という項目をクリックします。

アプリの実行設定の変更手順1

3.実行時の設定を管理する画面「Run/Debug Configurations」が表示されるので、一応、左側の項目で「app」が選択されているのを確認します。右側の「Launch Options」項目の「Launch」が「Default Activity」になっているので、それをクリックします。

アプリの実行設定の変更手順2

4.「Default Activity」クリックすると下にメニューが表示されるので、その中から「Nothing」を選択します。

アプリの実行設定の変更手順3

5.「Launch」を「Nothing」に変更したら「OK」をクリックして「Run/Debug Configurations」画面を閉じます。

アプリの実行設定の変更手順5

6.再度、Android Studio の実行ボタンの左にある「app」を確認すると赤色の「×」マークが消えています。これで実行(インストール)が可能になります。

アプリの実行設定の変更手順5

END

 

タップすると画像が切り替わるウィジェットの作り方(APIレベル 27まで)

タップで画像が切り替わるウィジェット

今回はウィジェットをタップするとその中に配置してある ImageView の画像が切り替わるウィジェットを作ります。ホーム画面に配置されたウィジェットを変更する方法はいくつかありますが、今回はウィジェットが自分自身で内部の画像を切り替える方法を説明します。

(注意)この作成方法はAPIレベルが27まで使えます。

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

ウィジェットはアクティビティを必要としませんので、MainActivity.java があっても使用することはありません。プロジェクト内に最初からある場合はそのまま放置してください。

サンプルコード②(MyWidget3.java)

今回のメインで唯一のJavaのプログラムコードになります。サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MyWidget3 extends AppWidgetProvider {

// ウィジェットをホーム画面に配置すると呼び出される
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);

// ウィジェットIDを取得(配列にあるの最初の値でOK
int myWidgetID = appWidgetIds[0];

// ウィジェットのレイアウトファイルにアクセスするための準備(レイアウトファイルの取得)
RemoteViews remoteViews1 = new RemoteViews(context.getPackageName(), R.layout.activity_main);
// インテントを生成してインスタンス化
Intent intent = new Intent();
// インテントにアクション名「CLICK_TEST」をセット
intent.setAction("CLICK_TEST");
// インテントにウィジェットIDを追加
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, myWidgetID);
// ウィジェットIDとインテントをブロードキャストするペンディングインテントを生成
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(context, myWidgetID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// imageView」をクリックした時にペンディングインテントを実行できるようにする
remoteViews1.setOnClickPendingIntent(R.id.imageView, pendingIntent1);
// ウィジェットを更新する
appWidgetManager.updateAppWidget(myWidgetID, remoteViews1);
}

// ブロードキャストを受信すると呼び出される
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);

// CLICK_TEST」というアクション名だけに反応させる
if (intent.getAction().equals("CLICK_TEST")) {

// 押されたウィジェットIDをインテントから取得
int changeWidgetID = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
// ウィジェットのレイアウトファイルにアクセスするための準備(レイアウトファイルの取得)
RemoteViews remoteViews2 = new RemoteViews(context.getPackageName(), R.layout.activity_main);
// レイアウトファイル内にある「imageView」の画像を「pslogo」に差し替える
remoteViews2.setImageViewResource(R.id.imageView, R.drawable.pslogo);
// ウィジェットマネージャーを取得
AppWidgetManager widgetManager1 = AppWidgetManager.getInstance(context);
// ウィジェットを更新する
widgetManager1.updateAppWidget(changeWidgetID, remoteViews2);
}
}

@Override
public void onDisabled(Context context) {
super.onDisabled(context);
// 必要に応じて記述
}

@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
// 必要に応じて記述
}

@Override
public void onEnabled(Context context) {
super.onEnabled(context);
// 必要に応じて記述
}

@Override
public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
super.onRestored(context, oldWidgetIds, newWidgetIds);
// 必要に応じて記述
}
}

 

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

マニフェストファイルにはウィジェットのレシーバーを登録して、「CLICK_TEST」というアクション名のインテントフィルターを追記します。これを忘れるとウィジェットが正しく動きませんので注意してください。

それから、ウィジェットの情報ファイル「my_app_widget_info.xml」の登録も忘れずに行ってください。

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

<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">

<receiver android:name=".MyWidget3">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<!-- CLICK_TEST」というアクション名のインテントも受信する設定 -->
<action android:name="CLICK_TEST" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>

</application>
</manifest>

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

レイアウトファイルは画面の中央に ImageView を1個配置しただけの簡単なものです。ウィジェットのレイアウトは中央以外にも左寄せ、右寄せにすることもできますが、ホーム画面に配置した時に左右に寄ってしまうので、上下左右ともに中央寄せにした方が良いでしょう。

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

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:src="@drawable/image" />
</RelativeLayout>

サンプルコード⑤(my_app_widget_info.xml)

このファイルは、使用するレイアウト、縦横の最小サイズ、リサイズの設定、更新時間、どこで使うウィジェットなのか、というのを設定するファイルです。

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/activity_main"
android:minHeight="40dp"
android:minWidth="40dp"
android:previewImage="@drawable/image"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen"></appwidget-provider>

実行結果

① 今回のウィジェットをホーム画面に配置すると縦横のリサイズができるシンプルな画像だけのウィジェットが表示されます。

アンドロイド開発「タッチに反応するウィジェットの作り方」①

② そのウィジェットをタップすると、ウィジェットの画像が別の画像に切り替わります。

アンドロイド開発「タッチに反応するウィジェットの作り方」②

③ 今度は同じウィジェットをホーム画面に複数(今回は6個)並べてみます。

アンドロイド開発「タッチに反応するウィジェットの作り方」③

④ 並べられたウィジェットを適当にタップすると、タップされたウィジェットの画像だけが別の画像に切り替わります。

アンドロイド開発「タッチに反応するウィジェットの作り方」④

備考・その他

通常、同じウィジェットを多数並べることはありませんが、この方法を利用すれば基本的に同じ機能を持ちながらも、微妙に違っているウィジェットを配置できるようになるので、工夫次第では便利なアプリを作ることができそうですね。

END

Matrix(マトリクス)でビットマップ画像を画像の中心を軸に拡大・縮小する方法

ビットマップ画像を中心を軸に拡大・縮小する

今回は、オリジナルの View(MyScale.class)に配置したビットマップ画像をマトリックス(Matrix)を使って画像の中心を軸に拡大・縮小します。View に配置したビットマップ画像の中心を軸に拡大・縮小する方法がわからないという方は、ぜひ参考にしてください。

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

MainActivity.java では、別ファイルで用意したオリジナルの View(MyScale.class)を読み込んでいます。

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//オリジナルの ViewMyScale.class)をセットして使用
setContentView(new MyScale(this));
}

サンプルコード②(MyScale.java

MyScale.java は実際に画面の表示と制御を行っているファイルです。サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MyScale extends View {

//オブジェクト・変数の準備
private Bitmap bitmap1; //ビットマップ画像
private Resources resources1; //リソース
private BitmapFactory.Options options1; //ビットマップオプション
private Timer timer1; //タイマー
private float scale_x, scale_y; //ビットマップ画像の表示倍率
private boolean kakudai; //拡大・縮小判定用

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

//プロジェクトのリソースにアクセスするための準備
resources1 = context.getResources();
//ビットマップを読み込む際のオプションを準備
options1 = new BitmapFactory.Options();
//スケーリングを無効にセット(原寸大表示にする)
options1.inScaled = false;
//リソース(drawable)から画像を読み込んで、同時にオプションを適用
bitmap1 = BitmapFactory.decodeResource(resources1, R.drawable.donbei, options1);
//Viewの背景色を赤色にする
setBackgroundColor(Color.RED);

//拡大・縮小を判断する変数の初期化(trueで拡大)
kakudai = true;
//ビットマップ画像の表示倍率を初期化
scale_x = 0f;
scale_y = 0f;

//タイマーをインスタンス
timer1 = new Timer();
//タイマーにスケジュールを設定してタイマーをスタート
timer1.schedule(new TimerTask() {
@Override
public void run() {

//ビットマップ画像の表示倍率の計算
if (kakudai == true) {
//拡大率を上げる
scale_x += 0.01f;
scale_y += 0.01f;
} else if (kakudai == false) {
//拡大率を下げる
scale_x -= 0.01f;
scale_y -= 0.01f;
}

//描画の更新(描画メソッドが実行される)
postInvalidate();

//ビットマップ画像の横幅で拡大・縮小を判断
if (scale_x > 4f) {
//縮小モードにする
kakudai = false;
} else if(scale_x < 0f) {
//拡大モードにする
kakudai = true;
}
}
},10,10); //10ms後に10ms間隔で実行
}

//描画メソッド
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//canvasに文字を表示するためのPaintの準備
Paint paint = new Paint(); //ペイントの生成とインスタンス
paint.setAntiAlias(true); //アンチエイリアスを有効
paint.setColor(Color.WHITE); //文字を白色に設定
paint.setTextSize(40); //文字サイズを50

//マトリクスを生成してインスタンス
Matrix matrix1 = new Matrix();
//マトリクスに幅と高さの表示倍率とピボット(中心となる)位置を設定
matrix1.setScale( scale_x, scale_y, bitmap1.getWidth() / 2, bitmap1.getHeight() / 2);
//マトリクスに移動座標を設定
matrix1.postTranslate( 600f, 800f);

//canvasにビットマップ画像を表示
canvas.drawBitmap( bitmap1, matrix1, null);
//テキスト文字の表示
canvas.drawText("現在の表示倍率:横幅 " + scale_x + " ",10,50, paint);
canvas.drawText("現在の表示倍率:高さ " + scale_y + " ",10,100, paint);
}
}

サンプルコード③(AndroidManifest.xml

マニフェストファイルの変更はありません。

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

画面表示にはオリジナルの View(MyScale.class)をセットしているので、レイアウトファイルは使用しません。

実行結果

① 起動すると X座標600px、Y座標800px の位置にビットマップ画像が表示され、画像の中心を軸に 10ms 間隔で徐々に拡大します。

アンドロイド開発「Matrix」画像の拡大・縮小①

② ビットマップ画像の横幅の拡大率が4倍以上になると拡大がストップして、今度は徐々に縮小していきます。

アンドロイド開発「Matrix」画像の拡大・縮小②

③ アプリを終了するまで拡大と縮小を繰り返します。

アンドロイド開発「Matrix」画像の拡大・縮小③

備考・その他

実行画面に表示されている幅と高さの拡大率をみるとわかる通り float(浮動小数点)には微妙な誤差が生まれてしまうので「float変数 == 整数値」という判断はできません。扱いにはくれぐれも注意が必要です。

END

Matrix(マトリクス)でビットマップ画像を連続回転させながら移動する方法

ビットマップ画像を連続回転させながら移動する

今回は、オリジナルの View(MyNewViewView.class)に配置した画像を、マトリックス(Matrix)を使い連続回転させながら移動するサンプルです。View に配置したビットマップ画像の回転や移動の方法がよくわからないという方はぜひ参考にしてください。

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

このサンプルは、画面を描画するオリジナルの View(MyNewViewView.class) を別ファイルにせず MainActivity の中に含めた「一体型」になっています。なお、サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends AppCompatActivity {

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

//オリジナルの ViewMyNewViewView.class)をセットして使用
setContentView(new MyNewViewView(this));
}

//オリジナルのViewMyNewViewView
public class MyNewViewView extends View {

//各オブジェクト・変数を準備
private Bitmap bitmap;
private BitmapFactory.Options options;
private Resources resources;
private float scale_x, scale_y;
private float ichi_x, ichi_y;
private float kakudo;

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

//タイマーの生成とインスタンス
Timer timer1 = new Timer();
//表示位置用の変数を初期化
ichi_x = 0f;
ichi_y = 150f;
//角度用の変数を初期化
kakudo = 0f;
//大きさ用の変数を初期化
scale_x = 0.1f;
scale_y = 0.1f;

//プロジェクトのリソースにアクセスするための準備
resources = context.getResources();
//ビットマップを読み込む際のオプションを準備
options = new BitmapFactory.Options();
//スケーリングを無効にセット(原寸大表示にする)
options.inScaled = false;
//リソース(drawable)から画像を読み込んで、同時にオプションを適用
bitmap = BitmapFactory.decodeResource(resources, R.drawable.pslogo, options);
//ビットマップの背景色に合わせてViewの背景色を黒色にする
setBackgroundColor(Color.BLACK);

//タイマーにスケジュールを登録してスタート
timer1.schedule(new TimerTask() {
@Override
public void run() {

//表示位置を計算
ichi_x += 2f;
ichi_y += 2f;
//角度の計算(5度ずつ回転)
kakudo += 5f;
//表示の更新(描画用メソッドを呼び出している)
postInvalidate();

//画像が5回転したらタイマー処理をキャンセルして終了させる
if(kakudo >= 1800f){
//自分(timer1)をキャンセル
this.cancel();
}
}
},100,100); //100ms後に100ms間隔で実行
}

//描画用メソッド
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//マトリックスを生成してインスタンス
Matrix matrix = new Matrix();
//マトリクスに座標をセット
matrix.setTranslate(ichi_x,ichi_y);
//続いて、ビットマップ画像の中心を軸にした回転角度を設定
matrix.postRotate(kakudo,ichi_x + bitmap.getWidth() / 2,ichi_y + bitmap.getHeight() / 2);
//文字を表示するためのPaintの準備
Paint paint = new Paint(); //ペイントの生成とインスタンス
paint.setAntiAlias(true); //アンチエイリアスを有効
paint.setColor(Color.WHITE); //文字を白色に設定
paint.setTextSize(50); //文字サイズを50

//matrix を適用したビットマップ画像を表示
canvas.drawBitmap(bitmap, matrix,null);
//テキスト文字の表示
canvas.drawText("現在の角度:" + kakudo + "",10,50, paint);
canvas.drawText("現在の位置:X座標" + ichi_x,10,100, paint);
canvas.drawText("現在の位置:Y座標" + ichi_y,10,150, paint);
}
}
}

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

マニフェストファイルの記述はありません。

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

画面にはオリジナルの View(MyNewViewView.class)をセットするので、レイアウトファイルは使用しません。

実行結果

① 起動すると X座標0px、Y座標150px の位置にビットマップ画像が表示され 100ms 間隔で5度ずつ回転しながら斜め右方向(X方向に2px、Y方向に2px)に移動を始めます。

アンドロイド開発「Matrix」回転と移動①

② しばらくして 5回転(1800度回転)したところで回転と移動が停止します。

アンドロイド開発「Matrix」回転と移動②

備考・その他

マトリックス(Matrix)の使い方がわかってくると、実際にあるいろいろな動きを再現できるようになります。今回のサンプルのコードを変更していろいろなテストをしてみてください。

END

マトリックス(Matrix)を使ってビットマップ画像を連続回転させる方法

マトリックスでビットマップ画像を連続回転させる

今回は、オリジナルの View(MyNewView.class)に配置した画像を、マトリックス(Matrix)を使用し同じ位置で連続回転させるサンプルです。View に配置したビットマップ画像の回転方法がわからない・・・という方はぜひ参考にしてください。

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

このサンプルは、画面を描画するオリジナルの View(MyNewView.class) を別ファイルにせず MainActivity の中に含めて「一体型」にしてあります。なお、サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//オリジナルの ViewMyNewView.class)をセットして使用
setContentView(new MyNewView(this));
}

//オリジナルのViewMyNewView
public class MyNewView extends View {
//各オブジェクト・変数を準備
private Bitmap bitmap;
private BitmapFactory.Options options;
private Resources resources;
private float kakudo;

public MyNewView(Context context) {
super(context);
//タイマーのインスタンス
Timer timer1 = new Timer();
//角度変数の初期化
kakudo = 0;
//プロジェクトのリソースにアクセスするための準備
resources = context.getResources();
//ビットマップを読み込む際のオプションを準備
options = new BitmapFactory.Options();
//スケーリングを無効にセット(原寸大表示にする)
options.inScaled = false;
//リソース(drawable)から画像を読み込んで、同時にオプションを適用
bitmap = BitmapFactory.decodeResource(resources, R.drawable.donbei, options);

//タイマーにスケジュールを登録してスタート
timer1.schedule(new TimerTask() {
@Override
public void run() {
//角度の計算(2度ずつ回転)
kakudo += 2;
//表示の更新(描画用メソッドを呼び出している)
invalidate();
}
},100,100); //100ms後に100ms間隔で実行
}

//描画用メソッド
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//マトリックスを生成してインスタンス
Matrix matrix = new Matrix();
//画像の中心を基準に画像を回転させる
matrix.setRotate(kakudo,bitmap.getWidth() / 2,bitmap.getHeight() / 2);
//回転させた画像をx=200,y=200の位置に移動
matrix.postTranslate(200,200);

Paint paint = new Paint(); //ペイントの生成とインスタンス
paint.setAntiAlias(true); //アンチエイリアスを有効
paint.setColor(Color.BLACK); //黒色に設定
paint.setTextSize(50); //文字サイズを50

//matrixを適用して画像を表示 + 現在角度の表示
canvas.drawBitmap(bitmap, matrix, null);
canvas.drawText("現在の角度:" + kakudo + "",10,50, paint);
}
}
}

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

マニフェストファイルの記述はありません。

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

画面表示にオリジナルの View(MyNewView.class)をセットするので、レイアウトファイルは使用しません。

実行結果

① 起動すると X座標200px、Y座標200px の位置に画像が表示され 100ms 間隔で2度ずつ回転を始めます。

アンドロイド開発「マトリックスで画像を回転させる」①

② 画像はアプリを終了するまで回り続けます。

アンドロイド開発「マトリックスで画像を回転させる」②

備考・その他

マトリックス(Matrix)の使い方がわかると、ちょっと変わったおもしろい動きを表現できるようになりますので、今回のサンプルのコードを変更していろいろとテストしてみてください。

END

画面の回転(縦位置 ⇔ 横位置)に対応する方法

画面の回転に対応する方法

アンドロイドアプリは画面を回転(横位置 ⇔ 縦位置)させると、自動でアクティビティが再起動される仕組み(仕様)になっているため、変数のデータも破棄されてしまいます。

そのため、画面の回転に対応するには、回転の直前に呼び出される「onSaveInstanceState」メソッド内でデータを一旦保存し、次に画面の回転が完了すると呼び出される「onRestoreInstanceState」メソッド、または「onCreate」メソッドの中で、画面回転前に保存したデータを読み込み再び画面上にセットするという処理が必要になります。

アプリの縦横回転対応

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

今回のサンプルでは1秒間隔でカウントをアップして画面に表示します。サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends AppCompatActivity {

private Timer timer1; //タイマーオブジェクト
private Handler handler1; //ハンドラーオブジェクト
private TextView textView; //テキストビューオブジェクト
private int counter; //カウンター変数

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

//レイアウトファイルをビューにセット
setContentView(R.layout.activity_main);

//レイアウトファイルのテキストビューを取得
textView = (TextView)findViewById(R.id.counter);
//タイマーをインスタンス
timer1 = new Timer();
//ハンドラーをインスタンス
handler1 = new Handler();
//カウントアップ用変数を初期化
counter = 0;

//タイマーを生成+スケジュールを実行
timer1.schedule(new TimerTask() {
@Override
public void run() {
//ハンドラー経由で画面を更新 ※直接更新するとエラーになる
handler1.post(new Runnable() {
@Override
public void run() {
//カウントアップ
counter += 1;
//テキストビューにカウント値をセット
textView.setText(String.valueOf(counter));
}
});
}
}, 1000, 1000); //1秒後から1秒間隔で実行
}

//画面の回転時に呼び出される(データを保存する)
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

//保存領域に「CONTENT_VALUE」というキーの数値を保存
outState.putInt("COUNT_VALUE", counter);
}

//アクティビティが再起動するときに呼び出される(データ読み出し)
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);

//保存領域から「CONTENT_VALUE」というキーの数値を取得
counter = savedInstanceState.getInt("COUNT_VALUE");
//レイアウトファイルのテキストビューにセット
textView.setText(String.valueOf(counter));
}
}

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

今回はマニフェストファイルへの記述はありません。

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

レイアウトファイルは画面の中央に大きめの TextView を配置しただけの簡単なものです。

<TextView
android:id="@+id/counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="100sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

実行結果

① 起動するとカウントアップ(1秒間隔)が始まります。

アンドロイド開発「画面回転対応」① 

② 画面の回転に対応しているので、端末を横位置にしてアプリが再起動してもカウント用の変数は保持されカウントが継続されます。

アンドロイド開発「画面回転対応」④

<画面の回転に対応していない場合>

端末を横位置にするとアクティビティの再起動と同時にカウント用の変数もクリアーされ「0」からの再スタートになってしまいます。

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

備考・その他

他に、画面の回転に対応する方法として「マニフェストファイルに記述する属性で画面が回転してもアクティビティを再起動させないようにする(android:configChanges="orientation")」というのがありますが、この方法はアンドロイドの公式開発サイトで「この属性の使用はなるべく避けて、あくまで最後の手段として使用する必要がある」とされているため、少々手間がかかりますが今回紹介した方法を利用した方が良いでしょう。

END

Android Studio 3.3 の「レイアウトエディタ」のプレビュー表示を以前の状態に戻す方法

レイアウトエディタのプレビュー表示が変わった

Android Studio 3.3 になって「レイアウトエディタ」のプレビュー表示が変わりました。

以前はタイトルバー(アクションバー)や、上段の通知バー、下段にホームボタン、戻るボタンなどがありましたが、Android Studio 3.3 では、それら(デコレーション)がデフォルトでは表示されず、スクリーンの必要な部分だけになっています。

デザイン自体はできるのでこのままでも問題はありませんが、バーやボタンが表示されないため画面全体としてのイメージがしにくく、また、プレビューと背景の境界もやや分かりづらいため以前の表示に戻したいと思います。

Android Studio 3.3 メイン画面

レイアウトエディタのプレビュー表示の変更方法

プレビューにタイトルバー(アクションバー)、通知バーなどを表示するには、プレビュー表示の左上にある「目」のアイコンをクリックし、表示されるメニュー項目にある「Show Layout Decoration」にチェックを入れます。

Android Studio 3.3 レイアウトエディタ「プレビュー表示」の変更方法

「Show Layout Decoration」にチェックを入れると少し間をおいてプレビューが更新され、以前のようなタイトルバー(アクションバー)や、通知バーなども表示されるようになります。

Android Studio 3.3 レイアウトエディタ「プレビュー表示」の変更方法

備考・その他

Android Studio 3.3 は、プレビュー表示のタイトルバー(アクションバー)や通知バーを表示したり隠したりすことができるようになったので、デザイン自体はしやすくなるかもしれませんね。

END

端末のバッテリー充電レベルを監視する方法

端末のバッテリー充電レベルを監視する

バッテリーの充電レベルを監視するには、バッテリーの充電レベルが変化するたびに端末から発信されるバッテリー充電レベルのインテントをブロードキャストレシーバーで受信する必要があります。

<流れ>

1.端末のバッテリー受電レベルが変化
       ↓
2.システムがバッテリー充電レベルの変化インテントを発信する
       ↓
3.アプリがブロードキャストレシーバーでインテントを受信
       ↓
4.アプリがバッテリー充電レベルの表示を更新する

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

ブロードキャストレシーバーのコードは別ファイルではなく MainActivity.java 内に記述します。(※詳しい説明はコード内のコメントを見てください。)

public class MainActivity extends Activity {

//インテントフィルターの箱を用意
private IntentFilter intentFilter1;
//テキストビューの箱を用意
public static TextView textView1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//レイアウトファイルをViewにセットする
setContentView(R.layout.activity_main);
//レイアウトファイルにあるテキストビュー「disp_level」を取得して格納
textView1 = (TextView)findViewById(R.id.disp_level);
//インテントフィルターをインスタンス
intentFilter1 = new IntentFilter();
//インテントフィルターにバッテリー残量のアクションを追加
intentFilter1.addAction(Intent.ACTION_BATTERY_CHANGED);

//ブロードキャストレシーバーとインテントフィルターを登録
registerReceiver(mbroadcastReceiver, intentFilter1);
}

//アプリが破棄(終了)される時に呼び出される
@Override
protected void onDestroy() {
super.onDestroy();
//ブロードキャストレシーバーの登録を解除
unregisterReceiver(mbroadcastReceiver);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////ブロードキャストレシーバー//////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
private static BroadcastReceiver mbroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//バッテリー関連のインテントを受信した場合
if (intent.getAction() == Intent.ACTION_BATTERY_CHANGED) {
//テキストビューにバッテリー残量レベルの値をセット
textView1.setText(intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) + "%");
}
}
};
}

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

今回はマニフェストファイルにブロードキャストレシーバーを登録する必要はありません。

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

レイアウトファイルは画面の中央に大きめの TextView を配置しただけの単純なものです。

<TextView
android:id="@+id/disp_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0%"
android:textSize="100sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

実行結果

① 起動すると画面の中央にテキストが表示されるので、そのままバッテリー充電レベルが変化するのを待つか、エミュレーターであれば、エミュレーターの「拡張コントロール」でバッテリーの充電レベルをマウスで操作します。

アンドロイド開発「バッテリー残量の監視」①

② バッテリー充電レベルが増えたり減ったりすると、画面中央のバッテリー残量レベルの数値もそれに合わせて更新されます。

アンドロイド開発「バッテリー残量の監視」②

備考・その他

今回のサンプルは「ホームボタン」や「バックボタン」でバックグラウンドに移行しても動作し続けます。バックグラウンドから削除(onDestroy)されると完全に終了します。

END

ブロードキャストレシーバーの基本的な使い方(その2)

ブロードキャストレシーバーの使い方(その2)

ブロードキャストレシーバーは端末が発信している様々なメッセージや情報を受信するクラスです。これを利用すると、時間やタイムゾーンの変化、バッテリーの残量や充電状態など、細かな端末の状態を知ることができます。また、自作の情報を発信してそれを受信することも可能です。

前回は自作のインテントを受信しましたが、今回は端末自らが発信(ブロードキャスト)しているインテント情報を受信する方法を説明したいと思います。

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

今回はブロードキャストレシーバーに関するコードを別ファイルに記述するのではなく MainActivity.java に記述します。サンプルコードの詳しい説明はコード内のコメントを参照してください。

public class MainActivity extends Activity {

//オブジェクト・変数の準備
private static TextView textView1;
private static int kaisu;

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

//Viewにレイアウトファイルをセットして使用する
setContentView(R.layout.activity_main2);

//レイアウトファイルのTextViewを取得
textView1 = (TextView)findViewById(R.id.textView);
//変数kaisu0をセット
kaisu = 0;

//インテントフィルターを準備
IntentFilter intentFilter1 = new IntentFilter();
//現在時刻が変更された(分単位)
intentFilter1.addAction(Intent.ACTION_TIME_TICK);
//レシーバーを登録
registerReceiver(mbroadcastReceiver, intentFilter1);

//TextViewの更新メソッドを実行
koushin();
}

//TextView更新メソッド
public static void koushin() {

//TextViewの更新
textView1.setText("受信回数:" + kaisu + "");
}

///////////////////////////////////////////////////////////////////////////////
////////////ブロードキャストレシーバー部///////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
private static BroadcastReceiver mbroadcastReceiver = new BroadcastReceiver() {

//受信した時に呼び出されるメソッド
@Override
public void onReceive(Context context, Intent intent) {

//受信回数+1
kaisu += 1;
//更新メソッドを呼び出す
koushin();
}
};
}

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

「ブロードキャストレシーバーの基本的な使い方(その1)」ではマニフェストファイルにブロードキャストレシーバーを登録しましたが、今回は登録する必要がありません。

<注意点>
今回の使い方で受信できるのは端末が自ら発信しているブロードキャストのみで、自作のインテントを受信することができません。自作のインテントを受信するには、マニフェストファイルに登録する「ブロードキャストレシーバーの基本的な使い方(その1)」を参考にしてください。

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

レイアウトファイルは画面の中央に大きめの TextView を配置しただけの簡単なものです。

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="TextView"
android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

実行結果

① 起動すると画面の中央にテキストが表示されるので、現在時刻の変更のブロードキャストがあるまでそのまま待ちます。

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

② 現在時刻が変更(1分間隔)されるたびにブロードキャストレシーバーがそれを受信して「受信回数」がひとつずつ加算されていきます。

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

備考・その他

ブロードキャストレシーバーで受信できる端末情報の種類はとても多いのですが、知っていると非常に便利なので一度目を通しておいた方が良いと思います。

Android Developers|Intent> Intent  |  Android Developers

END