소켓 통신 구현

2012. 5. 15. 20:50

[안드로이드] 서버/클라이언트 소켓(Socket) 통신하기

 

 추가: 설명은 퍼왔으나 소스 부분은 자체적으로 수정 했습니다.

 

<목표> [안드로이드] 서버/클라이언트 소켓(Socket) 통신하기

     

   

   오늘은  서 버, 클라이언트의 소켓(Soket) 통신에 대해서 알아보겠습니다. 기존의 많은 안드로이드 어플리케이션이 각각의 서버를 이용하여 정보를 주고 받습니다. 아무래도 기기 내에서 만으로 서비스하기에는 한계가 있기 때문이죠. 정보를 저장하고, 서버에서 처리하여 결과를 주고, 클라이언트는 그 결과를 받아서 어플리케이션에 알맞은 동작을 취하도록 합니다. 트위터 서비스나 스마트폰을 이용해서 공짜 문자(통신료 제외)를 주고 받을 수 있는 것도 서비스를 제공하는 곳에서 서버를 두기 때문입니다. 그 덕분에 핸드폰을 벗어나 더 많은 정보를 처리할 수 있도록 할 수 있습니다.

 

   서 버/클라이언트 소켓 통신은 기존의 자바를 이용해서 소켓 통신을 해보신 분들이라면 어렵지 않게 사용하실 수 있습니다. 서버의 소스 자체는 완전히 자바 소스로 이루어지기 때문이지요. 실제로 서버는 안드로이드를 통해서 돌리는 것이 아니라, 기존의 자바 프로그래밍을 이용하여 수행합니다. 클라이언트는 당연히 안드로이드로 개발을 해야겠지요. 오늘 보여드릴 예제 소스는 인터넷에 돌아다니는 간단한 서버 – 클라이언트 소켓 통신을 가지고 와서 나름대로 수정을 해본 것입니다. 기존의 샘플 코드가 하나의 메시지를 서버로 보내고 난 뒤에, 바로 클라이언트에서 기다리면서 데이터가 오기를 기다리는 형태로 제공되었습니다. 그렇기 때문에 클라이언트가 계속 데이터를 기다리면서 블록킹 되어 있는 상태에서만 프로그래밍이 돌아갔습니다. 이러한 부분을 수정하여 쓰레드를 이용하여 백그라운드에서 돌아가게 하여, 기존의 클라이언트에서는 원래의 프로그램이 수행되고, 서버에서 오는 정보를 받는 부분은 쓰레드를 통해 해결했습니다.

 

    먼저 서버를 만들 자바 코드를 알아보고, 뒤에는 안드로이드에서 소켓 통신을 위한 설정과정을 간단한 예제를 통해서 알아보겠습니다.

     

     

     

STEP 1  Java Source Code

     

   자바 코드는 두 가지를 다루게 됩니다. 처음은 서버를 돌리는 데 필요한 자버 코드를 알아보고, 두 번째는 안드로이드 클라이언트 코드를 알아보겠습니다.

 

 

  [[ 서버 ]]   TCP Server Java Code

 

package kr.or.ddit;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class JavaServer {

    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            System.out.println("Starting Java Socket Server ......");
            ServerSocket serverSocket = new ServerSocket(5001);
            System.out.println("Listening at port : 5001");

            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println(getTime() + " host : "
                        + socket.getInetAddress() + ", port : "
                        + socket.getPort());

                ObjectInputStream inputStream = new ObjectInputStream(
                        socket.getInputStream());
                String inputString = inputStream.readUTF();
                System.out.println("ID : " + inputString);

                ObjectOutputStream outputStream = new ObjectOutputStream(
                        socket.getOutputStream());
                outputStream.writeUTF(inputString);
                outputStream.flush();
                socket.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    static String getTime() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd [hh:mm:ss]");
        return format.format(new Date());
    }

}

 

    서버의 기능은 클라이언트에서 오는 데이터를 받아들이는 게 핵심입니다. 자신의 포트를 세팅하여 소켓을 여는 것부터 시작하여, 클라이언트에서 오는 정보를 받기 위해서 accept() 를 통해서 기다립니다. 그리고 데이터가 왔다는 신호가 오면 리더를 통해서 스트림을 읽어냅니다. 그리고 자신이 받은 스트림 정보를 다시 돌려보내는 역할을 합니다. 현재는 하나의 클라이언트와 컨넥트를 통해 데이터를 주고 받는 형식입니다. 많은 클라이언트와 통신하고 싶으면 클라이언트에 대한 정보를 저장하고, 여러 클라이언트에게 적절하게 보내는 기능을 추가하시면 되겠습니다.

 

 

   [[ 클라이언트 ]]   TCP Client Java Code

 

 


======================================================================================================
AndroidSocketClientActivity.java
======================================================================================================
package kr.or.ddit;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class AndroidSocketClientActivity extends Activity {
    TextView textView;
    EditText editText;
    String myName = "";
    String serverIp = "xxx.xxx.xx.xx"; //서버의 아이피를 적어야 함.
    int portNum = 50001;

    Handler handler = new Handler();

    ClientThread thread;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        textView = (TextView) findViewById(R.id.textView);
        editText = (EditText) findViewById(R.id.editText);
        Button requestBtn = (Button) findViewById(R.id.requst);

        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                textView.append(msg.getData().getString("msg") + "\r\n");
            }
        };

        thread = new ClientThread(handler, serverIp, portNum, myName);
        thread.start();

        requestBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                thread.writeMessage(editText.getText().toString());
                editText.setText("");
            }
        });
    }

}



====================================================================================================
ClientThread.java
====================================================================================================
package kr.or.ddit;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

public class ClientThread extends Thread {
    static DataOutputStream outputStream;
    static Socket socket;
    private SendService sendService;
    private ReceiverService receiverService;
    private String name;
   
    public ClientThread(Handler handler, String ip, int port, String name) {
        sendService = new SendService();
        sendService.start();
        receiverService = new ReceiverService(handler, ip, port);
        receiverService.start();
        this.name = name;
        try {
            outputStream.writeUTF(name);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
   
    public void writeMessage(String msg) {
        sendService.writeMessage(msg);
    }
   
    class SendService extends Thread {
        public void writeMessage(String msg) {
            try {
                if (ClientThread.socket != null && ClientThread.socket.isConnected()) {

                    outputStream.writeUTF("[" + name + "]" + msg);
                    outputStream.flush();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
   
    class ReceiverService extends Thread {
        private DataInputStream inputStream;
        private Handler handler;
       
        public ReceiverService(Handler handler, String ip, int port) {
            this.handler = handler;
            try {
                socket = new Socket(ip, port);
                inputStream = new DataInputStream(socket.getInputStream());
                outputStream = new DataOutputStream(socket.getOutputStream());
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
       
        @Override
        public void run() {
            try {
//                while (socket != null && socket.isConnected()) {
                while (inputStream != null) {
                    String receiveMessage = inputStream.readUTF().toString();
                    Message message = handler.obtainMessage();
                    Bundle bundle = new Bundle();
                    bundle.putString("msg", receiveMessage);
                    message.setData(bundle);
                    handler.sendMessage(message);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
       
        public void cancel() {
            try {
                socket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

 

 

   이 번에 살펴볼 코드는 안드로이드 클라이언트 프로그램입니다. 안드로이드 클라이언트 코드에서는 서버에 소켓을 연결하고 받은 정보를 이용하여 프로그램을 수행시키는 것을 다룹니다. 먼저 onCreate()가 되면 서버에 연결하도록 설계되어 있습니다. IP 주소와 포트 번호를 알맞게 설정해주시고, 소켓을 연결합니다. 그리고 데이터를 주고 받기 위해서 리드, 라이터를 설정하여 둡니다. 예제에서는 간단히 텍스트 박스에 문자를 적고 버튼을 누르면, 서버로 데이터를 보냅니다. 그리고 서버에서 오는 데이터를 계속 받기 위해서 while을 쓰레드를 통해서 실행합니다. 여기서 받은 데이터 정보를 토스트 기능을 통해 출력합니다. 여기서 UI에 대한 접근을 핸들러를 통해서 하고 있는 것을 확인할 수 있습니다. 이렇게 하는 이유는 다른 포스트에 올리겠습니다. 일단 UI에 대한 접근은 핸들러를 통해서 수행한다고 생각하시고 프로그램을 수행해야한다는 것만 기억하시고 계시면 될 것 같습니다.

       

 

STEP 2  Xml Code

       

    Xml 코드에는 간단히 EditText에서 넣은 문자열을 버튼을 통해 서버로 데이터를 보내는 기본적인 기능만 추가하시면 됩니다.

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffffff" >

    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <TextView
            android:id="@+id/textView"
            android:layout_width="fill_parent"
            android:layout_height="88dp"
            android:background="#ffffaadd"
            android:textColor="#ff000000" />
    </ScrollView>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/editText"
            android:layout_width="250dp"
            android:layout_height="wrap_content"
            android:hint="@string/editText" />

        <Button
            android:id="@+id/requst"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/request" />
    </LinearLayout>

</RelativeLayout>

 

        

     

STEP 3  AndroidManifest.xml Code

     

    메니페스트에는 소켓을 이용하기위해 인터넷을 사용해야 하므로, 인터넷을 사용하겠다는 퍼미션만 추가해주시면 됩니다.

       

AndroidManifest.xml 에 추가해야할 인터넷 사용 허가권

<uses-permission android:name="android.permission.INTERNET" />

 

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

    <uses-sdk android:minSdkVersion="8" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-permission android:name="android.permission.INTERNET" />
</manifest>

     

     

     

 < 마무리 >   서버/클라이언트 소켓(Socket) 통신하기

     

 

   서버와 클라이언트의 소켓 통신에 대해서 알아보았습니다. 핸드폰 내에서의 기능과 정보만으로는 어플리케이션이 한정적일 수 밖에 없습니다. 그렇기 때문에 통신을 통해서 좀 더 폭넓은 서비스를 제공하기 위해서 서버와 클라이언트가 서로 통신할 수 있도록 소켓에 대해서 알아볼 필요가 있었습니다. 하나의 서버가 여러 곳의 클라이언트들에 대해서 서비스를 해줘야 할 때는 클라이언트에 대한 정보를 가지고 있으면서 소켓을 각각 연결시켜주어야 합니다. 안드로이드 클라이언트 부분에서도 현재 수행되고 있는 동작에 영향을 받지 않으면서 백그라운드에서 쓰레드가 돌면서 서버에서 오는 정보를 수시로 받을 수 있도록 해야합니다. 소켓 통신에 대한 기존 개념을 알고 계신 분들이라면 크게 어렵지 않게 이해하실 수 있으셨으리라 봅니다. 처음 접하시는 분들은 데이터를 주고 받는 부분에서 자신이 수행하고 싶은 기능만 추가해주시면 간단히 소켓 통신을 하실 수 있으리라 생각합니다.

   

   

   

[참고자료]  


'안드로이드' 카테고리의 다른 글

You must supply a layout_width attribute  (0) 2012.05.15
Progressbar  (0) 2012.05.15
안드로이드 색상표  (0) 2012.05.11
안드로이드 강좌 모음 pdf&동영상 강좌.  (0) 2012.05.11
화면회전  (0) 2012.05.08
Posted by 사라링

발생 : 인텐트로 .ItemEdit 액티비티를 호출 할 때 발생 

10-20 09:00:59.928: DEBUG/dalvikvm(207): GC freed 43 objects / 2096 bytes in 235ms
10-20 09:01:05.298: DEBUG/dalvikvm(107): GC freed 2132 objects / 124248 bytes in 232ms
10-20 09:01:32.229: WARN/KeyCharacterMap(321): No keyboard for id 0
10-20 09:01:32.238: WARN/KeyCharacterMap(321): Using default keymap: /system/usr/keychars/qwerty.kcm.bin
10-20 09:01:33.140: INFO/ActivityManager(52): Starting activity: Intent { cmp=com.misun.smileproj.timemetrix/.ItemEdit }
10-20 09:01:33.269: DEBUG/AndroidRuntime(321): Shutting down VM
10-20 09:01:33.269: WARN/dalvikvm(321): threadid=3: thread exiting with uncaught exception (group=0x4001b188)
10-20 09:01:33.278: ERROR/AndroidRuntime(321): Uncaught handler: thread main exiting due to uncaught exception
10-20 09:01:33.310: ERROR/AndroidRuntime(321): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.misun.smileproj.timemetrix/com.misun.smileproj.timemetrix.ItemEdit}: java.lang.RuntimeException: Binary XML file line #3: You must supply a layout_width attribute.
...

원인 : 레이아웃 설정파일에서 위젯의 layout_height 과 layout_width를 제대로 명시하지 않았다. 
해결 : 필수 속성값 명시하기


'안드로이드' 카테고리의 다른 글

소켓 통신 구현  (0) 2012.05.15
Progressbar  (0) 2012.05.15
안드로이드 색상표  (0) 2012.05.11
안드로이드 강좌 모음 pdf&동영상 강좌.  (0) 2012.05.11
화면회전  (0) 2012.05.08
Posted by 사라링

Progressbar

2012. 5. 15. 09:46
Progress Dialog 생성하기


프로그레스 다이얼로그 ( Progress Dialog ) 는 AlertDialog 클래스를 상속받은 클래스 입니다. 이것은 끝나는 시점이 명확하지 않은 상태의 태스크에 대한 진행상황을 '진행바퀴' 로 보여줍니다.

끝나는 시점이 정해진 태스크라면 좀 더 명확하게 '진행바' 로 보여주는것도 좋겠네요

이 다이얼로그는 버튼을 제공할 수도 있습니다. ProgressDialog 의 구현은 ProgressDialog.show() 메소드를 호출하는 것만으로 처리할 수 있습니다.


show() 메소드는 모두 static 메소드라는 점에 주의하시면 되겠습니다.


    ProgressDialog dialog = ProgressDialog.show(AndroidTest.this, "",
    "로딩 중입니다. 잠시 기다려주세요", true);





show() 메소드의 첫번째 인자는 어플리케이션의 컨텍스트, 두번재는 다이얼로그 타이틀, 세번째는 텍스트 내용, 네번째는 '진행바'에서 사용되는 루프 진행 여부를 나타냅니다.

프로그레스 다이얼로그의 디폴트 스타일은 '진행바퀴' 입니다.


진행바 표시하기



명확한 진행율을 보여주는 진행바를 만들기 위해선 아래와 같은 방법을 일반적으로 사용합니다.
1. 생성자인 ProgressDialog(Context context) 로 초기화 합니다.
2. 진행 스타일을 setProgressStyle() 메소드에 "STYLE_xxx" 로 지정하고 그외 속성들을 지정합니다
3. 다이얼로그 표시를 위해 show() 메소드를 호출 하거나, onCreateDialog() 에서 ProgressDialog 객체를 리턴해도 됩니다.
4. 전체 값을 setProgress() 로 지정하거나 incrementProgressBy(int) 메소드로 현재 진행양에 증가분 값을 더할 수 있습니다.


ProgressDialog progressDialog;
progressDialog = new ProgressDialog(AndroidTest.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("로딩중입니다...");
progressDialog.setCancelable(false);
progressDialog.show();


 


직접 소스코드를 테스트 해보시는 분들도 계시겠지만, 직접 테스트해보면 다이얼로그가 시작되면 그 다이얼로그가 끝나서 종료되어 끝나기 전에는 해당 액티비티에서 "BACK" 버튼을 눌러도 반응을 하지 않습니다.

진행상태라는것의 특성상 어떤 진행 상태는 오래걸릴수도 있고 다른 기타 이유들 때문에 별도의 스레드에서 처리하고 , 메인 스레드는 계속 사용자에게 즉각 반응을 해야되는데요, 이 작업을 위해선 스레드를 새로 생성 후 핸들러를 이용하여 메인스레드에게 결과를 알려주는 방식을 써야 합니다.


별도의 스레드에서 ProgressBar 구현

진행상태 표시와 처리를 위하여 , 별도의 스레드를 만들고 그 스레드는 진행이 이루어질 때 마다 핸들러를 통해 메인 스레드로 알려주면 됩니다. 그러면 메인 액티비티에서 Progress 다이얼로그를 업데이트하게 하면 되겠지요.


package exam.androidtest;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class NotificationTest extends Activity {
static final int PROGRESS_DIALOG = 0;
Button button;
ProgressThread progressThread;
ProgressDialog progressDialog;

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

button = (Button) findViewById(R.id.btn);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
showDialog(PROGRESS_DIALOG);
}
});
}

protected Dialog onCreateDialog(int id) {
switch (id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(NotificationTest.this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("Loading...");
progressThread = new ProgressThread(handler);
progressThread.start();
return progressDialog;
default:
return null;
}
}

// 핸들러는 정의하여 스레드가 메시지를 보내면 프로그레스를 업데이트 합니다.
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
int total = msg.getData().getInt("total");

progressDialog.setProgress(total);
if (total >= 100) {
dismissDialog(PROGRESS_DIALOG);
progressThread.setState(ProgressThread.STATE_DONE);
}
}
};

/** 프로그레스를 처리하는 클래스를 내부 클래스로 정의. */
private class ProgressThread extends Thread {
Handler mHandler;
final static int STATE_DONE = 0;
final static int STATE_RUNNING = 1;
int mState;
int total;

ProgressThread(Handler h) {
mHandler = h;
}

public void run() {
mState = STATE_RUNNING;
total = 0;
while (mState == STATE_RUNNING) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 에러처리
}
Message msg = mHandler.obtainMessage();
Bundle b = new Bundle();
b.putInt("total", total);
msg.setData(b);
mHandler.sendMessage(msg);
total++;
}
}

// 현재의 상태를 설정하는 메소드
public void setState(int state) {
mState = state;
}
}
}





AsyncTask 를 이용한 프로그레스바 추가.java


package kr.or.ddit;

import java.security.PublicKey;
import java.util.ArrayList;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class AsyncTaskProgressbarActivity extends Activity {

    ProgressBar bar;
    TextView textView;
    Handler handler;
    AsyncTaskProgress task;
    int cnt=0;
    float fileSize = 1024;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ArrayList<String> list = new ArrayList<String>();
       
        bar = (ProgressBar) findViewById(R.id.progressbar);
        bar.setMax(100);
        textView= (TextView) findViewById(R.id.textView);
     
        Button startBtn = (Button) findViewById(R.id.startBtn);
        startBtn.setOnClickListener(new OnClickListener() {
           
            @Override
            public void onClick(View arg0) {
            task = new AsyncTaskProgress();
            task.execute(fileSize);
            }
        });
        Button cancelBtn = (Button) findViewById(R.id.cancelBtn);
        cancelBtn.setOnClickListener(new OnClickListener() {
           
            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                task.cancel(true);
            }
        });
    }
    class AsyncTaskProgress extends AsyncTask<Float, Integer, Integer>{

        @Override
        protected void onPreExecute() {
            cnt=0;
            bar.setProgress(0);
            textView.setText("push ple download");
        }
        @Override
        protected Integer doInBackground(Float... value) {
            while (!isCancelled()) {
                if(cnt>value[0]){
                    break;
                }else {
                    publishProgress(cnt);
                }
               
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                cnt++;
            }
            return cnt;
       
        }
        @Override
        protected void onProgressUpdate(Integer... integers) {
            //bar.incrementProgressBy(integers[0].intValue());
            bar.setProgress((int) (integers[0].floatValue()/fileSize*100));
            textView.setText((int)(integers[0].floatValue()/fileSize*100)+"% down loading.. ");
        }
        @Override
        protected void onPostExecute(Integer result) {
            bar.setProgress(0);
            textView.setText("down loading compleate!");
           
        }
       
        @Override
        protected void onCancelled() {
            bar.setProgress(0);
            textView.setText("down load cancel");
        }
    }
   

}

'안드로이드' 카테고리의 다른 글

소켓 통신 구현  (0) 2012.05.15
You must supply a layout_width attribute  (0) 2012.05.15
안드로이드 색상표  (0) 2012.05.11
안드로이드 강좌 모음 pdf&동영상 강좌.  (0) 2012.05.11
화면회전  (0) 2012.05.08
Posted by 사라링
컬러 이름 16진수       색상     
red #FF0000  
crimson #DC143C  
firebrick #B22222  
maroon #800000  
darkred #8B0000  
brown #A52A2A  
sienna #A0522D  
saddlebrown #8B4513  
indianred #CD5C5C  
rosybrown #BC8F8F  
lightcoral #F08080  
salmon #FA8072  
darksalmon #E9967A  
coral #FF7F50  
tomato #FF6347  
sandybrown #F4A460  
lightsalmon #FFA07A  
peru #CD853F  
chocolate #D2691E  
orangered #FF4500  
orange #FFA500  
darkorange #FF8C00  
tan #D2B48C  
peachpuff #FFDAB9  
bisque #FFE4C4  
moccasin #FFE4B5  
navajowhite #FFDEAD  
wheat #F5DEB3  
burlywood #DEB887  
darkgoldenrod #B8860B  
goldenrod #DAA520  
gold #FFD700  
yellow #FFFF00  
lightgoldenrodyellow #FAFAD2  
palegoldenrod #EEE8AA  
khaki #F0E68C  
darkkhaki #BDB76B  
lawngreen #7CFC00  
greenyellow #ADFF2F  
chartreuse #7FFF00  
lime #00FF00  
limegreen #32CD32  
yellowgreen #9ACD32  
olive #808000  
olivedrab #6B8E23  
darkolivegreen #556B2F  
forestgreen #228B22  
darkgreen #006400  
green #008000  
seagreen #2E8B57  
mediumseagreen #3CB371  
darkseagreen #8FBC8F  
lightgreen #90EE90  
palegreen #98FB98  
springgreen #00FF7F  
mediumspringgreen #00FA9A  
teal #008080  
darkcyan #008B8B  
lightseagreen #20B2AA  
mediumaquamarine #66CDAA  
cadetblue #5F9EA0  
steelblue #4682B4  
aquamarine #7FFFD4  
powderblue #B0E0E6  
paleturquoise #AFEEEE  
lightblue #ADD8E6  
lightsteelblue #B0C4DE  
skyblue #87CEEB  
lightskyblue #87CEFA  
mediumturquoise #48D1CC  
turquoise #40E0D0  
darkturquoise #00CED1  
aqua #00FFFF  
cyan #00FFFF  
deepskyblue #00BFFF  
dodgerblue #1E90FF  
cornflowerblue #6495ED  
royalblue #4169E1  
blue #0000FF  
mediumblue #0000CD  
navy #000080  
darkblue #00008B  
midnightblue #191970  
darkslateblue #483D8B  
slateblue #6A5ACD  
mediumslateblue #7B68EE  
mediumpurple #9370DB  
darkorchid #9932CC  
darkviolet #9400D3  
blueviolet #8A2BE2  
mediumorchid #BA55D3  
plum #DDA0DD  
lavender #E6E6FA  
thistle #D8BFD8  
orchid #DA70D6  
violet #EE82EE  
indigo #4B0082  
darkmagenta #8B008B  
purple #800080  
mediumvioletred #C71585  
deeppink #FF1493  
fuchsia #FF00FF  
magenta #FF00FF  
hotpink #FF69B4  
palevioletred #DB7093  
lightpink #FFB6C1  
pink #FFC0CB  
mistyrose #FFE4E1  
blanchedalmond #FFEBCD  
lightyellow #FFFFE0  
cornsilk #FFF8DC  
antiquewhite #FAEBD7  
papayawhip #FFEFD5  
lemonchiffon #FFFACD  
beige #F5F5DC  
linen #FAF0E6  
oldlace #FDF5E6  
lightcyan #E0FFFF  
aliceblue #F0F8FF  
whitesmoke #F5F5F5  
lavenderblush #FFF0F5  
floralwhite #FFFAF0  
mintcream #F5FFFA  
ghostwhite #F8F8FF  
honeydew #F0FFF0  
seashell #FFF5EE  
ivory #FFFFF0  
azure #F0FFFF
snow #FFFAFA  
white #FFFFFF  
gainsboro #DCDCDC  
lightgrey #D3D3D3  
silver #C0C0C0  
darkgray #A9A9A9  
lightslategray #778899  
slategray #708090  
gray #808080  
dimgray #696969  
darkslategray #2F4F4F  
black #000000  


출처 http://griper.tistory.com/entry

'안드로이드' 카테고리의 다른 글

You must supply a layout_width attribute  (0) 2012.05.15
Progressbar  (0) 2012.05.15
안드로이드 강좌 모음 pdf&동영상 강좌.  (0) 2012.05.11
화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
Posted by 사라링


PDF]  안드로이드 입문서 3rd Edition 파일의 링크

http://www.kandroid.com/kandroid_book_3rd_edition.pdf


'안드로이드' 카테고리의 다른 글

Progressbar  (0) 2012.05.15
안드로이드 색상표  (0) 2012.05.11
화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
Posted by 사라링

화면회전

2012. 5. 8. 18:37

 

화면 회전

  • Resource에서 화면(layout) 정의
  • 세로 화면 : /res/layout-port/~.xml
  • 가로 화면 : /res/layout-land/~.xml
  • 회전 상태 확인
  • Android 2.1
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
-   
//--- orientation : 0. 세로, 1. 가로
int orientation = display.getOrientation();
  • Android 2.2
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();

//--- Surface.ROTATION_0   : 세로
//--- Surface.ROTATION_90  : 가로
//--- Surface.ROTATION_180 : 세로
//--- Surface.ROTATION_270 : 가로
int rotation = display.getRotation();
  • 화면 회전 금지 방법 1
  • AndroidManifest.xml에서 모든 Activity에 다음을 추가 한다.
android:screenOrientation="portrait"       //--- 세로 화면 고정
android:screenOrientation="landscape"      //--- 가로 화면 고정
  • 화면 회전 금지 방법 2
//--- public void onCreate(Bundle savedInstanceState) 함수에서
//--- setContentView(~); 다음에 아래 라인을 추가 한다.

//--- 세로 화면 고정
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//--- 가로 화면 고정
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  • 화면이 회전해도 Activity가 다시 로딩되지 않도록 하는 방법
  • Activity가 다시 로딩되지 않으므로 화면의 크기는 변하지 않는다. 따라서 수작업으로 화면을 조정해 주어야 한다.
  • AndroidManifest.xml에서 모든 Activity에 다음을 추가 한다.
android:configChanges="keyboardHidden|orientation"
  • 모든 Activity에 다음을 추가 한다.
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    switch(newConfig.orientation) {
        case Configuration.ORIENTATION_PORTRAIT:   
//--- 수작업으로 세로 화면 생성 break; case Configuration.ORIENTATION_LANDSCAPE:
//--- 수작업으로 가로 화면 생성 break; } }
  • 화면 회전시 정보를 저장한 후 복구하는 방법
  • public void onCreate(Bundle savedInstanceState)에 복구와 관련된 코딩을 한다.
Object obj = getLastNonConfigurationInstance()
if (obj != null) {
    HashMap<String, Object> map = (HashMap<String, Object>) obj;
    //--- 저장된 복구 데이터로 화면 또는 Thread를 복구 한다.
}
  • public void onDestroy()에 화면 종료와 관련된 코딩을 한다.
public void onDestroy() {
   if (isFinishing()) {
         //--- 화면이 종료될 때, Thread 처리 등을 한다.
         //---   worker.interrupt();
        //---   worker = null;
    } else {
         //--- 화면이 회전할 때
    }
    super.onDestroy();
}
  • public Object onRetainNonConfigurationInstance()에 화면 회전시 데이터를 저장하는 코딩을 한다.
public Object onRetainNonConfigurationInstance() {
    HashMap<String, Object> map = null;

    map = new HashMap<String, Object>();
    //--- 화면 또는 Thread에서 복구할 데이터를 저장 한다.
    return map;
}
  • 참고 문헌


*** 참고 문헌 ***

이 글은 스프링노트에서 작성되었습니다.

'안드로이드' 카테고리의 다른 글

안드로이드 색상표  (0) 2012.05.11
안드로이드 강좌 모음 pdf&동영상 강좌.  (0) 2012.05.11
배경화면꾸미기  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
밑에 메뉴바.  (0) 2012.05.08
Posted by 사라링

배경화면꾸미기

2012. 5. 8. 18:36

 

 
이번에는 안드로이드 SDK에 포함된 예제인 JetBoy를 따라해 보도록 하겠습니다.  물론 게쪽을 사용해서 만들기 때문에 소스는 완전히 다른 스타일로 진행 됩니다.
 
 
 
기초 작업
 
우 선 [그림 1]처럼 프로젝트의 메인 Activity의 Package Name은 app.main으로 정했습니다.  이 부분은 여러분들이 각자 마음에 드는 이름으로 지정하셔도 됩니다.  그리고, Main Activity Class의 이름은 Main, 배경을 담당 할 클래스의 이름은 BackGround로 지정하였습니다.  이후 RyuLib를 src 폴더에 복사해서 사용하고 있습니다.
 
"RyuLib.Game for Android 시작하기"에서 이미 설명한 것과 중복되지만, "Add External JARs" 등을 이용하여 외부 참조를 하는 것이 익숙하지 않은 분들에게서 질문이 올라오고 있어서, 좀 쉽게 가기로 했습니다.  모든 예제의 소스에는 해당 예제를 작성할 때의 RyuLib를 복사해서 배포 할 예정입니다.  RyuLib는 아직 안정화 단계가 아니기 때문에 서로 버전이 달라 질 수 있음을 유의하시기 바랍니다.
 
이미지와 관련해서는 JetBoy의 drawable 폴더에 있는 이미지를 그대로 복사 해 왔습니다.
 
K-20101224-145750-5.png
[그림 1] 프로젝트 준비
 
이제 [소스 1]과 같이 AndroidManifest.xml 를 수정하여 화면을 가로로 변경하도록 하겠습니다.
 
[소스 1] AndroidManifest.xml
   1 :  <?xml version="1.0" encoding="utf-8"?>
   2 :  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   3 :       package="app.main"
   4 :       android:versionCode="1"
   5 :       android:versionName="1.0">
   6 :      <application android:icon="@drawable/icon" android:label="@string/app_name">
   7 :          <activity android:name=".Main"
   8 :                   android:label="@string/app_name"
   9 :                   android:screenOrientation="landscape">
  10 :              <intent-filter>
  11 :                  <action android:name="android.intent.action.MAIN" />
  12 :                  <category android:name="android.intent.category.LAUNCHER" />
  13 :              </intent-filter>
  14 :          </activity>
  15 :      </application>
  16 :  </manifest> 
 
9: android:screenOrientation="landscape" 를 추가해서 지금 작성하는 어플리케이션이 가로로 화면이 출력되어야 함을 알립니다.
 
 
 
일단 무조건 표시하고 이동 해보자
 
이후 Main.java와 BackGround.java의 소스를 [소스 2]와 [소스 3]과 같이 작성합니다.
 
[소스 2] Main.java
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GamePlatform;
   4 : import android.app.Activity;
   5 : import android.os.Bundle;
   6 : import android.view.ViewGroup;
   7 : import android.widget.LinearLayout;
   8 : 
   9 : public class Main extends Activity {
  10 :     /** Called when the activity is first created. */
  11 :     @Override
  12 :     public void onCreate(Bundle savedInstanceState) {
  13 :         super.onCreate(savedInstanceState);
  14 : 
  15 :         _GamePlatform = new GamePlatform(this);
  16 :         _GamePlatform.setLayoutParams(
  17 :         new LinearLayout.LayoutParams(
  18 :         ViewGroup.LayoutParams.FILL_PARENT, 
  19 :         ViewGroup.LayoutParams.FILL_PARENT, 
  20 :         0.0F
  21 :         )
  22 :         );
  23 : 
  24 :         setContentView(_GamePlatform);
  25 :         
  26 :         _BackGround = new BackGround(this);
  27 :         _GamePlatform.AddControl(_BackGround);
  28 :     }
  29 :     
  30 :     private GamePlatform _GamePlatform = null;
  31 :     private BackGround _BackGround = null;
  32 :     
  33 : }
 
"RyuLib.Game for Android 시작하기"에서 충분히 설명한 부분이라서 설명을 생략합니다.
 
 
[소스 3] BackGround.java
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GameControl;
   4 : import ryulib.game.GamePlatformInfo;
   5 : import android.content.Context;
   6 : import android.content.res.Resources;
   7 : import android.graphics.Bitmap;
   8 : import android.graphics.BitmapFactory;
   9 : import android.graphics.Canvas;
  10 : import android.graphics.Paint;
  11 : 
  12 : public class BackGround extends GameControl {
  13 : 
  14 : public BackGround(Context context) {
  15 : super(null);
  16 : 
  17 : Resources resources = context.getResources();
  18 : 
  19 : _BackGoundBitmap = BitmapFactory.decodeResource(resources,   20 : R.drawable.background_a);
  21 : }
  22 : 
  23 : private Canvas _Canvas = null;
  24 : private Paint _Paint = null;
  25 : 
  26 : private Bitmap _BackGoundBitmap = null;
  27 : private double _X01 = 0;
  28 : 
  29 : @Override
  30 : protected void onStart(GamePlatformInfo platformInfo) {
  31 : _Canvas = platformInfo.getCanvas();
  32 : _Paint = platformInfo.getPaint();
  33 : }
  34 : 
  35 : @Override
  36 : protected void onDraw(GamePlatformInfo platformInfo) {
  37 : long tick = platformInfo.getTick();
  38 : 
  39 : // 현재 위치에 먼 거리 배경 이미지 그리기
  40 : _Canvas.drawBitmap(_BackGoundBitmap, (int) _X01, 0, _Paint);
  41 : 
  42 : _X01 = _X01 - (25 * tick / 1000);
  43 : }
  44 : 
  45 : }
 
17: Resources는 이미지나 오디오 등의 자원들을 코드에서 사용해 줄 수 있게 합니다.  우리는 지금 이미지가 필요한 시기이므로 간절히 필요합니다.
 
19: BitmapFactory를 이용해서 Resources에서 이미지를 불러옵니다.  drawable 폴더에서 background_a.png 파일을 가져옵니다.
 
 
26: 배경 이미지를 시간이 갈 수록 왼쪽으로 스크롤 하기 위해서, 이미지가 표시될 왼쪽 좌표를 저장 할 변수를 _X01로 지정하였습니다.  좌표의 이름 뒤에 "01"을 붙인 이유는 배경 이미지 표시가 두 번 필요하기 때문입니다.  이는 뒤에서 설명하도록 하겠습니다.
 
 
39: 현재 좌표 (_X01, 0)에 비트맵을 그려줍니다.  배경의 Y축 좌표는 변하지 않기 때문에 0으로 지정하였습니다.
 
 
41: 시간에 따라 X축 좌표를 왼쪽으로 이동하기 위해 시간에 맞춰서 빼기 연산을 합니다.  얼마만큼 이동하느냐는 1초 단위마다 25 픽셀로 정하였습니다.  tick은 이미 설명한 것과 같이 1/1000(천분의 1) 단위를 가지고 있기 때문에 1000으로 나눠서 계산하였습니다.
 
 
 
 
[그림 2]는 실행 화면입니다.  배경 이미지의 크기 이상으로 스크롤이 진행되자, 화면 오른쪽에 이미지의 잔상만을 남기면서 이상한 모양새가 되어 버렸습니다.
 
screenshot1.png
[그림 2] 배경 이미지 크기 이상으로 스크롤 된 화면
 
이 미지를 무한히 크게 하지 않는 한 이런 현상을 피할 수는 없습니다.  따라서, 대개의 경우 배경 이미지를 반복적으로 사용하게 됩니다.  아예 배경 이미지를 실시간으로 랜더링하는 방법도 있긴 합니다.  하지만, 실시간 랜더링은 다소 복잡하니 나중으로 미루도록 하겠습니다.
 
 
 
배경 이미지 순환 반복
 
여 기서는 이미지를 두 장 사용하여, 한 장이 화면에서 지나가면 바로 오른쪽에 이어서 표시하는 방식을 거듭해서 무한히 반복되도록 하겠습니다.  [그림 3]을 보시면 "배경 이미지 1"이 스크롤되면서 생긴 화면의 공백을 "배경 이미지 2"가 이어서 표시되도록 하는 것이 보입니다.  이후 "배경 이미지 1"이 화면영역에서 완전히 벗어나면, 이것을 다시 오른쪽에 이어 붙여서 재 활용 합니다.
 
pic1.png
[그림 3] 배경 이미지 두 장으로 연속적인 스크롤 구현
 
 
[소스 4] BackGround.java의 1차 수정
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GameControl;
   4 : import ryulib.game.GamePlatformInfo;
   5 : import android.content.Context;
   6 : import android.content.res.Resources;
   7 : import android.graphics.Bitmap;
   8 : import android.graphics.BitmapFactory;
   9 : import android.graphics.Canvas;
  10 : import android.graphics.Paint;
  11 : 
  12 : public class BackGround extends GameControl {
  13 : 
  14 : private static final int BACKROUND_SPEED = 25;
  15 : 
  16 : public BackGround(Context context) {
  17 : super(null);
  18 : 
  19 : Resources resources = context.getResources();
  20 : 
  21 : _BackGoundBitmap = BitmapFactory.decodeResource(resources,   22 : R.drawable.background_a);
  23 : }
  24 : 
  25 : private Canvas _Canvas = null;
  26 : private Paint _Paint = null;
  27 : 
  28 : private Bitmap _BackGoundBitmap = null;
  29 : private double _X01 = 0;
  30 : private double _X02 = 0;
  31 : 
  32 : @Override
  33 : protected void onStart(GamePlatformInfo platformInfo) {
  34 : _Canvas = platformInfo.getCanvas();
  35 : _Paint = platformInfo.getPaint();
  36 : }
  37 : 
  38 : @Override
  39 : protected void onDraw(GamePlatformInfo platformInfo) {
  40 : long tick = platformInfo.getTick();
  41 : 
  42 : // 현재 위치에 먼 거리 배경 이미지 그리기
  43 : if ((_BackGoundBitmap.getWidth()+_X01) > 0) {
  44 : _Canvas.drawBitmap(_BackGoundBitmap, (int) _X01, 0, _Paint);
  45 : }
  46 : 
  47 : if ((_BackGoundBitmap.getWidth()+_X01) <= _Canvas.getWidth()) {
  48 : _Canvas.drawBitmap(_BackGoundBitmap, (int) _X02, 0, _Paint);
  49 : }
  50 : 
  51 : _X01 = _X01 - (BACKROUND_SPEED * tick / 1000);
  52 : _X02 = _X01 + _BackGoundBitmap.getWidth();
  53 : 
  54 : if ((_BackGoundBitmap.getWidth()+_X02) <= _Canvas.getWidth()) {
  55 : double temp = _X01;
  56 : _X01 = _X02;
  57 : _X02 = temp + _BackGoundBitmap.getWidth();
  58 : }
  59 : }
  60 : 
  61 : }
 
14: [소스 3]의 41: 라인에 있는 25라는 숫자는 어떤 의미로 쓰이는 지 알 수가 없기 때문에 이런 경우에는 상수로 정의해서 쓰는 것이 정석입니다.  따라서, 배경 이미지의 스크롤 속도에 맞게 상수 이름을  BACKROUND_SPEED로 정하였습니다.
 
29: 배경 이미지 좌표 정보가 두 개 필요하기 때문에 추가된 코드 입니다.
 
42: 첫 번째 이미지가 좌측 경계선을 넘어서면, 이미지를 표시 할 필요가 없습니다.
 
46: 두 번째 이미지는 첫 번째 이미지가 화면 전체를 덮지 못 할 경우에 표시 합니다.  첫 번째 이미지의 크기에 좌표를 더하면 현재 화면에 표시되고 있는 이미지의 넓이가 나옵니다.  여기서 좌표를 더하는 이유는 좌표가 음수 영역이기 때문입니다.
 
50-51: 우선 첫 번째 이미지 좌표 _X01을 이동 시키고, _X02는 _X01에 이미지의 넓이를 더해 줍니다.
 
53-57: 두 번째 이미지 마저 화면을 꽉 채우지 못하게 되면, 이미 사라져 버린 _X01과 자리를 바꿔서 이미지 표시를 순환 반복 합니다.
 
 
[그림 4]를 보시면 이미지 스크롤이 이미지 크기보다 더 진행되어 이미지의 앞 부분이 화면 오른쪽에 다시 반복되서 출력되는 것을 확인하실 수 있습니다.  이제 배경 이미지는 빈틈없이 순환 반복되어 출력 됩니다.
 
screenshot2.png
[그림 4] 배경 이미지의 순환 반복
 
배 경 이미지 표시를 이대로 해도 큰 문제가 되지는 않지만, 좀 더 세련된 표현을 해보도록 하겠습니다.  게임에서 자주 활용되는 기법인데, 배경 이미지를 여러 개 사용하고, 모든 배경 이미지의 속도를 다르게 함으로써 입체적인 표시를 하는 것 입니다.  느린 속도로 스크롤하는 배경은 멀리 있어 보이고, 빠를 수록 가까운 거리로 느껴지게 됩니다.
 
자 그럼 어떻게 하면 됩니까?  이미 배경 이미지 표시는 해봤으니까, 속도만 변경해서 Bitmap과 좌표를 저장 할 변수를 선언하여 코딩하면 쉽게 해결 될 것 입니다.  그리고, 이제 바로 코딩에 들어 가면 될 까요?
 
K-20101224-163446-4.png
No!!  틀렸지만, 참 잘 했어요 ^^
 
 
 
배경 이미지 처리를 캡슐화하여 코드를 간결하게
 
그 런건 아마추어들이나 하는 짓이고, 우리는 프로답게 "프로그래밍 7거지악" 중에 하나인 중복 코드도 피해가고, 배경 이미지의 위치를 계산하는 등의 전체 흐름과 무관한 코드를 캡슐화 해보록 하겠습니다.  우선 새로운 클래스인 BackGroundImage.java를 생성하였고, 그로 인해서 BackGround.java는 [소스 5]와 같이 간결해 졌습니다.
 
[소스 5] BackGround.java의 2차 수정
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GameControl;
   4 : import ryulib.game.GamePlatformInfo;
   5 : import android.content.Context;
   6 : import android.content.res.Resources;
   7 : import android.graphics.BitmapFactory;
   8 : import android.graphics.Canvas;
   9 : import android.graphics.Paint;
  10 : 
  11 : public class BackGround extends GameControl {
  12 : 
  13 : private static final int BACKROUND_SPEED_SLOW = 25;
  14 : private static final int BACKROUND_SPEED_FAST = 50;
  15 : 
  16 : public BackGround(Context context) {
  17 : super(null);
  18 : 
  19 : Resources resources = context.getResources();
  20 : 
  21 : _BackGround_A.setBitmap(
  22 : BitmapFactory.decodeResource(resources, R.drawable.background_a)
  23 : );
  24 : _BackGround_A.setSpeed(BACKROUND_SPEED_SLOW);
  25 : 
  26 : _BackGround_B.setBitmap(
  27 : BitmapFactory.decodeResource(resources, R.drawable.background_b)
  28 : );
  29 : _BackGround_B.setSpeed(BACKROUND_SPEED_FAST);
  30 : }
  31 : 
  32 : private Canvas _Canvas = null;
  33 : private Paint _Paint = null;
  34 : 
  35 : private BackGroundImage _BackGround_A = new BackGroundImage();
  36 : private BackGroundImage _BackGround_B = new BackGroundImage();
  37 : 
  38 : @Override
  39 : protected void onStart(GamePlatformInfo platformInfo) {
  40 : _Canvas = platformInfo.getCanvas();
  41 : _Paint = platformInfo.getPaint();
  42 : }
  43 : 
  44 : @Override
  45 : protected void onDraw(GamePlatformInfo platformInfo) {
  46 : long tick = platformInfo.getTick();
  47 : 
  48 : _BackGround_A.Draw(_Canvas, _Paint, tick);
  49 : _BackGround_B.Draw(_Canvas, _Paint, tick);
  50 : }
  51 : 
  52 : }
 
전반적으로 수정되어 수정된 부분을 표시하지 않았습니다.
 
14: 새로운 배경 이미지는 속도를 두 배 빠르게 하여 가까운 배경으로 지정할 예정입니다. 
 
35-36: 배경 이미지를 처리해 주는 BackGroundImage의 객체를 두 개 생성합니다.
 
21, 26: Resources에서 각각 background_a와 background_b를 불러 들여 BackGroundImage.setBitmap()으로 지정 합니다.
 
24, 29: 각각 BackGroundImage의 객체의 속도를 지정해 줍니다.  하나는 느리고, 하나는 빠르게 설정하였습니다.
 
48-49: 배경 이미지의 그리기와 스크롤 처리를 BackGroundImage 클래스에게 위임하여 캡슐화 하였기 때문에, onDraw() 메소드 구현이 상당히 간결해 졌습니다.
 
 
[소스 6]는 BackGroundImage의 내부 구현 입니다.
 
[소스 6] BackGroundImage.java
   1 : package app.main;
   2 : 
   3 : import android.graphics.Bitmap;
   4 : import android.graphics.Canvas;
   5 : import android.graphics.Paint;
   6 : 
   7 : public class BackGroundImage {
   8 : 
   9 : private double _X01 = 0;
  10 : private double _X02 = 0;
  11 : 
  12 : // Property 
  13 : private Bitmap _Bitmap = null;
  14 : private int _Speed = 0;
  15 : 
  16 : public void Draw(Canvas canvas, Paint paint, long tick) {
  17 : // 현재 위치에 먼 거리 배경 이미지 그리기
  18 : if ((_Bitmap.getWidth()+_X01) > 0) {
  19 : canvas.drawBitmap(_Bitmap, (int) _X01, 0, paint);
  20 : }
  21 : 
  22 : if ((_Bitmap.getWidth()+_X01) <= canvas.getWidth()) {
  23 : canvas.drawBitmap(_Bitmap, (int) _X02, 0, paint);
  24 : }
  25 : 
  26 : _X01 = _X01 - (_Speed * tick / 1000);
  27 : _X02 = _X01 + _Bitmap.getWidth();
  28 : 
  29 : if ((_Bitmap.getWidth()+_X02) <= canvas.getWidth()) {
  30 : double temp = _X01;
  31 : _X01 = _X02;
  32 : _X02 = temp + _Bitmap.getWidth();
  33 : }
  34 : }
  35 : 
  36 : public void setBitmap(Bitmap value) {
  37 : _Bitmap = value;
  38 : }
  39 : public Bitmap getBitmap() {
  40 : return _Bitmap;
  41 : }
  42 : 
  43 : public void setSpeed(int value) {
  44 : _Speed = value;
  45 : }
  46 : 
  47 : public int getSpeed() {
  48 : return _Speed;
  49 : }
  50 : 
  51 : }
 
대분의 코드는 BackGround.java에서 구현했던 것과 동일 합니다.  특별히 다시 설명해야 할 만한 곳이 보이지 않기 때문에 설명을 생략하도록 하겠습니다.
 
이제와서 느낀건데 왜 BackGround가 Background보다 자연스럽게 느껴졌던 걸까요?  아놔!!
 
 
screenshot3.png
[그림 5] 완성된 결과
 
 
 
정리
  • 배경 이미지를 표시하고 스크롤 하기 (배경 이미지 순환 반복)
  • 배경 이미지를 여러 개를 준비하고, 속도를 달리하여 입체감 살리기
 
 
 
소스
 
첨부 파일을 참고 하세요.
 
 

이 글은 스프링노트에서 작성되었습니다.

'안드로이드' 카테고리의 다른 글

안드로이드 강좌 모음 pdf&동영상 강좌.  (0) 2012.05.11
화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
밑에 메뉴바.  (0) 2012.05.08
나인패치  (1) 2012.05.08
Posted by 사라링

배경화면꾸미기

2012. 5. 8. 18:36

 

 
이번에는 안드로이드 SDK에 포함된 예제인 JetBoy를 따라해 보도록 하겠습니다.  물론 게쪽을 사용해서 만들기 때문에 소스는 완전히 다른 스타일로 진행 됩니다.
 
 
 
기초 작업
 
우 선 [그림 1]처럼 프로젝트의 메인 Activity의 Package Name은 app.main으로 정했습니다.  이 부분은 여러분들이 각자 마음에 드는 이름으로 지정하셔도 됩니다.  그리고, Main Activity Class의 이름은 Main, 배경을 담당 할 클래스의 이름은 BackGround로 지정하였습니다.  이후 RyuLib를 src 폴더에 복사해서 사용하고 있습니다.
 
"RyuLib.Game for Android 시작하기"에서 이미 설명한 것과 중복되지만, "Add External JARs" 등을 이용하여 외부 참조를 하는 것이 익숙하지 않은 분들에게서 질문이 올라오고 있어서, 좀 쉽게 가기로 했습니다.  모든 예제의 소스에는 해당 예제를 작성할 때의 RyuLib를 복사해서 배포 할 예정입니다.  RyuLib는 아직 안정화 단계가 아니기 때문에 서로 버전이 달라 질 수 있음을 유의하시기 바랍니다.
 
이미지와 관련해서는 JetBoy의 drawable 폴더에 있는 이미지를 그대로 복사 해 왔습니다.
 
K-20101224-145750-5.png
[그림 1] 프로젝트 준비
 
이제 [소스 1]과 같이 AndroidManifest.xml 를 수정하여 화면을 가로로 변경하도록 하겠습니다.
 
[소스 1] AndroidManifest.xml
   1 :  <?xml version="1.0" encoding="utf-8"?>
   2 :  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   3 :       package="app.main"
   4 :       android:versionCode="1"
   5 :       android:versionName="1.0">
   6 :      <application android:icon="@drawable/icon" android:label="@string/app_name">
   7 :          <activity android:name=".Main"
   8 :                   android:label="@string/app_name"
   9 :                   android:screenOrientation="landscape">
  10 :              <intent-filter>
  11 :                  <action android:name="android.intent.action.MAIN" />
  12 :                  <category android:name="android.intent.category.LAUNCHER" />
  13 :              </intent-filter>
  14 :          </activity>
  15 :      </application>
  16 :  </manifest> 
 
9: android:screenOrientation="landscape" 를 추가해서 지금 작성하는 어플리케이션이 가로로 화면이 출력되어야 함을 알립니다.
 
 
 
일단 무조건 표시하고 이동 해보자
 
이후 Main.java와 BackGround.java의 소스를 [소스 2]와 [소스 3]과 같이 작성합니다.
 
[소스 2] Main.java
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GamePlatform;
   4 : import android.app.Activity;
   5 : import android.os.Bundle;
   6 : import android.view.ViewGroup;
   7 : import android.widget.LinearLayout;
   8 : 
   9 : public class Main extends Activity {
  10 :     /** Called when the activity is first created. */
  11 :     @Override
  12 :     public void onCreate(Bundle savedInstanceState) {
  13 :         super.onCreate(savedInstanceState);
  14 : 
  15 :         _GamePlatform = new GamePlatform(this);
  16 :         _GamePlatform.setLayoutParams(
  17 :         new LinearLayout.LayoutParams(
  18 :         ViewGroup.LayoutParams.FILL_PARENT, 
  19 :         ViewGroup.LayoutParams.FILL_PARENT, 
  20 :         0.0F
  21 :         )
  22 :         );
  23 : 
  24 :         setContentView(_GamePlatform);
  25 :         
  26 :         _BackGround = new BackGround(this);
  27 :         _GamePlatform.AddControl(_BackGround);
  28 :     }
  29 :     
  30 :     private GamePlatform _GamePlatform = null;
  31 :     private BackGround _BackGround = null;
  32 :     
  33 : }
 
"RyuLib.Game for Android 시작하기"에서 충분히 설명한 부분이라서 설명을 생략합니다.
 
 
[소스 3] BackGround.java
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GameControl;
   4 : import ryulib.game.GamePlatformInfo;
   5 : import android.content.Context;
   6 : import android.content.res.Resources;
   7 : import android.graphics.Bitmap;
   8 : import android.graphics.BitmapFactory;
   9 : import android.graphics.Canvas;
  10 : import android.graphics.Paint;
  11 : 
  12 : public class BackGround extends GameControl {
  13 : 
  14 : public BackGround(Context context) {
  15 : super(null);
  16 : 
  17 : Resources resources = context.getResources();
  18 : 
  19 : _BackGoundBitmap = BitmapFactory.decodeResource(resources,   20 : R.drawable.background_a);
  21 : }
  22 : 
  23 : private Canvas _Canvas = null;
  24 : private Paint _Paint = null;
  25 : 
  26 : private Bitmap _BackGoundBitmap = null;
  27 : private double _X01 = 0;
  28 : 
  29 : @Override
  30 : protected void onStart(GamePlatformInfo platformInfo) {
  31 : _Canvas = platformInfo.getCanvas();
  32 : _Paint = platformInfo.getPaint();
  33 : }
  34 : 
  35 : @Override
  36 : protected void onDraw(GamePlatformInfo platformInfo) {
  37 : long tick = platformInfo.getTick();
  38 : 
  39 : // 현재 위치에 먼 거리 배경 이미지 그리기
  40 : _Canvas.drawBitmap(_BackGoundBitmap, (int) _X01, 0, _Paint);
  41 : 
  42 : _X01 = _X01 - (25 * tick / 1000);
  43 : }
  44 : 
  45 : }
 
17: Resources는 이미지나 오디오 등의 자원들을 코드에서 사용해 줄 수 있게 합니다.  우리는 지금 이미지가 필요한 시기이므로 간절히 필요합니다.
 
19: BitmapFactory를 이용해서 Resources에서 이미지를 불러옵니다.  drawable 폴더에서 background_a.png 파일을 가져옵니다.
 
 
26: 배경 이미지를 시간이 갈 수록 왼쪽으로 스크롤 하기 위해서, 이미지가 표시될 왼쪽 좌표를 저장 할 변수를 _X01로 지정하였습니다.  좌표의 이름 뒤에 "01"을 붙인 이유는 배경 이미지 표시가 두 번 필요하기 때문입니다.  이는 뒤에서 설명하도록 하겠습니다.
 
 
39: 현재 좌표 (_X01, 0)에 비트맵을 그려줍니다.  배경의 Y축 좌표는 변하지 않기 때문에 0으로 지정하였습니다.
 
 
41: 시간에 따라 X축 좌표를 왼쪽으로 이동하기 위해 시간에 맞춰서 빼기 연산을 합니다.  얼마만큼 이동하느냐는 1초 단위마다 25 픽셀로 정하였습니다.  tick은 이미 설명한 것과 같이 1/1000(천분의 1) 단위를 가지고 있기 때문에 1000으로 나눠서 계산하였습니다.
 
 
 
 
[그림 2]는 실행 화면입니다.  배경 이미지의 크기 이상으로 스크롤이 진행되자, 화면 오른쪽에 이미지의 잔상만을 남기면서 이상한 모양새가 되어 버렸습니다.
 
screenshot1.png
[그림 2] 배경 이미지 크기 이상으로 스크롤 된 화면
 
이 미지를 무한히 크게 하지 않는 한 이런 현상을 피할 수는 없습니다.  따라서, 대개의 경우 배경 이미지를 반복적으로 사용하게 됩니다.  아예 배경 이미지를 실시간으로 랜더링하는 방법도 있긴 합니다.  하지만, 실시간 랜더링은 다소 복잡하니 나중으로 미루도록 하겠습니다.
 
 
 
배경 이미지 순환 반복
 
여 기서는 이미지를 두 장 사용하여, 한 장이 화면에서 지나가면 바로 오른쪽에 이어서 표시하는 방식을 거듭해서 무한히 반복되도록 하겠습니다.  [그림 3]을 보시면 "배경 이미지 1"이 스크롤되면서 생긴 화면의 공백을 "배경 이미지 2"가 이어서 표시되도록 하는 것이 보입니다.  이후 "배경 이미지 1"이 화면영역에서 완전히 벗어나면, 이것을 다시 오른쪽에 이어 붙여서 재 활용 합니다.
 
pic1.png
[그림 3] 배경 이미지 두 장으로 연속적인 스크롤 구현
 
 
[소스 4] BackGround.java의 1차 수정
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GameControl;
   4 : import ryulib.game.GamePlatformInfo;
   5 : import android.content.Context;
   6 : import android.content.res.Resources;
   7 : import android.graphics.Bitmap;
   8 : import android.graphics.BitmapFactory;
   9 : import android.graphics.Canvas;
  10 : import android.graphics.Paint;
  11 : 
  12 : public class BackGround extends GameControl {
  13 : 
  14 : private static final int BACKROUND_SPEED = 25;
  15 : 
  16 : public BackGround(Context context) {
  17 : super(null);
  18 : 
  19 : Resources resources = context.getResources();
  20 : 
  21 : _BackGoundBitmap = BitmapFactory.decodeResource(resources,   22 : R.drawable.background_a);
  23 : }
  24 : 
  25 : private Canvas _Canvas = null;
  26 : private Paint _Paint = null;
  27 : 
  28 : private Bitmap _BackGoundBitmap = null;
  29 : private double _X01 = 0;
  30 : private double _X02 = 0;
  31 : 
  32 : @Override
  33 : protected void onStart(GamePlatformInfo platformInfo) {
  34 : _Canvas = platformInfo.getCanvas();
  35 : _Paint = platformInfo.getPaint();
  36 : }
  37 : 
  38 : @Override
  39 : protected void onDraw(GamePlatformInfo platformInfo) {
  40 : long tick = platformInfo.getTick();
  41 : 
  42 : // 현재 위치에 먼 거리 배경 이미지 그리기
  43 : if ((_BackGoundBitmap.getWidth()+_X01) > 0) {
  44 : _Canvas.drawBitmap(_BackGoundBitmap, (int) _X01, 0, _Paint);
  45 : }
  46 : 
  47 : if ((_BackGoundBitmap.getWidth()+_X01) <= _Canvas.getWidth()) {
  48 : _Canvas.drawBitmap(_BackGoundBitmap, (int) _X02, 0, _Paint);
  49 : }
  50 : 
  51 : _X01 = _X01 - (BACKROUND_SPEED * tick / 1000);
  52 : _X02 = _X01 + _BackGoundBitmap.getWidth();
  53 : 
  54 : if ((_BackGoundBitmap.getWidth()+_X02) <= _Canvas.getWidth()) {
  55 : double temp = _X01;
  56 : _X01 = _X02;
  57 : _X02 = temp + _BackGoundBitmap.getWidth();
  58 : }
  59 : }
  60 : 
  61 : }
 
14: [소스 3]의 41: 라인에 있는 25라는 숫자는 어떤 의미로 쓰이는 지 알 수가 없기 때문에 이런 경우에는 상수로 정의해서 쓰는 것이 정석입니다.  따라서, 배경 이미지의 스크롤 속도에 맞게 상수 이름을  BACKROUND_SPEED로 정하였습니다.
 
29: 배경 이미지 좌표 정보가 두 개 필요하기 때문에 추가된 코드 입니다.
 
42: 첫 번째 이미지가 좌측 경계선을 넘어서면, 이미지를 표시 할 필요가 없습니다.
 
46: 두 번째 이미지는 첫 번째 이미지가 화면 전체를 덮지 못 할 경우에 표시 합니다.  첫 번째 이미지의 크기에 좌표를 더하면 현재 화면에 표시되고 있는 이미지의 넓이가 나옵니다.  여기서 좌표를 더하는 이유는 좌표가 음수 영역이기 때문입니다.
 
50-51: 우선 첫 번째 이미지 좌표 _X01을 이동 시키고, _X02는 _X01에 이미지의 넓이를 더해 줍니다.
 
53-57: 두 번째 이미지 마저 화면을 꽉 채우지 못하게 되면, 이미 사라져 버린 _X01과 자리를 바꿔서 이미지 표시를 순환 반복 합니다.
 
 
[그림 4]를 보시면 이미지 스크롤이 이미지 크기보다 더 진행되어 이미지의 앞 부분이 화면 오른쪽에 다시 반복되서 출력되는 것을 확인하실 수 있습니다.  이제 배경 이미지는 빈틈없이 순환 반복되어 출력 됩니다.
 
screenshot2.png
[그림 4] 배경 이미지의 순환 반복
 
배 경 이미지 표시를 이대로 해도 큰 문제가 되지는 않지만, 좀 더 세련된 표현을 해보도록 하겠습니다.  게임에서 자주 활용되는 기법인데, 배경 이미지를 여러 개 사용하고, 모든 배경 이미지의 속도를 다르게 함으로써 입체적인 표시를 하는 것 입니다.  느린 속도로 스크롤하는 배경은 멀리 있어 보이고, 빠를 수록 가까운 거리로 느껴지게 됩니다.
 
자 그럼 어떻게 하면 됩니까?  이미 배경 이미지 표시는 해봤으니까, 속도만 변경해서 Bitmap과 좌표를 저장 할 변수를 선언하여 코딩하면 쉽게 해결 될 것 입니다.  그리고, 이제 바로 코딩에 들어 가면 될 까요?
 
K-20101224-163446-4.png
No!!  틀렸지만, 참 잘 했어요 ^^
 
 
 
배경 이미지 처리를 캡슐화하여 코드를 간결하게
 
그 런건 아마추어들이나 하는 짓이고, 우리는 프로답게 "프로그래밍 7거지악" 중에 하나인 중복 코드도 피해가고, 배경 이미지의 위치를 계산하는 등의 전체 흐름과 무관한 코드를 캡슐화 해보록 하겠습니다.  우선 새로운 클래스인 BackGroundImage.java를 생성하였고, 그로 인해서 BackGround.java는 [소스 5]와 같이 간결해 졌습니다.
 
[소스 5] BackGround.java의 2차 수정
   1 : package app.main;
   2 : 
   3 : import ryulib.game.GameControl;
   4 : import ryulib.game.GamePlatformInfo;
   5 : import android.content.Context;
   6 : import android.content.res.Resources;
   7 : import android.graphics.BitmapFactory;
   8 : import android.graphics.Canvas;
   9 : import android.graphics.Paint;
  10 : 
  11 : public class BackGround extends GameControl {
  12 : 
  13 : private static final int BACKROUND_SPEED_SLOW = 25;
  14 : private static final int BACKROUND_SPEED_FAST = 50;
  15 : 
  16 : public BackGround(Context context) {
  17 : super(null);
  18 : 
  19 : Resources resources = context.getResources();
  20 : 
  21 : _BackGround_A.setBitmap(
  22 : BitmapFactory.decodeResource(resources, R.drawable.background_a)
  23 : );
  24 : _BackGround_A.setSpeed(BACKROUND_SPEED_SLOW);
  25 : 
  26 : _BackGround_B.setBitmap(
  27 : BitmapFactory.decodeResource(resources, R.drawable.background_b)
  28 : );
  29 : _BackGround_B.setSpeed(BACKROUND_SPEED_FAST);
  30 : }
  31 : 
  32 : private Canvas _Canvas = null;
  33 : private Paint _Paint = null;
  34 : 
  35 : private BackGroundImage _BackGround_A = new BackGroundImage();
  36 : private BackGroundImage _BackGround_B = new BackGroundImage();
  37 : 
  38 : @Override
  39 : protected void onStart(GamePlatformInfo platformInfo) {
  40 : _Canvas = platformInfo.getCanvas();
  41 : _Paint = platformInfo.getPaint();
  42 : }
  43 : 
  44 : @Override
  45 : protected void onDraw(GamePlatformInfo platformInfo) {
  46 : long tick = platformInfo.getTick();
  47 : 
  48 : _BackGround_A.Draw(_Canvas, _Paint, tick);
  49 : _BackGround_B.Draw(_Canvas, _Paint, tick);
  50 : }
  51 : 
  52 : }
 
전반적으로 수정되어 수정된 부분을 표시하지 않았습니다.
 
14: 새로운 배경 이미지는 속도를 두 배 빠르게 하여 가까운 배경으로 지정할 예정입니다. 
 
35-36: 배경 이미지를 처리해 주는 BackGroundImage의 객체를 두 개 생성합니다.
 
21, 26: Resources에서 각각 background_a와 background_b를 불러 들여 BackGroundImage.setBitmap()으로 지정 합니다.
 
24, 29: 각각 BackGroundImage의 객체의 속도를 지정해 줍니다.  하나는 느리고, 하나는 빠르게 설정하였습니다.
 
48-49: 배경 이미지의 그리기와 스크롤 처리를 BackGroundImage 클래스에게 위임하여 캡슐화 하였기 때문에, onDraw() 메소드 구현이 상당히 간결해 졌습니다.
 
 
[소스 6]는 BackGroundImage의 내부 구현 입니다.
 
[소스 6] BackGroundImage.java
   1 : package app.main;
   2 : 
   3 : import android.graphics.Bitmap;
   4 : import android.graphics.Canvas;
   5 : import android.graphics.Paint;
   6 : 
   7 : public class BackGroundImage {
   8 : 
   9 : private double _X01 = 0;
  10 : private double _X02 = 0;
  11 : 
  12 : // Property 
  13 : private Bitmap _Bitmap = null;
  14 : private int _Speed = 0;
  15 : 
  16 : public void Draw(Canvas canvas, Paint paint, long tick) {
  17 : // 현재 위치에 먼 거리 배경 이미지 그리기
  18 : if ((_Bitmap.getWidth()+_X01) > 0) {
  19 : canvas.drawBitmap(_Bitmap, (int) _X01, 0, paint);
  20 : }
  21 : 
  22 : if ((_Bitmap.getWidth()+_X01) <= canvas.getWidth()) {
  23 : canvas.drawBitmap(_Bitmap, (int) _X02, 0, paint);
  24 : }
  25 : 
  26 : _X01 = _X01 - (_Speed * tick / 1000);
  27 : _X02 = _X01 + _Bitmap.getWidth();
  28 : 
  29 : if ((_Bitmap.getWidth()+_X02) <= canvas.getWidth()) {
  30 : double temp = _X01;
  31 : _X01 = _X02;
  32 : _X02 = temp + _Bitmap.getWidth();
  33 : }
  34 : }
  35 : 
  36 : public void setBitmap(Bitmap value) {
  37 : _Bitmap = value;
  38 : }
  39 : public Bitmap getBitmap() {
  40 : return _Bitmap;
  41 : }
  42 : 
  43 : public void setSpeed(int value) {
  44 : _Speed = value;
  45 : }
  46 : 
  47 : public int getSpeed() {
  48 : return _Speed;
  49 : }
  50 : 
  51 : }
 
대분의 코드는 BackGround.java에서 구현했던 것과 동일 합니다.  특별히 다시 설명해야 할 만한 곳이 보이지 않기 때문에 설명을 생략하도록 하겠습니다.
 
이제와서 느낀건데 왜 BackGround가 Background보다 자연스럽게 느껴졌던 걸까요?  아놔!!
 
 
screenshot3.png
[그림 5] 완성된 결과
 
 
 
정리
  • 배경 이미지를 표시하고 스크롤 하기 (배경 이미지 순환 반복)
  • 배경 이미지를 여러 개를 준비하고, 속도를 달리하여 입체감 살리기
 
 
 
소스
 
첨부 파일을 참고 하세요.
 
 

이 글은 스프링노트에서 작성되었습니다.

'안드로이드' 카테고리의 다른 글

화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
밑에 메뉴바.  (0) 2012.05.08
나인패치  (1) 2012.05.08
activity  (0) 2012.05.08
Posted by 사라링

밑에 메뉴바.

2012. 5. 8. 18:36
  1. package kr.or.ddit.hello;

    import java.net.URL;

    import android.app.Activity;
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.Toast;

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

            Button startBtn  = (Button)findViewById(R.id.startBtn);
           
            startBtn.setOnClickListener(new OnClickListener() {
                public void onClick(View v) {
    //                Toast.makeText(getApplicationContext(), "시작 버튼을 누룸", 2000).show();
                    //2000(2초) 잠시 창이 뛰어짐.
                    Intent myIntent = new Intent(getApplicationContext(),NewActivity.class);
                    startActivity(myIntent);
                }
            });
           
            Button secondBtn = (Button)findViewById(R.id.secondBtn);
            secondBtn.setOnClickListener(new OnClickListener() {
               
                public void onClick(View v) {
                    Intent myIntent = new Intent(Intent.ACTION_VIEW,Uri.parse("http://m.daum.net"));
                    startActivity(myIntent);
                    //Intent란?
                    // 전달 해주는 역활.
                    // 인터넷 다음 창으로 접속함.
                }
            });
            Button endBtn = (Button)findViewById(R.id.endBtn);
            endBtn.setOnClickListener(new OnClickListener() {
               
                public void onClick(View v) {
                    Intent  myIntent = new Intent(Intent.ACTION_VIEW,Uri.parse("tel:010-8837-6366"));
                    startActivity(myIntent);
                   
                }
            });
           
           
           
        }

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
                MenuItem item01 = menu.add(Menu.FIRST,Menu.FIRST, Menu.NONE,"설정");
              MenuItem item02 = menu.add(Menu.FIRST,Menu.FIRST+1, Menu.NONE,"전화하기");
              MenuItem item03 = menu.add(Menu.FIRST,Menu.FIRST+2, Menu.NONE,"웹뷰");
              MenuItem item04 = menu.add(Menu.FIRST,Menu.FIRST+3, Menu.NONE,"바탕화면");
              item01.setIcon(R.drawable.ic_launcher);
            return super.onCreateOptionsMenu(menu);
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
    //        if(item.getItemId()==1){
    //           
    //        }else if(item.getItemId()==2){
    //           
    //        }
           
            switch (item.getItemId()) {
            case 1:
                Intent intent  = new Intent(getBaseContext(),NewActivity.class);
                startActivity(intent);
                break;
            case 2:
                Intent  myIntent = new Intent(Intent.ACTION_VIEW,Uri.parse("tel:010-8837-6366"));
                startActivity(myIntent);
               
                break;
            case 3:
                Intent myIntentw = new Intent(Intent.ACTION_VIEW,Uri.parse("http://m.daum.net"));
                startActivity(myIntentw);
                break;
            case 4:
                Toast.makeText(getApplicationContext(), "바탕화면누룸", 2000).show();
                break;

            default:
                break;
            }
           
           
            return super.onOptionsItemSelected(item);
        }
       
       
       
    }

이 글은 스프링노트에서 작성되었습니다.

'안드로이드' 카테고리의 다른 글

화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
나인패치  (1) 2012.05.08
activity  (0) 2012.05.08
Posted by 사라링

나인패치

2012. 5. 8. 18:36

Nine-Patch
 - 동적 비트맵 이미지
 - 나인 패치를 배경 이미지로  사용하면 안드로이드가 컨텐츠 크기에 따라서 자동적으로 배경 이미지의
   사이즈를 조절하게 된다.
 - 예를 들어 버튼의 경우 버튼 안의 문자열 길이에 따라 버튼이 가변적으로 늘어나게 된다.
 - 가장 자리에 1px의 라인을 포함하는 표준 PNG로써, 확장자는 반드시 '이름.9.png' 형태로 저장되어야한다.


참고 ::
http://developer.android.com/guide/topics/graphics/2d-graphics.html#nine-patch
http://developer.android.com/guide/developing/tools/draw9patch.html


구조

 - 왼쪽, 상단은 지정된 픽셀 길이만큼 늘어나는 영역에 해당된다.
 - 오른쪽, 하단은 지정된 픽셀 길이만큼 내용이 들어가는 영역에 해당된다.


 

만드는 방법
 - 나인 패치 제작 툴은 자바 어플리케이션이므로 JDK가 설치되어 있어야한다.
 - 안드로이드 SDK 폴더내 tools 폴더에 보면 'draw9patch.bat' 파일을 실행하거나 아래 파일을 다운 받아 실행한다.

 
1. 툴 실행 화면 

 

2. 나인 패치를 적용할 이미지를 불러온다.

 

3. 첨에 불러왔을때 위와 같이 나오는데 아래처럼 Zoom 옵션을 만져주면 아래처럼 정상적으로 나온다.

 

4. 먼저 가변적으로 늘어날 영역을 지정한다. 'Show patches'를 체크한 후 왼쪽과 상단 가장자리에
   마우스를 클릭하면 픽셀이 생기는데 그 라인이 늘어날 영역에 해당된다. (오른쪽 클릭은 픽셀 제거)

 

5. 이제 내용(컨텐츠)가 들어갈 영역을 지정해준다. 'Show contents'를 체크한 후 오른쪽 화면을 보면서

   오른쪽과 하단 영역 가장자리 픽셀에 원하는 만큼 그어준다.



6. 다 되었으면 저장한다. 확장자는 반드시 '이름.9.png'여야 적용이 된다.

 

7. 나인 패치가 적용된 이미지는 아래처럼 보인다.

 

8. 나인 패치를 배경 이미지로 적용한 결과

 


응용)

이 글은 스프링노트에서 작성되었습니다.

'안드로이드' 카테고리의 다른 글

화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
밑에 메뉴바.  (0) 2012.05.08
activity  (0) 2012.05.08
Posted by 사라링

activity

2012. 5. 8. 18:35

액티비티, 뷰 그리고 레이아웃

액티비티(Activity)

액티비티는 안드로이드 어플리케이션을 구성하는 가장 기본적인 빌딩블록입니다. 보통의 경우 한 화면을 차지하면서 뷰(View)로 구성된 유저 인터페이스를 화면에 표시하고 사용자의 입력을 처리하는 역할을 합니다.보통의 어플리케이션은 여러 다른 화면을 가지고 있고, 각각의 화면은 다른 액티비티로 구현되어있습니다. 그러므로 화면의 전환이 이루어지게되면 새로운 액티비티가 실행되어 처리하게 됩니다. 어떤 경우는 액티비티 간에 데이터를 서로 주고 받을 수도 있습니다. 새로운 화면이 생성되며 기존의 화면은 스택에 놓여집니다. 각각의 액티비티는 스택을 통해 관리되며 현재 액티비티를 종료하면 그 이전 화면의 액티비티로 돌아가게 됩니다.

뷰(View)

뷰는 화면상에서 유저 인터페이스를 구성하는 기본 빌딩블록입니다. 예를 들어 버튼, 그림, 텍스트, 에디트, 라디오 버튼, 체크박스 등의 기본적인 화면 구성 요소들이 뷰에 포함됩니다. 또한 웹, 맵, 비디오등을 표시하는 고급 구성 요소들도 모두 뷰에 포함됩니다. 뷰의 리스너(Listener) 설정을 통해 이벤트가 발생했을 경우를 처리할 수 있습니다. 예를 들어 버튼이 클릭되었을때 등록된 OnClickListener가 호출되어 처리됩니다.

레이아웃(Layout)

각각의 뷰들을 화면상에 배치하고 구성해주는 것을 레이아웃이라고 합니다. 레이아웃은 보통의 경우 XML을 이용하여 구성합니다. 이전 강좌에서도 리소스(res) 아래의 layout에 myactivity.xml파일을 생성해 준 적이 있지요. 

 레이아웃 XML

01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03.android:orientation="vertical"
04.android:layout_width="fill_parent"
05.android:layout_height="fill_parent"
06.>
07.<TextView
08.android:layout_width="fill_parent"
09.android:layout_height="wrap_content"
10.android:text="안녕하세요 모바일 플레이스 안드로이드 강좌 수강생 여러분"
11./>
12.</LinearLayout>

(예제 4-1)

lecture4-1.JPG
(그림 4-1)

먼저 기본으로 생성되는 형태의 레이아웃 XML인 (예제 4-1)을 분석해보겠습니다.

XML파일은 <?xml version="1.0" encoding="utf-8"?> 으로 시작합니다. 그리고 첫번째 태그에는 xmlns:android="http://schemas.android.com/apk/res/android" 라는 XML Namespace가 들어가야 합니다.  레이아웃 XML의 각각의 태그 (LinearLayout, TextView)는 하나의 뷰를 나타내며 연결되는 자바 클래스를 가지고 있습니다. 뷰는 크게 두가지 형태가 존재하는데 버튼, 그림과 같이 실제 기능을 가지고 화면에 표시되는 뷰와 다른 뷰를 포함하고 배치하는 역할을 하는 레이아웃이 있습니다. 레이아웃은 하는 역할은 약간 다르지만 View 클래스를 상속하는 일종의 뷰라고 할 수입니다. 레이아웃 XML의 최상위에는 하나의 뷰만 존재할 수 있습니다. 보통 레이아웃이 들어가서 트리 형태로 다른 뷰들을 배치하고 구성하지만 일반 뷰 하나만 들어있는 레이아웃 XML도 작성할 수 있습니다.

흔히 많이 쓰이는 뷰와 레이아웃은 다음과 같습니다.

뷰       : TextView, Button, ImageView, ListView, EditText, ...
레이아웃 : LinearLayout, RelativeLayout, FrameLayout, AbsoluteLayout, ...

(예제 4-1)은 LinearLayout이 하나의 TextView를 자식으로 가지고 있는 형태입니다. 레이아웃 태그 사이에는 자식 뷰들이 여러 개 들어갈 수 있으며 레이아웃이 레이아웃을 자식으로 가지고 있는 형태도 가능합니다.

View Attribute

XML에서 각각의 뷰가 가지는 속성을 Attribute로 지정해할 수 있습니다. 모든 View가 공통적으로 가지고 있는 Attribute에 대해서 먼저 설명하도록 하겠습니다.

layout_width, layout_height 뷰의 넓이와 높이를 지정합니다. 값으로 fill_parent, wrap_content 혹은 절대적인 수치를 가질 수 있습니다. fill_parent는 컨테이너 즉 부모가 가지는 길이를 모두 채울때 사용하고, wrap_content는 해당 뷰가 그려질 수 있게 필요한 길이만 차지한다는 것을 나타냅니다. 절대적인 값도 넣을 수 있는데 픽셀의 경우 "100px", "100dp", "100sp" 처럼 수치와 단위를 써서 지정해줍니다. 사용할 수 있는 단위는 px, in, mm, pt, dp, sp등이 있는데 주로 dp, sp, px이 주로 쓰입니다. px는 픽셀을 나타내고, dp는 Density-independent Pixel이라고 화면의 밀도의 변화에 독립적으로 1dp는 160dpi의 화면에서의 1px에 대응됩니다. sp는 Scale-independent Pixel 이라고 하여 사용자의 폰트 선호도에 따라 크기가 달라지며 주로 폰트 사이즈 설정에 사용됩니다.

background 배경색 혹은 그림을 지정해줍니다. 색은 #RGB, #ARGB, #RRGGBB, #AARRGGBB 의 포맷으로 지정해 줄 수 있는데 저는 통일성있게 #AARRGGBB포맷만을 주로 사용합니다. 제일 앞에 AA는 투명도를 의미하고 나머지 부분은 RGB값을 의미합니다. 투명도인 AA는 00이 완전 투명, FF가 불투명이 됩니다. 예를 들어 android:background="#FFFF0000"로 지정해주면 빨간색으로 배경을 칠하게 됩니다. 배경그림을 지정해줄 수도 있는데 android:background="@drawable/background_image" 와 같은 형태로 사용가능합니다. 배경 그림은 리소스에 들어있는 jpg, png등의 그림을 배경으로 지정할 때 사용합니다.


visibility 뷰가 화면에 보이게 할지 안보이게 할지를 설정합니다. visible, invisible, gone의 값을 가질 수 있습니다. visible 화면에 보임, invisible 화면에 보이지 않으나 공간은 차지함, gone 화면에 보이지도 않고 공간도 차지 하지 않음.

id 코드에서 해당 뷰를 찾아올 수 있도록 id를 지정합니다. id에 사용에 대해서는 자바코드와 연결하는 부분에서 자세하게 다루도록 하겠습니다.

myactivity.xml을 수정해보면서 각 속성값이 어떻게 실제 화면에 영향을 주는지 확인해보도록 하겠습니다.

01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03.android:orientation="vertical"
04.android:layout_width="fill_parent"
05.android:layout_height="fill_parent"
06.android:background="#FF888888"
07.>
08.<TextView
09.android:layout_width="fill_parent"
10.android:layout_height="wrap_content"
11.android:text="파랑"
12.android:background="#FF0000FF"
13./>
14.</LinearLayout>

(예제 4-2)

lecture4-2.JPG
(그림 4-2)

자 그럼 속성값들을 변경해보도록 하겠습니다. 먼저 배경색을 지정하여 실제 뷰가 차지하는 영역을 살펴보도록 하겠습니다. (예제4-2)에서 LinearLayout은 width, height를 각각 fill_parent로 화면 전체를 채우고 있으며, TextView는 width는 fill_parent로 부모의 길이만큼 모두 차지하지만 height를 wrap_content로 하여 자신이 차지하는 부분만을 높이로 가지는 것을 볼수 있습니다.

01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03.android:orientation="vertical"
04.android:layout_width="fill_parent"
05.android:layout_height="fill_parent"
06.>
07.<TextView
08.android:layout_width="fill_parent"
09.android:layout_height="wrap_content"
10.android:text="빨강"
11.android:background="#FFFF0000"
12./>
13.<TextView
14.android:layout_width="wrap_content"
15.android:layout_height="50dp"
16.android:text="녹색"
17.android:background="#FF00FF00"
18./>
19.<TextView
20.android:layout_width="fill_parent"
21.android:layout_height="wrap_content"
22.android:text="파랑"
23.android:background="#FF0000FF"
24./>
25.</LinearLayout>

(예제 4-3)

lecture4-3.JPG
(그림 4-3)

자 이번에는 (예제 4-3)에 TextView를 두개 더 추가하여 배경색을 빨강, 녹색, 파랑으로 칠하고 가운데 있는 TextView의 width를 wrap_content로 height를 50dp로 지정해 보았습니다.

lecture4-4.JPG
(그림 4-4)

lecture4-5.JPG
(그림 4-5)


(그림 4-4)와 (그림 4-5)는 각각 두번째 TextView에 android:visibility="invisible", android:visibility="gone"으로 설정하여 화면에 보일때와 안보일 때 그리고 영역을 차지 하지 않을 때의 차이를 살펴보실 수 있습니다.

이 글은 스프링노트에서 작성되었습니다.

'안드로이드' 카테고리의 다른 글

화면회전  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
배경화면꾸미기  (0) 2012.05.08
밑에 메뉴바.  (0) 2012.05.08
나인패치  (1) 2012.05.08
Posted by 사라링

BLOG main image
.. by 사라링

카테고리

사라링님의 노트 (301)
JSP (31)
J-Query (41)
JAVA (24)
VM-WARE (0)
디자인패턴 (1)
스크랩 (0)
스트러츠 (3)
안드로이드 (11)
오라클 (45)
우분투-오라클 (1)
이클립스메뉴얼 (6)
스프링3.0 (23)
자바스크립트 (10)
HTML5.0 (17)
정보처리기사 (1)
기타(컴퓨터 관련) (1)
문제점 해결 (3)
프로젝트 (2)
AJAX (4)
하이버네이트 (3)
트러스트폼 (11)
Jeus (2)
재무관리(회계) (5)
정규식 (5)
아이바티스 (8)
취미 (2)
소프트웨어 보안 관련모음 (0)
정보보안기사 (6)
C언어 베이직 및 프로그램 (3)
보안 관련 용어 정리 (2)
넥사크로 (6)
Total :
Today : Yesterday :