안드로이드를 대학 다닐때만 해보고 그 이후로는 다룰일이 별로 없었지만 안드로이드 프로그래밍에 관심이 많았다. 새로운 UI들이 많이 나오고 저건 어떻게 만들었을까 공부하다가 정리한 내용이다.




View 상속



출처 : http://lazineer.tistory.com/90

 

layout이나 button 같은 UI 관련 클래스들은 모두 View를 상속 받아서 생성된다. 이중에 ViewGroup은 button이나 ImageView같은 것과는 다르게 자식뷰를 가질수 있으면 이를 배치하는 역할을 한다.

 

자신만의 button 이나 View를 만들고 싶다면 View 클래스를 상속 받아서 필요한 메소드들을 오버라이드 하면된다.

 

자신만의 Layout을 만들고 싶다면 ViewGroup 클래스를 상속 받아서 필요한 메소드들을 오버라이드 하면된다.

 

 

Custom View View 클래스를 상속하여 만든다.

Custom Layout Viewgroup 클래스를 상속하여 만든다.

 

 

 

 

 

 

 

View 메소드 호출 순서



 

 

 

 

 

 

 

 

View 오버라이드할 메소드

 

 

onMeasure

하는

View 크기를 결정할 불리는 함수이다.

함수 호출 과정

View.measure() -> View.onMeasure() -> View.setMeasuredDimesion()

 

설명

view 자신의 크기를 지정하는 함수이다.

인자로는 (int widthMeasureSpec, int heightMeasureSpec) 넓이, 높이 값이 모드와 함께 조합된 값이다. 값들을 사용하기 위해서는 MeausreSpce getMode() getSize() 사용해야 한다.

 

int widthMode = MeasureSpec.getMode(widthMeasureSpec)

int width = MeasureSpec.getSize(widthMeasureSpec)

 

이때 Mode 3가지가 존재한다.

MeasureSpec.UNSPECIFIED : 소스상에서 View 생성했을 해당 모드가 넘어 온다. 경우 그냥 인자로 넘어온 그대로 사용하면 된다.

MeasureSpec.AT_MOST : 이경우는 View 내부의 크기를 계산한 값을 넘겨주면 된다. View layout_width, layout_height  wrap_content 설정된 경우 모드가 넘어오게 된다. MeasureSpec.getSize() 통해 나온 값은 View 최대로 가질수 있는 크기이다. 이를 바탕으로 내부 크기를 계산해야한다. 내부 크기가 값보다 크다면 문제가 있다.

MeasureSpec.EXACTLY : 해당 View layout_width, layout_height fill_parent match_parent 경우 부모 layout(viewGroup) 해당 View 크기를 미리 지정해서 width height 넘겨주므로 MeasureSpec.getSize() 으로 반환된 값을 그냥 사용하면 된다.

 


onMeasure() 함수 override 내부에서는 setMeasuredDimesion()함수를 호출해서 자신의 크기를 설정하여야만 합니다.(기본동작은 super.onMeasure() 호출해라.)

setMeasuredDimesion()함수는 자신의 크기를 설정하는 함수로 view width,height 값에 상관없이 실제 크기는 함수를 통해 지정됩니다. (view width,height view 크기를 권고하는 것입니다. 실제는 setMeasuredDimesion() 지정한 값으로 크기가 결정됩니다.)

만약 onMeasure()에서 setMeasuredDimesion() 호출하지 않으면 java.lang.IllegalStateException 예외가 발생합니다.



 

풀어서 설명

1 ) 만약 부모가 크기가 width 100, height 200이고 자식의 크기는 width" wrap_content, height : wrap_content 이라면

onMeasure 의 widthMeasureSpec [100,AT_MOST] heightMeasureSpec은 [200,AT_MOST] 넘어올 것이다. 부모가 자식에게 말하는 것이다. 너의 내부 컨텐츠의 크기가 너의 크기가 될때 너의 크기는 몇이냐? 근데 너가 최대로 커질수 있는 크기는 100x200이야. 자식은 자신의 내부 컨텐츠 크기를 계산하고 크기가 100x200 넘지 않는다면 크기가 자신의 크기가 되는 것이고 내부 컨텐츠가 크기가 100x200 넘어간다면 자신의 크기는 100x200 되야 한다. 물론 widthMeasureSpec ,heightMeasureSpec은 부모가 자식에게 권고하는 것이다. 만약 자신의 설정이 wrap_content라도 무조건 부모가 지정한 최대크기만큼 커질꺼야하는 자식은 100x200으로 설정하면 된다.

 

2) 만약 부모가 크기가 width 100, height 200이고 자식의 크기는 width" fill_content, height : wrap_content이라면 onMeasure 의 widthMeasureSpec [100,EXACTLY  ] heightMeasureSpec은 [200,EXACTLY ] 넘어올 것이다. 부모가 자식에게 말하는 것이다. 너는 내가 지정한 100x200 되어야해. 잘듣는 자식이라면 자신의 크기를 100x200으로 지정하면 된다. 하지만 이것또한 권고 일뿐이다. 기능이 다르게 동작하는 자식이라면 알아서 크기를 계산하면 된다.

 

3) 만약 부모가 크기가 width 100, height 200이고 자식의 크기는 width: 80, height : 50이라면 onMeasure 의 widthMeasureSpec [80,EXACTLY ] heightMeasureSpec은 [50,EXACTLY  ] 넘어올 것이다자식은 이미 크기가 지정되어 있다는 부모는 안다. 그래서 부모는 자식에게 " 내가 지정한 80x50 되어야해" 라고 말하는 것이다.

만약에 부모 크기가 40x30이라면 자식이 아무리 크기가 80x50으로 설정했다해도 부모는 자식에게 MeasureSpec [40,EXACTLY ],[30,EXACTLY ] 보낼 것이다. 일반적인 레이아웃은 자식이 부모보다 커질수 없다.

 


보통 view onMeasure() 함수의 동작은 잘듣는 자식과 같다.

좀더 말하면 자식의 width, height 속성값이 지정되어 있더라도 이를 결정 짓는 것은 부모이다. 꼭꼭 기억하라.

 

 

만약 자식 뷰가 부모 뷰보다 크더라도 자식 뷰는 부모 뷰보다 크게 그려지지 않습니다. 부모 뷰보다 크게 그려지는 부분은 보이지 않게 됩니다.

 

 

 

내부에서 많이 쓰이는 함수

this.measureChild() : 자식 뷰의 크기를 계산시킨다.

this.getChildMeasureSpec() : measureChildren()에서 사용하는 하나의 메소드이다. 메소드를 통해 특정 자식 view MeasureSpce 만들수 있다.

 

 

 

 

 

 

 

 

 

onLayout

하는

child view 위치를 잡아주는 일을 한다. onMeasure 호출 후에 onLayout 호출 되므로 자신의 크기를 이미 인지하는 상태에서 위치를 잡아 있다.

 

주의할 점은 위치가 장비 디스플레이의 절대적 위치이다. 부모를 기준으로한 상대적인 위치가 아니다.


만약 view Layout 크기가 width 100, height 100이고 measure 크기가 width 20, height이라면 공간은 100x100 할당하지만 크기는 20x20으로 그려진다

 

함수 호출 과정

ViewGroup.layout()-> View.layout() -> View.onLayout()

 

설명

ViewGroup 상속하여 layout 만드는 경우 child view의 위치를 잡아주는 역할 한다.

넘어 오는 파라미터는 어플리케이션 전체를 기준으로 위치가 넘어오므로 주의해야한다.

 

 

내부에서 많이 쓰이는 함수

this.getPaddingLeft() : 자신의 왼쪽 패딩값

this.getPaddingTop() : 자신의 위쪽 패딩값

this.getPaddingRight() : 자신의 오른쪽 패딩값

this.getPaddingBottom() : 자신의 아래쪽 패딩값

this.getMeasuredWidth() : 자신의 크기 계산후 나온 넓이

this.getMeasuredHeight() : 자신의 크기 계산후 나온 높이

getChildCount() : 자식 뷰의 개수를 반환한다.

child.measure() : 자식 뷰의 크기를 반환한다. MeasureSpec.AT_MOST 거의 호출한다.

child.getMeasuredWidth() : 자식 뷰의 넓이를 가져온다. child.measure() 호출 호출한다.

child.getMeasuredHeight() : 자식 뷰의 길이를 가져온다. child.measure() 호출 호출한다.

child.layout() : 자식 뷰의 위치를 지정한다.

 

 

 

 

onDraw

하는

화면을 그리는 일을 한다.

 

주의할 점은 이때 좌표의 기준은 디스플레이의 절대적인 위치가 아니다. 자신을 기준으로한 좌표이다.

 

설명

자신을 그리고 자식이 있는 경우 자식들을 그리면 된다.

 

 

내부에서 많이 쓰이는 함수

cavans.() : Canvas 모든 그리는 함수는 사용한다.

this.getMeasuredWidth() : view 자신의 넓이를 반환한다. 이를 바탕으로 그리면 된다.

this.getMeasuredHeight() : view 자신의 높이를 반환한다. 이를 바탕으로 그리면 된다.

 

 

 

기타

쓰이는 함수

MeasureSpec.makeMeasureSpec : 길이와 모드를 조합하여 measurespec 값을 만든다.

 

 

참고사항

padding margin 차이 : padding 내부 여백, margin 외부 여백
http://clason.tistory.com/321 : 마진(margin) VS 패딩(padding)

 

invalidate() requestLayout() 차이 : invalidate 화면이 유효하지 않으니 다시 그리도록 하라는 것이고 requestLayout() layout 갱신하라는 것이다.

http://stackoverflow.com/questions/13856180/usage-of-forcelayout-requestlayout-and-invalidate

 

 

 

 

 

 

 

 

 

예제를 통한 Custom View 만들기

예제를 통해 Custom View 어떻게 만드는지 보겠다.

만들어볼 View MyProgressBar 라는 진행바이다.





위와 같은 모습의 진행바이다.

 

최대값을 지정하고 현재값에 따른 백분율에 따라 원을 그리는 진행바이다. 위의 예는 최대값이 100일때 현재값이 25이면 원이 25% 그려지는 상황과 현재값이 최대값일때 원을 그리는 상황이다.

 

Attribute 원의 , 최대값, 현재값이다.

그리고 1초마다 가운데 텍스트가 깜빡깜빡거린다.

 

 

Custom View 만들기 위해서는

  1. View 상속
  2. Attribute 정의
  3. onDraw 메소드 오버라이드

 

 

 

 

 

 

 

  1. Custom Attribute 정의

xml layout 통해 MyProgressBar 속성을 조작하기 위해서는 attribute 우선 정의해야한다.

정의는 res/values/attrs.xml 정의한다.

<resources>

    <declare-styleable name="MyProgressBar">

        <attr name="maxValue" format="integer"/>

        <attr name="curValue" format="integer" />

        <attr name="lineColor" format="color" />

    </declare-styleable>

</resources>

maxValue : 최대값, 정수

curValue : 현재값, 정수

lineColor : 진행바 , Color라는 객체를 받을 이기 때문에 참조인 color

 

 

 

  1. MyProgressBar 클래스 생성 Attribute 처리하기

public class MyProgressBar extends View {

    private int curValue; //현재값 저장

    private int maxValue; //최대값 저장

    private int lineColor; //진행바 색 저장

 

 

    public MyProgressBar(Context context) {

        super(context);

    }

 

    public MyProgressBar(Context context, AttributeSet attrs) {

        super(context, attrs);

 

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyProgressBar,0,0);

 

        try{

            this.curValue = a.getInteger(R.styleable.MyProgressBar_curValue,0);

            this.maxValue = a.getInteger(R.styleable.MyProgressBar_maxValue,100);

            this.lineColor = a.getColor(R.styleable.MyProgressBar_lineColor,0xff000000);

 

        }finally {

 

        }

 

    }

 

 

    public int getLineColor() {

        return lineColor;

    }

 

    public void setLineColor(int color) {

        this.lineColor = color;

        invalidate();

        requestLayout();

    }

 

    public int getCurValue() {

        return curValue;

    }

 

    public void setCurValue(int curValue) {

        this.curValue = curValue;

        invalidate();

        requestLayout();

    }

 

    public int getMaxValue() {

        return maxValue;

    }

 

    public void setMaxValue(int maxValue) {

        this.maxValue = maxValue;

        invalidate();

        requestLayout();

    }

 }

 

 

View 상속받은 MyProgressBar 만든다. xml layout 통한 속성을 사용하기 위해서는   public MyProgressBar(Context context, AttributeSet attrs) 정의하여야 한다.

 

클래스는 속성을 저장할 curValue, maxValue, lineColor 변수를 가진다. 셋다 int 값이다.(color int값이다.)

또한 속성값을 조작할 setter, getter 메소드도 만든다. setter 경우는 속성 값이 바뀌면 view 다시 그리게 하도록 invalidate();  ,requestLayout(); 함수를 호출한다.

invalidate() view 다시 그리라는 함수이고 requestLayout() 크기와 위치를 다시 조정하도록 하는 함수이다.

 

 

 

  1. onDraw 통해 그림 그리기

onDraw 함수는 View 자신을 그리도록 호출되는 함수이다.

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.RectF;

import android.os.Handler;

import android.util.AttributeSet;

import android.view.View;

 

public class MyProgressBar extends View {

    private int curValue; //현재값 저장

    private int maxValue; //최대값 저장

    private int lineColor; //진행바 색 저장

 

 

    public MyProgressBar(Context context) {

        super(context);

    }

 

    public MyProgressBar(Context context, AttributeSet attrs) {

        super(context, attrs);

 

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,R.styleable.MyProgressBar,0,0);

 

        try{

            this.curValue = a.getInteger(R.styleable.MyProgressBar_curValue,50);

            this.maxValue = a.getInteger(R.styleable.MyProgressBar_maxValue,100);

            this.lineColor = a.getColor(R.styleable.MyProgressBar_lineColor,0xff000000);

 

        }finally {

 

        }

 

    }

 

 

    public int getLineColor() {

        return lineColor;

    }

 

    public void setLineColor(int color) {

        this.lineColor = color;

        invalidate();

        requestLayout();

    }

 

    public int getCurValue() {

        return curValue;

    }

 

    public void setCurValue(int curValue) {

        this.curValue = curValue;

        invalidate();

        requestLayout();

    }

 

    public int getMaxValue() {

        return maxValue;

    }

 

    public void setMaxValue(int maxValue) {

        this.maxValue = maxValue;

        invalidate();

        requestLayout();

    }

 

 

    Handler mHandler = new Handler();

 

    @Override

    protected void onDraw(Canvas canvas) {

        super.onDraw(canvas);

 

        int width  =  this.getMeasuredWidth();

        int height = this.getMeasuredHeight();

 

 

        // 진행바 그리기 속성

        Paint circle = new Paint();

        circle.setColor(this.lineColor);

        circle.setStrokeWidth(10);

        circle.setAntiAlias(false);

        circle.setStyle(Paint.Style.STROKE);

 

 

        // 진행바 그리기

        canvas.drawArc(new RectF(0,0,width,height),-90,((float)this.curValue/(float)this.maxValue*360),false,circle);

 

 

        // 텍스트 그리기 속성

        Paint textp = new Paint();

        textp.setColor(Color.BLACK);

        textp.setTextSize(100);

        textp.setTextAlign(Paint.Align.CENTER);

 

 

 

        // 짝수초에는 텍스트를 표시하고 홀수초에는 텍스트를 표시하지 않는다.

        // 깜빡깜빡거리는 효과

        if(System.currentTimeMillis()/1000%2==0) {

            // 텍스트 그리기

            canvas.drawText(this.curValue + " / " + this.maxValue, width / 2, height / 2, textp);

        }

 

 

        // 1초후에 invalidate()호출을 통해 해당 View를 다시 그리게 한다.

        // 깜빡깜빡거리는 효과를 위해서 아래 코드 사용

        mHandler.postDelayed(new Runnable() {

            @Override

            public void run() {

 

                invalidate();

            }

        },1000);

 

 

    }

}

canvas 좌표는 View 기준으로하는 좌표이다.(어플리케이션 절대좌표가 아니다.)

그리고 canvas 영역(view영역) 밖으로는 그림을 그려도 표시되지 않는다.

 

1초후에 깜빡깜빡 거리도록 System.currentTimeMillis() 시간을 가져오고 짝수초에는 텍스트를 표시하고 홀수초에는 텍스트를 표시하지 않게 하였다.

 

그리고 handler.postDelayed() 함수를 통해 1초후 invalidate()함수가 호출되고 View 자기자신을 다시 그리게 된다.

 

 

 

 

예제 테스트

실제로 MyProgressBar layout 추가하고 동작하는지 확인해 보겠다.

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    xmlns:custom="http://schemas.android.com/apk/res-auto"

 

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"

    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    tools:context="com.example.shh.test004_myprogressbar.MainActivity"

   >

 

    <com.example.shh.test004_myprogressbar.MyProgressBar

        android:layout_width="300dp"

        android:layout_height="300dp"

        custom:curValue="25"

        custom:maxValue="100"

        custom:lineColor="@color/colorPrimaryDark"

        />

 

</RelativeLayout>

 

 



 

이쁘지는 않지만 얼추 모양은 나왔다.

 

사진을 올릴 수는 없지만 가운데 텍스트는 깜빡깜빡거린다..

 

View 외곽 사각형을 그린다는 깜빡했다.;;

 

 

 

 

 

예제 코드


Test004_MyProgressBar.zip


 

 

 

 

핵심 내용

  1. View 상속 받아 사용할때는 onLayout(), onMeasure() 오버라이드 하지 않아도 된다.(View클래스는 크기와 위치에 대한 행동이 기본적으로 정의되어있다.)
  2. xml 통해 속성을 정의하는 방법과 사용방법
  3. onDraw() 통해 그리는 방법
  4. 깜빡깜빡하는 거와 같은 계속적인 애니메이션을 Handler invalidate() 통해 하는 방법

 

 

 

 

 

예제를 통한 Custom Layout



예제는 위와 같이 레이아웃을 만들어 것이다.

MyLayout 이라고 명명하겠다.

 

MyLayout row, column 라는 속성이이 있다. 위에와 같은 상태라면 3x9 이다.

row 3, column 9이다.

 

그리고 내부에 들어가는 view(예를 들어 버튼) MyLayout 속성인 rowSize, columnSize, rowPosition, columnPosition 값을 사용하여 크기와 위치를 조정하겠다.

 

버튼 1 rowSize= 1, columnSize=1, rowPosition = 0, columnPosition = 0

버튼 2 rowSize = 2, columnSize=2, rowPosition = 1, columnPosition = 1

버튼 3 rowSize = 1, columnSize=1, rowPosition = 4, columnPosition = 0

이다.

 

 

예제는 WPF Grid 갑자기 떠올라서 비슷하게 만들게 되었다.

 

 

  1. Custom Attribute 정의

xml layout 통해 MyProgressBar 속성을 조작하기 위해서는 attribute 우선 정의해야한다.

정의는 res/values/attrs.xml 정의한다.

<resources>

    <declare-styleable name="MyLayout">

        <attr name="rowSize" format="integer"/>

        <attr name="columnSize" format="integer" />

        <attr name="rowPosition" format="integer" />

        <attr name="columnPosition" format="integer" />

    </declare-styleable>

</resources>

rowSize: layout 개수

columnSize: layout 개수

rowPosition: 자식 뷰가 MyLayout 위치할 위치

columnPostion : 자식 뷰가 MyLayout 위치할 위치

 

 

  1. MyLayout 클래스 생성 Attribute 처리하기

실제 처리할 속성은 rowSize columnSize이다.

의문점이 있을 것이다. rowPosition columnPosition 처리하지 않는 것일까?

rowPosition columnPosition MyLayout에서 정의한 속성이지만 실제로는 자식뷰에서 사용할 속성이다. rowPosition columnPosition 정의하지 않은 어떠한 view라도 속성값들을 가질수는 있다. MyLayout 자식 view 설정된 rowPosition columnPosition 보고 위치를 설정할 것이다.

C# 의존 속성과 비슷한 개념이다.

 

 

우선 클래스 생성  

  • public MyLayout(Context context)
  • public MyLayout(Context context, AttributeSet attrs)
  • protected void onLayout(boolean changed, int l, int t, int r, int b)

3개는 필수로 Override 해야한다.

 

그리고 MyLayout 자식 view MyLayout 속성(rowPosition, columnPosition) 가져올 있도록 하기 위해

  • LayoutParams inner class 만들어야 한다.
  • public LayoutParams generateLayoutParams(AttributeSet attrs)
  • protected LayoutParams generateDefaultLayoutParams() Override
  • protected ViewGroup.LayoutParams generateLayoutParams Override
  • protected boolean checkLayoutParams(ViewGroup.LayoutParams p) Override

이렇게 하면 레이아웃 파라미터에 사용자 정의 속성을 가져올 있다.

 

 

public class MyLayout extends ViewGroup{

 

    private int rowSize;

    private int columnSize;

 

 

    public MyLayout(Context context) {

        super(context);

    }

 

    public MyLayout(Context context, AttributeSet attrs) {

        super(context, attrs);

 

        TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.MyLayout,0,0);

 

        try{

            this.rowSize =a.getInteger(R.styleable.MyLayout_rowSize,1);

 

            this.columnSize =a.getInteger(R.styleable.MyLayout_rowSize,1);

 

        }finally {

            a.recycle();

        }

 

 

 

 

 

 

 

    }

 

    @Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

 

    }

 

 

    /**

     * 자식 view 에 사용자 정의 레이아웃 파라미터를 정의하고 얻기위한 것이다.

     *

    * */

 

 

 

    @Override

    public LayoutParams generateLayoutParams(AttributeSet attrs) {

        return new MyLayout.LayoutParams(getContext(), attrs);

    }

 

    @Override

    protected LayoutParams generateDefaultLayoutParams() {

        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

    }

 

    @Override

    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {

        return new LayoutParams(p);

    }

 

    @Override

    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {

        return p instanceof LayoutParams;

    }

 

 

 

    /**

     *  자식 view 당 사용자 정의 레이아웃 정보

     *  이것으로 자식 view에 사용자가 정의한 속성값을 얻을 수 있다.

     */

    public static class LayoutParams extends MarginLayoutParams {

 

 

        public int rowPosition ;

        public int columnPosition ;

 

        public LayoutParams(Context c, AttributeSet attrs) {

            super(c, attrs);

 

            // Pull the layout param values from the layout XML during

            // inflation.  This is not needed if you don't care about

            // changing the layout behavior in XML.

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.MyLayout);

            try {

                rowPosition = a.getInt(R.styleable.MyLayout_rowPosition, 0);

                columnPosition = a.getInt(R.styleable.MyLayout_columnPosition, 0);

            }finally {

                a.recycle();

            }

 

        }

 

        public LayoutParams(int width, int height) {

            super(width, height);

        }

 

        public LayoutParams(ViewGroup.LayoutParams source) {

            super(source);

        }

    }

}

 

코드를 보면 LayoutParams 위한 코드가 중요하다. 이를 통해 있는 것은 사용자가 정의한 레이아웃의 자식뷰들은 부모가 정의한 속성을 설정하고 얻을 있다는 것이다.

MyLayout으로 예를 들자면 자식뷰들은 rowPosition 대해서 속성이 정의 되어 있지 않아도 부모가 정의한 rowPosition 사용할 있고 부모는 자신이 만든 LayoutParams 자식뷰에게 전달하므로 부모는 자식에 사용된 rowPosition값을 얻을 있다.

 

 

 

 

  1. onMeasure, onLayout 처리

 @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

 

 

        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);

        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);

 

 

        double cellWidth= maxWidth/this.columnSize;

        double cellHeight= maxHeight/this.rowSize;

 

 

        final int count = getChildCount();

 

        for (int i = 0; i < count; i++) {

            final View child = getChildAt(i);

            final LayoutParams lp = (LayoutParams)child.getLayoutParams();

 

 

            /*자식뷰 크기지정*/

            /*고정크기로 설정하기 위해 MeasureSpec.EXACTLY 사용 */

            child.measure(MeasureSpec.makeMeasureSpec((int)(cellWidth*lp.columnSize),MeasureSpec.EXACTLY)

            ,MeasureSpec.makeMeasureSpec((int)(cellHeight*lp.rowSize),MeasureSpec.EXACTLY));

 

        }

 

        setMeasuredDimension(maxWidth,maxHeight);

    }

 

    @Override

    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int maxWidth = getMeasuredWidth();

        int maxHeight = getMeasuredHeight();

 

        double cellWidth= maxWidth/this.columnSize;

        double cellHeight= maxHeight/this.rowSize;

 

 

        final int count = getChildCount();

 

        for (int i = 0; i < count; i++) {

            final View child = getChildAt(i);

            final LayoutParams lp = (LayoutParams)child.getLayoutParams();

 

            int cl = (int)(cellWidth*lp.columnPosition);

            int ct = (int)(cellHeight*lp.rowPosition);

            int cr = (int)(cl + cellWidth * lp.columnSize);

            int cb = (int)(ct + cellHeight * lp.rowSize);

 

 

            /*자식뷰 위치지정*/

            child.layout(cl,ct,cr,cb);

 

        }

 

 

 

 

 

 

 

 

 

 

예제 테스트

 테스트1

 

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"

    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    tools:context="com.example.shh.mylayout.MainActivity"

  >

 

 

 

        <com.example.shh.mylayout.MyLayout

            android:layout_width="fill_parent"

            android:layout_height="fill_parent"

            xmlns:custom="http://schemas.android.com/apk/res-auto"

            custom:rowSize="6"

            custom:columnSize="6" >

 

            <TextView

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="Hello World!"

                custom:rowSize="1"

                custom:columnSize="2"

                custom:columnPosition="1"

                custom:rowPosition="0"

 

                />

            <Button

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                custom:rowSize="1"

                custom:columnSize="1"

                custom:columnPosition="5"

                custom:rowPosition="1"

 

                />

 

            <CalendarView

                android:layout_width="match_parent"

                android:layout_height="match_parent"

                custom:rowSize="3"

                custom:columnSize="3"

                custom:columnPosition="2"

                custom:rowPosition="3"

                >

            </CalendarView>

 

        </com.example.shh.mylayout.MyLayout>

 

 

</RelativeLayout>

 



 

 

 

테스트2

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:paddingBottom="@dimen/activity_vertical_margin"

    android:paddingLeft="@dimen/activity_horizontal_margin"

    android:paddingRight="@dimen/activity_horizontal_margin"

    android:paddingTop="@dimen/activity_vertical_margin"

    tools:context="com.example.shh.mylayout.MainActivity"

  >

 

 

 

        <com.example.shh.mylayout.MyLayout

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            xmlns:custom="http://schemas.android.com/apk/res-auto"

            custom:rowSize="6"

            custom:columnSize="6" >

 

            <TextView

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="Hello World!"

                android:background="#ffff00"

                custom:rowSize="1"

                custom:columnSize="2"

                custom:columnPosition="1"

                custom:rowPosition="0"

 

                />

            <Button

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:background="@color/colorAccent"

                custom:rowSize="1"

                custom:columnSize="1"

                custom:columnPosition="3"

                custom:rowPosition="0"

 

                />

            <Button

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:background="@color/colorPrimary"

                custom:rowSize="1"

                custom:columnSize="1"

                custom:columnPosition="4"

                custom:rowPosition="0"

 

                />

 

            <CalendarView

                android:layout_width="match_parent"

                android:layout_height="match_parent"

                custom:rowSize="3"

                custom:columnSize="3"

                custom:columnPosition="0"

                custom:rowPosition="2"

                >

 

            </CalendarView>

 

        </com.example.shh.mylayout.MyLayout>

 

</RelativeLayout>

 



 

 

예제 코드


MyLayout.zip


 

 

 

 

핵심 내용

  1. layout custom attribute 자식뷰에 설정하고 값을 얻기 위해서는
  • LayoutParams inner class 만들어야 한다.
  • public LayoutParams generateLayoutParams(AttributeSet attrs)
  • protected LayoutParams generateDefaultLayoutParams() Override
  • protected ViewGroup.LayoutParams generateLayoutParams Override
  • protected boolean checkLayoutParams(ViewGroup.LayoutParams p) Override

  1. onMeasure() , onLayout() 구현해야한다.
  2. 필요하다면 onDraw() 구현해야한다.






드디어 Custom Layout도 올렸습니다. 귀차니즘 때문인지 코드와 설명에 배려가 부족합니다. 이해해주세요.



 

 

 

 

 

정말 설명과 예제가 너무 엉성해서 글을 읽는 분이 있다면 너무 미안합니다. 고등교육과 대학교육을 받았는데도 불구하고 맞춤법과 글쓰기가 어려운 초등학생 수준의 글재주를 가지고 있습니다. 글을 계속 써도 나아지질 않네요;; 노력이 부족한가 봅니다.

  1. 1466265016 2016.06.19 00:50

    제 블로그도 놀러오세요~

  2. 1467034511 2016.06.27 22:35

    좋은글 감사

  3. 하하하 2016.06.30 21:30

    안녕하세요! 좋은글 잘 읽었습니다~
    질문 하나만 드려도 될는지 ㅠㅠㅠ

    Questin : View의 method 호출 순서 그림에서 invalidate()의 흐름은 requestLayout()의 흐름에 전부 포함 되는 것으로 보이는데요,
    왜 setter에서 view를 갱신할 때 두 함수를 모두다 호출 하는지 궁금합니다. 흐름대로라면 requestLayout()만 호출하면 될것 같아서요...ㅎㅎ

    • 단세포소년 2016.07.01 00:07 신고

      말씀과 같이 루틴에 따라 화면만 갱신하는 것이라면 invalidate(), 레이아웃이 변해야하는 경우 requestLayout()을 호출하시면 됩니다. 둘다의 상황이라면 둘다 호출하시면 됩니다. UI와 상관없는 값이라면 둘다 호출 안하시면 됩니다. 위의 예제에서 값에 따라 텍스트의 길이가 달라지고 속성(width, height의 fill_parent, wrap_contents)에 따라 크기가 변경될수 있기 때문에 requestLayout()을 호출하고 있습니다. android 4.0 부터는 layout 변경사항이 없을때는 requestLayout() 을 호출해도 루틴을 동작하지 않아 오버헤드를 최소화한다고 들었습니다.(확실한 정보는 아닙니다.)

    • 하하하 2016.07.01 11:32

      아하 그렇군요!
      많이 배워갑니닷 감사해요 ㅎㅎㅎ

  4. ㅈㄷㅂㅈㄷ 2018.04.23 14:27

    감사합니다


내가 제목과 같은 "activity in activity 혹은 activity in fragment 혹은 태블릿에서 fragment 를 이용하여 mapview 여러개 뛰우기" 를 찾게 된 이유는 태블릿 개발을 하고 있는데 main activity는 하나이고 fragment 를 이용하여 맵뷰를 띄우고 있는데 맵뷰를 띄울 곳이 이곳 저곳 많았다. mapview 는 mapactivity 에 하나만 존재해야한다는 제약이 있는데 이 때문에 어떻게 해야 mapview를 여러개 띄울지 고민이었다.

현재 내가 찾은 내용은 정리가 덜 되었지만 아이디어는 이것이다.
바로 tabhost, tabwidget ~~~~~

안드로이드를 어느정도 알고 있는 사람이라면 무릎을 딱 칠것이다.

tabhost 는 content로 activity를 가질수 있다는 사실을...

아래 코드를 보고 아이디어를 얻을수 있도록 한다...
정말 구글에서 미친듯이 찾다가 아닌가 싶었던 자료를 다시 보았는데 아래의 아이디어를 넣었다.
마구잡이로 찾던 자료라 출처가 어디인지 불분명하다.. 구글 프로젝트 그룹이었던 것으로 기억하는데..
다시 찾아서 출처를 꼭 남기겠다.

글쓰다가 출처를 찾았다.

출처 : https://github.com/petedoyle/android-support-v4-googlemaps/tree/dev-samples/src/samples/FragmentLayoutMaps

이곳에서 예제 샘플을 받아서 분석하도록..

mTabHost = (TabHost)view.findViewById(android.R.id.tabhost);
        mTabHost.setup(getLocalActivityManager());
        TabSpec tab = mTabHost.newTabSpec("map")
                              .setIndicator("map")
                              .setContent(new Intent(getActivity(), MyMapActivity.class));
       
        mTabHost.addTab(tab);        
        tab = mTabHost.newTabSpec("map2")
                .setIndicator("map2")
                .setContent(new Intent(getActivity(), MyMapActivity.class));
        
 
        
        mTabHost.addTab(tab);    


액티비티를 다이얼로그 형식으로 사용했으면 좋겠다라고 생각할 경우가 있습니다.
방법은 간단합니다.

1. themes.xml 에 다이얼로그 style을 정의해 둔다.
2. manifest에서 다이얼로그처럼 사용할 액티비티 속성으로 테마 스타일을 추가한다.
3. 액티비티를 실행시킨다.


1. themes.xml파일에 다이얼로그 style 정의

우선 res/values 폴더에 themes.xml 을 만듭니다.
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Dialog" parent="android:style/Theme.Dialog">
<item name="android:windowBackground">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
</style>
</resources>

여기서 설정된 것들은, 기본으로 깔리는 까만색 bakcground를 사용하지 않는것 타이틀바를 없애는것 등입니다.
이 스타일의 이름은 Dialog 로 정의되어 있으므로 매니페스트파일에서 사용시에는
android:theme="@android:style/Theme.Dialog" 로 사용할 수 있게 됩니다.




2. manifest에서 다이얼로그처럼 사용할 액티비티 속성으로 테마 스타일을 추가한다.

이제 매니페스트에서 코드를 추가해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
package="me.croute.dialogactivity"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name" >
<activity android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DialogActivity" android:theme="@android:style/Theme.Dialog" />
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>

기본 액티비티로 MainActivity 가 존재하고, 다이얼로그로 사용할 액티비토로 DialogActivity를 추가했습니다.
다이얼로그 액티비티의 테마로는 위에서 만든 다이얼로그 스타일을 사용해줍니다.

이렇게 설정해 두면
어디서든 아래의 코드를 사용해서 다이얼로그로 보이는 액티비티를 부를 수 있게 됩니다.
startActivity(new Intent(this, DialogActivity.class));


 
 
  1. 이@희 2013.06.17 06:21

    아주 도움이 많이 되었습니다! 정말감사합니다 ㅎㅎㅎ

 

Double click/tap detection on android's MapView

If there is a cleaner way to do it, please share :)

1. Override the default MapView with your own implementation;
2. Override the onInterceptTouchEvent method;
3. Check if the last event was also a click and happened close by (say in the last 250ms);
3.1. If so, it’s a double tap; do whatever you want (in this case I zoom in on the last clicked point);
3.2. If not, ignore :)

Here’s the code (Sorry for the formatting mess, but... :)):

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import com.google.android.maps.MapView;

public class MyMapView extends MapView {

private long lastTouchTime = -1;

public MyMapView(Context context, AttributeSet attrs) {

super(context, attrs);
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

if (ev.getAction() == MotionEvent.ACTION_DOWN) {

long thisTime = System.currentTimeMillis();
if (thisTime - lastTouchTime < 250) {

// Double tap
this.getController().zoomInFixing((int) ev.getX(), (int) ev.getY());
lastTouchTime = -1;

} else {

// Too slow :)
lastTouchTime = thisTime;
}
}
  1. 산소희박 2013.04.03 13:16

    간단하게 더블탭 구현하고 있었는데 잘보고 갑니다^^


ontouchlistener 구현시 그냥 구현하게 되면 클릭부터 스크롤 심지어는 롱클릭 더블클릭등을 다 구현해야한다.

그래서 관련자료를 찾다가 발견하게된 것이 있어서 퍼왔다.

아래는 퍼온내용이다.





[Intro]

어플리케이션 개발을 하다보면 반드시 해야하는 것이 모션 이벤트 처리 입니다.

터치 이벤트 같은 것들은 DOWN - MOVE - UP의 단계를 거치면서

사용자가 어떤 동작을 입력 하는지 감지 할 수 있습니다.

이 입력의 어떤 조합으로 사용자가 어떤 동작을 했는지 감지 할 수 있겠죠.

하지만 직접 이런 제스쳐들을 구현하기란 쉬운 일만은 아닙니다. (무엇보다 귀찮죠~)

그래서 Android에서는 GestureDetector라는 클래스를 아얘 제공합니다.

[About GestureListener]

GestureDetector는 두 가지 Listener를 가지고 있습니다.

interface GestureDetector.OnDoubleTapListener
interface GestureDetector.OnGestureListener

http://developer.android.com/reference/android/view/GestureDetector.html

자세한 설명은 Reference를 보시면 됩니다.

OnDoubleTapListener는 이름 그대로 두번 터치 했을 때,

OnGestureListener는 일반적인 제스쳐들, 한번 터치나 스크롤 관련 Listner입니다.

그리고 저 두 가지 interface를 모두 가진 녀석이 있습니다.

class GestureDetector.SimpleOnGestureListener

보통 SimpleOnGestureListener만 extends 하면 모든 제스쳐를 다 사용 할 수 있습니다.

[Usage of GestureDetector]

사용법도 매우 간단합니다.

GestureDetector를 만들기만 하면 땡이죠.

mGestureDetector = new GestureDetector(this, new SimpleGestureListener());
mGestureDetector.onTouchEvent(event);

음... 너무 뜬금 없는 코드인가요? 일단 아주 간단하게 적어 봤습니다.

1. GestureDetector를 만들 때 GestureListener를 등록 하고

2. 감시할 MotionEvent를 onTouchEvent에 넣어 주면 GetstureListener가 호출이 되는 구조 입니다.

좀 더 자세하게 살펴 보면,

private final class SimpleGestureListener
extends GestureDetector.SimpleOnGestureListener {
// Implementation
}

private GestureDetector mGestureDetector;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mGestureDetector = new GestureDetector(this, new SimpleGestureListener());
}

@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}

위와 같습니다.

[Analyze Gestures]

사용법은 간단하지만 제스쳐는 그리 간단하지 않습니다.

그래서 각 제스쳐에 대해서 분석을 해봤습니다.

public boolean onDoubleTap(MotionEvent e)
public boolean onDoubleTapEvent(MotionEvent e)
public boolean onDown(MotionEvent e)
public boolean onFling(MotionEvent e1, MotionEvent e2, float vX, float vY)
public void onLongPress(MotionEvent e)
public boolean onScroll(MotionEvent e1, MotionEvent e2, float dX, float dY)
public void onShowPress(MotionEvent e)
public boolean onSingleTapConfirmed(MotionEvent e)
public boolean onSingleTapUp(MotionEvent e)

일단 제스쳐 이벤트에 대해서 간단히 살펴 봅시다.

onDoubleTap은 두 번 터치입니다.

onDoubleTap은 두 번 터치하면 이벤트가 더 이상 발생 되지 않지만,

onDoubleTapEvent는 DOWN, MOVE, UP 이벤트를 모두 포함 합니다. (나중에 살펴 보겠습니다.)

onDown은 터치하려고 손을 대기만 해도 발생되는 이벤트며,

모든 제스쳐의 시작입니다.

onFling은 onScroll에서 끝을 살짝 튕기는 동작에서 발생하는 이벤트며,

onScroll은 말 그대로 스크롤 시에 발생하는 이벤트 입니다.

onLongPress는 길게 눌렀을 때 발생 하는 이벤트며,

onShowPress는 onLongPress보다는 좀 더 짧은 시간동안 누르고 있으면 발생 하는 이벤트 입니다.

onSingleTap은 한 번 터치 했을 때 발생하는 이벤트며,

onSingleTabConfirmed는 한 번 터치 하고 다음에 다시 터치 이벤트가 들어오지 않았을 때,

한 번 터치가 확실 하다고 확인 시켜주는 이벤트 입니다.

자... 이제 시작입니다! 이건 간단히 살펴 본것 밖에 안되는...

본격적으로 실제 제스쳐에 따라서 어떤 이벤트가 호출 되는지 살펴 보겠습니다.

시간은 로그를 통해 한번만 계산된 시간이며,

대략 그 시간 범위에서 이벤트가 발생 한다고 보시면 됩니다.

1. 아주 살짝 터치

000ms onDown : ACTION_DOWN
060ms onSingleTapUp : ACTION_UP
306ms onSingleTapConfirmed : ACTION_DOWN

일단 손을 대면 무조건 onDown 이벤트 발생입니다.

살짝 터치를 하게 되면 보통 30~60ms 정도 후에 손이 떨어지게 됩니다.

손이 떨어 지면 onSingleTapUp 이벤트가 발생하며,

onDown이벤트 발생 후 약 300ms 안에 다시 onDown 이벤트가 발생 하지 않는다면

onSingleTapConfirmed 이벤트가 발생하여 확실히 한 번 터치 되었다는 이벤트를 발생 시킵니다.

2. 살짝 터치

000ms onDown : ACTION_DOWN
097ms onShowPress : ACTION_DOWN
172ms onSingleTapUp : ACTION_UP
303ms onSingleTapConfirmed : ACTION_DOWN

역시나 onDown 이벤트 부터 시작입니다.

1번 보다는 살짝 길게, 약 90~100ms 정도 터치되면 onShowPress 이벤트가 발생합니다.

172ms 이후에 손을 떼었으며,

역시나 300ms 정도 지나면 onSingleTapConfirmed 이벤트가 발생 됩니다.

3. 약간 길게 터치

000ms onDown : ACTION_DOWN
103ms onShowPress : ACTION_DOWN
460ms onSingleTapUp : ACTION_UP

2번보다 좀 더 길지만, LongPress는 아닌 상황입니다.

역시 이 때도 약 100ms 정도에 onShowPress 이벤트가 발생하긴 하지만

300ms 이후에 손을 떼었기 때문에 onSingleTapConfirmed 이벤트가 먹히는 현상이 일어납니다.

4. 아주 길게 터치

000ms onDown : ACTION_DOWN
096ms onShowPress : ACTION_DOWN
590ms onLongPress : ACTION_DOWN

LongPress가 발생 하는 상황입니다.

100ms 정도에 onShowPress 이벤트가 발생 하며,

590~600ms 정도에 onLongPress 이벤트가 발생 합니다.

이때 onSingleTapUp 이벤트는 발생 하지 않습니다.

두 번 터치 하는 경우에는 이벤트가 두 가지이기 때문에,

두 가지 조합의 경우 모두 살펴 보겠습니다.

5. 두 번 터치 (onDoubleTap)

000ms onDown : ACTION_DOWN
060ms onSingleTapUp : ACTION_UP
140ms onDoubleTap : ACTION_DOWN
140ms onDown : ACTION_DOWN (지연 될 수 있음)

먼저 onSingleTapUp 이벤트가 발생 합니다.

그리고 onSingleTapConfirmed 가 발생 하기 전에

다시 onDown 이벤트가 들어오게 되면 onDoubleTap 이벤트가 발생 합니다.

참고로 두번째 들어오는 onDown 이벤트는 onDoubleTap 이벤트보다 늦게 들어 올 수 있습니다.

(항상 같이 들어 오는게 아니라 onDoubleTap이 먼저 발생 합니다.)

6. 두 번 터치 (onDoubleTapEvent)

000ms onDown : ACTION_DOWN
041ms onSingleTapUp : ACTION_UP
130ms onDoubleTapEvent : ACTION_DOWN
130ms onDown : ACTION_DOWN (지연 될 수 있음)
190ms onDoubleTapEvent : ACTION_UP

onDoubleTap 이벤트와의 차이는 DOWN, MOVE, UP 이벤트까지 모두 캐치된다는 점이며,

마지막에 onDoubleTapEvent에 UP 액션이 들어오는 것을 확인 할 수 있습니다.

(위의 경우, 190ms 이후에 두번째 터치에서 손이 떨어졌다는 것을 확인 할 수 있습니다.)

7. 두 번 터치 (onDoubleTap + onDoubleTapEvent)

000ms onDown : ACTION_DOWN
080ms onSingleTapUp : ACTION_UP
200ms onDoubleTap : ACTION_DOWN
200ms onDoubleTapEvent : ACTION_DOWN (지연 될 수 있음)
200ms onDown : ACTION_DOWN (지연 될 수 있음)
260ms onDoubleTapEvent : ACTION_UP

같이 사용 하게 되면 onDoubleTap, onDoubleTapEvent 이벤트 둘 다 발생하며,

항상 onDoubleTap 이벤트가 먼저 발생 하게 됩니다.

(결과적으로 onDoubleTap - onDoubleTapEvent - onDown 순서로 발생 합니다.)

8. 두 번 터치, 두 번째 터치시 스크롤 (onDoubleTapEvent)

000ms onDown : ACTION_DOWN
080ms onSingleTapUp : ACTION_UP
179ms onDoubleTapEvent : ACTION_DOWN
179ms onDown : ACTION_DOWN (지연 될 수 있음)
280ms onDoubleTapEvent : ACTION_MOVE
289ms onShowPress : ACTION_DOWN

290ms onDoubleTapEvent : ACTION_MOVE
...
779ms onLongPress : ACTION_DOWN
800ms onDoubleTapEvent : ACTION_UP

평소에 절대 나올법한 제스쳐지만 onDoubleTapEvent의 특성을 살펴보기 위한 제스쳐 입니다.

두 번째 onDown 이벤트 이후에 MOVE 이벤트가 들어 오는 것을 확인 할 수 있으며,

한가지 특이한 점은 계속 스크롤 되지 않고 onLongPress 이벤트가 발생하면 끝난다는 점입니다.

손을 뗄 수 밖에 없는 상황이 오게 되죠.

위에서 보시다 시피,

조금 길게 눌러짐에 따라 onShowPressonLongPress가 도중에 발생 할 수도 있습니다.

9. 스크롤

000ms onDown : ACTION_DOWN
030ms onScroll : ACTION_DOWN, ACTION_MOVE
...

스크롤 이벤트는 간단 합니다.

최소 30ms 이후 부터는 onScroll 이벤트가 발생 할 수 있으며,

플링시키지 않고 살며시 손을 떼면 끝까지 onScroll 이벤트만 연속으로 발생 합니다.

10. 플링

000ms onDown : ACTION_DOWN
030ms onScroll : ACTION_DOWN, ACTION_MOVE
...
900ms onFling : ACTION_DOWN, ACTION_UP

마지막에 손가락을 슬며시 튕기는 플링 동작입니다.

스크롤 이벤트와 비슷하지만, 마지막에 UP 액션과 함께 onFling 이벤트가 동작합니다.

스크롤과 플링 제스쳐 모두 시간에 따라 onShowPress 이벤트가 발생 할 수 있습니다.

[Outro]

Android에 기본으로(API Level 1) 들어 있는 GestureDetector에 대해서

조금은 자세하게 알아 봤습니다.

그냥 막연하게 이벤트 이름만 보고서 프로그래밍을 하기 보다는,

정확하게 어떻게 동작이 되는지 확인 하고 프로그래밍을 하면

좀 더 자신이 원하는 제스쳐를 캐치하여 좀 더 나이스한 어플리케이션을 만들 수 있을 것입니다.

출처 : http://blog.naver.com/PostView.nhn?blogId=visualc98&logNo=94697746&viewDate=&currentPage=1&listtype=0&userTopListOpen=false&userTopListCount=5&userTopListManageOpen=false&userTopListCurrentPage=undefined





아래는 내가 테스트해보기 위해 만든 간단한 코드이다. onCreate 부분

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
      View v= findViewById(R.id.mapview);
      
       
       
        final   GestureDetector g = new GestureDetector(new OnGestureListener() {
   
   public boolean onSingleTapUp(MotionEvent e) {
    Log.w("shh", "onSingleTapUp");
    return true;
   }
   
   public void onShowPress(MotionEvent e) {
    // TODO Auto-generated method stub
    Log.w("shh", "onShowPress");
   }
   
   public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
     float distanceY) {
    Log.w("shh", "onScroll");
    return true;
   }
   
   public void onLongPress(MotionEvent e) {
    Log.w("shh", "onLongPress");
    
   }
   
   public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
     float velocityY) {
    Log.w("shh", "onFling");
    return true;
   }
   
   public boolean onDown(MotionEvent e) {
    Log.w("shh", "onDown");
    return true;
   }
  });
          
   v.setOnTouchListener(new OnTouchListener() {
   
   public boolean onTouch(View v, MotionEvent event) {
    
    return g.onTouchEvent(event);
   }
  });
  
}


인텐트

  • 컴포넌트 들 간에 작업요청 및 데이터를 전달하는 메시지
  • 작업요청 컴포넌트는 인텐트를 보내기 위해 다음 메서드 호출
    • startActivity()
    • startService()
  • 수신 컴포넌트
    • 인텐트 필터 intent filter 를 통해 특정 인텐트를 수신하고 작업을 수행

인텐트의 생성 주체

  • 안드로이드시스템
  • 안드로이드 프레임워크
  • 애플리케이션

인텐트 전송메서드

  • 컴포넌트 별로 별도의 메서드 존재
  • 액티비티
    • Context.startActivity()
    • Activity.startActivityForResult()
  • 서비스
    • Context.startService() - 초기화 또는 새로운 지시
    • Context.bindService() - 서비스와의 연결
  • 브로드캐스트리시버
    • 브로드캐스팅 메서드 - 리시버에게 메시지를 보내는 메서드 들임
      • Context.sendBroadcast()
      • Context.sendOrderedBroadcast()
      • Context.sendStickyBroadcast()

인텐트 객체의 구성 요소

  • 컴포넌트이름 / 액션(Action) / 카테고리(Category) / 데이터(Data) / 데이터 타입 (MIME Type) / 엑스트라(Extras)
  • 컴포넌트 이름
    • 클래스 이름 또는 매니페스트 파일에서 설정된 패키지 이름과 클래스 이름의 조합
    • 컴포넌트 이름을 지정하는 인텐트를 '명시적인텐트'라 함
    • 인텐트 객체는 지정한 클래스의 인스턴스를 생성하고 곧바로 전달
    • 컴포넌트 이름 설정 - setComponent() , setClass() , setClassName()
    • 컴포넌트 이름 읽기 - getComponent()
  • 액션 - 액티비티액션 / 브로드캐스트액션
    • 인텐트액션은 매니페스트 화일의 인텐트 필터와 연관된다. ACTION_MAIN은 'android.intent.action.MAIN'으로 사용
    • 인텐트객체의 액션 설정 - setAction()
    • 인텐트객체의 액션 읽기 - getAction()
    • 액티비티액션
      • ACTION_MAIN - 시작하는 액티비티 지정
      • ACTION_VIEW - URI가 가리키는 데이터를 사용자에게 보여줌
      • ACTION_EDIT - URI가 가리키는 데이터를 편집
      • ACTION_DELETE - URI가 가리키는 데이터를 삭제
      • ACTION_DEFAULT - view와 같음
      • ACTION_PICK - 데이터에서 URI가 가리키는 정보를 반환
      • ACTION_GET_CONTENT - 데이터에서 하나의 콘텐트를 선택하여 반환
      • ACTION_RUN - 데이터를 실행
      • ACTION_INSERT - 빈 아이템 작성
      • ACTION_CALL - 전화연결 요청, 초기화 작업 추가되어 있음, 긴급전화 안됨
      • ACTION_DIAL - 전화연결 요청, 주로 사용
      • ACTION_SENDTO - 데이터의 메시지를 보내라
      • ACTION_ANSWER - 전화 착신에 관한 액션
      • ACTION_SYNC - 폰과 서버의 데이터를 동기화
    • 브로드캐스트액션
      • ACTION_BATTERY_CHANGED - 배터리 잔량의 변화를 알림
      • ACTION_BATTERY_LOW - 배터리 부족 경고
      • ACTION_BOOT_COMPLETED - 시스템 부팅 완료 알림
      • ACTION_DATE_CHANGE - 날짜 변경 알림
      • ACTION_HEADSET_PLUG - 헤드셋이 기기에 연결 또는 분리되었음을 알림
      • ACTION_PACKAGE_ADDED - 어플리케이션 패키지 추가 알림
      • ACTION_PACKAGE_REMOVED - 어플리케이션 패키지 제거 알림
      • ACTION_SCREEN_ON - 스크린이 켜졌음을 알림
      • ACTION_TIMEZONE_CHANGED - 타임존 변경시 알림
      • ACTION_TIME_CHANGED - 시간 지정시 알림
      • ACTION_TIME_TICK - 매시간 변경시 알림
  • 카테고리
    • 액션을 수신하여야 할 컴포넌트의 종류에 대한 정보를 포함하는 문자열
    • 몇가지 카티고리 상수를 제공
      • CATEGORY_BROWSABLE - 타겟 액티비티는 링크에 의해 참조되는 데이터를 보여주기 위해 호출되는 부라우져여야 한다.
      • CATEGORY_HOME - 홈화면을 보여주는 액티비티가 되어야 한다.
      • CATEGORY_LAUNCHER - 액티비티는 하나의 태스크에서 최초로 생성된 액티비티가 되며, 최상위 계층의 어플리케이션으로 시작된다.
      • CATEGORY_PREFERENCE - 타깃 액티비티는 설정 프리퍼런스이다.
  • 데이터
    • 처리할 데이터에 대한 URI와 MIME 타입
    • 액션이 'ACTION_EDIT' 이면, 데이터 필드는 편집을 위한 문서의 URI
    • 액션이 'ACTION_DIAL'이면, 데이터 필드는 전화번호를 가진 'tel:' URI
    • 데이터 타입을 URI만으로 판단하기 어려운 경우 MIME 제공
      • setData() : URI 지정
      • getData() : URI 읽기
      • setType() : MIME 지정
      • getType() : MIME 읽기
      • setDataAndType() : URI, MIME 동시 지정
  • 엑스트라
    • 엑스트라에 필요한 값을 입력하면 안드로이드는 엑스트라 값을 번들객체로 변환
    • 번들객체는 인텐트를 사용하여 컴포넌트를 호출할 때 onCreate() 콜백 메서드의 파라미터로 전달된다.
      • protected void onCreate(Bundle savedInstanceState)
    • 번들객체는 HashMap<String,Object> 데이터 타입
    • putExtra()
      • putExtra(EXTRA_NAME,'이름')
      • getStringExtra(EXTRA_NAME)
      • getIntExtra(EXTRA_INT)
      • getBooleanExtra(EXTRA_BOOLEAN)
  • 플래그
    • 액티비티를 시작시키는 방법, 시작 이후 액티비티를 다루는 방법
    • intent.addFlag(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - 이건 호출되는 액티비티가 존재하면 새로운 액티비티 인스턴스를 생성하지 말고 현재 스택에 존재하는 액티비티의 인스턴스를 프로세스 스택의 최상단으로 위치시켜 포그라운드로 만들어라는 플래그
    • intent.addFlag(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - 안드로이드에게 호출되었던 액티비티가 존재한다면 시작 시 자신의 상위 액티비티들을 모두 제거시켜라

펜딩인텐트

  • 한 컴포넌트가 다른 컴포넌트에 작업을 요청하는 인텐트를 사전에 만들어 놓는다. 특정 시점에 자신이 아닌 다른 컴포넌트들이 펜딩인텐트를 사용하여 다른 컴포넌트에 작업을 요청하는데 사용된다.
  • 예] 다운로드 서비스 - 상태바에 노티피케이션 아이콘을 만들고, 다운로드 완료되면 아이콘으로 완료 표시 / 상태바를 확장하면 나타나는 화면을 노티피케이션리스트 또는 확장메시지라 하는데 여기에 있는 완료메시지를 클릭하면 노티피케이션은 사전에 서비스에서 작성하여 놓은 펜딩인텐트를 사용하여 다운로드된 파일을 읽을 수 있는 어플리케이션을 호출하여 파일을 재생한다. 즉, 인텐트 작성은 다운로드를 하는 '서비스'가 하고 인텐트 사용은 '노티피케이션'이 하고 결과적으로 다운로드 결과를 활용할 다른 어플리케이션이 실행된다.

    출처 : http://blog.naver.com/hojoon108?Redirect=Log&logNo=80140497011

  XmlPullParserFactory parserCreator = XmlPullParserFactory.newInstance();
  XmlPullParser parser = parserCreator.newPullParser();
  parser.setInput(xmlInput, "utf-8");
  parser.next();
  
  int parserEvent = parser.getEventType();
  while(parserEvent !=XmlPullParser.END_DOCUMENT)
  {

   switch(parserEvent)
   {
   case XmlPullParser.START_DOCUMENT:
    break;
   case XmlPullParser.START_TAG:
    break;
   case XmlPullParser.END_TAG:
    break;
   case XmlPullParser.TEXT:
    break;
   }
   
   parserEvent=parser.next();
  }


이게 기본 뼈대이다.
XmlPullParser 는 문서를 순차적으로 읽으며 이벤트를 발생시킨다.

START_DOCUMENT 이벤트는 문서의 시작을
END_DOCUMENT 은 문서의 끝을
START_TAG는 태그의 시작을 (예 : <data> )
END_TAG는 태그의 끝을 (예 : </data> )
TEXT는 태그의 시작과 끝 사이에서 나타난다. (예 : <data>여기서 TEXT 이벤트 발생</data> )

XmlPullParser는 순차적으로 문서를 읽어가면서 이벤트를 발생시키므로 뒤로 돌아가지 못하는 문제가 있다.

그래서 이를 해결하기 위해
XmlPullParser.START_TAG 이벤트가 발생하면 임시변수(String)에 Tag값을 저장하고
XmlPullParser.TEXT이벤트에서 임시변수에 저장된 Tag값을 확인하여 적절한 변수에 값을 넣어야한다.


주의 : XmlPullParser.START_TAG와 XmlPullParser.END_TAG 에서는 getName()을 사용하여야 하고 XmlPullParser.TEXT에서는 getText()를 사용하여야한다. 그렇지 않으면  null값을 반환한다.
XmlPullParser.TEXT 이벤트는 태그의 택스트 영역에 문자가 존재하지 않아도 발생한다.
<data attribute=1 >text</data> 이와 같이 태그 안에 속성이 존재할때는 XmlPullParser.START_TAG 이벤트가 발생했을 때 속성 값을 추출하라.


예제
String preName=null;    //임시변수 생성
  
  int parserEvent = parser.getEventType();
  while(parserEvent !=XmlPullParser.END_DOCUMENT)
  {

   switch(parserEvent)
   {
   case XmlPullParser.START_DOCUMENT:
    break;
   case XmlPullParser.START_TAG:
    preName = parser.getName();  //임시변수에 Tag값 저장
    if(preName.equalsIgnoreCase("data"))
    {
     paw = new PlotAndWeather();
    }
    break;
   case XmlPullParser.END_TAG:      
     preName = null;   // Tag 가 끝나면 임시변수에 null값으로 초기화
    break;
   case XmlPullParser.TEXT:    // TEXT 이벤트에서 임시변수에 저장된 문자열을 확인하여 적절한 객체에 저장
    if(preName==null)  //혹시 preName이 null일수 있으니 처리
    {
     
    }
    else if(preName.equalsIgnoreCase("time"))
    {
     paw.setTime(parser.getText());
    }else if(preName.equalsIgnoreCase("plotName"))
    {
     paw.setPlotName(parser.getText());
    }    
    break;
   }
   
   parserEvent=parser.next();
  }



만약
<data>야울</data> 가 있다면 XmlPullParser.START_TAG,XmlPullParser.TEXT,XmlPullParser.END_TAG 가 순차적으로 발생한다.
<data></data> 인 경우에도 택스트가 없지만 XmlPullParser.START_TAG,XmlPullParser.TEXT,XmlPullParser.END_TAG 가 순차적으로 발생한다
만약 <data><data1>text</data1></data> 인 경우 START_TAG,TEXT,START_TAG,TEXT,END_TAG ,END_TAG 이렇게 발생한다.



정리 :
1. XmlPullParser 는 문서를 순차적으로 읽으면서 이벤트를 발생시키므로 뒤로 가지 못한다. 따라서 XmlPullParser.START_TAG 발생시 임시변수를 선언하여 Tag값을 저장한다.
2. TEXT 이벤트시 임시변수에 저장된 Tag값을 확인하여 XmlPullParser.TEXT 이벤트가 발생한 위치(태그)가 어디인지를 구분하고 적절하게 대처한다.
3. XmlPullParser.END_TAG 에서는 임시변수를 초기화(null) 시킨다.
4. XmlPullParser.START_TAG 와  XmlPullParser.END_TAG 이벤트에서는 getName() 메소드를 사용한다. getText()사용시 null반환
5. XmlPullParser.TEXT 이벤트시에는 getText() 메소드를 사용한다. getName()사용시 null반환
6. XmlPullParser.TEXT 이벤트는 텍스트가 존재하지 않아도 발생한다. 예) <data></data>
7. 태그 내에 존재하는 속성값은 XmlPullParser.START_TAG 이벤트시 추출하자. 예) <data size=100>test</data>


생각나는대로 막 적어서 다른분들은 이해하셨을지 모르겠다.. 나 글 정말 못쓴다....

  1. 성국 2011.10.19 18:18

    그냥 보고가려다가 ㅋ 마지막에 나글정말못쓴다 이 부분이 걸리는군요 ㅋ
    api쪽 공부하다가 TEXT 하고 END 태그구분이안가서 들렀는데 도움받고갑니다! 좋은정보감사해요~

    • 단세포소년 2011.10.31 08:38 신고

      저에게 온 첫 댓글이네요ㅜㅜ 감사합니다.

    • 시리정 2016.04.13 16:36 신고

      '나글정말못쓴다' 똑같은 부분에서 마음이 동하네요 ㅎㅎ
      찾아본 글 중, 가장 쉽고 유용하게 알려주신거 같아요! 감사합니다.

  2. 맹경호 2012.01.05 19:12

    정말 모르겠습니다.
    저는 공부를 더 해야하는 것 같습니다.
    상세한 설명 감사드립니다.

    • 단세포소년 2012.01.06 07:15 신고

      저도 어디가서 자랑 할 실력은 아닌지라..
      저도 열심히 공부해야겠네요.

  3. 파서급행ㅠ 2012.05.09 18:53

    뭐하나 여쭤봐도 되겠습니까

    여기서 값을 출력시켜주는 부분이 어느부분이죠?ㅎㅎ

    • 단세포소년 2012.05.16 00:31 신고

      죄송합니다 답변이 늦었네요

      조건문 즉 case XmlPullParser.START_TAG 와 case XmlPullParser.TEXT 에서 출력을 할수 있습니다.

      START_TAG 에서 경우 getName() 을 통해 TAG 이름을 넣을수 있고 또한 getAttributeValue() 를 통해 TAG 내에 속성값을 출력할수 있습니다.

      TEXT 이벤트에서는 getText()를 통해 START_TAG와 END_TAG 사이의 텍스트를 출력할수 있습니다.

      조심하셔야할점은 START_TAG 이벤트에서 getText()나 TEXT 이벤트에서 getName() 함수를 호출하게 된다면 null을 반환합니다. XmlPullParser는 순차적으로 파싱을 하기 때문에 지나간 이벤트의 값은 가져오지 못한다는 점을 염두해 두시고 프로그래밍 하시기 바랍니다.

  4. 성현 2013.03.14 14:32

    구글링 하는 중인데 여기가 제일 깔끔하게 정리해 놓은 것 같네요 ^^ 감사합니다

  5. cys 2013.05.22 09:30

    저도 제가본것중에 가장 잘되 있어요
    감사합니다

+ Recent posts