在自定义视图中重复执行具有时间延迟的任务

Repeat a task with a time delay inside a custom view

问题 Repeat a task with a time delay? talks about a repeated task within an activity. The top voted answer looks good for that situation. I am trying to make a blinking cursor inside a completely custom EditText. I tried copying and adapting code from the Android TextView and Editor 代码,但我没有得到任何眨眼。

这是我一直在尝试使用的一些当前代码:

private boolean shouldBlink() {
    if (!mCursorVisible || !isFocused()) return false;

    final int start = getSelectionStart();
    if (start < 0) return false;

    final int end = getSelectionEnd();
    if (end < 0) return false;

    return start == end;
}

void makeBlink() {
    if (shouldBlink()) {
        mShowCursor = SystemClock.uptimeMillis();
        if (mBlink == null) mBlink = new Blink();
        this.removeCallbacks(mBlink);
        this.postDelayed(mBlink, BLINK);
    } else {
        if (mBlink != null) this.removeCallbacks(mBlink);
    }
}

private class Blink implements Runnable {
    private boolean mCancelled;

    public void run() {
        if (mCancelled) {
            return;
        }

        MongolEditText.this.removeCallbacks(this);

        if (shouldBlink()) {
            if (mLayout != null) {
                MongolEditText.this.invalidateCursorPath();
            }

            MongolEditText.this.postDelayed(this, BLINK);
        }
    }

    void cancel() {
        if (!mCancelled) {
            MongolEditText.this.removeCallbacks(this);
            mCancelled = true;
        }
    }

    void uncancel() {
        mCancelled = false;
    }
}

private void invalidateCursorPath() {
    int start = getSelectionStart();
    if (start < 0) return;
    Rect cursorPath = getCursorPath(start);
    invalidate(cursorPath.left, cursorPath.top, cursorPath.right, cursorPath.bottom);
}

private void suspendBlink() {
    if (mBlink != null) {
        mBlink.cancel();
    }
}

private void resumeBlink() {
    if (mBlink != null) {
        mBlink.uncancel();
        makeBlink();
    }
}

@Override
public void onScreenStateChanged(int screenState) {
    switch (screenState) {
        case View.SCREEN_STATE_ON:
            resumeBlink();
            break;
        case View.SCREEN_STATE_OFF:
            suspendBlink();
            break;
    }
}

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    resumeBlink();
}

@Override
public void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    suspendBlink();
}

我决定我需要退后一步,用一个更简单的例子来解决问题,所以我正在创建一个 MCVE。我的答案(假设我能做到)如下。我的目标如下:

我的基本问题是如何让视图开始自己的重复任务来改变它的外观? (如闪烁)

以下示例显示如何在自定义视图上设置重复任务。该任务通过使用每秒运行一些代码的处理程序来工作。触摸视图启动和停止任务。

public class MyCustomView extends View {

    private static final int DELAY = 1000; // 1 second
    private Handler mHandler;

    // keep track of the current color and whether the task is running
    private boolean isBlue = true;
    private boolean isRunning = false;

    // constructors
    public MyCustomView(Context context) {
        this(context, null, 0);
    }
    public MyCustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public MyCustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mHandler = new Handler();
    }

    // start or stop the blinking when the view is touched
    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (isRunning) {
                stopRepeatingTask();
            } else {
                startRepeatingTask();
            }
            isRunning = !isRunning;
        }
        return true;
    }

    // alternate the view's background color
    Runnable mRunnableCode = new Runnable() {
        @Override
        public void run() {
            if (isBlue) {
                MyCustomView.this.setBackgroundColor(Color.RED);
            }else {
                MyCustomView.this.setBackgroundColor(Color.BLUE);
            }
            isBlue = !isBlue;

            // repost the code to run again after a delay
            mHandler.postDelayed(mRunnableCode, DELAY);
        }
    };

    // start the task
    void startRepeatingTask() {
        mRunnableCode.run();
    }

    // stop running the task, cancel any current code that is waiting to run
    void stopRepeatingTask() {
        mHandler.removeCallbacks(mRunnableCode);
    }

    // make sure that the handler cancels any tasks left when the view is destroyed 
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        stopRepeatingTask();
    }
}

这是点击后的视图。

感谢 this answer 的想法。