Handler實(shí)現(xiàn)倒計(jì)時(shí)功能
本文實(shí)例為大家分享了Handler實(shí)現(xiàn)倒計(jì)時(shí)功能的具體代碼,供大家參考,具體內(nèi)容如下
1、需求
1.1 實(shí)現(xiàn)目標(biāo)
當(dāng)后臺(tái)傳遞一個(gè)時(shí)間戳?xí)r,與當(dāng)前系統(tǒng)時(shí)間做時(shí)間差,并轉(zhuǎn)換為時(shí)分秒,作為商品活動(dòng)的倒計(jì)時(shí);
如下圖所示:
1.2 實(shí)現(xiàn)步驟
自定義View
1、實(shí)現(xiàn)倒計(jì)時(shí)功能,封裝成方法;
2、初始化倒計(jì)時(shí)功能,及布局文件;
3、通過(guò)Handler中的post()或sendMessage()方法向主線程傳遞消息,不對(duì)刷新UI;
4、對(duì)外暴露一個(gè)方法,接收后臺(tái)傳入的時(shí)間戳;
在Activity中實(shí)現(xiàn)
通過(guò)自定義View中的方法,接收時(shí)間戳;
2、封裝成自定義view
2.1 倒計(jì)時(shí)功能
方法名 processCountMsg()
private boolean processCountMsg() { if (hou == 0 && min == 0 && sec == 0) { Toast.makeText(getContext(), "時(shí)間到", 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 初始化倒計(jì)時(shí)功能及布局文件
初始化代碼 init()
private void init() { //TODO LayoutInflater中inflate三個(gè)參數(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; //沒(méi)隔一秒再次執(zhí)行一次run方法,實(shí)現(xiàn)倒計(jì)時(shí)功能 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 提供對(duì)外方法,處理時(shí)間戳
使用post() 發(fā)送消息
public void setData(long curDate) { //TODO String time; //計(jì)算時(shí)間戳與系統(tǒng)時(shí)間的時(shí)間差,單位為秒 int timeDifference = (int) (curDate - System.currentTimeMillis()); //將總秒數(shù)轉(zhuǎn)化為時(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); } //通過(guò)“:”分離時(shí)、分、秒 String[] sArray = time.split(":"); hou = Integer.parseInt(sArray[0]); min = Integer.parseInt(sArray[1]); sec = Integer.parseInt(sArray[2]); //通過(guò)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中實(shí)現(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、遇到的問(wèn)題總結(jié)
4.1 LayoutInflate
inflate(int resource,ViewGroup root,boolean attachToRoot)
resource:加載的布局id; root:在該布局的外部再嵌套一層父布局,但不是把當(dāng)前布局放入到界面已有的布局中,比如xml界面,這個(gè)方法只是單穿的返回一個(gè)view對(duì)象。默認(rèn)attachToRoot是true。
1、如果root為null,attachToRoot將失去作用,設(shè)置任何職都沒(méi)有意義;
2、如果root不為null,attachToRoot設(shè)為true,則會(huì)給加載布局文件指定一個(gè)父布局,即root;
3、如果root不為null,attachToRoot設(shè)為false,則會(huì)將布局文件最外層的所有l(wèi)ayout屬性進(jìn)行設(shè)置,當(dāng)該view被添加到父view當(dāng)中時(shí),這些layout屬性則自動(dòng)生效。
4、在不設(shè)置attachToRoot參數(shù)的情況下,如果root不為null,attachToRoot參數(shù)默認(rèn)為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()通過(guò)調(diào)用getPostMessage()方法將Runnable賦值到Message的callback變量中;
消息處理:Looper從MessageQueue中取出Message之后,會(huì)調(diào)用dispatchMessage方法進(jìn)行處理;
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,一般為通過(guò)post(Runnable)方式,會(huì)直接執(zhí)行Runnable的run()。因此這里的Runnable實(shí)際上就是一個(gè)回調(diào)接口,跟線程Thread沒(méi)有任何關(guān)系;
2、如果Message的callback為null,這種一般為sendMessage的方式,則會(huì)調(diào)用handlerMessage()方法進(jìn)行處理;
4.3 Handler如何實(shí)現(xiàn)線程隔離的
final MessageQueue mQueue; public static @Nullable Looper myLooper(){ return sThreadLocal.get(); }
ThreadLocal是一個(gè)能創(chuàng)建線程局部變量的類(lèi)。通過(guò)ThreadLocal提供的get和set方法,可以為每一個(gè)使用該變量的線程保存一份數(shù)據(jù)副本,且線程之間是不能相互訪問(wèn),從而達(dá)到變量在線程間隔離、封閉的效果。
4.4 sendMessageDelayed()是如何實(shí)現(xiàn)的
向Message隊(duì)列中插入Message時(shí),會(huì)根據(jù)Message的執(zhí)行時(shí)間排序,而消息的延時(shí)處理的核心實(shí)現(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中取出一個(gè)Message,但是當(dāng)前的系統(tǒng)時(shí)間小于Message.when,因此會(huì)計(jì)算一個(gè)timeout,目的是實(shí)現(xiàn)在timeout時(shí)間段后再將UI線程喚醒,因此后續(xù)處理Message的代碼只會(huì)在timeout時(shí)間之后才會(huì)被CPU執(zhí)行;
如果當(dāng)前系統(tǒng)時(shí)間大于或等于Message.when,那么會(huì)返回Message給Looper.loop().但是這個(gè)邏輯只能保證在when之前的消息不被處理,不能保證一定在when時(shí)被處理。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android利用handler實(shí)現(xiàn)倒計(jì)時(shí)功能
- Android Handler實(shí)現(xiàn)閃屏頁(yè)倒計(jì)時(shí)代碼
- Android使用Handler實(shí)現(xiàn)倒計(jì)時(shí)功能
- Android使用Handler實(shí)現(xiàn)定時(shí)器與倒計(jì)時(shí)器功能
- Android中Handler實(shí)現(xiàn)倒計(jì)時(shí)的兩種方式
- Android中使用Handler及Countdowntimer實(shí)現(xiàn)包含倒計(jì)時(shí)的閃屏頁(yè)面
相關(guān)文章
Android UI實(shí)現(xiàn)SlidingMenu側(cè)滑菜單效果
這篇文章主要為大家詳細(xì)介紹了Android UI實(shí)現(xiàn)SlidingMenu側(cè)滑菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12詳解Android冷啟動(dòng)實(shí)現(xiàn)APP秒開(kāi)的方法
這篇文章給大家介紹的是Android冷啟動(dòng)實(shí)現(xiàn)APP秒開(kāi)的方法,對(duì)大家日常開(kāi)發(fā)APP還是很實(shí)用的,有需要的可以參考借鑒。2016-08-08Android 手機(jī)防止休眠的兩種實(shí)現(xiàn)方法
這篇文章主要介紹了Android 手機(jī)防止休眠方法的相關(guān)資料,一種是在Manifest.xml文件里面聲明,另外一種方法是在代碼里面修改LayoutParams的標(biāo)志位,需要的朋友可以參考下2017-08-08Android實(shí)現(xiàn)院系專(zhuān)業(yè)三級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)院系專(zhuān)業(yè)三級(jí)聯(lián)動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03android studio按鈕監(jiān)聽(tīng)的5種方法實(shí)例詳解
這篇文章主要介紹了android studio按鈕監(jiān)聽(tīng)的5種方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03在Android中創(chuàng)建菜單項(xiàng)Menu以及獲取手機(jī)分辨率的解決方法
本篇文章小編為大家介紹,在Android中創(chuàng)建菜單項(xiàng)Menu以及獲取手機(jī)分辨率的解決方法。需要的朋友參考下2013-04-04Android RelativeLayout相對(duì)布局屬性簡(jiǎn)析
在Android應(yīng)用開(kāi)發(fā)過(guò)程中,為了界面的美觀考慮,經(jīng)常會(huì)使用到布局方面的屬性,本文就以此問(wèn)題深入解析,詳解一下Android RelativeLayout相對(duì)布局屬性在實(shí)際開(kāi)發(fā)中的應(yīng)用,需要的朋友可以參考下2012-11-11