CHAPTER 08 레이아웃을 이용한 화면 배치

1. 레이아웃

  • 레이아웃은 위젯들을 어떠한 방식으로 화면에 배치해줄지를 결정하는 하나의 "컨테이너" 역할
  • 레이아웃에 포함되는 위젯들은 하나의 뷰(View)를 상속받은 것들이며, 결국 레이아웃은 뷰들을 담을 수 있는 객체라 할 수 있습니다. 따라서, 레이아웃을 ViewGroup이라고도 합니다. (실제로 다른 뷰를 담을 수 있는 객체들은 ViewGroup을 상속하고 있습니다.)
  • 레이아웃 구성 방법
XML 자원코드에서 직접 레이아웃 생성코드에서 직접 레이아웃 생성
가장 많이 사용하는 방법
res/layout 폴더에 위치한 XML 파일들을 이용하여 레이아웃을 구성함
화면구성을 코드와 분리시켜 유지보수시 편리
레이아웃 편집기를 이용해서 편집할 수도 있음
XML에 비해 복잡하고 유지보수에 어려움이 있음
  • 레이아웃 종류
종류설명
리니어 레이아웃(Linear Layout)어떻게 정렬할 것인지 지정하느냐에 따라 수평 또는 수직으로 모든 자식 요소들을 한 방향으로 정렬
프레임 레이아웃(Frame Layout)왼쪽 상단에 위치하게 하고, 위로 쌓이게 하는 구조
릴레이티브 레이아웃(Relative Layout)자식 객체들이 ID로 명명된 다른 객체나 부모 객체에 대해 상대적인 위치를 지정
테이블 레이아웃(Table Layout)하위의 객체들을 행과 열을 이용하여 배치. 각 셀은 위젯을 가지고, 각 행들은 가장 큰 열의 크기로 리사이징 된다. 셀의 테두리는 보여지지 않는다.
탭 레이아웃(Tab Layout)탭을 구성할 수 있는 레이아웃

1.1. 리니어레이아웃

  • orientation
    • 배치 방향을 결정하는 속성. (디폴트는 horizontal)
    • vertical : 차일드를 위에서 아래로 수직으로 배열
    • horizontal : 차일드를 왼쪽에서 오른쪽으로 수평 배열
  • layout_width, layout_height
    • 크기지정
    • wrap_content : 자신(컨테이너/위젯)이 포함하는 내용(content)에 딱 맞을 만큼의 크기로 자기 자신을 설정
    • fill_parent : 자신을 포함하는 부모 컨테이너를 꽉 채우도록 설정에 꽉 차도록 설정.
    • 상수 + 단위
  • layout_weight
    • 중요도에 따라 크기를 균등 분할
    • 중요도가 0이면 자신의 고유한 크기만큼, 1 이상이면 형제 뷰와의 비율에 따라 부모의 영역을 균등하게 배분

res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <!-- 3명의 고객정보에 대한 수직배치: 시작 -->    
    <LinearLayout 
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" >
        <TextView  
            android:text="@string/customer1"
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" />
        <TextView  
            android:text="@string/customer2"
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" />
        <TextView  
            android:text="@string/customer3"
            android:layout_width="fill_parent" 
            android:layout_height="wrap_content" 
            android:layout_weight="1" />
    </LinearLayout>
    <!-- 3명의 고객정보에 대한 수직배치: 끝 -->    

    <!-- 3가지 색에 대한 수평배치: 시작 -->    
    <LinearLayout 
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" >
        <TextView  
            android:text="@string/color1"
            android:gravity="center_horizontal"
            android:background="#ff0000"
            android:layout_width="wrap_content" 
            android:layout_height="fill_parent" 
            android:layout_weight="1" />
        <TextView  
            android:text="@string/color2"
            android:gravity="center_horizontal"
            android:background="#00ff00"
            android:layout_width="wrap_content" 
            android:layout_height="fill_parent" 
            android:layout_weight="3" />
        <!-- 색 3 -->
        <TextView  
            android:text="@string/color3"
            android:gravity="center_horizontal"
            android:background="#0000ff"
            android:layout_width="wrap_content" 
            android:layout_height="fill_parent" 
            android:layout_weight="3" />
    </LinearLayout>
    <!-- 3가지 색에 대한 수평배치: 끝 -->    

</LinearLayout>

1.2. 테이블 레이아웃

  • TableRow 객체로 구성되며, TableRow 안에 열이 배치되고, 이를 셀이라고 함
  • 테이블의 전체 크기 = 행(TableRow) * 열(셀)
  • 테이블의 차일드들은 정해진 규칙에 따라 크기가 결정되므로 크기 속성에 대한 제약이 있다.
    • TableRow 객체의 높이는 항상 wrap_content로 강제됨 -> 여러 개의 행이 한 테이블에 공존해야 하기 때문
    • layout_width 속성은 따로 지정할 수 없으며 항상 fill_parent로 가정됨 -> 셀에 배치되는 자식 뷰는 무조건 주어진 셀 안에 배치되기 때문
  • 속성
    • layout_column : 이 뷰가 위치할 열의 번호
    • layout_span : 이 뷰가 차지할 열의 개수
    • stretchColumns : 지정된 번호의 열은 해당 행을 꽉 채움
    • shrinkColumns : 지정된 번호의 열의 폭은 줄임
    • collapseColums : 지정된 번호의 열을 숨김

res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
 
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"    
    android:layout_width="fill_parent"    
    android:layout_height="fill_parent"    
    android:stretchColumns="1">    
    <TableRow>        
        <TextView 
            android:layout_column="1"            
            android:text="Open..."            
            android:padding="3dip" />        
        <TextView 
            android:text="Ctrl-O"            
            android:gravity="right"            
            android:padding="3dip" />  
    </TableRow>    
    <TableRow>        
        <TextView            
            android:layout_column="1"            
            android:text="Save..."            
            android:padding="3dip" />        
        <TextView   
            android:layout_column="2"            
            android:text="Ctrl-S"            
            android:gravity="right"            
            android:padding="3dip" />    
    </TableRow>    
    <TableRow>        
        <TextView            
            android:layout_column="1"            
            android:text="Save As..."            
            android:padding="3dip" />        
        <TextView            
            android:text="Ctrl-Shift-S"            
            android:gravity="right"            
            android:padding="3dip" />    
    </TableRow>    
    <View        
        android:layout_height="2dip"        
        android:background="#FF909090" />    
    <TableRow>        
        <TextView            
            android:text="X"            
            android:padding="3dip" />        
        <TextView            
            android:text="Import..."            
            android:padding="3dip" />    
    </TableRow>    
    <TableRow>        
        <TextView            
            android:text="X"            
            android:padding="3dip" />        
        <TextView            
            android:text="Export..."            
            android:padding="3dip" />        
        <TextView            
            android:text="Ctrl-E"            
            android:gravity="right"            
            android:padding="3dip" />    
    </TableRow>    
    <View        
        android:layout_height="2dip"        
        android:background="#FF909090" />    
    <TableRow>        
        <TextView            
            android:layout_column="1"            
            android:text="Quit"            
            android:padding="3dip" />    
    </TableRow>
</TableLayout>


1.3. 탭 레이아웃

  • 탭을 이용하여 여러 개의 뷰를 하나씩 보여줄 수 있음
  • 크게 TabHost, TabWidget, FrameLayout으로 구성

res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"    
    android:id="@android:id/tabhost"    
    android:layout_width="fill_parent"    
    android:layout_height="fill_parent" >    
    <LinearLayout        
        android:orientation="vertical"        
        android:layout_width="fill_parent"        
        android:layout_height="fill_parent"        
        android:padding="5dp" >        
        <TabWidget            
            android:id="@android:id/tabs"            
            android:layout_width="fill_parent"            
            android:layout_height="wrap_content" />        
        <FrameLayout            
            android:id="@android:id/tabcontent"            
            android:layout_width="fill_parent"            
            android:layout_height="fill_parent"            
            android:padding="5dp" />    
    </LinearLayout>
</TabHost>

TabViewActivity.java


package com.andro;

import android.app.TabActivity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.widget.TabHost;

public class TabViewActivity extends TabActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Resources res = getResources(); // 리소스 객체 생성    
        TabHost tabHost = getTabHost();  // 탭메뉴 액티비티 생성    
        TabHost.TabSpec spec;  // 각 탭의 메뉴와 컨텐츠를 위한 객체 선언    
        Intent intent;  // 각 탭에서 사용할 인텐트 선언    
        
        // 인텐트 생성    
        intent = new Intent().setClass(this, CustListActivity.class);    
        
        // 각 탭의 메뉴와 컨텐츠를 위한 객체 생성
        spec = tabHost.newTabSpec("custList").setIndicator("고객현황").setContent(intent);    
        tabHost.addTab(spec);    
        
        intent = new Intent().setClass(this, CustRegActivity.class);    
        spec = tabHost.newTabSpec("custReg").setIndicator("고객등록").setContent(intent);    
        tabHost.addTab(spec);    
        
        intent = new Intent().setClass(this, CustHelpActivity.class);    
        spec = tabHost.newTabSpec("custHelp").setIndicator("도움말").setContent(intent);    
        tabHost.addTab(spec); 
        
        tabHost.setCurrentTab(0);
    }
}

CustListActivity.java


package com.andro;

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

public class CustListActivity extends Activity {    
	public void onCreate(Bundle savedInstanceState) {        
		super.onCreate(savedInstanceState); 
		
		// 현재 객체에 TextView 객체 생성 
		TextView textview = new TextView(this);    
		// 출력할 문자 설정 
		textview.setText("고객현황 화면");
		// 현재 객체에 TextView 객체에서 설정한 문자 출력  
		setContentView(textview);    
	}
}

2. 고급위젯

  • 일반위젯보다 구성이 어려움
  • 예) ListView, Gallery, GridView
  • Adapter
    • 데이터를 표현해주는 방식은 고급 위젯이 아닌 Adapter가 모두 제어
    • 고급 위젯에 쓰이는 데이터들은 보통은 동적으로 바뀌기 때문에 XML 형식으로 정의해서 자주 쓰지 않음.
    • 여러종류의 Adapter 중 목적에 맞는 Adapter를 사용하는 것이 중요

2.1. 리스트뷰

  • 주로 여러 개의 데이터를 사용자에게 보여주기 위해서 사용

res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"    
    android:layout_height="fill_parent"    
    android:padding="10dp"    
    android:textSize="16sp" >
</TextView>

res/values/strings.xml


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

    <string name="app_name">국가명</string>

    <string-array name="countries_array">
        <item>Afghanistan</item>
        <item>Albania</item>
        <item>Algeria</item>
        <item>Anguilla</item>
        <item>Bahrain</item>
        <item>Bangladesh</item>
        <item>Barbados</item>
        <item>Belarus</item>
        <item>Cambodia</item>
        <item>Cameroon</item>
        <item>Canada</item>
        <item>Cuba</item>
    </string-array>        
    
</resources>

ListViewActivity.java


package com.andro;

import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.app.AlertDialog;

public class ListViewActivity extends ListActivity implements OnItemClickListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        ///// 리스트뷰 생성: 시작 /////
        // strings.xml에 정의된 국가명 아이템을 문자열 배열로 저장 
        String[] countries = getResources().getStringArray(R.array.countries_array);
        // 문자열 배열의 각 문자열을 main.xml의 TextView로 대응하는 리스트뷰를 위한 커서를 만듦  
        setListAdapter(new ArrayAdapter<String>(this, R.layout.main, countries));
        // 리스트뷰를 생성함 
        ListView lv = getListView();
        ///// 리스트뷰 생성: 끝 /////        
        
        // 리스트뷰의 클릭 대기 
        lv.setOnItemClickListener(this);
    }
    
    // 리스트뷰의 아이템이 클릭되었을 때 실행 
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    	
    	AlertDialog.Builder alert = new AlertDialog.Builder(ListViewActivity.this);
        alert.setTitle("알림창");
        // 클릭된 아이템 위치, 아이템명 출력
        alert.setMessage(position + ", " + ((TextView)view).getText() + "를  누르셨네요!");
        alert.setIcon(R.drawable.ic_launcher);
        alert.setPositiveButton("확인", null);
        alert.show();
    	
    }
}

2.2. 그리드뷰

  • 아이템을 바둑판 형식으로 나타내는 고급 위젯
  • 간단한 텍스트나 이미지를 표현하는 경우에 자주 사용됨
  • 속성
    • horizontalSpaction : 그리드뷰의 행 간격
    • verticalSpacting : 그리드뷰의 열 간격
    • NumColumns : 그리드뷰의 열의 갯수

res/layout/main.xml


<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"     
    android:id="@+id/gridview"    
    android:layout_width="fill_parent"     
    android:layout_height="fill_parent"    
    android:columnWidth="90dp"    
    android:numColumns="auto_fit"    
    android:verticalSpacing="10dp"    
    android:horizontalSpacing="10dp"    
    android:stretchMode="columnWidth"    
    android:gravity="center" />

ImageAdapter.java


package com.andro;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

public class ImageAdapter extends BaseAdapter {    
	private Context mContext;   
	
	public ImageAdapter(Context c) {       
		mContext = c;    
	}    
	
	// 이미지셋에 있는아이템의 수를 반환함(그리드뷰는 아이템의 수에 해당하는 행렬을 준비함)  
	public int getCount() {        
		return mThumbIds.length;    
	}
	
	public Object getItem(int position) {        
		return null;    
	}
	
	public long getItemId(int position) {        
		return 0;    
	}    
	
	// 주어진 위치(position)에 출력할 이미지를 반환함
	public View getView(int position, View convertView, ViewGroup parent) {        
		ImageView imageView;        
		if (convertView == null) {              
			imageView = new ImageView(mContext);            
			imageView.setLayoutParams(new GridView.LayoutParams(85, 85));            
			imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);            
			imageView.setPadding(8, 8, 8, 8);        
	    } else {            
			imageView = (ImageView) convertView;        
		}        
		
		// 이미지뷰에 주어진 위치의 이미지를 설정함 
		imageView.setImageResource(mThumbIds[position]);        
		return imageView;    
	}    
	
	// 출력될 이미지 데이터셋(res/drawable 폴더)    
	private Integer[] mThumbIds = {            
			R.drawable.sample_0, 
			R.drawable.sample_1,            
			R.drawable.sample_2,            
			R.drawable.sample_3,            
			R.drawable.sample_4, 
			R.drawable.sample_5,            
			R.drawable.sample_6,            
			R.drawable.sample_7,            
			R.drawable.sample_0, 
			R.drawable.sample_1,            
			R.drawable.sample_2,            
			R.drawable.sample_3,            
			R.drawable.sample_4, 
			R.drawable.sample_5,            
			R.drawable.sample_6,            
			R.drawable.sample_7 };            
}

GridViewActivity.java


package com.andro;

import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;

public class GridViewActivity extends Activity implements OnItemClickListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // main.xml의 레이아웃에 있는 GridView를 인식함 
        GridView gridview = (GridView) findViewById(R.id.gridview);
        // GridView에 이미지들을 배치함  
        gridview.setAdapter(new ImageAdapter(this));    
        // GridView에 나타나는 아이템에 대한 클릭 대기, 클릭되면 onItemClick() 메소드를 실행함  
        gridview.setOnItemClickListener(this);
    }
    
	// @Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    	AlertDialog.Builder alert = new AlertDialog.Builder(GridViewActivity.this);
        alert.setTitle("알림창");
        // 클릭된 이미지 위치 출력
        alert.setMessage(position + "번째 이미지를 누르셨네요!");
        alert.setIcon(R.drawable.ic_launcher);
        alert.setPositiveButton("확인", null);
        alert.show();
	}
}