在 RecyclerView.Adapter 中使用不同的 TextWatcher 实现
Use of different TextWatcher-implementations inside RecyclerView.Adapter
目前我使用RecyclerView来表示动态配置列表表单
每个配置项(RecyclerView 列表中的条目)都包含一个 EditText 项。
为了避免错误的用户输入(一些字段只允许整数,其他字段只允许逗号后的一位数字),我实现了两个不同的 TextWatcher-filters 来纠正非法输入("DecimalFilterDigitsAfterComma" 和 "DecimalFilterInteger")。
我的RecyclerView一共有16个配置项,但是一次最多只能显示8个。
我的问题是 TextWatcher 被分配给了特定的项目(整数和小数点 TextEdit)。但是当我稍微滚动一下时,它们会改变它们的顺序,这样十进制和整数过滤器就会交换。
TextWatcher 项目将在 RecyclerView.Adapter 的 ConfigurationAdapter 中创建。我已经设法通过使用 mListConfigInit 为每个条目创建一次 TextWatcher,它是项目的布尔标志列表。
ConfigurationAdapter.java:
public class ConfigurationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/*
...
*/
private List<ConfigItem> mConfiguration = new ArrayList<>();
// make sure that DecimalFilter is only created once for each item
private List<Boolean> mListConfigInit = new ArrayList<>();
public ConfigurationAdapter() {
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.listitem_configuration,
parent,
false);
final ConfigurationViewHolder vh = new ConfigurationViewHolder(v);
/*
...
*/
return vh;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final ConfigurationViewHolder vh = (ConfigurationViewHolder) holder;
ConfigItem config = mConfiguration.get(position);
if(config.ShowValueAsFloat()) {
vh.SetTextWatcherType(ConfigurationViewHolder.TextWatcherType.type_FloatActive);
} else {
vh.SetTextWatcherType(ConfigurationViewHolder.TextWatcherType.type_IntActive);
}
// set name and unit
vh.mName.setText(config.mName);
vh.mUnit.setText(config.mUnit);
/*
...
*/
}
@Override
public int getItemCount() {
return mConfiguration.size();
}
public void addConfigItem(ConfigItem item) {
mConfiguration.add(item);
mListConfigInit.add(new Boolean(false));
notifyItemInserted(mConfiguration.size() - 1);
//notifyDataSetChanged();
}
/*
...
*/
}
ConfigurationViewHolder.java(根据 pskink-comments 更改):
public final class ConfigurationViewHolder extends RecyclerView.ViewHolder implements TextWatcher {
public TextView mName;
public CheckBox mCheckbox;
public SeekBar mSeekbar;
public EditText mValueEditText;
public TextView mUnit;
private List<TextWatcher> mListTextWatchers = new ArrayList<>();
public enum TextWatcherType {
type_FloatActive(0),
type_IntActive(1);
private int mValue;
TextWatcherType(int value) {
mValue = value;
}
int val() { return mValue; }
}
private TextWatcherType mTextWatcherType = TextWatcherType.type_FloatActive;
public ConfigurationViewHolder(View itemView) {
super(itemView);
mName = (TextView) itemView.findViewById(R.id.textView_configuration_name);
mValueEditText = (EditText) itemView.findViewById(R.id.editText_configuration_value);
mUnit = (TextView) itemView.findViewById(R.id.textView_configuration_unit);
mCheckbox = (CheckBox) itemView.findViewById(R.id.checkbox_configuration);
mSeekbar = (SeekBar) itemView.findViewById(R.id.seekBar_configuration);
mListTextWatchers.add(0, new DecimalFilterDigitsAfterComma(mValueEditText, 1));
mListTextWatchers.add(1, new DecimalFilterInteger(mValueEditText));
mValueEditText.addTextChangedListener(this);
}
public void SetTextWatcherType(TextWatcherType textWatcherType) {
mTextWatcherType = textWatcherType;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mListTextWatchers.get(mTextWatcherType.val()).afterTextChanged(editable);
}
}
DecimalFilterInteger.java
public class DecimalFilterInteger implements TextWatcher {
private final static String TAG = ConfigurationAdapter.class.getSimpleName();
private final EditText mEditText;
private String mLastTextValue = new String("");
public DecimalFilterInteger(EditText editText) {
this.mEditText = editText;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public synchronized void afterTextChanged(final Editable text) {
String strInput = text.toString().trim();
if(strInput.isEmpty()) {
return;
}
if(strInput.equals(mLastTextValue)) { // return when same value as last time to avoid endless loop
return;
}
if ((strInput.charAt(0) == '.')) { // handle dot at beginning
strInput = "";
}
if(strInput.contains(".")){ // cut trailing comma
String numberBeforeDecimal = strInput.split("\.")[0];
strInput = numberBeforeDecimal;
}
mEditText.removeTextChangedListener(this);
mEditText.getText().clear(); // do not use setText here to avoid changing the keyboard
mEditText.append(strInput); // back to default (e. g. from 123-mode to abc-mode),
// see:
mLastTextValue = mEditText.getText().toString();
mEditText.setSelection(mEditText.getText().toString().trim().length());
mEditText.addTextChangedListener(this);
}
}
非常感谢您的帮助!
RecyclerView 中两个不同的 TextWatcher 实现的 swap/switching 行为的原因是我在它们的 afterTextChanged
方法中调用了 removeTextChangedListener
和 addTextChangedListener
来避免重新触发 afterTextChanged
方法。
避免重新触发的最佳方法是简单地检查文本自上次调用后是否更改:
public class DecimalFilterInteger implements TextWatcher {
private final static String TAG = ConfigurationAdapter.class.getSimpleName();
private final EditText mEditText;
private String mLastTextValue = new String("");
// ...
@Override
public synchronized void afterTextChanged(final Editable text) {
String strInput = text.toString().trim();
if(strInput.isEmpty()) {
return;
}
if(strInput.equals(mLastTextValue)) { // return when same value as last time to avoid endless loop
return;
}
if ((strInput.charAt(0) == '.')) { // handle dot at beginning
strInput = "";
}
if(strInput.contains(".")){ // cut trailing comma
String numberBeforeDecimal = strInput.split("\.")[0];
strInput = numberBeforeDecimal;
}
//mEditText.removeTextChangedListener(this); // CAUSE OF SWAP-ERROR !!!
mEditText.getText().clear(); // do not use setText here to avoid changing the keyboard
mEditText.append(strInput); // back to default (e. g. from 123-mode to abc-mode),
// see:
mLastTextValue = mEditText.getText().toString();
mEditText.setSelection(mEditText.getText().toString().trim().length());
//mEditText.addTextChangedListener(this); // CAUSE OF SWAP-ERROR !!!
}
}
目前我使用RecyclerView来表示动态配置列表表单
每个配置项(RecyclerView 列表中的条目)都包含一个 EditText 项。 为了避免错误的用户输入(一些字段只允许整数,其他字段只允许逗号后的一位数字),我实现了两个不同的 TextWatcher-filters 来纠正非法输入("DecimalFilterDigitsAfterComma" 和 "DecimalFilterInteger")。 我的RecyclerView一共有16个配置项,但是一次最多只能显示8个。
我的问题是 TextWatcher 被分配给了特定的项目(整数和小数点 TextEdit)。但是当我稍微滚动一下时,它们会改变它们的顺序,这样十进制和整数过滤器就会交换。
TextWatcher 项目将在 RecyclerView.Adapter 的 ConfigurationAdapter 中创建。我已经设法通过使用 mListConfigInit 为每个条目创建一次 TextWatcher,它是项目的布尔标志列表。
ConfigurationAdapter.java:
public class ConfigurationAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
/*
...
*/
private List<ConfigItem> mConfiguration = new ArrayList<>();
// make sure that DecimalFilter is only created once for each item
private List<Boolean> mListConfigInit = new ArrayList<>();
public ConfigurationAdapter() {
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.listitem_configuration,
parent,
false);
final ConfigurationViewHolder vh = new ConfigurationViewHolder(v);
/*
...
*/
return vh;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final ConfigurationViewHolder vh = (ConfigurationViewHolder) holder;
ConfigItem config = mConfiguration.get(position);
if(config.ShowValueAsFloat()) {
vh.SetTextWatcherType(ConfigurationViewHolder.TextWatcherType.type_FloatActive);
} else {
vh.SetTextWatcherType(ConfigurationViewHolder.TextWatcherType.type_IntActive);
}
// set name and unit
vh.mName.setText(config.mName);
vh.mUnit.setText(config.mUnit);
/*
...
*/
}
@Override
public int getItemCount() {
return mConfiguration.size();
}
public void addConfigItem(ConfigItem item) {
mConfiguration.add(item);
mListConfigInit.add(new Boolean(false));
notifyItemInserted(mConfiguration.size() - 1);
//notifyDataSetChanged();
}
/*
...
*/
}
ConfigurationViewHolder.java(根据 pskink-comments 更改):
public final class ConfigurationViewHolder extends RecyclerView.ViewHolder implements TextWatcher {
public TextView mName;
public CheckBox mCheckbox;
public SeekBar mSeekbar;
public EditText mValueEditText;
public TextView mUnit;
private List<TextWatcher> mListTextWatchers = new ArrayList<>();
public enum TextWatcherType {
type_FloatActive(0),
type_IntActive(1);
private int mValue;
TextWatcherType(int value) {
mValue = value;
}
int val() { return mValue; }
}
private TextWatcherType mTextWatcherType = TextWatcherType.type_FloatActive;
public ConfigurationViewHolder(View itemView) {
super(itemView);
mName = (TextView) itemView.findViewById(R.id.textView_configuration_name);
mValueEditText = (EditText) itemView.findViewById(R.id.editText_configuration_value);
mUnit = (TextView) itemView.findViewById(R.id.textView_configuration_unit);
mCheckbox = (CheckBox) itemView.findViewById(R.id.checkbox_configuration);
mSeekbar = (SeekBar) itemView.findViewById(R.id.seekBar_configuration);
mListTextWatchers.add(0, new DecimalFilterDigitsAfterComma(mValueEditText, 1));
mListTextWatchers.add(1, new DecimalFilterInteger(mValueEditText));
mValueEditText.addTextChangedListener(this);
}
public void SetTextWatcherType(TextWatcherType textWatcherType) {
mTextWatcherType = textWatcherType;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mListTextWatchers.get(mTextWatcherType.val()).afterTextChanged(editable);
}
}
DecimalFilterInteger.java
public class DecimalFilterInteger implements TextWatcher {
private final static String TAG = ConfigurationAdapter.class.getSimpleName();
private final EditText mEditText;
private String mLastTextValue = new String("");
public DecimalFilterInteger(EditText editText) {
this.mEditText = editText;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public synchronized void afterTextChanged(final Editable text) {
String strInput = text.toString().trim();
if(strInput.isEmpty()) {
return;
}
if(strInput.equals(mLastTextValue)) { // return when same value as last time to avoid endless loop
return;
}
if ((strInput.charAt(0) == '.')) { // handle dot at beginning
strInput = "";
}
if(strInput.contains(".")){ // cut trailing comma
String numberBeforeDecimal = strInput.split("\.")[0];
strInput = numberBeforeDecimal;
}
mEditText.removeTextChangedListener(this);
mEditText.getText().clear(); // do not use setText here to avoid changing the keyboard
mEditText.append(strInput); // back to default (e. g. from 123-mode to abc-mode),
// see:
mLastTextValue = mEditText.getText().toString();
mEditText.setSelection(mEditText.getText().toString().trim().length());
mEditText.addTextChangedListener(this);
}
}
非常感谢您的帮助!
RecyclerView 中两个不同的 TextWatcher 实现的 swap/switching 行为的原因是我在它们的 afterTextChanged
方法中调用了 removeTextChangedListener
和 addTextChangedListener
来避免重新触发 afterTextChanged
方法。
避免重新触发的最佳方法是简单地检查文本自上次调用后是否更改:
public class DecimalFilterInteger implements TextWatcher {
private final static String TAG = ConfigurationAdapter.class.getSimpleName();
private final EditText mEditText;
private String mLastTextValue = new String("");
// ...
@Override
public synchronized void afterTextChanged(final Editable text) {
String strInput = text.toString().trim();
if(strInput.isEmpty()) {
return;
}
if(strInput.equals(mLastTextValue)) { // return when same value as last time to avoid endless loop
return;
}
if ((strInput.charAt(0) == '.')) { // handle dot at beginning
strInput = "";
}
if(strInput.contains(".")){ // cut trailing comma
String numberBeforeDecimal = strInput.split("\.")[0];
strInput = numberBeforeDecimal;
}
//mEditText.removeTextChangedListener(this); // CAUSE OF SWAP-ERROR !!!
mEditText.getText().clear(); // do not use setText here to avoid changing the keyboard
mEditText.append(strInput); // back to default (e. g. from 123-mode to abc-mode),
// see:
mLastTextValue = mEditText.getText().toString();
mEditText.setSelection(mEditText.getText().toString().trim().length());
//mEditText.addTextChangedListener(this); // CAUSE OF SWAP-ERROR !!!
}
}