Android仿華為天氣繪制刻度盤

liang52413 7年前發布 | 20K 次閱讀 安卓開發 Android開發 移動開發

效果圖

可以看到這個自定義控件結合了顏色漸變、動態繪制刻度、動態水球效果。接下來我們就來看看這個效果是如何一步一步實現的。

開始自定義控件

和很多自定義控件方式一樣需要去基礎某種View或者某種ViewGroup

我這里選擇的是View,如下所示:

public class HuaWeiView extends View {

/**
 * 用來初始化畫筆等
 * @param context
 * @param attrs
 */
public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
}

/**
 * 用來測量限制view為正方形
 * @param widthMeasureSpec
 * @param heightMeasureSpec
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

/**
 * 實現各種繪制功能
 * @param canvas
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

}</code></pre>

其中構造方法用來布局中使用。

onMeasure()方法用來測量和限定view大小

onDraw()方法用來進行具體的繪制功能

如想詳細了解請點擊:

構造方法

onMeasure()

MeasureSpec

onDraw()

了解以上方法功能后,我們在來看看如何具體使用吧

1使用onMeasure()方法將View限制為一個正方形

只有確定了一個矩形才能夠去畫橢圓,如果這個矩形是正方形,橢圓也就隨之變成了圓形。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width=MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //以最小值為正方形的長
        len=Math.min(width,height);
        //設置測量高度和寬度(必須要調用,不然無效果)
        setMeasuredDimension(len,len);
    }

分別通過MeasureSpec取得用戶設置的寬和高,然后取出最小值,設置給我們的view,這樣我們就做好了一個矩形

現在使用在布局中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="

<com.example.huaweiview.HuaWeiView
    android:layout_gravity="center"
    android:background="@color/colorAccent"
    android:layout_width="200dp"
    android:layout_height="300dp"
    />

</LinearLayout></code></pre>

父布局背景為藍色背景,控件背景為粉色背景,而且設置的寬高不同,但是控件的顯示效果還是一個正方形,而且以小值為準。我們的onMeasure()生效了

接下來就是如何在確定一個圓形區域了

2onDraw()繪制圓形區域

繪制之前我們需要對 Android 中的坐標系有個了解

我們都知道手機屏幕左上角為坐標原點,往右為X正軸,往下為Y正軸。其實手機頁面就是activity的展示界面,也是一個View。那可不可以說所有的View在繪制圖形的時候都有自己的這么一個坐標系呢(個人想法。。。)

也就是所每個View都有自己的一個坐標系,比如現在的自定義View:

現在我們需要在我們自定義的view中繪制一個圓弧,那么這個圓弧的半徑就是我們自定義view的長度的一半,即:

radius=len/2;

那么圓心的坐標剛好是(radius,radius)

接下來開始繪制

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫圓弧的方法
        canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
    }

介紹一下繪制圓弧的方法:

  • 參數一oval是一個RectF對象為一個矩形
  • 參數二startAngle為圓弧的起始角度
  • 參數三sweepAngle為圓弧的經過角度(掃過角度)
  • 參數四useCenter為圓弧是一個boolean值,為true時畫的是圓弧,為false時畫的是割弧
  • 參數五paint為一個畫筆對象
    也就是說只要確定了一個矩形,在確定他起始和經過的角度就能夠畫出一個圓弧(這點大家可以用畫板測試)

下來就是初始化這些參數

初始化矩形

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //以最小值為正方形的長
        len = Math.min(width, height);
        //實例化矩形
        oval=new RectF(0,0,len,len);
        //設置測量高度和寬度(必須要調用,不然無效果)
        setMeasuredDimension(len, len);
    }

畫矩形需要確定左上角和右下角的坐標(通過畫板可以 測試 ),通過上面的分析坐標原點就是我們view的左上角,右下角的坐標當然就是len了。

接下來就是初始化起始和經過角度

private float startAngle=120;
private float sweepAngle=300;

需要搞清楚往下為Y軸正軸,剛好和上學時候學的相反,也就是說90度在下方,-90度在上方

初始化畫筆

 public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint =new Paint();
        //設置畫筆顏色
        paint.setColor(Color.WHITE);
        //設置畫筆抗鋸齒
        paint.setAntiAlias(true);
        //讓畫出的圖形是空心的(不填充)
        paint.setStyle(Paint.Style.STROKE);
    }
useCenter=false

到這里真不容易呀,然而發現只畫個圓弧沒用呀,我要的是刻度線呀,canvas里面又沒用給我們提供畫刻度線的方法,這個時候就需要我們自己去寫一個畫刻度線的方法了。

通過觀察圖片我們可以看出,所有的線都是從圓弧上的點為起點向某個方向畫一條直線, 那么該如何確定這兩個點呢,需要我們做兩件事:

移動坐標系

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //畫圓弧的方法
        canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
        //畫刻度線的方法
        drawViewLine(canvas);
    }

private void drawViewLine(Canvas canvas) {
    //先保存之前canvas的內容
    canvas.save();
    //移動canvas(X軸移動距離,Y軸移動距離)
    canvas.translate(radius,radius);
    //操作完成后恢復狀態
    canvas.restore();
}</code></pre> 

我們自己寫了一個繪制刻度線的方法并在onDraw()方法中調用。移動坐標系之前需要保存之前的canvas狀態,然后X和Y軸分別移動圓弧半徑的距離,如下圖:

canvas.translate(radius,radius);方法移動的是坐標系(通過實際效果和查資料所得)

canvas.save()和canvas.restore()要成對出現,就好像流用完要關閉一樣。

第一件事情完成后,開始第二件事情,旋轉坐標系

只通過移動坐標系,仍然很難確定圓弧點上的坐標,和另外一點的坐標,如果這兩個點都在坐標軸上該多好呀,下面實現:

private void drawViewLine(Canvas canvas) {
        //先保存之前canvas的內容
        canvas.save();
        //移動canvas(X軸移動距離,Y軸移動距離)
        canvas.translate(radius,radius);
        //旋轉坐標系
        canvas.rotate(30);
        //操作完成后恢復狀態
        canvas.restore();
    }

畫刻度線的方法了增加了一個旋轉30度的代碼,旋轉后的坐標系應該怎么樣呢;

因為起始點和90度相差30,旋轉之后,起始點剛好落在了Y軸上,那么這個點的坐標就很好確定了吧,沒錯就是(0,radius);如果我們在Y軸上在找一點不就可以畫出一條刻度線了嗎,那么它的坐標是多少呢?對,應該是(0,radius-y),因為我們要往內部化刻度線,因此是減去一個值,趕快去試試吧,代碼如下:

private void drawViewLine(Canvas canvas) {
        //先保存之前canvas的內容
        canvas.save();
        //移動canvas(X軸移動距離,Y軸移動距離)
        canvas.translate(radius,radius);
        //旋轉坐標系
        canvas.rotate(30);
        Paint linePatin=new Paint();
        //設置畫筆顏色
        linePatin.setColor(Color.WHITE);
        //線寬
        linePatin.setStrokeWidth(2);
        //設置畫筆抗鋸齒
        linePatin.setAntiAlias(true);
        //畫一條刻度線
        canvas.drawLine(0,radius,0,radius-40,linePatin);
        //操作完成后恢復狀態
        canvas.restore();
    }

根據得到的兩個點的坐標,畫出來一條白線,如圖:

當然這些點都是移動后的坐標系在旋轉30度得到的,這里畫好了一條線,如果畫多條呢,還是剛才的思路每次都讓它旋轉一個小角度然后畫條直線不就好了嗎,那么旋轉多少度呢,比如這里:總共掃過的角度sweepAngle=300;需要100條刻度,那么每次需要旋轉的角度rotateAngle=sweepAngle/100,具體代碼如下:

private void drawViewLine(Canvas canvas) {
        //先保存之前canvas的內容
        canvas.save();
        //移動canvas(X軸移動距離,Y軸移動距離)
        canvas.translate(radius,radius);
        //旋轉坐標系
        canvas.rotate(30);
        Paint linePatin=new Paint();
        //設置畫筆顏色
        linePatin.setColor(Color.WHITE);
        //線寬
        linePatin.setStrokeWidth(2);
        //設置畫筆抗鋸齒
        linePatin.setAntiAlias(true);
        //確定每次旋轉的角度
        float rotateAngle=sweepAngle/99;
        for(int i=0;i<100;i++){
            //畫一條刻度線
            canvas.drawLine(0,radius,0,radius-40,linePatin);
            canvas.rotate(rotateAngle);
        }

        //操作完成后恢復狀態
        canvas.restore();
    }

100個刻度,需要101次循環畫線(請看你的手表),畫完線就旋轉。依次循環,如圖

經過這么久的時間總于完成了刻度盤了,接下來就是去確定不同角度顯示什么樣的顏色,首選我們需要確定要繪制的范圍targetAngle:

繪制有色部分

private void drawViewLine(Canvas canvas) {
        //先保存之前canvas的內容
        canvas.save();
        //移動canvas(X軸移動距離,Y軸移動距離)
        canvas.translate(radius,radius);
        //旋轉坐標系
        canvas.rotate(30);
        Paint linePatin=new Paint();
        //設置畫筆顏色
        linePatin.setColor(Color.WHITE);
        //線寬
        linePatin.setStrokeWidth(2);
        //設置畫筆抗鋸齒
        linePatin.setAntiAlias(true);
        //確定每次旋轉的角度
        float rotateAngle=sweepAngle/100;
        //繪制有色部分的畫筆
        Paint targetLinePatin=new Paint();
        targetLinePatin.setColor(Color.GREEN);
        targetLinePatin.setStrokeWidth(2);
        targetLinePatin.setAntiAlias(true);
        //記錄已經繪制過的有色部分范圍
        float hasDraw=0;
        for(int i=0;i<=100;i++){
            if(hasDraw<=targetAngle&&targetAngle!=0){//需要繪制有色部分的時候
                //畫一條刻度線
                canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
            }else {//不需要繪制有色部分
                //畫一條刻度線
                canvas.drawLine(0,radius,0,radius-40,linePatin);
            }
            //累計繪制過的部分
            hasDraw+=rotateAngle;
            //旋轉
            canvas.rotate(rotateAngle);
        }

        //操作完成后恢復狀態
        canvas.restore();
    }

我們需要不斷的去記錄繪制過的有效部分,之外的部分畫白色。

根據角度的比例,顏色漸變

需要計算出已經繪制過的角度占總角度(300)的比例

for(int i=0;i<=100;i++){
            if(hasDraw<=targetAngle&&targetAngle!=0){//需要繪制有色部分的時候
                //計算已經繪制的比例
                float percent=hasDraw/sweepAngle;
                int red= 255-(int) (255*percent);
                int green= (int) (255*percent);
                targetLinePatin.setARGB(255,red,green,0);
                //畫一條刻度線
                canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
            }else {//不需要繪制有色部分
                //畫一條刻度線
                canvas.drawLine(0,radius,0,radius-40,linePatin);
            }
            hasDraw+=rotateAngle;
            canvas.rotate(rotateAngle);
        }

只是在繪制有色部分的時候,利用三元素來實現漸變。所占比例越低紅色值越大,反正綠色值越大。

實現動態顯示

先想一下它的運動情況,分為前進狀態和后退狀態,如果正在運動(一次完整的后退和前進沒用結束),就不能開始下次運動,需要兩個參數,state和isRunning

//判斷是否在動
    private boolean isRunning;
    //判斷是回退的狀態還是前進狀態
    private int state = 1;
    public void changeAngle(final float trueAngle) {
        if (isRunning){//如果在動直接返回
            return;
        }
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                switch (state) {
                    case 1://后退狀態
                    isRunning=true;
                        targetAngle -= 3;
                        if (targetAngle <= 0) {//如果回退到0
                            targetAngle = 0;
                            //改為前進狀態
                            state = 2;
                        }
                        break;
                    case 2://前進狀態
                        targetAngle += 3;
                        if (targetAngle >= trueAngle) {//如果增加到指定角度
                            targetAngle = trueAngle;
                            //改為后退狀態
                            state = 1;
                            isRunning=false;
                            //結束本次運動
                            timer.cancel();
                        }
                        break;
                    default:
                        break;
                }
                //重新繪制(子線程中使用的方法)
                postInvalidate();
            }
        }, 500, 30);
    }

利用時間任務,每個30毫秒去執行一次run方法,每次都重新繪制圖片,然后在activity中調用此方法

HuaWeiView hwv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        hwv= (HuaWeiView) findViewById(R.id.hwv);
        hwv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //點擊事件中,調用動的方法
                hwv.changeAngle(200);
            }
        });

    }

看到這里了,相信你對坐標系和角度動態變化,以及刻度盤的繪制有了個很好的認識,多多驗證會有助于理解。

接下來要實現背景動態漸變

想想咱們的view中哪里用了漸變呢?對,在繪制有色部分的時候,如果我們能將顏色漸變的值不斷的傳到activity中該多好呀,下面就要用接口傳值實現這一功能了:

  • 首選在自定義View中聲明一個內部接口:
private OnAngleColorListener onAngleColorListener;

    public void setOnAngleColorListener(OnAngleColorListener onAngleColorListener) {
        this.onAngleColorListener = onAngleColorListener;
    }

    public interface OnAngleColorListener{
       void colorListener(int red,int green);
    }

我們在自定義View中聲明一個內部接口,并聲明一個全局接口對象,提供一個set方法

接口內有個方法用來獲取顏色值

接下來就是在合適的地方調用這個方法,那么哪里呢,就是我們繪制顏色刻度時調用:

for (int i = 0; i <= 100; i++) {
            if (hasDraw <= targetAngle && targetAngle != 0) {//需要繪制有色部分的時候
                //計算已經繪制的比例
                float percent = hasDraw / sweepAngle;
                int red = 255 - (int) (255 * percent);
                int green = (int) (255 * percent);
                //實現接口回調,傳遞顏色值
                if (onAngleColorListener!=null){
                    onAngleColorListener.colorListener(red,green);
                }
                targetLinePatin.setARGB(255, red, green, 0);
                //畫一條刻度線
                canvas.drawLine(0, radius, 0, radius - 40, targetLinePatin);
            } else {//不需要繪制有色部分
                //畫一條刻度線
                canvas.drawLine(0, radius, 0, radius - 40, linePatin);
            }

我們在繪制的時候實現了接口回調,接下來去activity中實現接口

public class MainActivity extends AppCompatActivity {

    HuaWeiView hwv;
    LinearLayout ll_parent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        hwv= (HuaWeiView) findViewById(R.id.hwv);
        //實例父布局
        ll_parent= (LinearLayout) findViewById(R.id.ll_parent);
        hwv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //點擊事件中,調用動的方法
                hwv.changeAngle(200);
            }
        });
        //設置角度顏色變化監聽
        hwv.setOnAngleColorListener(new HuaWeiView.OnAngleColorListener() {
            @Override
            public void colorListener(int red, int green) {
                Color color=new Color();
                //通過Color對象將RGB值轉為int類型
                int backColor=color.argb(100,red,green,0);
                //父布局設置背景
                ll_parent.setBackgroundColor(backColor);
            }
        });

    }
}

給父布局一個id,然后實例化。給我們的自定義控件設置一個角度顏色變化監聽,從而拿到回調中傳過來的值,然后借助Color對象將RGB值轉為int值,再設置給父布局背景,這里背景稍稍透明一些。效果圖:

到了這里是不是感覺炫酷了不少呢,其實功能已經實現的差不多了,接下來就是去繪制里面的內容吧

繪制文字

當然不去繪制文字也是可以的,你可以直接在布局中添加textview等。好話不多說,先分析一下繪制的過程吧,在刻度盤的內部有一個小圓,然后這些文字就在小圓內,繪制小圓只需要讓它的半徑小點就OK了。

/**
     * 繪制小圓和文本的方法,小圓顏色同樣漸變
     * @param canvas
     */
    private void drawScoreText(Canvas canvas) {
        //先繪制一個小圓
        Paint smallPaint = new Paint();
        smallPaint.setARGB(100,red,green,0);
        // 畫小圓指定圓心坐標,半徑,畫筆即可
        int smallRadius=radius-60;
        canvas.drawCircle(radius, radius, radius - 60, smallPaint);
        //繪制文本
        Paint textPaint=new Paint();
        //設置文本居中對齊
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(smallRadius/2);
        //score需要通過計算得到
        canvas.drawText(""+score,radius,radius,textPaint);
        //繪制分,在分數的右上方
        textPaint.setTextSize(smallRadius/6);
        canvas.drawText("分",radius+smallRadius/2,radius-smallRadius/4,textPaint);
        //繪制點擊優化在分數的下方
        textPaint.setTextSize(smallRadius/6);
        canvas.drawText("點擊優化",radius,radius+smallRadius/2,textPaint);

    }

這里將之前漸變的red和green提為全局變量,先繪制一個小圓,畫筆顏色漸變。然后繪制文字分數score需要通過計算的到

//計算得到的分數
score=(int)(targetAngle/sweepAngle*100);
//重新繪制(子線程中使用的方法)
postInvalidate();

在時間任務中,每次繪制之前計算得到分數,然后在右上方畫一個固定值分,再在下方一個固定內容點擊優化(這個時候的坐標已經回到最初的模樣)

到此為止功能已經寫的差不多了,還有一個水波加速球效果,下篇博客中寫吧。

項目主頁:http://www.baiduhome.net/lib/view/home/1494484954559

 本文由用戶 liang52413 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!