Android關於Canvas你所知道的和不知道的一切

張風捷特烈發表於2018-11-07

在一年的Android自學中,Canvas一直是我能避且避的類,甚至不惜封裝自己的繪相簿來替代它。
如今回首,虐我千萬次的Canvas也不過如此,靜下心看看,其實也沒有想象中的那麼糟糕。
就像曾經等級30的我去打點等級40的副本(Canvas)非常吃力,現在等級50的我回來吊打它一樣。
所以朋友,遇到承受不了的困擾,不要太沮喪,去別的地方刷怪升級,一旦境界提升了,早晚可以"報仇雪恨"
Android技術棧C模組,第一篇正式開講:

如果將View、Canvas、Paint、Coder(編寫程式碼的人)做個類比:

那麼Canvas是一個黑匣子裡的白紙,它的特性是可以新增圖層和平移,旋轉、縮放、斜切等,最重要的就是它的n種drawXXX...
Paint是繪製用的畫筆,它的特性是提供繪製工具與制定畫筆的特殊效果(如筆頭Cap,線接方式Join,六種Effect)
View則是讓黑匣子變成透明的視口,也是我們最熟悉。那Coder就是在操縱畫筆的在白紙上繪製的人,是最核心的


一、前期準備:

1.自定義View中的canvas:

說起Canvas物件,貌似很少去new它,更多的是在自定義控制元件時的Ondraw方法裡回撥有canvas物件

public class CanvasView extends View {

    public CanvasView(Context context) {
        this(context, null);

    }

    public CanvasView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        //TODO init 初始化
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //TODO drawGrid 繪製網格
        //TODO drawCoo 繪製座標系
    }
}
複製程式碼

這裡值得提一下:onDarw中儘量不要建立物件,因為檢視更新都會走onDarw(),而不斷開闢空間

onDraw.png


2.準備網格與座標系

如果要演示繪製,這兩者必不可少,放在analyze包裡
實現效果:給出座標原點後會自動繪製座標系以及網格和數字

網格和座標系效果2.png

1).使用方式:
//成員變數
    private Paint mGridPaint;//網格畫筆
    private Point mWinSize;//螢幕尺寸
    private Point mCoo;//座標系原點
    
//TODO init 初始化:release:
    //準備螢幕尺寸
    mWinSize = new Point();
    mCoo = new Point(500, 500);
    Utils.loadWinSize(getContext(), mWinSize);
    mGridPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
//TODO drawGrid 繪製網格:release:
    HelpDraw.drawGrid(canvas, mWinSize, mGridPaint);
//TODO drawCoo 繪製座標系:release:
    HelpDraw.drawCoo(canvas, mCoo, mWinSize, mGridPaint);
複製程式碼
2).網格--路徑輔助:com.toly1994.c.view.analyze.HelpPath#gridPath
/**
 * 繪製網格:注意只有用path才能繪製虛線
 *
 * @param step 小正方形邊長
 * @param winSize 螢幕尺寸
 */
public static Path gridPath(int step, Point winSize) {
    Path path = new Path();
    for (int i = 0; i < winSize.y / step + 1; i++) {
        path.moveTo(0, step * i);
        path.lineTo(winSize.x, step * i);
    }
    for (int i = 0; i < winSize.x / step + 1; i++) {
        path.moveTo(step * i, 0);
        path.lineTo(step * i, winSize.y);
    }
    return path;
}
複製程式碼
2).網格--繪製:com.toly1994.c.view.analyze.HelpDraw#drawGrid
/**
 * 繪製網格
 * @param canvas 畫布
 * @param winSize 螢幕尺寸
 * @param paint 畫筆
 */
public static void drawGrid(Canvas canvas, Point winSize, Paint paint) {
    //初始化網格畫筆
    paint.setStrokeWidth(2);
    paint.setColor(Color.GRAY);
    paint.setStyle(Paint.Style.STROKE);
    //設定虛線效果new float[]{可見長度, 不可見長度},偏移值
    paint.setPathEffect(new DashPathEffect(new float[]{10, 5}, 0));
    canvas.drawPath(HelpPath.gridPath(50, winSize), paint);
}
複製程式碼
3).輔助--獲取螢幕尺寸:com.toly1994.c.view.analyze.Utils#loadWinSize
/**
 * 獲得螢幕高度
 *
 * @param ctx 上下文
 * @param winSize 螢幕尺寸
 */
public static void loadWinSize(Context ctx, Point winSize) {
    WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics outMetrics = new DisplayMetrics();
    if (wm != null) {
        wm.getDefaultDisplay().getMetrics(outMetrics);
    }
    winSize.x = outMetrics.widthPixels;
    winSize.y = outMetrics.heightPixels;
}
複製程式碼
4).座標系--路徑:com.toly1994.c.view.analyze.HelpPath#cooPath
/**
 * 座標系路徑
 *
 * @param coo     座標點
 * @param winSize 螢幕尺寸
 * @return 座標系路徑
 */
public static Path cooPath(Point coo, Point winSize) {
    Path path = new Path();
    //x正半軸線
    path.moveTo(coo.x, coo.y);
    path.lineTo(winSize.x, coo.y);
    //x負半軸線
    path.moveTo(coo.x, coo.y);
    path.lineTo(coo.x - winSize.x, coo.y);
    //y負半軸線
    path.moveTo(coo.x, coo.y);
    path.lineTo(coo.x, coo.y - winSize.y);
    //y負半軸線
    path.moveTo(coo.x, coo.y);
    path.lineTo(coo.x, winSize.y);
    return path;
}
複製程式碼
5).座標系--繪製:com.toly1994.c.view.analyze.HelpDraw#drawCoo
/**
 * 繪製座標系
 * @param canvas  畫布
 * @param coo     座標系原點
 * @param winSize 螢幕尺寸
 * @param paint   畫筆
 */
public static void drawCoo(Canvas canvas, Point coo, Point winSize, Paint paint) {
    //初始化網格畫筆
    paint.setStrokeWidth(4);
    paint.setColor(Color.BLACK);
    paint.setStyle(Paint.Style.STROKE);
    //設定虛線效果new float[]{可見長度, 不可見長度},偏移值
    paint.setPathEffect(null);
    //繪製直線
    canvas.drawPath(HelpPath.cooPath(coo, winSize), paint);
    //左箭頭
    canvas.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y - 20, paint);
    canvas.drawLine(winSize.x, coo.y, winSize.x - 40, coo.y + 20, paint);
    //下箭頭
    canvas.drawLine(coo.x, winSize.y, coo.x - 20, winSize.y - 40, paint);
    canvas.drawLine(coo.x, winSize.y, coo.x + 20, winSize.y - 40, paint);
    //為座標系繪製文字
    drawText4Coo(canvas, coo, winSize, paint);
}
/**
 * 為座標系繪製文字
 *
 * @param canvas  畫布
 * @param coo     座標系原點
 * @param winSize 螢幕尺寸
 * @param paint   畫筆
 */
private static void drawText4Coo(Canvas canvas, Point coo, Point winSize, Paint paint) {
    //繪製文字
    paint.setTextSize(50);
    canvas.drawText("x", winSize.x - 60, coo.y - 40, paint);
    canvas.drawText("y", coo.x - 40, winSize.y - 60, paint);
    paint.setTextSize(25);
    //X正軸文字
    for (int i = 1; i < (winSize.x - coo.x) / 50; i++) {
        paint.setStrokeWidth(2);
        canvas.drawText(100 * i + "", coo.x - 20 + 100 * i, coo.y + 40, paint);
        paint.setStrokeWidth(5);
        canvas.drawLine(coo.x + 100 * i, coo.y, coo.x + 100 * i, coo.y - 10, paint);
    }
    //X負軸文字
    for (int i = 1; i < coo.x / 50; i++) {
        paint.setStrokeWidth(2);
        canvas.drawText(-100 * i + "", coo.x - 20 - 100 * i, coo.y + 40, paint);
        paint.setStrokeWidth(5);
        canvas.drawLine(coo.x - 100 * i, coo.y, coo.x - 100 * i, coo.y - 10, paint);
    }
    //y正軸文字
    for (int i = 1; i < (winSize.y - coo.y) / 50; i++) {
        paint.setStrokeWidth(2);
        canvas.drawText(100 * i + "", coo.x + 20, coo.y + 10 + 100 * i, paint);
        paint.setStrokeWidth(5);
        canvas.drawLine(coo.x, coo.y + 100 * i, coo.x + 10, coo.y + 100 * i, paint);
    }
    //y負軸文字
    for (int i = 1; i < coo.y / 50; i++) {
        paint.setStrokeWidth(2);
        canvas.drawText(-100 * i + "", coo.x + 20, coo.y + 10 - 100 * i, paint);
        paint.setStrokeWidth(5);
        canvas.drawLine(coo.x, coo.y - 100 * i, coo.x + 10, coo.y - 100 * i, paint);
    }
}
複製程式碼

二、Canvas繪製基礎圖形(如果覺得簡單可跳過)

以前看到一個類有很多方法都有些不耐煩,這麼多,怎麼記得住。
現在看到一個類有很多方法,--哇,太好了,哈哈,竟然連這方法都有,作者真給力省的我實現了。
Canvas圖形繪製的API,所有的我分了一下類:如下(顏色、點、線、矩形、類圓、文字、圖片、其他)
下面一一介紹:

Canvas繪製API


1.繪製顏色

繪製顏色

    /**
     * 繪製顏色(注意在畫座標系前繪製,否則後者覆蓋)
     * @param canvas
     */
    private void drawColor(Canvas canvas) {
//        canvas.drawColor(Color.parseColor("#E0F7F5"));
//        canvas.drawARGB(255, 224, 247, 245);
//        三者等價
        canvas.drawRGB(224, 247, 245);
    }
複製程式碼

繪製顏色.png


2.繪製點

繪製點.png

    /**
     * 繪製點
     * @param canvas
     */
    private void drawPoint(Canvas canvas) {
        //繪製點
        canvas.drawPoint(100, 100, mRedPaint);
        ////繪製一組點,座標位置由float陣列指定(必須是2的倍數個)
        canvas.drawPoints(new float[]{
                400, 400, 500, 500,
                600, 400, 700, 350,
                800, 300, 900, 300
        }, mRedPaint);
    }
複製程式碼

繪製點.png


3.繪製直線

繪製線

/**
 * 繪製線
 * @param canvas
 */
private void drawLine(Canvas canvas) {
    canvas.drawLine(500, 200, 900, 400, mRedPaint);
    //繪製一組點,座標位置由float陣列指定(必須是4的倍數個)
    canvas.drawLines(new float[]{
            200, 200, 400, 200,
            400, 200, 200, 400,
            200, 400, 400, 400
    }, mRedPaint);
}
複製程式碼

繪製線.png


4.繪製矩形

繪製矩形.png

    /**
     * 繪製矩形
     *
     * @param canvas
     */
    private void drawRect(Canvas canvas) {
        canvas.drawRect(100, 100, 500, 300, mRedPaint);
        //等價上行
//        Rect rect = new Rect(100, 100, 500, 300);
//        canvas.drawRect(rect,mRedPaint);
        //(左上右下X圓角,Y圓角)
        canvas.drawRoundRect(100 + 500, 100, 500 + 500, 300, 50, 50, mRedPaint);
    }
複製程式碼

繪製矩形.png


5.繪製類圓

繪製類圓.png

    /**
     * 繪製類圓
     *
     * @param canvas
     */
    private void drawLikeCircle(Canvas canvas) {
        //繪製圓(矩形邊界,畫筆)
        canvas.drawCircle(650, 200, 100, mRedPaint);
//        canvas.drawOval(100, 100, 500, 300, mRedPaint);
        //等價上行
        //繪製橢圓(矩形邊界,畫筆)
        RectF rect = new RectF(100, 100, 500, 300);
        canvas.drawOval(rect, mRedPaint);

        RectF rectArc = new RectF(100 + 500, 100, 500 + 500, 300);
        //繪製圓弧(矩形邊界,開始角度,掃過角度,使用中心?邊緣兩點與中心連線區域:邊緣兩點連線區域)
        canvas.drawArc(rectArc, 0, 90, true, mRedPaint);

        RectF rectArc2 = new RectF(100 + 500 + 300, 100, 500 + 500 + 300, 300);
        //繪製圓弧(矩形邊界,開始角度,掃過角度,使用中心?邊緣兩點與中心連線區域:邊緣兩點連線區域)
        canvas.drawArc(rectArc2, 0, 90, false, mRedPaint);
    }
複製程式碼

繪製類圓.png


6.繪製圖片

繪製圖片.png

    /**
     * 繪製圖片
     * @param canvas
     */
    private void drawBitmap(Canvas canvas) {
        //1.定點繪製圖片
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.menu_bg);
        canvas.drawBitmap(bitmap, 100, 100, mRedPaint);
        //2.適用變換矩陣繪製圖片
        Matrix matrix = new Matrix();
        //設定變換矩陣:縮小3倍,斜切0.5,右移150,下移100
        matrix.setValues(new float[]{
                1, 0.5f, 1500 * 3,
                0, 1, 100 * 3,
                0, 0, 3
        });
        canvas.drawBitmap(bitmap, matrix, mRedPaint);

        //3.圖片適用矩形區域不剪裁
        RectF rectf1 = new RectF(100 + 900, 100, 600 + 900, 400);
        canvas.drawBitmap(bitmap, null, rectf1, mRedPaint);

        //4.圖片裁剪出的矩形區域
        Rect rect = new Rect(300, 300, 400, 400);
        //圖片適用矩形區域
        RectF rectf2 = new RectF(100 + 900, 100 + 400, 600 + 900, 400 + 400);
        canvas.drawBitmap(bitmap, rect, rectf2, mRedPaint);
    }

複製程式碼

繪製圖片.png


7.繪製Picture

1).一開始挺納悶Picture不就是圖片嗎?然後翻了一下API:

/**
 * A Picture records drawing calls (via the canvas returned by beginRecording)
 * and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or
 * {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles),
 * drawing a sequence from a picture can be faster than the equivalent API
 * calls, since the picture performs its playback without incurring any
 * method-call overhead.
 *
 * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot
 * be replayed on a hardware accelerated canvas.</p>
 
 一個Picture類記錄繪製(通過beginRecording方法返回的Canvas),可以展示這個Canvas
 到其他Canvas上(通過Picture#draw(Canvas)或者Canvas#drawPicture(Picture)),
 對於大多數的內容,從picture繪製都要比相應的API要快速,因為picture的展現不會招致方法呼叫開銷
 在API級別23之前,無法在硬體加速畫布上展示Picture(自譯,僅供參考)
複製程式碼

2).通過一段程式碼你就能很清楚它是幹嘛用的

如果繪製一個品字,需要這樣:

testPicture.png

  private void drawPicture(Canvas canvas) {
        canvas.drawRect(100, 0, 200, 100, mRedPaint);
        canvas.drawRect(0, 100, 100, 200, mRedPaint);
        canvas.drawRect(200, 100, 300, 200, mRedPaint);
    }
複製程式碼

如果想要複用這個品字形,大多數人知道,平移畫布,複製貼上,
對於少量的程式碼,這還可以接收,如果是非常複雜的圖形,每次繪製重複的內容,會浪費效能

 private void drawPicture(Canvas canvas) {
     //建立Picture物件
     Picture picture = new Picture();
     //確定picture產生的Canvas元件的大小,並生成Canvas元件
     Canvas recodingCanvas = picture.beginRecording(canvas.getWidth(), canvas.getHeight());
     //Canvas元件的操作
     recodingCanvas.drawRect(100, 0, 200, 100, mRedPaint);
     recodingCanvas.drawRect(0, 100, 100, 200, mRedPaint);
     recodingCanvas.drawRect(200, 100, 300, 200, mRedPaint);
     //Canvas元件繪製結束
     picture.endRecording();
     
     canvas.save();
     canvas.drawPicture(picture);//使用picture的Canvas元件
     canvas.translate(0, 300);
     picture.draw(canvas);//同上:使用picture的Canvas元件
     canvas.drawPicture(picture);
     canvas.translate(350, 0);
     canvas.drawPicture(picture);
     canvas.restore();
複製程式碼

testPicture2.png

Picture相當於先拍一張照片,並且是在別的Canvas上,在別的Canvas上,在別的Canvas上!
重要的話說三遍:當需要的時候在貼在當前的canvas上,picture繪製的優勢就是節能減排
當有大量複雜內容需要複用,Picture這個的canvas元件是不二的選擇:


8.繪製文字(文字的效果有Paint決定,細節將在Paint篇介紹)

繪製文字

    /**
     * 繪製文字
     *
     * @param canvas
     */
    private void drawText(Canvas canvas) {
        mRedPaint.setTextSize(100);
        canvas.drawText("張風捷特烈--Toly", 200, 300, mRedPaint);
    }
複製程式碼

繪製文字.png

無聊的程式碼終於敲完了,進入正題。


三、Canvas的畫布變換

以前對Canvas的變換很厭倦,現在看了鍵值是神技
作為一代PS大神的我,理解Canvas狀態儲存與恢復本應易如反掌,為何最近才豁然開朗

1.先看下面的圖形:將座標系原點設為(500,500)
    private void stateTest(Canvas canvas) {
        canvas.drawLine(mCoo.x + 500, mCoo.y + 200, mCoo.x + 900, mCoo.y + 400, mRedPaint);
//        canvas.rotate(45);
        canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
    }
複製程式碼

狀態測試1.png

問題來了,想畫一個斜45度的矩形怎麼辦?
貌似沒有斜矩形的API,一個一個點找,貌似太麻煩了,我把紙轉一下不就行了嗎!
紙就是Canvas,看一下API,果然有rotate()方法,懷著忐忑的心情:

    private void stateTest(Canvas canvas) {
        canvas.drawLine(mCoo.x + 500, mCoo.y + 200, mCoo.x + 900, mCoo.y + 400, mRedPaint);
        canvas.rotate(45);
        canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
    }
複製程式碼

果然轉得天翻地覆

狀態測試旋轉.png


2.圖層的概念

PS中的圖層可謂PS的精華,它保證了在一個圖層中繪製而不會影響到其他的圖層
在Canvas中每次的save()都存將先前的狀態儲存下來,產生一個新的繪圖層,
我們可以隨心所欲地地畫而不會影響其他已畫好的圖,最後用restore()將這個圖層合併到原圖層
這像是棧的概念,每次save(),新圖層入棧(注意可以save多次),只有棧頂的層可以進行操作,restore()彈棧

圖層.png


3.旋轉畫布:rotate()
    private void stateTest(Canvas canvas) {
        canvas.drawLine(mCoo.x + 500, mCoo.y + 200, mCoo.x + 900, mCoo.y + 400, mRedPaint);
        canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
        canvas.save();//儲存canvas狀態
        //(角度,中心點x,中心點y)
        canvas.rotate(45, mCoo.x + 100, mCoo.y + 100);
        mRedPaint.setColor(Color.parseColor("#880FB5FD"));
        canvas.drawRect(mCoo.x + 100, mCoo.x + 100, mCoo.y + 300, mCoo.y + 200, mRedPaint);
        canvas.restore();//圖層向下合併
    }
複製程式碼

定點旋轉.png

4.平移畫布:translate():寫一堆mCoo,也就是讓畫布移動一下而已

效果必變,是不是清爽許多

    private void stateTest(Canvas canvas) {
        canvas.save();
        canvas.translate(mCoo.x, mCoo.y);//將原點平移到座標系原點
        canvas.drawLine(500, 200, 900, 400, mRedPaint);
        canvas.drawRect(100, 100, 300, 200, mRedPaint);
        canvas.save();//儲存canvas狀態
        //(角度,中心點x,中心點y)
        canvas.rotate(45, 100, 100);
        mRedPaint.setColor(Color.parseColor("#880FB5FD"));
        canvas.drawRect(100, 100, 300, 200, mRedPaint);
        canvas.restore();//圖層向下合併
        canvas.restore();
    }
複製程式碼

平移.png

5.縮放畫布:scale()
    private void stateTest(Canvas canvas) {
        canvas.save();
        canvas.translate(mCoo.x, mCoo.y);//將原點平移到座標系原點
        canvas.drawLine(500, 200, 900, 400, mRedPaint);
        canvas.drawRect(100, 100, 300, 200, mRedPaint);
        canvas.save();//儲存canvas狀態
        //(角度,中心點x,中心點y)
        canvas.scale(2, 2, 100, 100);
        mRedPaint.setColor(Color.parseColor("#880FB5FD"));
        canvas.drawRect(100, 100, 300, 200, mRedPaint);
        canvas.restore();//圖層向下合併
        canvas.restore();
    }
複製程式碼

定點縮放.png

6.斜切畫布:scale()
    private void stateTest(Canvas canvas) {

        canvas.save();
        canvas.translate(mCoo.x, mCoo.y);//將原點平移到座標系原點
        canvas.drawLine(500, 200, 900, 400, mRedPaint);
        canvas.drawRect(100, 100, 300, 200, mRedPaint);
        canvas.save();//儲存canvas狀態
        canvas.skew(1f,0f);
        mRedPaint.setColor(Color.parseColor("#880FB5FD"));
        canvas.drawRect(100, 100, 300, 200, mRedPaint);
        canvas.restore();//圖層向下合併
        canvas.restore();
    }
複製程式碼

斜切.png

7.畫布選擇儲存狀態:

canvas儲存狀態.png

public int save (int saveFlags)
預設:MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG

ALL_SAVE_FLAG	            儲存全部狀態
CLIP_SAVE_FLAG	            過期---僅儲存剪輯區(不作為圖層)
CLIP_TO_LAYER_SAVE_FLAG	    過期---僅剪裁區作為圖層儲存
FULL_COLOR_LAYER_SAVE_FLAG	過期---僅儲存圖層的全部色彩通道
HAS_ALPHA_LAYER_SAVE_FLAG	過期---僅儲存圖層的alpha(不透明度)通道
MATRIX_SAVE_FLAG	        過期---僅儲存Matrix資訊( translate, rotate, scale, skew)
複製程式碼
    int count = canvas.getSaveCount();//獲取圖層的個數3
    canvas.restoreToCount(1);//直接恢復到第幾個圖層
複製程式碼
四、Canvas的裁剪
1.可見主要就兩種型別,內裁剪和外裁剪,Op的操作被廢棄了

canvas剪裁.png

2.內剪裁:(區域內的之後繪製的內容儲存)
    private void clip(Canvas canvas) {
        //剪裁區域
        Rect rect = new Rect(20, 100, 250, 300);
        canvas.clipRect(rect);
        canvas.drawRect(0, 0, 200, 300, mRedPaint);
    }
複製程式碼

內剪裁.png

3.外剪裁:(區域外的之後繪製的內容儲存)--注意API26及以上可用
    private void clip(Canvas canvas) {
        //剪裁區域
        Rect rect = new Rect(20, 100, 250, 300);
        canvas.clipOutRect(rect);
        canvas.drawRect(0, 0, 200, 300, mRedPaint);
    }
複製程式碼

外剪裁.png

Canvas的相關內容就到這裡(注:路徑的繪製會在Path篇精講),下一節將帶來畫筆Paint的知識


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1--無 2018-11-5 Android關於Canvas你所知道的和不知道的一切
V0.2--無 2018-11-6 增加繪製Picture的內容
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的CSDN 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章