ラベル Android の投稿を表示しています。 すべての投稿を表示
ラベル Android の投稿を表示しています。 すべての投稿を表示

2015年9月10日木曜日

Recon Jet向けAndroidアプリ開発:ヘッドトラッキング編

前回はRecon JetでAndroidアプリ開発するための前準備について書きました。

【注意】
ReconOSを最新版にしておきましょう。
Recon UplinkアプリをダウンロードしてデバイスをPCに接続するだけでアップロードが始まります。なお、Recon UplinkはEngageに登録後ダウンロードできます。
最新版のファームにしないと実機デバッグができない場合があります。

今回はヘッドトラッキングをするための手順をメモしていきます。
こちらの手引きを参考にすればできます。
英語なので苦手な方に参考になれば幸いです。

①AndroidManifest.xmlに追加します。
applicationタグの間に下線部を追加します。
<下記は引用>
<manifest…>
…
<application…>
…
<uses-library android:name="com.reconinstruments.os" />
…
</application>
…
</manifest>

②パッケージのインポート、マネージャーの取得、リスナーの実装をします。
Activityクラスに直接実装する場合は大枠は以下のようになるはずです。
<下記は引用>
import com.reconinstruments.os.HUDOS; ←インポート
import com.reconinstruments.os.hardware.sensors.HUDHeadingManager; 
←インポート
import com.reconinstruments.os.hardware.sensors.HeadLocationListener; 
←インポート

public class SampleActivity extends Activity implements HeadLocationListener ←ロケーション情報コールバック用のリスナーを実装します。
{

HUDHeadingManager mHUDHeadingManager = null; ←マネージャー保持用変数

@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG,"onCreate");
...
mHUDHeadingManager = (HUDHeadingManager) HUDOS.getHUDService(HUDOS.HUD_HEADING_SERVICE); ←onCreate時にマネージャー取得します。
}

@Override
public void onStart() {
Log.i(TAG,"onStart");
super.onStart();
mHUDHeadingManager.register(this); ←onStart時にリスナーを登録します。
mIsStarted = true; ←トラッキング中かどうかのフラグを保存しておけば便利になります。
}
@Override
public void onStop() {
Log.d(TAG,"onStop");
mHUDHeadingManager.unregister(this); ←onStop時にリスナーを登録を解除します。
mIsStarted = false;
 ←トラッキング中かどうかのフラグを保存しておけば便利になります。
super.onStop();
}

@Override
public void onHeadLocation(float yaw, float pitch, float roll) { ←リスナーの実装部分です。
// ここでRecon Jetの現在のヘッドロケーションがコールバックされます。
// yaw値:頭の水平方向の移動(※詳細は後述)
// pitch値:頭の上下方向の傾き(※詳細は後述)
// roll値:頭の左右方向の傾き(※詳細は後述)
}

}

③onHeadLocationコールバックについて
実際に20ms前後の間隔でコールバックされてきました。
yaw, pitch, rollそれぞれの値の単位は恐らく【度(°)】です。
yaw=0°になる位置とiPhoneのコンパスアプリの北が微妙にずれていました。
ちゃんと検証してないので間違っているかもしれないのでご注意を。
だいたい以下のような感じです。
■yaw
0°=北
90°=東
180°=南
360°=北
■pitch
−90°=真上仰向け
0°=正面
90°=真下
■roll
-90°=右真横
0°=垂直
90°=左真横

④あとは応用
好きに料理しちゃってください。

あまりRecon JETでアプリ開発してる人いなそうな。
ぐぐってもあまり情報がないような。。。

2015年7月4日土曜日

Recon Jet向けAndroidアプリ開発:開発準備編

昨今話題になっているメガネ型のウェアラブルデバイス「Recon Jet」
詳細はここ→http://www.reconinstruments.com/products/jet/

ようやくSDKが公開されたので早速アプリを開発しましょう!
ということで準備しましょ。

【注意事項】
Androidアプリの開発環境がすでに整っている前提とします。
また、ご自身の環境に合わせて読みといてください。

■環境
OS:Mac OS X 10.10.4
Java:1.8

■必須
事前にRecon JetのOSのバージョンを最新の4.2.0(2015年7月3日現在)にアップデートしておいてください。

本家のページを参考にすれば数分でセットアップは終わります。
http://www.reconinstruments.com/developers/develop/getting-started/recon-sdk-4-api/

1、Android Studioを起動します。Configure→SDK Managerを起動します。
2、一旦すべてのチェックをはずしてから、APIレベル16(4.1.2)のSDK PlatformとSources for Android SDKを選択しインストールする。
3、次にRecon SDK Add-Onをインストールするために、SDK ManagerのToolsメニュー→Manage Add-on sitesを選択する。
4、User Defined Sitesタブを選択し、Newを選択する。
5、http://www.reconinstruments.com/wp-content/recon-sdk/repository.xmlを入力してOKを選択し、Closeを選択する。
6、Recon instruments SDK Add-onが追加されているのでインストールする。
7、appモジュール内のbuild.gradleを開いてcompileSdkVersionの項目を下記のように修正する。
compileSdkVersion 'Recon Instruments:Recon Instruments SDK Add-On:16'
8、ついでに、targetSdkVersionをついでに「16」に変更します。
9、このままだと実機(Recon Jet)を接続しても表示されないので、Adbを差し替えます。
9−1、http://www.reconinstruments.com/wp-content/recon-sdk/adb.zipからダウンロードする。※3つのバージョンが入ってます。
9−2、./adb kill-serverで一旦終了させてから、既存のadbはadb.orgなどにリネームして置いておくといいと思います。
9−3、解凍したadbに置き換えてください。
10、アプリを実行してみてください。めでたくReconJetが表示されれば成功です。

2015年6月12日金曜日

Mac OS X Yosemite 10.10.3でParrot社のDroneSDKをAndroid向けにビルド

いろいろな事前準備と予期せぬエラーで大変だったのでメモしておきます。
間違っていたらご指摘ください。コメントなどで補足いただけると幸いです。

※個人メモのコピペなので親切じゃないです。
パスなど環境は適宜に読み替えてください。
※個人の環境によっても発生するエラーが異なるので
がんばってください。。。orz

公式ブログも合わせてご参照ください。
http://ardroneblog.com/ARDroneBlog/DroneBlog/Entries/2015/2/24_Bebop_SDK_in_OSX.html

-------ここから------
Homebrewをインストールして、brewコマンドを使えるようにしておく

Oracle Javaをインストールしてjava -versionでバージョン表示されるようにする

brewコマンドでgit, wget, automake, autoconf, libtool, yasm, nasm, mysqlあたりはインストールできるはず。
※mysqlはなぜかビルド失敗時にそれ関係のログを吐いたので入れてます

rplは以下のURLからダウンロードして解凍してパスに通しておく
http://sourceforge.net/projects/rpl/files/rpl/rpl-1.5.5/

AndroidSDKとAndroidNDKをそれぞれダウンロードして解凍しておく
適当に~/devディレクトリを作成して、~/dev/sdk、~/dev/ndkのように入れておく
次にANDROID_SDK_PATHとANDROID_NDK_PATHにそれぞれSDKのルートまでのパスをとおしておく。
Macだと~/.bash_profileでいいと思います。

$ANDROID_SDK_PATH/tools/androidを起動してAPI14とAPI19の2つに関するもの、
ADTの最新版をインストールしておく。
SupportLibraryなども入れといた方が良さそうです。

~/dev/android_toolchainsディレクトリを作成し、さらに中に
~/dev/android_toolchains/toolchain_arm,
~/dev/android_toolchains/toolchain_mips,
~/dev/android_toolchains/toolchain_x86の3つのディレクトリを作成する

以下のコマンドで上記の3つのディレクトリにツールを入れる
$ANDROID_NDK_PATH/build/tools/make-standalone-toolchain.sh --platform=android-14 --arch=<arch> --install-dir=<your/installation/dir>
【注意】
<arch>はそれぞれarm, mips, x86
<your/installation/dir>はフルパス指定で作成した3つのディレクトリをそれぞれ指定
(例)
<arch>がarmの場合は、
<your/installation/dir>を/Users/tsuyoshi/android_toolchains/toolchain_arm

上記のすべてが終われば事前準備は完了のはず。はず。

ビルドスクリプトをgitからダウンロードする。
~/dev/dronesdkを作成して
cd ~/dev/dronesdk; git clone https://github.com/ARDroneSDK3/ARSDKBuildUtils.git
すでにclone済みの人はgit pullで最新版にしておいてください。

【注意】
以下は、ARSDKBuildUtilsの中のpythonスクリプトが実行できない場合、改行コードが影響していると思われるエラーが出たらやってください。実行できるなら必要ないです。
ARSDKBuildUtils以下にある.pyファイルの改行コードをLFに変換する。
ARSDKBuildUtils/Utils以下にある.bashファイルの改行コードをLFに変換する。

環境セットアップが正しいかどうか必ずCheckEnv.pyを実行してオールグリーンにしておく
ただし、AndroidSDKのAPI19をインストールしてもMissingって出るのは放置。←10.10.3では出なかったな。。。
Platoforms/Android-19があることを確認して、あればおk。

CheckEnv.phを実行してAndroidセクションがOKであれば次はビルドを実行する

./SDK3Build.py -t Androidとすればビルドが開始される

2015/06/05にビルドしたらopensslのダウンロードで証明証エラーでコケる。
opensslで証明証エラーって怖いな。。と思いつつ、手動でダウンロード。
※これは発生しない場合もあったので注意
wget --no-check-certificate https://www.openssl.org/source/openssl-1.0.2a.tar.gz -O /Users/[USERNAME]/mydev/drone/dronesdk/ARSDKBuildUtils/../openssl/openssl-1.0.2a.tar.gz
【追記】2015/06/12時点で上記のコマンドではエラーになる。原因はopenssl-1.0.2a.tar.gzがないようだ。openssl-1.0.2b.tar.gzになっているので以下のように手動でダウンロードおよび解凍をしてください。--no-check-certificateオプションは必要に応じて。
wget --no-check-certificate https://www.openssl.org/source/openssl-1.0.2b.tar.gz -O /Users/[USERNAME]/mydev/drone/dronesdk/ARSDKBuildUtils/../openssl/openssl-1.0.2a.tar.gz
ダウンロードしたら、手動で解凍します。
cd ../opensslに移動して、tar xzvf openssl-1.0.2a.tar.gzで解凍します。
openssl-1.0.2bと解凍されるので、openssl-1.0.2aにリネームしてください。

そしたら、またARSDKBuildUtilsディレクターに戻って、
再度./SDK3Build.py -t Androidを実行してください。

スペックに依存しますが、10分前後くらい時間かかります。
正常にすべてのビルドが終了すると以下のような表示になるはずです。
そうするとARSDKBuildUtils/Targetsの中にビルド結果ができてますね。
結構面倒です。
がんばってください。。。

2015年1月6日火曜日

Android StudioでFacebook SDKを追加する際に発生するエラーの回避

OS: Mac OS X 10.10
Android Studio: 1.0.2
Facebook SDK: 3.21.1

主に2つのエラーに出くわしました。
・SDKに関するプロパティがない
・bolts-androidがない
前者はわりと簡単に解決できました。
後者は他のブログでおっしゃっているような方法とちょっと異なっていたので、
メモしておきます。
例えば下記のブログエントリーではboltsのパスを書き換えればよいと書いてある。

android studioでfacebook sdkを使えるようにするまでにやった事


でも実際のbuild.gradleは以下のような記述になっています。
compile 'com.parse.bolts:bolts-android:1.1.2'
しかし、しっかりとbolts-androidは存在していた!なのになぜ?!
もしかして、mavenCentral()って書いてあるけど見てない??
facebookのbuild.gradleのリポジトリはmavenCentral()となっているけど、
作成したプロジェクトのbuild.gradleはjcenter()ってなってる!!!?ん??
ん??もしかしてこれに上書きされているのか!?
もっと調べてみるとSOFでこんな投稿を見つけた。
http://stackoverflow.com/questions/24852219/android-buildscript-repositories-jcenter-vs-mavencentral
どうやらまだ統一されていないようだ。

いろいろgradleファイルのrepositoryを書き換えてリビルドしたけど結局解決しないので、libsにあるものを見るようにしてあげた。これで一応ビルドは通るけどしっくりこないんだよね!!
//compile 'com.parse.bolts:bolts-android:1.1.2'
    compile files('libs/bolts-android-1.1.2.jar')

原因が分かる方いましたら教えてくださいm(_ _)m

とりあえずこれで。
まとめると以下のような手順。

1.SDKファイルをダウンロードする
ダウンロード先:https://developers.facebook.com/docs/android?locale=ja_JP
2.Android Studioにインポートする
2−1.Android Studioを起動する
2−2.ダウンロードしたzipファイルを解凍する
2−3.Android Studioの[File]-[Import Module...]を選択する
2−4.回答したフォルダ(例:facebook-android-sdk-3.21.1)の中のfacebookというフォルダを選択する
2−5.下記のようなエラーメッセージが表示される
Error:(8, 0) Could not find property 'ANDROID_BUILD_SDK_VERSION' on project ':Todo'.
2−6.gradle.propertiesフィアルに以下を追加する
※編集できないというメッセージが出ても強制編集
ANDROID_BUILD_TARGET_SDK_VERSION=18
ANDROID_BUILD_TOOLS_VERSION=20
ANDROID_BUILD_SDK_VERSION=18
ANDROID_BUILD_MIN_SDK_VERSION=14
2−7.Android Studioの[Build]-[Rebuild Project]を選択する
3.Android Studioの[File]-[Project Structure...]を選択する
4.左側の[app]を選択し、[Dependencies]タブを選択する
5.左下の[+]ボタンを選択肢[Module Dependency]を選択する
6.先ほど追加した[:facebook]を選択してOKを選択する
7.さらにOKで画面を閉じる
8.今度は以下のようなエラーメッセージが表示される
bolts-android.jarがありません
9.追加したfacebookモジュールのbuild.gradleを開く
10.11行目をコメントアウトし以下のように追加する
    //compile 'com.parse.bolts:bolts-android:1.1.2'
    compile files('libs/bolts-android-1.1.2.jar')
11.Android Studioの[Build]-[Rebuild Project]を選択する

2013年7月21日日曜日

Android2.3.3でAsyncTaskLoaderを使おう

Android2.3.3でアプリを開発しています。

バックグラウンドで通信して、取ってきたデータをキャッシュしてユーザに提示するような、
よくあるアプリケーションを制作しています。
実際にはアップデートを担当しています。

現在、アプリのバックグラウンド処理にはAsyncTaskを利用しています。
AsyncTaskについて調べているとAndroid3.0からはAsyncTaskは非推奨になっていて、
代わりにAsyncTaskLoaderなるものが登場していることを知った。

http://www.srv-shinra.com/wordpress/?p=308

を見てもわかるようにそういうことだそうです。
APIレベルを上げてやればいいのですが、Android.2.3.3のユーザも結構いるため、
そう簡単に切り捨てることはできません。

でも、AsyncTaskはcancel処理あたりに問題があるという記事をチラホラみかけます。
例えば、

GCMのIntentServiceでAsyncTaskを実行したらsending message to a Handler on a dead threadの警告が出て悩まされる


に取り上げられていることが発生するみたいです。
回避するためにはThreadを使ったりするみたいですが、それも面倒です。

だから、Android2.3.3でもAsyncTaskLoaderを使いたいという結論に至りました。
でも、ネット上で実装している人の情報をあまり見つけることができません。
調べ方が悪いのか。。。

でも、SupportLibraryにLoaderManagerとかあるのになんでActivityにgetLoaderManagerメソッドがないの??

と思っていろいろ調べていたら、FragmentActivityに実装されていました。
なるほど、もうActivityで作るのは時代遅れなのですね。。

でも、ActivityにないLoaderManagerさえ自前で自作してしまえば
ActivityでもAsyncTaskLoaderが使えるようです。

Androidソースコードを見ると、LoaderManagerはAbstractなので実際に実装されているクラスは、
LoaderManagerImplというクラスがありました。

それを参考にしてLoaderManagerだけ実装してやれば実現できそうです。
HashMapで管理するLoaderManagerを実装して簡単に試してみたらできました。

でも、もうActivityはもう時代遅れなのか?!。。。

Androidソースを除いてたらSparseArrayなるクラスがあることを知った。

「AndroidのSparseArrayは本当に速いのか測定してみた」
http://thinking-megane.blogspot.jp/2012/06/androidsparsearray.html

ここを見ると以降SparseArrayを使おうと思いました。

結局、今回の調べごとで勉強したことは、
・FragmentActivityをつけば問題は解決されることと
・SparseArrayはHashMapより速い
でした。

収穫収穫!!

以下は実装途中ですが、こんな感じでLoaderManagerを実装すればActivity(Android2.3.3)でもAsyncTaskLoaderが使えるということです。
--------
package com.example.asyncloadertest;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

public class TestLoader extends AsyncTaskLoader<String> {

    String result;

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

    @Override
    public String loadInBackground() {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet("http://www.yahoo.co.jp/");
try {
    HttpResponse execute = client.execute(get);
    String res = EntityUtils.toString(execute.getEntity());
    return res;
} catch (ClientProtocolException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
return null;
    }

    @Override
    public void deliverResult(String data) {
if (isReset()) {
    if (this.result != null) {
this.result = null;
    }
    return;
}

this.result = data;

if (isStarted()) {
    super.deliverResult(data);
}
    }

    @Override
    protected void onStartLoading() {
if (this.result != null) {
    deliverResult(this.result);
}
if (takeContentChanged() || this.result == null) {
    // これをやっておくとonCreateLoaderで開始処理をしなくてよくなる
    forceLoad();
}
    }

    @Override
    protected void onStopLoading() {
super.onStopLoading();
cancelLoad();
    }

    @Override
    protected void onReset() {
super.onReset();
onStopLoading();
    }

    @Override
    public void dump(String prefix, FileDescriptor fd, PrintWriter writer,
    String[] args) {
super.dump(prefix, fd, writer, args);
writer.print(prefix);
writer.print("result=");
writer.println(this.result);
    }

}
--------
package com.example.asyncloadertest;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;

import android.os.Bundle;
import android.app.Activity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

public class AsyncTascLoaderTestActivity extends Activity implements LoaderCallbacks<String> {

    // Yahooをロードする
    public static final int TASK_001 = 0x00001;
    
    LoaderManager loaderManager = new LoaderManager() {
        HashMap<Integer, Loader<String>> loaders = new HashMap<Integer, Loader<String>>();
        
        @Override
        public <String> Loader<String> restartLoader(int arg0, Bundle arg1,
        LoaderCallbacks<String> arg2) {
    return null;
        }
        
        /**
         * ローダの初期化
         */
        @Override
        public <String> Loader<String> initLoader(int arg0, Bundle arg1,
        LoaderCallbacks<String> arg2) {
            Log.d("Called","initLoader");
    Loader<String> loader = arg2.onCreateLoader(arg0, arg1);
    // do initialize...
    loaders.put(arg0, (Loader<String>) loader);
    return loader;
        }
        
        @Override
        public <String> Loader<String> getLoader(int arg0) {
            Log.d("Called","getLoader");
    return (Loader<String>) loaders.get(arg0);
        }
        
        @Override
        public void dump(String arg0, FileDescriptor arg1, PrintWriter arg2,
        String[] arg3) {
            // nothing
        }
        
        /**
         * 破棄
         * これより前にロードを止めるべき
         */
        @Override
        public void destroyLoader(int arg0) {
            Log.d("Called","destroyLoader");
            loaders.remove(arg0);
        }
    };
    // TextView
    TextView view;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_tasc_loader_test);
// ローダー初期化
loaderManager.initLoader(TASK_001, null, this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.async_tasc_loader_test, menu);
return true;
    }

    /**
     * ローダー作成
     */
    @Override
    public Loader<String> onCreateLoader(int arg0, Bundle arg1) {
        Log.d("Called","onCreateLoader");
// Create
TestLoader loader = new TestLoader(this);
return loader;
    }

    /**
     * バックグラウンド処理が終わったら
     */
    @Override
    public void onLoadFinished(Loader<String> arg0, String arg1) {
        Log.d("Called","onLoadFinished");
view = (TextView) findViewById(R.id.view);
if (arg0 != null && arg0.getId() == TASK_001) {
    view.setText(arg1);
}
    }

    /**
     * 処理がリセットされたら
     */
    @Override
    public void onLoaderReset(Loader<String> arg0) {
if (view != null)
    view.setText("Loader is reset");
    }


}
--------

2012年6月12日火曜日

AndroidのToggleButtonをiPhoneのスイッチ風に本気でカスタマイズする(XML編)

カスタマイズした結果は以前示した通りです。
かなりiPhoneに近くなりました。

実際に用いたXMLを以下に示す。
ON・OFFの画像は私の作品ではないので著作権の問題を考慮し、
公開しません。ご自分で用意してください。

なにかツッコミ、質問がありましたらよろしくお願いします。

--------------以下XML----------------
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="false"><layer-list>
            <item><shape>
                    <size android:height="27dp" android:width="80dp" />
                    <corners android:radius="25dp" />
                    <solid android:color="#dcdcdc" />
                    <stroke android:width="1dip" android:color="#808080" />
                </shape></item>
            <item android:bottom="1.5dp" android:left="5dp" android:right="8dp" android:top="13dp"><shape>
                    <corners android:radius="15dp" />
                    <gradient android:angle="270" android:centerColor="#ededed" android:centerX="0.5" android:centerY="0.5" android:endColor="#ededed" android:gradientRadius="10" android:startColor="#ededed" android:type="linear" />
                </shape></item>
            <item android:right="52dp"><shape>
                    <size android:height="27dp" android:width="28dp" />
                    <corners android:radius="25dp" />
                    <solid android:color="#ffffff" />
                    <gradient android:angle="270" android:centerColor="#dcdcdf" android:centerX="0.5" android:centerY="0.3" android:endColor="#ffffff" android:gradientRadius="10" android:startColor="#dcdcdc" android:type="linear" />
                    <stroke android:width="1dip" android:color="#808080" />
                </shape></item>
            <item android:left="27dp"><bitmap android:gravity="center" android:src="@drawable/off" />
            </item>
        </layer-list></item>
    <item android:state_checked="true"><layer-list>
            <item><shape>
                    <size android:height="27dp" android:width="80dp" />
                    <corners android:radius="25dp" />
                    <solid android:color="#0099ff" />
                    <stroke android:width="1dip" android:color="#808080" />
                </shape></item>
            <item android:bottom="1.5dp" android:left="8dp" android:right="5dp" android:top="13dp"><shape>
                    <corners android:radius="15dp" />
                    <gradient android:angle="270" android:centerColor="#33aaff" android:centerX="0.5" android:centerY="0.5" android:endColor="#33aaff" android:gradientRadius="10" android:startColor="#33aaff" android:type="linear" />
                </shape></item>
            <item android:left="52dp"><shape>
                    <size android:height="27dp" android:width="28dp" />
                    <solid android:color="#ffffff" />
                    <gradient android:angle="270" android:centerColor="#dcdcdf" android:centerX="0.5" android:centerY="0.3" android:endColor="#ffffff" android:gradientRadius="10" android:startColor="#dcdcdc" android:type="linear" />
                    <corners android:radius="25dp" />
                    <stroke android:width="1dip" android:color="#808080" />
                </shape></item>
            <item android:right="27dp"><bitmap android:gravity="center" android:src="@drawable/on" />
            </item>
        </layer-list></item>
</selector>

2012年6月8日金曜日

AndroidのToggleButtonをiPhone風に本気でカスタマイズする(XMLは次回)

iPhoneのUIは素晴らしいと思います。
Androidでそれを実現するのは結構たいへんです。

せめてスイッチだけでも。。。
かなり頑張ってiPhone風にカスタマイズしました。

とりあえず完成品だけ。
XMLソースは整理したらアップします。

用いているのはXMLとON, OFF画像だけです。

こんな感じです。
iPhone風スイッチボタン

2012年4月16日月曜日

AndroidでiPhone風のスイッチを作る

普通のAndroidのToggleButtonを使ってもつまらないですね!

デザインならiPhoneが今のところ一番だと考えます。

なるべく近づけてみましょう。

論より証拠ということで以下のコードを参考にしてみてください。
←普通のToggleButton

←これがカスタマイズした後のToggleButton

手順は以下の通りです。
1、以下のコードをres/drawable/customtoggle.xmlとでもして保存して下さい。
2、backgroundプロパティに指定して下さい。
3、on textとoff textにそれぞれ半角スペースを指定して下さい。


<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:state_checked="false">
        <layer-list>
            <item>
                <shape>
                    <size android:height="27dp" android:width="60dp" />

                    <gradient android:angle="270" android:centerColor="#c0c0c0" android:centerX="0.5" android:centerY="0.5" android:endColor="#c0c0c0" android:gradientRadius="10" android:startColor="#c0c0c0" android:type="linear" />

                    <corners android:radius="8dp" />

                    <solid android:color="#c0c0c0" />

                    <stroke android:width="1dip" android:color="#808080" />
                </shape>
            </item>
            <item android:left="30dp">
                <shape>
                    <size android:height="27dp" android:width="30dp" />
                    <corners android:radius="8dp" />
<gradient android:angle="270" android:centerColor="#B0C4DE" android:centerX="0.5" android:centerY="0.5" android:endColor="#778899" android:gradientRadius="10" android:startColor="#ADD8E6" android:type="linear" />


                    <stroke android:width="1dip" android:color="#808080" />
                </shape>
            </item>
            <item android:left="-30dp">
                <bitmap android:src="@drawable/off" android:gravity="center" />
            </item>
        </layer-list>
    </item>
    <item android:state_checked="true">
        <layer-list>
            <item>
                <shape>
                    <size android:height="27dp" android:width="60dp" />

                    <corners android:radius="8dp" />

                    <solid android:color="#ffffff" />

                    <stroke android:width="1dip" android:color="#808080" />
                </shape>
            </item>
            <item android:right="30dp">
                <shape>
                    <size android:height="27dp" android:width="30dp" />
<gradient android:angle="270" android:centerColor="#B0C4DE" android:centerX="0.5" android:centerY="0.5" android:endColor="#778899" android:gradientRadius="10" android:startColor="#ADD8E6" android:type="linear" />
                    <corners android:radius="8dp" />

                    <stroke android:width="1dip" android:color="#808080" />
                </shape>
            </item>
            <item android:right="-29dp">
                <bitmap android:src="@drawable/on" android:gravity="center" />
            </item>
        </layer-list>
    </item>

</selector>

2012年4月14日土曜日

Androidでアラームを作るための手順の例

ネットで「Android アラーム 開発」などとググるといろんな方が記事を書いています。

しかし、断片的なイメージが強かったのでまとめてみたいと思います。

■今回作成するアプリの概要
AlarmManagerを用いて、指定した時間後にアラーム通知画面が表示され、アラーム音を鳴らす

■手順
SampleActivity(アラーム設定画面とします)
↓
(任意)MyAlarmManager(設定されたアラームを管理する独自クラス)
↓
MyAlarmService(サービスを起動してブロードキャストメッセージを送ります)
↓
MyAlarmNotificationReceiver(メッセージを受信して通知画面を起動します)
↓
AlarmNotificationActivity(通知画面です)

■条件
1、スリープ画面であってもアラーム画面を表示する

■実装
まず、マニフェストファイルで設定しないといけない項目があります。
以下のXMLをAndroidManifest.xmlのapplicationタグ内の適切なところに配置します。
intent-filterで独自のメッセージ「"MyAlarmAction"」を受信するようにしてます。
------------------------------------------------------------------------------------------------
 <receiver android:name="MyAlarmNotificationReceiver">
            <intent-filter>
                <action android:name="MyAlarmAction"/>
            </intent-filter>
</receiver>
<service android:name="MyAlarmService"></service>
<activity android:name="AlarmNotificationActivity"></activity>
------------------------------------------------------------------------------------------------

以下の2行をmanifesstタグ直下に配置します。
これは、スリープ状態を解除するために必要な権限です。
------------------------------------------------------------------------------------------------ 
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
------------------------------------------------------------------------------------------------

【SampleActivity.java】
アラームをセットするために必要なのは以下の2行だけです。
実行する場所はどこでもいいと思います。ボタンを押したタイミングなど...

今回はテストのため簡略化しています。
便宜上引数を設けた方がいいと思います。
MyAlarmManager mam = new MyAlarmManager(this);
mam.addAlarm();

【MyAlarmManager.java】
import java.util.Calendar;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyAlarmManager {
    Context c;
    AlarmManager am;
    private PendingIntent mAlarmSender;
   
    public MyAlarmManager(Context c){
    this.c = c;
    am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
    Log.v("MyAlarmManger","初期化完了");
    }
   
    public void addAlarm(/*今はなにもなしで*/){
    mAlarmSender = PendingIntent.getService(c, -1, new Intent(c, MyAlarmService.class), PendingIntent.FLAG_UPDATE_CURRENT);
    // アラーム時間設定
    Calendar cal = Calendar.getInstance();
    cal.setTimeInMillis(System.currentTimeMillis());
    cal.add(Calendar.MINUTE, 1);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MILLISECOND, 0);
    Log.v("MyAlarmManagerログ",cal.getTimeInMillis()+"ms");
    am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), mAlarmSender);
    Log.v("MyAlarmManagerログ","アラームセット完了");
    }
}

【MyAlarmService.java】
Serviceを継承してます。

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyAlarmService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
    Log.v("MyAlarmServiceログ","Create");
    Thread thr = new Thread(null, mTask, "MyAlarmServiceThread");
    thr.start();
    Log.v("MyAlarmServiceログ","スレッド開始");
    }

    /**
     * アラームサービス
     */
    Runnable mTask = new Runnable() {
    public void run() {
        // ここでアラーム通知する前の処理など...

        Intent alarmBroadcast = new Intent();
        alarmBroadcast.setAction("MyAlarmAction");//独自のメッセージを送信します
        sendBroadcast(alarmBroadcast);
        Log.v("MyAlarmServiceログ","通知画面起動メッセージを送った");
        MyAlarmService.this.stopSelf();//サービスを止める
        Log.v("MyAlarmServiceログ","サービス停止");
    }
    };
}

【MyAlarmNotificationReceiver.java】
これは、サービスから送られて来たメッセージを受信する役割を担います。
今回はただ通知画面を起動するだけです。

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyAlarmNotificationReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
    Log.v("レシーバログ", "action: " + intent.getAction());
    Intent notification = new Intent(context,
        AlarmNotification.class);
    //ここがないと画面を起動できません
    notification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(notification);
    }

}

【AlarmNotificationActivity.java】
これが実際に表示されるアラーム通知画面です。
onStartでアラーム音を再生しています。
onDestroyでリソースを解放します。

import android.app.Activity;
import android.app.KeyguardManager;
import android.app.KeyguardManager.KeyguardLock;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;

public class AlarmNotification extends Activity {
    private WakeLock wakelock;
    private KeyguardLock keylock;

    MediaPlayer mp;

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.alarm);
    Log.v("通知ログ", "create");
    // スリープ状態から復帰する
    wakelock = ((PowerManager) getSystemService(Context.POWER_SERVICE))
        .newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
            | PowerManager.ACQUIRE_CAUSES_WAKEUP
            | PowerManager.ON_AFTER_RELEASE, "disableLock");
    wakelock.acquire();

    // スクリーンロックを解除する
    KeyguardManager keyguard = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    keylock = keyguard.newKeyguardLock("disableLock");
    keylock.disableKeyguard();
    }

    @Override
    public void onStart() {
    super.onStart();
    if (mp == null)
        mp = MediaPlayer.create(this, R.raw.alarm);
    mp.start();
    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    stopAndRelaese();
    }

    private void stopAndRelaese() {
    if (mp != null) {
        mp.stop();
        mp.release();
    }
    }
}


大まかにこんな感じでアラームが起動します。
必要に応じてコードは書き換えて下さい。
リソースファイルなどを用意していないと動きません。
コピペで動く保障はしません。

他にも手段はあります。
あくまでも一例としてみて頂ければと思います。

アドバイス、指摘などありましたらコメントをお願いいたします。

2012年1月19日木曜日

[Android]IntentでObjectをputExtraで渡す

既に題名に関する情報はいろんな方が公開しています。
自分のためにメモ。

Activityを新しく起動した際に、Intentを介してデータを渡したい、
こういったことがよくあると思います。

ふと、Object型を渡したいって思った時にやったことが無かったので
調べてみました。

最初にこのページを見つけて以下のヒントを得ました。
「Serializableインタフェースを実装することによって画面遷移でオブジェクトを渡すことが出来る」

ここで疑問に思ったのがなぜSerializableである必要があるのか?

まず、Serializableについて調べてみました。
http://d.hatena.ne.jp/daisuke-m/20100414/1271228333
http://www.ne.jp/asahi/hishidama/home/tech/java/serial.html

これらのページから分かることは、Serializableはオブジェクトを直列化、
つまりStreamにながせる状態にし、かつ、デシリアライズ可能であること。

文章がへたくそですみません。

消しゴム→ねりけし(シリアライズ)→(ストリート)→消しゴム(デシリアライズ)

的なイメージ?w
さらに分かりにくくなってすみませんorz

なぜ、Serializableでなければいけないのか、
内部的にどうやってデータを渡しているのか、
Androidのソースをダウンロードして解析してみたいと思います。

モチベーションが続いてたら、続く。

【追記】
Androidソースをダウンロードして探して見た!
詳細は不明だけど、IntentにputExtrasでセットしたデータは
ファイルに落とし込まれることは分かった。

ぜひ、どなたか詳しい方教えて欲しいですorz

「ファイルに落とし込むためにSerializableでなければならない」
という結論に至った。

詳しいことは依然として不明です...orz

2011年11月12日土曜日

AFreeChartを動的に動かす

AFreeChartに関する情報が少ないと感じたので少しでも役に立てれば..

【やりたいこと】
Androidアプリでグラフを表示したい。
しかも動的(リアルタイム的な)に動かしたい!
※)TimeSeriesのサンプルをベースとする。

グラフ表示ライブラリを探した所いくつかあると言うことがわかった。
直感的にAFreeChartが一番良さそうな感じがしたので使うことにした!

AFreeChartの導入方法に関しては他のブログで書いている方が
いらっしゃるので割愛します。

サンプルソースを実行すると静的なグラフを表示することは簡単にできます。
動的にするにはAFreeChartが保有しているDatasetにデータを追加してあげると
自動的に再描画される。

なので、最初に思いつく方法としては動的にDatasetを更新して行けばできる、
だとうという方法です。
例えば、別スレッドを用意して100msごとにDatasetを更新するようなプログラムを
作って動かすとあることに気付くと思います。データ数が増えるに連れて、
恐らくGCが高頻度で発生し、
描画に時間がかかりUIスレッドに負担がかかり、
ANRを引き起こす可能性があります。

結果的にDatasetの更新をトリガーとして再描画して動的に見せる方法では、
限界があるということになります。

デフォルトのサンプルアプリだと、生成したAFreeChartインスタンスに
ChartChangeListenerがセットされているので、Dataset更新すると再描画が発生する。
それを抑えるためにはBaseView(511行目前後)でリスナーセットする処理をコメントアウトするか、
リスナーを登録を削除するか、
しないとダメです。

そうすると、Datasetを更新しても再描画されることはなくなります。
この状態だと再描画がなくてリアルタイムに見えません。

現段階での私のソリューションとしては以下のような感じです。

①Dataset更新用スレッド、再描画用スレッドの2つを生成する
②それぞれをstartさせる
③再描画スレッドは定期的にHandlerに対してView#postInvalidateを呼び出すだけ

パッと思いついただけの方法なので、もっと効率のいい方法があったら
ご教授願いますm(_ _)m

再描画スレッドは以下のような感じです。(一部抜粋)
もちろんこのままでは動かないので適宜に補完してください^^;

repaintThread = new Thread(new Runnable() {
  public void run() {
   while (!stop) {
    if (pause)
     pause();
    mainHandler.post(new Runnable() {
     public void run() {
      // repaint();
      timeChartView.postInvalidate();
     }
    });
    try {
     Thread.sleep(interval);
    } catch (InterruptedException e) {
     // TODO 自動生成された catch ブロック
     e.printStackTrace();
    }
   }
  }
 });
 repaintThread.start();