Buna zaten bir milyon cevap olduğunu biliyorum ve biri kabul edildi. Bununla birlikte, kabul edilen yanıtta çok sayıda hata vardır ve geri kalanların çoğu, olası tüm kullanım durumlarını genişletmeden bunlardan birini (veya belki ikisini) giderir.
Bu yüzden, temelde destek yanıtlarında önerilen hata düzeltmelerinin çoğunu derledim ve aynı zamanda aralık dışındaki sayıların 0 yönünde sürekli girişine izin veren bir yöntem ekledim (aralık 0'dan başlamazsa), en azından başlayana kadar artık aralıkta olamayacağından emin. Çünkü açık olmak gerekirse, bu, diğer çözümlerin çoğunda gerçekten sorun yaratan tek zamandır.
İşte düzeltme:
public class InputFilterIntRange implements InputFilter, View.OnFocusChangeListener {
private final int min, max;
public InputFilterIntRange(int min, int max) {
if (min > max) {
// Input sanitation for the filter itself
int mid = max;
max = min;
min = mid;
}
this.min = min;
this.max = max;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
// Determine the final string that will result from the attempted input
String destString = dest.toString();
String inputString = destString.substring(0, dstart) + source.toString() + destString.substring(dstart);
// Don't prevent - sign from being entered first if min is negative
if (inputString.equalsIgnoreCase("-") && min < 0) return null;
try {
int input = Integer.parseInt(inputString);
if (mightBeInRange(input))
return null;
} catch (NumberFormatException nfe) {}
return "";
}
@Override
public void onFocusChange(View v, boolean hasFocus) {
// Since we can't actively filter all values
// (ex: range 25 -> 350, input "15" - could be working on typing "150"),
// lock values to range after text loses focus
if (!hasFocus) {
if (v instanceof EditText) sanitizeValues((EditText) v);
}
}
private boolean mightBeInRange(int value) {
// Quick "fail"
if (value >= 0 && value > max) return false;
if (value >= 0 && value >= min) return true;
if (value < 0 && value < min) return false;
if (value < 0 && value <= max) return true;
boolean negativeInput = value < 0;
// If min and max have the same number of digits, we can actively filter
if (numberOfDigits(min) == numberOfDigits(max)) {
if (!negativeInput) {
if (numberOfDigits(value) >= numberOfDigits(min) && value < min) return false;
} else {
if (numberOfDigits(value) >= numberOfDigits(max) && value > max) return false;
}
}
return true;
}
private int numberOfDigits(int n) {
return String.valueOf(n).replace("-", "").length();
}
private void sanitizeValues(EditText valueText) {
try {
int value = Integer.parseInt(valueText.getText().toString());
// If value is outside the range, bring it up/down to the endpoint
if (value < min) {
value = min;
valueText.setText(String.valueOf(value));
} else if (value > max) {
value = max;
valueText.setText(String.valueOf(value));
}
} catch (NumberFormatException nfe) {
valueText.setText("");
}
}
}
Bazı girdi durumlarının "aktif olarak" (yani, kullanıcı onu girerken) ele alınmasının imkansız olduğuna dikkat edin, bu yüzden onları yok saymalıyız ve kullanıcı metni düzenlemeyi bitirdikten sonra bunları ele almalıyız.
İşte bunu nasıl kullanabileceğiniz:
EditText myEditText = findViewById(R.id.my_edit_text);
InputFilterIntRange rangeFilter = new InputFilterIntRange(25, 350);
myEditText.setFilters(new InputFilter[]{rangeFilter});
// Following line is only necessary if your range is like [25, 350] or [-350, -25].
// If your range has 0 as an endpoint or allows some negative AND positive numbers,
// all cases will be handled pre-emptively.
myEditText.setOnFocusChangeListener(rangeFilter);
Şimdi, kullanıcı aralığın izin verdiğinden 0'a yakın bir sayı yazmaya çalıştığında, iki şeyden biri gerçekleşecek:
Eğer min
ve max
basamak aynı sayıda onlar nihai rakam aldıktan sonra, hepsi de girdi bunu izin verilmez.
Metin odağı kaybettiğinde alanda aralığın dışında bir sayı bırakılırsa, otomatik olarak en yakın sınıra ayarlanacaktır.
Ve tabii ki, kullanıcının 0'dan aralığın izin verdiğinden daha uzak bir değer girmesine asla izin verilmeyecektir ve böyle bir sayının bu nedenle "kazara" metin alanına girmesi mümkün değildir.
Bilinen Sorunlar?)
- Bu, yalnızca
EditText
kullanıcının işini bitirdiğinde odağı kaybederse çalışır .
Diğer seçenek, kullanıcı "bitti" / dönüş tuşuna bastığında sterilize etmektir, ancak çoğu veya hatta çoğu durumda, bu yine de odak kaybına neden olur.
Ancak, yumuşak klavye kapanış olacak değil otomatik elemanı un-odaklanır. Eminim Android geliştiricilerinin% 99,99'u bunu isterdi (ve EditText
öğelere odaklanma genel olarak bir bataklık değildi), ancak henüz bunun için yerleşik bir işlevsellik yok. Bunu aşmak için bulduğum en kolay yöntem, gerekirse,EditText
şunun gibi bir şeyi :
public class EditTextCloseEvent extends AppCompatEditText {
public EditTextCloseEvent(Context context) {
super(context);
}
public EditTextCloseEvent(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EditTextCloseEvent(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
for (InputFilter filter : this.getFilters()) {
if (filter instanceof InputFilterIntRange)
((InputFilterIntRange) filter).onFocusChange(this, false);
}
}
return super.dispatchKeyEvent(event);
}
}
Bu, görünümde olmasa bile, filtreyi girişi temizlemesi için "kandırır". aslında odağı kaybetmemiş . Görünüm daha sonra kendi kendine odak kaybederse, girdi temizliği tekrar tetiklenir, ancak zaten düzeltildiği için hiçbir şey değişmez.
Kapanış
Whew. Bu çok fazlaydı. Başlangıçta oldukça basit bir problem gibi görünen şey, pek çok çirkin vanilya Android parçasını (en azından Java'da) ortaya çıkarmakla sonuçlandı. Ve bir kez daha, aralığınız bir şekilde 0'ı içermiyorsa dinleyiciyi eklemeniz ve genişletmeniz yeterlidir EditText
.(Ve gerçekçi olarak, aralığınız 0 içermiyor ancak 1 veya -1 ile başlıyorsa, sorun yaşamazsınız.)
Son bir not olarak, bu yalnızca ints için işe yarar . Ondalık sayılarla ( double
, float
) çalışmak için onu uygulamanın kesinlikle bir yolu var , ancak ne benim ne de asıl soranın buna ihtiyacı olmadığından, özellikle bu kadar derinlere inmek istemiyorum. Aşağıdaki satırlarla birlikte tamamlama sonrası filtrelemeyi kullanmak çok kolay olacaktır:
// Quick "fail"
if (value >= 0 && value > max) return false;
if (value >= 0 && value >= min) return true;
if (value < 0 && value < min) return false;
if (value < 0 && value <= max) return true;
Yalnızca int
yerine float
(veya double
) arasında değişiklik yapmanız , tek bir .
(veya ,
ülkeye bağlı olarak?) Eklenmesine izin vermeniz ve ondalık türlerden biri olarak ayrıştırmanız gerekir int
.
Bu, işin çoğunu zaten halleder, bu yüzden çok benzer şekilde çalışır.