电竞比分网-中国电竞赛事及体育赛事平台

分享

自定義 View

 quasiceo 2016-08-03

定義 View——Canvas 與 ValueAnimator – Idtk

時間 2016-05-27 13:40:39 Idtk

涉及知識

繪制過程

類別 API 描述
布局 onMeasure 測量View與Child View的大小

onLayout 確定Child View的位置

onSizeChanged 確定View的大小
繪制 onDraw 實際繪制View的內容
事件處理 onTouchEvent 處理屏幕觸摸事件
重繪 invalidate 調用onDraw方法,重繪View中變化的部分

(如果對繪制過程與構造函數還不了解的,請查看我之前文章 自定義View——Android坐標系與View繪制流程 )

Canvas涉及方法

類別 API 描述
繪制圖形 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次為繪制點、直線、矩形、圓角矩形、橢圓、圓、扇形
繪制文本 drawText, drawPosText, drawTextOnPath 依次為繪制文字、指定每個字符位置繪制文字、根據路徑繪制文字
畫布變換 translate, scale, rotate, skew 依次為平移、縮放、旋轉、傾斜(錯切)
畫布裁剪 clipPath, clipRect, clipRegion 依次為按路徑、按矩形、按區(qū)域對畫布進行裁剪
畫布狀態(tài) save,restore 保存當前畫布狀態(tài),恢復之前保存的畫布

Paint涉及方法

類別 API 描述
顏色 setColor,setARGB,setAlpha 依次為設置畫筆顏色、透明度
類型 setStyle 填充(FILL),描邊(STROKE),填充加描邊(FILL_AND_STROKE)
抗鋸齒 setAntiAlias 畫筆是否抗鋸齒
字體大小 setTextSize 設置字體大小
字體測量 getFontMetrics(),getFontMetricsInt() 返回字體的測量,返回值一次為float、int
文字寬度 measureText 返回文字的寬度
文字對齊方式 setTextAlign 左對齊(LEFT),居中對齊(CENTER),右對齊(RIGHT)
寬度 setStrokeWidth 設置畫筆寬度
筆鋒 setStrokeCap 默認(BUTT),半圓形(ROUND),方形(SQUARE)

(PS: 因API較多,只列出了涉及的方法,想了解更多,請查看官方文檔)

(注意: 以下的代碼中未指定函數名的都是在onDraw函數中進行使用,同時為了演示方便,在onDraw中使用了一些new方法,請在實際使用中不要這樣做,因為onDraw函數是經常需要重新運行的)

一、Canvas

1、創(chuàng)建畫筆

創(chuàng)建畫筆并初始化

//創(chuàng)建畫筆
private Paint mPaint = new Paint();

private void initPaint(){
    //初始化畫筆
    mPaint.setStyle(Paint.Style.FILL);//設置畫筆類型
    mPaint.setAntiAlias(true);//抗鋸齒
}

2、繪制坐標軸

使用onSizeChanged方法,獲取根據父布局等因素確認的View寬高

//寬高
private int mWidth;
private int mHeight;

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mWidth = w;
    mHeight = h;
}

把原點從左上角移動到畫布中心

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(mWidth/2,mHeight/2);// 將畫布坐標原點移動到中心位置
}

繪制坐標原點

//繪制坐標原點
mPaint.setColor(Color.BLACK);//設置畫筆顏色
mPaint.setStrokeWidth(10);//為了看得清楚,設置了較大的畫筆寬度
canvas.drawPoint(0,0,mPaint);

繪制坐標系的4個端點,一次繪制多個點

//繪制坐標軸4個斷點
canvas.drawPoints(new float[]{
    mWidth/2*0.8f,0
    ,0,mHeight/2*0.8f
    ,-mWidth/2*0.8f,0
    ,0,-mHeight/2*0.8f},mPaint);

繪制坐標軸

mPaint.setStrokeWidth(1);//恢復畫筆默認寬度
//繪制X軸
canvas.drawLine(-mWidth/2*0.8f,0,mWidth/2*0.8f,0,mPaint);
//繪制Y軸
canvas.drawLine(0,mHeight/2*0.8f,0,mHeight/2*0.8f,mPaint);

繪制坐標軸箭頭,一次繪制多條線

mPaint.setStrokeWidth(3);
//繪制X軸箭頭
canvas.drawLines(new float[]{
    mWidth/2*0.8f,0,mWidth/2*0.8f*0.95f,-mWidth/2*0.8f*0.05f,            mWidth/2*0.8f,0,mWidth/2*0.8f*0.95f,mWidth/2*0.8f*0.05f
},mPaint);
//繪制Y軸箭頭
canvas.drawLines(new float[]{
      0,mHeight/2*0.8f,mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f,
      0,mHeight/2*0.8f,-mWidth/2*0.8f*0.05f,mHeight/2*0.8f-mWidth/2*0.8f*0.05f,
},mPaint);

為什么Y軸的箭頭是向下的呢?這是因為原坐標系原點在左上角,向下為Y軸正方向,有疑問的可以查看我之前的文章 自定義View——Android坐標系與View繪制流程

如果覺得不舒服,一定要箭頭向上的話,可以在繪制Y軸箭頭之前翻轉坐標系

canvas.scale(1,-1);//翻轉Y軸

3、畫布變換

繪制矩形

//繪制矩形
mPaint.setStyle(Paint.Style.STROKE);//設置畫筆類型
canvas.drawRect(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8,mPaint);

平移,同時使用 new Rect 方法設置矩形

canvas.translate(200,200);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);

縮放

canvas.scale(0.5f,0.5f);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);

旋轉

canvas.rotate(90);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);

錯切

canvas.skew(1,0.5f);
mPaint.setColor(Color.BLUE);
canvas.drawRect(new RectF(-mWidth/8,-mHeight/8,mWidth/8,mHeight/8),mPaint);

4、畫布的保存和恢復

save():用于保存canvas的狀態(tài),之后可以調用canvas的平移、旋轉、縮放、錯切、裁剪等操作。restore():在save之后調用,用于恢復之前保存的畫布狀態(tài),從而在之后的操作中忽略save與restore之間的畫布變化。

float point = Math.min(mWidth,mHeight)*0.06f/2;
float r = point*(float) Math.sqrt(2);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.BLACK);
canvas.save();
canvas.rotate(90);
canvas.drawCircle(200,0,r,mPaint);//圓心(200,0)
canvas.restore();
mPaint.setColor(Color.BLUE);
canvas.drawCircle(200,0,r,mPaint);//圓心(200,0)

保存畫布,旋轉90°,繪制一個圓,之后恢復畫布,使用相同參數再繪制一個圓。可以看到在恢復畫布前后,相同參數繪制的圓,分別顯示在了坐標系的不同位置。

二、豆瓣加載動畫

繪制2個點和一個半圓弧

mPaint.setStyle(Paint.Style.STROKE);//設置畫筆樣式為描邊,如果已經設置,可以忽略
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(10);
float point = Math.min(mWidth,mHeight)*0.2f/2;
float r = point*(float) Math.sqrt(2);
RectF rectF = new RectF(-r,-r,r,r);
canvas.drawArc(rectF,0,180,false,mPaint);
canvas.drawPoints(new float[]{
        point,-point
        ,-point,-point
},mPaint);

但是豆瓣表情在旋轉的過程中,是一個鏈接著兩個點的270°的圓弧

mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(10);
float point = Math.min(mWidth,mHeight)*0.2f/2;
float r = point*(float) Math.sqrt(2);
RectF rectF = new RectF(-r,-r,r,r);
canvas.drawArc(rectF,-180,270,false,mPaint);

這里使用ValueAnimator類,來進行演示(實際上應該是根據touch以及網絡情況來進行加載的變化)

簡單說下ValueAnimator類:

API 簡介
ofFloat(float… values) 構建ValueAnimator,設置動畫的浮點值,需要設置2個以上的值
setDuration(long duration) 設置動畫時長,默認的持續(xù)時間為300毫秒。
setInterpolator(TimeInterpolator value) 設置動畫的線性非線性運動,默認AccelerateDecelerateInterpolator
addUpdateListener(ValueAnimator.AnimatorUpdateListener listener) 監(jiān)聽動畫屬性每一幀的變化

分解步驟,計算一下總共需要的角度:

1、一個笑臉,x軸下方的圓弧旋轉135°,覆蓋2個點,此過程中圓弧增加45°

2、畫布旋轉135°,此過程中圓弧增加45°

3、畫布旋轉360°,此過程中圓弧減少360/5度

4、畫布旋轉90°,此過程中圓弧減少90/5度

5、畫布旋轉135°,釋放覆蓋的2個點

動畫部分:

private ValueAnimator animator;
private float animatedValue;
private long animatorDuration = 5000;
private TimeInterpolator timeInterpolator = new DecelerateInterpolator();

private void initAnimator(long duration){
    if (animator !=null &&animator.isRunning()){
        animator.cancel();
        animator.start();
    }else {
        animator=ValueAnimator.ofFloat(0,855).setDuration(duration);
        animator.setInterpolator(timeInterpolator);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animatedValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.start();
    }
}

表情部分:在繪制前最好使用sava()方法保存當前的畫布狀態(tài),在結束后使用restore()恢復之前保存的狀態(tài)。為了是表情看上去更自然,所以減少10°的初始角度

private void doubanAnimator2(Canvas canvas, Paint mPaint){
    mPaint.setStyle(Paint.Style.STROKE);//描邊
    mPaint.setStrokeCap(Paint.Cap.ROUND);//圓角筆觸
    mPaint.setColor(Color.rgb(97, 195, 109));
    mPaint.setStrokeWidth(15);
    float point = Math.min(mViewWidth,mViewWidth)*0.06f/2;
    float r = point*(float) Math.sqrt(2);
    RectF rectF = new RectF(-r,-r,r,r);
    canvas.save();

    // rotate
    if (animatedValue>=135){
        canvas.rotate(animatedValue-135);
    }

    // draw mouth
    float startAngle=0, sweepAngle=0;
    if (animatedValue<135){
        startAngle = animatedValue +5;
        sweepAngle = 170+animatedValue/3;
    }else if (animatedValue<270){
        startAngle = 135+5;
        sweepAngle = 170+animatedValue/3;
    }else if (animatedValue<630){
        startAngle = 135+5;
        sweepAngle = 260-(animatedValue-270)/5;
    }else if (animatedValue<720){
        startAngle = 135-(animatedValue-630)/2+5;
        sweepAngle = 260-(animatedValue-270)/5;
    }else{
        startAngle = 135-(animatedValue-630)/2-(animatedValue-720)/6+5;
        sweepAngle = 170;
    }
    canvas.drawArc(rectF,startAngle,sweepAngle,false,mPaint);

    // draw eye
    canvas.drawPoints(new float[]{
        -point,-point
        ,point,-point
    },mPaint);

    canvas.restore();
}

在調試完成之后就可以刪除,坐標系部分的代碼了

三、小結

本文介紹了canvas的變化,文中的不同部分穿插說明了canvas繪制各種圖形的方法,以及結合ValueAnimator制作的豆瓣加載動畫。之后的一篇文章會主要分析字符串的長度和寬度,根據這些來參數調整字符串的位置,以達到居中等效果,再后一篇文章內容應該就會編寫 PieChart 了。如果在閱讀過程中,有任何疑問與問題,歡迎與我聯(lián)系。

GitHub: https://github.com/Idtk

博客:http://www.

郵箱: Idtkma@gmail.com

分享

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多