AndroidのChromeブラウザのアドレスバーにURLが表示されます。それをタップすると、ポップアップWindowが表示され、その中でURLまたは検索キーワードを入力できます。
同じようなURL入力Dialogを実現するために、色々やったことを纏めます。
機能概要
- アドレスバーをタップしたら、入力ダイアログが画面一番上に表示します。
- ダイアログの表示とともに、URLテキストが選択された状態になり、キーボードを表示します。
- 入力フィールドで何も選択されない状態で長押すると、ペストメニューが表示されます。
- 入力フィールドで文字を選択すると、コピペActionbarが表示されます。
- バックボタンをクリックすると、ダイアログがクロースされ、元のActivity画面に戻ります。
- 入力ダイアログの外でタップすると、ダイアログがクロースされます。
- キーボードのGoキーをクリックしたら、ダイアログがクロースされ、入力された内容を元のActivity画面に反映します。
PopupWindowで実現
PopupWindowを利用すれば、簡単に実現できますが、EditTextの長押イベントを拾えない致命的な問題があるため、PopupWindowを諦めて、DialogFragmentを利用することにしました。
※本記事を書き始めると、PopupWindowを継承して、onTouchEventメソッドを独自に実装したら解決になるかもしれないと思っていますが、試してないので、何もいえません。
DialogFragmentで実現
style設定で画面の一番上にダイアログを表示します。
<style name="EditUrlDialogTheme" parent="android:style/Theme.Holo.Light.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowActionModeOverlay">false</item>
<item name="android:gravity">top|left</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
</style>
・windowIsFloatingにfalseに記載しないと、画面の一番上に表示されません。
・windowActionModeOverlayにfalseを記載しないと、入力フィールド長押で表示されたActionbarが入力フィールドの上に被ってしまいます。
・windowBackgroundにtransparentを設定しないと、OSのステータスバーが上書きされて、見えなくなります。
カスタマイズDialog
上記Styleを弄ったから、ダイアログ以外の領域をタップする場合、ダイアログがクロースされなくなった為、onTouchEventを独自実装します。
public class EditUrlDialog extends Dialog {
public EditUrlDialog(Context context) {
super(context);
}
public EditUrlDialog(Context context, int themeResId) {
super(context, themeResId);
}
protected EditUrlDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
super(context, cancelable, cancelListener);
}
/**
* ダイアログの外でタッチされた場合、ダイアログを閉じる。
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
dismiss();
return true;
}
}
カスタマイズDialogFragment
本来はDialogFragmentを利用する場合、透明度が自動的に設定されますが、上記style設定(windowBackgroundにtransparent)を弄ったせいで、ダイアログが表示された時は、後ろのActivity画面が暗くなりませんでした。
そのため、プログラムで透明度を設定します。
public class EditUrlDialogFragment extends DialogFragment {
// URL入力フィールド
private EditTextExtension txtUrlInput;
/**
* 入力値をActivityに返すインタフェース。
*/
public interface EditNameDialogListener {
void onFinishEditDialog(String inputText);
}
/**
* コンストラクタ。
*/
public EditUrlDialogFragment() {
// コンストラクタの代わりに、newInstanceメソッドを使ってください。
}
/**
* DialogFragmentのインスタンスを生成する。
*
* @param url 初期表示URL
* @return DialogFragmentのインスタンス
*/
public static EditUrlDialogFragment newInstance(String url) {
EditUrlDialogFragment frag = new EditUrlDialogFragment();
Bundle args = new Bundle();
args.putString("url", url);
frag.setArguments(args);
return frag;
}
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Dialog dialog = new EditUrlDialog(getActivity(), R.style.EditUrlDialogTheme);
dialog.setContentView(R.layout.url_input_dialog_layout);
txtUrlInput = (EditTextExtension)dialog.findViewById(R.id.txtUrlInput);
txtUrlInput.setText(getArguments().getString("url"));
txtUrlInput.setDialog(dialog);
txtUrlInput.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER)) {
dialog.dismiss();
((MainActivity) getActivity()).onFinishEditDialog(txtUrlInput.getText().toString());
return true;
}
return false;
}
});
txtUrlInput.requestFocus();
// 透明度を設定することで、Activity画面を暗くする
updateBehindWindow(0.4f);
return dialog;
}
@Override
public void onStart() {
super.onStart();
// キーボードを強制表示させる
KeyboardUtils.show(getDialog().getContext(), txtUrlInput);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
// 透明度を設定することで、Activity画面を正常状態に戻る
updateBehindWindow(1f);
}
/**
* ウインドウの透明度を設定する。
*
* @param value 透明度
*/
private void updateBehindWindow(float value) {
Window window = getActivity().getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.alpha = value;
window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.setAttributes(lp);
}
}
カスタマイズEditText
キーボードが表示された状態で、バックボタンをクリックすると、キーボードが消えるだけになり、ダイアログをクロースするために、もう一回バックボタンをクリックする必要があります。
Chromeと同じように、バックボタンをクリックしたら、入力ダイアログも消えるようにするために、カスタマイズのEditTextを実装して、バックボタン押下イベントをキーボードより先に検知しなければなりません。
レイアウト定義は次の通り。
url_input_dialog_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1">
<bridge.jp.co.mybrowser.ui.EditTextExtension
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/txtUrlInput"
android:imeOptions="actionGo|flagForceAscii"
android:gravity="left|center_vertical"
android:selectAllOnFocus="true"
android:background="@android:color/white"
android:textColor="@android:color/black"
android:paddingLeft="10dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:paddingRight="10dp"
android:singleLine="true"
android:focusable="true" >
</bridge.jp.co.mybrowser.ui.EditTextExtension>
</LinearLayout>
カスタマイズEditTextのソースコードは次の通り。
public class EditTextExtension extends EditText {
private Dialog dialog;
private Context context;
public EditTextExtension(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (dialog != null) {
KeyboardUtils.hide(context, this);
dialog.dismiss();
}
}
return false;
}
public void setDialog(Dialog dialog) {
this.dialog = dialog;
}
}