1. 아두이노, 프로세싱, 퍼널라이브러리 등등 설치

2. 이클립스 설치


3. 프로젝트 생성


4. 프로젝트 설정 -> 자바 빌드 패치 -> 라이브러리 -> 추가 ( 프로세싱폴더 lib-> core.jar, jna.jar ), (modes\java\libraries\serial\library -> RXTXcomm.jar, serial.jar )


5. serial.dll 복사 ( modes\java\libraries\serial\library\windows32 ) windows\system32로 복사



자바 기본 코드


import processing.core.PApplet;

import processing.serial.Serial;



public class Test extends PApplet {


Serial mPort;

public void setup() {

size(400,400);

println(Serial.list());

mPort = new Serial(this, Serial.list()[1], 9600);

// mPort = new Serial(this, "COM11", 9600);

}

public void draw() {

background(255);

}

public void mousePressed() {

mPort.write(255);

}

public void mouseReleased() {

mPort.write(0);

}

static public void main(String args[]) {

PApplet.main(new String[] { "--bgcolor=#F0F0F0", "Test" });

}

}



아두이노 기본 코드

int ledPin = 9;

void setup()
{
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop()
{
  byte brightness;
  
  
  if ( Serial.available() )
  {
    brightness = Serial.read();
    analogWrite(ledPin, brightness);
  }
}



이전 2의 제곱수가 아닌 이미지 출력하기...와 함께 적으려고했는데..
길이가 너무 길어지는것 같아.. 따로 작성합니다.

대부분의 이미지를 일부러 2의 제곱수로 만들지 않으므로 2의 제곱수로 변환하고 텍스쳐를 생성하는 작업을 많이하게됩니다.
그래서 이미지가 많이사용되는 게임의 경우에는 1024*1024크기의 이미지에 전부 넣어놓고 특정 부분만 잘라서 사용하는 방식을 사용하더군요.. 팔라독, 룰더스카이 등등..

확실히 큰 이미지 몇개를 로딩하는것이 작은것 여러개를 변환하고 만들어 로딩하는것보다는 효율적인것 같습니다.

이번에는 텍스쳐의 부분부분을 출력하는 방법을 알아봅시다.
다시 512*512 크기의 팬더를 사용해서 해보겠습니다.

팬더의 왼쪽눈이 대충 71, 174 위치에 자리잡고 있군요.. 크기는 120, 80 정도로 잡겠습니다.

GLGameRenderer.c
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	updatePanda();
	//drawPanda();
	drawPandaClip(71, 174, 120, 80);
}
기존의 drawPanda()를 잠시 쉬게하고 drawPandaClip을 만듭니다.

void drawPandaClip(int sx, int sy, int w, int h)
{
	GLfloat vertices[12] = {
			g_nX	,	g_nY+h	,	0.0f,	// LEFT  | BOTTOM
			g_nX+w	,	g_nY+h	,	0.0f,	// RIGHT | BOTTOM
			g_nX	,	g_nY	,	0.0f,	// LEFT  | TOP
			g_nX+w	,	g_nY	,	0.0f	// RIGHT | TOP
	};

	GLfloat texture[8] = {
			(GLfloat)sx/(GLfloat)g_nPandaWidth		, (GLfloat)(sy+h)/(GLfloat)g_nPandaHeight,
			(GLfloat)(sx+w)/(GLfloat)g_nPandaWidth	, (GLfloat)(sy+h)/(GLfloat)g_nPandaHeight,
			(GLfloat)sx/(GLfloat)g_nPandaWidth		, (GLfloat)sy/(GLfloat)g_nPandaHeight,
			(GLfloat)(sx+w)/(GLfloat)g_nPandaWidth	, (GLfloat)sy/(GLfloat)g_nPandaHeight
	};

	
	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
sx, sy
 팬더의 눈이 시작될 지점이 되겠군요.
w, h
 팬더 눈의 크기 입니다.
vertices[12]
 그려질 위치와 크기를 지정해 주는곳이죠.
 g_nX, g_nY는 그려질 위치이며, 입력받은 w, h만큼의 크기로 그려준다고 알려줍니다.
texture[8]
 이 부분이 실제 텍스쳐에서 그려질 부분을 지정하는 부분입니다.
 기존의 drawPanda()소스에서는
	GLfloat texture[8] = {
			0	, 1,
			1	, 1,
			0	, 0,
			1	, 0
	};
왼쪽이 width, 오른쪽이 height라고 말씀드렸었죠.
0 ~ 1까지가 0 ~ 100%를 표현한 것이라고도 말씀드렸었구요.
위 소스를 실행시켰을때가 이미지가 100%의 크기로 나왔으니 0은 텍스쳐의 시작을 1은 끝을 의미한다고 볼 수 있습니다.
그럼 0은 원하는 좌표(sx, sy)비율을 1은 원하는 크기까지(sx+w, sy+h)의 비율을 넣으면 됩니다.

왼쪽의 0은 width 부분의 시작 : 이미지 가로길이중에 sx가 차지하는 비율
왼쪽의 1은 width 부분의 끝    : 이미지 가로길이중에 sx+w가 차지하는 비율
오른쪽의 0은 height 부분의 시작 : 이미지 세로길이중에 sy가 차지하는 비율
오른쪽의 1은 height 부분의 끝    : 이미지 세로길이중에 sy+h가 차지하는 비율

빌드 후 실행..


깔끔하군요!


이번에는 원하는 좌표를 중심으로 갖는 이미지를 출력해보겠습니다.

GLGameRenderer.c
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	updatePanda();
	//drawPanda();
	//drawPandaClip(71, 174, 120, 80);
	drawPandaC(240, 400);
}
방금 작업한 clip도 휴식을 주고 drawPandaC()함수를 제작합니다.
void drawPandaC(int cx, int cy)
{
		GLfloat vertices[12] = {
			cx - (g_nPandaWidth>>1)	,	cy + (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | BOTTOM
			cx + (g_nPandaWidth>>1)	,	cy + (g_nPandaHeight>>1)	,	0.0f,	// RIGHT | BOTTOM
			cx - (g_nPandaWidth>>1)	,	cy - (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | TOP
			cx + (g_nPandaWidth>>1)	,	cy - (g_nPandaHeight>>1)	,	0.0f	// RIGHT | TOP
	};

	GLfloat texture[8] = {
			0				, g_textureHeight,
			g_textureWidth	, g_textureHeight,
			0				, 0,
			g_textureWidth	, 0
	};

	
	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
이미지의 중점 cx, cy를 받아오는군요.
vertices좌표에서 기존의 g_nX, g_nY가 받아온 중점으로 바뀌었습니다.
그리고 그 중점을 기준으로 이미지의 절반씩 더하거나 빼줍니다.

빌드 후 실행 ~

 
딱 가운데 왔지요~! 


회전을 해볼까요?
void drawPandaC(int cx, int cy, int deg)
방금사용한 함수에 파라메타를 하나 추가합니다.
	GLfloat vertices[12] = {
			- (g_nPandaWidth>>1)	,	+ (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | BOTTOM
			+ (g_nPandaWidth>>1)	,	+ (g_nPandaHeight>>1)	,	0.0f,	// RIGHT | BOTTOM
			- (g_nPandaWidth>>1)	,	- (g_nPandaHeight>>1)	,	0.0f,	// LEFT  | TOP
			+ (g_nPandaWidth>>1)	,	- (g_nPandaHeight>>1)	,	0.0f	// RIGHT | TOP
	};
vertices 좌표가 달라졌습니다.
이유는 기존의 상태로 회전을 시키면 원하는대로 회전해주지 않으니까요 ~
0,0을 이미지 중점으로 놓고 그려줍니다.


	glLoadIdentity();
	glTranslatef(cx, cy, 0.0f);
	glRotatef(deg, 0.0f, 0.0f, 1.0f);
좌표계를 초기화 해주고 ( 이전 좌표계의 영향을 받을 수 있으므로 )
glTranslatef(cx, cy, 0.0f);
 기준 축을 cx, cy로 이동시킵니다.
glRotatef(deg, 0.0f, 0.0f, 1.0f);
  z축을 기준으로 회전을 시킵니다.

빌드 후 실행 ~
원하는 좌표(cx, cy)에서 빙글빙글 도는 팬더가 출력되는군요. 


확대와 축소를 해볼차례 입니다~!
회전하기전 drawPandaC() 소스를 기반으로 하겠습니다.
void drawPandaScale(int cx, int cy, GLfloat scale)
{
	GLfloat vertices[12] = {
			cx - (g_nPandaWidth>>1)/scale	,	cy + (g_nPandaHeight>>1)/scale	,	0.0f,	// LEFT  | BOTTOM
			cx + (g_nPandaWidth>>1)/scale	,	cy + (g_nPandaHeight>>1)/scale	,	0.0f,	// RIGHT | BOTTOM
			cx - (g_nPandaWidth>>1)/scale	,	cy - (g_nPandaHeight>>1)/scale	,	0.0f,	// LEFT  | TOP
			cx + (g_nPandaWidth>>1)/scale	,	cy - (g_nPandaHeight>>1)/scale	,	0.0f	// RIGHT | TOP
	};

	GLfloat texture[8] = {
			0				, g_textureHeight,
			g_textureWidth	, g_textureHeight,
			0				, 0,
			g_textureWidth	, 0
	};

	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
vertices만 바뀌었습니다.
scale 값이 0 < scale < 1.0 이면 확대
1.0 < scale 축소 입니다.

 
왼쪽은 scale이 0.5일때의 화면이며
오른쪽은 scale이 2.0일때의 화면입니다. 






먼저 이전에 텍스쳐는 2의 제곱수여야만 한다는 전제가 있었습니다.
그럼! 모든 이미지를 2의 제곱수로 만들어야 하나? 말도안되지요..

1. 2의 제곱수가 아닌 이미지 출력하기
안드로이드 프로젝트 생성시 포함되어있는 아이콘이 72*72군요. 2의 제곱수가 아닙니다~! 이걸로 해봅시다.

GLView.java
 onSurfaceCreated() 함수안에 만들어놓았던 소스를 변경하겠습니다.
Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_launcher);
panda -> ic_launcher로 이미지를 바꾸어주었습니다.
빌드 후 실행해보면..

역시 이미지가 출력되지 않는군요~

GLGameRenderer.c
 setTextureData()함수에서 2의 제곱수만큼의 비어있는 버퍼를 생성하고 현재 픽셀값을 넣어주는 작업을 합니다.

int makeWidth, makeHeight;
char *makeBuf;
만들어질 버퍼의 가로, 세로 그리고 픽셀데이터를 저장할 버퍼를 선언합니다.

	makeWidth = makeHeight = 2;

	while( makeWidth < width )
		makeWidth = makeWidth << 1;

	while( makeHeight < height )
		makeHeight = makeHeight << 1;

	makeBuf = (char *)malloc((sizeof(char)*makeWidth*makeHeight)<<2);
가로와 세로는 2의 제곱수로 만들어야 하기 때문에 초기값을 2로 주겠습니다.
두개의 while문은 기존의 이미지를 모두 포함해야하기 때문에 기존 이미지보다 크면서 가장 가까운 2의 제곱수를 찾는 것입니다.
마지막으로 위에서 만든 가로, 세로만큼의 픽셀데이터를 가질 버퍼를 생성합니다.

RGBA 변환을 마친 후 데이터를 정리해보겠습니다.
아래 이미지중에서 검은색 배경부분은 새로 생성한 128*128크기의 버퍼입니다.
흰색 배경부분은 72*72이미지 입니다.
현재 검은색 버퍼를 완성한 상태이고, 아래 그림과같이 원하는 이미지를 정렬해서 넣어주는 작업이 필요합니다.



	int row, col;

	row = -1;
	col = 0;
	for ( i = 0; i < width*height*4; i += 4 )
	{
		if ( i % (width<<2) == 0 )
		{
			row++;
			col = 0;
		}

		makeBuf[(row*makeWidth<<2)+col] = buf[i];
		makeBuf[(row*makeWidth<<2)+col+1] = buf[i+1];
		makeBuf[(row*makeWidth<<2)+col+2] = buf[i+2];
		makeBuf[(row*makeWidth<<2)+col+3] = buf[i+3];
		col += 4;
	}
두개의 변수가 추가로 필요하네요. row(행)와 col(렬)
위 코드는 흰색배경부분의 픽셀을 검은색 배경 0,0위치부터 붙여나갑니다.
그러다가 흰배경부분 1줄을 다 그렸을때 2번째 줄을 이어서 그리면? 안되죠!
2번째 줄은 검은색 배경에도 2번째 줄부터 그려주어야 합니다. 

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, makeWidth, makeHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)makeBuf);
텍스쳐의 가로, 세로 그리고 버퍼 부분이 바뀌었습니다.
새롭게 생성한 makeWidth, makeHeight크기의 텍스쳐를 생성할 것이며, 픽셀 데이터는 변환된 makeBuf를 넣어주어야지요.

사용한 메모리를 해제해 주는것도 잊지맙시다.
free(makeBuf);

빌드 후 실행해보면 ~ 



이미지는 정상 출력되는데... 뭔가 이상합니다.. 뭔가가...

우리가 원하는것은 72*72 크기의 이미지만을 그려주는 것이였는데 말이죠..


	GLfloat	g_textureWidth;
	GLfloat	g_textureHeight;

	g_textureWidth = (GLfloat)g_nPandaWidth / (GLfloat)makeWidth;
	g_textureHeight = (GLfloat)g_nPandaHeight / (GLfloat)makeHeight;
전역 변수로 g_textureWidth와 g_textureHeight를 추가시켜줍니다.
실제 텍스쳐의 크기 비율을 저장할 것입니다.

그리고 계속해서 코드를 추가시킵니다.
새롭게(makeBuf) 만든 텍스쳐의 크기중에 실제 텍스쳐가 차지하는 비율을 계산하고있습니다.
전체가 1이라고 보면 0.xxxx가 되겠지요?

setTextureData()에서 해야할 일은 끝이났습니다!

이젠 drawPanda()함수로 이동합니다.
texture 배열 부분이 있죠?
	GLfloat texture[8] = {
			0				, g_textureHeight,
			g_textureWidth	, g_textureHeight,
			0				, 0,
			g_textureWidth	, 0
	};
0을 제외한 부분이 1이였는데 이 값을 텍스쳐에서 실제 이미지가 해당되는 비율로 바꾸어주면 됩니다.

빌드 후 실행!
 



뭔가... 높이가 안맞아 보이는듯하네요.. 폰에서는 제대로 보입니다.

그런데, 자꾸 뒷배경이 검은색으로 나오네요..
이유는 ? 알파값! 투명처리가 안되고있습니다.

onSurfaceChanged() 함수에 

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
두줄을 추가시킵니다.

glEnable(GL_BLEND); 
 블렌딩 효과를 활성화.
 블렌딩이 적용되면 새로지정된 색상과 기존색상이 결합되어 옵션에 따라 효과가 달라집니다.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 음... 두 인자에따라 RGB에 계산을 하는데... 위 소스는 알파값을 적용하기 위한 효과이고.. 나머지는 찾아보시길..

두줄을 onSurfaceChanged()에 추가시킨 이유는 다른 블렌딩 효과를 사용안하고 그냥 알파값을 항상 사용하기때문에...-,.- 

빌드 후 확인해보면..



드디어 원하던 이미지 그대로 나왔습니다!


setTextureData() 함수에 변형부분이 많아 최종 소스를
 

void setTextureData(char *data, int width, int height)
{
	int i;
	int row, col;
	int makeWidth, makeHeight;
	char *buf;
	char *makeBuf;

	makeWidth = makeHeight = 2;

	while( makeWidth < width )
		makeWidth = makeWidth << 1;

	while( makeHeight < height )
		makeHeight = makeHeight << 1;

	makeBuf = (char *)malloc((sizeof(char)*makeWidth*makeHeight)<<2);
	buf = (char *)malloc((sizeof(char)*width*height)<<2);

	for (i = 0; i < width*height*4; i += 4)
	{
		buf[i]   = data[i+2];
		buf[i+1] = data[i+1];
		buf[i+2] = data[i];
		buf[i+3] = data[i+3];
	}


	row = -1;
	col = 0;
	for ( i = 0; i < width*height*4; i += 4 )
	{
		if ( i % (width<<2) == 0 )
		{
			row++;
			col = 0;
		}

		makeBuf[(row*makeWidth<<2)+col] = buf[i];
		makeBuf[(row*makeWidth<<2)+col+1] = buf[i+1];
		makeBuf[(row*makeWidth<<2)+col+2] = buf[i+2];
		makeBuf[(row*makeWidth<<2)+col+3] = buf[i+3];
		col += 4;
	}

	g_nPandaWidth = width;
	g_nPandaHeight = height;

	g_textureWidth = (GLfloat)g_nPandaWidth / (GLfloat)makeWidth;
	g_textureHeight = (GLfloat)g_nPandaHeight / (GLfloat)makeHeight;

	glGenTextures(1, &g_textureName);
	
	glBindTexture(GL_TEXTURE_2D, g_textureName);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, makeWidth, makeHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)makeBuf);

	free(buf);
	free(makeBuf);
}
요래~



'Android > OpenGL' 카테고리의 다른 글

[NDK-OpenGL_06] 내맘대로 텍스쳐 ~  (0) 2012.03.06
[NDK-OpenGL_04] 쓰레드 생성  (0) 2012.03.04
[NDK-OpenGL_03] 텍스쳐 입히기  (5) 2012.02.27
[NDK-OpenGL_02] 해상도 지정하기  (3) 2012.02.26
[NDK-OpenGL_01] 프로젝트 준비  (3) 2012.02.26



쓰레드를 만들고 이미지를 움직여보겠습니다.

자바소스에서 GameThread.java를 만들겠습니다.

package pe.berabue.opengl;

import android.content.Context;

public class GameThread extends Thread {
		
	private boolean isRun;
	
	public GameThread(Context context) {
		isRun = true;
	}

	public synchronized void run() {
		while(isRun) {
			try	{
				MainActivity.mGLView.requestRender();
				Thread.sleep(10);
			}
			catch (InterruptedException e1)	{
				e1.printStackTrace();
			}
		}
	}
    }
}
딱 한 부분만 중요합니다.
MainActivity.mGLView.requestRender();
   requestRender()는 onDrawFrame()함수를 호출해줍니다. 쓰레드가 동작하는동안 지속적으로 onDrawFrame을 호출하겠지요.


MainActivity.java에서 쓰레드를 동작시키겠습니다.
package pe.berabue.opengl;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class MainActivity extends Activity {
	
	public static GLSurfaceView mGLView;
	public static GameThread	mThread;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mGLView = new GLView(getApplicationContext());
        mThread = new GameThread(getApplicationContext());
        
        setContentView(mGLView);
        
        mThread.start();
    }
    
    static {
    	System.loadLibrary("berabueLib");
    }
}
public static GLSurfaceView mGLView; 
 private -> public static으로 변경하였습니다. 쓰레드에서 mGLView 객체에 접근하기위해서. 

쓰레드 객체를 생성하고 쓰레드를 시작합니다~!


GLGameRenderer.c 에서 이전에 띄운 이미지를 움직여 보도록하겠습니다.
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	updatePanda();
	drawPanda();
}

void updatePanda()
{
	g_nY = ( g_nY < 0 ) ? 800 - g_nPandaHeight : g_nY-5;
}
updateGameLoop()안에 updatePanda()를 추가시키고 좌표가 변화되는 코드를 입력합니다.

빌드 후 실행을해보면~!
팬더가 지속적으로 올라가는 화면이 출력됩니다! 





원래 점, 선, 면 부터 시작을 해야하지만 급한일이 생기는 관계로 텍스쳐입히는 방법으로 바로 들어가겠습니다!

텍스쳐 재료로 사용될 이미지는 기본적으로 2의 제곱수 이여야합니다. 2의 제곱수가 아니라면 텍스쳐가 정상출력되지 않습니다.
또한 이미지의 크기는 최대 1024 * 1024까지 사용할 수 있습니다. 가로와 세로는 같을필요가 없고 가로와 세로 모두 2의 제곱수이면 됩니다.

2의 제곱수인 이미지를 한 장 준비합시다. 이 팬더이미지는 512*512 입니다.




1. 자바에서 이미지 데이터 보내기
이미지를 drawable폴더에 넣고 GLView.java를 보겠습니다.

package pe.berabue.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.view.MotionEvent;

public class GLView extends GLSurfaceView implements Renderer {

	private Context mContext;
	
	private static native void nativeCreated(); 
    private static native void nativeChanged(int w, int h);
    private static native void nativeUpdateGame();
	private static native void nativeOnTouchEvent(int x, int y, int touchFlag);
	private static native void nativeSetTextureData(int[] pixels, int width, int height);
	
	public GLView(Context context) {
		super(context);
		this.setRenderer(this);
		this.requestFocus();
		this.setRenderMode(RENDERMODE_WHEN_DIRTY);
		this.setFocusableInTouchMode(true);
		
		mContext = context;
	}

	public void onDrawFrame(GL10 gl) {
		nativeUpdateGame();
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		nativeChanged(w, h);
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		nativeCreated();
		
		Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.panda);
		int[] pixels = new int[bmp.getWidth()*bmp.getHeight()];
		bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
		
		nativeSetTextureData(pixels, bmp.getWidth(), bmp.getHeight());
	} 
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		nativeOnTouchEvent((int)event.getX(), (int)event.getY(), event.getAction());
		return true;
	}
}
비트맵을 사용하기위한 Context와 텍스쳐데이터를 보내기위한 nativeSetTextureData();
그리고 onSurfaceCreated() 부분의 코드가 추가되었습니다.
가장 중요한 onSurfaceCreated(); 부분을 보겠습니다.

Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.panda);
 위에서 저장한 panda이미지를 불러옵니다.
int[] pixels = new int[bmp.getWidth()*bmp.getHeight()];
 픽셀데이터를 담을수 있도록 위 이미지의 가로*세로 크기의 int배열을 생성합니다.
bmp.getPixels(pixels, 0, bmp.getWidth(), 0, 0, bmp.getWidth(), bmp.getHeight());
 해당 이미지의 픽셀데이터를 읽어와 배열에 저장합니다.
nativeSetTextureData(pixels, bmp.getWidth(), bmp.getHeight());
 해당 픽셀데이터, 가로, 세로 크기를 전달합니다 ~


2. jni-ndk.c 에서 받기

void Java_pe_berabue_opengl_GLView_nativeSetTextureData(JNIEnv* env, jobject thiz, jintArray arr, jint width, jint height)
{
	char *data = (*env)->GetByteArrayElements(env, arr, 0);

	setTextureData(data, width, height);

	(*env)->ReleaseByteArrayElements(env, (jbyteArray)arr, data, JNI_ABORT);
}
char *data = (*env)->GetByteArrayElements(env, arr, 0);
 자바에서 넘어온 배열을 통째로 복사합니다.
setTextureData(data, width, height);
 픽셀데이터와, 이미지의 가로, 세로 크기를 보내버리고 ~
(*env)->ReleaseByteArrayElements(env, (jbyteArray)arr, data, JNI_ABORT);
 자바에서 넘어온 배열을 해제시켜줍니다!

JNI 부분은 아직도 이해가안되서 사용하기가 어렵네요... 대충 기능만 적었습니다..ㅜㅜ


3. GLGameRenderer.c에서 나머지를 해결해봅시다.

먼저 변수 몇개가 필요할것같군요.
GLuint	g_textureName;
int		g_nX;
int		g_nY;
int		g_nPandaWidth;
int		g_nPandaHeight;
g_textureName;
 각각의 텍스쳐마다 이름을 지정해주어 해당텍스쳐를 불러내는 용도입니다.
텍스쳐의의 시작점(LT기준)과 텍스쳐 크기에 사용될 변수가 있습니다. ( 적당히 초기화를 해주시면됩니다. )
void setTextureData(char *data, int width, int height)
{
	g_nPandaWidth = width;
	g_nPandaHeight = height;

	glGenTextures(1, &g_textureName);
	
	glBindTexture(GL_TEXTURE_2D, g_textureName);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)data);
}
텍스쳐를 만들어내는 곳입니다.

glGenTextures(1, &g_textureName);
 void glGenTextured(GLsizei n, GLuint *텍스쳐);
 OpenGL에서는 텍스쳐에 각각의 번호를 매겨사용을합니다.
 생성할 텍스쳐의 수(n)와 텍스쳐의 이름(번호)으로 사용될 배열을 넘겨주면 배열에 값이 들어옵니다.
glBindTexture(GL_TEXTURE_2D, g_textureName);
 사용할 텍스쳐를 선택합니다. 2차원이면서 g_textureName의 이름(번호)를 가진 텍스쳐를 사용할겁니다.
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 축소시에 부드럽게
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 확대시에 픽셀을 유지
 위 두 함수의대한 설명은 http://mrhoya.tistory.com/entry/Filtering-MIPMAP%EC%9D%98-%EC%9D%B4%ED%95%B4 
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,(void*)data);
 
 glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height
       , GLint border, GLenum format, GLenum type, const GLvoid *pixels)
 target : 2차원 텍스쳐를 생성합니다.
 level : 디테일 레벨, 밉맵핑을 사용하지 않으면 보통 0
 internalformat : 이미지 데이터의 내부 포맷
 width : 텍스쳐 가로폭
 height : 텍스쳐 세로폭
 border : 테두리두께로 0 ~ 2 인듯싶습니다(?) 보통 0
 format : 픽셀 데이터의 포맷
 pixels : 픽셀 데이터


텍스쳐를 생성했으니 그려봅니다!


void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


	drawPanda();
}

void drawPanda()
{
	GLfloat vertices[12] = {
			g_nX				,	g_nY+g_nPandaHeight	,	0.0f,	// LEFT  | BOTTOM
			g_nX+g_nPandaWidth	,	g_nY+g_nPandaHeight	,	0.0f,	// RIGHT | BOTTOM
			g_nX				,	g_nY				,	0.0f,	// LEFT  | TOP
			g_nX+g_nPandaWidth	,	g_nY				,	0.0f	// RIGHT | TOP
	};
	
	GLfloat texture[8] = {
			0	, 1,
			1	, 1,
			0	, 0,
			1	, 0
	};

	
	glEnable(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, g_textureName);

    glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
    
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glTexCoordPointer(2, GL_FLOAT, 0, texture);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    glDisableClientState(GL_VERTEX_ARRAY);

	glDisable(GL_TEXTURE_2D);
}
drawPanda(); 팬더를 그립니다 ~ 
vertices
 텍스쳐가 그려질 좌표(정점)입니다.
texture
 그려질 텍스쳐의 영역입니다. 0 ~ 1까지 값이 들어가며 0 ~ 100%로 보시면됩니다.
 위 배열에서 왼쪽은 w이며, 오른쪽은 h입니다.
 만약, 이미지의 좌상단 1/4만을 출력하고싶다면 1을 0.5로 바꾸어주면 됩니다.
glEnable(GL_TEXTURE_2D);
 2D텍스쳐를 활성화시킵니다.
glBindTexture(GL_TEXTURE_2D, g_textureName);
 바로 위에서 만들어주었던 이름(번호)과 동일한 텍스쳐를 그릴것이구요~
glEnableClientState(GL_VERTEX_ARRAY);
 해당 기능 활성화시 glVertexPointer에 설정한 vertices를 참고하여 렌더링
glVertexPointer(3, GL_FLOAT, 0, vertices);
 
버텍스 데이터 좌표배열을 지정 
 3 : 좌표 당 버텍스 수. 2~4
 GL_FLOAT : 타입
 0 : 배열 내의 버텍스 간격. ( 여기서는 배열하나의 여러개의 좌표를 갖고있지 않으므로 0 )
 vertices : 데이터 포인터 
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 해당 기능 활성화시 glTexCoordPointer 에 설정한 texture 를 참고하여 렌더링 
glTexCoordPointer(2, GL_FLOAT, 0, texture);
 텍스쳐 좌표 배열을 지정
 2 : 배열당 좌표 수 1 ~ 4
 GL_FLOAT : 타입
 0 : 배열 내의 좌표 간격
 texture : 데이터 포인트
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
 GL_TRIANGLE_STRIP(연속 삼각형) : 해당 모양으로 그리기
 0 : 배열의 첫번째 인덱스
 4 : 사용할 인덱스 수
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);

 비활성화


여기까지! 입니다!!

빌드 후 실행해보면 ~ 짜잔!!
























으헉... 빼먹은게 있습니다.
자바에서 픽셀데이터를 보내왔을때 데이터의 색상순서가 RGBA가 아닌 BGRA순서입니다.
그럼, 순서를 바꾸어줍시다~!
void setTextureData(char *data, int width, int height)
{
	int i;
	char *buf;

	buf = (char *)malloc((sizeof(char)*width*height)<<2);

	for (i = 0; i < width*height*4; i += 4)
	{
		buf[i]   = data[i+2];
		buf[i+1] = data[i+1];
		buf[i+2] = data[i];
		buf[i+3] = data[i+3];
	}

	g_nPandaWidth = width;
	g_nPandaHeight = height;

	glGenTextures(1, &g_textureName);
	
	glBindTexture(GL_TEXTURE_2D, g_textureName);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (void*)buf);

	free(buf);
}
변수 두개와 메모리할당, for문, 그리고 아래쪽에 메모리를 해제시켜주는 부분이 추가되었습니다.
픽셀데이터와 같은 크기의 메모리를 할당받고 BGRA -> RGBA순서로 변경해줍니다.
glTexImage2D() 마지막 값이 data -> buf로 바뀌었내요.

다시 빌드 후 실행해보면 ~

























정상출력되었습니다!

 
위의 vertices와 texture 배열을 이용하면
원하는 위치를 중심으로 출력하거나, 좌표, 그려질 영역등을 마음대로 조절할 수 있습니다.
또한 값의 순서를 바꾸면 이미지가 정상출력이 되지 않습니다.
OpenGL은 모든 도형을 삼각형을 합쳐서 그리기때문에 정점의 순서를 맞추어주지 않으면 텍스쳐가 제대로 나오지 않습니다~
한번해보세요 ~

오늘은 여기까지 ~ 





기능별로 나누기 위해 소스를 약간 수정하였습니다.

jni-ndk.c는 java <-> c의 통신에 사용될 예정이라 OpenGL소스를 따로 빼내겠습니다.
GLGameRenderer.c
GLGameRenderer.h
두 파일을 만들고 jni폴더에 넣습니다.

jni-ndk.c
#include <jni.h>

/**
 * Created by berabue on 12. 2. 23..
 * berabue@gmail.com
 */

void Java_pe_berabue_opengl_GLView_nativeCreated(JNIEnv*  env)
{	
	onSurfaceCreate();
}

void Java_pe_berabue_opengl_GLView_nativeChanged(JNIEnv* env, jobject thiz, jint w, jint h)
{
	onSurfaceChanged(w, h);
}
   
void Java_pe_berabue_opengl_GLView_nativeUpdateGame(JNIEnv* env)
{
	updateGameLoop();
}

void Java_pe_berabue_opengl_GLView_nativeOnTouchEvent(JNIEnv* env, jobject thiz, jint x, jint y, jint touchFlag)
{
	onTouchEvent(x, y, touchFlag);
}
 
기존의 소스에서 OpenGL관련 헤더파일 두개와 색상과 관련되었던 소르를 제거하고
onSurfaceCreate,  onSurfaceChanged, updateGameLoop, onTouchEvent 함수를 적어놓습니다.


GLGameRenderer.c
#include <GLES/gl.h>
#include <GLES/glext.h>
#include "GLGameRenderer.h"

void onSurfaceCreate()
{
	glClearColor(0.4f, 0.4f, 0.4f, 0.4f);
}

void onSurfaceChanged(int width, int height)
{
}
   
void updateGameLoop()
{
	glClear(GL_COLOR_BUFFER_BIT);
}

void onTouchEvent(int x, int y, int touchFlag)
{
}
 
jni-ndk.c 에서 지웠던 헤더파일을 붙여넣고 함수를 작성해줍니다.
GLGameRenderer.h 파일에 함수원형을 적어주세요. ( 이부분은 앞으로 말하지 않겠습니다. )


onSurfaceChanged() 함수에 소스를 추가시키겠습니다.
void onSurfaceChanged(int width, int height)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	glOrthof(0.0f, 480, 800, 0.0f, 1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glViewport(0, 0, width, height);
}
glMatrixMode(GL_PROJECTION);
 이 명령뒤에 나오는 행렬과 관련된 것은 투영행렬에 영향을 줍니다.
glLoadIdentity();
 행렬을 초기화시킵니다.
glOrthof(0.0f, 480, 800, 0.0f, 1.0f, -1.0f);
 우리는 2D를 만들것이므로 직교투영으로 화면을 지정합니다. 개발자가 원하는 영역을 지정해줍니다.
glMatrixMode(GL_MODELVIEW);
 이 명령뒤에 나오는 행렬과 관련된 것은 모델뷰행렬에 영향을 줍니다.
glLoadIdentity();
 초기화.
glViewport(0, 0, width, height);
 실제 기기에 보여질 화면영역을 지정해줍니다.

여기서 중요한것은 glOrthof()와 glViewport()인데요.
먼저 glOrthof()에 원하는 작업공간크기를 지정해줍니다.
"나는 480*800 화면을 기준으로 할꺼야." 그러면 위와같이 480, 800을 넣어주시면됩니다.
glViewport()에서는 실제 기기의 해상도를 입력해주면 모든 해상도에서 올바른 자리에 출력되는 이미지를 보실 수 있습니다.
1280*720 단말에서 돌린다면 480*800을 늘려서 1280*720해상도에 맞게 그리고 그보다 작다면 축소시켜서 그려줍니다.

이제 해상도 관련 세팅이 완료되었습니다.
 





먼저, 해당 프로젝트는 2D를 기준으로 진행합니다.
또한, 잘못알고있는 부분이나 틀린점이 있을 수 있습니다.
잘못된 부분에대해서는 코멘트해주시면 감사하겠습니다 ^^ 

Android NDK를 이용하여 OpenGL을 사용하기전에 ndk환경을 마련해야합니다.
ndk개발환경 구축방법은 따로 설명드리지 않겠습니다.

그럼, 프로젝트 생성부터 시작하겠습니다.
1. Android Project 생성


쭈욱 입력해줍니다.
프로젝트 버전은 2.1이상으로 작성하였습니다.






























2. GLView.java 생성
package pe.berabue.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;

public class GLView extends GLSurfaceView implements Renderer {

	public GLView(Context context) {
		super(context);
		// GLSurfaceView extends시 생성하여야함
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		// Renderer implements시 생성하여야함
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		// Renderer implements시 생성하여야함		
	}

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		// Renderer implements시 생성하여야함		
	}

}
새로 만들어준 GLView class에 GLSurfaceView를 extends하고 Renderer를 implements해줍니다.
위와같이 하나의 생성자와 3개의 함수를 구현해주어야합니다.

public void onDrawFrame(GL10 gl) {}
public void onSurfaceChanged(GL10 gl, int w, int h) {}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {}
 onSurfaceChanged()와 onSurfaceCreated()는 일반 SurfaceView와 같습니다.
 onDrawFrame() 이함수를 호출하여 실질적으로 그림을 그리게됩니다. 


3. GLView 생성자 부분을 작성합니다.
public GLView(Context context) {
		super(context);
		this.setRenderer(this);
		this.requestFocus();
		this.setRenderMode(RENDERMODE_WHEN_DIRTY);
		this.setFocusableInTouchMode(true);
	}
setRenderer(this);
 렌더러를 세팅합니다. ( 뭐라고 설명해야될지 모르겠습니다. )
setRenderMode(RENDERMODE_WHEN_DIRTY);
 렌더러모드를 ( 1회 그리기 )모드로 세팅합니다. onDrawFrame이 1회 불림  


4. C로 작성된 함수를 호출할 native함수를 작성하고 적절한 위치에서 함수를 호출합니다.
package pe.berabue.opengl;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.view.MotionEvent;

public class GLView extends GLSurfaceView implements Renderer {

	private static native void nativeCreated(); 
    private static native void nativeChanged(int w, int h);
    private static native void nativeUpdateGame();
	private static native void nativeOnTouchEvent(int x, int y, int touchFlag);
	
	public GLView(Context context) {
		super(context);
		this.setRenderer(this);
		this.requestFocus();
		this.setRenderMode(RENDERMODE_WHEN_DIRTY);
		this.setFocusableInTouchMode(true);
	}

	public void onDrawFrame(GL10 gl) {
		nativeUpdateGame();
	}

	public void onSurfaceChanged(GL10 gl, int w, int h) {
		nativeChanged(w, h); 
	}

	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		nativeCreated();
	} 
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		nativeOnTouchEvent((int)event.getX(), (int)event.getY(), event.getAction());
		return true;
	}
}
GLView에서 해주어야할 일이 끝났습니다!


5. GLView를 메인View에 연결해볼 차례입니다. MainActivity로 가서..
package pe.berabue.opengl;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class MainActivity extends Activity {
	
	private GLSurfaceView mGLView;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        mGLView = new GLView(getApplicationContext());
        setContentView(mGLView);
    }

    static {
    	System.loadLibrary("berabueLib");
    }
}
mGLView = new GLView(getApplicationContext());
setContentView(mGLView);

 GLSurfaceView를 만들고 메인View로 지정해줍니다. 
System.loadLibrary("berabueLib"); 
 jni폴더를 컴파일하였을때 나오는 berabueLib.so 라이브러리파일을 사용하겠다고 알려줍니다. 

여기까지가 자바에서 해야할 일입니다!


6. 이제 프로젝트 폴더에 jni 폴더를 생성해줍시다.

7. Android.mk 스크립트 파일을 작성합니다.
Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := berabueLib
LOCAL_CFLAGS := -DANDROID_NDK
LOCAL_SRC_FILES := jni-ndk.c
LOCAL_LDLIBS := -lGLESv1_CM\
				-llog

include $(BUILD_SHARED_LIBRARY)

LOCAL_PATH := $(call my-dir)
 컴파일하고자 하는 소스파일 위치를 알려줍니다. Android.mk를 jni에 놔두었으니 프로젝트/jni폴더가 됩니다.
include $(CLEAR_VARS)
 
  LOCAL로 시작하는것들을 초기화시킨다고합니다. LOCAL_PATH는 제외.
LOCAL_MODULE := berabueLib
 
  생성될 so파일명입니다.
LOCAL_CFLAGS := -DANDROID_NDK
 
  컴파일 옵션설정. 자세히 모름.
LOCAL_SRC_FILES := jni-ndk.c
 
  컴파일할 소스파일을 추가합니다.  
LOCAL_LDLIBS := -lGLESv1_CM\ -llog
 해당 라이브러리를 사용합니다.
include $(BUILD_SHARED_LIBRARY)
 공유 라이브러리를 생성합니다.

  
8. Application.mk 파일을 작성합니다.
APP_PLATFORM	:= android-7
APP_PLATFORM := android-7
 프로젝트 버전을 입력해줍니다.


9. jni-ndk.c 작성
실질적으로 Java에서 작성한 native 함수가 있는곳입니다.
#include <jni.h>
#include <GLES/gl.h>
#include <GLES/glext.h>

void Java_pe_berabue_opengl_GLView_nativeCreated(JNIEnv*  env)
{	
	glClearColor(0.4f, 0.4f, 0.4f, 0.4f);
}

void Java_pe_berabue_opengl_GLView_nativeChanged(JNIEnv* env, jobject thiz, jint w, jint h)
{
}
   
void Java_pe_berabue_opengl_GLView_nativeUpdateGame(JNIEnv* env)
{
	glClear(GL_COLOR_BUFFER_BIT);
}

void Java_pe_berabue_opengl_GLView_nativeOnTouchEvent(JNIEnv* env, jobject thiz, jint x, jint y, jint touchFlag)
{
}
먼저 jni헤더와 OpenGL에 사용할 헤더파일을 include합니다.

jni을 사용하여 함수명을 작성하는방법은 아래와같습니다.
Java_패키지명_native함수원형을적은클래스명_함수명();

c -> java함수를 호출하거나 데이터를 보내는것은 다음에 설명해드리겠습니다.
우선 OpenGL 함수들을 봅시다.

nativeCreated() 함수를 먼저 보겠습니다.
glClearColor(0.4f, 0.4f, 0.4f, 0.4f);
 RGBA 색상으로 화면을 칠합니다. 0.0 ~ 1.0f
glClear(GL_COLOR_BUFFER_BIT);
 위에서 입력한 색상으로 화면을 초기화합니다. 

c소스를 빌드 후 어플을 실행시키면 아래와같이 회색화면의 화면을 가득채울 것입니다. 



이것으로 프로젝트 준비단계가 끝이났습니다.


간단한 프로젝트 설명.


src 폴더
현재 MainActivity와 GLView 두개의 클래스로만 되어있습니다.
추후 Thread와 JNI를 이용해 Java와 C가 통신을할 별도의 클래스를 두개정도를 더 추가할 예정입니다.

jni 폴더
Android.mk : native lib을 생성하기위한 정보를 담고있는 스크립트 파일
Application.mk : native 모듈 생성파일
jni-ndk.c : GLView클래서에서 c소스를 호출할때 사용할 c파일

libs, obj 폴더
jni폴더 빌드시 libs, obj폴더가 만들어지고 so파일이 생성됩니다.

















이      름 : 이상한 나라의 사천성
장      르 : 퍼즐
가      격 : 2,000원 / 무료 / 인앱
언      어 : 한국어
지원버전 : 안드로이드OS  2.1이상
마      켓 : T스토어, 구글 스토어, OZ스토어



T스토어 바로가기



-------------------------------------------------------------------------------------

담당 역할
- 커스텀 맵을 제외한 클라이언트 개발
- JSP를 이용한 데이터 관리


'프로젝트' 카테고리의 다른 글

[T스토어 / OZ스토어] 레인보우팡팡  (0) 2011.03.22
[OZ스토어/T스토어] CrazyPanda 크레이지판다  (0) 2011.01.25
[GNEX, WIPI] 스도쿠  (0) 2011.01.03
[WIPI] 풍선 타이쿤  (0) 2011.01.03
[GNEX] 스포츠 천국  (0) 2011.01.03



맥 환경에서 이클립스를 사용해 C/C++ 작업을 하려고했더니

launch failed binary not found 에러가 발생한다.

아무리 찾아봐도 해결불가.......

 
그러다가 위 그림과같이 프로젝트를 생성해서 만들어 보았다.

/*
 ============================================================================
 Name        : HelloWorld.c
 Author      : 
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

//#include <stdio.h>
//#include <stdlib.h>
//
//int main(void) {
//	puts("!!!Hello World!!!"); /* prints !!!Hello World!!! */
//	return EXIT_SUCCESS;
//}

#include <stdio.h>

void main()
{
	printf("Hello world");
}
기본적으로 생성된 코드를 모두 주석처리하고 Hello World를 찍어보았는데

정상작동한다! 뭐가 다른건지 모르겠지만... 일단 실행은 되니...

사용하다가 막히면 다시 찾아봐야겠다 





먼저 windows.h 헤더파일을 포함시켜준다.

사이즈 변경방법 - 원하는 숫자로 변경
 system("mode con:cols=50 lines=30");
제목 변경
 system("title 제목");





헤더 파일을 선언할때 #include <stdio.h> 이런식으로 선언을 한다.
그런데 헤더 파일을 선언할때 <>와 "" 두가지 방법으로 선언하는 것을 보았을 것이다.


먼저 <>를 사용한 헤더 파일 선언.

#include <stdio.h>
C에서 제공하는 표준 헤더 파일 디렉토리에서 지정된 파일을 포함시킨다.


그리고 ""를 사용한 헤더 파일 선언.

#include "myheader.h" 
사용자가 직접 작성한 헤더 파일을 소스 파일과 같은 디렉토리에서 찾아 포함시킨다.


구분자는 우선순위를 지정해 주는 것 뿐이며 구분자가 다르더라도 해당되는 헤더 파일을 알아서 찾는다고 한다.
그런데 직접 만든 헤더 파일을 <>사용해 선언해 주었더니 fatal error C1083: 포함 파일을 열 수 없습니다. 라는 말과 함께 빌드에 실패해 검색 후 ""를 사용해 선언을 하였더니 정상 작동한다. 





하나의 DB에 테이블 8개, 총 레코드 수 200여개..
DB를 update식으로 초기화 하려고했더니 어플이 응답이 없어지면서 종료되는 현상이 일어난다..

db.beginTransaction();
   try {
     ...
     db.setTransactionSuccessful();
   } finally {
     db.endTransaction();
   }
트랜잭션을 사용하면 데이타를 빠르게 처리할 수 있다.
위 소스는 개발자사이트에서 Transaction을 검색한 결과.

자세히는 모르지만 쿼리를 날릴때마다 트랜잭션이 발생하는데 그걸 멈추었다가 모든 쿼리문을 날리고
트랜잭션을 발생시켜 쿼리를 한번에 처리하는 방식인듯 싶다..

	/* delete DB */
	public void deleteDB() {
		mDBManager.getWritableDatabase();

		mDB.beginTransaction();
		try {
			mDB.execSQL("DELETE FROM aaa;");
			mDB.execSQL("DELETE FROM bbb;");
			mDB.execSQL("DELETE FROM ccc;");
			mDB.execSQL("DELETE FROM ddd;");
			mDB.execSQL("DELETE FROM eee;");
			mDB.execSQL("DELETE FROM fff;");
			mDB.execSQL("DELETE FROM ggg;");
			initializeDatabases(mDB);
			mDB.setTransactionSuccessful();
		} finally {
			mDB.endTransaction();
		}
		
		mDBManager.close();
	}
속도도 빨라졌으니 그냥 테이블을 삭제시키고 처음 DB를 만들때 insert해주는 초기화 부분을 다시불러 초기화 해주었다.
응답이 없다고 종료되던 현상이 1초도 안걸리고 초기화 되었다 ~ ! 



'Android > Databases' 카테고리의 다른 글

데이터베이스 검색  (0) 2011.07.13
데이터베이스 삭제 및 갱신  (2) 2011.07.03
데이터베이스 업그레이드하기  (0) 2011.07.03
데이터베이스 사용하기  (1) 2011.07.02



먼저 설정창에 쓰일 layout 파일을 만든다.

option.xml
 

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:key="et_name"
android:title="이름"
/>
<CheckBoxPreference
android:key="cb_draw"
android:title="Drawing?"
android:summary="이미지 보기"
android:defaultValue="true"
/>
<PreferenceCategory
android:title="카테고리">
<PreferenceScreen
android:key="fluit"
android:title="과일">
<CheckBoxPreference
android:key="fluit_01"
android:title="사과"/>
<CheckBoxPreference
android:key="fluit_02"
android:title="배"/>
<CheckBoxPreference
android:key="fluit_03"
android:title="포도"/>
</PreferenceScreen>
</PreferenceCategory>
</PreferenceScreen>

레이아웃은 PrefenceScreen으로 해야하며 위 코드는 EditText, CheckBox, Category가 들어있다.
기본적으로 key, title, summary의 값을 지닌다. 



LiveWallPaPer_Canvas.java
package pe.berabue.livewallpaper;

import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.util.Log;

public class LiveWallPaPer_Canvas extends PreferenceActivity implements OnPreferenceClickListener, OnPreferenceChangeListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.layout.option);
        
        EditTextPreference editName = (EditTextPreference)findPreference("et_name");
        CheckBoxPreference checkDraw = (CheckBoxPreference)findPreference("cb_draw");
        editName.setOnPreferenceChangeListener(this);
        checkDraw.setOnPreferenceClickListener(this);
        
        // fluit category
        CheckBoxPreference checkFluit_01 = (CheckBoxPreference)findPreference("fluit_01");
        checkFluit_01.setOnPreferenceClickListener(this);
    }

	@Override
	public boolean onPreferenceClick(Preference preference) {
		if ( preference.getKey().equals("cb_draw")) {
			boolean isDraw = preference.getSharedPreferences().getBoolean("cb_draw", false);
			Log.v(null,"Click CheckPreference : "+isDraw);
			
			WallPaPer_Canvas.isDraw = isDraw;
		}
		if ( preference.getKey().equals("fluit_01") ) {
			Log.v(null,"Click fluit_01");
		}
		return false;
	}

	@Override
	public boolean onPreferenceChange(Preference preference, Object newValue) {
		if ( preference.getKey().equals("et_name") ) {
			preference.setSummary((CharSequence) newValue);
			Log.v(null,""+newValue);
		}
		return false;
	}
}
PreferenceActivity를 상속받는다.
addPreferencesFromResource(R.layout.option);
 먼저 작성해둔 option.xml 을 불러들인다.
xml파일에서 작성해둔 key값으로 리스너를 등록시킨다.

WallPaPer_Canvas.java 의 그림을 그려주는 부분에 isDraw를 추가시켜 놓았는데 Drawing?이 체크되면 이미지를 보이고 체크가 해제되면 이미지를 보여주지 않도록 바꾸어 놓았다.

WallPaPer_Canvas.jaca -> DrawFrame();
if (canvas != null) {
	if ( isDraw )
		canvas.drawBitmap(imgIcon, 0, 0, null);
}
 



ex.xml
 

<?xml version="1.0" encoding="UTF-8"?> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" android:thumbnail="@drawable/icon" android:description="@string/test" android:settingsActivity="pe.berabue.livewallpaper.LiveWallPaPer_Canvas"/>

ex.xml 파일내의 settingsActivity 옵션에 "패키지명.액티비티명"을 입력 시키면 완성 ~



'Android > Live WallPaper' 카테고리의 다른 글

[ Live WallPaper - 02 ] 이미지 띄우기  (9) 2011.06.10
[ Live Wallpaper - 01 ] 기본 구조  (0) 2011.06.10



데이터베이스의 레코드를 검색하는 방법을 알아본다.

검색 메서드는 다른 SQL문과는 달리 결과셋을 리턴해야 하므로 execSQL 메서드가 아닌 rawQuery 메서드를 실행해야 한다고 한다.

		int index = 0;
		cursor = mDB.rawQuery("SELECT item, price FROM shop", null);
		while(cursor.moveToNext()) {
			arrShop[index][0] = cursor.getInt(0);
			arrShop[index][1] = cursor.getInt(1);
	        index ++;
		}

shop 이라는 테이블안에 item, price 라는 레코드가 들어있음.
cursor 를 moveToNext();로 하나씩 레코드를 검색한다. 만약 더이상 레코드가 없으면 while문을 빠져나오게 된다.
레코드의 순서를 미리 알고있다면 cursor.getInt(순서); 식으로 불러오면된다.

shop 테이블 안에 레코드의 순서는 item, price 순서대로 만들어 놓았으므로 각각 0번과 1번을 갖게된다.





앞서 데이터베이스의 간단한 사용방법과 데이터의 추가, 업그레이드 방법을 알아보았다.
이번에는 데이터 삭제 및 갱신하는 방법에 대하여 알아본다.
기존에 테스트하던 앱을 제거 후 새로운코드로 재설치 하였다.

DB_Test.java
package pe.berabue.dbtest;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class DB_Test extends Activity {
	
	private DBManager mDBManager;
	
	/** Called when the activity is first created. */
	@Override
 	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		findViewById(R.id.btn_insert).setOnClickListener(mClickListener);
		findViewById(R.id.btn_delete).setOnClickListener(mClickListener);
		findViewById(R.id.btn_update).setOnClickListener(mClickListener);
		findViewById(R.id.btn_select).setOnClickListener(mClickListener);
		
		mDBManager = new DBManager(this);
		mDBManager.getReadableDatabase();
	//	mDBManager.getWritableDatabase();
		mDBManager.close();
    }
	
	Button.OnClickListener mClickListener = new View.OnClickListener() {
		public void onClick(View v) {
			switch(v.getId()) {			
			case R.id.btn_insert:	mDBManager.insert(mDBManager);	break;
			case R.id.btn_delete:	mDBManager.delete(mDBManager);	break;
			case R.id.btn_update:	mDBManager.update(mDBManager);	break;
			case R.id.btn_select:	mDBManager.select(mDBManager);	break;
			}
		}
	};
}
액티비티에 4개의 버튼을 구현해 놓고 각기 다른 기능을 넣어두었다.
4번째 버튼인 select버튼은 미구현.


DBManager.java
package pe.berabue.dbtest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBManager extends SQLiteOpenHelper {

	public static final String	DB_NAME		= "dbtest.db";
	public static final int		DB_VERSION	= 1;
	
	private SQLiteDatabase mDB;
	private String str;
	private int temp;
	
	public DBManager(Context context) {
		super(context, DB_NAME, null, DB_VERSION);
		
		str = "str_";
		temp = 0;
	}
	
	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE Android( _id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT, price INTEGER);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Cupcake'			, 500		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Donut'			, 1000		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Eclair'			, 1500		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Froyo'			, 10000		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Ginger bread'	, 100000	);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Honeycomb'		, 999999	);");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("INSERT INTO Android VALUES (null, 'Icecream Sandwich'	, 1	);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Jellybean'			, 2	);");
	}
	
	@Override
	public void onOpen(SQLiteDatabase db) {
		super.onOpen(db);
		mDB = db;
	}

	public void insert(DBManager mDBManager) {
		mDBManager.getWritableDatabase();
		mDB.execSQL("INSERT INTO Android VALUES (null, '"+ str +"', "+ temp +" );");
		mDBManager.close();
		
		str += temp;
		temp++;
		Log.v(null,"Insert DB : "+temp);
	}
	
	public void delete(DBManager mDBManager) {
		mDBManager.getWritableDatabase();
		mDB.execSQL("DELETE FROM Android WHERE price = "+ (temp-1) +";");
		mDBManager.close();
		
		temp--;
		Log.v(null,"Delete DB : "+temp);
	}
	
	public void update(DBManager mDBManager) {
		mDBManager.getWritableDatabase();
		mDB.execSQL("UPDATE Android SET price = "+ (temp+10) +" WHERE price = "+ (temp-1) +";");
		mDBManager.close();

		Log.v(null,"Update DB : "+temp);
	}
	
	public void select(DBManager mDBManager) {

	}
}
open();
 DB를 열때 mDB에 객체를 저장시켜 놓는다.
insert();
 DB를 읽기/쓰기 용으로 열고 name과 price를 입력시켜 테이블에 추가한다.
delete();
 DB를 읽기/쓰기 용으로 열고 Android 테이블에 price가 temp-1인 데이터를 찾아 삭제시킨다.
update(); 
 DB를 읽기/쓰기 용으로 열고 Android 테이블에 price가 temp-1인 데이터를 찾아 price를 temp+10값으로 변경시킨다. 


main.xml


    



'Android > Databases' 카테고리의 다른 글

데이터베이스 Transaction 사용하기  (0) 2011.07.26
데이터베이스 검색  (0) 2011.07.13
데이터베이스 업그레이드하기  (0) 2011.07.03
데이터베이스 사용하기  (1) 2011.07.02



데이터를 넣어 앱을 배포하였는데 다음버전에서 데이터가 추가되거나 삭제되어야 한다면?
데이터베이스 버전을 바꾸고 재배포 하면된다.


package pe.berabue.dbtest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBManager extends SQLiteOpenHelper {

	public static final String	DB_NAME		= "dbtest.db";
	public static final int		DB_VERSION	= 2;
	
	
	public DBManager(Context context) {
		super(context, DB_NAME, null, DB_VERSION);
	}
	
	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE Android( _id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT, price INTEGER);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Cupcake'			, 500		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Donut'			, 1000		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Eclair'			, 1500		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Froyo'			, 10000		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Ginger bread'	, 100000	);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Honeycomb'		, 999999	);");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("INSERT INTO Android VALUES (null, 'Icecream Sandwich'	, 1	);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Jellybean'			, 2	);");
	}
}
만약 기존의 DB배포 버전이 1이였다면, 재배포 하는 DB의 버전을 2로 올려준다.
그러면 DB의 버전이 바뀌었으므로 DB가 실행될때 자동으로 onUpgrade(); 메서드를 호출한다.
 


2011/07/02 - [Android/Databases] - 데이터베이스 사용하기
 
위 소스에 onUpgrade(); 부분과 DB버전만 변형시켜서 실행해보았다.
7,8번 데이터가 추가된 것을 확인 할 수 있다. 



'Android > Databases' 카테고리의 다른 글

데이터베이스 Transaction 사용하기  (0) 2011.07.26
데이터베이스 검색  (0) 2011.07.13
데이터베이스 삭제 및 갱신  (2) 2011.07.03
데이터베이스 사용하기  (1) 2011.07.02



프로젝트를 진행하다보면 데이터를 저장해야 할 때가 있다.
데이터를 저장하는 방법은 프레프런스, 파일, DB 등이 있다.

SQLite는 다른 데이터베이스에 비해 사용 가능한 자료형이 많지않고 5가지를 지원한다.
NULL
INTEGER : 1,2,3,4,6,8bytes의 정수값
REAL : 8bytes의 부동소수점값
TEXT : UTF-8, UTF-16BE, UTE-16LE 인코딩의 문자열
BLOB : 입력된 그대로 저장

SELECT 명령을 제외한 대부분의 명령을 execSQL(String sql); 메서드로 실행 할 수 있다. execSQL("CREATE TABLE 테이블명( _id INTEGER PRIMARY KEY AUTOINCREMENT,변수 자료형);");
 _id 필드는 자동으로 값이 증가한다. 변수+자료형 필드는 콤마(,)를 사용해 여러개를 지정 할 수 있다.
execSQL("INSERT INTO 테이블명 VALUES (데이터);");
 레코드 추가
execSQL("DELETE FROM 테이블명 WHERE 조건;");
 레코드 삭제, WHERE문을 적지 않으면 테이블 삭제
execSQL("UPDATE 테이블명 SET 갱신내용 WHERE 조건;");
 레코드 업데이트
 

DB_Test.java
package pe.berabue.dbtest;

import android.app.Activity;
import android.os.Bundle;

public class DB_Test extends Activity {
	
	private DBManager mDBManager;
//	private SQLiteDatabase mDB;
	
	/** Called when the activity is first created. */
	@Override
 	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
        
		mDBManager = new DBManager(this);
		mDBManager.getReadableDatabase();
	//	mDBManager.getWritableDatabase();
		mDBManager.close();
    }
}
기능이 있는 화면은 아니다.
mDBManager.getReadableDatabase();  읽기전용으로 DB를 불러온다. 이 때 생성된 DB가 없으면 onCreate(); DB가 있지만 버전이 바뀌었다면 onUpgrade();를 호출한다.
mDBManager.getWritableDatabase();  읽고/쓰기가 가능하다. getReadableDatabase();과 마찬가지로 onCreate(); onUpgrade();를 호출. mDBManager.close();
 위 두 구문을 실행 한 뒤에 호출하여 DB를 닫아준다.


DBManager.java
package pe.berabue.dbtest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBManager extends SQLiteOpenHelper {

	public static final String	DB_NAME		= "dbtest.db";
	public static final int		DB_VERSION	= 1;
	
	
	public DBManager(Context context) {
		super(context, DB_NAME, null, DB_VERSION);
	}
	
	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL("CREATE TABLE Android( _id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT, price INTEGER);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Cupcake'			, 500		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Donut'			, 1000		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Eclair'			, 1500		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Froyo'			, 10000		);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Ginger bread'	, 100000	);");
		db.execSQL("INSERT INTO Android VALUES (null, 'Honeycomb'		, 999999	);");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// TODO Auto-generated method stub
	}

}
생성할 DB의 이름과 버전을 상수로 정의해 놓았다.
생성자에서 DB이름과 버전을 정의해 놓고 DB가 사용될때 버전이 더 높다면 onUpgrade();를 통해 내용을 수정시킬 수 있다.
db.execSQL("INSERT INTO 테이블명 VALUES (데이터);");
 데이터를 삽입할때 사용하는 쿼리문.
Android 라는 테이블 하나를 생성하고 Android 테이블에 안드로이드 코드명 C ~ H의 코드네임과 멋대로 가격을 입력시켜 놓았다.
실제 단말기에서 DB를 열어보려면 루팅을 해야 하고, 에뮬레이터로 실행시키면 DDMS를 통해 DB파일이 생성되었는지 확인이 가능하다.

명령프롬프트 창을 이용해 DB를 들여다보자.



데이터베이스는 data/data/패키지명/databases 경로에 저장되어있다.
해당경로로 이동을 하고 'sqlite3 DB명'을 사용해 저장된 DB를 실행시키면 버전과 도움말을 보는 방법을 알려준다.
해당DB의 테이블을 보고싶다면 .tables를 입력한다.
위에서 생성한 Android라는 테이블과 기본적인 android_metadata라는 테이블이 존재한다.
테이블안에 들어있는 데이터를 보려면 select * from 테이블명; 을 입력한다.

입력한 그대로 1번부터 6번까지의 데이터가 출력된다.
 




'Android > Databases' 카테고리의 다른 글

데이터베이스 Transaction 사용하기  (0) 2011.07.26
데이터베이스 검색  (0) 2011.07.13
데이터베이스 삭제 및 갱신  (2) 2011.07.03
데이터베이스 업그레이드하기  (0) 2011.07.03



    	if ( Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
    		String path = Environment.getExternalStorageDirectory()+"/android/data/pe.berabue.maptools/.image";
    		File file = new File(path);

    		if( !file.exists() ) {// 원하는 경로에 폴더가 있는지 확인
    			file.mkdirs();

    			for ( int i = 0; i < 27; i++ ) {
    				byte [] buffer = new byte[8*1024];
    				int length = 0;
    				InputStream is = this.getResources().openRawResource(R.drawable.map1+i);  
    				BufferedInputStream bis = new BufferedInputStream(is);    

    				try {
    					FileOutputStream fos = new FileOutputStream(path+"/map"+(i+1)+".png");
    					while ((length = bis.read(buffer)) >= 0)
    						fos.write(buffer, 0, length);
    					fos.flush();  
    					fos.close();
    				} catch (Exception e) {} 
    			}
    		}
    	}

프로젝트 drawable 폴더안에 있는 png파일을 sdCard로 옮기는 방법.

먼저 sdCard가 연결이 되어 있는지 확인을 한다.
그리고 파일을 옮겨놓을 경로를 path에 입력을 시킨다.
원하는 경로의 폴더가 있는지 확인 후 없으면 새로 만들어준다.

for문이 28번 돌아가는 이유는 테스트할때 이미지가 28개였기때문에..

조금 변형시키면 raw폴더안 파일이나 다른 파일들도 충분히 이동 가능할듯!



'Android > File' 카테고리의 다른 글

ZipEntry를 활용한 압축해제  (0) 2013.06.02
단말기 내부에 폴더 및 txt파일 생성하기  (0) 2011.03.02
Sd Card 이미지 읽어오기  (0) 2011.02.14
txt 파일 읽어오기  (0) 2011.02.11
SD Card에 txt 파일로 저장하기  (0) 2011.02.05



WallPePer_Canvas.java

package pe.berabue.livewallpaper;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class WallPaPer_Canvas extends WallpaperService {
    	
	private final Handler mHandler = new Handler();
	
	@Override
	public void onCreate() {
		super.onCreate();
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
	}

	@Override
	public Engine onCreateEngine() {
		return new CanvasEngine();
	}

	private class CanvasEngine extends Engine {  

		private Bitmap imgIcon;
		
		private final Runnable mRunnable = new Runnable() {
			public void run() {
				drawFrame();
			}
		};
		private boolean isVisible;
	        
		public CanvasEngine() {
			imgIcon = BitmapFactory.decodeResource(getApplicationContext().getResources(), R.drawable.icon);
		}
		
		@Override
		public void onCreate(SurfaceHolder surfaceHolder) {
			super.onCreate(surfaceHolder);
			setTouchEventsEnabled(true);
		}

		@Override
		public void onDestroy() {
			super.onDestroy();
			mHandler.removeCallbacks(mRunnable);
		}

		@Override
		public void onVisibilityChanged(boolean visible) {
			isVisible = visible;

			if (visible) {
				drawFrame();
			} else {
				mHandler.removeCallbacks(mRunnable);
			}
		}

		@Override
		public void onSurfaceCreated(SurfaceHolder holder) {
			super.onSurfaceCreated(holder);
		}
		
		@Override
		public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
			super.onSurfaceChanged(holder, format, width, height);
			Log.v(null," :::: onSurfaceChanged : "+format+" / "+width+", "+height);
			drawFrame();
		}

		@Override
		public void onSurfaceDestroyed(SurfaceHolder holder) {
			super.onSurfaceDestroyed(holder);
			isVisible = false;
			mHandler.removeCallbacks(mRunnable);
		}

		@Override
		public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
			Log.v(null," :::: onOffsetsChanged : "+xOffset+", "+yOffset+", "+xStep+", "+yStep+", "+xPixels+", "+yPixels);
			drawFrame();
		}

		@Override
		public void onTouchEvent(MotionEvent event) {
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Log.v(null," :::: onTouchEvent");
			}
			super.onTouchEvent(event);
		}

		public void drawFrame() {
			final SurfaceHolder holder = getSurfaceHolder();

			Canvas canvas = null;
			try {
				canvas = holder.lockCanvas();
				if (canvas != null) {
					canvas.drawBitmap(imgIcon, 0, 0, null);
				}
			} finally {
				if (canvas != null)
					holder.unlockCanvasAndPost(canvas);
			}

			mHandler.removeCallbacks(mRunnable);
			if (isVisible) {
				mHandler.postDelayed(mRunnable, 1000 / 25);
			}
		}
	}
}

mHandler를 등록하고 이미지를 준비시킨다.

onVisibilityChanged();  화면이 보여지고 있는지 가려졌는지를 알아내 isVisible에 넣는다.  화면이 보여지고 있다면 drawFrame();으로 들어간다. drawFrame();  실직적으로 이미지가 그려지는 곳.  이미지 처리를 완료하고 핸들러를 제거한다. 화면이 계속 보여지는 중이라면 원하는 딜레이로 핸들러를 다시 등록하여준다.
 

화면이 가려지거나(다른어플 실행, 화면꺼짐 등) 해당 Live Wallpaper를 종료시킬때 핸들러를 제거하여준다.
아래 화면과 같이 타이틀바 영역을 포함하여 좌표가 시작된다.


 



'Android > Live WallPaper' 카테고리의 다른 글

[ Live WallPaper - 03 ] 설정 화면 만들기  (0) 2011.07.15
[ Live Wallpaper - 01 ] 기본 구조  (0) 2011.06.10



AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="pe.berabue.livewallpaper"
      android:versionCode="1"
      android:versionName="1.0">

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".LiveWallPaPer_Canvas"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

  <service android:name=".WallPaPer_Canvas"  
        android:label="@string/app_name"  
        android:permission="android.permission.BIND_WALLPAPER">  
<intent-filter>  
<action android:name="android.service.wallpaper.WallpaperService"/>  
</intent-filter>  
<meta-data android:name="android.service.wallpaper" android:resource="@xml/ex"/>  
</service>  
    </application>
    
    <uses-sdk android:minSdkVersion="7" />
    <uses-feature android:name="android.software.live_wallpaper" />
</manifest>

xml파일을 syntaxhighlighter로 변환하면 이상하게 /> 문자열이 자동으로 바뀌어버리는......
아무튼, Activity는 필수가 아니다. 기본적으로 service부분과 uses-feature 부분만 넣어주면된다.



ex.xml

<?xml version="1.0" encoding="UTF-8"?>
<wallpaper 
    xmlns:android="http://schemas.android.com/apk/res/android"  
    android:thumbnail="@drawable/icon" 
    android:description="@string/test"
    android:settingsActivity="PreferenceActivity"/>

Live Wallpaper를 적용시킬때 나오는 아이콘과 설명 그리고 설정화면을 등록해준다.
android:settingsActivity부분을 넣지않으면 설정 버튼이 나오지 않는다.



WallPePer_Canvas.java

package pe.berabue.livewallpaper;

import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class WallPaPer_Canvas extends WallpaperService {
    	
	@Override
	public void onCreate() {
		super.onCreate();
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
	}

	@Override
	public Engine onCreateEngine() {
		return new CanvasEngine();
	}

	private class CanvasEngine extends Engine {  
        
		public CanvasEngine() {

		}
		
		@Override
		public void onCreate(SurfaceHolder surfaceHolder) {
			super.onCreate(surfaceHolder);
		}

		@Override
		public void onDestroy() {
			super.onDestroy();
		}

		@Override
		public void onVisibilityChanged(boolean visible) {

		}

		@Override
		public void onSurfaceCreated(SurfaceHolder holder) {
			super.onSurfaceCreated(holder);
		}
		
		@Override
		public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
			super.onSurfaceChanged(holder, format, width, height);
			Log.v(null," :::: onSurfaceChanged : "+format+" / "+width+", "+height);
		}

		@Override
		public void onSurfaceDestroyed(SurfaceHolder holder) {
			super.onSurfaceDestroyed(holder);
		}

		@Override
		public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
			Log.v(null," :::: onOffsetsChanged : "+xOffset+", "+yOffset+", "+xStep+", "+yStep+", "+xPixels+", "+yPixels);
		}

		@Override
		public void onTouchEvent(MotionEvent event) {
			if (event.getAction() == MotionEvent.ACTION_DOWN) {
				Log.v(null," :::: onTouchEvent");
			}
			super.onTouchEvent(event);
		}
	}
}

Activity대신 WallpaperService를 상속받는데 그러면 onCreateEngine() 메서드를 구현해 주어야 한다. Engine을 상속받은 CanvasEngine클래스가 중요한 부분. 클래스 이름에 Canvas를 붙인 이유는.. 목표가 OpenGL용 live wallpaper이기 때문에...(?)

onVisibilityChanged();  메서드는 화면이 보여질때와 가려질때를 알아낸다. onOffsetsChanged();  화면을 밀어 옆으로 이동할때 실행된다. 



+ Recent posts