Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android自定義View之鐘表誕生記

android自定義View之鐘表誕生記

編輯:關於Android編程

很多筒子覺得自定義View是高手的象征,其實不然。大家覺得自定義View難很多情況下可能是因為自定義View涉及到了太多的類和API,把人搞得暈乎乎的,那麼今天我們就從最簡單的繪圖API開始,帶大家來一步一步深入自定義View的世界。

先來看看我們今天要實現的一個效果圖:

\

整個效果很簡單,就是在屏幕上顯示一個鐘表,該鐘表可以自動走動。

OK,那就開始動工吧。

1.准備工作

首先,要實現這個時鐘,我得繼承自View來自己繪制時鐘,因為這種效果沒有辦法繼承已有控件去完善功能。然後我們來看看我們這裡需要哪些變量?在這篇博客中我暫時不打算介紹自定義屬性以及View的測量,這裡我只想介紹繪圖API,所以View的大小以及鐘表表針的顏色等我都暫時先給一個固定的值。OK,那麼我們需要的變量主要就是下面幾個:

 

/**
     * 繪制表盤的畫筆
     */
    private Paint circlePaint;

    /**
     * 繪制表盤數字
     */
    private Paint numPaint;
    /**
     * 繪制表心
     */
    private Paint dotPaint;
    /**
     * 時針
     */
    private Paint hourPaint;
    /**
     * 分針
     */
    private Paint minutePaint;
    /**
     * 秒針
     */
    private Paint secondPaint;
    /**
     * View寬度,默認256dp
     */
    private int width;
    /**
     * View高度,默認256dp
     */
    private int height;
    /**
     * 日歷類,用來獲取當前時間
     */
    private Calendar calendar;
    /**
     * 當前時針顏色
     */
    private int hourColor;
    /**
     * 當前分針顏色
     */
    private int minuteColor;
    /**
     * 當前秒針顏色
     */
    private int secondColor;
    /**
     * 時針寬度
     */
    private int hourWidth;
    /**
     * 分針寬度
     */
    private int minuteWidth;
    /**
     * 秒針寬度
     */
    private int secondWidth;

一共就是這麼多個變量。

 

2.關於構造方法

大家看到,當我繼承View之後,系統要求我實現它的構造方法,構造方法主要有四個,如下:

1.

public ClockView(Context context)

該構造方法是當我在Java代碼中new一個View的時候調用的。

 

2.

 

public ClockView(Context context, AttributeSet attrs)

該構造方法是當我在布局文件中添加一個View時調用的。

 

3.

public ClockView(Context context, AttributeSet attrs, int defStyleAttr)

很多筒子看到第三個參數defStyleAttr之後,誤以為如果我在布局文件中寫了style就會調用該構造方法,其實不然,這個構造方法系統並不會自己去調用(大家有興趣可以自己寫一個style,然後在這個方法中打印日志,看看該方法究竟會不會調用),要由我們自己顯式調用(可以在第二個構造方法中調用)。那麼這裡的defStyleAttr究竟是什麼意思呢?正如這個參數的字面意思,它是我們為自定義的View指定的一個默認樣式。(後面博客我們再來詳細說一下這個方法)。

另外,還有一個高版本使用的構造方法,我們這裡暫不做介紹。

一般情況下,我們需要在構造方法中完成一些初始化的操作,比如讀取在XML文件中定義的屬性,或者初始化畫筆等等,因為我們的控件既有可能是通過Java代碼實例化的,也有可能是在布局文件中通過xml添加的。如前所述,如果我們在Java代碼中初始化控件,那麼將調用第一個構造方法,如果我們在xml布局文件中添加控件,那麼將調用第二個構造方法。這時問題來了,那我們的初始化控件的方法應該寫在那個構造方法中呢?你可以按下面這種方式來寫:

 

    public ClockView(Context context) {
        super(context);
        initView();
    }
    public ClockView(Context context, AttributeSet attrs) {
        super(context,attrs);
        initView();
    }

在兩個構造方法中分別調用初始化的方法,這種方式沒有問題,但是顯得你的思路沒有條理,參考Android提供的其他控件的源碼,我建議在初始化控件時按照下面這種方式來寫:

 

 

    //在代碼中創建控件時調用
    public ClockView(Context context) {
        this(context, null);
    }

    //在布局文件中創建View時調用
    public ClockView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

雖然結果都一樣,但是下面這種寫法顯得你思路很清晰。

 

3.初始化控件

我們在准備工作中定義了許多變量,包括鐘表的顏色,指針的顏色等等許多變量,那麼接下來我們需要在initView這個方法中來初始化這些變量,以供下一步使用,OK,我們來看一看初始化代碼:

 

    private void initView() {
        //獲取當前時間的實例
        calendar = Calendar.getInstance();
        //時鐘默認寬高
        width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
        height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
        //初始化表針的顏色
        hourColor = Color.RED;
        minuteColor = Color.GREEN;
        secondColor = Color.BLUE;
        //初始化表針的寬度
        hourWidth = 8;
        minuteWidth = 5;
        secondWidth = 2;
        //初始化各種畫筆
        circlePaint = new Paint();
        //去鋸齒
        circlePaint.setAntiAlias(true);
        //設置畫筆顏色
        circlePaint.setColor(Color.GREEN);
        //設置畫筆style為描邊
        circlePaint.setStyle(Paint.Style.STROKE);
        //設置描邊的寬度
        circlePaint.setStrokeWidth(6);
        dotPaint = new Paint();
        dotPaint.setAntiAlias(true);
        dotPaint.setColor(Color.RED);
        dotPaint.setStyle(Paint.Style.FILL);
        numPaint = new Paint();
        numPaint.setColor(Color.RED);
        numPaint.setAntiAlias(true);
        //文本對齊方式
        numPaint.setTextAlign(Paint.Align.CENTER);
        hourPaint = new Paint();

        hourPaint.setColor(hourColor);
        hourPaint.setStyle(Paint.Style.FILL);
        hourPaint.setStrokeWidth(hourWidth);
        minutePaint = new Paint();
        minutePaint.setColor(minuteColor);
        minutePaint.setStyle(Paint.Style.FILL);
        minutePaint.setStrokeWidth(minuteWidth);
        secondPaint = new Paint();
        secondPaint.setColor(secondColor);
        secondPaint.setStyle(Paint.Style.FILL);
        secondPaint.setStrokeWidth(secondWidth);
    }

首先是獲得一個當前時間的實例,因為我需要根據手機上的時間來顯示鐘表的時間,其次就是對表針的各種屬性和畫筆進行初始化,這裡的東西都很簡單,我就不再一一細說,大家看代碼注釋,相信都能看得懂。

 

4.繪制鐘表

上面所有的工作做完之後,接下來就是繪制鐘表了,繪制工作我們放在了onDraw方法中執行,在自定義控件時,如果該控件是我們繼承自View來實現的,那麼基本上這個控件就是需要我們自己來繪制了。

OK,那我們來看看鐘表的繪制吧。

鐘表不算復雜,但是我們也需要一步一步來:

首先是繪制表盤,這個最簡單:

 

        //1.圓心X軸坐標,2.圓心Y軸坐標,3.半徑,4.畫筆
        int radius = width / 2 - 10;
        //畫表盤
        canvas.drawCircle(width / 2, height / 2, radius, circlePaint);

radius表示表盤的半徑,通過drawCircle繪制一個圓環,四個參數分別是圓環的中心點坐標,圓環的半徑以及繪制圓環的畫筆。

 

圓環畫好之後,那麼接下來就是繪制表心了,也就是表盤正中心那個紅色的圓心。

 

canvas.drawCircle(width / 2, height / 2, 15, dotPaint);

很簡單吧。

 

OK,兩個最簡單的東東畫完之後,那麼接下來就是繪制表盤的時間刻度了,時間刻度的繪制除了數字之外,還有一個綠色的短線,我們一共要畫十二個這種東西,那麼這個要怎麼繪制呢?思路有很多,你可以按照高中的數學知識,計算出每一個數字的坐標以及每一個短線起始位置和結束位置的坐標,然後繪制出來,毫無疑問,這種方式的計算量太大,那我們這裡采取一個簡單的方式:我每次只在十二點的那個位置上進行繪制,如果需要繪制一點,那麼我把畫布逆時針旋轉30度到十二點的位置,然後畫上1和一個短線之後再將畫布順時針旋轉30度,如果是繪制2點,那麼我把畫布逆時針旋轉60度到十二點的位置,然後繪制上2和一個短線,繪制完成之後再將畫布順時針旋轉60度,思路就是這樣,下面我們來看看代碼:

 

        for (int i = 1; i < 13; i++) {
            //在旋轉之前保存畫布狀態
            canvas.save();
            canvas.rotate(i * 30, width / 2, height / 2);
            //1.2表示起點坐標,3.4表示終點坐標,5.畫筆
            canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 10, circlePaint);
            //畫表盤數字1.要繪制的文本,2.文本x軸坐標,3.文本基線,4.文本畫筆
            canvas.drawText(i + "", width / 2, height / 2 - radius + 22, numPaint);
            //恢復畫布狀態
            canvas.restore();
        }

我用一個循環來繪制這十二個刻度,在每次旋轉畫布之前,我都通過一個canvas.save()方法來保存畫布當前的狀態,保存之後再對畫布進行旋轉操作,旋轉完成之後就是畫線畫數字,這些都很簡單,在繪制完成之後,我需要調用canvas的restore()方法,該方法可以讓畫布恢復到旋轉之前的角度。一般情況下,canvas.save()方法和canvas.restore()方法都是成對出現的,這一點大家要注意。

 

OK,這些東西都繪制完成之後,接下來就該繪制表針了,表針的繪制思路和上面一樣,也是先旋轉表盤,然後繪制表針,繪制完成之後,再把表盤旋轉回之前的狀態。這裡我就不再詳細說明了,大家看代碼:

 

        //獲得當前小時
        int hour = calendar.get(Calendar.HOUR);
        canvas.save();
        //旋轉屏幕
        canvas.rotate(hour * 30, width / 2, height / 2);
        //畫時針
        canvas.drawLine(width / 2, height / 2 + 20, width / 2, height / 2 - 90, hourPaint);
        canvas.restore();

        int minute = calendar.get(Calendar.MINUTE);
        canvas.save();
        canvas.rotate(minute * 6, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 + 30, width / 2, height / 2 - 110, minutePaint);
        canvas.restore();
        int second = calendar.get(Calendar.SECOND);
        canvas.save();
        canvas.rotate(second * 6, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 + 40, width / 2, height / 2 - 130, secondPaint);
        canvas.restore();

OK,所有的事情做完之後,我們可以在布局文件中添加如下代碼,來查看我們的工作做得怎麼樣:

 

 

    

添加完成之後,運行,不出意外的話,你的App上應該已經顯示了一個時鐘了,但是這個時鐘是靜止的,那麼我們該怎麼讓時鐘動起來呢?其實很簡答,我們只需要每隔一秒重新獲取calendar實例,然後重繪鐘表即可,所以,在onDraw方法的開始和結束,我們還要分別添加如下兩行代碼:

 

1.開始處添加:

 

calendar = Calendar.getInstance();

這行代碼用來獲取最新的時間的實例

 

2.結束處添加:

 

postInvalidateDelayed(1000);

這行代碼用來重繪鐘表,不過重繪是在1秒之後。

 

OK,至此,我們的自定義鐘表就完成了,完整的代碼應該是這個樣子:

 

/**
 * Created by wangsong on 2016/3/29.
 */
public class ClockView extends View {

    /**
     * 繪制表盤的畫筆
     */
    private Paint circlePaint;

    /**
     * 繪制表盤數字
     */
    private Paint numPaint;
    /**
     * 繪制表心
     */
    private Paint dotPaint;
    /**
     * 時針
     */
    private Paint hourPaint;
    /**
     * 分針
     */
    private Paint minutePaint;
    /**
     * 秒針
     */
    private Paint secondPaint;
    /**
     * View寬度,默認256dp
     */
    private int width;
    /**
     * View高度,默認256dp
     */
    private int height;
    /**
     * 日歷類,用來獲取當前時間
     */
    private Calendar calendar;
    /**
     * 當前時針顏色
     */
    private int hourColor;
    /**
     * 當前分針顏色
     */
    private int minuteColor;
    /**
     * 當前秒針顏色
     */
    private int secondColor;
    /**
     * 時針寬度
     */
    private int hourWidth;
    /**
     * 分針寬度
     */
    private int minuteWidth;
    /**
     * 秒針寬度
     */
    private int secondWidth;

    //在代碼中創建控件時調用
    public ClockView(Context context) {
        this(context, null);
    }

    //在布局文件中創建View時調用
    public ClockView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        //獲取當前時間的實例
        calendar = Calendar.getInstance();
        //時鐘默認寬高
        width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
        height = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics());
        //初始化表針的顏色
        hourColor = Color.RED;
        minuteColor = Color.GREEN;
        secondColor = Color.BLUE;
        //初始化表針的寬度
        hourWidth = 8;
        minuteWidth = 5;
        secondWidth = 2;
        //初始化各種畫筆
        circlePaint = new Paint();
        //去鋸齒
        circlePaint.setAntiAlias(true);
        //設置畫筆顏色
        circlePaint.setColor(Color.GREEN);
        //設置畫筆style為描邊
        circlePaint.setStyle(Paint.Style.STROKE);
        //設置描邊的寬度
        circlePaint.setStrokeWidth(6);
        dotPaint = new Paint();
        dotPaint.setAntiAlias(true);
        dotPaint.setColor(Color.RED);
        dotPaint.setStyle(Paint.Style.FILL);
        numPaint = new Paint();
        numPaint.setColor(Color.RED);
        numPaint.setAntiAlias(true);
        //文本對齊方式
        numPaint.setTextAlign(Paint.Align.CENTER);
        hourPaint = new Paint();

        hourPaint.setColor(hourColor);
        hourPaint.setStyle(Paint.Style.FILL);
        hourPaint.setStrokeWidth(hourWidth);
        minutePaint = new Paint();
        minutePaint.setColor(minuteColor);
        minutePaint.setStyle(Paint.Style.FILL);
        minutePaint.setStrokeWidth(minuteWidth);
        secondPaint = new Paint();
        secondPaint.setColor(secondColor);
        secondPaint.setStyle(Paint.Style.FILL);
        secondPaint.setStrokeWidth(secondWidth);
    }

    //繪制View
    @Override
    protected void onDraw(Canvas canvas) {
        calendar = Calendar.getInstance();
        //1.圓心X軸坐標,2.圓心Y軸坐標,3.半徑,4.畫筆
        int radius = width / 2 - 10;
        //畫表盤
        canvas.drawCircle(width / 2, height / 2, radius, circlePaint);

        canvas.drawCircle(width / 2, height / 2, 15, dotPaint);
        for (int i = 1; i < 13; i++) {
            //在旋轉之前保存畫布狀態
            canvas.save();
            canvas.rotate(i * 30, width / 2, height / 2);
            //1.2表示起點坐標,3.4表示終點坐標,5.畫筆
            canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 10, circlePaint);
            //畫表盤數字1.要繪制的文本,2.文本x軸坐標,3.文本基線,4.文本畫筆
            canvas.drawText(i + "", width / 2, height / 2 - radius + 22, numPaint);
            //恢復畫布狀態
            canvas.restore();
        }
        //獲得當前小時
        int hour = calendar.get(Calendar.HOUR);
        canvas.save();
        //旋轉屏幕
        canvas.rotate(hour * 30, width / 2, height / 2);
        //畫時針
        canvas.drawLine(width / 2, height / 2 + 20, width / 2, height / 2 - 90, hourPaint);
        canvas.restore();

        int minute = calendar.get(Calendar.MINUTE);
        canvas.save();
        canvas.rotate(minute * 6, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 + 30, width / 2, height / 2 - 110, minutePaint);
        canvas.restore();
        int second = calendar.get(Calendar.SECOND);
        canvas.save();
        canvas.rotate(second * 6, width / 2, height / 2);
        canvas.drawLine(width / 2, height / 2 + 40, width / 2, height / 2 - 130, secondPaint);
        canvas.restore();
        //每隔1秒重繪View,重繪會調用onDraw()方法
        postInvalidateDelayed(1000);
    }
}

以上。

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved