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();
}
但是我的自定义视图将成为库的一部分,因此我无法控制其他开发人员在其中使用我的自定义视图的活动。
我尝试了 onFocusChanged
、onScreenStateChanged
和 onDetachedFromWindow
,但其中 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;
}
}
}
我有一个带有闪烁光标的自定义视图。我使用 Handler
制作闪烁光标,并在 500 毫秒延迟后向其发送 Runnable
。
当视图所在的 activity 时,我想通过删除处理程序上的回调来停止闪烁。但是,我注意到当我切换到另一个应用程序时,handler/runnable 继续显示,即日志显示它仍在闪烁。
如果我能控制视图,我会做类似 this
的事情@Override
protected void onPause() {
handler.removeCallbacks(runnable);
super.onPause();
}
但是我的自定义视图将成为库的一部分,因此我无法控制其他开发人员在其中使用我的自定义视图的活动。
我尝试了 onFocusChanged
、onScreenStateChanged
和 onDetachedFromWindow
,但其中 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;
}
}
}