シャカシャカシェイクリスナー

Standard
シャカシャカシェイクリスナー

「Android端末をフンッ!って振ったら、今いる場所の住所をtwitterでつぶやく」っていう機能を実装したときのメモ。GPSから逆ジオコーディングとかjavaのtwitterライブラリ、twitter4jの使い方とかはサンプルがたくさんあるので他を参照。SensorEventListenerを使った、「端末がシェイクされた」っていうのの検知部分をメモっとくばいー
 
 
物理わからん
 
「android 加速度センサー シェイク」とかでググって出てきたのがここらへん。
http://www.grandnature.net/blog/archives/2009/02/android_2.html
http://andronavi.com/2010/03/15069
 
ハイカットとローカットとかCONVERSEしか思いつかないので、先人の知恵をほぼ流用させてもらうことにした。あと、いい歳した大人が夜な夜な携帯を振りまくって得たという謎の計算式も使っちゃう。Sensor.TYPE_ACCELEROMETER使って取得したx,y,zそれぞれの軸に対する端末の傾き具合座標のイベント発生前後の差がある一定の値を超えたら、「振られた」って判断してるってことだと思う。
 
ということで「振られた」を判断するためのクラスを↓みたいに書いた。
ShakeDeterminator.java

package com.bugcloud.android.touchme.util;

import android.hardware.SensorEvent;

public class ShakeDeterminator {

    private float[] currentOrientationValues = {0.0f, 0.0f, 0.0f};
    private float[] currentAccelerationValues = {0.0f, 0.0f, 0.0f};
    private static final int LEVEL = 2;

    public boolean isShake(SensorEvent event) {
        float shakeLevel = 30 + (8 * (float)LEVEL);
        boolean result = false;

        float values[] = event.values;
        currentOrientationValues[0] = values[0] * 0.1f + currentOrientationValues[0] * (1.0f - 0.1f);
        currentOrientationValues[1] = values[1] * 0.1f + currentOrientationValues[1] * (1.0f - 0.1f);
        currentOrientationValues[2] = values[2] * 0.1f + currentOrientationValues[2] * (1.0f - 0.1f);
        currentAccelerationValues[0] = values[0] - currentOrientationValues[0];
        currentAccelerationValues[1] = values[1] - currentOrientationValues[1];
        currentAccelerationValues[2] = values[2] - currentOrientationValues[2];

        float targetValue = Math.abs(currentAccelerationValues[0]) + 
                                    Math.abs(currentAccelerationValues[1]) +
                                    Math.abs(currentAccelerationValues[2]);
        if (targetValue > shakeLevel) {
            result = true;
        }
        return result;
    }
}

 
ただコレだと、端末の傾きしか見てないので正確には「振る」を検知できない気がする。端末の傾きを変えないまま上下に動かすときとか。端末の直線的な動きしか見てないので正確には「振る」を検知できない気がする。端末の位置を変えないままブンブンやるときとか。「Androidって振るときにコツがいるんですよー」で逃げれそうな気配しかしないので今回は深追いしません。。。。。。
 
上のクラスを使ったActivityが↓な感じ
 

package com.bugcloud.android.touchme;

import java.util.List;

import com.bugcloud.android.touchme.util.ShakeDeterminator;

import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.Toast;

public class TouchMe extends Activity implements SensorEventListener {
    
    private boolean registeredSensor;
    private SensorManager sensorManager;
    
    private ShakeDeterminator shakeDeterminator;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        registeredSensor = false;
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        shakeDeterminator = new ShakeDeterminator();
    }
    
    @Override
    protected void onResume()
    {
        super.onResume();
        
        List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
        if (sensors.size() > 0) {
            Sensor sensor = sensors.get(0);
            registeredSensor = sensorManager.registerListener(this,
                                                                sensor,
                                                                SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

    
    @Override
    protected void onPause()
    {
        if (registeredSensor) {
            sensorManager.unregisterListener(this);
            registeredSensor = false;
        }
        
        super.onPause();
    }

    // Show Toast Message
    private void show(String message)
    {
        Toast.makeText(
            this,
            message,
            Toast.LENGTH_SHORT
        ).show();
    }

    public void onAccuracyChanged(Sensor arg0, int arg1) {
        // TODO Auto-generated method stub
        
    }

    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            if(shakeDeterminator.isShake(event)) {
                show("加速度センサーが反応した");
            }
        }
    }
}

 
shakeDeterminator.isShake(event)の戻り値がTRUEだったときにやる処理を別スレッドでやったりするときは、クラス変数にそのスレッドが動いてるかどうかのフラグでも持っといてやんないとスレッドめっちゃ立っちゃう気がします。
ShakeDeterminator.javaのLEVELはセンサーの感度、sensorManager.registerListener()の第3引数はセンサーイベントの検知頻度。実機で試した結果、LEVEL=2で、頻度はSensorManager.SENSOR_DELAY_NORMALっていうが今回の場合はちょうどよかった。
 
 
Activity殺害事件
 
Androidは画面の縦/横でレイアウトを変えたりできるわけですが、この時にActivityは一回死んでもっかいonCreate()が走る。らしい。ので、この「振る」ときにActivityがぶっ生き返しまくる。これを回避するには画面の方向を固定してやればよかと。AndroidManifest.xmlのactivityにandroid:screenOrientation属性を追加してやる。
 
AndroidManifest.xml

< ?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.bugcloud.android.touchme"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".TouchMe"
                  android:label="@string/app_name"
                   android:screenOrientation="nosensor">
            <intent -filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent>
        </activity>
    </application>
    <uses -sdk android:minSdkVersion="4" />
</manifest> 

 
 
振ると揺れるおっぱいシミュレータをtechnohippyさんより先に作ろうと思う。

早起きしすぎた

Standard
早起きしすぎた

ちょっと昼寝のつもりが12時間ぐらい爆睡してしまったので、今日は4時すぎから起きとります。
 
せっかく早起きしたので、こないだ寝る寸前に思いついたAndroidアプリを作ってみました。Androidケータイ持ってないので実機でテストできません。ヒマな人、貸してください。あとデザイン変えたいのでムフフな絵を描いてくれる人いませんか?Android Marketで公開できたあかつきには、売上の半分を渡します。ちなみにアプリは50円にする予定です。開発中のベータ版アプリはこれです。XperiaとかDesireとか持ってる人はインストールしてみてください。そしてキャプチャを送ってください。背景が伸びまくってる予感しかしません。 → あぽり
以下がいよー
 
 
アプリ名:考え中
アプリ説明:カラダを触るといろんな声を出すよ。触る場所と触り方で声の種類も変わるよ。
カテゴリ:foolish
アプリキャプチャ:
screenshot

CAP 001

screenshot

CAP 002

備考:
   Picture from テケ
   Sounds from あみたろ
 
 
 
「こする」の検知がよくわからん
 
「カラダが触られた」っていうのは、透明のボタンを配置しておいて、そのボタンにリスナーを設定してます。「タッチされた」っていうのはOnClickListenerで

((Button)findViewById(R.id.btn)).setOnClickListener(new OnClickListener() {
    public void onClick(View v) {
        show("どんたっちみー");
    }
});

みたいなことをやればいいと思うんだけど、「こすられた」っていうのはどう判断すればいいのかよくわからんです。いまはOnTouchListenerを設定しといて、android.view.MotionEvent.getAction() で「タッチされた部分が動かされたら」みたいなことをやってます。ただこれをエミュレータ上で試すと何回もイベントが発生してるような。。実機でやってみたら違ったりする系のノリですか?

((Button)findViewById(R.id.btn)).setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_MOVE:
                show("こすってんじゃないわよ");
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
        }
    return false;
    }
});

 
 
 
先週から仕事でも軽いAndroidアプリを作ってるんですが、学んだ教訓がひとつ。
安易な気持ちでTableLayoutを使わない
です。
htmlと同じ勢いでテーブル使ってるとレイアウトの調整に苦しむ気がしました。LinearLayout使って、横に並べたいときはandroid:orientation=”horizontal”、縦はandroid:orientation=”vertical”を組み合わせてったほうが楽に調整できる気がします。あと、android:layout_widthとandroid:layout_heightにはwrap_contentとかfill_parentじゃなくて”?px”とか”?sp”が使えることに気づくとグッと調整が早くなりました。
上の開発中アプリで使ってるレイアウトのxmlは↓な感じです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#fefefe"
    android:scrollbars="vertical"
    android:scrollbarSize="18px"
    android:scrollbarStyle="outsideInset">
<ScrollView android:layout_width="fill_parent" android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/back001">
    <Button android:text=""
            android:id="@+id/head_move"
            android:layout_height="26px"
            android:layout_width="wrap_content"
            android:layout_marginTop="60sp"
            android:layout_gravity="center"
            android:gravity="center"
            android:minWidth="200sp"
            android:background="@drawable/alpha"/>
    <Button android:text=""
            android:id="@+id/head"
            android:layout_height="50sp"
            android:layout_width="50sp"
            android:layout_marginTop="0sp"
            android:layout_gravity="center"
            android:gravity="center"
            android:background="@drawable/alpha"/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_marginTop="140sp"
    android:gravity="center">
    <Button android:text=""
            android:id="@+id/beachLeft"
            android:layout_height="50px"
            android:layout_width="50px"
            android:layout_gravity="left"
            android:layout_marginRight="25sp"
            android:gravity="center"
            android:background="@drawable/alpha"/>
    <Button android:text=""
            android:id="@+id/beachRight"
            android:layout_height="50px"
            android:layout_width="50px"
            android:layout_gravity="left"
            android:gravity="center"
            android:background="@drawable/alpha"/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_marginTop="50sp"
    android:gravity="center">
    <Button android:text=""
            android:id="@+id/heso"
            android:layout_height="50px"
            android:layout_width="30px"
            android:layout_gravity="center"
            android:gravity="center"
            android:background="@drawable/alpha"/>
</LinearLayout>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_marginTop="10sp"
    android:gravity="center">
    <Button android:text=""
            android:id="@+id/xxx"
            android:layout_height="50px"
            android:layout_width="30px"
            android:layout_gravity="center"
            android:gravity="center"
            android:background="@drawable/alpha"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

 
 
 
もう少しタッチできる部分を増やしたらいったん公開して、Version2.0とかで音を設定できるようにすればいいかなーと思ってます。
 
追記
会社の人に教えてもらったけど、MotionEvent.ACTION_MOVEのところは、OnTouchListenerが動くたびに反応してるので上だと反応しまくってると。なので、別に「こする」検知用のクラスを用意して、eventを渡すメソッド作ればよいみたいです。受けとった側でしきい値を超えたら、「こすられた」って判断してやればいいと。数学とかもうすっかり忘れたけど、ピタゴラスの定理とか使えばいんじゃね?
 
 

Cover image is not found
amazon.co.jpで詳細情報を見る


ストーキングやばい

Standard
ストーキングやばい

仕事でAndroid端末から位置情報をサーバに送信して、その情報をマップ上に表示するっていうシステムを作りました、こないだ。テストがおもしろくてやばかったです。Androidアプリを起動してバスに乗ってもらって渋谷区界隈をグルッと回ってもらいました。ボクらは会社からマップ見ながらどんぐらいズレてるかを確認してたわけなんですが、意外とそんなにズレなくて、Androidが衛星をつかまえれてさえいれば、どこにいるか世界まる見えなわけですよ。なんていうかこう、他人の私生活のぞき見感満載な気分を味わえました。浮気調査の依頼はぜひウチにですよ奥さん。テスト終わったっつってキャバクラに直行してくれたらおもしろかったのになー 
 
 
マップの描画はGoogle Map APIのVersion3を使いました。マップ上にルートを描画するDirectionsサービスを初めて使ったわけですが、賢さにビックリしてオシッコちょっと出そうになったのでメモ。Travel Modesやばしなのでそれについて。
 
Directionsサービスを利用するときにリクエストパラメータに入れないといけないTravel Modes。指定できるのは以下の3つ。

  • DirectionsTravelMode.DRIVING → 車
  • DirectionsTravelMode.WALKING → 歩き
  • DirectionsTravelMode.BICYLING → チャリ

 
DirectionsTravelMode.BICYLINGについてはVersion3から追加されたモードで今のところアメリカの地図でしか使えない。DRIVINGモード使ったら一通とかちゃんと避けて線が引かれたから会社で思わず「Uho」って言ってしまった。Google携帯にはGoogleカーナビが標準装備されててもいいと思った。
 
サンプルはこんな感じ。モードを選ぶと鶴ヶ峰駅か横浜駅までのルートを描画するよ。途中で三ツ沢公園を経由するばいね。いんたーねっとえくすぷろーらーで見れるのか知りませーん
  サンポー
 
ソース

<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<head>
<title>Google Map Directions Sample</title>
<style>
html {
  height: 100%;
}
body {
  height:100%;
  margin:20px;
  padding:0px;
}
#map {
  height:90%;
  width:90%;
}
#selector {
  margin-top:15px;
}
#select_walk {
  margin:0px 20px 0px 5px;
}
#select_drive {
  margin:0px;
}
</style>
<script src="http://www.google.com/jsapi"></script>
<script>google.load("jquery","1.4.2");</script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script>
$(function() {
  var otsuru = new google.maps.LatLng(35.47469,139.549029);
  var op = {
    zoom:18,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    center: otsuru
  }
  map = new google.maps.Map(document.getElementById("map"), op);
  $('#select_walk,#select_drive').bind('click', function() {
    var directionsService = new google.maps.DirectionsService();
    var directionDisplay = new google.maps.DirectionsRenderer();
    var hama = new google.maps.LatLng(35.466464,139.620802);
    var request = {
      origin:otsuru,
      destination:hama,
      waypoints:[{
        location:'35.4705,139.606876',
        stopover:true
      }],
      travelMode: ($(this).attr('id')=='select_walk')? google.maps.DirectionsTravelMode.WALKING : google.maps.DirectionsTravelMode.DRIVING
    };
    directionsService.route(request, function(result, status) {
      if(status == google.maps.DirectionsStatus.OK) {
        directionDisplay.setDirections(result);
      }
    });
    map = new google.maps.Map(document.getElementById("map"), op);
    directionDisplay.setMap(map);
  });
});
</script>
</head>
<div id="map"></div>
<div id="selector">
<span id="select_walk"><a href="#">横浜まで歩く</a></span>
<span id="select_drive"><a href="#">横浜まで車で行く</a></span>
</div>
<body>
</body>
</html>

緯度経度を知りたいときは、Googleマップで知りたい場所を右クリック→「この場所について」で取得できるよ。
 
 
マイミクをストーキングできるmixiアプリを作ろうかな。
 
 

Cover image is not found
amazon.co.jpで詳細情報を見る


Rails 3 が動かない。

Standard
Rails 3 が動かない。
  • `‘: undefined method `default_external=’ for Encoding:Class (NoMethodError)

って言われます。ググると当該部分をコメントアウトしろって書いてあるけど、どこかわからなかった。泣きたい。