package com.example.wordbook; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.EditText; import android.widget.ListView; import android.widget.WrapperListAdapter; import com.example.wordbook.common.Common; import com.example.wordbook.common.DBAccess; import com.example.wordbook.common.Data; /** * 一覧画面クラス */ public class WBListActivity extends Activity implements OnScrollListener { /** TAG */ private static final String TAG = WBListActivity.class.getSimpleName(); /** フラグ選択ダイアログ */ private static final int DIALOG_FLAG = 1; /** メモ編集ダイアログ */ private static final int DIALOG_EDIT = 2; /** コマンド選択ダイアログ */ private static final int DIALOG_COMM = 7; /** インスタンス状態用データ先頭キー */ private static final String IS_TOP = "top"; /** インスタンス状態用データ位置キー */ private static final String IS_POS = "pos"; /** インスタンス状態用データリストキー */ private static final String IS_DATA = "data"; /** 単語帳DBデータインデックス */ private int mIndex; /** 単語帳DBデータ項目数 */ private int mCount; /** 出題方法 */ private int mMode; /** 出題順序 */ private int mSort; /** 短縮コマンド */ private int mComm; /** 試験データ位置 */ private int mPos = 0; /** 試験データリスト(表示分) */ private List mData = new ArrayList(); /** ListView */ private ListView mListView; /** ListView footer */ private View mFooter; /** ExecutorService */ private ExecutorService mES; /* * (非 Javadoc) * * @see android.app.Activity#onCreate(android.os.Bundle) */ @SuppressWarnings("unchecked") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate()"); // 単語帳DBデータ情報取得 String title = ""; Bundle ext = getIntent().getExtras(); if (ext == null) { Log.w(TAG, "onCreate() Intent==null"); finish(); } else { mIndex = ext.getInt(Common.INTENT_INDEX, 0); mCount = ext.getInt(Common.INTENT_COUNT, 0); title = ext.getString(Common.INTENT_TITLE); } Log.d(TAG, "index=" + mIndex + "/count=" + mCount + "/title=" + title); // 状態復帰 int top = 0; if (savedInstanceState != null) { top = savedInstanceState.getInt(IS_TOP); mPos = savedInstanceState.getInt(IS_POS); mData = (List) savedInstanceState.getSerializable(IS_DATA); } Log.d(TAG, "top=" + top + "/pos=" + mPos + "/data=" + mData.size()); // 設定取得 mMode = Common.getMode(getApplicationContext()); mSort = Common.getSort(getApplicationContext()); mComm = Common.getComm(getApplicationContext()); // レイアウト設定 setContentView(R.layout.wblist); // タイトル設定 setTitle(title); // ListView設定 mFooter = getLayoutInflater().inflate(R.layout.list_footer, null); mListView = (ListView) findViewById(R.id.list); // 試験データ追加済み時はフッタ削除 if (mData.size() != mCount) { mListView.addFooterView(mFooter); } // アダプタ設定 WBListAdapter adp = new WBListAdapter(this, R.layout.list_wblist, mData); mListView.setAdapter(adp); // イベントリスナ設定 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { doItemClick(parent, position, id); } }); mListView.setFastScrollEnabled(true); mListView.setSelectionFromTop(top, 0); mListView.setOnScrollListener(this); } /* * (非 Javadoc) * * @see android.app.Activity#onResume() */ @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume()"); // ExecutorService開始 mES = Executors.newSingleThreadExecutor(); } /* * (非 Javadoc) * * @see android.app.Activity#onPause() */ @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause()"); // タスク動作時はキャンセル if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) { Log.w(TAG, "onPause()=RUNNING"); // キャンセル mTask.cancel(true); } // ExecutorService終了 final long TIMEOUT = 100; mES.shutdown(); try { // タスク終了待ち if (!mES.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS)) { mES.shutdownNow(); Log.w(TAG, "onPause()=TIMEOUT"); } } catch (InterruptedException e) { mES.shutdownNow(); Thread.currentThread().interrupt(); Log.e(TAG, "onPause()=InterruptedException"); e.printStackTrace(); } } /* * (非 Javadoc) * * @see android.app.Activity#onSaveInstanceState(android.os.Bundle) */ @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); Log.d(TAG, "onSaveInstanceState()"); // 状態保存 outState.putInt(IS_TOP, mListView.getFirstVisiblePosition()); outState.putInt(IS_POS, mPos); outState.putSerializable(IS_DATA, (Serializable) mData); } /* * (非 Javadoc) * * @see android.app.Activity#onCreateDialog(int) */ @Override protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_FLAG: // フラグ選択ダイアログ生成 return getDialogFlag(); case DIALOG_EDIT: // メモ編集ダイアログ生成 return getDialogEdit(); case DIALOG_COMM: // コマンド選択ダイアログ生成 return getDialogComm(); default: Log.w(TAG, "onCreateDialog() id=" + id); return null; } } /* * (非 Javadoc) * * @see android.app.Activity#onPrepareDialog(int, android.app.Dialog) */ @Override protected void onPrepareDialog(int id, Dialog dialog) { switch (id) { case DIALOG_FLAG: // フラグ選択ダイアログ準備 // 処理無し break; case DIALOG_EDIT: // メモ編集ダイアログ準備 setDialogEdit(dialog); break; case DIALOG_COMM: // コマンド選択ダイアログ準備 // 処理無し break; default: Log.w(TAG, "onPrepareDialog() id=" + id); break; } } /* * (非 Javadoc) * * @see android.widget.AbsListView.OnScrollListener#onScroll(android.widget. * AbsListView, int, int, int) */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { Log.v(TAG, "onScroll()=" + totalItemCount + "/" + firstVisibleItem + "/" + visibleItemCount); if (totalItemCount == firstVisibleItem + visibleItemCount) { // 最終行表示済み時は追加読み込み loadData(); } else if (totalItemCount < firstVisibleItem + visibleItemCount) { // 異常時は位置補正(フッタ処理時) view.setSelection(totalItemCount - visibleItemCount < 0 ? 0 : totalItemCount - visibleItemCount); } } /* * (非 Javadoc) * * @see * android.widget.AbsListView.OnScrollListener#onScrollStateChanged(android * .widget.AbsListView, int) */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } /* * (非 Javadoc) * * @see android.app.Activity#finish() */ @Override public void finish() { super.finish(); // overridePendingTransition(0, 0); } /** * フラグ選択ダイアログ生成 * * @return フラグ選択ダイアログ */ private AlertDialog getDialogFlag() { // ダイアログ生成 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(false); builder.setTitle(getString(R.string.item_flag)); // イベントリスナ設定 builder.setNegativeButton(android.R.string.cancel, null); // アダプタ設定 FlagAdapter adapter = new FlagAdapter(this, Common.getFlagItemList(getApplicationContext()), R.layout.dlg_flag, new String[] { Common.FLAG_TEXT }, new int[] { R.id.textView }); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 選択フラグ更新 mData.get(mPos).flag = which; // 更新通知 notifyUpdate(); // データ更新タスク呼び出し saveData(mData.get(mPos)); } }); return builder.create(); } /** * メモ編集ダイアログ生成 * * @return メモ編集ダイアログ */ private AlertDialog getDialogEdit() { // ダイアログ生成 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(false); builder.setTitle(getString(R.string.item_edit)); // インフレート対象にダイアログ用テーマを反映 LayoutInflater inflater = builder.create().getLayoutInflater(); View view = inflater.inflate(R.layout.dlg_edit, null); // ビュー取得 final EditText edit = (EditText) view.findViewById(R.id.editText); // イベントリスナ設定 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 選択メモ更新 mData.get(mPos).data2 = edit.getText().toString(); // 更新通知 notifyUpdate(); // データ更新タスク呼び出し saveData(mData.get(mPos)); } }); builder.setNegativeButton(android.R.string.cancel, null); builder.setView(view); return builder.create(); } /** * コマンド選択ダイアログ生成 * * @return コマンド選択ダイアログ */ private AlertDialog getDialogComm() { // ダイアログ生成 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(false); builder.setTitle(getString(R.string.title_commdialog)); // イベントリスナ設定 builder.setNegativeButton(android.R.string.cancel, null); // アイテム設定 builder.setItems(R.array.comm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // クリック&コマンド識別 doCommClick(which, mPos); } }); return builder.create(); } /** * メモ編集ダイアログ準備 * * @param dialog * ダイアログ */ private void setDialogEdit(Dialog dialog) { // 現状メモ設定 EditText edit = (EditText) dialog.findViewById(R.id.editText); edit.setText(mData.get(mPos).data2); } /** * アイテムクリック処理 * * @param parent * AdapterView * @param position * Position * @param id * ID */ private void doItemClick(AdapterView parent, int position, long id) { Log.d(TAG, "doItemClick() id=" + id); // クリック領域識別(アイコン) if (id < 0) { // アダプタからロングクリックイベントを送信できないためIDで識別 if (id == Common.ITEM_CLICK) { // クリック&コマンド識別 doCommClick(mComm, position); } else if (id == Common.ITEM_LONG_CLICK) { // ロングクリック doCommLongClick(mComm, position); } else { Log.w(TAG, "doItemClick() id=" + id); } } else { // 表示状態更新 Data data = (Data) parent.getItemAtPosition(position); switch (data.stat & Data.STAT_SHOW) { case Data.STAT_QUESTION: // break; case Data.STAT_ANSWER: // 非表示->表示 data.stat = Data.STAT_SHOW; break; case Data.STAT_SHOW: // 表示->非表示 data.stat = mMode; break; default: Log.w(TAG, "doItemClick() stat=" + data.stat); break; } // 更新通知 notifyUpdate(); // 単語帳DBデータ更新 saveData(data); } } /** * アイコンクリック処理 * * @param command * Command * @param position * Position */ @SuppressWarnings("deprecation") private void doCommClick(int command, int position) { // 選択アイテム位置保存 mPos = position; // コマンド識別 switch (command) { case 0: // フラグ選択ダイアログ表示 showDialog(DIALOG_FLAG); break; case 1: // メモ編集ダイアログ表示 showDialog(DIALOG_EDIT); break; case 2: // TODO Log.d(TAG, "SOUND"); break; case 3: // TODO Log.d(TAG, "LINK"); break; default: Log.w(TAG, "doCommClick() command=" + command); break; } } /** * アイコンロングクリック処理 * * @param command * Command * @param position * Position */ @SuppressWarnings("deprecation") private void doCommLongClick(int command, int position) { // 選択アイテム位置保存 mPos = position; // コマンド選択ダイアログ表示 showDialog(DIALOG_COMM); } /** * 更新通知 */ private void notifyUpdate() { // フッタ設定時対応更新通知 if (mListView.getAdapter() instanceof WrapperListAdapter) { ((BaseAdapter) ((WrapperListAdapter) mListView.getAdapter()) .getWrappedAdapter()).notifyDataSetChanged(); } else { ((BaseAdapter) mListView.getAdapter()).notifyDataSetChanged(); } } /** * データ更新タスク呼び出し * * @param data * 更新データ */ private void saveData(Data data) { // データ更新タスク依頼 try { if (mES.submit(new DBAccess.SaveDataTask(getApplicationContext(), mIndex, data)) == null) { Log.w(TAG, "saveData() Future==null"); } } catch (RejectedExecutionException e) { Log.e(TAG, "saveData()=RejectedExecutionException"); e.printStackTrace(); } } /** 試験データ追加タスク */ private AsyncTask> mTask = null; /** 試験データ追加フラグ */ private List mLoad = new ArrayList(); /** * 試験データ追加タスク呼び出し */ private void loadData() { final int ADD_SIZE = 100; // 試験データ追加済み時は処理無し if (mData.size() == mCount) { Log.w(TAG, "loadData() size=" + mCount); return; } // 試験データ追加タスク動作時は処理無し if (mTask != null && mTask.getStatus() == AsyncTask.Status.RUNNING) { Log.w(TAG, "loadData()=RUNNING"); return; } // 試験データ追加タスク実行 mTask = new AsyncTask>() { @Override protected void onPreExecute() { super.onPreExecute(); Log.d(TAG, "onPreExecute()"); // ランダム時の初回呼び出し時のみ追加フラグリストを初期化 if (mSort == 0 && mData.size() == 0 && mLoad.size() == 0) { // 識別子は1から for (int i = 1; i <= mCount; i++) { mLoad.add(i); } Collections.shuffle(mLoad); } } @Override protected void onPostExecute(List result) { if (result.size() > 0) { // 試験データ更新 mData.addAll(result); // 更新通知 notifyUpdate(); if (mData.size() == mCount) { // 試験データ追加済み時はフッタ削除 mListView.removeFooterView(mFooter); } } else { // 試験データ追加無し時はフッタ削除 mListView.removeFooterView(mFooter); if (mData.size() == 0) { // フッタ設定時はsetEmptyView()が無効のため手動で可視化 findViewById(R.id.empty).setVisibility(View.VISIBLE); mListView.setVisibility(View.GONE); } } Log.d(TAG, "onPostExecute()=" + result.size()); super.onPostExecute(result); } @Override protected List doInBackground(Void... params) { Log.d(TAG, "doInBackground()"); List data = null; // 単語帳DBデータリスト取得 if (mSort == 0) { // ランダム時は追加フラグリストから取得候補リストを取得 List in = new ArrayList(); for (int i = 0; i < ADD_SIZE && i < mLoad.size(); i++) { in.add(mLoad.get(i)); } if (in.size() == 0) { data = new ArrayList(); } else { data = DBAccess.getDBDataList(getApplicationContext(), mIndex, 0, ADD_SIZE, 0, in); } // 追加フラグリスト更新 mLoad.removeAll(in); } else { data = DBAccess.getDBDataList(getApplicationContext(), mIndex, mData.size(), ADD_SIZE, mSort, null); } // 表示状態補正 for (Data d : data) { d.stat |= mMode; } return data; }; }.execute(); } }