xml의 layout_gravity에 대한 숨겨진 규칙


안드로이드 앱 개발을 하다보면 

xml 레이아웃 파일에서 조정하는 작업들이 

생각처럼 잘 안 먹혀 들어갈 때가 있다.

특히 layout_gravity에 대해 작업할 때

그런 경험을 자주 하게 된다.

그 이유는 이런 개념 때문이다.



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

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

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical" >

    <Button

           android:id="@+id/btnPlus"

           android:layout_width="wrap_content"

           android:layout_height="wrap_content"

           android:padding="10dip"

           android:text=" 테스트 "

           android:layout_gravity="center"

           android:textSize="25sp" />

</LinearLayout>


LinearLayout의 orientation이 vertical일 경우 

Button의 layout_gravity는 수평 관련 속성만 적용이 된다.

예를 들어 layout_gravity="left" 혹은 center_horizontal 혹은 right와 같이 수평 관련 속성만 적용이 된다.

수직 관련 속성은 적용이 안된다. 

예를 들어서 layout_gravity="bottom", 혹은 top...은 적용이 안된다.


만일 orientation이 horizontal이라면 

Button의 layout_gravity는 수직 관련 속성만 적용이 된다.

bottom, top, center_vertical


이 이야기의 핵심은 LinearLayout에서 첫 번째 방향을 설정했으면(만일 수평이면) 두 번째 방향 설정(layout_gravity)에서는 수평이 상태에서 그 수평 중 어느 위치로 움직일 것인지를 결정하는 것만 허용이 된다는 것이다.

즉 수평이 첫 번째 설정 값이면(LinearLayout에서) 그 수평 중 top에 놓일지, bottom에 놓일지, center_vertical에 놓일지만 결정한다는 뜻이다.



타이틀바 없애고 전체화면, 화면 가로로(혹은 세로로) 고정하는 법


안드로이드 앱 실행시 전체화면으로 보이게 하고, 타이틀 바 없애고 화면을 가로로 고정할려면

소스 코드 상에서도 처리할 수 있으나 아래와 같이 Manifest 파일에서 설정할 수 있다.

소스 코드 상에서는 실행 시점에 처리한다면 Manifest 파일에서 설정하면 컴파일 단계에서 미리 처리를 한다.


AndroidManifest.xml에 다음을 추가


        <activity

            android:name=".TestActivity"

            android:label="@string/app_name"

            android:configChanges="orientation|keyboardHidden"

            android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

            android:screenOrientation="landscape" >


화면을 전체 화면과 타이틀 바를 없애는 기능이

android:theme="@android:style/Theme.NoTitleBar.Fullscreen"

이고


화면을 가로고 고정하는 방법이 

android:screenOrientation="landscape"

이다.

만일 화면을 세로로 고정하고자 한다면 

android:screenOrientation="portrait"

이다.


혹은 앱만이 아닌 화면 자체를 세로로 뒤집을려면

android:screenOrientation="reversePortrait"

과같이 하면 된다.







안드로이드 xml 문서에(예: strings.xml)에 공백을 넣기 위해서 HTML에서 사용하는

&nbsp;를 사용하면 컴파일 단계에서 에러가 발생한다.


&nbsp; ⇒ HTML에서 공백 넣은 이 기호는 에러


이때 공백을 포함한 특수 문자를 넣기 위해서는 아래의 방식으로 처리하면 된다. 


&#160;    ⇒     공백

&lt;          ⇒     <

&gt;         ⇒     >

\t             ⇒     탭

\n            ⇒     한 줄 띄우기

\'             ⇒     apostrophe(예: Tom\'s)



안드로이드에서 DB 작업을 하다보면 다음과 같은 에러를 만날 때가 있다.


"android.database.

       CursorIndexOutOfBoundsException: 

             Index -1 requested, with a size of ..."


이 에러는 DB에서 SELECT한 결과를 Cursor로 받는데 이때 Cursor의 위치가 첫번째 항목 바로 앞(index가 -1인) 위치에 놓이게 된다.

이때 다음 명령을 실행하면 


cursor.getString(cursor.getColumnIndex("name"));


android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1과 같은 에러가 발생한다.

왜냐하면 cursor의 위치가 before the first entry이기 때문이다. 즉 cursor의 index가 첫 번째 데이터 바로 앞인 -1의 위치에 있기 때문이다.


이건 다음 둘 중 한 방법으로 처리 해야한다.


1) cursor.moveToFirst()를 cursor.getString() 이전에 실행해서 index가 -1인 (첫 번째 항목 바로 앞) 위치에서 첫 번째 항목으로 이동시킨 후

cursor.getString(cursor.getColumnIndex("name"));

과 같은 명령을 통해 원하는 데이터를 추출하면 된다.


2) while문을 이용해서 cursor.moveToNext()를 이용해서 하면 moveToFirst()를 안 해도 된다.

이때 movetToFirst()를 실행하면 맨 첫번째 데이터를 놓치고 넘어가게 된다.

while(cursor.moveToNext()) {...}를 하면 index -1에서 cursor.moveToNext()로 인해

첫 번째 데이터 위치로 index가 옮겨가게 되므로 굳이 moveToFirst()가 필요 없게 된다.


while(cursor.moveToNext()) {

name = cursor.getString(cursor.getColumnIndex("name"));

}


아래는 코드 조각이다.


DBHelper db_Helper = new DBHelper(mContext);

SQLiteDatabase db = db_Helper.getReadableDatabase();

String sql = "SELECT * FROM "+mContext.getString(R.string.memberTable) 

+" WHERE "+mContext.getString(R.string.pinNum)+"='"+pin_num+"';";

//A Cursor object, which is positioned before the first entry.

Cursor cursor = db.rawQuery(sql, null);

//cursor.moveToFirst(); 

if (cursor.getCount() > 0) {

while(cursor.moveToNext()) {

name = cursor.getString(cursor.getColumnIndex("name"));

}

}

db_Helper.close();





안드로이드 전화번호 입력시 자동으로 dash(-) 붙이기


전화번호를 입력할 때 01028910318과 같이 입력해도(-를 입력하지 않아도) 010-2891-0318과 같이 자동으로 자릿 수에 적당하게 -를 입력되게하는 기능이 안드로이드에서 제공된다. 

해당 클래스는 PhoneNumberFormattingTextWatcher이다.


TextView의 메소드 중 아래 메소드에 적용하면 된다.

public void addTextChangedListener (TextWatcher watcher)


EditText edPhone = 

            (EditText)findViewById(R.id.edPhone);


edPhone.addTextChangedListener(new PhoneNumberFormattingTextWatcher()); 

⇒ 이 메소드의 매개인자로 PhoneNumberFormattingTextWatcher가 올수 있는 이유는 

PhoneNumberFormattingTextWatcher가 TextWatcher 인터페이스를 구현했기 때문에 가능하다.




callback 메소드가 있는 클래스의 객체 선언 법


abstract 클래스도 아니고 interface도 아닌 

일반 클래스임에도 콜백 메소드(시스템에 의해 자동 호출되는 메소드)가 있을 경우 abstract 클래스나 interface와 유사한 방식으로 객체를 생성을 할 수 있다. 

아래 두 경우를 보면 모두 일반 클래스이다.


public class Handler extends Object

public class PhoneStateListener extends Object


그런데 이들 클래스 안에 콜백 메서드를 사용하기 위해 다음과 같이 mHandler라는 객체를 다음과 같이 생성한다.


 Handler mHandler = new Handler(){ 

     @Override

     public void handleMessage(Message msg){ //이게 콜백 메소드

         //여기서 원하는 기능 수행

     }

  };


위의 handleMessage() 메소드는 핸들러로 메시가 들어올때 즉 이 핸들러 호출이 있을 때 시스템에 의해 이 메소드 호출된다.



PhoneStateListener mPhoneState = new PhoneStateListener(){

    public void onCallStateChanged(int state, String incomingNumber){ //이게 콜백 메소드이다.

    switch(state){

    case TelephonyManager.CALL_STATE_IDLE :

    txt.append("\n☆ 전화 상태 : 대기 상태");

    break;

    case TelephonyManager.CALL_STATE_OFFHOOK :

    txt.append("\n☆ 전화 상태 : 통화 중");

    break;

    case TelephonyManager.CALL_STATE_RINGING :

    txt.append("\n☆ 전화가 왔습니다 : " + incomingNumber);

    break;

    }

    }

    };





한 프로그램에 여러 Activity가 있을 때 처음 실행되는 Activity 지정하는 법


보통 한 프로그램 안에 여러 개의 Activity가 돌아간다. 

이때 어느 것을 Main으로 설정하는지 다음과 같이 Menifest 파일에 등록해 주면 된다.


아래 적색 부분을 포함하고 있는 Activity가 프로그램 실행 시 화면에 맨 처음 뜨게 된다.

여기서 AndExam.java가 Main으로 등록되어 있다.



        <activity android:name=".AndExam"

                  android:label="@string/app_name">

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>





안드로이드와 서버와의 통신에서 연결하는 절차


-. URL로 연결할 지점에(접속할 주소) 대한 객체를 만든다. 


URL url = new URL("http://www.google.com");


※ URL이란 A Uniform Resource Locator that identifies the location of an Internet resource 

as specified by RFC 1738.


-. HttpURLConnection 클래스의 openConnection() 메소드로 해당 주소에 접속한다. 

이때 반환되는 객체가 URLConnection 클래스의 객체이다. 이때 프로토콜이 뭐냐에 따라 

HttpURLConnection, FtpURLConnection... 등이 반환된다. 따라서 해당 프로토콜에 맞게 다운캐스팅해서 사용하면된다.


HttpURLConnection httpURL = (HttpURLConnection)url.openConnection();


-. 해당 프로토콜에 필요한 각종 속성을 설정한다. API문서를 참조


httpURLCon.setConnectTimeout(10000);

httpURLCon.setUseCaches(false);


-. Http 연결일 경우는 요청방법을 지정해야 한다. 


httpURLCon.setRequestMethod("GET"); //이게 디폴트

httpURLCon.setRequestMethod("POST");


-. 모든 속성이 완료되면 서버에 요청을 보낸다. 요청이 무사히 전달됐으면 HTTP_OK(200)가 리턴된다. 반환되는 값에 대한 정보는 HttpURLConnection 클래스에 Constants로 잘 정의되어 있다.


httpURLCon.getResponseCode(); //public int getResponseCode ()


-. 다음과 같이 BufferedReader를 이용해서 서버에서 보내온 데이타를 읽는다.

BufferedReader 클래스는 생성자의 매개인자로 Reader 클래스의 객체가 와야 한다.

public BufferedReader (Reader in)

여기서 Reader클래스를 상속받은 클래스들이 BufferedReader, CharArrayReader, FilterReader, 

InputStreamReader, PipedReader, StringReader이다.

따라서 생성의 매개인자로 Reader클래스를 상속받은 하위 클래스의 객체가 올수있다. 

따라서 InputStreamReader 객체가 올 수 있다. 

InputStreamReader의 객체 생성은 생성자가 아래와 같이 되어 있다.


InputStreamReader(InputStream in) ==> InputStreamReader의 생성자. 매개인자로는 InputStream 객체가 올수 있다.

매개인자로 들어갈 InputStream 객체를 얻는 방법은 URLConnection의 메소드인 getInputStream()을 통해서 얻을 수 있다.


public InputStream getInputStream() ==> 이 메소드는 HttpURLConnection이 상속받은 상위 클래스인 URLConnection의 메소드인데 상속 


BufferedReader br = new BufferedReader(new InputStreamReader(httpURLCon.getInputStream()));

String temp = ""; //서버에서 보내온 데이터를 받을 공간


BufferedReader클래스의 메소드 가운데 이런 편리한 메소드가 있다. 


public String readLine () 

      ⇒ Returns the next line of text available from this reader. 

          A line is represented by zero or more characters followed 

          by '\n', '\r', "\r\n" or the end of the reader. 

          The string does not include the newline sequence.

      ⇒ Returns : the contents of the line or null if no characters were read before the end of the reader has been reached.

      ⇒ 한 마디로 readLine() 메소드는 캐리지 리턴을 기준으로 한 라인씩 읽어서 String으로 반환한다.


다음과 같이 서버로 부터 보내온 데이터를 읽으면 된다.


String addr = "";

for(;;){

      temp = br.readLine();

      if (temp == null) 

         break;

      addr += temp;

}

       

br.close(); //다 읽었으면 닫아 준다.

httpURLCon.disconnect(); //연결을 끊어 준다.


네트워크에 연결하는 경우는 다양한 이유로 연결에 실패할 수 있기 때문에 각종 Exception이 발생할 수 있다. 이에 대해서는 API문서를 참조할 것.





폰의 저장 공간(외부 SD 카드가 아닌 디바이스 자체의 저장 공간)의 

특정 디렉토리에 

특정 파일을 

저장하는 법.


특정 디렉토리는 MyDir

특정 파일 명은 MyImg.jpg라고 가정.


각 안드로이드 기기의 저장 공간에 대한 경로 명이 제조사 별로 상이하다.

따라서 저장 공간의 경로 명을 먼저 가져와야 된다. 

아래 메소드를 이용해서


String sdPath = 

    Environment.getExternalStorageDirectory().getAbsolutePath();


이렇게 얻어진 디바이스의 기본 경로에 내가 원하는 경로(MyDir)을 추가해 준다.


sdPath += "/MyDir";

이렇게 추가된 경로가 존재하지 않을 수 있기 때문에 존재 하지 않는다면 이 디렉토리를 새로 생성해 준다.


File file = new File(sdPath);

file.mkdirs(); //없으면 디렉토리 생성, 있으면 통과


이제 내가 생성하고자 하는 파일을 생성한다.


sdPath += "/MyImg.jpg";


이 파일도 존재하지 않는 파일이기에 File 클래스의 createNewFile() 메소드를 통해 새로 생성한다.


file = new File(sdPath);

try {

file.createNewFile();

Toast.makeText(mContext, "이미지 디렉토리 및 파일생성 성공~", 1).show();

} catch(IOException ie){

Toast.makeText(mContext, "이미지 디렉토리 및 파일생성 실패", 1).show();

}


아래는 소스 조각이다.


String sdPath = Environment.getExternalStorageDirectory().getAbsolutePath();

sdPath += "/MyDir";

File file = new File(sdPath);

file.mkdirs();

sdPath += "/MyImg.jpg";

file = new File(sdPath);

try {

file.createNewFile();

Toast.makeText(mContext, "이미지 디렉토리 및 파일생성 성공~", 1).show();

} catch(IOException ie){

Toast.makeText(mContext, "이미지 디렉토리 및 파일생성 실패", 1).show();

}





안드로이드 ProgressDialog 만들기


생상자가 아래와 같이 2가지 형태가 있다.


ProgressDialog(Context context)

ProgressDialog(Context context, int theme)


첫 번째는 디폴트 형태로 만드는 방식이고 두 번째는 다이알로그의 형태(배경 색, 모양, 크기...)를 두 번째 매개인자가 지정하는 형태(theme)대로 만드는 방식이다.


theme에는 다음과 같은 형태들이 있다.


//검정색 바탕에 흰 글씨(폭이 좁은 형태)

ProgressDialog pDialog = 

                new ProgressDialog(TestActivity.this, AlertDialog.THEME_HOLO_DARK); 

       

//흰색 바탕에 검정색 글씨(폭이 좁은 형태)

ProgressDialog pDialog = 

                   new ProgressDialog(TestActivity.this, AlertDialog.THEME_HOLO_LIGHT); 

       

//검정색 바탕에 흰 글씨(폭이 넓은 형태)

ProgressDialog pDialog = 

                 new ProgressDialog(TestActivity.this, AlertDialog.THEME_TRADITIONAL); 

       

//장비의 안드로이드 버전에 따른 형태(검정색 바탕)

ProgressDialog pDialog = 

         new ProgressDialog(TestActivity.this, AlertDialog.THEME_DEVICE_DEFAULT_DARK);

       

//장비의 안드로이드 버전에 따른 형태(흰색 바탕) - 아래 이미지와 같다

ProgressDialog pDialog = 

        new ProgressDialog(TestActivity.this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);




이들 Theme은 AlertDialog 클래스에 정의되어 있다. 그런데 안드로이드 버전 23부터는 

대부분 deprecated되었다.

아래는 코드 조각이다.


ProgressDialog pDialog = new ProgressDialog(TestActivity.this);

pDialog.setCancelable(true);

//pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); //직선 막대그래프 형태

pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); //원형 형태

//pDialog.setTitle("등록 상황"); //타이틀

pDialog.setMessage("잠시만 기다리세요...");

pDialog.show();




+ Recent posts