Use SpinnerHelper in TimeListEdit.
Resolves the spinner's onItemSelected handler to back invoked repeatedly on faster devices.
This commit is contained in:
parent
a1ca4677c0
commit
da3f02648c
2 changed files with 145 additions and 14 deletions
137
app/src/main/java/info/nightscout/utils/SpinnerHelper.java
Normal file
137
app/src/main/java/info/nightscout/utils/SpinnerHelper.java
Normal file
|
@ -0,0 +1,137 @@
|
|||
package info.nightscout.utils;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.SpinnerAdapter;
|
||||
|
||||
/**
|
||||
* Spinner Helper class that works around some common issues
|
||||
* with the stock Android Spinner
|
||||
*
|
||||
* A Spinner will normally call it's OnItemSelectedListener
|
||||
* when you use setSelection(...) in your initialization code.
|
||||
* This is usually unwanted behavior, and a common work-around
|
||||
* is to use spinner.post(...) with a Runnable to assign the
|
||||
* OnItemSelectedListener after layout.
|
||||
*
|
||||
* If you do not call setSelection(...) manually, the callback
|
||||
* may be called with the first item in the adapter you have
|
||||
* set. The common work-around for that is to count callbacks.
|
||||
*
|
||||
* While these workarounds usually *seem* to work, the callback
|
||||
* may still be called repeatedly for other reasons while the
|
||||
* selection hasn't actually changed. This will happen for
|
||||
* example, if the user has accessibility options enabled -
|
||||
* which is more common than you might think as several apps
|
||||
* use this for different purposes, like detecting which
|
||||
* notifications are active.
|
||||
*
|
||||
* Ideally, your OnItemSelectedListener callback should be
|
||||
* coded defensively so that no problem would occur even
|
||||
* if the callback was called repeatedly with the same values
|
||||
* without any user interaction, so no workarounds are needed.
|
||||
*
|
||||
* This class does that for you. It keeps track of the values
|
||||
* you have set with the setSelection(...) methods, and
|
||||
* proxies the OnItemSelectedListener callback so your callback
|
||||
* only gets called if the selected item's position differs
|
||||
* from the one you have set by code, or the first item if you
|
||||
* did not set it.
|
||||
*
|
||||
* This also means that if the user actually clicks the item
|
||||
* that was previously selected by code (or the first item
|
||||
* if you didn't set a selection by code), the callback will
|
||||
* not fire.
|
||||
*
|
||||
* To implement, replace current occurrences of:
|
||||
*
|
||||
* Spinner spinner =
|
||||
* (Spinner)findViewById(R.id.xxx);
|
||||
*
|
||||
* with:
|
||||
*
|
||||
* SpinnerHelper spinner =
|
||||
* new SpinnerHelper(findViewById(R.id.xxx))
|
||||
*
|
||||
* SpinnerHelper proxies the (my) most used calls to Spinner
|
||||
* but not all of them. Should a method not be available, use:
|
||||
*
|
||||
* spinner.getSpinner().someMethod(...)
|
||||
*
|
||||
* Or just add the proxy method yourself :)
|
||||
*
|
||||
* (Quickly) Tested on devices from 2.3.6 through 4.2.2
|
||||
*
|
||||
* @author Jorrit "Chainfire" Jongma
|
||||
* @license WTFPL (do whatever you want with this, nobody cares)
|
||||
*/
|
||||
public class SpinnerHelper implements OnItemSelectedListener {
|
||||
private final Spinner spinner;
|
||||
|
||||
private int lastPosition = -1;
|
||||
private OnItemSelectedListener proxiedItemSelectedListener = null;
|
||||
|
||||
public SpinnerHelper(Object spinner) {
|
||||
this.spinner = (spinner != null) ? (Spinner)spinner : null;
|
||||
}
|
||||
|
||||
public Spinner getSpinner() {
|
||||
return spinner;
|
||||
}
|
||||
|
||||
public void setSelection(int position) {
|
||||
lastPosition = Math.max(-1, position);
|
||||
spinner.setSelection(position);
|
||||
}
|
||||
|
||||
public void setSelection(int position, boolean animate) {
|
||||
lastPosition = Math.max(-1, position);
|
||||
spinner.setSelection(position, animate);
|
||||
}
|
||||
|
||||
public void setOnItemSelectedListener(OnItemSelectedListener listener) {
|
||||
proxiedItemSelectedListener = listener;
|
||||
spinner.setOnItemSelectedListener(listener == null ? null : this);
|
||||
}
|
||||
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position != lastPosition) {
|
||||
lastPosition = position;
|
||||
if (proxiedItemSelectedListener != null) {
|
||||
proxiedItemSelectedListener.onItemSelected(
|
||||
parent, view, position, id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
if (-1 != lastPosition) {
|
||||
lastPosition = -1;
|
||||
if (proxiedItemSelectedListener != null) {
|
||||
proxiedItemSelectedListener.onNothingSelected(
|
||||
parent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setAdapter(SpinnerAdapter adapter) {
|
||||
if (adapter.getCount() > 0) {
|
||||
lastPosition = 0;
|
||||
}
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
public SpinnerAdapter getAdapter() { return spinner.getAdapter(); }
|
||||
public int getCount() { return spinner.getCount(); }
|
||||
public Object getItemAtPosition(int position) { return spinner.getItemAtPosition(position); }
|
||||
public long getItemIdAtPosition(int position) { return spinner.getItemIdAtPosition(position); }
|
||||
public Object getSelectedItem() { return spinner.getSelectedItem(); }
|
||||
public long getSelectedItemId() { return spinner.getSelectedItemId(); }
|
||||
public int getSelectedItemPosition() { return spinner.getSelectedItemPosition(); }
|
||||
public void setEnabled(boolean enabled) { spinner.setEnabled(enabled); }
|
||||
public boolean isEnabled() { return spinner.isEnabled(); }
|
||||
}
|
|
@ -1,22 +1,17 @@
|
|||
package info.nightscout.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.widget.TextViewCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.Layout;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -42,7 +37,7 @@ public class TimeListEdit {
|
|||
private final int ONEHOURINSECONDS = 60 * 60;
|
||||
|
||||
private View[] intervals = new View[24];
|
||||
private Spinner[] spinners = new Spinner[24];
|
||||
private SpinnerHelper[] spinners = new SpinnerHelper[24];
|
||||
private NumberPicker[] numberPickers1 = new NumberPicker[24];
|
||||
private NumberPicker[] numberPickers2 = new NumberPicker[24];
|
||||
private ImageView[] addButtons = new ImageView[24];
|
||||
|
@ -88,7 +83,7 @@ public class TimeListEdit {
|
|||
for (int i = 0; i < 24; i++) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View childview = intervals[i] = inflater.inflate(R.layout.timelistedit_element, layout, false);
|
||||
spinners[i] = (Spinner) childview.findViewById(R.id.timelistedit_time);
|
||||
spinners[i] = new SpinnerHelper(childview.findViewById(R.id.timelistedit_time));
|
||||
numberPickers1[i] = (NumberPicker) childview.findViewById(R.id.timelistedit_edit1);
|
||||
numberPickers2[i] = (NumberPicker) childview.findViewById(R.id.timelistedit_edit2);
|
||||
addButtons[i] = (ImageView) childview.findViewById(R.id.timelistedit_add);
|
||||
|
@ -221,7 +216,7 @@ public class TimeListEdit {
|
|||
}
|
||||
|
||||
private View buildInterval(int i) {
|
||||
Spinner timeSpinner = spinners[i];
|
||||
SpinnerHelper timeSpinner = spinners[i];
|
||||
View childview = intervals[i];
|
||||
final NumberPicker editText1 = numberPickers1[i];
|
||||
final NumberPicker editText2 = numberPickers2[i];
|
||||
|
@ -254,7 +249,7 @@ public class TimeListEdit {
|
|||
return childview;
|
||||
}
|
||||
|
||||
private void fillSpinner(Spinner spinner, int secondsFromMidnight, int previous, int next) {
|
||||
private void fillSpinner(SpinnerHelper spinner, int secondsFromMidnight, int previous, int next) {
|
||||
int posInList = 0;
|
||||
ArrayList<CharSequence> timeList = new ArrayList<>();
|
||||
int pos = 0;
|
||||
|
@ -267,10 +262,7 @@ public class TimeListEdit {
|
|||
ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(context,
|
||||
R.layout.spinner_centered, timeList);
|
||||
spinner.setAdapter(adapter);
|
||||
AdapterView.OnItemSelectedListener l = spinner.getOnItemSelectedListener();
|
||||
spinner.setOnItemSelectedListener(null);
|
||||
spinner.setSelection(posInList, false);
|
||||
spinner.setOnItemSelectedListener(l);
|
||||
}
|
||||
|
||||
private int itemsCount() {
|
||||
|
@ -369,8 +361,10 @@ public class TimeListEdit {
|
|||
}
|
||||
|
||||
private void log() {
|
||||
for (int i = 0; i < data1.length(); i++) {
|
||||
log.debug(i + ": @" + DateUtil.timeStringFromSeconds(secondFromMidnight(i)) + " " + value1(i) + (data2 != null ? " " + value2(i) : ""));
|
||||
if (log.isDebugEnabled()) {
|
||||
for (int i = 0; i < data1.length(); i++) {
|
||||
log.debug(i + ": @" + DateUtil.timeStringFromSeconds(secondFromMidnight(i)) + " " + value1(i) + (data2 != null ? " " + value2(i) : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue