web-dev-qa-db-ja.com

Android:CountDownTimerは最後のonTick()をスキップします!

コード:

public class SMH extends Activity {  

    public void onCreate(Bundle b) {  
        super.onCreate(b);  
        setContentView(R.layout.main);  

        TextView tv = (TextView) findViewById(R.id.tv);  

        new CountDownTimer(10000, 2000) {  
            public void onTick(long m) {  
               long sec = m/1000+1;  
               tv.append(sec+" seconds remain\n");  
            }  
            public void onFinish() {  
               tv.append("Done!");  
            }  
        }.start();  
   }

出力:
残り10秒
残り8秒
残り6秒
残り4秒
完了!

問題:

2秒が残ります」と表示するにはどうすればよいですか?経過時間は実際には10秒ですが、最後のonTick()は発生しません。 2番目のパラメーターを2000から1000に変更すると、これが出力になります。

残り10秒
残り9秒
残り8秒
残り7秒
残り6秒
残り5秒
残り4秒
残り3秒
残り2秒
完了!

つまり、最後のonTick()呼び出しをスキップしているようです。また、XMLファイルは基本的に、デフォルトのmain.xmlであり、TextViewにはid tvが割り当てられ、テキストは ""に設定されます。

44

最後のティックが機能しない理由はわかりませんが、たとえば Runable で独自のタイマーを作成できます。

class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
        }
    public void Start() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                if(millisInFuture <= 0) {
                    Log.v("status", "done");
                } else {
                    long sec = millisInFuture/1000;
                    Log.v("status", Long.toString(sec) + " seconds remain");
                    millisInFuture -= countDownInterval;
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

そして、それを開始するには、

new MyCountDownTimer(10000, 2000).Start();

グーフィーの質問の編集

カウンターの状態を保持する変数(ブール値)が必要です。次に、Start()のようなStop()メソッドを記述できます。

グーフィーの質問に対するEDIT-2

実際には、カウンターの停止にはバグはありませんが、停止(再開)後の再開にはバグがあります。

私が試したばかりの新しい更新された完全なコードを書いていますが、それは機能しています。これは、開始ボタンと停止ボタンで画面に時間を表示する基本的なカウンターです。

カウンターのクラス

public class MyCountDownTimer {
    private long millisInFuture;
    private long countDownInterval;
    private boolean status;
    public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
            this.millisInFuture = pMillisInFuture;
            this.countDownInterval = pCountDownInterval;
            status = false;
            Initialize();
    }

    public void Stop() {
        status = false;
    }

    public long getCurrentTime() {
        return millisInFuture;
    }

    public void Start() {
        status = true;
    }
    public void Initialize() 
    {
        final Handler handler = new Handler();
        Log.v("status", "starting");
        final Runnable counter = new Runnable(){

            public void run(){
                long sec = millisInFuture/1000;
                if(status) {
                    if(millisInFuture <= 0) {
                        Log.v("status", "done");
                    } else {
                        Log.v("status", Long.toString(sec) + " seconds remain");
                        millisInFuture -= countDownInterval;
                        handler.postDelayed(this, countDownInterval);
                    }
                } else {
                    Log.v("status", Long.toString(sec) + " seconds remain and timer has stopped!");
                    handler.postDelayed(this, countDownInterval);
                }
            }
        };

        handler.postDelayed(counter, countDownInterval);
    }
}

アクティビティクラス

public class CounterActivity extends Activity {
    /** Called when the activity is first created. */
    TextView timeText;
    Button startBut;
    Button stopBut;
    MyCountDownTimer mycounter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        timeText = (TextView) findViewById(R.id.time);
        startBut = (Button) findViewById(R.id.start);
        stopBut = (Button) findViewById(R.id.stop);
        mycounter = new MyCountDownTimer(20000, 1000);
        RefreshTimer();
    }

    public void StartTimer(View v) {
        Log.v("startbutton", "saymaya basladi");
        mycounter.Start();
    }

    public void StopTimer(View v) {
        Log.v("stopbutton", "durdu");
        mycounter.Stop();
    }

    public void RefreshTimer() 
    {
        final Handler handler = new Handler();
        final Runnable counter = new Runnable(){

            public void run(){
                timeText.setText(Long.toString(mycounter.getCurrentTime()));
                handler.postDelayed(this, 100);
            }
        };

        handler.postDelayed(counter, 100);
    }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:orientation="vertical"
    Android:layout_width="fill_parent"
    Android:layout_height="fill_parent"
    Android:weightSum="1">
    <TextView Android:textAppearance="?android:attr/textAppearanceLarge" 
              Android:text="TextView" Android:layout_height="wrap_content" 
              Android:layout_width="wrap_content" 
              Android:id="@+id/time">
    </TextView>
    <Button Android:text="Start" 
            Android:id="@+id/start" 
            Android:layout_width="wrap_content" 
            Android:layout_height="wrap_content" 
            Android:onClick="StartTimer">
    </Button>
    <Button Android:text="Stop" 
            Android:id="@+id/stop" 
            Android:layout_width="wrap_content" 
            Android:layout_height="wrap_content" 
            Android:onClick="StopTimer">
    </Button>
</LinearLayout>
22
ocanal

CountDownTimerのソースコードを確認しました。 「欠落したティック」は、CountDownTimerの特別な機能に由来しますが、他の場所でドキュメント化されていません。

すべてのティックの開始時、onTick()が呼び出される前に、カウントダウンの終了までの残り時間が計算されます。この時間がカウントダウンの時間間隔よりも小さい場合、onTickはnotが呼び出されます。代わりに、次のティック(onFinish()メソッドが呼び出される場所)のみがスケジュールされます。

ハードウェアクロックが常に非常に正確ではないという事実を考えると、CountDownTimerを実行するスレッドを遅延させる他のプロセスがバックグラウンドに存在する可能性があり、さらにAndroid自体がCountDownTimerのメッセージハンドラーを呼び出すときに小さな遅延を作成する可能性がありますカウントダウンの終了前の最後のティックの呼び出しが少なくとも1ミリ秒遅れている可能性が高いため、onTick()は呼び出されません。

私のアプリケーションでは、ティック間隔を「わずかに」小さくするだけでこの問題を解決しました(500ミリ秒)

    myCountDownTimer = new CountDownTimer(countDownTime, intervalTime - 500) {
                                   ...
    }

コードをそのままにしておくこともできます。インターバル時間の長さが重要なアプリケーションの場合、ここに掲載されている他のソリューションがおそらく最適です。

50
Nantoka

私が思いついた最も簡単な解決策は次のとおりです。秒カウントダウンで表示するシンプルな画面が必要な場合にのみ機能することに注意してください。

mTimer = new CountDownTimer(5000, 100){
            public void onTick(long millisUntilFinished) {
                mTimerView.setText(Long.toString(millisUntilFinished/1000));                
             }

             public void onFinish() {
                 mTimerView.setText("Expired");
             }
        };

        mTimer.start();

上記のコードでは、onTick()は100ミリ秒ごとに呼び出されますが、視覚的には秒のみが表示されます。

4
Zzokk

この問題を理解するために何時間も費やしてきましたが、素敵な回避策をお見せできることを嬉しく思います。 onFinish()呼び出しを待たずに、ユニットに1(または任意の間隔)を追加してから、onTick()呼び出しにifステートメントを追加してください。最後のonFinish()onTick()タスクを実行するだけです。ここに私が持っているものがあります:

    new CountDownTimer( (countDownTimerValue + 1) * 1000, 1000) { //Added 1 to the countdownvalue before turning it into miliseconds by multiplying it by 1000.
        public void onTick(long millisUntilFinished) {

          //We know that the last onTick() happens at 2000ms remaining (skipping the last 1000ms tick for some reason, so just throw in this if statement.
            if (millisUntilFinished < 2005){ 
                //Stuff to do when finished.
            }else{
                mTextField.setText("Time remaining: " + (((millisUntilFinished) / 1000) - 1));  //My textfield is obviously showing the remaining time. Note how I've had to subtrack 1 in order to display the actual time remaining.
            }
        }

        public void onFinish() {
        //This is when the timer actually finishes (which would be about 1000ms later right? Either way, now you can just ignore this entirely.


        }
    }.start();
3
ajwest

上記のソリューションは有効ですが、さらに改善することができます。別のクラス内に実行可能ファイルが不必要にあります(既に独自に処理できます)。したがって、スレッド(または実行可能)を拡張するクラスを作成するだけです。

    class MyTimer extends Thread {
      private long millisInFuture;
      private long countDownInterval;
      final Handler mHandler = new Handler();

      public MyTimer(long pMillisInFuture, long pCountDownInterval) {
        this.millisInFuture = pMillisInFuture;
        this.countDownInterval = pCountDownInterval;
      }

      public void run() {
        if(millisInFuture <= 0) {
          Log.v("status", "done");
        } else {
          millisInFuture -= countDownInterval;
          mHandler.postDelayed(this, countDownInterval);
        }
      }
    }
3
Mike Welsh

だから、タイマーはpostDelayハンドラーを使用するのではなく、独自のスレッドで実行されるため、少し時間をかけて行ったと思いますが、作成されたスレッドに常にポストバックします。考え。また、キャンセルして再起動することもできます。それは私のニーズにないので、私は一時停止が組み込まれていません。

/**
* Created by MinceMan on 8/2/2014.
*/
public abstract class SecondCountDownTimer {

private final int seconds;
private TimerThread timer;
private final Handler handler;

/**
 * @param secondsToCountDown Total time in seconds you wish this timer to count down.
 */
public SecondCountDownTimer(int secondsToCountDown) {
    seconds = secondsToCountDown;
    handler = new Handler();
    timer = new TimerThread(secondsToCountDown);
}

/** This will cancel your current timer and start a new one.
 *  This call will override your timer duration only one time. **/
public SecondCountDownTimer start(int secondsToCountDown) {
    if (timer.getState() != State.NEW) {
        timer.interrupt();
        timer = new TimerThread(secondsToCountDown);
    }
    timer.start();
    return this;
}

/** This will cancel your current timer and start a new one. **/
public SecondCountDownTimer start() {
    return start(seconds);
}

public void cancel() {
    if (timer.isAlive()) timer.interrupt();
    timer = new TimerThread(seconds);
}

public abstract void onTick(int secondsUntilFinished);
private Runnable getOnTickRunnable(final int second) {
    return new Runnable() {
        @Override
        public void run() {
            onTick(second);
        }
    };
}

public abstract void onFinish();
private Runnable getFinishedRunnable() {
    return new Runnable() {
        @Override
        public void run() {
            onFinish();
        }
    };
}

private class TimerThread extends Thread {

    private int count;

    private TimerThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        try {
            while (count != 0) {
                handler.post(getOnTickRunnable(count--));
                sleep(1000);
            }
        } catch (InterruptedException e) { }
        if (!isInterrupted()) {
            handler.post(getFinishedRunnable());
        }
    }
}

}

2
MinceMan

タイマーに数ミリ秒を追加して、コードを処理する時間を確保します。 _+100_をタイマーの長さに追加し、1を追加するのではなく、Math.ceil()を追加して結果を切り上げました。

また...最初の目盛りは[〜#〜] after [〜#〜] 2000ミリ秒なので、追加しない限り「10秒残り」のエントリはありません。

_protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final TextView tv = (TextView) findViewById(R.id.tv);
    tv.setText("10 Seconds remain\n"); //displayed before the first tick.
    new CountDownTimer(10000+25, 1000) { //25 to account for processing time
        public void onTick(long m) {
            long sec = (long) Math.ceil(m / 2000 ); //round up, don't add 1
            tv.append(sec + " seconds remain\n");
        }
        public void onFinish() {
            tv.append("Done!");
        }
    }.start();
}
_
2

簡単な解決策を見つけました。 ProgressBarを更新するにはCountDownが必要なので、これを行いました:

new CountDownTimer(1000, 100) {

    private int counter = 0;

    @Override
    public void onTick(long millisUntilFinished) {
        Log.d(LOG_TAG, "Tick: " + millisUntilFinished);
        if (++counter == 10) {
            timeBar.setProgress(--lenght); // timeBar and lenght defined in calling code
            counter = 0;
        }
    }


    @Override
    public void onFinish() {
        Log.d(LOG_TAG, "Finish.");

        timeBar.setProgress(0);
    }

};

小さなダニはトリックを行います:)

2
ryabenko-pro

Nantokaの答えを詳しく説明します。ビューを正しく更新するためのコードは次のとおりです。

countDownTimer = new CountDownTimer(countDownMsec, 500) 
{
    public void onTick(long millisUntilFinished)
    {
        if(millisUntilFinished!=countDownMsec)
        {
            completedTick+=1;
            if(completedTick%2==0)      // 1 second has passed
            {
                // UPDATE VIEW HERE based on "seconds = completedTick/2"
            }
            countDownMsec = millisUntilFinished;  // store in case of pause
        }
    }

    public void onFinish()
    {
        countDownMsec = 0;
        completedTick+=2;       // the final 2 ticks arrive together
        countDownTimer = null;

        // FINAL UPDATE TO VIEW HERE based on seconds = completedTick/2 == countDownMsec/1000
    }
}
1
Simon Huckett

時間間隔が4秒を超える場合、すべてのonTick()呼び出しは適切ではありません。したがって、正確な結果が必要な場合は、間隔を5秒未満にしてください。 Reseaonは、onTick()が呼び出される前のすべてのティックの開始点にあり、カウントダウンの終了までの残り時間が計算されます。この時間がカウントダウンの時間間隔より小さい場合は、onTick()はもう呼び出されません。代わりに、次のティック(onFinish()メソッドが呼び出される場所)のみがスケジュールされます。

1
Kundan

CountDownTimerでも同じ問題に直面し、さまざまなアプローチを試みました。そのため、最も簡単な方法の1つは、@ Nantocaが提供するソリューションにあります-彼は、周波数を1000ミリ秒から500ミリ秒に2倍にすることを提案しています。しかし、私はこの解決策が好きではありません。なぜなら、それはより多くの仕事をし、余分なバッテリーリソースを消費するからです。

そこで、@ ocanalのsoultionを使用して、独自の単純なCustomCountDownTimerを作成することにしました。

しかし、私は彼のコードにいくつかの欠陥を見つけました。

  1. 少し非効率的です(結果を公開するための2番目のハンドラーを作成します)

  2. 最初の結果の公開が遅れて開始されます。 (最初の初期化中にpost()ではなくpostDelayed()メソッドを実行する必要があります)

  3. 奇妙に見える。大文字のメソッド、古典的なisCanceledブール値などのステータスではなくステータス。

だから私はそれを少しきれいにし、彼のアプローチのより一般的なバージョンは次のとおりです:

private class CustomCountDownTimer {

    private Handler mHandler;
    private long millisUntilFinished;
    private long countDownInterval;
    private boolean isCanceled = false;

    public CustomCountDownTimer(long millisUntilFinished, long countDownInterval) {
        this.millisUntilFinished = millisUntilFinished;
        this.countDownInterval = countDownInterval;
        mHandler = new Handler();
    }

    public synchronized void cancel() {
        isCanceled = true;
        mHandler.removeCallbacksAndMessages(null);
    }

    public long getRemainingTime() {
        return millisUntilFinished;
    }

    public void start() {

        final Runnable counter = new Runnable() {

            public void run() {

                if (isCanceled) {
                    publishUpdate(0);
                } else {

                    //time is out
                    if(millisUntilFinished <= 0){
                        publishUpdate(0);
                        return;
                    }

                    //update UI:
                    publishUpdate(millisUntilFinished);

                    millisUntilFinished -= countDownInterval;
                    mHandler.postDelayed(this, countDownInterval);
                }
            }
        };

        mHandler.post(counter);
    }
}
0
Kirill Karmazin