52

コード:

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が割り当てられ、テキストは "" に設定されています。

4

12 に答える 12

57

CountDownTimer のソースコードを確認しました。「目盛りがありません」は、他の場所で文書化されているのをまだ見ていない CountDownTimer の特別な機能に由来します。

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

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

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

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

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

于 2012-09-05T14:09:53.493 に答える
23

最後のティックが機能しない理由はわかりませんが、たとえば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();

グーフィーの質問を編集

counter status (boolean) を保持する変数が必要です。その後、Start() のような Stop() メソッドを記述できます。

グーフィーの質問の編集-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>
于 2012-01-13T23:24:37.517 に答える
4

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

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 ミリ秒ごとに呼び出されますが、視覚的には秒しか表示されません。

于 2012-06-16T16:18:04.523 に答える
4

私はこの問題を解決するために何時間も費やしてきました。わざわざ呼び出しを待つ必要はありません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();
于 2012-01-30T03:53:42.147 に答える
3

上記の解決策は有効ですが、さらに改善することができます。不必要に別のクラス内に実行可能なものがあります(すでに独自に処理できます)。そのため、スレッド (または実行可能) を拡張するクラスを作成するだけです。

    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);
        }
      }
    }
于 2012-03-07T03:49:26.980 に答える
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);
    }

};

小さな目盛りはトリックを行います:)

于 2014-04-30T10:19:43.100 に答える
2

したがって、タイマーは作成されたスレッドに常にポストバックされますが、タイマーは 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());
        }
    }
}

}

于 2014-08-02T15:13:35.873 に答える
-1

残り時間を正しく計算していません。コールバックは、タスクが完了するまでのミリ秒数を取得します。

public void onTick(long m) {  
    long sec = m/1000+1;  
    tv.append(sec+" seconds remain\n");  
}  

する必要があります

public void onTick(long m) {  
    long sec = m/1000;  
    tv.append(sec+" seconds remain\n");  
}

私はこのクラスを自分で使用したことはありませんが、開始した瞬間にコールバックを取得できないようです。そのため、エントリが欠落しているように見えます。たとえば、10000 ミリ秒、1 ティックあたり 1000 ミリ秒の場合、10 ではなく、合計 9 つの更新コールバックが得られます。

于 2012-01-14T04:56:09.333 に答える