Flex、AIR、Java、Androidなど

2月20日 2009

※追記有り【Android】端末を振ってオプションメニューを出す

Posted by: tachibana In: Android| プログラミング

2/22追記:またしてもeggさまに教えて頂いたのですが、センサの値を毎回比較するのは遅い&精度が悪いです。振られたことを検知したら一定時間の値を配列に貯めて、処理に振り分ける際に一度だけ比較した方がいいと思います。

2/20追記:eggさまにコメントで教えて頂き、振動の強弱で押下するボタンを振り分けることができるようになりました。弱く振るとメニュー、強く振るとbackボタンを押下するイベントを送信します。変更したのはShakableActivity.javaの真ん中くらいです。ありがとうございました。動画も追加しときます。

変更前:一定の強さ以上の振動を引き金に「メニューボタンが押された」イベントを送信

変更後:弱い振動でメニューボタン、強い振動でbackボタンを押下のイベントを送信

G1/Dev Phoneはメニューボタンが小さい上押しにくい位置にあり、メニューを何度も押すようなアプリケーションでは私はイライラしてしまっていました。

ので、端末を振ることで加速度センサに振動を感知させ、それでもって「押しにくいボタン押したこと」を代用するようなことができないかなと思いつき、色々見てるうちにKeyDownイベントをプログラム的に発生させることも、加速度で振動を検知することも可能なようなのでActivityを継承したShakableActivityというのを作ってみました。

加速度センサはGrandNatureさまAndroid – 加速度センサー再び

KeyEventの方はMy life with Android :-)さまGenerating keypresses programmatically

本当にありがとうございます。

ちなみに私が自分で書いたところはほぼありません(笑)。上述のお二人が書かれたソースを組み合わせただけです。

ちなみに
■ android.os.ServiceManager
■ android.view.IWindowManager

の二つはパブリックでないということですのでGenerating keypresses programmaticallyさまのとこから落とす必要があります。

それをパッケージ内に保存したらあとはActivityを継承したShakableActivityを書き、アプリケーションはActivityを継承するのではなくShakableActivityを継承させるだけです。

まあ人にもよると思いますが、

src
- android.os
- ServiceManager.java
- android.view
- IWindowManager.java
- com.example.ShakableActivity
- KeyDownGenerator.java
- ShakableActivity.java
- com.example
- 実際に利用するクラス群

というような感じになるのではないでしょうか。ShakavleActivityはKeyDownGeneratorのサブクラス、実際に利用するクラス群はShakableActivityのサブクラスです。

ソースは以下。

biz.stachibana.ShakableActivity.KeyDownGenerator.java

package biz.stachibana;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ServiceManager;
import android.view.IWindowManager;
import android.view.KeyEvent;

public class KeyDownGenerator extends Activity {
    Handler handler;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Thread t = new Thread() {
            public void run() {
                Looper.prepare();
                handler = new Handler();
                Looper.loop();
            }
        };
        t.start();
    }

    protected void generateMenuDown() {
        handler.post( new Runnable() {
            public void run() {
                IBinder wmbinder = ServiceManager.getService( "window" );
                IWindowManager wm =
                    IWindowManager.Stub.asInterface( wmbinder );
                keyUpDown( wm,KeyEvent.KEYCODE_MENU );
            }
        } );
    }

    protected void generateBackDown() {
        handler.post( new Runnable() {
            public void run() {
                IBinder wmbinder = ServiceManager.getService( "window" );
                IWindowManager wm =
                    IWindowManager.Stub.asInterface( wmbinder );
                keyUpDown( wm,KeyEvent.KEYCODE_BACK );
            }
        } );
    }

    protected void keyUpDown( IWindowManager wm, int keycode ) {
        wm.injectKeyEvent( new KeyEvent( KeyEvent.ACTION_DOWN, keycode ),true );
        wm.injectKeyEvent( new KeyEvent( KeyEvent.ACTION_UP, keycode ),true );
    }
}

biz.stachibana.ShakableActivity.ShakableActivity.java

package biz.stachibana.ShakableActivity;

import java.text.DecimalFormat;

import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

public class ShakableActivity extends KeyDownGenerator implements SensorListener {

    View layout;
    TextView accelerometerValue;
    TextView orientationValue;
    TextView filteredAccelerationValue;
    TextView filteredOrientationValue;
    TextView orientation;
    SensorManager sensorManager;
    static DecimalFormat format;
    static {
        format = new DecimalFormat();
        format.applyLocalizedPattern("#0.000");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // --- sensors
        sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    }

    @Override
    protected void onStop() {
        sensorManager.unregisterListener(this);
        super.onStop();
    }
    @Override
    protected void onResume() {
        super.onResume();
        maximumValue = 0; // 追加
        sensorManager.registerListener(this,
                SensorManager.SENSOR_ACCELEROMETER |
                SensorManager.SENSOR_ORIENTATION,
                SensorManager.SENSOR_DELAY_FASTEST);
    }
    private float[] currentOrientationValues = {0.0f, 0.0f, 0.0f};
    private float[] currentAccelerationValues = {0.0f, 0.0f, 0.0f};

    /** 追加 */
    boolean waitFlag = false;
    float maximumValue = 0;
    /** ここまで */

    public void onSensorChanged(int sensor, float[] values) {
        switch(sensor) {
        case SensorManager.SENSOR_ACCELEROMETER:
            // 傾き(ハイカット)
            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];
            // 振ってる? 絶対値(あるいは2乗の平方根)の合計がいくつ以上か?
            // 実装例
            float targetValue =
                Math.abs(currentAccelerationValues[0]) +
                Math.abs(currentAccelerationValues[1]) +
                Math.abs(currentAccelerationValues[2]);

            /* 変更前
            if(targetValue > 20.0f)
            {
                super.generateBackDown();
                sensorManager.unregisterListener(this);
                new Thread() {
                    public void run() {
                         try{
                              sleep(300);
                         } catch (Exception e) {  }
                         sensorManager.registerListener(ShakableActivity.this,
                                 SensorManager.SENSOR_ACCELEROMETER |
                                 SensorManager.SENSOR_ORIENTATION,
                                 SensorManager.SENSOR_DELAY_FASTEST);
                    }
               }.start();
            }
            else if(targetValue > 15.0f)
            {
                super.generateMenuDown();
                sensorManager.unregisterListener(this);
                new Thread() {
                    public void run() {
                         try{
                              sleep(300);
                         } catch (Exception e) {  }
                         sensorManager.registerListener(ShakableActivity.this,
                                 SensorManager.SENSOR_ACCELEROMETER |
                                 SensorManager.SENSOR_ORIENTATION,
                                 SensorManager.SENSOR_DELAY_FASTEST);
                    }
               }.start();
            }
            */

            /** 変更後 */
            Handler processHandler = new Handler();
            Runnable processRunnable = new Runnable(){
                public void run() {
                waitFlag = false;
            }};

            if(targetValue > 12.0f)
            {
                if(!waitFlag)
                {
                    if(maximumValue < targetValue)
                    {
                        maximumValue = targetValue;
                    }
                    else
                    {
                        if(maximumValue > 20.0f)
                        {
                            super.generateBackDown();
                        }
                        else
                        {
                            super.generateMenuDown();
                        }
                        waitFlag = true;
                        maximumValue = 0;
                        processHandler.postDelayed(processRunnable, 300);
                    }
                }
            }
            default:
        }
    }

    public void onAccuracyChanged(int sensor, int accuracy) {

    }

    static final int MENU_ADD  = 0, MENU_REMOVE = 1, MENU_HELP = 2, MENU_ABOUT = 3, MENU_TO_TOP = 4;

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        super.onCreateOptionsMenu(menu);
        menu.add(Menu.NONE, MENU_ADD, Menu.NONE, "追加")
        .setIcon(android.R.drawable.ic_menu_add);
        menu.add(Menu.NONE, MENU_HELP, Menu.NONE, "使い方")
        .setIcon(android.R.drawable.ic_menu_help);
        menu.add(Menu.NONE, MENU_TO_TOP, Menu.NONE, "トップへ")
        .setIcon(android.R.drawable.ic_menu_revert);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case MENU_ADD:
                //popUpDialog(this, "ブログの追加" , MENU_ADD);
                break;
            case MENU_HELP:
                //popUpDialog(this, "使い方" , MENU_HELP);
                break;
            case MENU_TO_TOP:
                //popUpDialog(this, "トップへ" , MENU_TO_TOP);
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}

これだけです。貴重な情報を提供して下さっている方々に感謝です。

あとは、加速度センサを始めてさわったのでコツが分からないのですが、上記ShakableActivity.javaの中ほど、コメントアウトしているような形で強く振るとメニューボタン、弱く振るとバックボタンというような感じで振り分けようと思ったのですが、センサが渡す値が徐々に上がり、徐々に下がる為、ボタンが連続で押されるのを防ぐために入れたThreadとの兼ね合いもあり、うまくできませんでした。

まあそこらも踏まえ汚いソースですから、どなたか色々とうまくやるコツを教えてくださいm(_ _)m そしてそれを公開して下さい(笑) 私はDev Phoneを使っている限りこの振動でボタンを代用する機能は使います。片手だとそれが一番効率がよいように感じますし。

また、これをServiceとIntentで作ることが出来ればどのアプリでも共通のこと(強く振るとBack、弱く振るとMenuとか)ができるのでしょうか?

そうなると使い勝手がかなり上がると思いますのでどなたかお願いします(笑) 私はやりません(出来ません) :-)

EasyFreeAds Blog News Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google Yahoo Buzz StumbleUpon

9 Responses to "※追記有り【Android】端末を振ってオプションメニューを出す"

1 | egg

2月20日 2009 at 10:42 AM

Avatar

Threadを使わずに、例えば

boolean waitFlag;
Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
waitFlag = false;
}
};
・・・
if(targetValue > 15.0f) {
if(!waitFlag) {
waitFlag = true;
generateMenuDown();
handler.postDelayed(runnable, 300);
}
・・・

のような感じではどうでしょうか。

2 | tachibana

2月20日 2009 at 12:13 PM

Avatar

コメントありがとうございます。恐縮です。

手元に開発環境がないので全く検討違いなことをかいているかもしれないのですが、ご教授下さったThreadを使わないパターンを使わせていただいて、

boolean waitFlag = false;
Handler handler = new Handler();
Runnable runnable = new Runnable()
{
public void run() {
waitFlag = false;
}
};
float maximumValue = 0;
if(targetValue > 15.0f) // 15.0以上の値が渡されたら
{
if(!waitFlag)
{
if(maximumValue < targetValue) // センサに渡された値が上昇中なら
{
maximumValue = targetValue; // 最大値を更新
}
else // targetValueが下降し始めた時
{
waitFlag = true;

if(maximumValue > 25.0f) // 一回の振りが渡したセンサの最大値が25.0f以上なら
{
generateBackDown(); // backボタンを押下
}
else // 最大値が15.0f~25.0fなら
{
generateMenuDown(); // menuボタンを押下
}
maximumValue = 0; // 一時的に格納していた値をリリース

handler.postDelayed(runnable, 300);
}
}
}

というような形で振りが渡す値の曲線が最大値に達する前に触れ幅が弱い状態の処理が行われ、Threadによりその後の処理がブロックされてしまうことは無くなるのかな、と思っています。

でもこれだとセンサが通知するたびにif文を実行するので重くなったりしないのでしょうか?

何か良い方法があればお時間のある時にでも是非教えてください。

すごくいい勉強になります。

3 | egg

2月21日 2009 at 4:00 PM

Avatar

そうなんですよね。
Androidのアプリは基本単一のスレッドで全て動いているので、それがわかりやすい点なのですが、遅いとアプリ全体の動きがもっさりするので、むずかしいですよね。
processHandlerやprocessRunnableはセンサーのメソッドの中で作ると遅くなるのではないでしょうか。
外にstaticで置いといて一回だけ生成して、あとは同じオブジェクトを使い回す感じでいけると思います。
(HandlerはUIと同じスレッド上なので同期や排他を気にしなくてもいいです。)

あと、強く振ってるか弱く振ってるかは、前回との比較だけだとうまくいかない事が多いので
一定時間の値を配列とかにためて判断した方がいいと思います。そうするとセンサーのメソッドの中で毎回比較するわけじゃなくなり、ステップが減るので結果的に結構速くなるんじゃないかと思います。

4 | tachibana

2月22日 2009 at 1:11 PM

Avatar

こんにちは。ご指摘ありがとうございます。

> あと、強く振ってるか弱く振ってるかは、前回との比較だけだとうまくいかない事が多いので
おっしゃる通り、時々精度が悪いなと感じることはありました。

ArrayList valueArray = new ArrayList();
if(targetValue > 12.0f)
{
if(!waitFlag)
{
valueArray.clear();
valueArray.add(targetValue);
waitFlag = true;
processHandler.postDelayed(processRunnable, 200);
}
else
{
valueArray.add(targetValue);
}
}

とやって、遅れて発生させたprocessRunnableでvalueArrayの最大値を取って振り分けてやってみましたが、こっちの方が精度もいいですしスマートですね。

ありがとうございます(^^)

6 | schetovodstvo

9月26日 2013 at 6:40 PM

Avatar

Hey! Quick question that’s completely off topic. Do you know how to make your
site mobile friendly? My blog looks weird when viewing from my
iphone4. I’m trying to find a theme or plugin that might be able to fix this problem.
If you have any suggestions, please share. Thanks!

7 | lainaa autoa vastaan heti

2月9日 2014 at 8:11 AM

Avatar

Hello my family member! I want to say that this post is amazing, nice written and come with approximately all important
infos. I’d like to look extra posts like
this .

8 | list.ly

4月18日 2014 at 4:49 AM

Avatar

Hurrah! In the end I got a blog from where I can genuinely
obtain valuable facts concerning my study and knowledge.

9 | Counter Depth refrigerators

4月24日 2014 at 12:10 AM

Avatar

Have you ever considered about adding a little bit more than just your articles?
I mean, what you say is important and everything. Nevertheless think of if you added some great pictures or videos to give your posts more, “pop”!
Your content is excellent but with images and videos, this blog
could undeniably be one of the very best in its field.
Amazing blog!

Categories

 

2017年12月
« 4月    
 123
45678910
11121314151617
18192021222324
25262728293031

About

Author: tachibana

  • ちょっとしたことはTwitterに書いています。こっちはアプリの公開等の時に更新されます。
  • 最近はもっぱらJavaとObjective Cです。AS3は飽きました。
  • スクリプト言語ではPerlが好きでしたが最近はGAE/Jで何でもやってます。
  • Linuxは自宅サーバー建てるのがやっとのレベルです。前の会社で何日も徹夜してやったのはいい思い出です。
  • アプリへのご要望などご意見等ありましたらお気軽にご連絡下さい。

Alternative content here