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

1、需求

1.1 实现目标

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

如下图所示:

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:d", timeDifference % 60);
        } else if (timeDifference < 3600) {
            time = String.format("00:d:d", timeDifference / 60, timeDifference % 60);
        } else {
            time = String.format("d:d:d", 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时被处理。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持Devmax。

Handler实现倒计时功能的更多相关文章

  1. ios – 来自UIAlertController的self.navigationController?.popViewControllerAnimated

    我是新手,但我想我已经掌握了它.这让我的进步很难过.我想要做的是当我们无法找到他的查询的相关数据时向用户抛出错误消息,然后继续将他带回到之前的ViewController.但是,我在这方面遇到了麻烦.在我添加操作的行上,我收到以下错误:’UIViewController?’不是Void的子类型我该怎么做呢?

  2. ios – Swift闭包为AnyObject

    如何将()–>()转换为AnyObject?我试图将它转换为:处理程序为AnyObject,但它给我一个错误说:()–>()不符合协议’AnyObject’解决方法HowcanIcast()->()intoAnyObject?

  3. Twilio电话在iOS和Android中无法正常工作

    我正在尝试使用twilioclient在反应本机应用程序.这里是链接Twiliorepo完成所有设置与反应本机twilio.当我打电话一个数字得到问题.我使用了这个链接中的所有步骤.[tid:com.facebook.react.JavaScript]处理程序不是一个函数.(在“处理程序(rtn)”中,’handler’是未定义的)2016-09-2711:00:57.857[致命][tid:co

  4. iOS Parse Stripe Integration

    所有指南和文档都指向了这个方向.我真的不明白GET/POST是什么以及它如何适合iOSObjective-C编程.关于如何设置它的任何指导都将非常感激.我已经坚持了一段时间了.解决方法Parse的条带API并不像它应该的那样完整.它本身不包含许多功能,但可以通过HTTP请求完成.我必须学习一点Javascript和HTTP请求以获得许多功能.当然,你的第一直觉应该告诉你不要在任何设备上存储CC号码!

  5. [Swift]UIKit学习之警告框:UIAlertController和UIAlertView

    Important:UIAlertViewisdeprecatediniOS8.(NotethatUIAlertViewDelegateisalsodeprecated.)TocreateandmanagealertsiniOS8andlater,insteaduseUIAlertControllerwithapreferredStyleofUIAlertControllerStyleAlert.

  6. Swift基础之对话框UIAlertController

    varalertController=UIAlertController(title:"标题",message:"这是一个UIAlertController的默认样式",preferredStyle:UIAlertControllerStyle.Alert)varcancelAction=UIAlertAction(title:"取消",style:UIAlertActionStyle.Cance

  7. Swift下弹出对话框

  8. UIAlertController 测试的修正

    作者:dom,原文链接,原文日期:2015-11-25译者:小袋子;校对:lfb_CD;定稿:千叶知风两个月前,我曾发布了一篇如何测试UIAlertController的文章。一个读者发现测试没有如期地起作用:@dasdom你的测试是正常的,但是在MockUIAction中的简便init方法没有被调用。那是因为handler确实被调用了,看起来就像UIAlertAction真的把handler作为内部变量去存储动作的handler闭包。这是非常脆弱的,并且Larhythimx在另一个tweet指出在他的测

  9. Swift2.0-异常处理Exception handler

    Swift2.0-异常处理前言关于我们为什么要使用异常处理,请看百度百科为我们作出的描述,想要更详细的资料请点这里以上摘自百度百科:关联,在Objective-C中,异常处理一般都是使用NSError类接收异常和抛出异常,使用方法像这样不得不说,Swift的异常处理更为优雅,下面会重点介绍。去执行该函数不建议使用try!

  10. Swift面向协议编程简介

    Swift面向协议编程简介协议是Swift编程语言中一个非常强大的特性。Swift在编译时检查协议一致性问题,使得开发者可以在运行程序前发现代码中的一些致命错误。Swfit通过提供一些最常见奇怪问题的解决方案以及许多其他编程语言的接口限制,进一步获得了使用协议的便利性。Swift标准库扩展除了扩展自己的协议,还可以扩展来自Swift标准库的协议。

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部