自定义View做的一个Clock案例

这是整个项目的结构。下来看看My_Clock.java文件内容

My_Clock.java Code:
  1. package com.android.yhb;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5.   
  6. public class My_Clock extends Activity {  
  7.     /** Called when the activity is first created. */  
  8.     @Override  
  9.     public void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.main);  
  12.     }  
  13. }  

My_ClockView.java的代码:

My_ClockView.Java  Code:
  1. package com.android.yhb;  
  2.   
  3. import java.util.TimeZone;  
  4.   
  5. import android.content.BroadcastReceiver;  
  6. import android.content.Context;  
  7. import android.content.Intent;  
  8. import android.content.IntentFilter;  
  9. import android.content.res.Resources;  
  10. import android.graphics.Canvas;  
  11. import android.graphics.drawable.Drawable;  
  12. import android.os.Handler;  
  13. import android.text.format.Time;  
  14. import android.util.AttributeSet;  
  15. import android.view.View;  
  16.   
  17. public class My_ClockView extends View {  
  18.   
  19.     private Time mCalendar;  
  20.       
  21.     //表盘上的给指针图片  
  22.     private Drawable mHourHand;  
  23.     private Drawable mMinuteHand;  
  24.     private Drawable mDial;  
  25.       
  26.     private boolean mAttached; //附着状态  
  27.       
  28.     private final Handler mHandler = new Handler(); //定一个Handler类实现更新时间  
  29.   
  30.     //定义表盘的宽和高  
  31.     private int mDialWidth;  
  32.     private int mDialHeight;  
  33.       
  34.     private float mMinute;  
  35.     private float mHour;  
  36.     private boolean mChanged;  
  37.       
  38.     public My_ClockView(Context context) {  
  39.         this(context, null);  
  40.         // TODO Auto-generated constructor stub  
  41.     }  
  42.       
  43.     public My_ClockView(Context context, AttributeSet attrs) {  
  44.         this(context, attrs, 0);  
  45.         // TODO Auto-generated constructor stub  
  46.     }  
  47.   
  48.     public My_ClockView(Context context, AttributeSet attrs, int defStyle) {  
  49.         super(context, attrs, defStyle);  
  50.         // TODO Auto-generated constructor stub  
  51.         Resources r = context.getResources();  
  52.           
  53.         //通过资源加载各图片,用于绘制时钟  
  54.         mDial = r.getDrawable(R.drawable.clock);  
  55.         mHourHand = r.getDrawable(R.drawable.hour_hander);  
  56.         mMinuteHand = r.getDrawable(R.drawable.minute_hander);  
  57.           
  58.         mCalendar = new Time();  
  59.         mDialWidth = mDial.getIntrinsicWidth();  
  60.         mDialHeight = mDial.getIntrinsicHeight();  
  61.     }  
  62.       
  63.      @Override  
  64.         protected void onAttachedToWindow() {  
  65.             super.onAttachedToWindow();  
  66.             if (!mAttached) {  
  67.                 mAttached = true;  
  68.                 IntentFilter filter = new IntentFilter(); //注册一个消息过滤器,获取时间改变、时区改变的action  
  69.                 filter.addAction(Intent.ACTION_TIME_TICK);   
  70.                 filter.addAction(Intent.ACTION_TIME_CHANGED);  
  71.                 filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);  
  72.                 getContext().registerReceiver(mIntentReceiver, filter, null, mHandler);  
  73.             }  
  74.               mCalendar = new Time();  
  75.             onTimeChanged();  
  76.         }  
  77.         @Override  
  78.         protected void onDetachedFromWindow() {  
  79.             super.onDetachedFromWindow();  
  80.             if (mAttached) {  
  81.                 getContext().unregisterReceiver(mIntentReceiver); //反注册消息过滤器  
  82.                 mAttached = false;  
  83.             }  
  84.         }  
  85.   
  86.   
  87.     @Override  
  88.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  89.         // TODO Auto-generated method stub  
  90.         //取得组件的宽和高,以及指定模式  
  91.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  92.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  93.         int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  94.         int heightSize = MeasureSpec.getSize(heightMeasureSpec);  
  95.           
  96.         float hScale = 1.0f;  
  97.         float vScale = 1.0f;  
  98.           
  99.         if(widthMode != MeasureSpec.UNSPECIFIED && widthSize < mDialWidth) {  
  100.             hScale = (float)widthSize/(float)mDialWidth;  
  101.         }  
  102.         if(heightMode != MeasureSpec.UNSPECIFIED && heightSize < mDialHeight) {  
  103.             vScale = (float)heightSize/(float)mDialHeight;  
  104.         }  
  105.           
  106.         //如果表盘图像的宽和高超出其组件的宽和高,即要进行相应的缩放  
  107.         float scale = Math.min(hScale, vScale);  
  108.         setMeasuredDimension(resolveSize((int)(mDialWidth*scale), widthMeasureSpec), resolveSize((int)(mDialHeight*scale), heightMeasureSpec));  
  109.     }  
  110.       
  111.     //绘制组件的方法,使用Canvas对象绘制组件的具体表现  
  112.     @Override  
  113.     protected void onDraw(Canvas canvas) {  
  114.         // TODO Auto-generated method stub  
  115.         super.onDraw(canvas);  
  116.         //用changed标识来判断是否需要重新绘制  
  117.         boolean changed = mChanged;  
  118.         if(changed) {  
  119.             mChanged = false;  
  120.         }  
  121.           
  122.         final int mRight = getRight();  
  123.         final int mLeft = getLeft();  
  124.         final int mTop = getTop();  
  125.         final int mBottom = getBottom();  
  126.           
  127.         int availableWidth = mRight - mLeft;  
  128.         int availableHeight = mBottom - mTop;  
  129.           
  130.         int x = availableWidth/2;  
  131.         int y = availableHeight/2;  
  132.           
  133.         final Drawable dial = mDial;  
  134.         int w = dial.getIntrinsicWidth();  
  135.         int h = dial.getIntrinsicHeight();  
  136.         boolean scaled = false;  
  137.           
  138.         if(availableWidth < w || availableHeight < h) {  
  139.             scaled = true;  
  140.             float scale = Math.min((float)availableWidth/(float)w, (float)availableHeight/(float)h);  
  141.             canvas.save();  
  142.               
  143.             canvas.scale(scale, scale, x, y);  
  144.         }  
  145.           
  146.         if(changed) {  
  147.             dial.setBounds(x-(w/2), y-(h/2), x+(w/2), y+(h/2));//设置界线  
  148.         }  
  149.           
  150.         dial.draw(canvas);  
  151.           
  152.         canvas.save();  
  153.         canvas.rotate(mHour/12.0f*360.0f, x, y);//根据系统时间绘制时针旋转的角度  
  154.         final Drawable hourHand = mHourHand;  
  155.         if(changed) {  
  156.             w = hourHand.getIntrinsicWidth();  
  157.             h = hourHand.getIntrinsicHeight();  
  158.             hourHand.setBounds(x-(w/2), y-(h/2), x+(w/2), y+(h/2));  
  159.         }  
  160.         hourHand.draw(canvas);  
  161.         canvas.restore();  
  162.           
  163.         canvas.save();  
  164.         canvas.rotate(mMinute / 60.0f * 360.0f, x, y); //同理,分针旋转的角度  
  165.         final Drawable minuteHand = mMinuteHand;  
  166.         if (changed) {  
  167.             w = minuteHand.getIntrinsicWidth();  
  168.             h = minuteHand.getIntrinsicHeight();  
  169.             minuteHand.setBounds(x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2));  
  170.         }  
  171.         minuteHand.draw(canvas);  
  172.         canvas.restore();  
  173.   
  174.         if(scaled) {  
  175.             canvas.restore();  
  176.         }  
  177.     }  
  178.       
  179.     private void onTimeChanged() {  //获取时间改变,计算当前的时分秒  
  180.         mCalendar.setToNow();  
  181.         int hour = mCalendar.hour;  
  182.         int minute = mCalendar.minute;  
  183.         int second = mCalendar.second;  
  184.         mMinute = minute + second / 60.0f;  
  185.         mHour = hour + mMinute / 60.0f;  
  186.         mChanged = true;  
  187.     }  
  188.   
  189.       
  190.     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { //监听获取时间改变action  
  191.         @Override  
  192.         public void onReceive(Context context, Intent intent) {  
  193.             if (intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED)) {  
  194.                 String tz = intent.getStringExtra("time-zone");  
  195.                 mCalendar = new Time(TimeZone.getTimeZone(tz).getID());  
  196.             }  
  197.             onTimeChanged(); //获取新的时间  
  198.               
  199.             invalidate(); //刷新屏幕,强制类调用onDraw方法实现分针时针的走动  
  200.         }  
  201.     };   
  202.   
  203. }  

main.xml的代码:

Code:
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent"  
  6.     android:background="@drawable/theme"  
  7.     android:gravity="center"  
  8.     >  
  9. <com.android.yhb.My_ClockView  
  10.     android:layout_width="200px"  
  11.     android:layout_height="200px"  
  12.     />  
  13. </LinearLayout>  

 

上图是最终的运行结果!这个其实是OPhoneSDN里面的一个实例,做的时候遇到了一点小麻烦,不是技术上的问题,而是在找Clock素材的时候,我找的是一个完整的Clock,然后通过PS把它的时针和分针抠出来,抠出来保存的时候我并没有根据表盘的尺寸进行保存,而是直接把图片大小wrap时针/分针进行保存,出现了错误,导致分针/时针在表盘中散乱分部,还应注意一点,保存的图片需要旋转,使得时针/分针指向正上方,也就是在没有旋转之前让时针/分针指向12点这个位置,这样旋转出来的角度才会准确,进而做出来的Clock才能和真实的时间一致,大家是否注意到我的Clock的显示和真实的数字时间有微小的出入,正是因为我在旋转图片的时候没有准确旋转导致的……

对上面的代码进行一下简单的分析总结:1.关于onMeasure()方法,该方法里面传入的参数是widthMeasureSpec和heightMeasureSpec,可以通过这两个参数获取规定的宽和高以及模式。关于模式有三种:A、UNSPECIFIED说明容器对组件本身的尺寸没有任何限制,组件可以根据自己的需要随意规划自己的尺寸。这种情况下,容器提供的尺寸没有任何意义了;

B、EXACTLY说明容器严格要求其组件的尺寸必须为给定尺寸,不能自己决定尺寸大小;

C、AT_MOST说明容器提供的尺寸是一个最小值,也就是说,组件可以随意决定自己的尺寸,只要不大于容器指定的尺寸即可。可以通过方法public static int makeMeasureSpec(int size, int mode)进行定义模式。

关于onDraw()方法的主要步骤分析:大致是这样的,先比较dial/minuteHand/hourHand与View的大小,如果大的话对canvas进行缩放,然后接着就是设置setBounds();这个方法只要就是为了把图片画在制定的矩形区域里面,其实你可以任意设置这个区域的大小,当然图片也会跟着矩形区域的大小进行缩放,这里并没有进行缩放,即按照原图片的大小进行矩形区域的绘画,只是把这个矩形区域花在了view的中央,这样把限制在这个矩形区域里面的图片绘画在已经缩放过的cavans中的,达到了缩放效果……由于已经在画dial的之前对canvas进行了缩放,所以之后的hourHand/minuteHand直接画在canvas上,而无需再次惊醒缩放……这是我对这个过程的理解,有不对之处敬请大家的指教……

 

你可能感兴趣的:(自定义View做的一个Clock案例)