Android 7.1 Deskclock(二) TimerFragment分析

来源:互联网 发布:马士兵java教程全集 编辑:程序博客网 时间:2024/06/06 08:55

    这次我要讲解TimerFragment部分,因为此部分在DeskClock模块也算一个重要部分。

  TimerFragment即是计时器。它包括两部分:一个是CreateTimerView视图——设置时间部分;一个是TimersView视图——定时运行部分。CreateTimerView部分主要是给用户设定时间并显示出来;TimersView部分主要用圆圈和数字动态显示时间流逝过程。

 本文主要从三个方面讲解:自定义控件、数据模型、代码流程。

1. 自定义控件

   不管是CreateTimerView,还是TimersView视图,主要包含了自定义控件。所以需要了解其自定义控件是怎么样实现的。

(1)CreateTimerView(TimerSetupView )

    CreateTimerView部分实际布局为TimerSetupView,而TimerSetupView是自定义视图类,里面包含显示时间的自定义控件和显示0-9的按键。从TimerSetupViewextends LinearLayout implements Button.OnClickListener,Button.OnLongClickListener看,OnClickListener是针对0-9按键和删除按键,而OnLongClickListener只针对删除按键。

 TimerSetupView 的布局由time_setup_view控制,time_setup_view里面有自定义控件com.android.deskclock.timer.TimerView和0-9的buttion,我们着重讲下com.android.deskclock.timer.TimerView。

  

   如上图所示:TimerView里面布局包含了八个TextView和一个ImageView,八个TextView分别用来存放小时的十位、个位值,分钟的十位和个位值,秒,以及汉字“小时、分钟、秒”;一个ImageView是放置“删除”图片。写到这里,大家应该就明白:其实在CreateTimerView界面设置时间时,是用一个一个TextView显示时间的值,那么整个时间设定界面就是如此:当用户点击0-9个按钮时,我们把对应的值用这八个buttion显示出来,同时计算出来等于多少微妙,将其值传给Timer。

   那点击的button值,是如何传入TextView呢?

         for (int i = 0; i < mNumbers.length; i++) {

           mNumbers[i].setOnClickListener(this);

           mNumbers[i].setText(String.valueOf(i));

           mNumbers[i].setTextColor(Color.WHITE);

           mNumbers[i].setTag(R.id.numbers_key, i);

       }

     由上面代码看出,首先在每一个Button中设置点击事件,然后将button上面显示的值存入到numbers_key(其中numbers_key为<item name="numbers_key" type="id" />)中,那么用户点击哪个按钮,值就放入其中,然后就可以取出来显示和计算了。

     但显示规则不同于常规,它是从右边向左边移动,比如用户依次按下123,那么手机上是这样显示的:1秒->12秒->1分12秒。

     这种平移是怎么实现的呢?

    public static void arraycopy(Object src, int srcPos,Object dest,intdestPos,int length)

    src:源数组;      

    srcPos:源数组要复制的起始位置;

    dest:目的数组;        

    destPos:目的数组放置的起始位置;      

    length:复制的长度。

    只要将源数组和目的数组使用同一个数组即可实现数组平移,如下所示: 

System.arraycopy(mInput,0, mInput, 1, mInputPointer + 1);

    mInput是用户点击0-9button后的一串数组值,mInputPointer(默认值为-1)是用户点击的button个数。

    而删除也是一个一个的删除,是从左到右,那么也可以用此方法反过来操作:

System.arraycopy(mInput,1, mInput, 0, mInputPointer);

    剩下来就是计算其时间了,因为我们知道每一位的值,所以就很好计算时间了:

   final int hoursInSeconds = mInput[5] * 36000+ mInput[4] * 3600;

   final int minutesInSeconds = mInput[3] * 600+ mInput[2] * 60;

   final int seconds = mInput[1] * 10 +mInput[0];

(2)TimersView


  TimerView主要包括一个ViewPager,而ViewPager里面内容是TimerItemFragment,适配器是TimerPagerAdapter。TimerItemFragment里面的视图就是用户看到的一个白圆圈,一些变化的数字,和标签以及重置图标,布局实际为timer_item。timer_item就包含两个自定义控件:com.android.deskclock.timer.TimerCircleView和com.android.deskclock.timer.CountingTimerView,其中TimerCircleView是用户可以看到的红点走白圈;CountingTimerView是时间数字的变化。

    那么如何实现这两种视图呢?

   TimerCircleView其实比较简单,继承了View,首先计算出布局空间大小,算出原点,然后使用此方法:

   public void drawArc (RectF oval, floatstartAngle, float sweepAngle, boolean useCenter, Paint paint)

   参数说明:

   oval:圆弧所在的椭圆对象。

   startAngle:圆弧的起始角度。

   sweepAngle:圆弧的角度。

   useCenter:是否显示半径连线,true表示显示圆弧与圆心的半径连线,false表示不显示。

   paint:绘制时所使用的画笔。

   从此方法参数看,只要算出圆弧的角度,就能围绕圆心画圆弧,那如何实现动态移动呢?其实和动画片一样的方法,就是每一段时间更新变化的视图,人眼就以为在动。所以重点是计算角度,那怎么算呢?其实理解为:总时间走完就是一圈,所以如果总时间设定为X,那每一次绘制的角度为360/x,然后通过onDraw方法显示出来,定时用postInvalidateOnAnimation更新onDraw方法即可。

   CountingTimerView可以显示小时、分钟、秒,但圆圈中的位置只有那么大,所以当只有秒时,数字是最大的;当有小时、分钟、秒时字体会变小,并且秒显示的地方会移位,因此位置的计算以及是否有小时、分钟都需要考虑。而数字的变化是怎么显示出来呢?

   用到了 canvas.drawText方法,当我们计算出小时、分钟、秒时,在指定的位置显示出来,然后每隔一段时间就刷新显示,所以就实现了数字变化的视图效果。

  主要的视图讲完了,其他小视图,读者可以自己看看源码。

2. 数据模型

    在com.android.deskclock.data中包含了闹钟的所有数据的操作类,其中就有定时器相关的数据操作类。定时器数据操作类包括Timer.java、TimerDAO.java、TimerModel.java、DataModel.java。其中DataModel是DeskClock模块调用数据的“总管”,针对每一个fragment功能,DataModel里面就调用每一个功能的数据模型,例如定时器,DataModel里面调用了TimerModel,TimerModel是定时器独有的数据模型,而TimerModel调用的是TimerDAO,TimerDAO才是真正存储数据地方,他们会把定时器的数据放入闹钟中的SharedPreferences中存储着。

    那Timer是什么作用呢?其实Timer.java很重要,它里面有定时器的属性和状态等特性,所以我们在创建新定时器时,除了保存到SharedPreferences中,还会以Timer形式return回去。

    Timer包括RUNNING、PAUSED、 EXPIRED、 RESET四种状态。

    在定时器的整个过程中,每一状态的变更,都可能影响视图的变化(比如按钮)、定时器的时间计算,以及通知栏信息的变更。所以定时器数据流转过程为:当用户点击开始或者暂停,添加定时器等操作,就会把该定时器数据存储在SharedPreferences,并把状态、总共时间、剩余时间等信息以Timer形式return回,最后在TimerModel里面通过AlarmManager安排定时或者通过TimerKlaxon决定是否响铃的操作。

    明白了视图难点和数据流转,再来看看代码流程,就一目了然。

3. 代码流程

   

    上图中左边是创建定时器到定时器Running过程的代码流程,右边是到时间了铃声响起的EXPIRED代码流程。代码流程中,只主要列出关键性方法,其细节还需要大家自己去看源代码。

    刚进入TimerFragment时,会用hasTimers方法判断是否有定时器,如果没有则显示CreateTimerView视图,待用户点击0-9按钮设定时间后,点击开始按钮,CreateListener响应,因此会进入定时器Running视图——TimersView,同时会进入TimerModel中。TimersView里面主要是PagerView,其适配器是TimerPagerAdapter,而每一个page里面装载的内容是TimerItemFragment,即Running的完整视图;TimerModel里面有doUpdateTimer,它会对Timer数据更新并存储,会通过AlarmManager设定系统闹钟时间,如下所示:

mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,triggerTime, pi);

    ELAPSED_REALTIME_WAKEUP表示真实时间流逝闹钟,当闹钟发躰时唤醒手机休眠;

    triggerTime表示定时器的时长;

    Pi表示PendingIntent,而Internet激活的是TimerService,后面会说到;

  TimerModel里面的updateNotification会判断当前DeskClock是否属于当前应用,如果不是则调用NotificationCompat,在手机的通知栏显示定时器信息以及两个可以操作的按钮。

    定时器以及设置完毕,左边流程已经走完,然后等到定时器时间到,铃声响起,即进入右边流程。

    当定时器的时间到了,AlarmManager会激发PendingIntent里面的TimerService,而TimerService会根据之前设定的Action,进入DataModel对应的方法中,比如,当时间到了,定时器的状态就变为EXPIRED,进入DataModel中expireTimer方法,最后进入doUpdateTimer方法中,而里面会判断定时器状态,如果为EXPIRED,则进入TimerKlaxon控制声音的响起。需要说明的是TimerKlaxon最后是调用的Framwork层的接口播放音乐。如果用户在状态栏点击了暂停、重置等按钮,TimerService会进入DataModel中对应的方法响应。至此,整个流程都走完了。当然,如果用户通过点击界面中的暂停,或者通知栏的暂停操作,都会改变整个代码流程,但思路是差不多的,此代码流程图是在用户不做任何操作时的流程。

总结:

    文章主要介绍了TimerFragment相关的知识,其中TimerFragment的难点是自定义控件比较多,数据操作多,而代码流程不难。有一点我想提下:View.post(Runnable  action)方法,是及时更新UI界面的一种方法,其中ViewPager中的视图,都是通过此方法更新的:mViewPager.post(mTimeUpdateRunnable),以后记得熟练此方法。


原创粉丝点击