Mediatorパターン

      2017/02/09

各オブジェクト間の繋がりを別のオブジェクトに任せる手法。
例えばSeekBarEditTextで同じ値を設定できるUIを用意した場合、それぞれで値を変更させた場合に連動させる必要が出てきます。
その処理を各UIを定義したクラスで行うより、別のクラスに任せるようにした方が再利用なども容易になります。

SeekBarとEditTextで同じ値を設定できるUI

edit-seekbar

サンプルソース

Androidのソースコードです。

public class EditableSeekBarMediator implements SeekBar.OnSeekBarChangeListener, TextView.OnEditorActionListener, TextWatcher {

    private SeekBar mSeekBar;
    private EditText mEditText;
    private Range 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つだけですが、 もっと多くのオブジェクトが連動する場合は積極的に使っていきたいですね。

 - 設計 ,