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