참고 url : http://developer.android.com/guide/topics/ui/how-android-draws.html

이번에는 새로운 뷰를 만드는 부분을 볼 것이다.
아래와 같은 나침반을 만들 것이다.


나침반을 만드는데 필요한 순서는 다음과 같다.
1. 뷰가 사용해야 할 사이즈( 가로*세로) 산출하기
2. 렌더링 로직 구현하기

위의 것을 이야기 전에 안드로이드에서 어떻게 뷰를 렌더링 하는지를 알아보다.
안드로이드는 어플리케이션을 렌더링하기 전에 각 뷰와 뷰모델이 어느정도의 크기를 가지는지 호출을 한다.
그 크기를 알아내기 위해  measure(int, int) 함수를 호출을 하는데 OOP의 감동스런 구조(^^ ?)를 사용하여 액티버티가 사용하는 레이아웃의 첫 요소부터 시작해서 위에서 아래도 순차적으로 호출을 한다.
(또한 뷰모델은 자식 뷰 또는 뷰모델의 크기를 알아내기 위에서 top-down 순으로 호출을 하게 된다.)

measure 함수의 파라미터는 부모 뷰모델의 (안드로이드에서는 뷰는 단독으로 그려질 수 없으며 항상 뷰모델에 속해야 하니 부모는 항상 뷰모델이 된다.) 권장되는 넓이와 높이이다.

그리고 나서 onDraw()함수가 호출되는데 "기존뷰 재정의하기" 에서 보면 정해진 레이아웃 크기에 맞게 그려지게 되는 것이다.

그럼 이제 나침반을 렌더링하는 코드를 보자.

public class CompassView extends View {
private Paint markerPaint;
private Paint textPaint;
private Paint circlePaint;
private String northString;
private String eastString;
private String southString;
private String westString;
private int textHeight;
private float bearing;
public void setBearing(float _bearing){
this.bearing = _bearing;
}
public float getBearing(){
return this.bearing;
}
public CompassView(Context context) {
super(context);
initCompassView();
}
public CompassView(Context context, AttributeSet attrs){
super(context, attrs);
initCompassView();
}
public CompassView(Context context, AttributeSet attrs, int defaultStyle){
super(context, attrs, defaultStyle);
initCompassView();
}

protected void initCompassView(){
setFocusable(true);
Resources r = getResources();
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(r.getColor(R.color.background_color));
circlePaint.setStrokeWidth(1);
circlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
northString= r.getString(R.string.cardinal_north);
eastString = r.getString(R.string.cardinal_east);
southString = r.getString(R.string.cardinal_south);
westString= r.getString(R.string.cardinal_west);
textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(r.getColor(R.color.text_color));
textHeight = (int)textPaint.measureText("yY");
markerPaint =new Paint(Paint.ANTI_ALIAS_FLAG);
markerPaint.setColor(r.getColor(R.color.marker_color));
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int measuredWidth = measure(widthMeasureSpec);
int measuredHeight = measure(heightMeasureSpec);
int d = Math.min(measuredWidth, measuredHeight);
setMeasuredDimension(d,d);
}
private int measure(int measureSpec){
int result=0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
// specMode 가 지정되지 않은경우
if(specMode == MeasureSpec.UNSPECIFIED){
result=200;
} else {
result = specSize;
}
return result;
}

protected void onDraw(Canvas canvas){
int px = getMeasuredWidth() / 2;
int py = getMeasuredHeight() / 2;
int radius = Math.min(px, py);
canvas.drawCircle(px, py, radius, circlePaint);
canvas.save();

canvas.rotate(-this.bearing, px, py);
int textWidth = (int)textPaint.measureText("W");
int cardinalX = px - textWidth / 2;
int cardinalY = py - radius + textHeight;
for(int i=0 ; i < 24 ; i++){
canvas.drawLine(px, py-radius, px, py-radius+10, this.markerPaint);
canvas.save();
canvas.translate(0, textHeight);
if(i % 6 == 0){
String dirString = "";
switch(i){
case 0 : 
dirString = this.northString;
int arrowY = 2 * textHeight;
canvas.drawLine(px, arrowY, px-5, 3*textHeight, markerPaint);
canvas.drawLine(px, arrowY, px+5, 3*textHeight, markerPaint);
break;
case 6 : 
dirString = this.eastString;
break;
case 12 : 
dirString = this.southString;
break;
case 18 : 
dirString = this.westString;
break;
}
canvas.drawText(dirString, cardinalX, cardinalY, textPaint);
} else if( i%3 == 0) {
String angle = String.valueOf(i*15);
float angleTextWidth = textPaint.measureText(angle);
int angleTextX = (int)(px - angleTextWidth/2);
int angleTextY = py - radius + textHeight;
canvas.drawText(angle, angleTextX, angleTextY, textPaint);
}
canvas.restore();
canvas.rotate(15, px, py);
}
canvas.restore();  
}
}

새로운 뷰를 만들기 위해서 View 클래스를 상속하는데 만드는 것이 뷰이기 때문에 당연할 것이다.
( 그래도 당연한것을 인식을 하면서도 이 당연함을 안다는 것이 공부하는데 재미를 준다. ^^ )

시스템에서 액티비티를 렌더링 하기위해서 mearsure() 함수를 호출한다고 그랬는데 소스에서 보니 onMeasure() 함수를 재정의 해 놓았다. 여기에서 유추해 볼 수 있는게 measure() 함수 내부에서 뷰모델에게 onMeasure() 함수를 호출할 것이다. 
이 부분에서 MeasureSpec 클래스를 사용하는 부분이 있는데 이 클래스는 부모가 자식 뷰 또는 뷰모델에게 제한 사이즈를 알려주는 것이다.
예를 들어, LinearLayout 에서는 가로는 240px, 높이는 무제한을 말해 줄것이다.

이렇게 사이즈를 정하게 되면, setMeasuredDimension(d,d); 함수로 렌더링 사이즈를 설정한다.

그리고 onDraw() 함수에서 getMeasureWidth(), getMeasureHeight() 함수를 호출하여 설정한 사이즈를 가져와 렌더링을 하는 것이다.

Posted by hgjung

댓글을 달아 주세요