Mediatorパターン
各オブジェクト間の繋がりを別のオブジェクトに任せる手法。
例えばSeekBarとEditTextで同じ値を設定できるUIを用意した場合、それぞれで値を変更させた場合に連動させる必要が出てきます。
その処理を各UIを定義したクラスで行うより、別のクラスに任せるようにした方が再利用なども容易になります。
SeekBarとEditTextで同じ値を設定できるUI
サンプル
Androidのソースコードです。
public class EditableSeekBarMediator implements SeekBar.OnSeekBarChangeListener, TextView.OnEditorActionListener, TextWatcher {
private SeekBar mSeekBar;
private EditText mEditText;
private Range<Integer> mValueRange;
private int mValueStep;
private int mCurrentValue;
private boolean mDuringSynchronize;
public EditableSeekBarMediator(SeekBar seekBar, EditText editText, int min, int max, int step, int value) {
mSeekBar = seekBar;
mEditText = editText;
mValueRange = new Range<>(min, max);
mValueStep = step;
mCurrentValue = value;
mSeekBar.setMax((mValueRange.getUpper() - mValueRange.getLower()) / mValueStep);
mSeekBar.setProgress((mCurrentValue - mValueRange.getLower()) / mValueStep);
mEditText.setText(String.format(Locale.getDefault(), "%d", mCurrentValue));
mSeekBar.setOnSeekBarChangeListener(this);
mEditText.addTextChangedListener(this);
mEditText.setOnEditorActionListener(this);
}
public int getValue() {
return mCurrentValue;
}
public void setValue(int value) {
mEditText.setText(String.format(Locale.getDefault(), "%d", value));
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (mDuringSynchronize) {
return;
}
if (!fromUser) {
return;
}
mCurrentValue = (progress * mValueStep) + mValueRange.getLower();
mDuringSynchronize = true;
try {
mEditText.setText(String.format(Locale.getDefault(), "%d", mCurrentValue));
} finally {
mDuringSynchronize = false;
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// NOP
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
mCurrentValue = mSeekBar.getProgress() * mValueStep + mValueRange.getLower();
mDuringSynchronize = true;
try {
mEditText.setText(String.format(Locale.getDefault(), "%d", mCurrentValue));
} finally {
mDuringSynchronize = false;
}
}
@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 void afterTextChanged(Editable s) {
if (mDuringSynchronize) {
return;
}
updateValue();
mDuringSynchronize = true;
try {
mSeekBar.setProgress((mCurrentValue - mValueRange.getLower()) / mValueStep);
} finally {
mDuringSynchronize = false;
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
// EnterKeyで通知される.
updateValue();
mEditText.setText(String.format(Locale.getDefault(), "%d", mCurrentValue));
return false;
}
private void updateValue() {
String inputValue = mEditText.getText().toString();
int value;
if (inputValue.isEmpty()) {
mCurrentValue = mValueRange.getLower();
return;
}
try {
value = Integer.parseInt(inputValue);
} catch (NumberFormatException e) {
mCurrentValue = mValueRange.getLower();
return;
}
value = Math.round((float) value / mValueStep) * mValueStep;
mCurrentValue = mValueRange.clamp(value);
}
}
- SeekBarが変更されたら変更後の値をEditTextに反映します。
- EditTextが変更されたら変更後の値の位置へSeekBarのプログレスを移動させます。
変更通知がお互いで飛び交わないように
mDuringSynchronize
を使用して制御しています。
Mediatorクラスがあることで、画面側では連動処理を管理することなく、必要な時に値だけを取得することができます。
上記は管理するオブジェクトが2つだけですが、 もっと多くのオブジェクトが連動する場合は積極的に使っていきたいですね。