posted by By훈트 2011.02.15 09:42

안드로이드에서 기본적으로 지원하지 않는 UI 를 만들때 CustomView 를 사용합니다.

이러한 CustomView 의 기본적인 작성방법을 알아보도록 하겠습니다.

 

CustomView 는 “android.view.View” 클래스를 상속해서 만들어 집니다.

기본적으로 onDraw() 메소드만 재정의해서 xml 에 view 태그만 추가하면 오류없이 출력되는것을 볼 수 있습니다.

 

이번 포스트에서는 간단히 클릭하면 반응하는 CustomView 를 만들어 보도록 하겠습니다.

먼저 CustomView 소스를 확인해 보도록 하겠습니다.

 

- CustomView.java

package net.cranix.android.customviewtest;

import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View;
public class CustomView extends View {
private String text = null; private int backgroundColor = Color.RED;
private String tempText;
// 속성이 없는 생성자는 소스상에서 직접 생성할때만 쓰인다. public CustomView(Context context) { super(context); Log.w(Constants.TAG,"CustomView("+context+")"); }
/* * 리소스 xml 파일에서 정의하면 이 생성자가 사용된다. * * 대부분 this 를 이용해 3번째 생성자로 넘기고 모든 처리를 3번째 생성자에서 한다. */ public CustomView(Context context,AttributeSet attrs) { this(context,attrs,0); Log.w(Constants.TAG,"CustomView("+context+","+attrs+")"); }

/* * xml 에서 넘어온 속성을 멤버변수로 셋팅하는 역할을 한다. */ public CustomView(Context context,AttributeSet attrs,int defStyle) { super(context,attrs,defStyle); this.text = attrs.getAttributeValue(null,"text"); Log.w(Constants.TAG,"CustomView("+context+","+attrs+","+defStyle+"),text:"+text); }

/* * xml 로 부터 모든 뷰를 inflate 를 끝내고 실행된다. * * 대부분 이 함수에서는 각종 변수 초기화가 이루어 진다. * * super 메소드에서는 아무것도 하지않기때문에 쓰지 않는다. */ @Override protected void onFinishInflate() { setClickable(true); Log.w(Constants.TAG,"onFinishInflate()"); }
/* * 넘어오는 파라메터는 부모뷰로부터 결정된 치수제한을 의미한다. * 또한 파라메터에는 bit 연산자를 사용해서 모드와 크기를 같이 담고있다. * 모드는 MeasureSpec.getMode(spec) 형태로 얻어오며 다음과 같은 3종류가 있다. * MeasureSpec.AT_MOST : wrap_content (뷰 내부의 크기에 따라 크기가 달라짐) * MeasureSpec.EXACTLY : fill_parent, match_parent (외부에서 이미 크기가 지정되었음) * MeasureSpec.UNSPECIFIED : MODE 가 셋팅되지 않은 크기가 넘어올때 (대부분 이 경우는 없다) * * fill_parent, match_parent 를 사용하면 윗단에서 이미 크기가 계산되어 EXACTLY 로 넘어온다. * 이러한 크기는 MeasureSpec.getSize(spec) 으로 얻어낼 수 있다. * * 이 메소드에서는 setMeasuredDimension(measuredWidth,measuredHeight) 를 호출해 주어야 하는데 * super.onMeasure() 에서는 기본으로 이를 기본으로 계산하는 함수를 포함하고 있다. * * 만약 xml 에서 크기를 wrap_content 로 설정했다면 이 함수에서 크기를 계산해서 셋팅해 줘야한다. * 그렇지 않으면 무조껀 fill_parent 로 나오게 된다. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// height 진짜 크기 구하기 int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = 0; switch(heightMode) { case MeasureSpec.UNSPECIFIED://mode 가 셋팅되지 않은 크기가 넘어올때 heightSize = heightMeasureSpec; break; case MeasureSpec.AT_MOST://wrap_content (뷰 내부의 크기에 따라 크기가 달라짐) heightSize = 20; break; case MeasureSpec.EXACTLY://fill_parent, match_parent (외부에서 이미 크기가 지정되었음) heightSize = MeasureSpec.getSize(heightMeasureSpec); break; }
// width 진짜 크기 구하기 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = 0; switch(widthMode) { case MeasureSpec.UNSPECIFIED://mode 가 셋팅되지 않은 크기가 넘어올때 widthSize = widthMeasureSpec; break; case MeasureSpec.AT_MOST://wrap_content (뷰 내부의 크기에 따라 크기가 달라짐) widthSize = 100; break; case MeasureSpec.EXACTLY://fill_parent, match_parent (외부에서 이미 크기가 지정되었음) widthSize = MeasureSpec.getSize(widthMeasureSpec); break; }
Log.w(Constants.TAG,"onMeasure("+widthMeasureSpec+","+heightMeasureSpec+")"); setMeasuredDimension(widthSize, heightSize); }
/* * onMeasure() 메소드에서 결정된 width 와 height 을 가지고 어플리케이션 전체 화면에서 현재 뷰가 * 그려지는 bound 를 돌려준다. * 이 메소드에서는 일반적으로 이 뷰에 딸린 children 들을 위치시키고 크기를 조정하는 작업을 한다. * 유의할점은 넘어오는 파라메터가 어플리케이션 전체를 기준으로 위치를 돌려준다. * * super 메소드에서는 아무것도 하지않기때문에 쓰지 않는다. */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { Log.w(Constants.TAG,"onLayout("+changed+","+left+","+top+","+right+","+bottom+")"); }
/* * 이 뷰의 크기가 변경되었을때 호출된다. * * super 메소드에서는 아무것도 하지않기때문에 쓰지 않는다. */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { Log.w(Constants.TAG,"onSizeChanged("+w+","+h+","+oldw+","+oldh+")"); }
/* * 실제로 화면에 그리는 영역으로 View 를 상속하고 이 메소드만 구현해도 제대로 보여지게 된다. * * 그릴 위치는 0,0 으로 시작해서 getMeasuredWidth(), getMeasuredHeight() 까지 그리면 된다. * * super 메소드에서는 아무것도 하지않기때문에 쓰지 않는다. */ @Override protected void onDraw(Canvas canvas) { final Paint p = new Paint(); p.setColor(backgroundColor); canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(), p); if (text != null) { p.setColor(Color.BLACK); canvas.drawText(text, 10, 15, p); // 왼쪽 아래를 0,0 으로 보고있음 } Log.w(Constants.TAG,"onDraw("+canvas+")"); }
/* * 현재 view 가 focus 상태일때 key 를 누르면 이 메소드가 호출됨. * 즉 이 메소드를 사용하려면 setFocusable(true) 여야함. *
     * 그리고 super 메소드에서는 기본적인 키 작업(예를들면 BACK 키 누르면 종료)을 처리하기 때문에 
     * 일반적으로 return 시에 호출하는게 좋다.
     * 만약 기본적인 작업을 하지않게 하려면 super 함수를 호출하지 않아도 된다.
     * 
     * 다른 event 메소드들도 유사하게 동작한다.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.w(Constants.TAG,"onKeyDown("+keyCode+","+event+")");
        return super.onKeyDown(keyCode, event); 
    }
/* * 이 view 에 touch 가 일어날때 실행됨. * * 기본적으로 touch up 이벤트가 일어날때만 잡아내며 * setClickable(true) 로 셋팅하면 up,move,down 모두 잡아냄 */ @Override public boolean onTouchEvent(MotionEvent event) { Log.w(Constants.TAG,"onTouchEvent("+event+")"); switch(event.getAction()) { case MotionEvent.ACTION_UP: backgroundColor = Color.RED; text = tempText; break; case MotionEvent.ACTION_DOWN: backgroundColor = Color.YELLOW; tempText = text; text = "Clicked!"; break; case MotionEvent.ACTION_MOVE: backgroundColor = Color.BLUE; text = "Moved!"; break; } invalidate(); return super.onTouchEvent(event); }
public String getText() { return text; }
public void setText(String text) { this.text = text; } }

- 크기 계산하기

여기서 중요한 메소드는 onMeasure() 메소드 입니다.

이 메소드는 뷰의 전체 크기를 정하는 메소드 인데 안드로이드의 크기 정하는 방법에 따라 구현법이 달라져야 합니다.

 

안드로이드 레이아웃 xml 파일에서 크기를 지정하는 방법은 4가지가 있습니다.

   - fill_parent (상위 View 의 크기에 따름)

   - match_parent (상위 View 의 크기에 따름)

   - fixed (100px 와 같이 픽셀로 박아놨을때)

   - wrap_content (현재 뷰의 내용에 따름)

 

이렇게 4가지 방법의 특성에 따라서 넘어오는 크기의 종류는 3가지로 구분됩니다.

   - MeasureSpec.EXACTLY : fill_parent, match_parent, fixed 와 같이 상위에서 이미 결정되어버린 크기가 넘어올때 선택됩니다.

   - MeasureSpec.AT_MOST : wrap_content 를 선택했을때 선택됩니다.

   - MeasureSpec.UNSPECIFIED : xml 에 의하지 않고 소스상에서 직접 넣었을 때 나옵니다.

 

여기서 EXACTLY 과 UNSPECIFIED 는 외부에서 크기가 구해져서 내려오는 것이기 때문에 따로 계산할 것이 없으나 AT_MOST 는 내부적으로 크기계산을 해 주어야 합니다.

위의 소스에서는 간단하게 100,20 으로 박아놨지만 실제로 CustomView 를 구현하게 된다면 뷰의 특성에 따라 구현이 달라져야 할 것입니다.

 

 

- xml 에서 파라메터 받아내기

안드로이드 리소스 xml 에서 파라메터를 받아내려면 위 소스의 3번째 생성자에 있는것 처럼 아래와 같은 구문을 써야 합니다.

this.text = attrs.getAttributeValue(null,"text");

 

 

- xml 파일 구성하기

이렇게 만든 CustomView 를 xml 파일에서 사용하려면 아래와같은 xml 구성이 필요합니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<view class="net.cranix.android.customviewtest.CustomView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
text="test"
/>
</LinearLayout>

 

- 실행해 보기

이렇게 구성된 뷰를 Activity 에 넣고 실행해 보면 아래와 같은 화면이 나옵니다.

마우스를 클릭,이동 할때마다 색깔이 변경되는것을 볼 수 있습니다.

image

image

image


[출처] 닉스로그님의 블로그


저작자 표시
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

티스토리 툴바