activity 暂停时在自定义视图中取消 runnable/handler

Cancel the runnable/handler in a custom view when the activity pauses

我有一个带有闪烁光标的自定义视图。我使用 Handler 制作闪烁光标,并在 500 毫秒延迟后向其发送 Runnable

当视图所在的 activity 时,我想通过删除处理程序上的回调来停止闪烁。但是,我注意到当我切换到另一个应用程序时,handler/runnable 继续显示,即日志显示它仍在闪烁。

如果我能控制视图,我会做类似 this

的事情
@Override
protected void onPause() {
     handler.removeCallbacks(runnable);
     super.onPause();
}

但是我的自定义视图将成为库的一部分,因此我无法控制其他开发人员在其中使用我的自定义视图的活动。

我尝试了 onFocusChangedonScreenStateChangedonDetachedFromWindow,但其中 none 在用户切换到另一个应用程序时有效。

这是我的代码。我通过删除与问题无关的任何内容来简化它。

public class MyCustomView extends View {

    static final int BLINK = 500;
    private Handler mBlinkHandler;

    private void init() {
        // ...
        mBlinkHandler = new Handler();

        mTextStorage.setOnChangeListener(new MongolTextStorage.OnChangeListener() {
            @Override
            public void onTextChanged(/*...*/) {
                // ...
                startBlinking();
            }
        });
    }

    Runnable mBlink = new Runnable() {
        @Override
        public void run() {
            mBlinkHandler.removeCallbacks(mBlink);
            if (shouldBlink()) {
                // ...
                Log.i("TAG", "Still blinking...");
                mBlinkHandler.postDelayed(mBlink, BLINK);
            }
        }
    };

    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 startBlinking() {
        mBlink.run();
    }

    void stopBlinking() {
        mBlinkHandler.removeCallbacks(mBlink);
    }

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        if (focused) {
            startBlinking();
        } else {
            stopBlinking();
        }
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
    }

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

    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        startBlinking();
    }

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

我猜你正在使用 thread.run() 单独启动线程,而不是只创建一个方法并递归调用它像这样:

public void blink(){
     mBlinkHandler.postDelayed(mBlink, BLINK);
} 

并且在 运行nable:

Runnable mBlink = new Runnable() {
    @Override
    public void run() {
        mBlinkHandler.removeCallbacks(mBlink);
        if (shouldBlink()) {
            // ...
            Log.i("TAG", "Still blinking...");
           blink();
        }
    }
};

因为您是使用 运行 方法直接启动线程。所以它不会通过删除回调来停止。

希望对您有所帮助。

我按照@pskink 在评论中的建议解决了这个问题,并改编了 Android 1.6 中的代码。这可能是 Android 的旧版本,但闪烁的光标部分很适合我的目的。重写 onWindowFocusChanged 是关键。

我的 full code 在 GitHub 上。以下是相关部分:

public class MyCustomView extends View {

    private boolean mCursorVisible = true;
    private Blink mBlink;
    private long mShowCursor; // cursor blink timing based on system clock
    static final int BLINK = 500;

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        mShowCursor = SystemClock.uptimeMillis();
        if (focused) {
            makeBlink();
        }
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        int start = getSelectionStart();
        int end = getSelectionEnd();

        // draw the blinking cursor on top
        if (start == end && blinkShouldBeOn()) {
            canvas.drawRect(getCursorPath(start), mCursorPaint);
        }
    }

    private boolean blinkShouldBeOn() {
        if (!mCursorVisible || !isFocused()) return false;
        return (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK;
    }
    private void makeBlink() {
        if (!mCursorVisible) {
            if (mBlink != null) {
                mBlink.removeCallbacks(mBlink);
            }

            return;
        }

        if (mBlink == null)
            mBlink = new Blink(this);

        mBlink.removeCallbacks(mBlink);
        mBlink.postAtTime(mBlink, mShowCursor + BLINK);
    }

    public void setCursorVisible(boolean visible) {
        mCursorVisible = visible;
        invalidateCursorPath();

        if (visible) {
            makeBlink();
        } else if (mBlink != null) {
            mBlink.removeCallbacks(mBlink);
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);

        if (hasWindowFocus) {
            if (mBlink != null) {
                mBlink.uncancel();

                if (isFocused()) {
                    mShowCursor = SystemClock.uptimeMillis();
                    makeBlink();
                }
            }
        } else {
            if (mBlink != null) {
                mBlink.cancel();
            }
            hideSystemKeyboard();
        }
    }

    private static class Blink extends Handler implements Runnable {
        private WeakReference<MongolEditText> mView;
        private boolean mCancelled;

        Blink(MongolEditText v) {
            mView = new WeakReference<>(v);
        }

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

            removeCallbacks(Blink.this);

            MongolEditText met = mView.get();

            if (met != null && met.isFocused()) {
                int st = met.getSelectionStart();
                int en = met.getSelectionEnd();

                if (st == en && st >= 0 && en >= 0) {
                    if (met.mLayout != null) {
                        met.invalidateCursorPath();
                    }

                    postAtTime(this, SystemClock.uptimeMillis() + BLINK);
                }
            }
        }

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

        void uncancel() {
            mCancelled = false;
        }
    }
}