When the screen rotates my seekbar's colored bar goes back to its initial value, while the thumb remains at the correct position.
Basically from this:
It becomes like this:
Note that the TextView
showing 15
is connected to the seekbar and correctly shows the same value, which is updated in onCreateView
retrieving the value with getProgress
on the seekbar, so the seekbar has the correct progress internally but "forgets" to update its bar. Note also that if moved slightly, the bar will be updated correctly.
The strange thing is that I have an identical seekbar, on which I do exactly the same actions(method calls etc) but this one never has this problem.
They are defined in the same way in the XML layout file(except for the id
).
These seekbars are inside a Fragment
shown into a ViewPager
, here's more or less the code for the fragment:
public class NewCharacterNameFragment extends Fragment
implements NumericSeekBar.OnValueChangedListener {
private static final String LEVEL = "org.my.package.LEVEL";
private static final String STATS = "org.my.package.STATS";
private NumericSeekBar levelBar; // Causing problems
private NumericSeekBar statsBar; // Well behaved
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (savedInstanceState == null) { // defaults values to avoid multiple checks later
savedInstanceState = new Bundle();
savedInstanceState.putInt(LEVEL, 1);
savedInstanceState.putInt(STATS, 30);
}
View view = inflater.inflate(R.layout.new_character_name_fragment, container,
false);
levelBar = (NumericSeekBar) view.findViewById(R.id.levelSeekBar);
statsBar = (NumericSeekBar) view.findViewById(R.id.statPointsSeekBar);
levelBar.setValue(savedInstanceState.getInt(LEVEL));
levelBar.setMax(20);
levelBar.setValueChangedListener(this);
statsBar.setValue(savedInstanceState.getInt(STATS));
statsBar.setMax(100);
statsBar.setValueChangedListener(this);
// Initialize the text-views with the progress values:
TextView tView = (TextView) view.findViewById(R.id.statPointsNumTextView);
tView.setText(Integer.valueOf(statsBar.getValue()).toString());
tView = (TextView) view.findViewById(R.id.levelNumTextView);
tView.setText(Integer.valueOf(levelBar.getValue()).toString());
return view;
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt(LEVEL, levelBar.getValue());
savedInstanceState.putInt(STATS, statsBar.getValue());
}
@Override
public void onNumericSeekBarValueChanged(NumericSeekBar bar, int value,
boolean fromUser) {
// Called whenever the seekbar value changes
if (bar == statsBar) {
TextView view = (TextView) getView().findViewById(R.id.statPointsNumTextView);
view.setText(Integer.valueOf(value).toString());
} else if (bar == levelBar) {
TextView view = (TextView) getView().findViewById(R.id.levelNumTextView);
view.setText(Integer.valueOf(value).toString());
}
}
}
Where NumericSeekBar
is a widget I created and is basically a LinearLayout
with the two increment and decrement buttons and the seekbar:
public class NumericSeekBar extends LinearLayout
implements SeekBar.OnSeekBarChangeListener, View.OnClickListener {
public interface OnValueChangedListener {
public void onNumericSeekBarValueChanged(NumericSeekBar bar, int value,
boolean fromUser);
}
private Button incButton;
private Button decButton;
private SeekBar seekBar;
private OnValueChangedListener listener = null;
private int maxValue = 100;
private int value = 0;
public NumericSeekBar(Context ctx) {
super(ctx);
setOpts();
setWidgets();
}
public NumericSeekBar(Context ctx, AttributeSet attributes) {
super(ctx, attributes);
setOpts();
setWidgets();
}
public void setValueChangedListener(OnValueChangedListener listener) {
this.listener = listener;
}
public void setMax(int maxValue) {
this.maxValue = maxValue;
seekBar.setMax(maxValue);
}
public int getValue() {
return this.value; // using seekBar.getProgress() obtains same results
}
public boolean setValue(int value) {
if (value < 0 || value > maxValue) {
return false;
}
this.value = value;
seekBar.setProgress(value);
return true;
}
@Override
public void onStopTrackingTouch(SeekBar bar) {
bar.setSecondaryProgress(bar.getProgress());
}
@Override
public void onStartTrackingTouch(SeekBar bar) {
}
@Override
public void onProgressChanged(SeekBar bar, int value, boolean fromUser) {
this.value = value;
if (listener != null ){
listener.onNumericSeekBarValueChanged(this, value, fromUser);
}
if (!fromUser) {
bar.setSecondaryProgress(0);
}
}
@Override
public void onClick(View v) {
// Handle increment/decrement button clicks
if (v.equals(incButton)) {
this.setValue(this.getValue() + 1);
} else if(v.equals(decButton)) {
this.setValue(this.getValue() - 1);
}
}
private void setOpts() {
setOrientation(HORIZONTAL);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setShowDividers(SHOW_DIVIDER_NONE);
}
}
private void setWidgets() {
incButton = new Button(getContext());
decButton = new Button(getContext());
seekBar = new SeekBar(getContext());
incButton.setText("+");
incButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
incButton.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
(float) 0.4));
incButton.setOnClickListener(this);
decButton.setText("-");
decButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
decButton.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
(float) 0.4));
decButton.setOnClickListener(this);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
(float) 0.2);
layoutParams.gravity = Gravity.CENTER;
seekBar.setLayoutParams(layoutParams);
setMax(this.maxValue);
setValue(this.value);
addView(incButton);
addView(seekBar);
addView(decButton);
seekBar.setOnSeekBarChangeListener(this);
}
}
This happens both on the emulator and on a physical device.
EDIT: I just tested my app on an android 4 emulator and this does not happen, so it seems to be something 2.x related.
EDIT2: I've tried to invalidate()
the bars in onCreateView
, onStart
and onResume
but the problems still occurs. I've also tried to put a levelBar.setValue(levelBar.getValue())
in onResume
but nothing changed.
I really don't understand what's happening.
EDIT3: I've added an other fragment which contains six of these bars and only the levelBar
in the code above behaves strangely. I wonder how is this possible. Either there is some really strange bug, or I'm doing something not properly, even though I can't see where(and in android 4.x all works well).
EDIT4: My third edit is incorrect: now almost all the bars have this behaviour. The statsBar
above seems to be the only one that is never affected.
I finally understood what's wrong with the code.
It seems like changing the maximum value of a SeekBar
does not trigger a repaint of the color bar while it does trigger a repaint of the thumb. This is probably a bug in android 2.x(since it does not happen in android 4.x).
To solve this problem you simply have to set the maximum value before setting the progress on the seek-bar.
In my case only some bars were affected because I set the default maximum for the NumericSeekBar
to 100, and only the bars with a different maximum where affected.
It's still not clear why invalidating the view in onResume
does not produce the correct re-drawn of the widget.