Androidプログラミング・メモ2
by K.I
2015/04/13〜
Index
- Androidのアプリケーションプログラムに関する備忘録としてのメモ
- いろんなサイトのコードを抜粋しただけのものが多いんだけど。。
- Dialogや、ListView、画面サイズに関するもの
[top]
- たかがDialog表示なんだけど、Androidでは奥が深い。
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("title");
builder.setMessage("message");
builder.show();
new AlertDialog.Builder(context)
.setTitle("title")
.setMessage("message")
.show();
- まぁ、これで簡単にDialogを表示することが出来るんだけど、
- 画面を回転するとDialogは消えて、LogCatに以下のようなエラーが出る。
04-21 12:29:55.153: E/WindowManager(30605): android.view.WindowLeaked: Activity hm.orz.bluefish.xxx.yyy has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{41f05cb0 V.E..... R....... 0,0-1160,276} that was originally added here
- どうやら、メモリリークを起こしているみたいだ。
- 画面の回転のようなActivityの状態変化時に、Dialogを表示しっぱなしというのはダメらしい。
- Dialogの描画を、Activityに管理してもらうように記述すれば良いらしい。。
- ActivityのonCreateDialogで、Dialogの生成を記述して、showDialogで表示する
- これでOKなんだけど、Eclipseで「メソッドshowDialogは、Activityで使用すべきではありません」とWarningが出ている
- 現在は、DialogFragmentを使う方法が推奨されているらしい
- 以前は、画面毎にActivityを作成して、Layoutや処理を記述しなければならなかったのを、
- Layoutや処理はFragmentに記述、部品化して、Activityに貼り付けるという感じかな?
- Windowsで、DialogでLayoutを部品化して、Windowに貼り付けるのと同じようなものだろうか。
- あくまで、自分のイメージなので、ちょっと違うかもしれないけど。
- Activityの代わりに、FragmentActivityを継承して、
- DialogFragmentを継承したクラスのonCreateDialogでDialogの生成を記述して、showで表示する
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class MainActivity extends FragmentActivity {
:
:
private void showAlert() {
MyAlart af = new MyAlart();
af.show(getSupportFragmentManager(), "alert_dialog");
}
private static class MyAlart extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Title")
.setMessage("Message");
return builder.create();
}
}
}
- MyAlartクラスは、別ファイルにするのが面倒だったので、インナークラスで記述した
- ちゃんとダイアログは表示されたんだけど、
- 回転させた時、例外で落ちる。うーん何故だろう?
04-21 20:00:54.277: W/dalvikvm(1322): threadid=1: thread exiting with uncaught exception (group=0x415d5ba8)
04-21 20:00:54.287: E/AndroidRuntime(1322): FATAL EXCEPTION: main
04-21 20:00:54.287: E/AndroidRuntime(1322): Process: hm.orz.bluefish.xxx, PID: 1322
04-21 20:00:54.287: E/AndroidRuntime(1322): java.lang.RuntimeException: Unable to start activity ComponentInfo{hm.orz.bluefish.xxx/hm.orz.bluefish.xxx.yyy}:
android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment
hm.orz.bluefish.xxx.yyy$MyAlart: make sure class name exists, is public, and has an empty constructor that is public
- RuntimeExceptionの「make sure class name exists, is public, and has an empty constructor that is public」ってのがちょっと気になる
- とりあえず、インナークラスの MyAlartを、private→publicに変えてみた
- 今度は、回転しても大丈夫になった。
- インナークラスだから、privateクラスでも良いと思うんだけど。。
- 回転させた時、つまり2回目だとマズいのかな?
- よく見ると、InstantiationExceptionも出ている。こちらが問題なのかな
- つまりActivityの自動再生時には、
- DialogFragmentのコンストラクタが外部から呼び出されるってことなのか。
- DialogFragmentを使った、ボタン付きアラートの例
public class MainActivity extends FragmentActivity {
:
:
public void showAlert2() {
final MyAlart2 dialog = new MyAlart2();
dialog.show(getSupportFragmentManager(), "dialog");
}
public void doPositiveClick() {
Toast.makeText(this, "OK!", Toast.LENGTH_SHORT).show();
}
public void doNegativeClick() {
Toast.makeText(this, "Cancel!", Toast.LENGTH_SHORT).show();
}
public static class MyAlart2 extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("title");
builder.setMessage("message");
builder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MainActivity activity = (MainActivity) getActivity();
activity.doPositiveClick();
}
});
builder.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MainActivity activity = (MainActivity) getActivity();
activity.doPositiveClick();
}
});
return builder.create();
}
}
}
この例では、Activityの doPositiveClick, doNegativeClickメソッドを定義して、
- AlartDialogのPositiveButton, NegativeButtonから呼び出すようにしている
- このコードの方が、DialogFragmentで処理が完結しているし、
- OnClickListenerの処理を分けて記述してあるので、分かり易い感じがする。
public class MyAlart3 extends DialogFragment{
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("DialogFragment")
.setMessage("Message")
.setPositiveButton("OK", OK_Listener)
.setNeutralButton("SKIP",SKIP_Listener)
.setNegativeButton("CANCEL", CANCEL_Listener);
return builder.create();
}
private DialogInterface.OnClickListener OK_Listener
= new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Toast.makeText(getActivity(), "OK", Toast.LENGTH_LONG).show();
}
};
private DialogInterface.OnClickListener SKIP_Listener
= new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Toast.makeText(getActivity(), "SKIP", Toast.LENGTH_LONG).show();
}
};
private DialogInterface.OnClickListener CANCEL_Listener
= new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Toast.makeText(getActivity(), "CANCEL", Toast.LENGTH_LONG).show();
}
};
}
- それにしても、Javaの記述方法は、C以上に難解というか、
- 少なくとも可読性が良いとは言えない気がするなぁ。。
- EditTextを用意してsetViewすれば、値を入力することが出来る。
final EditText et = new EditText(gds.activity);
et.setInputType(InputType.TYPE_CLASS_PHONE);
et.setText(defa);
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
builder.setTitle(title)
builder.setView(et)
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
result = et.getText().toString();
}
})
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
})
Dialog d = builder.create();
- 前項とほとんど同じだけど、XMLでレイアウトを定義している
LayoutInflater inf = gds.activity.getLayoutInflater();
final View layout = inf.inflate(R.layout.numkey,null);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(title);
builder.setView(layout);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
Dialog d = builder.create();
- アラートダイアログは、positive, negative, neutralの3つのボタンは組み込まれてるので、
- 3つまでは、setPositiveButton, setNegativeButton, setNeutralButtonで、OnClickListenerを設定するだけで使える。
- さらにボタンを追加する場合は、XMLでレイアウトでボタンを定義しておいて、
- setPositiveButton等で定義したボタンを押すと、Dialogが閉じてしまうので、
Dialog d = builder.create()
LayoutParams lp = d.getWindow().getAttributes();
lp.width = 640;
d.getWindow().setAttributes(lp);
- 透明なdrawableを作っておいて、背景に設定する
Dialog d = builder.create();
d.getWindow().setBackgroundDrawable(getResources().getDrawable(R.drawable.transparent));
public void showAlert() {
final int interval = 5000; // 5秒間表示
final SpinningProgressDialog dialogFragment = SpinningProgressDialog
.newInstance(R.string.app_name, R.string.dialog_message);
dialogFragment.show(getSupportFragmentManager(), "dialog_fragment");
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
dialogFragment.dismiss();
}
}, interval);
}
public static class SpinningProgressDialog extends DialogFragment {
private ProgressDialog progressDialog;
public static SpinningProgressDialog newInstance(int title, int message) {
SpinningProgressDialog fragment = new SpinningProgressDialog();
Bundle args = new Bundle();
args.putInt("title", title);
args.putInt("message", message);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle safedInstanceState) {
int title = getArguments().getInt("title");
int message = getArguments().getInt("message");
progressDialog = new ProgressDialog(getActivity());
progressDialog.setTitle(title);
progressDialog.setMessage(getResources().getText(message));
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return progressDialog;
}
}
- 時間の掛かる読み込み中に、ダイアログを表示しようとしたんだけど、
- 自分の場合、onClickで時間の掛かる処理を動かして、プログレス表示しようとしたんだけど、表示されなかった。
- プログレスの表示を、処理時間よりも長く設定すると、処理後にプログレス表示された。
- 並列に動いているが、表示がみえない状態になっているのか。。
- そもそも、onClickは、時間の掛かる処理をしてはダメなんだろう。
- ここは、マルチスレッドについて考えないといけないらしい1。あぁ、面倒だ。。。
1せめてプログレス表示ぐらいは、単純な仕組みにしておいてくれればいいのに。。
[top]
- 基本的には、LinearLayoutで部品を並べれば良いだけ。
- この例では、CheckBox、画像描画用のカスタムView、TextViewを並べて表示する
- CheckBoxは固定幅、残りの幅をカスタムViewとTextViewで1:2に分配するようにしている
- ListViewで画像を表示する場合は、1行分のレイアウトのXMLでImageViewを配置しておいて、
- 画像を単に表示するだけなら、ImageViewを使えば良いが、
- 自由に描画したい場合は、カスタムViewを作成して、前項の例のように配置しておく。
public class myView extends View {
private Paint mPaint;
private int mColor;
public myView(Context context) {
super(context);
mPaint = new Paint();
mColor = 0x80FF0000;
mWidth = 10;
}
public myView(Context cont, AttributeSet attr) {
super(cont,attr);
mPaint = new Paint();
mColor = 0x80FF0000;
mWidth = 10;
}
public void setColor(int color) {
this.mColor = color;
requestLayout();
invalidate();
}
@Override
protected void onDraw(Canvas c) {
super.onDraw(c);
mPaint.reset();
c.drawColor(Color.BLACK);
Path ppp = new Path();
ppp.moveTo((float)(getMeasuredWidth()*0.1), (float)(getMeasuredHeight()*0.1));
ppp.lineTo((float)(getMeasuredWidth()*0.9), (float)(getMeasuredHeight()*0.1));
ppp.lineTo((float)(getMeasuredWidth()*0.9), (float)(getMeasuredHeight()*0.9));
ppp.lineTo((float)(getMeasuredWidth()*0.1), (float)(getMeasuredHeight()*0.9));
ppp.lineTo((float)(getMeasuredWidth()*0.1), (float)(getMeasuredHeight()*0.1));
ppp.setColor(this.mColor);
ppp.setStrokeWidth(mWidth);
ppp.setStyle(Paint.Style.FILL_AND_STROKE);
c.drawPath(ppp, mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(widthSize,heightSize);
}
}
- 例えば、こんな風に色を設定できるようにしておく
- で、これをカスタムLayoutから設定するようにすれば良い
参考: Holderを使わないListView
- ListView上にCheckBoxを配置して表示するのは、前々項のように簡単なんだけど
- その行のクリックが、CheckBoxの設定するだけの意味になってしまう
- CheckBoxの辺りをクリックした時は、CheckBoxがON/OFFで、それ以外の場所では別の意味にしたい。
- それで、ListViewをクリックした時の処理のonItemClickで、
- クリック位置がCheckBox辺りだった時だけ、position位置のCheckBoxデータを反転すれば良いんだけど、
- onItemClickでは、クリック位置が分からない。。
- 苦肉の策として、dispatchTouchEventでクリック位置を得て置いて、
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
clickx = ev.getX();
clicky = ev.getY();
return super.dispatchTouchEvent(ev);
}
- onItemClickで、この座標により判定することにした
- これで、CheckBox付近がクリックされる度に元データを更新して、ListViewを描き直すことになる
- それ以外の場所をクリックした場合は、別処理が可能になる
- ListViewの項目が多くなってくると、フリックで最後までスクロールするのが大変になってくる
- スクロールバーが欲しいな〜と思っていたんだけど、意外と簡単だった。
- ListViewのXMLに以下の設定を追加するだけ
android:fastScrollEnabled="true"
- フリックでスクロールすると、普通より大きなバーが出てくるので、それでスクロールすることが可能となる
[top]
- Androidでは、ユーザーインターフェースを、1つのUIスレッドのみで制御していて、
- UIスレッドで、時間の掛かる処理をすると、その間、ユーザーの操作に対して無反応になる
- これはユーザーインターフェース的に、望ましい状態ではない
- それに、長時間無反応状態だと、システムがアプリを強制終了させてしまう
- ファイル読込み等の時間の掛かる処理は、別スレッドで実行する必要がある。
- でも、別スレッドからUIをコントロールすることは出来ない。
- この場合も、例外で強制終了されてしまう
- 別スレッドから、UIスレッドを直接操作することが出来ないが、
- Handlerを使えば、UIスレッドに処理を登録することが出来る
- それぞれのスレッドは、Looperというキューを順番に処理していて、
- Handlerは、そのキューにJobを登録するものという感じなのかな。
- なんだけど、HandlerはAndroidの仕組みが分かってないといろいろ難しいらしい。
- 面倒なので、とりあえずHandlerは後回しにしよう。
- Androidでは、非同期処理を行うためのAsyncTaskというクラスを使ってみる
- Genericsで、以下のパラメータの型指定が必要
- 非同期処理 doInBackgroundの引数
- 進捗更新処理 onProgressUpdateの引数
- 終了処理 onPostExecuteの引数・結果 doInBackgroundの戻り値
public class MyAsyncTask
extends AsyncTask<String, Integer, Long>
implements OnCancelListener{
final String TAG = "MyAsyncTask";
ProgressDialog dialog;
Context context;
// コンストラクタ
public MyAsyncTask(Context context){
this.context = context;
}
@Override
protected void onPreExecute() {
// タスク開始時に実行される処理
Log.d(TAG, "onPreExecute");
dialog = new ProgressDialog(context);
dialog.setTitle("Please wait");
dialog.setMessage("Loading data...");
// dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
dialog.setCancelable(true);
dialog.setOnCancelListener(this);
dialog.setMax(100);
dialog.setProgress(0);
dialog.show();
}
@Override
protected Long doInBackground(String... params) {
// バッググラウンドで実行する処理 →時間が掛かる処理
Log.d(TAG, "doInBackground - " + params[0]);
// 進捗状態を更新したい時は、publishProgressを実行する →onProgressUpdateが呼び出される
// 時々、isCancelled()でチェックして、キャンセルされていればすぐに抜ける
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
// 進捗状態を更新する処理
Log.d(TAG, "onProgressUpdate - " + values[0]);
dialog.setProgress(values[0]);
}
@Override
protected void onCancelled() {
// 中断時の処理
Log.d(TAG, "onCancelled");
dialog.dismiss();
}
@Override
protected void onPostExecute(Long result) {
// タスク終了時に実行される処理
Log.d(TAG, "onPostExecute - " + result);
dialog.dismiss();
}
@Override
public void onCancel(DialogInterface dialog) {
Log.d(TAG, "Dialog onCancell... calling cancel(true)");
this.cancel(true);
}
}
- FragmentActivityを継承したActivityから、
- これで、時間のか掛かる処理をしている間、プログレス表示されるようになった!
- 基本的には、AsyncTaskはActivityから呼び出され、Activityに従属している
- 画面を回転させると、Activityの破棄と再生成が行われる
- AsyncTaskが処理を終了して、主であるActivityが違うものになっているので、落ちる
- これは従属している処理を無視して、Activityが破棄されてしまうというAndroidのシステム自体の欠陥だと思う。
- とりあえずの対策として、回転時にActivityを再生成しない様にする
- AndroidManifestのactivityタグに、android:configChanges属性を設定する
- 但し、APIレベル13以上の場合は、以下の様にする必要がある
- android:targetSdkVersion属性を12以下に設定することで2、
- この場合、回転時にレイアウトを変更したりすることが出来なくなってしまう。
- Activityを破棄させないのは、とりあえずの対策であって、根本対策にはならないかな。。
- 正統なやり方がわからない。。
- でも現状では、この方法が一番まともなので、これでやるしかないか。。。
- コンパイル時にエラーが出る時は、プロジェクトのビルドターゲットの設定をチェック
- ビルドターゲットを、プロジェクトを右クリック→プロパティ→Androidで、ビルドターゲットをAPI13以上に設定する
- AsyncTaskLoaderというクラスを使ってみる
- Genericsで、指定が必要なのは、
- 非同期処理 loadInBackgroundの引数・戻り値のみ
- 回転で、落ちなくなったが、ProgressDialogは消えてしまう。
- でも、たまに落ちるので、Dialogはdismissじゃなくて、hideするようにした
- また、2回目は、Progress表示されない
- 呼び出し側のActivityは、FragmentActivityを継承して、
- interfaceに、LoaderCallbacksを指定する。
public class SampleActivity extends FragmentActivity implements
LoaderCallbacks<Boolean> { // Activity生成時にCallbackルーチンが呼ばれる
static ProgressDialog dialog=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Toast.makeText(this, "Hello Android!", Toast.LENGTH_SHORT).show();
}
// 画面のタッチで、処理を開始
@Override
public boolean onTouchEvent(MotionEvent event) {
// Toast.makeText(SampleActivity.this,"TouchEvent X:" + event.getX() + ",Y:" + event.getY(), Toast.LENGTH_SHORT).show();
// Loaderの実行
// 第2引数で onCreateLoader に渡すBundleを指定
if (event.getAction() == MotionEvent.ACTION_UP){
getSupportLoaderManager().restartLoader(0, null, this);
}
return true;
}
// Loaderを新規作成して返す
// AsyncTask#onPreExecute()の処理を記述
@Override
public Loader<Boolean> onCreateLoader(int id, Bundle bundle) {
// ProgressDialog を生成する
dialog = new ProgressDialog(SampleActivity.this);
dialog.setTitle("title");
dialog.setMessage("message");
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
// ProgressDialog をキャンセルしたら Loader もキャンセルする
dialog.setCancelable(true);
dialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
Loader<Object> loader = getSupportLoaderManager().getLoader(0);
// Loader をキャンセルする
if (loader != null) {
((AsyncTaskLoader<?>) loader).cancelLoad();
loader = null;
}
}
});
// ProgressDialog を表示する
dialog.show();
// 生成した Loader を返します
return new SampleAsyncTaskLoader(this.getApplicationContext());
}
// Loader終了時の処理
// キャンセル時は呼ばれない
// AsyncTask#onPostExecute() の処理を記述
@Override
public void onLoadFinished(Loader<Boolean> loader, Boolean result) {
// ProgressDialog を破棄する
if (dialog != null) {
dialog.hide();
}
}
// Loaderの破棄時の処理
@Override
public void onLoaderReset(Loader<Boolean> loader) {
// ProgressDialog を破棄する
if (dialog != null) {
dialog.dismiss();
}
}
}
- 結局、ProgressDialog表示は、回転で消えてしまう
- どのように記述すべきか、よく分からないままだけど、これはちょっと保留とする。
- いろいろ調べたんだけど、これだ!という方法が見つからなくて、うまく再現も出来ない。
- たかがDialogなのに、何故こんなに面倒なんだろう。。
- 全然、うまく行かないので、ちゃんとしたやり方は、諦めることにした。
- AsyncTaskを使って、回転時にActivityを再生成しないという方法で、とりあえず行く事にしよう。。。
2なんか弊害もありそうな感じもあるけど。。
[top]
- RealSize ⇒端末のディスプレイ全体の大きさ3
- DisplaySize ⇒タイトルバーを含むコンテンツ領域の大きさ
- ViewSize ⇒指定したViewの大きさ
- ViewSizeはOnCreateでは確定していないので、普通はonWindowFocusChangedあたりで調べる
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Display display = getWindowManager().getDefaultDisplay();
Point p = new Point();
display.getRealSize(p);
Log.d("xxx","RealSize>>>>"+p.x+","+p.y);
display.getSize(p);
Log.d("xxx","DisplaySize>>>>"+p.x+","+p.y);
Log.d("xxx","ViewSize>>>>"+view.getWidth()+","+view.getHeight());
}
- ViewSizeは、Viewをカスタマイズしたクラスを作っているなら、
- onSizeChangedで、画面を回転させた時のサイズは簡単にわかる。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.d("xxx", ">>>>onSizeChanged! "+w+","+h);
}
- あるプログラムについて、Portraitの状態で起動して、各画面サイズを調べてみた
>>>>onSizeChanged! 1080,1410
RealSize>>>>1080,1920
DisplaySize>>>>1080,1776
ViewSize>>>>1080,1410 ⇒1776-1410 = 366
- 同じプログラムを、Landscapeの状態で起動して、各画面サイズを調べると、
>>>>onSizeChanged! 1776,738
RealSize>>>>1920,1080
DisplaySize>>>>1776,1080
ViewSize>>>>1776,738 ⇒1080-738 = 342
- 起動した時点の端末の向きが違うと、RealSizeとDisplaySizeは同じだけど、
- DisplaySizeからViewSizeを引いて、View以外の部品幅を計算すると、
- つまり、起動時にレイアウトの部品幅が決まり、回転してもそれは変わらないらしい。
- 起動時の向きによって、ViewSizeが変わってしまう場合があるようだ。
- これは、回転時にActivityを再生成しないようにしている場合だけかもしれない。
- 再生成した場合も、いずれかの部品の大きさが変化してしまう可能性はあるので結局は同じようなもんかな。
- 起動時の画面サイズでは無く、回転後の画面サイズというかViewSizeを求めたい
- まぁ、実際に一度でも回転させれば、簡単に分かるんだけど、
- 回転させずに求める一般的な方法があるんだろうか?
- 前項の例の場合は、ナビゲーションバーの位置が回転させても同じなので、
- DisplaySizeが変わらないため、View以外の部分の幅から、回転後のViewサイズを計算可能なんだけど、
- 端末によってはナビゲーションバーの位置が変わるので、ややこしい
- 回転後のナビゲーションバーの位置を知る方法があれば、可能なはずだけど。
- 一度だけユーザに向きを変えてもらうように指示して、Preferenceに記録する手も考えたけど、
- 起動時の向きによって、ViewSizeが変わる可能性を考えると、ちょっと面倒だ。
- まぁ、やりたいことは、
- 描画にものすごく時間が掛かるデータなので、回転させても描画できるような最小の大きさを求めたい。
- 大きめにとれば良いんだけど、出来るだけ沢山のBitmapを保持したいので、あまり大きくしたくない4。
- というわけで、PortraitとLandscapeでの正確なViewSizeを予め知りたいんだけど。。
- 自分の作成しているアプリで、各画面サイズを調べてみた
機種 | RealSizeP | DisplaySizeP | ViewSizeP | ViewSizeL | Disp-View幅 |
LG Nexus5 | 1080 x 1920 | 1080 x 1776 | 1080 x 1410 | 1776 x 714 | 366 |
Asus Nexus7 | 1200 x 1920 | 1200 x 1824 | 1200 x 1562 | 1920 x 842 | 262 |
Acer B1-760HD | 720 x 1280 | 720 x 1216 | 720 x 1052 | 1224 x 556 | 164 |
機種 | RealSizeL | DisplaySizeL | ViewSizeL | ViewSizeP | Disp-View幅 |
LG Nexus5 | 1920 x 1080 | 1776 x 1080 | 1776 x 738 | 1080 x 1434 | 342 |
Asus Nexus7 | 1920 x 1200 | 1920 x 1104 | 1920 x 842 | 1200 x 1562 | 262 |
Acer B1-760HD | 1280 x 720 | 1224 x 720 | 1224 x 567 | 720 x 1063 | 153 |
- 起動時の方向によって、Portrait,LandscapeのViewSizeが変わる場合がある ⇒Nexus5, B1-760HD
- これは、起動時に生成されるView以外の部品幅が異なることによる
- DisplaySizeは、向きが変わるとナビゲーションバーの配置により変わる場合がある ⇒Nexus7, B1-760HD
- 注:これは特定のレイアウトの結果であり、レイアウトの違いによって異なる結果になる可能性がある
- やはり実際に回転されるまでは、起動時のViewSizeで描画して、
- 実際に回転してから、新しいViewSizeで再描画、上書きするようにするべきか。
- と思っていたんだけど、回転後は中心位置を合わせて、Bitmapを置き直すだけにした。
- Bitmapの大きさが合わないので、左右或いは上下に描画されない部分が出来るけど、そのまま。
- 再表示した時に、初めてBitmapの大きさを合わせるようにした。
- 回転はめったにしないので、実際、これで十分か。。
- というか、表示するかどうか分からない領域を描くのもやはり勿体無い。
- 但し、回転直後は、BitmapとViewSizeの違いがあるので、
- そのままズームやスクロールするとズレるので、その差分を補正する必要がある。
// Portrait
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// Landscape
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// 自動回転
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- 但し、直ちに反映されるわけじゃないので、続けてやると最後の設定だけになる。
Configuration config = getResources().getConfiguration();
switch (config.orientation) {
case Configuration.ORIENTATION_LANDSCAPE:
Log.d("xxx","Orientation>>>>Landscape");
break;
case Configuration.ORIENTATION_PORTRAIT:
Log.d("xxx","Orientation>>>>Landscape");
break;
case Configuration.ORIENTATION_SQUARE:
Log.d("xxx","Orientation>>>>Square");
break;
}
- 特殊なデバイスで無い限り、Squareにはならないと思う
switch (activity.getWindowManager().getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
// 回転なし
break;
case Surface.ROTATION_90:
// 左90°回転
break;
case Surface.ROTATION_180:
// 180°回転
break;
case Surface.ROTATION_270:
// 右90°(左270°)回転
break;
}
- 回転なしっていうのが、常にPortraitになるとは限らないかもしれない。
3以下のやり方は、Android4.2以上じゃないと使えないらしいので注意!
4というか、ピッタリじゃないと気持ち悪いだけなんだけど。。
[top]
- 以前のコードと同じように書いているのに、setOnClickListenerでコンパイルエラーで嵌った。、
- Androidのバージョンの違いで、何か変更があったのかと思ったんだけど、
- ボタンなのに、Dialog関係のエラーになっていて、???という感じ
- 最初から、新規に以前のコードだけにしてみるとエラーにならないし。。
- 丸一日悩んでいたんだけど、以下の記事を見て、なるほどと思った。
- Eclipseで、エラー行をクリックして、Importするのは便利なんだけど、
- 違うライブラリをインポートしてしまうことがあるので、要注意!
- 何かの操作をした時に、Eclipseのワークスペースが、全部消えてしまった。
- パッケージ・エクスプローラに何も表示されなくなった。
- ウィンドウ→設定で、設定ダイアログを出して、
- 一般→開始およびシャットダウンで、起動時にワークスペースをプロンプトにチェック
- ワークスペースのディレクトリの設定に間違いは無い様にみえる
- C:\Android\pleiades\workspace
- が、ファイルが何も入っていない。。そんなことがあるのか!?
- で、ファイル名でサーチしてみると、Dドライブに入っていた。
- ワークスペースをDドライブに変更していたのを忘れていた。
- 何かの拍子に、設定が初期状態になってしまったらしい。。という落ちでした。
- AsyncTaskの処理内でPreferenceを読もうとしたら、MODE_PRIVATEが定義されていない。
- Activityで呼び出す場合はContextを継承してるから良いけど、そうじゃなければ、Contextから指定しないとダメだ
- this.contextに、Contextを入れてあったので、以下のようにした
SharedPreferences sp = this.context.getSharedPreferences("GDSviewer", this.context.MODE_PRIVATE);
- クラスで定義されてるから当たり前なんだけど、
- AndroidのContextって、何故か煩わしい感じがするなぁ。。
- Eclipseから、アプリケーションを実行すると、以下のエラーで起動しない
Android 起動!
adb is running normally.
Performing hm.orz.bluefish.gdsviewfile.GDSviewMain activity launch
Uploading xxxxx.apk onto device 'xxxxxxxx'
Failed to install xxxxx.apk on device 'xxxxxxxx': タイムアウト
起動はキャンセルされました!
- タイムアウトの時間を延ばしてみる。
- ウィンドウ→設定→Android→DDMSで、ADB接続タイムアウトはデフォルトで5000msなので、30000msにしてみた
- 確かにタイムアウトの時間は長くなったが、やっぱりダメだった
- USBケーブルを一度抜いて、もう一度接続し直したら直った。
- AsyncTaskでファイルの読み込み中にProgressDialogを表示するようにして、画面を回転するとこのエラーで落ちる。
- 画面回転でActivityが再生成されるので、それに関連するDialogがダメになるらしい
- 結局これも、AndroidManifest.xmlでActivityの再生成をしないように設定することで、まぁ誤魔化した。
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
- ProgressDialogのDismissの処理をちゃんとやれば良いみたいなんだけど、よくわからないので(以下、参考)
- AsyncTaskでオフスクリーン描画させるようにしたら、稀に、以下のエラーでいきなり落ちるようになった。
Fatal signal 7 (SIGBUS), code 2, fault addr 0xaf0882a8 in tid 30865 (AsyncTask #4)
必ず落ちるわけではないので、未だ原因は突き止めていないけど、
- 例えば描画が終わっていないのに、オフスクリーンの転送が始まったりしてるのかもしれない。
- とりあえず描画メソッドにsynchronizedを付けたら、落ちなくなった。
- でもそうすると、マルチタスクにした意味が無いか。。
- ちゃんと原因を確かめないとダメだな。。。(未解決)
[top]
- レイアウトにEditTextがあると、画面が開いたときにソフトキーボードが表示される。
- Backキーで消えるんだけど、EditTextをクリックした時に表示されれば良いので、以下のように設定する
- AndroidManifest.xmlで、Activityに以下を設定
android:windowSoftInputMode="stateAlwaysHidden"
- 或いは、ActivityのonCreateで、プログラムから設定
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
- XMLで、android:numericでintegerを指定することで、数値入力に限定できる
- android:numericが integer の場合は、数値しか入力できないので、マイナス値や小数点以下の数値は入力できない
- マイナスを入力できるようにするには signed、小数点を入力するには decimal を指定する
- 追記:android.numericは非推奨らしいので、以下の方が良い
android:inputType="number" // 整数値の場合
android:inputType="numberDecimal" // 小数点含む数値
android:inputType="numberSigned" // 符号付き整数値
- '1-16'のような入力をしたい場合、numberSignedだけだとダメだが、digitsを併用すれば行けるようだ
android:digits="0123456789-"
android:inputType="numberSigned"
- 数値のように簡単に指定する方法はないので、入力可能な文字を全て digits で指定する
android:inputType="textEmailAddress"
android:digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"
- 或いは、プログラムでフィルタを設定すれば良いらしい
public class EditTextValidate extends Activity {
private InputFilter[] filters = { new MyFilter() };
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
EditText edittext = (EditText) findViewById(R.id.edittext);
edittext.setFilters(filters);
}
class MyFilter implements InputFilter {
@Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
if( source.toString().matches("^[a-zA-Z0-9]+$") ){
return source;
}else{
return "";
}
}
}
}
- IMEが表示されたとき、Activityの画面が隠れてしまう場合、
[top]
[プログラムの部屋に戻る]