Now that we have hooked up the Activity to the DialogFragment we can return to the button's onClick event handler and write code that allow it to send the current value on the NumberPickers to the Activity and dismiss the dialog.
We need to get the current value as displayed on the NumberPickers. We could have chosen to update the current value every time the made a change. In this case all we need is to update the current value when the Done button is selected. A simple function to get that value from the NumberPickers is:
private int getValue(){ int value=0; int mult=1; for(int i=0;i<numDials;i++){ value+=numPickers[i].getValue()*mult; mult*=10; } return value; }
This gets the value of each NumberPicker in turn and multiplies by the appropriate power of ten.
The Onclick handler is:
okButton.setOnClickListener( new View.OnClickListener(){ @Override public void onClick(View view) { currentValue=getValue(); if (mListener != null) { mListener.onDone(currentValue); }; dismiss(); } });
That's all we need everything is now ready to be used.
Trying it out
To try the dialog out all we have to do is add a button and a ViewText to the Activity's layout.
The button just creates an instance of the dialog and adds it to the FragmentManager
This, of course has to be added to the end of the onCreate method. Notice that this creates a dialog with three NumberPickers set to 123.
We also need to implement the dialog's interface:
public class MyActivity extends Activity implements NumberDialog.OnNumberDialogDoneListene{
And right clicking on the interface name and selecting generate methods produces an empty onDone method which simply displays the value returned by the dialog:
Now if you run the program you will see the three NumberPickers set to the intial value. If you change them and select Done you will see the value appear in the ViewText:
However there are a number of shortcomings. The first is that if you dismiss the dialog by clicking on the app the value is lost and nothing is updated. You can implement a cancel button and it is up to you to decide on the correct behavior when the dialog is dismissed by "other means".
Persisting the current value
A more important problem is that the current value isn't persisted when the DialogFragment is destroyed and recreated. If you start to enter a value by spinning the NumberPickers and then rotate the device you will see that the UI is correctly recreated but the value displayed will return to the original initial value.
We need to persist the current value by saving it as part of the onSaveInstance method:
@Override public void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); outState.putInt("CurrentValue",getValue()); }
This automatically saves the setting of the NumberPickers when the dialog is being destroyed by the system.
What about restoring it?
There are lots of places you could restore the value - in any method that receives a savedInstanceState bundle.
In this case a reasonably logical place to do the job is in the onCreate because this is where the intial values are set. We can let the intial values be set and then override any that we consider have changed since the first creation of the dialog;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { numDials = getArguments().getInt(ARG_numDials); currentValue = getArguments().getInt(ARG_initValue); numPickers = new NumberPicker[numDials];}
if (savedInstanceState!=null){ currentValue=savedInstanceState. getInt("CurrentValue"); } }
Notice the way that the CurrentValue is first set to the initial value when the dialog is created and then this is overwritten by the updated value if there is a savedInstanceBundle.
Now the current value is persistent and you can rotate the device as much as you like and the NumberPickers will not change.
Tasks For The Reader
There are a lot of missing facilities in this dialog.
If you would like to try your hand at extending the project here are some suggestions in rough order of difficulty:
Add a parameter that allows the user to set the title of the dialog.
Add a cancel button.
Add a check that the user isn't specifying too many NumberPickers.
Allow the user to specify the max/min values for the NumberPickers and the multiplier to be applied.
Work out how to style the dialog and allow the user to set the style.
Listing
For completeness the full listing of the DialogFragment is:
public class NumberDialog extends DialogFragment{ private static final String ARG_numDials = "numDials"; private static final String ARG_initValue = "initValue"; private int numDials; private int currentValue; private NumberPicker[] numPickers; private OnNumberDialogDoneListener mListener; public static NumberDialog newInstance( int numDials, int initValue) { NumberDialog numdialog = new NumberDialog(); Bundle args = new Bundle(); args.putInt(ARG_numDials, numDials); args.putInt(ARG_initValue, initValue); numdialog.setArguments(args); return numdialog; }
public NumberDialog() { // Required empty public constructor } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { numDials = getArguments().getInt(ARG_numDials); currentValue = getArguments().getInt(ARG_initValue); numPickers = new NumberPicker[numDials];} if (savedInstanceState!=null){ currentValue=savedInstanceState. getInt("CurrentValue");} }
private int getDigit(int d, int i) { String temp = Integer.toString(d); if (temp.length() <= i) return 0; int r = Character.getNumericValue( temp.charAt(temp.length() - i - 1)); return r; }
private int getValue(){ int value=0; int mult=1; for(int i=0;i<numDials;i++){ value+=numPickers[i].getValue()*mult; mult*=10; } return value; }
@Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { LinearLayout linLayoutH = new LinearLayout(getActivity()); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); linLayoutH.setLayoutParams(params); for (int i = numDials - 1; i >= 0; i--) { numPickers[i] = new NumberPicker(getActivity()); numPickers[i].setMaxValue(9); numPickers[i].setMinValue(0); numPickers[i].setValue( getDigit(currentValue, i)); linLayoutH.addView(numPickers[i]); } LinearLayout linLayoutV = new LinearLayout(getActivity()); linLayoutV.setOrientation(LinearLayout.VERTICAL); linLayoutV.addView(linLayoutH); Button okButton = new Button(getActivity()); okButton.setOnClickListener( new View.OnClickListener(){ @Override public void onClick(View view) { currentValue=getValue(); if (mListener != null) { mListener.onDone(currentValue); }; dismiss(); } }); params.gravity = Gravity.CENTER_HORIZONTAL; okButton.setLayoutParams(params); okButton.setText("Done"); linLayoutV.addView(okButton); return linLayoutV; }
@Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnNumberDialogDoneListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnNumberDialogDoneListener"); } } @Override public void onDetach() { super.onDetach(); mListener = null; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("CurrentValue",getValue()); } public interface OnNumberDialogDoneListener { public void onDone(int value); } }
You can download the code for the programs from the CodeBin (note you have to register first).