Suragch
Suragch

Reputation: 511896

Repeat a task with a time delay inside a custom view

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:

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

Upvotes: 1

Views: 1041

Answers (1)

Suragch
Suragch

Reputation: 511896

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.

enter image description here

Thanks to this answer for ideas.

Upvotes: 2

Related Questions