12 สิงหาคม 2556

[Android Code] สร้าง Timer สำหรับนับเวลาถอยหลังด้วยคลาส CountDownTimer


        รอบนี้มาต่อกันด้วยบทความเรื่อง Timer หรือตัวนับเวลากัน เพราะมีผู้ที่หลงเข้ามาอ่านหลายคนอยากทำตัวจับเวลา โดยเฉพาะกับแอพจำพวก Quiz หรือตอบปัญหาที่ต้องการนับเวลาถอยหลังในการตอบคำถามแต่ละข้อ


        ในการนับเวลาถอยหลังจะใช้ Thread หรือ Handler ก็ได้ แต่ว่าก็มีคลาส CountDownTimer ให้ใช้อยู่แล้ว ไม่ต้องเขียนเอง

        สำหรับการใช้งานคลาสดังกล่าว จะอยู่ในรูปง่ายๆดังนี้


CountDownTimer cdt = new CountDownTimer(10000, 1000) {
    public void onTick(long millisUntilFinished) {
        // Tick
    }

    public void onFinish() {
        // Finish
    }
}.start();

        สำหรับคลาสนี้จะเป็นว่ามีการกำหนดค่าให้สองตัว ตัวแรกคือเวลาทั้งหมดที่ต้องการให้นับ [10000] และเวลาที่ต้องการให้นับในแต่ละช่วง [1000] โดยทั้งสองค่านี้จะมีหน่วยเป็นมิลลิวินาที (ms)
        จากตัวอย่างที่ได้กำหนดค่าไว้เป็น 10000 กับ 1000 เมื่อคลาสนี้ทำงาน จะนับเวลาทั้งหมด 10 วินาที โดยที่ทุกๆ 1 วินาที ฟังก์ชัน onTick จะทำงาน และเมื่อนับครบ 10 วินาที ฟังก์ชัน onFinish จะทำงาน

        หลักการง่ายๆเลย ทุกๆ 1 วินาทีจะทำอะไร ครบ 10 วินาทีแล้วอยากจะให้ทำอะไร


        ดูที่ฟังก์ชัน onTick มีตัวแปรมาด้วยเป็นตัวแปร Long ซึ่งตัวแปรตัวนี้เอาไว้ดูได้ว่าเหลือเวลาอีกกี่มิลลิวินาที ดังนั้นก็เอาตัวแปรนี้มาแปลงเป็นวินาทีก็จะได้ว่า ตอนนี้เหลือเวลาอีกกี่วินาที เอาไปแสดงยังไงก็แล้วแต่ และเมื่อนับครบแล้วหรือ onFinish ก็หยุดการนับ


        มาดูตัวอย่างการใช้งานจริงคร่าวๆ กันเลยดีกว่า ในตัวอย่างนี้จะให้มีปุ่ม Toggle Button แทน Button เพราะอยากให้กดเพื่อจับเวลาและหยุดจับเวลาได้ ก็เลยใช้ Toggle Button ไปเลย จะได้สะดวกดี สำหรับภาพที่ใช้กับ Toggle Button ก็มี 4 ภาพดังนี้


        โดยนำมา Custom Toggle Button ให้ดูเหมาะสมกันหน่อย

button_start.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:state_pressed="true" android:state_checked="false"
        android:drawable="@drawable/button_start_pressed" />
    <item android:state_pressed="false" android:state_checked="false"
        android:drawable="@drawable/button_start_normal" />
    <item android:state_pressed="true" android:state_checked="true"
        android:drawable="@drawable/button_stop_pressed" />
    <item android:state_pressed="false" android:state_checked="true"
        android:drawable="@drawable/button_stop_normal" />
</selector>


main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/RelativeLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".Main" >

    <ToggleButton
        android:id="@+id/btnCount"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_below="@+id/pbTimer"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_marginTop="10dp"
        android:background="@drawable/button_start"
        android:textOn=""
        android:textOff="" />

    <ProgressBar
        android:id="@+id/pbTimer"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

    <TextView
        android:id="@+id/tvTimer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="0"
        android:textSize="40sp" />

</RelativeLayout>


        สำหรับ Progress Bar ในโค๊ด Java จะใช้คำสั่งซ่อนเอาไว้ก่อน จะแสดงก็ต่อเมื่อกดจับเวลาเท่านั้น เมื่อจับเวลาเสร็จก็ซ่อนไว้ เพื่อให้ดูเหมือนว่ากดจับเวลาแล้วมีแถบวิ่งเพื่อให้รู้ว่าจับเวลา


Main.java
package app.akexorcist.countdowntimer;

import android.os.Bundle;
import android.os.CountDownTimer;
import android.app.Activity;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.ToggleButton;

public class Main extends Activity {
    CountDownTimer cdt;
    TextView tvTimer;
    ToggleButton btnCount;
    ProgressBar pbTimer;
    
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        tvTimer = (TextView)findViewById(R.id.tvTimer);
        
        pbTimer = (ProgressBar)findViewById(R.id.pbTimer);
        pbTimer.setVisibility(View.INVISIBLE);
        
        btnCount = (ToggleButton)findViewById(R.id.btnCount);
        btnCount.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton view
                    , boolean isChecked) {
                if(isChecked) {
                    pbTimer.setVisibility(View.VISIBLE);
                    
                    cdt = new CountDownTimer(10000, 50) {
                        public void onTick(long millisUntilFinished) {
                            String strTime = String.format("%.1f"
                                    , (double)millisUntilFinished / 1000);
                            tvTimer.setText(String.valueOf(strTime));
                        }
                        
                        public void onFinish() {
                            tvTimer.setText("0");
                            btnCount.setChecked(false);
                            pbTimer.setVisibility(View.INVISIBLE);
                        }
                    }.start();
                } else {
                    cdt.cancel();
                    tvTimer.setText("0");
                    pbTimer.setVisibility(View.INVISIBLE);
                }
            }
        });
    }
}


        1. ประกาศคลาสที่จะใช้งานในตัวอย่างนี้ทั้งหมด โดยจะมี CountDownTimer ที่เป็นคลาสสำหรับจับเวลา Text View, Progress Bar และ Toggle Button ในหน้า Layout

        2. กำหนดค่าให้กับ Text View ตามที่สร้างไว้ในหน้า Layout

        3. กำหนดค่าให้กับ Progress Bar โดยจะให้ทำการซ่อนไว้ก่อน

        4. กำหนดค่าให้กับ Button และเรียกใช้ OnCheckedChangeListener

        5. เมื่อเกิดกรณี isChecked เป็น True หรือก็คือ Toggle Button เป็น On ซึ่งหมายถึงกรณีที่ผู้ใช้กด Toggle Button เพื่อเริ่มนับเวลาถอยหลัง ก็จะแสดง Progress Bar ขึ้นมา และกำหนดค่าให้ CountDownTimer โดยกำหนดให้นับเวลาถอยหลังทั้งหมด 10 s และ Tick ทุกๆ 50 ms แล้วให้ใช้คำสั่ง start เพื่อให้เริ่มนับเวลาถอยหลังทันที

        6. ในกรณีที่ isChecked เป็น False หรือก็คือ Toggle Button เป็น Off หมายถึงกรณีที่ผู้ใช้กด Toggle Button เพื่อหยุดนับเวลาถอยหลัง ก็จะใช้คำสั่งหยุดนับเวลาและกำหนดให้แสดงข้อความเป็นเลข 0 แล้วทำการซ่อน Progress Bar เพื่อรอผู้ใช้กดจับเวลาครั้งต่อไป

        7. เมื่อเกิดเงื่อนไข Tick (ทุกๆ 50 ms) จะแสดงค่าเวลาบน Text View สำหรับเวลาที่เหลืออยู่จะเก็บอยู่ในตัวแปร millisUntilFinished ดังนั้นก็ดึงออกมาหาร 1,000 เพราะว่าจะแสดงในหน่วยวินาที แต่ตัวแปรดังกล่าวเก็บค่าไว้ในหน่วยมิลลิวินาทีไว้ และกำหนดรูปแบบ String ให้อยู่ในรูป %.1F ซึ่งก็คือการแสดงค่าดังกล่าวในรูปของทิศนิยมหนึ่งหลักเท่านั้น แล้วก็นำไปกำหนดใน Text View เพื่อแสดงให้ผู้ใช้เห็น

        8. ในกรณีที่เกิดเงื่อนไข Finish จะให้กำหนด Text View แสดงเลข 0 เปลี่ยนสถานะของ Toggle Button เป็น Off เพื่อรอให้กดจับเวลาใหม่ และก็อย่าลืมทำการซ่อน Progress Bar ทุกครั้งที่หยุดหรือนับเสร็จด้วย

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.countdowntimer"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.akexorcist.countdowntimer.Main"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

        สำหรับตัวอย่างนี้ก็ไม่ยากมากนัก กดเพื่อนับเวลาถอยหลัง 10 วินาที โดยแสดงเวลาที่เหลืออยู่เป็นตัวเลขทศนิยม 1 หลัก อยู่ตรงกลางจอที่มี Progress Bar แสดงอยู่รอบตัวเลข ซึ่งจะแสดงเมื่อกดจับเวลาเท่านั้น 

        และเมื่อกดเริ่มนับเวลาถอยหลัง ปุ่ม Toggle Button ก็จะเปลี่ยนเป็น Off หรือเรียกอีกอย่างว่า False ก็จะให้แสดงภาพปุ่มหยุดที่ได้กำหนดไว้ ถ้านับเวลาจนครบแล้วก็จะให้แสดงเลข 0 ทำการซ่อน Progress Bar และปุ่ม Toggle Button เป็น On เพื่อให้ผู้ใช้ได้กดจับเวลาใหม่ได้
        แต่กรณีที่ผู้ใช้กดปุ่มหยุดกลางคัน ก็จะให้หยุดนับเวลาถอยหลัง ซ่อน Progress Bar และแสดงข้อความใน Text View เป็น 0 ทันที


        สำหรับผู้ที่หลงเข้ามาอ่านคนใดต้องการไฟล์ตัวอย่างสามารถดาวน์โหลดได้จาก Count Down Timer [Google Drive]



เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium