📜 ⬆️ ⬇️

Android Drop-down list (Spinner) with download indicator

Greetings to you, reader!

I present to your attention a small essay on how I wanted to see a progress bar (“infinite circle”) while loading data into the drop-down list, which in Android is called Spinner.
The need for this has arisen when developing a small utility for working with a web service. The parameters of a 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 and was selected drop-down list. Initialization of lists, as expected, occurs asynchronously. And while the data is being loaded, looking at empty static elements without any progress is boring, dull and all.


Proper purpose


Standard Spinner looks like this:

After a little refinement, we get something like this (CustomSpinner):

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


in a light theme:



To get this effect, I see 2 ways:
1 Inherit from Spinner; override onDraw () and possibly some other methods; implement state processing (loaded / loaded)
2 Inherit from Layout; put Spinner and ProgressBar on it; organize the work of the control in accordance with their requirements.

The first path is probably more correct. But every time I stepped on it, I rested on the Exception and Departures of the application. Although I’m familiar with Android for a long time, I honestly admit that I still cannot understand what exactly you need to do in the onDraw () method. It would not be desirable to get covered in source codes, although sometimes it is useful. In general, this path ended, so just not starting.

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

XML layout


First we need to “sketch” our new control (custom_spinner.xml). There is nothing difficult in this - the root layout and two children (spinner and progress bar). RelativeLayout works well for this. I got it like this:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <Spinner android:id="@+id/spinner" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" /> <ProgressBar android:id="@+id/progress" style="@android:style/Widget.ProgressBar.Small" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout> 


Class CustomSpinner


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


Condition management


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

 public class CustomSpinner extends RelativeLayout { Context context; Spinner spinner; ProgressBar progress; ArrayAdapter<String> 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<String> items = new ArrayList<String>(Arrays.asList(strings)); emptyAdapter = new ArrayAdapter<String>(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 that are 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<String, Object>... params) { // ,   -   SpinnerAdapter,     CustomSpinner return null; } ... @Override protected void onPostExecute(SpinnerAdapter result) { spinner.loading(false); if (result != null) spinner.setAdapter(result); } 


"Crutches"


Well, of course, but where could they go without them!

Recall that we really wanted to Spinner. Therefore, the control behavior should be appropriate. With the chosen implementation, you need to implement several plugs:

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


The getAdapter(), setAdapter(), getSelectedItemPosition() methods simply “forward” actions to the internal Spinner.
Attention should be paid to the setOnItemSelectedListener(OnItemSelectedListener l) method. I use one handler (listener) for all controls (I think this is more correct) in which I use switch(*some_unique_value*)...case(R.id.model) determine 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), then in the tag of the drop-down list we write the identifier of the parent control spinner.setTag(getId()); ). Now, when calling the value change handler in the drop-down list, we will be able to identify which list has changed:

Dropdown 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 be. But alas.

CustomSpinner on the form


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

 <org.solo12zw74.app.CustomSpinner android:id="@+id/model" android:layout_width="fill_parent" android:layout_height="wrap_content" /> 


There are 25 such controls on my form. Once I’ve read something like you can specify the namespace of your application in the layout header and then there’s no need to prescribe the full class name org.solo12zw74.app.CustomSpinner . But now for some reason it did not work out.

Afterword


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

Source: https://habr.com/ru/post/151285/


All Articles