日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

自定義 View

 quasiceo 2016-08-03

定義 View——Canvas 與 ValueAnimator – Idtk

時(shí)間 2016-05-27 13:40:39 Idtk

涉及知識(shí)

繪制過(guò)程

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

onLayout 確定Child View的位置

onSizeChanged 確定View的大小
繪制 onDraw 實(shí)際繪制View的內(nèi)容
事件處理 onTouchEvent 處理屏幕觸摸事件
重繪 invalidate 調(diào)用onDraw方法,重繪View中變化的部分

(如果對(duì)繪制過(guò)程與構(gòu)造函數(shù)還不了解的,請(qǐng)查看我之前文章 自定義View——Android坐標(biāo)系與View繪制流程 )

Canvas涉及方法

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

Paint涉及方法

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

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

(注意: 以下的代碼中未指定函數(shù)名的都是在onDraw函數(shù)中進(jìn)行使用,同時(shí)為了演示方便,在onDraw中使用了一些new方法,請(qǐng)?jiān)趯?shí)際使用中不要這樣做,因?yàn)閛nDraw函數(shù)是經(jīng)常需要重新運(yùn)行的)

一、Canvas

1、創(chuàng)建畫(huà)筆

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

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

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

2、繪制坐標(biāo)軸

使用onSizeChanged方法,獲取根據(jù)父布局等因素確認(rèn)的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;
}

把原點(diǎn)從左上角移動(dòng)到畫(huà)布中心

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.translate(mWidth/2,mHeight/2);// 將畫(huà)布坐標(biāo)原點(diǎn)移動(dòng)到中心位置
}

繪制坐標(biāo)原點(diǎn)

//繪制坐標(biāo)原點(diǎn)
mPaint.setColor(Color.BLACK);//設(shè)置畫(huà)筆顏色
mPaint.setStrokeWidth(10);//為了看得清楚,設(shè)置了較大的畫(huà)筆寬度
canvas.drawPoint(0,0,mPaint);

繪制坐標(biāo)系的4個(gè)端點(diǎn),一次繪制多個(gè)點(diǎn)

//繪制坐標(biāo)軸4個(gè)斷點(diǎn)
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);

繪制坐標(biāo)軸

mPaint.setStrokeWidth(1);//恢復(fù)畫(huà)筆默認(rèn)寬度
//繪制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);

繪制坐標(biāo)軸箭頭,一次繪制多條線

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àn)樵鴺?biāo)系原點(diǎn)在左上角,向下為Y軸正方向,有疑問(wèn)的可以查看我之前的文章 自定義View——Android坐標(biāo)系與View繪制流程

如果覺(jué)得不舒服,一定要箭頭向上的話,可以在繪制Y軸箭頭之前翻轉(zhuǎn)坐標(biāo)系

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

3、畫(huà)布變換

繪制矩形

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

平移,同時(shí)使用 new Rect 方法設(shè)置矩形

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);

旋轉(zhuǎn)

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

錯(cuò)切

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

4、畫(huà)布的保存和恢復(fù)

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

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)

保存畫(huà)布,旋轉(zhuǎn)90°,繪制一個(gè)圓,之后恢復(fù)畫(huà)布,使用相同參數(shù)再繪制一個(gè)圓。可以看到在恢復(fù)畫(huà)布前后,相同參數(shù)繪制的圓,分別顯示在了坐標(biāo)系的不同位置。

二、豆瓣加載動(dòng)畫(huà)

繪制2個(gè)點(diǎn)和一個(gè)半圓弧

mPaint.setStyle(Paint.Style.STROKE);//設(shè)置畫(huà)筆樣式為描邊,如果已經(jīng)設(shè)置,可以忽略
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);

但是豆瓣表情在旋轉(zhuǎn)的過(guò)程中,是一個(gè)鏈接著兩個(gè)點(diǎn)的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類,來(lái)進(jìn)行演示(實(shí)際上應(yīng)該是根據(jù)touch以及網(wǎng)絡(luò)情況來(lái)進(jìn)行加載的變化)

簡(jiǎn)單說(shuō)下ValueAnimator類:

API 簡(jiǎn)介
ofFloat(float… values) 構(gòu)建ValueAnimator,設(shè)置動(dòng)畫(huà)的浮點(diǎn)值,需要設(shè)置2個(gè)以上的值
setDuration(long duration) 設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng),默認(rèn)的持續(xù)時(shí)間為300毫秒。
setInterpolator(TimeInterpolator value) 設(shè)置動(dòng)畫(huà)的線性非線性運(yùn)動(dòng),默認(rèn)AccelerateDecelerateInterpolator
addUpdateListener(ValueAnimator.AnimatorUpdateListener listener) 監(jiān)聽(tīng)動(dòng)畫(huà)屬性每一幀的變化

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

1、一個(gè)笑臉,x軸下方的圓弧旋轉(zhuǎn)135°,覆蓋2個(gè)點(diǎn),此過(guò)程中圓弧增加45°

2、畫(huà)布旋轉(zhuǎn)135°,此過(guò)程中圓弧增加45°

3、畫(huà)布旋轉(zhuǎn)360°,此過(guò)程中圓弧減少360/5度

4、畫(huà)布旋轉(zhuǎn)90°,此過(guò)程中圓弧減少90/5度

5、畫(huà)布旋轉(zhuǎn)135°,釋放覆蓋的2個(gè)點(diǎn)

動(dòng)畫(huà)部分:

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()方法保存當(dāng)前的畫(huà)布狀態(tài),在結(jié)束后使用restore()恢復(fù)之前保存的狀態(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();
}

在調(diào)試完成之后就可以刪除,坐標(biāo)系部分的代碼了

三、小結(jié)

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

GitHub: https://github.com/Idtk

博客:http://www.

郵箱: Idtkma@gmail.com

分享

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多