Handler實現(xiàn)倒計時功能
本文實例為大家分享了Handler實現(xiàn)倒計時功能的具體代碼,供大家參考,具體內(nèi)容如下
1、需求
1.1 實現(xiàn)目標
當后臺傳遞一個時間戳時,與當前系統(tǒng)時間做時間差,并轉換為時分秒,作為商品活動的倒計時;
如下圖所示:

1.2 實現(xiàn)步驟
自定義View
1、實現(xiàn)倒計時功能,封裝成方法;
2、初始化倒計時功能,及布局文件;
3、通過Handler中的post()或sendMessage()方法向主線程傳遞消息,不對刷新UI;
4、對外暴露一個方法,接收后臺傳入的時間戳;
在Activity中實現(xiàn)
通過自定義View中的方法,接收時間戳;
2、封裝成自定義view
2.1 倒計時功能
方法名 processCountMsg()
private boolean processCountMsg() {
if (hou == 0 && min == 0 && sec == 0) {
Toast.makeText(getContext(), "時間到", Toast.LENGTH_SHORT).show();
return false;
}
if (sec > 0) {
sec--;
} else {
sec = 59;
if (min == 0) {
min = 59;
hou--;
} else {
min--;
}
}
String hour, minute, second;
hour = (hou < 10) ? "0" + hou : "" + hou;
minute = (min < 10) ? "0" + min : "" + min;
second = (sec < 10) ? "0" + sec : "" + sec;
tv_hour.setText(hour);
tv_min.setText(minute);
tv_sec.setText(second);
return true;
}
2.2 初始化倒計時功能及布局文件
初始化代碼 init()
private void init() {
//TODO LayoutInflater中inflate三個參數(shù)代表含義
LayoutInflater.from(getContext()).inflate(R.layout.layout_countdown_time, this, true);
tv_hour = findViewById(R.id.btn_countdown_hour);
tv_min = findViewById(R.id.countdown_min);
tv_sec = findViewById(R.id.countdown_sec);
runnable = new Runnable() {
@Override
public void run() {
boolean needProcess = processCountMsg();
if(!needProcess)return;
//沒隔一秒再次執(zhí)行一次run方法,實現(xiàn)倒計時功能
mHandler.postDelayed(this, 1000);
}
};
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:layout_margin="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="距結束:"
android:textColor="#DAA520"
android:textSize="20dp"/>
<TextView
android:id="@+id/btn_countdown_hour"
android:layout_width="31dp"
android:layout_height="30dp"
android:layout_marginRight="2dp"
android:background="@drawable/countdown_shape"
android:gravity="center"
android:textColor="@color/white" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"/>
<TextView
android:id="@+id/countdown_min"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="2dp"
android:background="@drawable/countdown_shape"
android:textColor="@color/white"
android:gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"/>
<TextView
android:id="@+id/countdown_sec"
android:layout_width="30dp"
android:layout_height="30dp"
android:background="@drawable/countdown_shape"
android:textColor="@color/white"
android:gravity="center"/>
</LinearLayout>
2.3 提供對外方法,處理時間戳
使用post() 發(fā)送消息
public void setData(long curDate) {
//TODO
String time;
//計算時間戳與系統(tǒng)時間的時間差,單位為秒
int timeDifference = (int) (curDate - System.currentTimeMillis());
//將總秒數(shù)轉化為時分秒
if (timeDifference < 60) {
time = String.format("00:00:%02d", timeDifference % 60);
} else if (timeDifference < 3600) {
time = String.format("00:%02d:%02d", timeDifference / 60, timeDifference % 60);
} else {
time = String.format("%02d:%02d:%02d", timeDifference / 3600, timeDifference % 3600 / 60, timeDifference % 60);
}
//通過“:”分離時、分、秒
String[] sArray = time.split(":");
hou = Integer.parseInt(sArray[0]);
min = Integer.parseInt(sArray[1]);
sec = Integer.parseInt(sArray[2]);
//通過Handler中的post()方法傳遞message
mHandler.post(runnable);
}
使用sendMessage發(fā)送消息
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case COUNT_MSG:
boolean needProcess = processCountMsg();
if(!needProcess)return;
Message message = Message.obtain();
message.what = COUNT_MSG;
mHandler.sendMessageDelayed(message, 1000);
break;
}
}
};
public void setData(long curDate) {
...............
Message msg = Message.obtain();
msg.what = COUNT_MSG;
mHandler.sendMessage(msg);
}
3、在Activity中實現(xiàn)
public class MainHandlerActivity extends AppCompatActivity {
private CountDown mTime;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_handler);
mTime = findViewById(R.id.view_countdown);
mTime.setData(System.currentTimeMillis() + 10);
}
}
4、遇到的問題總結
4.1 LayoutInflate
inflate(int resource,ViewGroup root,boolean attachToRoot)
resource:加載的布局id; root:在該布局的外部再嵌套一層父布局,但不是把當前布局放入到界面已有的布局中,比如xml界面,這個方法只是單穿的返回一個view對象。默認attachToRoot是true。
1、如果root為null,attachToRoot將失去作用,設置任何職都沒有意義;
2、如果root不為null,attachToRoot設為true,則會給加載布局文件指定一個父布局,即root;
3、如果root不為null,attachToRoot設為false,則會將布局文件最外層的所有l(wèi)ayout屬性進行設置,當該view被添加到父view當中時,這些layout屬性則自動生效。
4、在不設置attachToRoot參數(shù)的情況下,如果root不為null,attachToRoot參數(shù)默認為true。
4.2 Handler中post()與sendMessage()區(qū)別
post(Runnable r)
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
sendMessage(msg)
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
總結:從源碼分析,post(runnable)與sendMessage(msg)本質(zhì)是一樣的,最后返回的都是sendMessageDelayed(msg,0);post()通過調(diào)用getPostMessage()方法將Runnable賦值到Message的callback變量中;
消息處理:Looper從MessageQueue中取出Message之后,會調(diào)用dispatchMessage方法進行處理;
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
dispatchMessage兩種情況
1、如果Message的callback不為null,一般為通過post(Runnable)方式,會直接執(zhí)行Runnable的run()。因此這里的Runnable實際上就是一個回調(diào)接口,跟線程Thread沒有任何關系;
2、如果Message的callback為null,這種一般為sendMessage的方式,則會調(diào)用handlerMessage()方法進行處理;
4.3 Handler如何實現(xiàn)線程隔離的
final MessageQueue mQueue;
public static @Nullable Looper myLooper(){
return sThreadLocal.get();
}
ThreadLocal是一個能創(chuàng)建線程局部變量的類。通過ThreadLocal提供的get和set方法,可以為每一個使用該變量的線程保存一份數(shù)據(jù)副本,且線程之間是不能相互訪問,從而達到變量在線程間隔離、封閉的效果。
4.4 sendMessageDelayed()是如何實現(xiàn)的
向Message隊列中插入Message時,會根據(jù)Message的執(zhí)行時間排序,而消息的延時處理的核心實現(xiàn)是在獲取Message的階段,MessageQueue的next方法如下:
Message next(){
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
從MessageQueue中取出一個Message,但是當前的系統(tǒng)時間小于Message.when,因此會計算一個timeout,目的是實現(xiàn)在timeout時間段后再將UI線程喚醒,因此后續(xù)處理Message的代碼只會在timeout時間之后才會被CPU執(zhí)行;
如果當前系統(tǒng)時間大于或等于Message.when,那么會返回Message給Looper.loop().但是這個邏輯只能保證在when之前的消息不被處理,不能保證一定在when時被處理。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android UI實現(xiàn)SlidingMenu側滑菜單效果
這篇文章主要為大家詳細介紹了Android UI實現(xiàn)SlidingMenu側滑菜單效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12
Android實現(xiàn)院系專業(yè)三級聯(lián)動
這篇文章主要為大家詳細介紹了Android實現(xiàn)院系專業(yè)三級聯(lián)動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-03-03
android studio按鈕監(jiān)聽的5種方法實例詳解
這篇文章主要介紹了android studio按鈕監(jiān)聽的5種方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
在Android中創(chuàng)建菜單項Menu以及獲取手機分辨率的解決方法
本篇文章小編為大家介紹,在Android中創(chuàng)建菜單項Menu以及獲取手機分辨率的解決方法。需要的朋友參考下2013-04-04
Android RelativeLayout相對布局屬性簡析
在Android應用開發(fā)過程中,為了界面的美觀考慮,經(jīng)常會使用到布局方面的屬性,本文就以此問題深入解析,詳解一下Android RelativeLayout相對布局屬性在實際開發(fā)中的應用,需要的朋友可以參考下2012-11-11

