CustomView를 만들어 보고자 합니다.
하고자 하는 것은 TextView Background 에 기본적으로 RoundRect 가 그려지도록 할 예정입니다.
여기서 필요한 기술이 두가지 입니다.
1. Background를 어떻게 그리지??
2. CustomeView의 크기를 어떻게 조절하지???
모든것을 설명해 드릴 수 없지만, 개발하는데 유용할 만한 내용은 될것이라 생각 됩니다.
먼저, Background에 이미지를 넣을때 아래와 같은 방법을 많이 씁니다. (전..그랬음.._)
- FrameLayout으로 감싼 후, Background로 활용할 이미지를 ImageView에 먼저 그린 후
그 다음으로 위에 표현하고자 하는 View를 올린다.
단점 ) View의 크기가 고정 되어 있지 않으면 (예를 들어 TextView와 같은 유동적인 View들)
Background ImageView의 크기를 맞추기 어렵다.
- View Background에 해당 이미지를 넣는다.
단점) Background가 고정이 된다. 유동적으로 색깔이 변하고 싶거나 모양이 변하고자 할 경우
표현이 어렵다.
위 방법으로 먼가 표현이 어려울 경우 아래와 같이 한번 해보세요.
- CustomView를 만들어서 onDraw에 canvas를 이용하여 그림을 그린다.
public class GroupTitleView extends TextView {
private int strokeWidth = 10;
private int color;
public GroupTitleView(Context context) {
super(context);
color = Color.RED;
strokeWidth = context.getResources().getDimensionPixelSize(R.dimen._2dp);
}
public GroupTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
color = Color.RED;
strokeWidth = context.getResources().getDimensionPixelSize(R.dimen._2dp);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint pnt = new Paint();
pnt.setStyle(Paint.Style.STROKE);
pnt.setStrokeWidth(strokeWidth);
pnt.setColor(color);
pnt.setDither(true); // set the dither to true
pnt.setStyle(Paint.Style.STROKE); // set to STOKE
pnt.setStrokeJoin(Paint.Join.ROUND); // set the join to round you want
pnt.setStrokeCap(Paint.Cap.ROUND); // set the paint cap to round too
pnt.setPathEffect(new CornerPathEffect(strokeWidth)); // set the path effect when they join.
pnt.setAntiAlias(true);
Path path = new Path();
final RectF oval = new RectF();
oval.set(strokeWidth, strokeWidth, getWidth()-strokeWidth, getHeight()-strokeWidth);
path.addRoundRect(oval, canvas.getHeight(), canvas.getHeight(), Path.Direction.CCW);
canvas.drawPath(path, pnt);
}
}
위와 같이 그리게 되면 백그라운드에 빨간색 RoundRect가 그려집니다.
하지만, 결과는 아래와 같습니다.
TextView의 text와 백그라운드 그림이 겹쳐버리는 거지요.
이럴 경우, 크기를 다시 조절해줘야 합니다.
package com.test.canvastest;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* Created by Chcheung on 2016-01-21.
*/
public class GroupTitleView extends TextView {
private int strokeWidth = 10;
private int color;
public GroupTitleView(Context context) {
super(context);
color = Color.RED;
strokeWidth = context.getResources().getDimensionPixelSize(R.dimen._2dp);
}
public GroupTitleView(Context context, AttributeSet attrs) {
super(context, attrs);
color = Color.RED;
strokeWidth = context.getResources().getDimensionPixelSize(R.dimen._2dp);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint pnt = new Paint();
pnt.setStyle(Paint.Style.STROKE);
pnt.setStrokeWidth(strokeWidth);
pnt.setColor(color);
pnt.setDither(true); // set the dither to true
pnt.setStyle(Paint.Style.STROKE); // set to STOKE
pnt.setStrokeJoin(Paint.Join.ROUND); // set the join to round you want
pnt.setStrokeCap(Paint.Cap.ROUND); // set the paint cap to round too
pnt.setPathEffect(new CornerPathEffect(strokeWidth)); // set the path effect when they join.
pnt.setAntiAlias(true);
Path path = new Path();
final RectF oval = new RectF();
oval.set(strokeWidth, strokeWidth, getWidth()-strokeWidth, getHeight()-strokeWidth);
path.addRoundRect(oval, canvas.getHeight(), canvas.getHeight(), Path.Direction.CCW);
canvas.drawPath(path, pnt);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int w = 0, h = 0;
// ceil not round - avoid thin vertical gaps along the left/right edges
int minw = getPaddingLeft()+getPaddingRight()+getSuggestedMinimumWidth()+getMeasuredWidth()
+(int)(getTextSize()*2)+strokeWidth*2;
int minh = getPaddingTop()+getPaddingBottom()+getSuggestedMinimumHeight()+getMeasuredHeight()
+(int)(getTextSize()*2)+strokeWidth*2;
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
switch (widthMode) {
case MeasureSpec.AT_MOST://wrap_content
w = minw;
break;
case MeasureSpec.UNSPECIFIED://unspecified
w = widthMeasureSpec;
break;
case MeasureSpec.EXACTLY://match_parent
w = MeasureSpec.getSize(widthMeasureSpec);
break;
}
switch (heightMode) {
case MeasureSpec.AT_MOST:
h = minh;
break;
case MeasureSpec.UNSPECIFIED:
h = heightMeasureSpec;
break;
case MeasureSpec.EXACTLY:
h = MeasureSpec.getSize(heightMeasureSpec);
break;
}
setMeasuredDimension(w, h);
}
}
그래서 수정한 소스 입니다.
그리고 아래는 결과 화면 입니다.
크기 조절은 그냥 제가 임의로 한 거기 때문에 공식이 맞지 않다고 생각 하시는 분은 수정 해서 사용해보세요~
OnMeasure 함수... 먼가 StackOverflow에서 많이 보신 함수 일 껍니다.
이 함수 어떻게 수정 해야 할지 고민이 많았습니다.
그래서 느낀점이 딱 이 공식을 얻으면 될 것 같습니다.
1. super.onMeasure(widthMeasureSpec, heightMeasureSpec); 함수를 호출하고 난 다음에
getMeasuredWidth와 getMeasuredHeight는 해당 View 사이즈를 넘겨 준다.
2. 매개변수로 넘어오는 widthMeasureSpec과 heightMeasureSpec은 Parent의 크기를 넘겨 준다.
즉, View가 추가된 부모 View나 Layout의 사이즈 Spec을 넘겨줍니다. (padding, margin 다 넘어옴)
3. MeasureSpec.AT_MOST : wrap_content 사이즈로
MeasureSpec.EXACTLE : match_parent 사이즈로
MeasureSpec.UNSPECIFIED : 0dp나 먼가 값이 정해지지 않은 상태
-> 위 상태는 부모의 상태를 주는 것이므로, 그 상태에 따라서 대응을 해주면 되겠습니다.
그리고 마지막에 꼭 setMeasureDimension 으로 크기를 지정해줘야 합니다.
함수 호출 순서는 아래와 같습니다.
View.measure() >> View.onMeasure() >> View.setMeasuredDimesion()
출처 : http://i5on9i.blogspot.kr/2013/05/android-view-onmeasure-onlayout.html
위 내용만 알면~ CustomView를 만들거나 소스를 분석할 때 좋은 자료가 될꺼라 생각됩니다.
참고하세요.
참고 사이트: http://stackoverflow.com/a/12267248/3534559
CanvasTest.zip 소스는 첨부 하였습니다.
'나의 플랫폼 > 안드로이드' 카테고리의 다른 글
[Android] Custom TextView not working gravity (0) | 2016.01.22 |
---|---|
[Android] CustomView에 Attribute 만들기 (0) | 2016.01.22 |
[Android] Get current fragment (0) | 2016.01.21 |
[Android] 강제로 AppbarLayout 열기 (0) | 2016.01.21 |
[Android] RecyclerView last position listener (7) | 2016.01.21 |