Handler實現(xiàn)倒計時功能
本文實例為大家分享了Handler實現(xiàn)倒計時功能的具體代碼,供大家參考,具體內(nèi)容如下
1、需求
1.1 實現(xiàn)目標
當后臺傳遞一個時間戳時,與當前系統(tǒng)時間做時間差,并轉(zhuǎn)換為時分秒,作為商品活動的倒計時;
如下圖所示:
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="距結(jié)束:" 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ù)轉(zhuǎn)化為時分秒 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、遇到的問題總結(jié)
4.1 LayoutInflate
inflate(int resource,ViewGroup root,boolean attachToRoot)
resource:加載的布局id; root:在該布局的外部再嵌套一層父布局,但不是把當前布局放入到界面已有的布局中,比如xml界面,這個方法只是單穿的返回一個view對象。默認attachToRoot是true。
1、如果root為null,attachToRoot將失去作用,設(shè)置任何職都沒有意義;
2、如果root不為null,attachToRoot設(shè)為true,則會給加載布局文件指定一個父布局,即root;
3、如果root不為null,attachToRoot設(shè)為false,則會將布局文件最外層的所有l(wèi)ayout屬性進行設(shè)置,當該view被添加到父view當中時,這些layout屬性則自動生效。
4、在不設(shè)置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); }
總結(jié):從源碼分析,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沒有任何關(guān)系;
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)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android UI實現(xiàn)SlidingMenu側(cè)滑菜單效果
這篇文章主要為大家詳細介紹了Android UI實現(xiàn)SlidingMenu側(cè)滑菜單效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-12-12Android實現(xiàn)院系專業(yè)三級聯(lián)動
這篇文章主要為大家詳細介紹了Android實現(xiàn)院系專業(yè)三級聯(lián)動,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-03-03android studio按鈕監(jiān)聽的5種方法實例詳解
這篇文章主要介紹了android studio按鈕監(jiān)聽的5種方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03在Android中創(chuàng)建菜單項Menu以及獲取手機分辨率的解決方法
本篇文章小編為大家介紹,在Android中創(chuàng)建菜單項Menu以及獲取手機分辨率的解決方法。需要的朋友參考下2013-04-04Android RelativeLayout相對布局屬性簡析
在Android應(yīng)用開發(fā)過程中,為了界面的美觀考慮,經(jīng)常會使用到布局方面的屬性,本文就以此問題深入解析,詳解一下Android RelativeLayout相對布局屬性在實際開發(fā)中的應(yīng)用,需要的朋友可以參考下2012-11-11