1  2  3  4  5  6  7  8  9  10  11

記事一覧

2011年12月27日

日本語版WTL 8.1.11324 アプリケーションウィザード

wtl001.png

■はじめに

WTLはライセンスがCPLのオープンソースプロジェクトとして公開されている。そのためおそらく勝手に日本語化したものを配布しても問題ないと思い用意しました。ライセンス的に問題があるようでしたらお知らせください。削除します。

リボンやタブビュー、ツリービューなどが扱えます。
リボン対応のプロジェクトのビルドにはWindows 7.1 SDKが必要です。


■ダウンロード

日本語版WTL 8.1.11324をダウンロード

(2011年12月27日現在最新版のWTLを元にしています)


2011年01月27日

HTC DesireのフラッシュLEDをアプリから制御する

test132_01.png
今回はHTC Desireに備わっているLED(カメラ撮影用フラッシュ)をネイティブから制御してON/OFFしてみる。

まずはHTC Developerセンターhttp://developer.htc.com/からDesireのソースコードをダウンロードする。今回は「bravo-2.6.32-gd96f2c0.tar.gz」でした。

test132_02.png
ダウンロードしている間に雛形となるプロジェクトをEasyProjectGenerator for Androidで作る。今回はC++プロジェクトとした。

test132_03.png
そして自動生成されたプロジェクトのjniフォルダに、ダウンロードしたHTC Desireのソースコードに含まれるmsm_camera.hをコピーする。

具体的なパスは「bravo-2.6.32-gd96f2c0\ include\ media\ msm_camera.h」。スクリーンキャプチャ用の環境ではtar.gzを解凍できなかったので別PCで解凍してコピーしてきました。

test132_04.png
次にEclipseでプロジェクトを読み込んで実際にLEDを制御するコードを実装する。

/dev/msm_camera/config0を開いて、int32数値を引数にしたMSM_CAM_IOCTL_FLASH_LED_CFGコマンドをioctlに送るだけです。今回はさらにMSM_CAM_IOCTL_GET_SENSOR_INFOを使って、カメラセンサー名とフラッシュサポートの有無を調べる関数も実装しました。
#if(true)
#define LOCAL_LOG
#define LOCAL_LOGD
#endif

#include <string.h>
#include <jni.h>

#ifdef LOCAL_LOG
#include <android/log.h>
#endif



#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include "msm_camera.h"

/*
 //msm_camera.hより
#define	MSM_CAM_IOCTL_MAGIC	'm'
#define	MSM_CAM_IOCTL_FLASH_LED_CFG	_IOW(MSM_CAM_IOCTL_MAGIC, 22, unsigned *)

#define MSM_CAMERA_LED_OFF 0
#define MSM_CAMERA_LED_LOW 1
#define MSM_CAMERA_LED_HIGH 2
#define MSM_CAMERA_LED_LOW_FOR_SNAPSHOT 3
*/



//
//	フラッシュコントロール
//
//nModeはMSM_CAMERA_LED_OFF、MSM_CAMERA_LED_LOW、MSM_CAMERA_LED_HIGHなど
//
//事前に↓を実行すること!
//	"chmod 666 /dev/msm_camera/config0"
//
int	CtrlCameraLED(int nMode)
{
	int		fd;
	int		nRet;

	fd = open("/dev/msm_camera/config0",O_RDWR | O_NONBLOCK);
	if(fd < 0)
		return	0;

	msm_camsensor_info	info;

	memset(&info,0,sizeof(info));
	nRet = ioctl(fd,MSM_CAM_IOCTL_GET_SENSOR_INFO,&info);
	if(nRet < 0 || info.flash_enabled == 0)
	{
		close(fd);
		return	0;
	}

	nRet = ioctl(fd,MSM_CAM_IOCTL_FLASH_LED_CFG,&nMode);
	close(fd);
	if(nRet < 0)
		return	0;

	return	1;
}


//
//フラッシュをサポートしないなら0、サポートするなら0以外を返す
//
//事前に↓を実行すること!
//	"chmod 666 /dev/msm_camera/config0"
//
int	IsSupportCameraLED(void)
{
	int		fd;
	int		nOnOff;		//uint_32
	int		nRet;
	msm_camsensor_info	info;

	fd = open("/dev/msm_camera/config0",O_RDWR | O_NONBLOCK);
	if(fd < 0)
		return	0;

	memset(&info,0,sizeof(info));
	nRet = ioctl(fd,MSM_CAM_IOCTL_GET_SENSOR_INFO,&info);
	close(fd);
	if(nRet < 0)
		return	0;

	if(info.flash_enabled)
		return	1;
	return	0;
}



//
//	カメラセンサー名の取得
//
//事前に↓を実行すること!
//	"chmod 666 /dev/msm_camera/config0"
//
int	GetCameraSensorName(char* pszName,int nLen)
{
	int		fd;
	int		nOnOff;		//uint_32
	int		nRet;
	msm_camsensor_info	info;

	if(nLen < MAX_SENSOR_NAME)
		return	0;

	fd = open("/dev/msm_camera/config0",O_RDWR | O_NONBLOCK);
	if(fd < 0)
		return	0;

	memset(&info,0,sizeof(info));
	nRet = ioctl(fd,MSM_CAM_IOCTL_GET_SENSOR_INFO,&info);
	close(fd);
	if(nRet < 0)
		return	0;

	strcpy(pszName,info.name);

	return	1;
}





extern "C"
jboolean Java_com_Test132_Test132Act_NativeIsSupportCameraLED(JNIEnv* env,jobject thiz)
{
	if(IsSupportCameraLED)
		return	JNI_TRUE;
	return	JNI_FALSE;
}

extern "C"
jboolean Java_com_Test132_Test132Act_NativeCtrlCameraLED(JNIEnv* env,jobject thiz,jint nMode)
{
	if(CtrlCameraLED(nMode))
		return	JNI_TRUE;
	return	JNI_FALSE;
}


extern "C"
jstring Java_com_Test132_Test132Act_NativeGetSensorName(JNIEnv* env,jobject thiz)
{
	char	pszName[MAX_SENSOR_NAME];

	if(GetCameraSensorName(pszName,MAX_SENSOR_NAME) == 0)
		return	env->NewStringUTF("Failed");

	return env->NewStringUTF(pszName);
}

test132_05.png
java側では、画面をタッチしたら順々にLEDの光量を変更するようにします。

また、/dev/msm_camera/config0を開くには事前にRW属性を付けておかないといけない&RW属性を付けるにはroot権限が必要なので、suコマンドによりchmodを実行する関数も作っておきます。

これで実機で実行すると、、、
package com.Test132;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.TextView;

public class Test132Act extends Activity
{
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		ExecSuCommand("chmod 666 /dev/msm_camera/config0");

		NativeCtrlCameraLED(_nCurrentMode);

		TextView	tv = new TextView(this);
		tv.setText( NativeGetSensorName() );
		setContentView(tv);
	}


	
	
	boolean	ExecSuCommand(String strCommandLine)
	{
		try
		{
			Process	process = Runtime.getRuntime().exec("su");
			java.io.DataOutputStream os = new java.io.DataOutputStream(process.getOutputStream());
			os.writeBytes(strCommandLine + "\nexit\n");
			os.flush();
			process.waitFor();
			return	true;
		}
		catch(java.io.IOException e)
		{
		}
		catch (InterruptedException e)
		{
		}
		return	false;
	}


	int		_nCurrentMode = MSM_CAMERA_LED_OFF;
	
	final	static	int	MSM_CAMERA_LED_OFF = 0;
	final	static	int	MSM_CAMERA_LED_LOW = 1;
	final	static	int	MSM_CAMERA_LED_HIGH = 2;
	final	static	int	MSM_CAMERA_LED_LOW_FOR_SNAPSHOT = 3;


	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
		case	MotionEvent.ACTION_DOWN:
			switch(_nCurrentMode)
			{
			case	MSM_CAMERA_LED_OFF:
				_nCurrentMode = MSM_CAMERA_LED_LOW;
				break;

			case	MSM_CAMERA_LED_LOW:
				_nCurrentMode = MSM_CAMERA_LED_HIGH;
				break;

			case	MSM_CAMERA_LED_HIGH:
				_nCurrentMode = MSM_CAMERA_LED_LOW_FOR_SNAPSHOT;
				break;

			case	MSM_CAMERA_LED_LOW_FOR_SNAPSHOT:
				_nCurrentMode = MSM_CAMERA_LED_OFF;
				break;
			}

			NativeCtrlCameraLED(_nCurrentMode);
			break;
		}

		return true;
	}

	
	public native boolean  NativeIsSupportCameraLED();
	public native boolean  NativeCtrlCameraLED(int nMode);
	public native String  NativeGetSensorName();

	static
	{
		System.loadLibrary("Test132");
	}
}

test132_06.png 初回起動時に「Superuser Request The following app is requesting superuser access:」と出て、su権限を許可するかを問われます。ここで「Allow」を選択します。

test132_07.png
すると画面にカメラセンサー名の「s5k3e2fx」という文字が表示されて、画面をタッチすると、、、

DSC_0715.JPG
LEDが点灯しました。

成功のように思えるのですが、何度タッチしても光量が変わりません。本当なら3段階&OFFの4パターンが順々に変化するはずなのですが、、




プロジェクトファイルをダウンロード

2011年01月26日

Android SDKで画像を読み込み、ピンチ操作でズームする

test131_01.png
今回は表示したJPEG画像をピンチ操作によりズームする。ピンチ操作というのは2本の指で画面をタッチして、指間隔を開けば拡大、指間隔を狭めれば縮小というもの。

まずはEasyProjectGenerator for Androidで雛形になるプロジェクトを作成。

test131_02.png そしてjavaのソースコードでアクティビティへImageFileViewクラスを割り当てる。このクラスにJPEG読み込みからピンチ操作、ズーム表示までを実装する。
package com.Test131;

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;

public class Test131Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		LinearLayout	layout = new LinearLayout(this);
		setContentView(layout);

		ImageFileView	view = new ImageFileView(this);
		layout.addView(view);
	}
}

test131_03.png
ImageFileView.javaというファイルを作り、実装する。

BitmapFactory.decodeFile()では、大きな画像ファイルを読み込もうとするとメモリが足りなくなってアプリが落ちてしまうので、今回は長辺がnMaxWidthピクセル以下になるように縮小して読み込むことにした。
package com.Test131;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.view.View;



public class ImageFileView extends View
{
	//表示画像
	protected	Bitmap	_bmpImageFile;


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

		{
			String	strFile = "/sdcard/DSC_0670.JPG";

			_bmpImageFile = LoadImageFile(strFile,800*2);
		}
	}


	public	Bitmap	LoadImageFile(String strFile,int nMaxWidth)
	{
		Bitmap	bmpImage;
		
		//画像の解析(大きい画像が開けない対策)
		BitmapFactory.Options	options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeFile(strFile,options);
		
		int		nWidth = options.outWidth;
		int		nHeight = options.outHeight;

		if(nWidth == 0 || nHeight == 0)
			return	null;

		if(nWidth <= nMaxWidth && nHeight <= nMaxWidth)
		{
			//リサイズ不要。そのまま読み込んで返す
			options.inJustDecodeBounds = false;
			return	BitmapFactory.decodeFile(strFile,options);
		}



		int		nScale;

		//nScaleは2の乗数にする
		{
			float	fScale;
			float	fScaleX = (float)nWidth / nMaxWidth;
			float	fScaleY = (float)nHeight / nMaxWidth;

			fScale = (fScaleX > fScaleY) ? fScaleX : fScaleY;

			nScale = 1;
			while(fScale >= nScale)
			{
				nScale *= 2;
			}
		}

		//画像の本読み込み
		options.inJustDecodeBounds = false;
		options.inSampleSize = nScale;
		bmpImage = BitmapFactory.decodeFile(strFile,options);

		return	bmpImage;
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if(_bmpImageFile == null)
			return;

		Matrix	matrix = new Matrix();

		//描画
		canvas.drawBitmap(_bmpImageFile, matrix, null);
	}
}


test131_04.png
これで実行すると画像が大きく表示された。

test131_05.png さらに画像のサイズに合わせて縮小&余白を表示する。

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if(_bmpImageFile == null)
			return;

		//画面表示用ズーム率
		float	fImageScale;

		//余白
		float	fMarginX = 0.0f;
		float	fMarginY = 0.0f;
		
		//ズーム率取得
		{
			int		nImageWidth	= _bmpImageFile.getWidth();
			int		nImageHeight= _bmpImageFile.getHeight();
			int		nViewWidth = getWidth();
			int		nViewHeight = getHeight();


			//画像に合わせる
			if((long)nImageWidth * nViewHeight > (long)nViewWidth * nImageHeight)
			{
				fImageScale = (float)nViewWidth / nImageWidth;
				fMarginY = (nViewHeight - fImageScale * nImageHeight) * 0.5f;
			}
			else
			{
				fImageScale = (float)nViewHeight / nImageHeight;
				fMarginX = (nViewWidth - fImageScale * nImageWidth) * 0.5f;
			}
		}


		//ズーム率
		float	fScale = fImageScale;

		//余白移動
		float	fMoveX = fMarginX;
		float	fMoveY = fMarginY;

		Matrix	matrix = new Matrix();

		//ズーム
		matrix.preScale(fScale,fScale);

		//移動
		matrix.postTranslate(fMoveX,fMoveY);

		//描画
		canvas.drawBitmap(_bmpImageFile, matrix, null);
	}

test131_06.png
これで画像に合わせて縮小&余白表示された。ここまではこれまでも紹介してきた流れで、ピンチ操作などは実装していない。

test131_07.png
次にようやくピンチ操作を実装する。

MotionEvent.ACTION_POINTER_DOWNやMotionEvent.ACTION_POINTER_MOVEでピンチ操作を簡単に検知できる。しかしどうも簡単にそれを使ってズームする仕組みは用意されていない模様。今回は簡易的なピンチズームを実装したものの、きちんとしたものを実装しようと思ったらMatrixは使わずに自分で領域計算して描画した方がよさそうです。

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		if(_bmpImageFile == null)
			return;

		//画面表示用ズーム率
		float	fImageScale;

		//余白
		float	fMarginX = 0.0f;
		float	fMarginY = 0.0f;
		
		//ズーム率取得
		{
			int		nImageWidth	= _bmpImageFile.getWidth();
			int		nImageHeight= _bmpImageFile.getHeight();
			int		nViewWidth = getWidth();
			int		nViewHeight = getHeight();


			//画像に合わせる
			if((long)nImageWidth * nViewHeight > (long)nViewWidth * nImageHeight)
			{
				fImageScale = (float)nViewWidth / nImageWidth;
				fMarginY = (nViewHeight - fImageScale * nImageHeight) * 0.5f;
			}
			else
			{
				fImageScale = (float)nViewHeight / nImageHeight;
				fMarginX = (nViewWidth - fImageScale * nImageWidth) * 0.5f;
			}
		}


		//ピンチを含めた総合ズーム率
		float	fScale = _fPinchScale * fImageScale;

		//余白を含めた移動量
		float	fMoveX = _fPinchMoveX + fMarginX;
		float	fMoveY = _fPinchMoveY + fMarginY;
		
		
		//ズーム原点指定
		fMoveX += _ptPinchStart.x - _ptPinchStart.x * _fPinchScale;
		fMoveY += _ptPinchStart.y - _ptPinchStart.y * _fPinchScale;

		Matrix	matrix = new Matrix();

		//ズーム
		matrix.preScale(fScale,fScale);

		//移動
		matrix.postTranslate(fMoveX,fMoveY);

		//描画
		canvas.drawBitmap(_bmpImageFile, matrix, null);
	}


	//ピンチによる現在のズーム率(拡大  > 1.0f > 縮小)
	private	float	_fPinchScale	= 1.0f;

	private	PointF	_ptPinchStart	= new PointF();
	private float	_fPinchStartDistance	= 0.0f;
	private	float	_fPinchMoveX	= 0.0f;
	private	float	_fPinchMoveY	= 0.0f;

	//タッチ操作内部処理用
	private static final int TOUCH_NONE = 0;
	private static final int TOUCH_DRAG = 1;
	private static final int TOUCH_ZOOM = 2;
	private int		_nTouchMode	= TOUCH_NONE;

	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{

		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
			//ピンチ開始 
		case MotionEvent.ACTION_POINTER_DOWN:
			if(event.getPointerCount() >= 2)
			{
				_fPinchStartDistance = GetDistance(event);
				if(_fPinchStartDistance > 50f)
				{
					GetCenterPoint(event,_ptPinchStart);
					_nTouchMode = TOUCH_ZOOM;
				}
			}
		    break;

		    //ピンチ中
		case MotionEvent.ACTION_MOVE:
		    if(_nTouchMode == TOUCH_ZOOM && _fPinchStartDistance > 0)
		    {
		    	PointF	pt = new PointF();

		    	GetCenterPoint(event,pt);
		    	_fPinchMoveX	= pt.x - _ptPinchStart.x;
		    	_fPinchMoveY	= pt.y - _ptPinchStart.y;

		    	_fPinchScale = GetDistance(event) / _fPinchStartDistance;
				invalidate();
		    }
		    break;

		    //ピンチ終了
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			if(_nTouchMode == TOUCH_ZOOM)
			{
				//ピンチ終了処理
				_nTouchMode = TOUCH_NONE;

				_fPinchMoveX	= 0.0f;
				_fPinchMoveY	= 0.0f;
				_fPinchScale	= 1.0f;
				_ptPinchStart.x	= 0.0f;
				_ptPinchStart.y	= 0.0f;
				invalidate();
			}
			break;
		}
		
		
		switch(event.getAction() & MotionEvent.ACTION_MASK)
		{
			//ドラッグ開始
		case	MotionEvent.ACTION_DOWN:
			if(_nTouchMode == TOUCH_NONE && event.getPointerCount() == 1)
			{
				_nTouchMode = TOUCH_DRAG;
			}
			break;

		case MotionEvent.ACTION_MOVE:
			if(_nTouchMode == TOUCH_DRAG)
			{
			}
			break;
			
			//ドラッグ終了
		case	MotionEvent.ACTION_UP:
			if(_nTouchMode == TOUCH_DRAG)
			{
				//ドラッグ終了処理
				_nTouchMode = TOUCH_NONE;
				break;
			}
		}

		return true;
	}	

	//ピンチ距離取得用
	private	float	GetDistance(MotionEvent event)
	{
		float x = event.getX(0) - event.getX(1);
		float y = event.getY(0) - event.getY(1);
		return android.util.FloatMath.sqrt(x * x + y * y);
	}

	//ピンチ開始座標取得用
	private	void	GetCenterPoint(MotionEvent event,PointF pt)
	{
		pt.x = (event.getX(0) + event.getX(1)) * 0.5f;
		pt.y = (event.getY(0) + event.getY(1)) * 0.5f;
	}



プロジェクトファイルをダウンロード

Android OSソースコード一式のダウンロード

公式サイトにある具体的なダウンロード方法やビルド方法はhttp://source.android.com/source/download.html

公式サイトからのソースコード配布はRepoとかGitが必要で、また、そのためにLinuxの環境が必要。ちなみにCygwinでもダウンロードしようと思えばできるもののビルドはできないらしいです。

ソースコードのダウンロードは何だかんだと面倒な作業になり、敷居がかなり高いので私がダウンロードしたAndroidソースコード一式をtar.zipに固めてアップロードしました。無責任な話しですがビルドなどの確認はしていないので本当にきちんとダウンロードできているのかは不明です。

■Android 2.3ソースコード一式

android23_20110122.tar.gz(3GB)


2011年01月21日

Android SDKでフォルダ内のファイルをソートして列挙する

test130_01.png
フォルダ内のファイル一覧は「File[] files = new File(strPath) .listFiles();」のような感じで簡単に習得できる。しかしこうやって取得したファイル配列は名前順に並んでいないし、隠しファイルや隠しフォルダも含まれている。

今回は隠しファイル/隠しフォルダを省き、さらにソートした状態のファイル配列を取得できる関数を作成した。
package com.Test130;

import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class Test130Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		File[]	aFiles = GetFileList("/system/lib/");

		for(File file : aFiles)
		{
			if(file.isDirectory())
				Log.d("Test130","Found : " + file.getAbsolutePath() + "/");
			else
				Log.d("Test130","Found : " + file.getAbsolutePath());
		}
	}


	//
	//パスで指定されたフォルダ内のファイル/フォルダをソートして返す
	//
	public	File[]	GetFileList(String strPath)
	{
		//ソート用の独自オブジェクトクラス ここでしか使わないから関数内で無理やり宣言
		final class Data
		{
			private File _data;

			public Data(File data)
			{
				_data = data;
			}

			public	File	getFile()
			{
				return	_data;
			}
			
			public	int	Compare(Data cmp)
			{
				String	str1 = _data.getAbsolutePath();
				String	str2 = cmp._data.getAbsolutePath();

				if(cmp == null || cmp._data == null || _data == null)
					return	0;
				
				if(_data.isDirectory() == cmp._data.isDirectory())
					return	str1.compareToIgnoreCase(str2);
				if(_data.isDirectory())
					return	-1;
				return	1;
			}
		}

		//ソート用比較関数  ここでしか使わないから関数内で無理やり宣言
		final class DataComparator implements java.util.Comparator
		{
			public int compare(Object o1, Object o2)
			{
				return	((Data)o1).Compare((Data)o2);
			}
		}
		
		
		//strPathをファイルオブジェクトにする
		File	file = new File(strPath);

		//strPathがファイルだったらそのファイルが含まれるフォルダを処理対象とする
		if(file.isFile())
		{
			file = file.getParentFile();
			if(file == null)
				return	null;
		}

		int		i;
		File[]	afTmp;

		//フォルダ内のファイル配列を取得
		afTmp = file.listFiles();
		if(afTmp == null || afTmp.length == 0)
			return	null;
		
		//一度独自オブジェクトのリストに変換して、、、
		java.util.ArrayList	alist = new java.util.ArrayList();
		for(i = 0; i < afTmp.length; i++)
		{
			if(afTmp[i].isHidden() == false)	//隠しファイル/フォルダは無視
				alist.add(new Data(afTmp[i]));
		}

		//オブジェクトリストからオブジェクト配列に変換
		Object[]	aObject = alist.toArray();

		//javaのオブジェクト用メソッドを使ってソート
		java.util.Arrays.sort(aObject,new DataComparator());

		//オブジェクト配列からファイル配列に変換
		afTmp = new File[aObject.length];
		for(i = 0; i < aObject.length; i++)
			afTmp[i] = ((Data)aObject[i]).getFile();

		return	afTmp;
	}
}

test130_02.png
これで指定したフォルダ内のファイルとフォルダがソートされた状態でログに出力された。




プロジェクトファイルをダウンロード

実行時クラスを取得/比較する

test129_01.png JavaではgetClass()を使えば実行時クラスを取得、比較できる。派生クラスの判別に利用可能。
package com.Test129;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class Test129Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		BASE	test = new AAA();

		if(test.getClass() == BASE.class)
			Log.d("Test129","test == BASE");
		else
			Log.d("Test129","test != BASE");

		if(test.getClass() == AAA.class)
			Log.d("Test129","test == AAA");
		else
			Log.d("Test129","test != AAA");

		if(test.getClass() == BBB.class)
			Log.d("Test129","test == BBB");
		else
			Log.d("Test129","test != BBB");
	}

	private	class	BASE
	{
	};
	
	private	class	AAA extends BASE
	{
	};
	
	private	class	BBB extends BASE
	{
	};
}

test129_02.png
きちんと比較できた。




プロジェクトファイルをダウンロード

Android SDKでメニュー項目を動的に操作する

test128_01.png
以前にAndroid SDKで「menu」ボタンでメニューを表示するでメニューを表示した。onCreateOptionsMenu()でメニュー項目を作り表示、onOptionsItemSelected()で選択されたメニュー項目を実行できる。

しかしonCreateOptionsMenu()は初回に1度だけ呼ばれるのみなので、動的にメニュー項目を増やしたり減らしたり、表示するメニューの文字を変えることはできなかった。動的にメニュー項目を変更するにはonPrepareOptionsMenu()をオーバーライドして、そこでメニュー項目を操作する。
package com.Test128;

import java.util.Calendar;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;


public class Test128Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}


	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);

		int		nId = 0;

		menu.add(0,nId++,Menu.NONE,"AAA").setIcon(android.R.drawable.ic_menu_set_as);
		menu.add(0,nId++,Menu.NONE,"BBB");
		menu.add(0,nId++,Menu.NONE,"CCC").setIcon(R.drawable.icon);


		return true;
	}


	@Override
	public boolean onPrepareOptionsMenu(Menu menu)
	{
		String	str;

		str = Calendar.getInstance().getTime().toString();
		menu.findItem(1).setTitle(str);

		return true;
	}

	
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		Log.d("Test124","MenuSelected " + item.getItemId() + " " + item.getTitle());

		switch(item.getItemId())
		{
		case 0:
			return	true;

		case 1:
			return	true;

		case 2:
			return	true;

		default:
			break;
		}

		return super.onOptionsItemSelected(item);
	}
}

test128_02.png
これでメニューを動的に操作して、表示するたびにそのときの日時をメニューとして表示することができた。




プロジェクトファイルをダウンロード

Android SDKでキーロック状態を取得する

test127.png
Androidアプリの実行中に今現在画面がロックされているかどうかのロック状態を調べるにはKeyguardManager:: inKeyguardRestrictedInputMode()を使う。trueならロック中、falseならロックされていない。

問題はロック/アンロックの検出。HTC Desireでは下のソースで検出できましたが、Androidエミュレーターでは検出できませんでした。また下のソースではアンロックの検出にフォーカス状態を利用しているので、フォーカスが一定しないアプリの場合は使えないはずです。 電源ボタンをハンドルすれば手動でのロック/アンロックは検出できるはずですが、そうすると今度はソフトウエア的なロックなどは検出できないはずですし、、、ということでロック/アンロックの検出方法は不明です。
package com.Test127;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;

public class Test127Act extends Activity
{
	private	KeyguardManager	_KeyguardManager;
	private	boolean			_bScreenLocked = false;
	
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		_KeyguardManager = (KeyguardManager)getSystemService(Context.KEYGUARD_SERVICE);
		_bScreenLocked = _KeyguardManager.inKeyguardRestrictedInputMode();
	}

	
	@Override
	public void	onPause()
	{
		super.onPause();

		_bScreenLocked = _KeyguardManager.inKeyguardRestrictedInputMode();
		if(_bScreenLocked)
		{
			//ロックされた
			//呼ばれないことあり
			Log.d("Test127","onPause() Locked!!! Do something ....");
		}
	}

	
	@Override
	public void	onWindowFocusChanged(boolean hasFocus)
	{

		super.onWindowFocusChanged(hasFocus);

		if(_bScreenLocked == true && hasFocus)
		{
			//アンロックされた
			//呼ばれないことあり
			Log.d("Test127","onWindowFocusChanged() Unlocked!!! Do something ....");
		}
		_bScreenLocked = _KeyguardManager.inKeyguardRestrictedInputMode();
	}
}


プロジェクトファイルをダウンロード

Android SDKで画面が533x320ドットになる問題の解決策

test126_01.png
Androidで画像表示をしているうちに、画面サイズが533×320ドットとして認識される問題にぶつかりました。

以下のようなソースコードで画面のサイズや解像度などの情報を取得する。実行にはHTC Desireを利用した。
package com.Test126;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;

public class Test126Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);


		WindowManager	wmWindowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
		Display			display = wmWindowManager.getDefaultDisplay();

		Log.d("Test126","Display.getWidth() " + display.getWidth());
		Log.d("Test126","Display.getWidth() " + display.getHeight());
		
		DisplayMetrics	displayMetrics = new DisplayMetrics();
		display.getMetrics(displayMetrics);
		Log.d("Test126","DisplayMetrics.widthPixels   " + displayMetrics.widthPixels);
		Log.d("Test126","DisplayMetrics.heightPixels  " + displayMetrics.heightPixels);
		Log.d("Test126","DisplayMetrics.density       " + displayMetrics.density);
		Log.d("Test126","DisplayMetrics.densityDpi    " + displayMetrics.densityDpi);
		Log.d("Test126","DisplayMetrics.scaledDensity " + displayMetrics.scaledDensity);
		Log.d("Test126","DisplayMetrics.xdpi          " + displayMetrics.xdpi);
		Log.d("Test126","DisplayMetrics.ydpi          " + displayMetrics.ydpi);
	}
}

test126_02.png
するとこのような結果になった。HTC Desireの物理的な画面サイズは800×480ドットだが、取得できたサイズは533×320ドット。

マニフェスト設定によってはこのような結果になるらしい。もちろんこの状態だと、アプリ内での描画サイズも533×320ドットになり、800×480ドットの画面サイズを活かした緻密な描画ができない。
01-21 00:22:41.873: DEBUG/Test126(8149): Display.getWidth() 533
01-21 00:22:41.873: DEBUG/Test126(8149): Display.getWidth() 320
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.widthPixels   533
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.heightPixels  320
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.density       1.5
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.densityDpi    240
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.scaledDensity 1.5
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.xdpi          254.0
01-21 00:22:41.873: DEBUG/Test126(8149): DisplayMetrics.ydpi          254.0

test126_03.png 533×320ドットになる問題の解決策のひとつはAndroidManifest.xmlの中でuses-sdk android:minSdkVersionを指定する方法。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.Test126"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Test126Act"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

<uses-sdk android:minSdkVersion="4"/>

</manifest>

test126_04.png これできちんと800×480ドットと認識できた。
01-21 00:23:46.933: DEBUG/Test126(8221): Display.getWidth() 800
01-21 00:23:46.933: DEBUG/Test126(8221): Display.getWidth() 480
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.widthPixels   800
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.heightPixels  480
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.density       1.5
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.densityDpi    240
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.scaledDensity 1.5
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.xdpi          254.0
01-21 00:23:46.933: DEBUG/Test126(8221): DisplayMetrics.ydpi          254.0

test126_05.png もう一つの解決策はsupports-screensでandroid:largeScreensなどを有効にする方法。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.Test126"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Test126Act"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>


<supports-screens
	android:smallScreens="true"
	android:normalScreens="true"
	android:largeScreens="true"
	android:anyDensity="true" />
</manifest>

test126_06.png こちらでも800×480ドットと認識された。
01-21 00:24:52.513: DEBUG/Test126(8294): Display.getWidth() 800
01-21 00:24:52.513: DEBUG/Test126(8294): Display.getWidth() 480
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.widthPixels   800
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.heightPixels  480
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.density       1.5
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.densityDpi    240
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.scaledDensity 1.5
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.xdpi          254.0
01-21 00:24:52.513: DEBUG/Test126(8294): DisplayMetrics.ydpi          254.0

プロジェクトファイルをダウンロード

2011年01月20日

Android SDKでファイル選択ダイアログを作る

test125_01.png
Androidはユーザーにファイルを意識させない設計がされているためなのか、標準でファイル選択ダイアログが用意されていません。ちょっと不便すぎるので今回はファイル選択ダイアログを作成します。

まずはJavaで雛形となるプロジェクトを作り、「menu」ボタンからファイル選択画面を開くSelectFile()を呼び出す流れを作る。この状態ではSelectFile()が実装されていないのでビルドできません。
package com.Test125;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class Test125Act extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);

		int		nId = 0;

		menu.add(0,nId++,Menu.NONE,"Open").setIcon(android.R.drawable.ic_menu_set_as);

		return true;
	}


	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch(item.getItemId())
		{
		case 0:
			SelectFile();
			return	true;

		default:
			break;
		}

		return super.onOptionsItemSelected(item);
	}
}

test125_02.png
プロジェクトに「SelectFileDialog.java」ファイルを追加して、ファイル選択ダイアログのメイン処理を実装する。

AlertDialog.Buildeを作り、そこにファイル名のリストメニューを表示。クリックされたアイテムがフォルダならさらに下のフォルダ用のAlertDialog.Builderを作り表示。クリックされたのがファイルなら終了という流れです。
package com.Test125;
 
import java.io.File;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.KeyEvent;

public class SelectFileDialog extends Activity
	implements	DialogInterface.OnClickListener
				,DialogInterface.OnKeyListener
{
	private	File		_fileCurrent;	//現在表示しているフォルダ
	private	File[]		_aFileList;		//現在表示しているフォルダのファイル一覧
	private	String[]	_astrFileName;	//現在表示しているフォルダのメニュー用ファイル名
	private	Context		_context;

	private	Dialog		_dlgThis;

	public SelectFileDialog(Context context)
	{
		_context = context;
	}



	@Override
	public void onPause()
	{
		if(_dlgThis != null && _dlgThis.isShowing())
			_dlgThis.dismiss();
		
		super.onPause();
	}


/*	@Override
	public void onResume()
	{
		Log.d("Test125","--onResume--- ");

		if(_dlgThis != null)
			_dlgThis.show();
		
		super.onResume();
	}

	public	String	GetCurrentPath()
	{
		if(_dlgThis == null || _dlgThis.isShowing() == false || _fileCurrent == null)
			return	"";

		return	_fileCurrent.getAbsolutePath();
	}
*/	

	public	boolean	Show(String strInitPath)
	{
		boolean	ret;

		ret = CreateFileList(strInitPath);
		if(ret == false)
			return	false;

		
		AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(_context);
		dlgBuilder.setCancelable(true);
		dlgBuilder.setOnKeyListener(this);
		dlgBuilder.setTitle(_fileCurrent.getPath());
		dlgBuilder.setItems(_astrFileName,this);

		_dlgThis = dlgBuilder.create();
		_dlgThis.show();

		return	true;
	}


	public	void	Close(DialogInterface dialog,File fileSelected)
	{
		((onSelectFileDialogListener)_context).onFileSelected_by_SelectFileDialog(fileSelected);
		dialog.dismiss();
		_dlgThis = null;
	}


	@Override
	public void onClick(DialogInterface dialog, int which)
	{
		File	file = _aFileList[which];

		if(file.isDirectory())
		{
			//フォルダが選択されたので開く
			Show(file.getAbsolutePath());
			dialog.dismiss();
		}
		else
		{
			//選択されたので終了
			Close(dialog,file);
		}
	}


	@Override
	public boolean  onKey(DialogInterface dialog, int keyCode, KeyEvent event)
	{
		if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN)
		{
			File	fileParent;

			fileParent = _fileCurrent.getParentFile();
			if(fileParent != null)
			{
				Show(fileParent.getAbsolutePath());
				dialog.dismiss();
			}
			else
			{
				//ルートだったので終了
				Close(dialog,null);
			}

			return	true;
		}
		return	false;
	}


	private	boolean	CreateFileList(String strPath)
	{
		File[]	aFiles;
		
		_aFileList = null;
		_astrFileName = null;

		_fileCurrent = new File(strPath);
		if(_fileCurrent == null)
			return	false;
		
		aFiles = _fileCurrent.listFiles();
		if(aFiles == null || aFiles.length == 0)
		{
			_aFileList = new File[0];
			_astrFileName = new String[0];
			return	true;
		}
		

		int			i;
		int			nCount;
		String[]	astrName;

		astrName = new String[aFiles.length];

		nCount = 0;
		for(i = 0; i < aFiles.length; i++)
		{
			if(aFiles[i].isDirectory() && aFiles[i].isHidden() == false)
			{
				//ディレクトリの場合
				astrName[i] = aFiles[i].getName() + "/";
				nCount++;
			}
			else if(aFiles[i].isFile() && aFiles[i].isHidden() == false)
			{
				//通常のファイル
				astrName[i] = aFiles[i].getName();
				nCount++;
			}
			else
			{
				aFiles[i] = null;
			}
		}


		_aFileList = new File[nCount];
		_astrFileName = new String[nCount];

		nCount = 0;
		for(i = 0; i < aFiles.length; i++)
		{
			if(aFiles[i] != null)
			{
				_aFileList[nCount] = aFiles[i];
				_astrFileName[nCount] = astrName[i];
				nCount++;
			}
		}

		//ソートするならここでソート
		
		return	true;
	}


	public interface onSelectFileDialogListener
	{
		public void onFileSelected_by_SelectFileDialog(File file);
	}
}

test125_03.png
そして再び戻ってSelectFileDialogを呼び出す処理を実装する。

本当はSelectFileDialogをメンバー変数ではなく、ローカルに宣言して利用できるようにしたかったのですが、、、ファイル選択ダイアログを開いているときに端末の向きを変えてオリエンテーションが変わるとOnPause、OnResumeが呼ばれずにメモリリークが起きるので、苦肉の策としてこのような実装にしました。
package com.Test125;

import java.io.File;

import com.Test125.SelectFileDialog.onSelectFileDialogListener;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

public class Test125Act extends Activity
		implements onSelectFileDialogListener
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);

		int		nId = 0;

		menu.add(0,nId++,Menu.NONE,"Open").setIcon(android.R.drawable.ic_menu_set_as);

		return true;
	}


	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch(item.getItemId())
		{
		case 0:
			SelectFile();
			return	true;

		default:
			break;
		}

		return super.onOptionsItemSelected(item);
	}


	protected	SelectFileDialog	_dlgSelectFile;

	
	private	void	SelectFile()
	{
		//ここで画面回転を固定すべき(画面が固定されていないなら)

		_dlgSelectFile = new SelectFileDialog(this);
		_dlgSelectFile.Show("/sdcard/");
	}

	
	@Override
	public void onFileSelected_by_SelectFileDialog(File file)
	{
		//ここで画面回転の固定を解除すべき(SelectFileDialog利用前に画面が固定されていなかったなら)

		if(file != null)
			Log.d("Test125","selected : " + file.getName());
		else
			Log.d("Test125","not selected");

		_dlgSelectFile = null;
	}

	
	@Override
	public void onPause()
	{
		if(_dlgSelectFile != null)
			_dlgSelectFile.onPause();
		
		super.onPause();
	}
}

test125_04.png
これで実行して、「memu」ボタンを押すとメニューが表示され、、、

test125_05.png
メニューの「Open」をクリックすると、、、

test125_06.png
ファイル選択ダイアログが開きました。フォルダを選択するとその中が表示され、「戻る」ボタンを押すとひとつ上のフォルダに移ります。

そして端末の向きを変えると、、、メニューが終了します。本当は向きを変えてメニューを再表示させるべきですが複雑になるのでやめました。




プロジェクトファイルをダウンロード

1  2  3  4  5  6  7  8  9  10  11