Android開發階段性總結——自定義view(1)

Android開發過程中總是會遇到系統提供的控件不能夠滿足需求,需要根據需求來自定義view;最近遇到了這樣的需求,總結一下自己對自定義view的理解。

座標系

你在編寫自定義view時就像是在指揮Android系統在給你畫畫,不過它是蒙着眼睛,需要你精準的告訴它要繪製的位置(xy座標);在android中有一套自己的座標系,以左上角爲原點的座標系;
在這裏插入圖片描述
這個座標系是相對手機屏幕的座標系(嚴謹的說是相對於當前Activity的座標系),相對於每個view同樣也是以view的左上角爲原點有一套座標系;系統也給我們提供出一些獲取位置的方法,圖中標註的很清楚;
在這裏插入圖片描述
舉個簡單的例子,利用系統提供的這些方法,很容易可以計算出view的寬高;

width = getRight() - getLeft();
height = getBottom() - getTop();

自定義view

在自定義 view時,我們首先應該繼承view類(viewgroup是view的擴展類)。繼承view之後,首先是需要我們實現構造方法;下面代碼中詳細標註對應使用方法:

    // 通過new去創建對象時調用
    public CustomView(Context context) {}
    // xml佈局文件中使用view時調用
    public CustomView(Context context, @Nullable AttributeSet attrs) {}
    // 不會自動調用,如果有默認style時,在第二個構造函數中調用
    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {}
    // API版本大於 21 時調用
    // 不會自動調用,如果有默認style時,在第二個構造函數中調用
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {}

我們自定義view時基本通過onMeasure、onLayout、onDraw三個方法對view進行繪製。

onMeasure()

在onMeasure中我們可以獲得view的寬高等信息。這裏就有一個坑,onMeasure會調用兩次,如果我們用getWidth()和getHeight()來獲得view的寬高,在第一次調用時會得到 0 ;所以我們通過getMeasuredWidth() 和 getMeasuredHeight()來獲得view的寬高。
onMeasure中主要用來給定義的一些變量賦值,一些位置信息的變量,如:寬高,半徑,邊距等;

onLayout()

view在繪製的時候會調用onlayout()來設置view的位置,我們可以通過改變view的left、top、right、bottom這4種屬性來控制View的座標。例如,我們可以重寫onTouchEvent() 方法,獲得用戶觸摸的座標點信息,通過layout()方法重新指定位置。代碼如下:

public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        int lastX = 0,lastY = 0;

        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
            	//手指觸摸點
                lastX = x;
                lastY = y;
                break;
            case MotionEvent.ACTION_MOVE:
            	//移動距離
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                //利用layout方法重新放置位置
                layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
                break;
        }
        return true;
    }

onDraw()

onDraw()方法顧名思義,就是在view佈局裏去畫你的自定義view。畫畫就少不了畫筆Paint,Paint就是一個畫筆,我們可以給他設置顏色,線條寬度,線條形狀等等,Paint還有很多進階的用法 如設置漸變色等,代碼如下:

Paint paint = new Paint();
paint.setAntiAlias(true);//抗鋸齒 性能消耗
paint.setDither(true);//防抖動 性能消耗
paint.setStyle(Paint.Style.STROKE);//線條樣式
paint.setStrokeCap(Paint.Cap.ROUND);//線條形狀
paint.setColor(Color.parseColor("#000000"));//顏色
paint.setStrokeJoin(Paint.Join.ROUND);//設置線段連接處樣式
paint.setTextSize(12);//字體大小 繪製文字時
//設置漸變色
radialGradient = new RadialGradient(
                    viewRadius,
                    viewRadius,
                    viewRadius,
                    gradientColors,
                    null,
                    Shader.TileMode.CLAMP
            );
paint.setShader(radialGradient);

畫筆設置完畢了,在哪裏畫呢?平時畫畫都在紙上,在android中畫畫canvas(畫布)就相當於紙。我們可以在canvas上畫線條,矩形、圓形、弧、甚至文字等等。不過就像之前說的那樣,android是蒙着眼睛畫畫,需要我們告訴它繪製的具體座標信息,它才能去繪製出對應的圖案;例如,畫圓,畫文字,代碼如下:

protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	//在canvas中畫畫之前,一定要初始化畫筆,每一個drawXXX方法都需要傳入paint
	initPaint();
    //畫圓
    canvas.drawCircle(100,100,100,paint);
    //畫弧 360° 圓弧
    RectF rectf = new RectF(80, 80,80,80);
    canvas.drawArc(rectf,0,360,false,paint);
    //畫文字 需要找基線 以下代碼文字是繪製在整個view中心
    String value = "我是一個好人";
    Rect rectBounds = new Rect();
    centerTextPaint.getTextBounds(value,0,value.length(),rectBounds);
    int dx = getWidth()/2 - rectBounds.width()/2;
    Paint.FontMetricsInt fontMetricsInt = centerTextPaint.getFontMetricsInt();
    int dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
    int baseLine = getHeight()/2 + dy;
    canvas.drawText(value,dx,baseLine, centerTextPaint);
}

Canvas重點

以上只是canvas的最基礎用法,canvas的高級用法就涉及到了相對座標系。相對座標系,就是結合canvas中的translat(移動view座標系原點),rotate(以原點爲中心旋轉座標系)去改變座標系的位置,在座標系位置改變後在計算對應座標位置,去進行繪製;在改變座標位置之前一定要通過save()保存畫布狀態,在繪製結束後再通過restore() 回覆到之前的狀態;以一個畫刻度盤的view爲例子,代碼如下:

		//畫刻度條
		//保存畫布狀態
        canvas.save();
        //移動view的座標系
        canvas.translate(viewRadius,viewRadius);
        //將座標系 順時針 旋轉180°
        canvas.rotate(180);
        //畫60條刻度
        for (int i=1;i<=60;i++){
       		//每畫一條刻度,整體座標系旋轉6°
            canvas.rotate(6);
            //畫刻度
            if(i%5 == 0){//長刻度
                canvas.drawLine(0,dialRadius + 10,0,dialRadius - 10,dialPaint);
            }else {//短刻度
                canvas.drawLine(0,dialRadius,0,dialRadius - 10,dialPaint);
            }
        }
        //恢復畫布,座標系還原
        canvas.restore();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章