int maxFaces = 10;// 検出する顔の最大数 FaceDetector detector = new FaceDetector(bitmap.getWidth(), bitmap.getHeight(), maxFaces); FaceDetector.Face[] faces = new FaceDetector.Face[maxFaces]; ArrayList<Bitmap> recognized = new ArrayList<Bitmap>(); // bitmapの中から顔を検出してfacesに格納する int num = detector.findFaces(bitmap, faces); // facesに格納されたデータから座標情報を取り出してbitmapを切り出す for (int i = 0; i < num; i++) { FaceDetector.Face face = faces[i]; PointF point = new PointF(0.0f, 0.0f); face.getMidPoint(point); int x = (int) (point.x - 1.5f * face.eyesDistance()); int y = (int) (point.y - 2 * face.eyesDistance()); int width = (int) (face.eyesDistance() * 3); int height = (int) (face.eyesDistance() * 4); x = (x < 0)? 0 : x; y = (y < 0)? 0 : y; width = (width > bitmap.getWidth())? bitmap.getWidth() : width; height = (height > bitmap.getHeight())? bitmap.getHeight() : height; recognized.add(Bitmap.createBitmap(bitmap, x, y, width, height)); } return recognized;
月別アーカイブ: 2011年12月
androidでprivateメソッドのテストをする
そういえば書いてなかったのでメモ。
/** * Hogeクラスのprivateメソッドadd(int a, int b)をテストする * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws InvocationTargetException */ public void testAdd() throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Hoge hoge = new Hoge(); Class<Hoge> clz = Hoge.class; Method method = clz.getDeclaredMethod("add", Integer.class, Integer.class); method.setAccessible(true); int result = (int) method.invoke(hoge, 5, 3); asserTrue(result == 8); }
■staticメソッド
/** * Hogeクラスのprivate staticメソッドadd(int a, int b)をテストする * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchMethodException * @throws IllegalAccessException * @throws InvocationTargetException */ public void testAdd() throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Class<Hoge> clz = Hoge.class; Method method = clz.getDeclaredMethod("add", Integer.class, Integer.class); method.setAccessible(true); int result = (int) method.invoke(clz, 5, 3); asserTrue(result == 8); }
■プライベートフィールド
Hoge hoge = new Hoge(); Field f = hoge.getClass().getDeclaredField("f"); f.setAccessible(true); assertEquals(value, f.get(hoge)); //assertEquals(Piyo.class, f.get(hoge).getClass());
AndroidTestCaseでテストアプリ側のContextを取得する
メモ。
public class AndroidExtendedTestCase extends AndroidTestCase { public Context getTextContext() throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Class<? extends AndroidExtendedTestCase> clz = (Class<? extends AndroidExtendedTestCase>) this.getClass(); Method method = clz.getMethod("getTestContext"); Context context = (Context) method.invoke(this); return context; } }
参考
androidでArrayAdapterを使う
■実装
mListView = (ListView) findViewById(R.id.list); mListView.setAdapter(mOriginalArrayAdapter);
mOriginalArrayAdapterを生成するとする。
public class OriginalArrayAdapter extends ArrayAdapter<OriginalDataRow> { private LayoutInflater inflater; /** * セル内における可変要素 */ public static class ViewHolder { public TextView text; } public OriginalArrayAdapter(Context context, int textViewResourceId, ArrayList<OriginalDataRow> data) { super(context, textViewResourceId, data); inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public View getView(int position, View convertView, ViewGroup parent) { final OriginalDataRow data = getItem(position);// フィールドなどから取得してはいけない...(a) // viewのセットアップ ViewHolder holder = null; if (convertView == null){ holder = new ViewHolder(); convertView = inflater.inflate(R.layout.cell_original_data, null); holder.text = (TextView) convertView.findViewById(R.id.text); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // dataをviewに書き出す holder.text.setText(data.text); return convertView; } }
filterを使用する
@Override public void onTextChanged(CharSequence s, int start, int count, int after) { String query = mEditText.getText().toString(); if (query.equals("")) { mListView.clearTextFilter(); } else { mListView.setFilterText(query); } }
(a)の部分でsuperクラスのgetItemを使用しないとフィルタリングが上手く適用されない。また、filterを使用する場合は、リストアイテムのtoString値が使用されるため、以下のように必要に応じてOverrideする必要がある。
public class OriginalDataRow { public String name; @Override public String toString() { return name;// filterに使用する文字列 } }
CakePHP 2.0.4で多言語対応する
■手順
アプリケーションのrootディレクトリで以下のコマンドを実行する。
php lib/Cake/Console/cake.php i18n
以下のように表示される。
Welcome to CakePHP v2.0.4 Console --------------------------------------------------------------- App : app Path: /var/www/domain/app/ --------------------------------------------------------------- I18n Shell --------------------------------------------------------------- [E]xtract POT file from sources [I]nitialize i18n database table [H]elp [Q]uit What would you like to do? (E/I/H/Q)
Eを選択する。
What is the path you would like to extract? [Q]uit [D]one [/var/www/domain/app/] >
Dを選択する。
What is the path you would like to output? [Q]uit [/var/www/domain/app//Locale] > Would you like to merge all domains strings into the default.pot file? (y/n)
yを選択すると、ファイルを読み込んでデフォルトの言語ファイルが生成される。
- /var/www/domain/app/Locale/default.pot
- /var/www/domain/app/Locale/eng
/var/www/domain/app/Locale/engをコピーして/var/www/domain/app/Locale/jpnを作り、/var/www/domain/app/Locale/default.potをコピーして、/var/www/domain/app/Locale/jpn/LC_MESSAGES/default.poを作る。この時、拡張子を変更しないと上手く動作しない。
翻訳ファイル
翻訳ファイルは以下のように記述する。
#: Controller/HogeController.php:65;166;188;214 msgid "Hello world" msgstr "世界こんにちわ"
use -keepclasseswithmembers instead of -keepclasseswithmembernames
SDKをアップデートしたら以下のようなエラーが出た。
Obsolete proguard file; use -keepclasseswithmembers instead of -keepclasseswithmembernames proguard.cfg /com.example line 1 Android Lint Problem
■対策
proguard.cfgをテキストエディタで開くと-keepclasseswithmembersというフラグが(3箇所くらい)あるので-keepclasseswithmembernamesに修正する。Window > Show View > Android > Lint Warnings のウィンドウのrefreshボタンを押す。
androidでnotificationを使う
めも。備忘録。
■実装
以下のようにして実装する。
package info.justoneplanet.android.sample.notification; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; public class MainActivity extends Activity { public static final int NOTIFICATION_REQUEST_CODE = 100; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); setNotification(); } /** * 通知のセットアップ */ private void setNotification() { NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Notification notification = new Notification( R.drawable.ic_launcher,// 表示するアイコン getString(R.string.hello),// ステータスバーに表示するテキスト System.currentTimeMillis()// 表示する時間 ); // ペンディングできるIntentの入れ物 PendingIntent intent = PendingIntent.getActivity( this, NOTIFICATION_REQUEST_CODE, new Intent(this, NotificationActivity.class), Intent.FLAG_ACTIVITY_NEW_TASK ); // お知らせをクリックした時に起動するActivityを指定する notification.setLatestEventInfo( this, getString(R.string.app_name2),// 通知を開いた時のタイトル getString(R.string.hello2),// 通知を開いた時の本文 intent ); notification.vibrate = new long[]{0, 200, 100, 200, 100, 200, 100, 200, 100, 200, 100, 200}; notificationManager.notify(R.string.app_name, notification);// 通知のID } }
このように実装する事はまずないだろう。
Alarmと組み合わせる
MainActivity.java
PendingIntent.getBroadcastで生生したBroadcastReceicerをAlarmManagerに結びつける。
package info.justoneplanet.android.sample.notification; import java.util.Date; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; public class MainActivity extends Activity { public static final int NOTIFICATION_BROADCAST_REQUEST_CODE = 100; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.setRepeating( AlarmManager.RTC_WAKEUP, new Date().getTime() + 1000,// 1秒後 AlarmManager.INTERVAL_DAY,// 1日毎 PendingIntent.getBroadcast( this, NOTIFICATION_BROADCAST_REQUEST_CODE, new Intent(this, NotificationReceiver.class), Intent.FLAG_ACTIVITY_NEW_TASK ) ); } }
NotificationReceiver.java
broadcast receiver。contextが引き数で受け取れるのでActivityの時と同じ使い勝手でnotificationをセットアップする事ができる。
package info.justoneplanet.android.sample.notification; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public class NotificationReceiver extends BroadcastReceiver { @SuppressWarnings("unused") private static final String TAG = NotificationReceiver.class.getSimpleName(); @SuppressWarnings("unused") private final NotificationReceiver self = this; public static final int NOTIFICATION_REQUEST_CODE = 100; @Override public void onReceive(Context context, Intent intent) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification( R.drawable.ic_launcher,// 表示するアイコン context.getString(R.string.hello),// ステータスバーに表示するテキスト System.currentTimeMillis()// 表示する時間 ); // ペンディングできるIntentの入れ物 PendingIntent pendingIntent = PendingIntent.getActivity( context, NOTIFICATION_REQUEST_CODE, new Intent(context, NotificationActivity.class), 0 ); // お知らせをクリックした時に起動するActivityを指定する notification.setLatestEventInfo( context, context.getString(R.string.app_name2),// 通知を開いた時のタイトル context.getString(R.string.hello2),// 通知を開いた時の本文 pendingIntent ); notification.vibrate = new long[]{0, 200, 100, 200, 100, 200, 100, 200, 100, 200, 100, 200}; notificationManager.notify(R.string.app_name, notification); } }
manifest
以下のように正しくreceiverを設定しないと上手く動作しない。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="info.justoneplanet.android.sample.notification" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="14" /> <uses-permission android:name="android.permission.VIBRATE" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <receiver android:name=".NotificationReceiver" android:process=":remote" /> <activity android:label="@string/app_name" android:name=".MainActivity" > <intent-filter > <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:label="@string/app_name" android:name=".NotificationActivity" > </activity> </application> </manifest>
ServiceもBroadcastReceiverと組み合わせてnotificationを出せるし、C2DMもBroadcastReceiverなので同様である。
notificationの削除
起動されたActivity側で以下のようにして起動元別にキャンセル処理を挟む必要がある。
Intent intent = getIntent(); String origin = intent.getStringExtra("origin"); if (origin != null && origin.equals("notification")) { NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.cancel(R.string.app_name); }
androidでdraggableなviewを作る
■実装
DraggableView.java
package info.justoneplanet.android.sample.drag; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class DraggableView extends View { @SuppressWarnings("unused") private static final String TAG = DraggableView.class.getSimpleName(); private final DraggableView self = this; private int offsetX; private int offsetY; private int currentX; private int currentY; public DraggableView(Context context) { super(context); } public DraggableView(Context context, AttributeSet attrs) { super(context, attrs); } public DraggableView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getRawX();// touch eventが発生したx座標 int y = (int) event.getRawY();// touch eventが発生したy座標 /* (2) 1つ前の値であるoffsetから * 現在ドラッグしている座標を引いて差を出し * 現在の座標に加算する * 最後にoffset値を更新する */ if (event.getAction() == MotionEvent.ACTION_MOVE) { Log.d("ACTION_MOVE", "ACTION_MOVE"); int diffX = x - offsetX; int diffY = y - offsetY; currentX = currentX + diffX; currentY = currentY + diffY; layout(currentX, currentY, currentX + getWidth(), currentY + getHeight()); offsetX = x; offsetY = y; } //...(1) タッチした時の値を初期値にする else if (event.getAction() == MotionEvent.ACTION_DOWN) { Log.d("ACTION_DOWN", "ACTION_DOWN"); offsetX = x; offsetY = y; } else if (event.getAction() == MotionEvent.ACTION_UP) { Log.d("ACTION_UP", "ACTION_UP"); } return true; } }
ACTION_MOVEの方が実行回数が大きいので先に記述する。
main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <info.justoneplanet.android.sample.drag.DraggableView android:layout_width="100dip" android:layout_height="100dip" android:background="#ff00ff00"> </info.justoneplanet.android.sample.drag.DraggableView> </FrameLayout>
MainActivity.javaは特に何も変更していない。Viewなので継承して使える。ImageViewとかでも同じように実装できる。