Repeat a task with a time delay inside a custom vi

2019-07-25 05:28发布

问题:

The question 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 code, but I wasn't getting anything to blink.

Here is some of the current code I have been trying to get to work:

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();
}

I decided I needed to step back and solve the problem with an easier example, so I am creating an MCVE. My answer (assuming I can do it) is below. My goals are as follows:

  • The view starts the repeating task when clicked on.
  • The handler's runnable code should be canceled when the view is destroyed.

My basic question is how to make the view start its own repeating task which changes its appearance? (like blinking on and off)

回答1:

The following example shows how to set a repeating task on a custom view. The task works by using a handler that runs some code every second. Touching the view starts and stops the task.

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();
    }
}

Here is what the view looks like after being clicked.

Thanks to this answer for ideas.