I have an AlertDialog with just some text, a NumberPicker, an OK, and a Cancel.
package org.dyndns.schep.example;
import android.os.Bundler;
import android.view.View;
import android.widget.NumberPicker;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
public class FooFragment extends DialogFragment {
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mParent = (MainActivity) activity;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
mParent.setFoo(foo());
}
})
.setNegativeButton(android.R.string.cancel, null);
View view = getActivity().getLayoutInflater.inflate(
R.layout.dialog_foo, null);
mPicker = (NumberPicker) view.findViewById(R.id.numberPicker1);
mPicker.setValue(mParent.getFoo());
builder.setView(view);
return builder.create();
}
public int foo() {
return mPicker.getValue();
}
private MainActivity mParent;
private NumberPicker mPicker;
}
(This dialog doesn't yet do the things it should to preserve state on Pause and Resume, I know.)
I would like the "Done" action on the soft keyboard or other IME to dismiss the dialog as though "OK" were pressed, since there's only the one widget to edit.
It looks like the best way to deal with an IME "Done" is usually to setOnEditorActionListener on a TextView. But I don't have any TextView variable, and NumberPicker doesn't obviously expose any TextView, or similar editor callbacks. (Maybe NumberPicker contains a TextView with a constant ID I could search for using findViewById?)
NumberPicker.setOnValueChangedListener does get triggered on the "Done" action, but it also fires when tapping or flicking the list of numbers, which definitely should not dismiss the dialog.
Based on this question, I tried checking out setOnKeyListener, but that interface didn't trigger at all when using the soft keyboard. Not a total surprise, since the KeyEvent documentation suggests it's meant more for hardware events, and in recent APIs the soft keyboard won't send them at all.
How can I connect the IME "Done" to my dialog's "OK" action?
Edit: From the looks of the source, a NumberPicker layout does contain a EditText, but its id is id/numberpicker_input in package com.android.internal. Using that would not be easy, and is obviously discouraged. But it seems like there might only be hack ways to get the behavior I want.
How can I connect the IME "Done" to my dialog's "OK" action?
The problem is that you can't pass the IME's events if you don't have a listener set on the TextView widget which currently works with the IME. One way to do what you want is to hook our own logic to the NumberPicker's child which works with the IME(like you already talked in the last part of your question). To avoid using certain ids or other layout tricks(which can be problematic) to get a hold of that widget, you could use a greedy tactic, setting the listener to any widget from the NumberPicker which could trigger the desired event(TextViews or any subclass of TextView). Something like this:
private AlertDialog mCurrentDialog;
private List<TextView> mTargets = new ArrayList<TextView>();
private OnEditorActionListener mListener = new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
// if a child of NumberPicker triggers the DONE editor event
// get a reference to the positive button(which you use in your
// code) and click it
mCurrentDialog.getButton(Dialog.BUTTON_POSITIVE).performClick();
}
return false;
}
};
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// ...
mPicker = (NumberPicker) view.findViewById(R.id.numberPicker1);
mPicker.setValue(mParent.getFoo());
// clear any previous targets
mTargets.clear();
// find possible targets in the NumberPicker
findTextViews(mPicker);
// setup those possible targets with our own logic
setupEditorListener();
builder.setView(view);
// get a reference to the current showed dialog
mCurrentDialog = builder.create();
return mCurrentDialog;
}
Where the methods are:
private void findTextViews(ViewGroup parent) {
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
final View child = parent.getChildAt(i);
if (child instanceof ViewGroup) {
findTextViews((ViewGroup) child);
} else if (child instanceof TextView) {
mTargets.add((TextView) child);
}
}
}
private void setupEditorListener() {
final int count = mTargets.size();
for (int i = 0; i < count; i++) {
final TextView target = mTargets.get(i);
target.setOnEditorActionListener(mListener);
}
}
The other possible(and reasonable) solution(like Naveen already mentioned in his comment) is to use one of the ports of the NumberPicker class(or modify the one from the SDK) out there and insert your own widget ids(which will make getting a reference to the widget a simple task). This would be easier to implement now but inconvenient to maintain on the long run.
My search and experimentation has also turned out empty handed. That said I wouldn't encourage this kind of behavior as it doesn't make much sense.
If you need a NumberPicker it is because you want the feature of scrolling to the value that you need, not entering it (though it is possible). If you want to enter the value you'd use an EditText and you would be able to implement what you need without problems.
Alternatively you have to copy NumberPicker implementation from the source and then change it according to your needs (fx by adding ime options).
Related
When using a TimePicker set to spinner mode, if I click on a number (minutes or hours), the number keyboard shows up.
But whenever I scroll any of the spinners, the keyboard changes to the text inputType.
How can I avoid this?
I've tried calling timePicker.setAddStatesFromChildren(true) and setting an OnTimeChangedListener, but that won't work, for if I scroll just enough for the spinner to move but not for the time to change, the listener is not triggered but the keyboard changes to text inputType anyway.
Also, timePicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS) is not what I'm looking for, for I still want the keyboard to show up, but only that it won't change its inputType to text.
In the end, I couldn't find which view was making the keyboard appear. I tried removing the next focus from every view inside the TimePicker, but nothing. Then, I thought the problem was that, since I was using a 24-hour format spinner, the view to blame was the hidden AM/PM CustomTextView inside the TimePicker. I made it non-focusable, but still the same issue. So I concluded that the problem was somewhere in the implementation of the TimePicker itself, who manages some event and displays the keyboard.
So I decided to iterate over the NumberPickers inside TimePicker —which are three— and set an OnScrollListener on them that hides the keyboard. But still, I get to see the text keyboard appearing before being dismissed. But that's the best I've managed to do.
public static <T extends View> List<T> getViewsByClassNameFromView(ViewGroup viewGroup, Class<T> clazz) {
final List<T> matches = new LinkedList<>();
final int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = viewGroup.getChildAt(i);
if (clazz.isInstance(child)) {
matches.add((T) child);
} else if (child instanceof ViewGroup) {
matches.addAll(getViewsByClassNameFromView((ViewGroup) child, clazz));
}
}
return matches;
}
public void hideSoftKeyboard(View view) {
InputMethodManager imm =
(InputMethodManager) view.getContext().getApplicationContext()
.getSystemService(Activity.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
view.clearFocus();
}
private void fixTimePicker() {
final List<NumberPicker> numberPickers = ViewUtil.getViewsByClassNameFromView(timePicker, NumberPicker.class);
for (final NumberPicker numberPicker: numberPickers) {
numberPicker.setOnScrollListener(new NumberPicker.OnScrollListener() {
#Override
public void onScrollStateChange(NumberPicker view, int scrollState) {
hideSoftKeyboard(view);
}
});
}
}
android:descendantFocusability="blocksDescendants" use this attribute in DatePicker Xml and it will resolve your issue.
I have extended EditTextPreference, but the Dialog Message won't display. This happens if I add the dialogMessage programatically or in the the preferences.xml.
Here is my onBindDialogView:
AutoCompleteTextView editText = mEditText;
editText.setText(getText());
ViewParent oldParent = editText.getParent();
if (oldParent != view) {
if (oldParent != null) {
((ViewGroup) oldParent).removeView(editText);
}
onAddEditTextToDialogView(view, editText);
}
Is the dialog message really absent? It's probably there but its text color might make it less (or not) visible. (Or try to dismiss software keyboard). Try experimenting with dialog messages having a number of "\n" characters and see if that affects dialog layout. If so, it means the dialog message is actually there but camouflaged too well.
EditTextPreference brings a text view (in the preference_dialog_edittext.xml) that replaces the existing one (in the alert_dialog.xml) for the dialog message, but unfortunately with different text style, which might cause a visibility problem under certain themes. Even their sizes are different.
One solution might be to obtain the text color and size from the original text view to be replaced and apply them to the new one, but I would suggest retaining the original text view instead, because it's more likely to be visually consistent if there are any future UI changes. Try adding the following overrides
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setMessage(getDialogMessage()); // bring back the original text view
}
protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
int id = getContext().getResources().getIdentifier("edittext_container", "id", "android");
ViewGroup container = (ViewGroup) dialogView.findViewById(id);
container.removeAllViews(); // remove the new text view
super.onAddEditTextToDialogView(dialogView, editText);
}
If you think the dialog message and the edittext view is too far apart, they can be brought together a little closer by adding another override:
protected void showDialog(Bundle state) {
super.showDialog(state);
int id = getContext().getResources().getIdentifier("message", "id", "android");
TextView message = (TextView) getDialog().findViewById(id);
message.setPadding(message.getPaddingLeft(), message.getPaddingTop(), message.getPaddingRight(), 0);
}
and add the following line in the onAddEditTextToDialogView method after calling removeAllViews:
container.setPadding(container.getPaddingLeft(), 0, container.getPaddingRight(), container.getPaddingBottom());
Here is what I am aiming for:
I am unsure if I am doing this correctly. There are probably better,more efficient, and cleaner ways to do it, but I need to know how.
This layout was designed in xml and inflated via an inflater. The produced view was then placed into an AlertDialog. Thus, this is seen as an AlertDialog by the user.
My concern is with the tags section at the bottom. I want this to work like how Tumblr tags work. Type a string, hit the button, and a button with that tag name will show up in the blank section below it.
Now, if you click on those buttons (with their respective tag names), they will disappear from the frame.
I have several concerns.
I have trouble implementing listeners. If the AddTag button creates more buttons in the (currently invisible, but present) LinearLayout, then what about the created buttons? How do those buttons implement onClick listeners that will remove themselves from the LinearLayout if they were created in some inner method defined from the AddTag button's onClick method?
I am afraid about having to declare some of these views as FINAL in order to reference them in button methods and inner classes. I am now stuck because of this.
Do I have to define my own layout for the tag buttons? You see, a LinearLayout displays things one after the other, yes? I want to try to recreate how some social networking sites do it. Fill the layout with buttons from top to bottom, left to right. If there is no room left in the current row, go to the next one and add the tag button there. It's basically a dynamic LinearLayout that has autowrapping.
If there are any better ways of implementing this, please let me know a general step by step of what to do. I have not learned Fragments yet, but I think it may be VERY applicable here. Also, should I be creating a class that extends ViewGroup, inflating the XML there, and adding helper methods to handle things? I suppose from a DialogFragment I could then addView(the class I just created) and work from there?
Here is my current code by the way. I am stuck and stumped.
/**
* Opens a view for the user to define their new action and add it to the
* dictionary.
*
* #param view
*/
public void defineNewAction(View view) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
View viewToSet = inflater.inflate(
R.layout.define_new_action_window_layout,
null);
final EditText newActionName = (EditText) viewToSet
.findViewById(R.id.set_action_name);
final RadioGroup priorityGroup = (RadioGroup) viewToSet
.findViewById(R.id.radiogroup_set_priority);
final EditText goalTimeHours = (EditText) viewToSet
.findViewById(R.id.set_goal_time_hours);
final EditText goalTimeMinutes = (EditText) viewToSet
.findViewById(R.id.set_goal_time_minutes);
final EditText addTagsInput = (EditText) viewToSet
.findViewById(R.id.add_tags_input);
Button addTagButton = (Button) viewToSet.findViewById(R.id.btn_add_tags);
final ArrayList<String> tags = new ArrayList<String>();
final LinearLayout currentTagsLayout = (LinearLayout) viewToSet
.findViewById(R.id.current_tags);
addTagButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String tag = addTagsInput.getText().toString();
tags.add(tag);
Button newTag = new Button(builder.getContext());
int tagId = tag.hashCode();
if (tagId < 0)
tagId *= -1;
newTag.setId(tagId);
newTag.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Button toRemove = (Button) currentTagsLayout.findViewById(tagId);
currentTagsLayout.removeView(toRemove);
}
});
currentTagsLayout.addView(newTag);
}
});
builder.setTitle("Define your action.");
builder.setView(viewToSet);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface arg0, int arg1) {
String name = newActionName.getText().toString();
int priority = priorityGroup.getCheckedRadioButtonId();
int goalHours = Integer
.parseInt(goalTimeHours.getText().toString());
int goalMinutes = Integer.parseInt(goalTimeMinutes.getText()
.toString());
}
});
builder.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface arg0, int arg1) {
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
I have trouble implementing listeners
There's no trouble. For the functionality you are trying to achieve, you can keep adding buttons and setting OnClickListeners on them. You don't even need to give them an id, or track them in any way. The following code inside your OnClickListener will do:
newTag.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use the View given to you
currentTagsLayout.removeView(v);
}
});
I am afraid about having to declare some of these views as FINAL
This is how Java works. I haven't noticed any crippling effects of this. You can also declare your variables as global to not have to define them as final. But I don't see why declaring them as final is an issue. Could you provide an example where this is a problem?
Do I have to define my own layout for the tag buttons?
This is something you will have to deal with yourself. It's a design decision. If you need auto-wrapping support, you can look at Android Flow Layout: Link. It's an extended LinearLayout that supports auto-wrap of its contents.
I have not learned Fragments yet, but I think it may be VERY
applicable here
I don't see why they would be.
Note/Aside: Some kind of a check here would be better:
String tag = "";
if (!addTagsInput.getText().toString().equals("")) {
tag = addTagsInput.getText().toString();
} else {
// handle empty string
}
I got a custom dialog contains a checkBox. When I check the CheckBox, new view is created and added into Dialog. If I unchecked the CheckBox, it will make the view added gone and changes it back to previous view.
Here's it looks. Pictures on the left hand side is before check and left hand side is after checked.
As the dialog is checked, dialog layout will becomes bigger as pictures above.
My question is how to make dialog layout becomes bigger gradually instead of instantly.
Something like animation that make the layout expand itself gradually.
This is my work so far.
public class CreateRoomDialogFragment extends DialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
//Get layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
//R.layout.add_player2 is the layout shows on dialog message
view = inflater.inflate(R.layout.createroom_dialog_fragment, null);
roomName = (EditText)view.findViewById(R.id.createroom_dialog_roomname);
roomKey = (EditText)view.findViewById(R.id.createroom_dialog_key);
keyLayout = (LinearLayout)view.findViewById(R.id.keyLayout);
checkBoxLayout1 = (RelativeLayout)view.findViewById(R.id.createroom_dialog_layout1);
checkBoxLayout2 = (RelativeLayout)view.findViewById(R.id.createroom_dialog_layout2);
// lock the room with key
checkBox1 = (CheckBox)view.findViewById(R.id.createroom_dialog_checkbox1);
// show the key
checkBox2 = (CheckBox)view.findViewById(R.id.createroom_dialog_checkbox2);
checkBox1.setOnCheckedChangeListener(new CreateRoomCheckBoxListener());
checkBox2.setOnCheckedChangeListener(new CreateRoomCheckBoxListener());
}
private class CreateRoomCheckBoxListener implements OnCheckedChangeListener{
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if(buttonView == checkBox1){
// lock the room with key
if(isChecked){
roomKey.setVisibility(View.VISIBLE);
checkBoxLayout2.setVisibility(View.VISIBLE);
}else{
roomKey.setVisibility(View.GONE);
checkBoxLayout2.setVisibility(View.GONE);
}
}
}
}
}
It is possible to animate a Dialog.
Please refer to this tutorial.
Sorry it is in japanese. And since I can read japanese I assume you can read japanese.
I've created a simple custom dialog that asks users to "Press a key". The purpose of this is so that I can map whatever key they press to a function in the app. Unfortunately, I can not figure out what is the correct interface to use to detect the key events. My class looks like this:
public class ScancodeDialog extends Dialog implements OnKeyListener
{
public ScancodeDialog( Context context )
{
super(context);
setContentView( R.layout.scancode_dialog );
setTitle( "Key Listener" );
TextView text = (TextView) findViewById( R.id.scancode_text );
text.setText( "Please press a button.." );
ImageView image = (ImageView) findViewById( R.id.scancode_image );
image.setImageResource( R.drawable.icon );
getWindow().setFlags( WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM );
}
#Override
public boolean onKey( DialogInterface dialog, int keyCode, KeyEvent event )
{
if( keyCode != KeyEvent.KEYCODE_MENU )
dismiss();
return true;
}
}
I've tried it with and without the getWindow().setFlags() line (that was a suggestion from another question, which didn't help me in my case). Obviously I will add more functionality to the class later, but for now the dialog box should close whenever the user presses a key. However, onKey is never called.
I originally tried using the key listener interface from View:
import android.view.View.OnKeyListener;
But since a Dialog is not a view, this didn't work. I also tried the one from DialogInterface:
import android.content.DialogInterface.OnKeyListener;
This seemed like a better choice, since the API indicates that Dialog implements DialogInterface, but I am still not receiving the key events. Any suggestions I can try?
You never declared the dialog to listen to the keys.
Example:
this.setOnKeyListener(...)
the this keyword is referring to the class that it is in.. which is a Dialog.