软件编程
位置:首页>> 软件编程>> Android编程>> Handler实现倒计时功能

Handler实现倒计时功能

作者:木子@喵  发布时间:2021-06-01 15:24:31 

标签:Handler,倒计时

本文实例为大家分享了Handler实现倒计时功能的具体代码,供大家参考,具体内容如下

1、需求

1.1 实现目标

当后台传递一个时间戳时,与当前系统时间做时间差,并转换为时分秒,作为商品活动的倒计时;

如下图所示:

Handler实现倒计时功能

1.2 实现步骤

自定义View

1、实现倒计时功能,封装成方法;
2、初始化倒计时功能,及布局文件;
3、通过Handler中的post()或sendMessage()方法向主线程传递消息,不对刷新UI;
4、对外暴露一个方法,接收后台传入的时间戳;

在Activity中实现
通过自定义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三个参数代表含义
    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;
               //没隔一秒再次执行一次run方法,实现倒计时功能
               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() 发送消息


public void setData(long curDate) {
       //TODO
       String time;
       //计算时间戳与系统时间的时间差,单位为秒
       int timeDifference = (int) (curDate - System.currentTimeMillis());
       //将总秒数转化为时分秒
       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发送消息


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中实现


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,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性则自动生效。
4、在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。

4.2 Handler中post()与sendMessage()区别

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)本质是一样的,最后返回的都是sendMessageDelayed(msg,0);post()通过调用getPostMessage()方法将Runnable赋值到Message的callback变量中;

消息处理:Looper从MessageQueue中取出Message之后,会调用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)方式,会直接执行Runnable的run()。因此这里的Runnable实际上就是一个回调接口,跟线程Thread没有任何关系;

2、如果Message的callback为null,这种一般为sendMessage的方式,则会调用handlerMessage()方法进行处理;

4.3 Handler如何实现线程隔离的


final MessageQueue mQueue;
public static @Nullable Looper myLooper(){
 return sThreadLocal.get();
}

ThreadLocal是一个能创建线程局部变量的类。通过ThreadLocal提供的get和set方法,可以为每一个使用该变量的线程保存一份数据副本,且线程之间是不能相互访问,从而达到变量在线程间隔离、封闭的效果。

4.4 sendMessageDelayed()是如何实现的

向Message队列中插入Message时,会根据Message的执行时间排序,而消息的延时处理的核心实现是在获取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,但是当前的系统时间小于Message.when,因此会计算一个timeout,目的是实现在timeout时间段后再将UI线程唤醒,因此后续处理Message的代码只会在timeout时间之后才会被CPU执行;
如果当前系统时间大于或等于Message.when,那么会返回Message给Looper.loop().但是这个逻辑只能保证在when之前的消息不被处理,不能保证一定在when时被处理。

来源:https://blog.csdn.net/qq_20647053/article/details/115858676

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com