Android Drop-down list (Spinner) with a progress bar

Greetings to you, reader!

I present to your attention a short essay on how I wanted to see the progress bar ("infinite circle") while loading data into the drop-down list, which is called Spinner in Android.
This need arose when developing a small utility for working with a web service. The parameters of a certain calculation are stored on a centralized server. The .NET web service provides lists of possible parameters in the form of arrays of different lengths (from 2 to 50 elements). To display these parameters, a drop-down list was selected. List initialization, as expected, occurs asynchronously. And while the data is loading, looking at empty static elements without any progress is boring, dull and generally.


The goal itself


The standard Spinner looks like this:

After a little refinement, it turns out something like this (CustomSpinner):


“What is the salt !?” - you ask? And salt is in an intermediate state (loading data):


in a light topic:



To get such an effect, I see 2 ways:
1 Inherited from Spinner; override onDraw () and, possibly, some other methods; implement state processing (loading / loading)
2 Inherited from Layout; place Spinner and ProgressBar on it; organize the work of the control in accordance with its requirements.

The first way is probably more correct. But every time I stepped on it, I ran into Exception'y and crashes of the application. Although I’ve known Android for a long time, I honestly admit that I still don’t understand what exactly needs to be done in the onDraw () method. I really do not want to poke around in the source, although sometimes it is useful. In general, this path ended, so only without starting.

The second way is described in more detail on the network. I walked through it quickly and naturally. And he was like that ...

XML layout


First, we need to “sketch” our new control (custom_spinner.xml). There is nothing complicated about it - a root layout and two daughter elements (spinner and progress bar). RelativeLayout is a good fit for this. I got it like this:



CustomSpinner Class


To work with the control, you need to implement the CustomSpinner class. Create a class inherited from RelativeLayout:

public class CustomSpinner extends RelativeLayout {
	Context context;
	Spinner spinner;
	ProgressBar progress;
	public CustomSpinner(Context c, AttributeSet attrs) {
		super(c, attrs);
		this.context = c;
		//Отрисовываем внещний вид из нашего ранее определённого ресурса custom_spinner.xml
		LayoutInflater.from(context).inflate(R.layout.custom_spinner, this,
				true);
		initViews();
	}	
	//Инициализируем дочерние контролы для дальнейшей работы с ними
	private void initViews() {
		spinner = (Spinner) findViewById(R.id.spinner);
		progress = (ProgressBar) findViewById(R.id.progress);
	}
}


State management


To achieve the original goal (showing a progress bar when loading data), we modify the CustomSpinner class:

public class CustomSpinner extends RelativeLayout {
	Context context;
	Spinner spinner;
	ProgressBar progress;	
	ArrayAdapter emptyAdapter;
	public CustomSpinner(Context c, AttributeSet attrs) {
		super(c, attrs);
		this.context = c;
		LayoutInflater.from(context).inflate(R.layout.custom_spinner, this,
				true);
		String[] strings = new String[] {};
		List items = new ArrayList(Arrays.asList(strings));
		emptyAdapter = new ArrayAdapter(c,
				android.R.layout.simple_spinner_item, items);
		initViews();
	}
	private void initViews() {
		spinner = (Spinner) findViewById(R.id.spinner);
		progress = (ProgressBar) findViewById(R.id.progress);
	}
	public void loading(boolean flag) {
		if (flag)
			spinner.setAdapter(emptyAdapter);
		progress.setVisibility(flag ? View.VISIBLE : View.GONE);
	}
}


In the case when the control is in the process of loading, you need to hide the possible values ​​of the list available in it - spinner.setAdapter(emptyAdapter);. And, in fact, show the progress bar.

Now with asynchronous loading, for which I use AsyncTask, we can control the behavior of the control:

	CustomSpinner spinner;	 
	...
	@Override
	protected void onPreExecute() {
		spinner.loading(true);		
	}
	...
	@Override
	protected SpinnerAdapter doInBackground(Map... params) {
		//Здесь данные, полученные от веб-сервиса укладываются в SpinnerAdapter, которы впоследствии назначется нашему CustomSpinner
		return null;
	}
	...
	@Override
	protected void onPostExecute(SpinnerAdapter result) {
		spinner.loading(false);
		if (result != null)
			spinner.setAdapter(result);
	}


Crutches


Well, of course, and where without them!

Recall that essentially we wanted Spinner. Therefore, the control behavior should be appropriate. With the selected implementation, you need to implement several gags:

public class CustomSpinner extends RelativeLayout {
	...
	//Обработчик события выбора значения выпадающего списка
	public void setOnItemSelectedListener(OnItemSelectedListener l) {
		spinner.setTag(getId());
		spinner.setOnItemSelectedListener(l);
	}
	//Назначаем адаптер выпадающему списку
	public void setAdapter(SpinnerAdapter adapter) {
		spinner.setAdapter(adapter);
	}
	// Возвращаем индекс выбранного в списке элемента
	public int getSelectedItemPosition() {
		return this.spinner.getSelectedItemPosition();
	}
	//Получение адаптера выпадающего списка
	public SpinnerAdapter getAdapter() {
		return this.spinner.getAdapter();
	}	
}


Methods getAdapter(), setAdapter(), getSelectedItemPosition()simply “forward” actions to the internal Spinner.
Attention should be paid to the method setOnItemSelectedListener(OnItemSelectedListener l). I use one listener for all controls (I think it’s more correct) in which I switch(*some_unique_value*)...case(R.id.model)determine with the help what to do next. Since the drop-down list inside our control does not have a unique global identifier (it is for all R.id.spinner), we write the identifier of the parent control ( spinner.setTag(getId());) in the drop-down list tag . Now, when we call the change value handler in the drop-down list, we can identify which list has changed:

Drop-down list handler:

class SpinnerItemSelectedListener implements OnItemSelectedListener {
		@Override
		public void onItemSelected(AdapterView paramAdapterView,
				View paramView, int paramInt, long paramLong) {
			int id = ((SimpleParameter) paramAdapterView.getAdapter().getItem(
					paramInt)).getId();
			switch ((Integer) paramAdapterView.getTag()) {
			case R.id.city:
				initCityDependant(id);
				break;
			case ...:
				otherMethod();
				break;
			default:
				break;
			}
		}
		@Override
		public void onNothingSelected(AdapterView paramAdapterView) {
		}
	}


If our custom control was inherited directly from Spinner, then these crutches might not have been. But alas.

CustomSpinner on the mold


It remains to insert our new element into the application interface (layout):



There are 25 such controls on my form. Once I kind of read that you can specify the namespace of your application in the layout header and then it seems like there will be no need to write the full class name org.solo12zw74.app.CustomSpinner. But now for some reason it didn’t work out.

Afterword


Thanks for attention. I would be very happy if someone gives links to sensible articles on the implementation of such control in the first way (inherited from Spinner). Or he will explain how to draw a progress bar in the onDraw () method, and then hide it if necessary.

Also popular now: