自定義View高級知識點(一)

Canvas基本Api速查

操作類型 相關API 備註
繪製顏色 drawColor, drawRGB, drawARGB 使用單一顏色填充整個畫布
繪製基本形狀 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次爲 點、線、矩形、圓角矩形、橢圓、圓、圓弧
繪製圖片 drawBitmap, drawPicture 繪製位圖和圖片
繪製文本 drawText, drawPosText, drawTextOnPath 依次爲 繪製文字、繪製文字時指定每個文字位置、根據路徑繪製文字
繪製路徑 drawPath 繪製路徑,繪製貝塞爾曲線時也需要用到該函數
頂點操作 drawVertices, drawBitmapMesh 通過對頂點操作可以使圖像形變,drawVertices直接對畫布作用、 drawBitmapMesh只對繪製的Bitmap作用
畫布剪裁 clipPath, clipRect 設置畫布的顯示區域
畫布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次爲 保存當前狀態、 回滾到上一次保存的狀態、 保存圖層狀態、 回滾到指定狀態、 獲取保存次數
畫布變換 translate, scale, rotate, skew 依次爲 位移、縮放、 旋轉、錯切
Matrix(矩陣) getMatrix, setMatrix, concat 實際畫布的位移,縮放等操作的都是圖像矩陣Matrix,只不過Matrix比較難以理解和使用,故封裝了一些常用的方法。

顏色混合模式(Alpha通道相關)

通過前面介紹我們知道顏色一般都是四個通道(ARGB)的,其中(RGB)控制的是顏色,而A(Alpha)控制的是透明度。

因爲我們的顯示屏是沒法透明的,因此最終顯示在屏幕上的顏色裏可以認爲沒有Alpha通道。Alpha通道主要在兩個圖像混合的時候生效。

默認情況下,當一個顏色繪製到Canvas上時的混合模式是這樣計算的:

(RGB通道) 最終顏色 = 繪製的顏色 + (1 - 繪製顏色的透明度) × Canvas上的原有顏色。

注意:

1.這裏我們一般把每個通道的取值從0(ox00)到255(0xff)映射到0到1的浮點數表示。

2.這裏等式右邊的“繪製的顏色”、“Canvas上的原有顏色” 都是經過預乘了自己的Alpha通道的值。如繪製顏色:0x88ffffff,那麼參與運算時的每個顏色通道的值不是1.0,而是(1.0 * 0.5333 = 0.5333)。 (其中0.5333 = 0x88/0xff)

使用這種方式的混合,就會造成後繪製的內容以半透明的方式疊在上面的視覺效果。

Paint.setXfermode模式

下表是各個PorterDuff模式的混合計算公式:(D指原本在Canvas上的內容dst,S指繪製輸入的內容src,a指alpha通道,c指RGB各個通道),各種模式如下圖所示。

porterDuff的模式

onMeasure

真正進行測量的是 setMeasuredDimension
如果對View的寬高進行修改了,不要調用 super.onMeasure( widthMeasureSpec, heightMeasureSpec); 要調用 setMeasuredDimension( widthsize, heightsize); 這個函數。

paint繪製圖形

一個要點:繪製的基本形狀由Canvas確定,但繪製出來的顏色,具體效果則由Paint確定
STROKE //描邊
FILL //填充
FILL_AND_STROKE //描邊加填充

1.圓角矩形

利用圓角矩形也可以繪製出橢圓

RectF rectF = new RectF(100, 100, 300, 300);
mPaint.setColor(Color.BLACK);
canvas.drawRect(rectF, mPaint);
mPaint.setColor(Color.RED);
canvas.drawRoundRect(rectF, 100, 50, mPaint);

2.橢圓

橢圓繪製可以理解爲做一個矩形的內切圓,如果這是一個正方形那麼繪製出來的就是一個圓形

RectF rectF = new RectF(100, 100, 400, 300);
mPaint.setColor(Color.BLACK);
canvas.drawOval(rectF, mPaint);

3.扇形

drawArc(RectF oval, float startAngle(開始角度0-360), float sweepAngle(掃過角度0-360), boolean useCenter(是否連接圓心),Paint paint) 
    // 使用中心
    RectF rectF = new RectF(100, 100, 200, 200);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 
    mPaint.setColor(Color.GRAY);
    canvas.drawRect(rectF, mPaint);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setColor(Color.BLACK);
    canvas.drawArc(rectF, 0, 90, true, mPaint);

    // 不使用中心
    rectF = new RectF(100, 300, 200, 400);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE); // 填充 並描邊
    mPaint.setColor(Color.GRAY);
    canvas.drawRect(rectF, mPaint);
    mPaint.setStyle(Paint.Style.STROKE); // 僅描邊
    mPaint.setColor(Color.RED);
    canvas.drawArc(rectF, 0, -90, false, mPaint);

扇形

4.畫一個圓餅圖

public class PieChartView extends View {

    private Paint mPaint;

    private List<Percent> mPercentList;

    public PieChartView(Context context) {
        super(context);
        init();
    }

    public PieChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PieChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(2);

        mPercentList = new LinkedList<>();
        mPercentList.add(new Percent("1", 0.4f, Color.WHITE));
        mPercentList.add(new Percent("2", 0.4f, Color.BLUE));
        mPercentList.add(new Percent("3", 0.2f, Color.RED));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        RectF rectF = new RectF(100, 100, 400, 400);
        canvas.drawOval(rectF, mPaint);

        float surplus = 1.0f; // 剩餘的範圍
        float startAngle = 0;
        Percent percent;
        for (int i = 0, size = mPercentList.size(); i < size; i++) {
            percent = mPercentList.get(i);
            if (percent.percent <= surplus) {
                surplus -= percent.percent;
                startAngle += percent.drawArc(mPaint, canvas, startAngle, rectF);
            }
        }

    }

    /**
    * 採用封裝的思想
    */
    public static class Percent {
        String tag;
        float percent;
        int color;

        public Percent(String tag, float percent, int color) {
            this.tag = tag;
            this.percent = percent;
            this.color = color;
        }

        /**
         * 繪製 這個百分比
         *
         * @param paint
         * @param canvas
         * @param startAngle
         * @param rectF
         * @return 返回繪製的角度大小
         */
        public float drawArc(Paint paint, Canvas canvas, float startAngle, RectF rectF) {
            int oldColor = paint.getColor();
            Paint.Style oldStyle = paint.getStyle();

            paint.setColor(color);
            paint.setStyle(Paint.Style.FILL);

            canvas.drawArc(rectF, startAngle, 360 * percent, true, paint);

            paint.setColor(oldColor);
            paint.setStyle(oldStyle);

            return 360 * percent;
        }
    }
}

圓餅圖

操作畫布

操作畫布,操作的都是座標系,是對當前座標系進行移動、旋轉、縮放、錯切等操作

操作畫布只會影響接下來的繪畫,而不會影響之前已經繪畫完成的圖像

1.移動畫布

    // 移動畫布
    canvas.translate(getWidth() / 2, getHeight() / 2);
    canvas.drawCircle(0, 0, 200, mPaint);
    canvas.drawCircle(0, 0, 180, mPaint);

    // 旋轉畫布 並畫線
    for (int i = 0; i < 36; i++) {
        canvas.drawLine(0, 180, 0, 200, mPaint);
        canvas.rotate(10);
    }

通過旋轉畫布實現兩圓之間的連線

2.旋轉畫布

    public void rotate(float degrees) {
        native_rotate(mNativeCanvasWrapper, degrees);
    }

    /**
     * 相對某點縮放
     */
    public final void rotate(float degrees, float px, float py) {
        translate(px, py);
        rotate(degrees);
        translate(-px, -py);
    }

看下圖爲相對某點進行縮放,圖中的繪製順序 1.紅 2.黃 3.藍 4.綠

相對某點縮放

        RectF rectF = new RectF(100, 100, 200, 200);
        mPaint.setColor(Color.RED);
        canvas.drawRect(rectF, mPaint);

        canvas.translate(200,200); // 先將座標系O移動到相對點P
        mPaint.setColor(Color.YELLOW);
        canvas.drawRect(rectF, mPaint);

        canvas.rotate(90);// 在相對點P處將座標系O進行旋轉 O-->o P-->p
        mPaint.setColor(Color.BLUE);
        canvas.drawRect(rectF, mPaint);

        canvas.translate(-200,-200);// 將旋轉後的座標系o中的p與P對齊,讓以後的操作都仍然相對於P點
        mPaint.setColor(Color.GREEN);
        canvas.drawRect(rectF, mPaint);

3.縮放畫布

    public void scale(float sx, float sy) {
        native_scale(mNativeCanvasWrapper, sx, sy);
    }

    /**
     * 相對於某點進行縮放,其相對原理與相對某點旋轉相同
     */
    public final void scale(float sx, float sy, float px, float py) {
        translate(px, py);
        scale(sx, sy);
        translate(-px, -py);
    }

縮放比例(sx,sy)取值範圍詳解:

取值範圍(n) 說明
[-∞, -1) 先根據縮放中心放大n倍,再根據中心軸進行翻轉
-1 根據縮放中心軸進行翻轉
(-1, 0) 先根據縮放中心縮小到n,再根據中心軸進行翻轉
0 不會顯示,若sx爲0,則寬度爲0,不會顯示,sy同理
(0, 1) 根據縮放中心縮小到n
1 沒有變化
(1, +∞) 根據縮放中心放大n倍

負值時,座標系方向取反,scale(1,-1)這個操作將使得座標系變爲數學中常用座標系

旋轉,縮放,位移三者應用

    // 移動畫布
    canvas.translate(getWidth() / 2, getHeight() / 2);

    mPaint.setColor(Color.BLUE);
    for (int j = 0; j < 10; j++) {
        canvas.drawCircle(0, 0, 200, mPaint);
        canvas.drawCircle(0, 0, 180, mPaint);
        canvas.rotate(j);
        // 旋轉畫布 並畫線![enter description here][6]
        for (float i = j; i <= 360f; i += 10f) {
            canvas.drawLine(0, 180, 0, 200, mPaint);
            canvas.rotate(10);
        }
        canvas.scale(0.75f, 0.75f);
    }

nothing

4.錯切(skew)

錯切是在某方向上,按照一定的比例對圖形的每個點到某條平行於該方向的直線的有向距離做放縮得到的平面圖形。(度娘)

    public void skew(float sx, float sy) {
        native_skew(mNativeCanvasWrapper, sx, sy);
    }

用法:

        canvas.translate(getWidth()/2,getHeight()/2);
        RectF rectF = new RectF(0, 0, 100, 100);
        mPaint.setColor(Color.RED);
        canvas.drawRect(rectF, mPaint);

        canvas.skew(-1,0); // 向左傾斜45度
        canvas.drawRect(rectF, mPaint);

效果圖:

錯切

本文中很多內容學習自自定義View系列,非常感謝!同時還有一些自己發現的小tips。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章