Android Adventures - A NumberPicker DialogFragment Project
Written by Mike James   
Thursday, 14 August 2014
Article Index
Android Adventures - A NumberPicker DialogFragment Project
Adding the Number Pickers
Trying it out

Adding the NumberPickers 

Next we need to add a number of NumberPickers. The simplest way is to use a for loop: 

for (int i = 0; i < numDials; i++) {
 NumberPicker np =
            new NumberPicker(getActivity());
 np.setMaxValue(9);
 np.setMinValue(0);
 linLayoutH.addView(np);
}

This works but saving a reference to each of the NumberPicker objects would be useful for later code development. All we need for this is a private array. Another problem is that if we simply use the loop index to store the NumberPickers in the array we will end up with the high order value in array[0]. To make things work as expected we need to store the low value NumberPicker in array[0].

To declare the array:

private NumberPicker[] numPickers;

and the for loop becomes:

for (int i =0 ; i <numDials; i++){
 numPickers[numDials-i-1] =
             new NumberPicker(getActivity());
 numPickers[numDials-i-1].setMaxValue(9);
 numPickers[numDials-i-1].setMinValue(0);
 numPickers[numDials-i-1].
  setValue(getDigit(currentValue,numDials-i-1));
 linLayoutH.addView(numPickers[numDials-i-1]);
}

The use of numDials-i-1 simply reverses the index - alternatively you could just run the for loop from numDials-1 to 0. 

The new line that involves setValue sets the NumberPickers to the current value which when the dialog is first displayed is the initial value supplied by the user. We still need to write the function getDigit.

Overall this code uses various things that are yet to be defined: numDials, currentValue and getDigit.

The Vertical LinearLayout

Finally we need the vertical LinearLayout and the button to complete the UI. 

Creating the vertical LinearLayout is just more of the same:

LinearLayout linLayoutV =
           new LinearLayout(getActivity());
linLayoutV.setOrientation(LinearLayout.VERTICAL);
linLayoutV.addView(linLayoutH);

The only new thing is that we have to set the orientation and finally we add the horizontal LinearLayout to nest it within the vertical layout. 

Now we need to add the button. This just has some text, "Done" and an onClick handler. The details of the onClick handler are best explained later so for the moment let's just use a stub that we can come back and fill in later:

Button okButton = new Button(getActivity());
okButton.setOnClickListener(
              new View.OnClickListener(){
  @Override
  public void onClick(View view) {
  }
 });

We also need to set the button to center inside the container and this is done by setting Gravity to CENTER_HORIZONTAL. If you didn't know this you could find it out by seeing what centered a button in a vertical LinearLayout using the designer - experiment is often the quickest way. In code you set gravity on a LayoutParams object because it is actually enforced by the container. This makes sense if you think about it because only the container can center an object within it. 

params.gravity = Gravity.CENTER_HORIZONTAL;
okButton.setLayoutParams(params);
okButton.setText("Done");

Finally we add the button to the vertical LinearLayout and return it as the result of the onCreateView:


 linLayoutV.addView(okButton);
 return linLayoutV;
}

Apart from a few missing details such as a value for numDials you could run the program at this point and you would see the UI appear as in the screen dump at the start of the article. 

All that remains to do is implement the initialization of the two parameters, provide the code for getDigit and deal with what should happen when the Done button is selected. 

Initialization

We have chosen to use the object factory approach to initializing the Fragment and this has been covered in previous chapters. 

Essentially all we  have to do is customize the generated code to work with our two integer parameters numDials and currentValue. First we need some private variables to store their values in:

private int numDials;
private int currentValue;

These replace the generated declarations of param1 and param2. 

As these values are going to be stored in a bundle for later retrieval we also need to customize the key names that have been generated. Change the lines at the top of the program that have ARG_PARM1 and ARG_PARAM2 to:

private static final String ARG_numDials =
   "numDials";
private static final String ARG_initValue =
  "initValue";

You don't have to use these key definitions if you don't want to - you can simply enter the keyvalues directly when needed, see later.

With these definitions we can now work on customising the object factory to use these parameters in place of the generated ones:

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;
}

You can see that now the factory accepts two parameters and these are stored in the arguments bundle for later use. 

In case you are still wondering why a bundle is used rather than simply storing the intial parameters in the corresponding private variables it is worth pointing out that this is a static method and it doesn't have access to the instance variables. You could create get/set methods for the private methods and then the static method could set them on the instance i.e. numdialog. You could even make the variables public and use;

numdialog.numDials=numDials;

However using the bundle also solves the problem of restoring the state when the fragment is recreated. 

So how does the instance get access to the initial parameters?

If you recall the answer is that the onCreate method retrieves them from the bundle:

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];
}
}

You can also see that this is the place to create the NumberPicker array that is needed. As onCreate is always called before onCreateView all of the data we need, including the array is ready to be used by it.

getDigit

Now that we have the currentValue of the NumberPickers safely stored we can tackle the problem of creating getDigit. This is a function that extracts the ith digit from a number n:

d=getDigit(n,i)

We need it because if the user specifies an initial value of 123 then we need to set the low order NumberPicker to 3, the next to 2 and the high order to 1. That is we need to extract each digit in turn. 

I have to admit that crating this function occupied more time that was reasonable. A purely numeric solution is to  take the remainder on division by a power of 10. For example (123)%10=3, (123)%100=23 and (123)%1000=123. If you integer divide the first by 1, the second by 10 and the final value by 100 you get 3, 2 and 1. 

This is fine but creating powers of 10 in Java involves the use of the math object and it returns a double. An alternative is to use String handling and this is the method I eventually used, but to be honest it seems too complicated as well:

 

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;
}

The computation with i is necessary because we are calling the low order digit, digit zero and it is the high order digit that is character zero in the String.

The onDone event

Now all we have to deal with is what happens when the user selects the Done button. 

To do this we need to implement the Fragment interface pattern as discussed in previous chapters. 

First we need to define an interface for a suitable listener and event handler. We need to call the interface something that isn't going to clash or mislead the user:

public interface OnNumberDialogDoneListener {
 public void onDone(int value);
}

This code has to replace the interface generated by the tempate. 

The onDone method simply sends the current value set on the NumberPickers to the Activity that is using the dialog as an event parameter. 

Next we have to modify the onAttach generated code so that we retrieve the Activity's implementation of the interface to work with the newly named interface:

@Override
public void onAttach(Activity activity) {
 super.onAttach(activity);
 try {
 mListener = (OnNumberDialogDoneListener) activity;
 } catch (ClassCastException e) {
 throw new ClassCastException(activity.toString()
  + " must implement OnNumberDialogDoneListener");
 }
}

We also need to modify the private variable mListener used to store the Activity's implementation:

private OnNumberDialogDoneListener mListener;



Last Updated ( Saturday, 25 July 2015 )