Do It! 안드로이드 앱 프로그래밍 둘째 마당 - Chapter 04 선택위젯의 사용과 커스텀뷰 만들기 Jun. 2013 이지스퍼블리싱(주) 제공 강의 교안 저자 : 정재곤 이번 장에서는 무엇을 다룰까요? 아이콘이 들어간 리스트가 포함된 화면을 만들 때는 어떻게 하나요? • 나인패치 이미지에 대해 알아볼까요? • 이미지로 보여지는 비트맵 버튼을 직접 만들어 볼까요? • 리스트뷰가 포함된 화면을 만들어 볼까요? • 콤보박스처럼 사용되는 스피너가 포함된 화면을 만들어 볼까요? • 사진을 선택할 때 사용할 수 있는 갤러리를 사용해 볼까요? • 테이블 모양으로 보여줄 수 있는 그리드뷰를 사용해 볼까요? • 여러 개의 위젯이 들어있는 복합 위젯을 직접 만들어 볼까요? • 달력 모양의 월별 캘린더를 만들어 볼까요? • 두 손가락으로 이미지를 다루는 멀티 터치 이미지 뷰어를 만들어 볼까요? -3- 이번 장에서는 무엇을 다룰까요? -4- 강의 주제 및 목차 강의 주제 대표적인 선택 위젯의 이해와 실습 목 차 1 나인패치 이미지 6 그리드뷰 사용하기 2 비트맵 버튼 만들기 7 복합위젯 만들기 3 리스트뷰 사용하기 8 월별 캘린더 만들기 4 스피너 사용하기 9 멀티터치 이미지뷰어 만들기 5 갤러리 사용하기 -5- 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 1. 나인패치 이미지 나인패치(Nine Patch) 이미지란? • 이미지가 늘어나거나 줄어들 때 생기는 이미지 왜곡을 해결하는 방법을 정의한 것 • 서로 다른 해상도를 가진 여러 단말에 dp 단위로 뷰의 크기를 맞추다 보면 이미지 크기가 자동 조절되면서 왜곡되는 현상 발생 나인패치 이미지로 해결 [이미지의 크기에 따른 원본 이미지의 왜곡] 1. 나인패치 이미지 [이미지의 크기를 늘릴 때 왜곡되는 영역] -7- 나인패치 이미지 예제 나인패치 이미지 예제 -일반 이미지와 나인패치 이미지 비교 -레이아웃에 여러 개의 버튼 추가 메인 액티비티의 XML 레이아웃 -일반 버튼 추가 1. 나인패치 이미지 메인 액티비티의 XML 레이아웃 -나인패치 버튼 추가 -8- XML 레이아웃 조정 • 일반 이미지를 배경으로 사용하는 버튼과 나인패치 이미지를 배경으로 사용하는 버튼 추가 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Small" 1 일반 이미지를 배경으로 만든 작은 버튼 정의 android:textColor="#ffffffff" android:background="@drawable/button_image_01" /> Continued.. 1. 나인패치 이미지 -9- XML 레이아웃 조정 (계속) <Button android:layout_width="wrap_content" android:layout_height="wrap_content" 2 일반 이미지를 배경으로 만든 중간 버튼 정의 android:text="MediumMediumMedium" android:textColor="#ffffffff" android:background="@drawable/button_image_01" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="LongLongLongLongLongLongLongLongLong" android:textColor="#ffffffff" android:background="@drawable/button_image_01" /> … 1. 나인패치 이미지 - 10 - 3 일반 이미지를 배경으로 만든 큰 버튼 정의 실행 화면 • 일반 이미지를 배경으로 사용하는 버튼은 모서리 부분의 이미지 깨짐 현상 발생 • 나인패치 이미지를 배경으로 사용하는 버튼은 크게 이상하게 보이지 않음 [Reference] void setBackgroundColor (int color) void setBackgroundDrawable (Drawable d) void setBackgroundResource (int resid) [일반 이미지와 나인패치 이미지를 적용한 버튼 모양 비교] 1. 나인패치 이미지 - 11 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 2. 비트맵 버튼 만들기 비트맵 버튼 만들기 • 비트맵 버튼을 직접 만들어 normal일 경우와 clicked일 경우의 이미지를 표시 • 나인패치 이미지를 적용하는 대표적인 경우가 바로 버튼인데 이렇게 배경 부분을 이미지로 지정하여 만든 버튼은 아무리 눌러도 이미지의 변화가 없어 사용자가 버튼을 눌렀는지 안 눌렀는지 알 수 없다는 단점이 있음 • 비트맵 이미지를 이용해 버튼의 상태를 표시하려면 버튼이 눌렸을 때와 떼어졌을 때를 이벤트로 구분하여 처리함 [Reference] public void onMeasure (int widthMeasureSpec, int heightMeasureSpec) public void onDraw(Canvas canvas) [Reference] void setMeasuredDimension (int measuredWidth, int measuredHeight) 2. 비트맵 버튼 만들기 - 13 - 뷰 위에 그래픽을 그리는 과정 • 뷰에 그래픽이 그려질 때 onDraw() 메소드 호출됨 • 다시 그리기는 invalidate() 메소드 사용 2. 비트맵 버튼 만들기 - 14 - 비트맵 버튼 만들기 예제 비트맵 버튼 만들기 예제 -이미지를 보여주는 비트맵 버튼 버튼을 상속한 새로운 클래스 정의 -새로운 버튼 클래스 정의 XML 레이아웃에 추가 -새로운 버튼 태그를 XML 레이 아웃에 추가 메인 액티비티 코드 작성 -메인 액티비티 코드에서 참조하여 사용 2. 비트맵 버튼 만들기 - 15 - 새로운 버튼 클래스 정의 • 터치 이벤트에 따라 배경 이미지를 바꾸어주는 버튼 클래스 정의 public TitleButton(Context context, AttributeSet atts) { super(context, atts); this.context = context; init(); } public void init() { setBackgroundResource(R.drawable.title_button); paint = new Paint(); paint.setColor(defaultColor); 1 초기화 메소드를 정의하는 코드로 배경 이미지 설정과 텍스트를 위한 페인트 객체 생성 paint.setAntiAlias(true); paint.setTextScaleX(defaultScaleX); paint.setTextSize(defaultSize); paint.setTypeface(defaultTypeface); } Continued.. 2. 비트맵 버튼 만들기 - 16 - 새로운 버튼 클래스 정의 (계속) public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); int action = event.getAction(); switch (action) { case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_DOWN: Toast.makeText(context, titleText, Toast.LENGTH_SHORT).show(); 2 터치 이벤트를 처리하여 버튼이 눌렸을 때 토스트 메시지 표시 break; } invalidate(); return true; } Continued.. 2. 비트맵 버튼 만들기 - 17 - 새로운 버튼 클래스 정의 (계속) public void onDraw(Canvas canvas) { super.onDraw(canvas); int curWidth = getWidth(); int curHeight = getHeight(); if (paintChanged) { 4 paint.setColor(defaultColor); 뷰가 화면에 그려질 때 호출되는 onDraw() 메소드 다시 정의 paint.setTextScaleX(defaultScaleX); paint.setTextSize(defaultSize); paint.setTypeface(defaultTypeface); } Rect bounds = new Rect(); paint.getTextBounds(titleText, 0, titleText.length(), bounds); float textWidth = ((float)curWidth - bounds.width())/2.0F; 5 텍스트의 크기 계산 float textHeight = ((float)(curHeight-4) + bounds.height())/2.0F-1.0F; Continued.. 2. 비트맵 버튼 만들기 - 18 - 새로운 버튼 클래스 정의 (계속) 6 캔버스 객체를 이용해 텍스트 그리기 canvas.drawText(titleText, textWidth, textHeight, paint); } ... public void setDefaultColor(int defaultColor) { this.defaultColor = defaultColor; 7 paintChanged = true; } ... } 2. 비트맵 버튼 만들기 - 19 - 색상 값 설정 메소드 정의 버튼 중앙에 텍스트를 그리는 계산 {(버튼의 폭 + 높이값) - (텍스트의 폭 + 높이값)}/2 • 새로 정의한 뷰 자체의 크기는 getWidth()와 getHeight() 메소드를 이용해 가져올 수 있으므로 버튼의 중앙에 텍스트를 그려주기 위해 버튼의 폭과 높이값에서 텍스트의 폭과 높이값을 뺀 후 절반으로 나누는 방식으로 텍스트의 위치를 조정함 2. 비트맵 버튼 만들기 - 20 - 레이아웃 만들기 <org.androidtown.ui.TitleButton android:id="@+id/titleBtn" android:layout_width="match_parent" 1 타이틀 영역을 보여주는 버튼 추가 android:layout_height=“22dp" android:layout_marginBottom=“4dp" /> <org.androidtown.ui.TitleBitmapButton android:id="@+id/arrowLeftBtn" android:layout_width=“20dp" android:layout_height=“40dp" 2 android:layout_alignParentLeft="true" android:layout_marginTop="4dp" android:layout_marginLeft="4dp" /> 2. 비트맵 버튼 만들기 - 21 - 타이틀 영역 안에 작은 버튼 추가 메인 액티비티 코드 만들기 package org.androidtown.ui.bitmap.widget; ... public class MainActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); TitleBitmapButton arrowLeftBtn = (TitleBitmapButton)findViewById(R.id.arrowLeftBtn); 1 화면의 기본 타이틀 없애기 Continued.. 2. 비트맵 버튼 만들기 - 22 - 메인 액티비티 코드 만들기 (계속) arrowLeftBtn.setBitmapResource(R.drawable.arrow_left, R.drawable.arrow_left_clicked); arrowLeftBtn.setOnClickListener(new OnClickListener() { public void onClick(View v) { finish(); 2 [비트맵] 버튼에서 사용 되는 이미지 설정 } }); String titleText = "비트맵 Title"; TitleButton titleBtn = (TitleButton)findViewById(R.id.titleBtn); titleBtn.setTitleText(titleText); titleBtn.setDefaultSize(18F); } 3 [비트맵] 버튼에 표시되는 텍스트 설정 } [[비트맵] 버튼을 이용한 상단 타이틀 표시] 2. 비트맵 버튼 만들기 - 23 - Selector를 이용하는 방법 • Selector를 이용해 버튼의 상태에 따라 이미지를 바꾸어 주는 방법도 적용 가능 • normal 상태와 selected 상태를 구분하여 XML로 정의 후 background 속성으로 적용 <Button <?xml version="1.0" encoding="UTF-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/arrow_left_clicked" /> <item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/arrow_left_clicked" /> <item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/arrow_left_clicked" /> <item android:drawable="@drawable/arrow_left_normal" /> </selector> android:id="@+id/arrowLeftBtn" android:layout_width="46dp" android:layout_height="74dp" android:layout_alignParentLeft="true" android:layout_marginTop="10dp" android:layout_marginLeft="10dp" android:background="@drawable/button_selector" /> 2. 비트맵 버튼 만들기 - 24 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 3. 리스트뷰 사용하기 왜 굳이 선택위젯이라는 이름으로 구분할까? • 안드로이드에서는 여러 아이템 중의 하나를 선택하는 선택위젯은 별도의 패턴을 사용함 • 여러 개의 아이템 중에서 하나를 선택하는 방식의 선택 위젯은 어댑터를 사용하여야 함 • 이 어댑터에서 데이터를 관리하도록 해야 할 뿐만 아니라 화면에 보여지는 뷰도 어댑터의 getView() 메소드에서 결정함 • 선택위젯의 가장 큰 특징은 원본 데이터를 위젯에 직접 설정하지 않고 어댑터라는 클래스를 사용하도록 되어 있다는 점으로 이 패턴을 잘 기억해 두어야 함 [선택 위젯과 어댑터] 3. 리스트뷰 사용하기 - 26 - 대표적인 선택 위젯 • 안드로이드에서 제공하는 대표적인 선택 위젯들은 그 사용방법을 잘 알아두어야 함 3. 리스트뷰 사용하기 - 27 - 리스트뷰로 보여줄 때 해야 할 일들 (1) 아이템을 위한 XML 레이아웃 정의하기 - 리스트뷰에 들어갈 각 아이템의 레이아웃을 XML로 정의함 (2) 아이템을 위한 뷰 정의하기 - 리스트뷰에 들어갈 각 아이템을 하나의 뷰로 정의. 이 뷰는 여러 개의 뷰를 담고 있는 뷰그룹이어야 함 (3) 어댑터 정의하기 - 데이터 관리 역할을 하는 어댑터 클래스를 만들고 그 안에 각 아이템으로 표시할 뷰를 리턴하는 getView() 메소드를 정의함 (4) 리스트뷰 정의하기 - 화면에 보여줄 리스트뷰를 만들고 그 안에 데이터가 선택되었을 때 호출될 리스너 객체를 정의함 3. 리스트뷰 사용하기 - 28 - 리스트뷰 사용하기 예제 리스트뷰 사용하기 예제 -아이콘이 들어 있는 리스트뷰의 아이템 정의 -리스트뷰의 한 아이템을 선택했을 때 토스트 표시 아이템의 XML 레이아웃 한 아이템의 데이터를 넣을 클래스 -한 아이템으로 보여질 뷰의 -한 아이템의 데이터를 넣을 클 XML 레이아웃 정의 래스 정의 리스트뷰를 사용하는 메인 액티비티 코드 작성 리스트뷰 선택 이벤트 처리 코드 작성 -리스트뷰를 보여주는 역할을 하 -리스트뷰를 사용하는 메인 액티 -한 아이템을 선택했을 때의 이 는 어댑터 클래스 정의 비티 코드 작성 벤트 처리 어댑터 클래스 정의 3. 리스트뷰 사용하기 - 29 - 한 아이템으로 보여줄 XML 레이아웃 정의 • 선택위젯에서 각각의 아이템은 동일한 레이아웃을 가진 뷰가 반복적으로 보여짐 • 각각의 아이템을 위한 XML 레이아웃이 필요함 [리스트뷰의 한 아이템이 갖는 레이아웃의 예] <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" > Continued.. 3. 리스트뷰 사용하기 - 30 - 한 아이템으로 보여줄 XML 레이아웃 정의 (계속) <ImageView android:id="@+id/iconItem" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="8dip" android:layout_gravity="center_vertical" /> <LinearLayout 1 왼쪽에 보이는 이미지뷰 정의 android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentLeft="true" > Continued.. 3. 리스트뷰 사용하기 - 31 - 한 아이템으로 보여줄 XML 레이아웃 정의 (계속) <TextView android:id="@+id/dataItem01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" 2 첫 번째 줄의 텍스트뷰 정의 android:textSize=“12dp" android:padding=“4dp" /> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding=“4dp" > 3 두 번째 줄을 표시할 상대 레이아웃 정의 Continued.. 3. 리스트뷰 사용하기 - 32 - 한 아이템으로 보여줄 XML 레이아웃 정의 (계속) <TextView android:id="@+id/dataItem02" android:layout_width="wrap_content" 4 텍스트뷰 정의 android:layout_height="wrap_content" /> <TextView android:id="@+id/dataItem03" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:textColor="#ccf88107" android:textSize=“14dp" 5 두 번째 줄의 오른쪽에 있는 텍스트뷰 정의 android:textStyle="bold" android:paddingRight="4dp" /> </RelativeLayout> </LinearLayout> </LinearLayout> 3. 리스트뷰 사용하기 - 33 - 한 아이템으로 보여줄 뷰 정의 • 어댑터의 getView() 메소드에서 리턴해 줄 뷰를 별도의 클래스로 정의하여 사용 public class IconTextView extends LinearLayout { … LayoutInflater inflater = (LayoutInflater) 1 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.listitem, this, true); mIcon = (ImageView) findViewById(R.id.iconItem); 아이템의 모양을 구성한 XML 레이아웃을 인플레이 터를 이용해 메모리 객체화 2 레이아웃에 정의된 이미지뷰 객체 참조 mIcon.setImageDrawable(aItem.getIcon()); mText01 = (TextView) findViewById(R.id.dataItem01); 3 레이아웃에 정의된 텍스트뷰 객체 중에 첫 번째 것 참조 mText01.setText(aItem.getData(0)); mText02 = (TextView) findViewById(R.id.dataItem02); mText02.setText(aItem.getData(1)); mText03 = (TextView) findViewById(R.id.dataItem03); mText03.setText(aItem.getData(2)); } 3. 리스트뷰 사용하기 - 34 - 한 아이템으로 보여줄 데이터 클래스 정의 • 각각의 아이템을 위한 데이터도 별도의 클래스로 정의하여 사용 public class IconTextItem { private Drawable mIcon; private String[] mData; 1 리스트뷰의 한 아이템에 표시할 데이터를 담고 있을 클래스 정의 2 Drawable 타입의 변수와 문자열 타입의 배열 변수 선언 public IconTextItem(Drawable icon, String[] obj) { 3 Drawable 객체와 문자열 타입의 배열을 파라미터로 전달받는 생성자 mIcon = icon; mData = obj; } 3. 리스트뷰 사용하기 - 35 - 새로운 어댑터 클래스 정의 public class IconTextListAdapter extends BaseAdapter { private Context mContext; 1 BaseAdapter를 상속하여 새로운 어댑터 클래스 정의 private List<IconTextItem> mItems = new ArrayList<IconTextItem>(); 2 public IconTextListAdapter(Context context) { 각 아이템의 데이터를 담고 있는 IconTextItem 객체를 저장할 ArrayList 객체 생성 mContext = context; } … public int getCount() { 3 전체 아이템의 개수를 리턴하는 메소드 정의 return mItems.size(); } … public Object getItem(int position) { return mItems.get(position); } … Continued.. 3. 리스트뷰 사용하기 - 36 - 새로운 어댑터 클래스 정의 (계속) public View getView(int position, View convertView, ViewGroup parent) { IconTextView itemView; if (convertView == null) { itemView = new IconTextView(mContext); } else { itemView = (IconTextView) convertView; } 4 아이템에 표시할 뷰 리턴하는 메소드 정의 itemView.setIcon(mItems.get(position).getIcon()); itemView.setText(0, mItems.get(position).getData(0)); itemView.setText(1, mItems.get(position).getData(1)); itemView.setText(2, mItems.get(position).getData(2)); return itemView; } } 3. 리스트뷰 사용하기 - 37 - getView() 메소드 [Reference] public View getView (int position, View convertView, ViewGroup parent) • 첫 번째 파라미터 - 아이템의 인덱스를 의미하는 것으로 리스트뷰에서 보일 아이템의 위치 정보라 할 수 있음. 0부터 시작하여 아이템의 개수만큼 파라미터로 전달됨 • 두 번째 파라미터 - 현재 인덱스에 해당하는 뷰 객체를 의미하는데 안드로이드에서는 선택 위젯이 데이터가 많아 스크롤될 때 뷰를 재활용하는 메커니즘을 가지고 있어 한 번 만들어진 뷰가 화면 상에 그대로 다시 보일 수 있도록 되어 있음. 따라서 이 뷰가 널값이 아니면 재활용 가능함 • 세 번째 파라미터 - 부모 컨테이너 객체임 3. 리스트뷰 사용하기 - 38 - 메인 액티비티 코드 만들기 • 데이터를 어댑터에 추가하고 아이템 선택 시의 이벤트 처리 package org.androidtown.ui.listview; ... public class SampleListViewActivity extends Activity { DataListView list; IconTextListAdapter adapter; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); list = new DataListView(this); adapter = new IconTextListAdapter(this); Resources res = getResources(); 3. 리스트뷰 사용하기 2 리스트뷰 객체 생성 3 어댑터 객체 생성 1 자바 코드에서 만든 리스트뷰 객체를 부모 레이아웃에 추가할 때 필요한 파라미터 Continued.. - 39 - 메인 액티비티 코드 만들기 (계속) adapter.addItem(new IconTextItem(res.getDrawable(R.drawable.icon05), "추억의 테트리스", "30,000 다운로드", "900 원")); list.setAdapter(adapter); 4 어댑터에 각 아이템의 데이터 추가 5 리스트뷰에 어댑터 객체 설정 list.setOnDataSelectionListener(new OnDataSelectionListener() { public void onDataSelected(AdapterView parent,View v,int position, long id) { IconTextItem curItem = (IconTextItem) adapter.getItem(position); 6 아이템을 클릭했을 때 토스트 메시지를 보여주 도록 리스너 설정 String[] curData = curItem.getData(); Toast.makeText(getApplicationContext(), "Selected : " + curData[0], Toast.LENGTH.SHORT).show(); } }); setContentView(list, params); } 7 액티비티 화면을 리스트뷰로 설정 } 3. 리스트뷰 사용하기 - 40 - 실행 화면 3. 리스트뷰 사용하기 - 41 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 4. 스피너 사용하기 스피너 사용하기 예제 스피너 사용하기 예제 -콤보박스처럼 선택할 수 있는 스피너 사용 -XML 레이아웃에 정의한 스피너 참조 메인 액티비티의 XML 레이아웃 -스피너를 포함하는 메인 화면의 메인 액티비티 코드 작성 -스피너 객체를 참조하여 설정 XML 레이아웃 정의 4. 스피너 사용하기 - 43 - XML 레이아웃 만들기 • <Spinner> 태그를 사용하여 레이아웃에 추가 <Spinner android:id="@+id/spinner" android:layout_width="match_parent" 1 android:layout_height="wrap_content" /> 4. 스피너 사용하기 - 44 - 스피너 정의 메인 액티비티 코드 만들기 • 안드로이드에서 미리 만들어 제공하는 어댑터를 사용할 수 있음 package org.androidtown.ui.spinner; ... public class MainActivity extends Activity implements AdapterView.OnItemSelectedListener { TextView selection; String[] items = { "mike", "angel", "crow", "john", "ginnie", "sally", "cohen", "rice" }; 1 스피너에 표시될 아이템들의 데이터를 배열로 정의 public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); selection = (TextView) findViewById(R.id.selection); Spinner spinner = (Spinner) findViewById(R.id.spinner); spinner.setOnItemSelectedListener(this); Continued.. 4. 스피너 사용하기 - 45 - 메인 액티비티 코드 만들기 (계속) ArrayAdapter<String> adapter = new ArrayAdapter<String>( this, android.R.layout.simple_spinner_item, items); adapter.setDropDownViewResource( 2 ArrayAdapter를 이용해 어댑터 객체 생성 android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); 3 스피너에 어댑터 설정 } public void onItemSelected(AdapterView<?> parent, View v, int position, long id) { selection.setText(items[position]); } public void onNothingSelected(AdapterView<?> parent) { selection.setText(""); } } 4. 스피너 사용하기 - 46 - 4 스피너의 아이템이 선택되었을 때 처리하는 메소드 정의 ArrayAdapter의 사용 [Reference] public ArrayAdapter (Context context, int textViewResourceId, T[] objects) • 첫 번째 파라미터는 Context 객체이므로 액티비티인 this를 전달하면 됨 • 두 번째 파라미터는 뷰를 초기화할 때 사용되는 XML 레이아웃의 리소스 ID 값으로 이 코드에서는 android.R.layout.simple_spinner_item을 전달하였음 • 세 번째 파라미터는 아이템으로 보일 문자열 데이터들의 배열임 [스피너를 이용한 아이템 선택] 4. 스피너 사용하기 - 47 - 하나의 문자열을 보여주는 간단한 리스트뷰 사용 • 리스트뷰의 경우에도 안드로이드에서 미리 제공하는 어댑터 사용 가능 package org.androidtown.ui.spinner; ... public class MainActivity extends ListActivity { TextView selection; String[] items = { "mike", "angel", "crow", "john", 1 리스트에 표시될 아이템들의 데이터를 배열로 정의 "ginnie", "sally", "cohen", "rice" }; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Continued.. 4. 스피너 사용하기 - 48 - 하나의 문자열을 보여주는 간단한 리스트뷰 사용 (계속) setListAdapter(new ArrayAdapter<String>( this, 2 ArrayAdapter를 이용해 어댑터 객체를 생성한 후 리스트에 설정 android.R.layout.simple_list_item_1, items)); selection = (TextView)findViewById(R.id.selection); } protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); String text = " position:" + position + " " + items[position]; selection.setText(text); } 3 리스트의 아이템이 선택되었을 때 처리 하는 메소드 정의 } [리스트뷰에 단순 문자열만 표시한 경우] 4. 스피너 사용하기 - 49 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 5. 갤러리 사용하기 갤러리 사용하기 예제 갤러리 사용하기 예제 -콤보박스처럼 선택할 수 있는 스피너 사용 -XML 레이아웃에 정의한 스피너 참조 메인 액티비티의 XML 레이아웃 -갤러리를 포함하는 메인 화면의 메인 액티비티 코드 작성 -갤러리 객체를 참조하여 설정 XML 레이아웃 정의 어댑터 클래스 정의 이미지뷰를 위한 액티비티 정의 -갤러리를 위한 어댑터 클래스 정의 -갤러리의 아이템 선택 시 보여 5. 갤러리 사용하기 줄 액티비티 정의 - 51 - 레이아웃 만들기 • 갤러리(Gallery)는 사진을 나열해서 볼 수 있도록 하는 간단한 위젯 • 갤러리는 사진을 일렬로 보여주고 이렇게 나열된 사진을 스크롤하거나 선택할 수 있도록 만들어 줌 • 갤러리는 <Gallery> 태그를 이용해 XML 레이아웃에 추가할 수 있음 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <Gallery android:id="@+id/gallery" android:layout_width="match_parent" android:layout_height="match_parent" android:verticalSpacing="10dp" 1 갤러리 정의 android:horizontalSpacing="10dp" /> </LinearLayout> 5. 갤러리 사용하기 - 52 - 메인 액티비티 코드 만들기 • 앨범에 있는 사진을 갤러리로 보여주기 위해 내용 제공자 사용 private void init() { String[] img = { MediaStore.Images.Thumbnails._ID }; imageCursor = managedQuery( MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, img, null, null, MediaStore.Images.Thumbnails.IMAGE_ID + ""); imageColumnIndex = imageCursor.getColumnIndexOrThrow( 1 앨범 사진들에 대한 썸네일 이미지를 쿼리하여 Cursor 객체로 받음 MediaStore.Images.Thumbnails._ID); count = imageCursor.getCount(); gallery = (Gallery) findViewById(R.id.gallery); gallery.setAdapter(new ImageAdapter(getApplicationContext())); 2 갤러리 객체에 ImageAdapter 객체 설정 gallery.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView parent,View v,int position, long id) { System.gc(); 3 갤러리의 아이템을 클릭했을 때 의 리스너 설정 String[] proj = { MediaStore.Images.Media.DATA }; 5. 갤러리 사용하기 Continued.. - 53 - 메인 액티비티 코드 만들기 (계속) actualImageCursor = managedQuery( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, proj, null, null, null); actualImageColumnIndex = actualImageCursor.getColumnIndexOrThrow( MediaStore.Images.Media.DATA); 4 앨범에 있는 원본 이미지를 쿼리하여 Cursor 객체로 받음 actualImageCursor.moveToPosition(position); String filename = actualImageCursor.getString(actualImageColumnIndex); System.gc(); Intent intent = new Intent(getApplicationContext(), OriginalPictureView.class); intent.putExtra("filename", filename); 5 startActivity(intent); } 이미지를 보기 위한 액티비티 띄우기 }); } Continued.. 5. 갤러리 사용하기 - 54 - 메인 액티비티 코드 만들기 (계속) public class ImageAdapter extends BaseAdapter { private Context mContext; public ImageAdapter(Context c) { 6 ImageAdapter 클래스 정의 mContext = c; } public int getCount() { return count; } public Object getItem(int position) { return position; 7 어댑터에서 리턴할 전체 아이템의 개수는 썸네일 이미지의 개수로 함 } public long getItemId(int position) { return position; } Continued.. 5. 갤러리 사용하기 - 55 - 메인 액티비티 코드 만들기 (계속) public View getView(int position,View convertView,ViewGroup parent) { System.gc(); ImageView imgView = new ImageView(mContext.getApplicationContext()); if (convertView == null) { 8 아이템으로 보일 뷰 객체를 리턴하는 getView() 메소드 정의 imageCursor.moveToPosition(position); int id = imageCursor.getInt(imageColumnIndex); imgView.setImageURI(Uri.withAppendedPath( MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, ""+id)); imgView.setScaleType(ImageView.ScaleType.FIT_XY); imgView.setLayoutParams(new Gallery.LayoutParams(136, 88)); } else { imgView = (ImageView) convertView; } return imgView; } } } 5. 갤러리 사용하기 - 56 - 9 썸네일 이미지를 이미지뷰에 설정 이미지를 보기 위한 액티비티의 레이아웃 • 갤러리에서 선택한 이미지를 보기 위한 액티비티의 레이아웃 정의 [Reference] MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI MediaStore.Images.Media.EXTERNAL_CONTENT_URI <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/imageView" android:layout_width="match_parent" android:layout_height="match_parent" 1 이미지뷰 정의 /> </LinearLayout> 5. 갤러리 사용하기 Continued.. - 57 - 이미지를 보기 위한 액티비티 코드 • 메인 액티비티로부터 이미지에 대한 정보를 전달받아 비트맵 객체로 로딩 후 설정 private void processIntent() { System.gc(); 1 메인 액티비티로부터 전달받은 인텐트 처리 Intent intent = getIntent(); Bundle extras = intent.getExtras(); filename = extras.getString("filename"); BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeFile(filename, options); 3 이미지 파일을 읽어 들여 비트맵 객체로 만듦 4 이미지뷰에 비트맵 객체 설정 imgView.setImageBitmap(bitmap); } } 5. 갤러리 사용하기 2 이미지를 메모리로 로딩할 때의 축소 비율을 1/2로 지정 - 58 - 실행 화면 5. 갤러리 사용하기 - 59 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 6. 그리드뷰 사용하기 그리드뷰 사용하기 예제 그리드뷰 사용하기 예제 -테이블 형태로 보여주는 데이터를 관리할 어댑터 정의 -테이블 모양의 뷰 생성 어댑터 클래스 정의 메인 액티비티 코드 작성 -그리드뷰를 위한 어댑터 클래스 - 그리드뷰와 어댑터를 이용해 정의 화면에 표시 6. 그리드뷰 사용하기 - 61 - 어댑터 클래스 정의 • 테이블 모양으로 데이터를 보여주기 위해 그리드뷰 위젯을 사용 • 이전의 윈도우 모바일 기반 PDA나 PC 또는 웹에서 많이 사용하는 것임 • 테이블(Table) 형태로 데이터를 보여주는 위젯 package org.androidtown.ui.gridview; ... public class DataAdapter extends BaseAdapter { Context mContext; Continued.. 6. 그리드뷰 사용하기 - 62 - 어댑터 클래스 정의 (계속) private MTable mTable; ... public DataAdapter(Context context) { super(); mContext = context; 1 가상 테이블 객체 선언 } public DataAdapter(Context context, MTable table) { super(); mContext = context; setTable(table); } public int getNumColumns() { if (mTable == null) { return 0; 2 테이블의 칼럼 개수 정보 확인 } return mTable.countColumn; } Continued.. 6. 그리드뷰 사용하기 - 63 - 어댑터 클래스 정의 (계속) public int getCount() { if (mTable == null) { return 0; 3 } return (mTable.count()+1) * mTable.countColumn; 전체 아이템의 개수 확인 (칼럼 수 * 레코드 수) } public View getView(int position, View view, ViewGroup group) { Log.d("DataAdapter", "getView(" + position + ") called."); 4 어댑터에서 사용되는 getView() 메소드 정의 if (mTable == null) { return null; } Continued.. 6. 그리드뷰 사용하기 - 64 - 어댑터 클래스 정의 (계속) int rowIndex = position / mTable.countColumn; int columnIndex = position % mTable.countColumn; Log.d("DataAdapter", "Index : " + rowIndex + ", " + columnIndex); ... 5 position 값을 이용해 행과 열의 인덱스 값 계산 if (position < mTable.countColumn) { itemView.setText(mTable.columns[columnIndex].name); } else { Object item = ""; try { item = mTable.getCell(rowIndex-1, columnIndex); 6 position 값에 해당하는 셀의 값 확인 } catch(Exception ex) { ex.printStackTrace(); } itemView.setText(item.toString()); } ... return container; } Continued.. 6. 그리드뷰 사용하기 - 65 - 전체 아이템 개수 계산 getCount() 리턴 값 = 칼럼 개수 * 레코드의 개수 public void onItemClick(AdapterView parent,View v,int position, long id) { if (selectionListener == null) { return; } int countColumn = 0; if (adapter != null) { if (adapter.getTable() != null) { countColumn = adapter.getTable().countColumn; } } int rowIndex = -1; int columnIndex = -1; if (countColumn > 0) { rowIndex = position / countColumn; 4 행과 열의 인덱스를 계산하여 새로 정의한 리스너 호출 columnIndex = position % countColumn; } selectionListener.onDataSelected(parent, v, rowIndex, columnIndex, id); 6. 그리드뷰 사용하기 - 66 - 메인 액티비티 코드 만들기 grid = new DataGridView(this); grid.setColumnWidth(100); 1 adapter = new DataAdapter(this); 새로 정의한 DataGridView 객체 생성 MTable table = null; try { table = createSampleTable(); } catch(MTableException ex) { ex.printStackTrace(); 2 새로 정의한 DataAdapter 객체 생성 } adapter.setTable(table); grid.setAdapter(adapter); 3 어댑터에 가상 테이블 객체 설정 Continued.. 6. 그리드뷰 사용하기 - 67 - 메인 액티비티 코드 만들기 grid.setOnDataSelectionListener(new OnDataSelectionListener() { public void onDataSelected(AdapterView parent,View v,int row, int column, long id) { Toast.makeText(getApplicationContext(), "Selected : " + row + ", " + column, Toast.LENGTH_LONG).show(); } }); setContentView(grid, params); } ... } [그리드뷰로 테이블 데이터를 보여준 경우] 6. 그리드뷰 사용하기 - 68 - 4 DataGridView 객체에 리스너 설정 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 7. 복합위젯 만들기 복합위젯 만들기 예제 복합위젯 만들기 예제 -여러 개의 뷰를 포함하는 복합 위젯 새로운 클래스를 위한 XML 레이아웃 정의 레이아웃을 상속하여 새로운 클래스 정의 -여러 개의 뷰를 포함하는 새로 -여러 개의 뷰를 포함하는 새로 운 클래스를 위한 레이아웃 정의 운 클래스 정의 메인 액티비티를 위한 XML 레이아웃 정의 -새로운 클래스 태그 추가 7. 복합위젯 만들기 메인 액티비티 코드 작성 -메인 액티비티 코드에서 참조하 여 사용 - 70 - 복합위젯 만들기 • 복합 위젯은 여러 개의 뷰를 조합하여 만든 새로운 위젯 - 뷰그룹에 여러 개의 뷰를 넣은 후 이벤트 처리 - 실무에 많이 사용하는 위젯 만들기 방법 • 날짜와 시간을 함께 선택할 수 있는 DateTimePicker 예제 - DatePicker와 TimePicker를 같이 포함한 복합 위젯 7. 복합위젯 만들기 - 71 - 날짜/시간 선택기능 위젯 • 날짜와 시간을 설정 위젯 - DatePicker와 TimePicker가 제공되며 대화상자로는 DatePickerDialog 와 TimePickerDialog가 제공됨 - DatePicker 와 DatePickerDialog 를 사용할 때는 시작 일자를 지정할 수 있으며, 그 포맷은 연, 월, 일 (year, month, day) 이 됨. - 월 단위 값은 0부터 11까지 이며 각각 1월부터 12월까지를 표시함 (January –December) • 리스너 설정 - 각각의 위젯을 사용자가 선택하면 OnDateChangedListener 또는 OnDateSetListener 콜백 객체가 호출됨. • TimePicker 와 TimePickerDialog - 초기 시간을 지정할 수 있으며 그 형태는 0부터 23까지의 시간 (hour :0 - 23) 과 0부터 59까지의 3분(minute :0 - 59)이 됨. - 선택할 수 있는 값들이 12 시간 모드(AM/PM 토글)인지 또는 24시간 모드인지 지정할 수 있음. - 사용자가 새로운 시간을 선택하면 OnTimeChangedListener 또는OnTimeSetListener 콜백 객체가 호출됨. 7. 복합위젯 만들기 - 72 - DatePicker와 TimePicker 사용하기 7. 복합위젯 만들기 - 73 - 레이아웃 만들기 <DatePicker android:id="@+id/datePicker" 1 날짜 선택 위젯 정의 android:layout_width="wrap_content" android:layout_height="wrap_content" /> <CheckBox android:id="@+id/enableTimeCheckBox" android:layout_width="wrap_content" 2 시간 선택 위젯을 보일지 여부를 결정하는 체크박스 정의 android:layout_height="wrap_content" android:text="시간 보이기" /> <TimePicker android:id="@+id/timePicker" android:layout_width="wrap_content" 3 시간 선택 위젯 정의 android:layout_height="wrap_content" /> 7. 복합위젯 만들기 - 74 - 레이아웃을 상속한 새로운 클래스 정의 public class DateTimePicker extends LinearLayout { 1 리니어 레이아웃을 상속하여 새로운 클래스 정의 public DateTimePicker(Context context, AttributeSet attrs){ super(context, attrs); LayoutInflater inflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE); inflater.inflate(R.layout.datetimepicker, this, true); … 2 새로 정의한 클래스를 위한 레이아웃 인플레이 션 datePicker.init(_currentYear, _currentMonth, _currentDay, new OnDateChangedListener() { public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) { if(onDateTimeChangedListener != null) { onDateTimeChangedListener.onDateTimeChanged( DateTimePicker.this, year, monthOfYear, dayOfMonth, timePicker.getCurrentHour(), timePicker.getCurrentMinute()); } } 3 날짜가 변경되는 이벤트가 발생했을 때 새로운 리스너의 메소드 호출 }); 7. 복합위젯 만들기 Continued.. - 75 - 레이아웃을 상속한 새로운 클래스 정의 (계속) enableTimeCheckBox = (CheckBox)findViewById(R.id.enableTimeCheckBox); enableTimeCheckBox.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { timePicker.setEnabled(isChecked); timePicker.setVisibility( (enableTimeCheckBox).isChecked()?View.VISIBLE:View.INVISIBLE); } }); timePicker = (TimePicker)findViewById(R.id.timePicker); timePicker.setOnTimeChangedListener(new OnTimeChangedListener() { public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { if(onDateTimeChangedListener != null) { onDateTimeChangedListener.onDateTimeChanged( DateTimePicker.this, datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth(), hourOfDay, minute); } } }); 7. 복합위젯 만들기 4 시간이 변경되는 이벤트가 발생했을 때 새로운 리스너의 메소드 호출 Continued.. - 76 - 레이아웃을 상속한 새로운 클래스 정의 (계속) timePicker.setCurrentHour(_currentHour); timePicker.setCurrentMinute(_currentMinute); timePicker.setEnabled(enableTimeCheckBox.isChecked()); timePicker.setVisibility((enableTimeCheckBox.isChecked()?View.VISIBLE:View.INVISIBLE)); } public void setOnDateTimeChangedListener(OnDateTimeChangedListener onDateTimeChangedListener) { this.onDateTimeChangedListener = onDateTimeChangedListener; } public void updateDateTime(int year, int monthOfYear, int dayOfMonth, int currentHour, int currentMinute){ datePicker.updateDate(year, monthOfYear, dayOfMonth); timePicker.setCurrentHour(currentHour); timePicker.setCurrentMinute(currentMinute); } public void updateDate(int year, int monthOfYear, int dayOfMonth){ datePicker.updateDate(year, monthOfYear, dayOfMonth); } ... } 7. 복합위젯 만들기 - 77 - 5 새로운 리스너 객체 설정 메소드 정의 XML 레이아웃 만들기 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" > <TextView android:id="@+id/text01" android:layout_width="match_parent" android:layout_height="wrap_content" 1 변경된 날짜와 시간 값을 표시하는 텍스트뷰 추가 /> <org.androidtown.ui.composite.DateTimePicker android:id="@+id/dateTimePicker" android:layout_width="wrap_content" android:layout_height="wrap_content" 2 새로 정의한 DateTimePicker 뷰 사용을 위해 추가 /> </LinearLayout> 7. 복합위젯 만들기 - 78 - 메인 액티비티 코드 만들기 package org.androidtown.ui.composite; ... public class MainActivity extends Activity { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy년 MM월 dd일 HH시 mm분"); public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final TextView text01 = (TextView)findViewById(R.id.text01); final DateTimePicker dateTimePicker = (DateTimePicker)findViewById(R.id.dateTimePicker); Continued.. 7. 복합위젯 만들기 - 79 - 메인 액티비티 코드 만들기 (계속) dateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int monthOfYear, int dayOfYear, int hourOfDay, int minute) { Calendar calendar = Calendar.getInstance(); calendar.set(year, monthOfYear, dayOfYear, hourOfDay, minute); text01.setText(dateFormat.format(calendar.getTime())); 1 DateTimePicker 객체 에 리스너 설정하여 날 짜나 시간이 변경될 때 마다 텍스트에 표시 } }); Calendar calendar = Calendar.getInstance(); calendar.set(dateTimePicker.getYear(), dateTimePicker.getMonth(), dateTimePicker.getDayOfMonth(), dateTimePicker.getCurrentHour(), dateTimePicker.getCurrentMinute()); text01.setText(dateFormat.format(calendar.getTime())); 2 텍스트에 현재 날짜와 시간 정보 표시 } } 7. 복합위젯 만들기 - 80 - 실행 화면 7. 복합위젯 만들기 - 81 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 8. 월별 캘린더 만들기 월별 캘린더 만들기 • 월별 캘린더는 격자 형태로 그리드뷰를 사용하는 가장 대표적인 뷰 • 선택 위젯이므로 어댑터를 이용해 7X6 형태의 격자 모양 구성 • Date와 Calendar 클래스를 이용해 선택한 월의 정보 확인 • 1일이 시작되는 요일을 확인하고 끝나는 일수를 확인하는 등의 계산 작업 필요 8. 월별 캘린더 만들기 - 83 - 월별 캘린더 만들기 예제 월별 캘린더 만들기 예제 -달력 형태로 보여주는 화면 어댑터 클래스 정의 -그리드뷰를 위한 어댑터 클래스 메인 액티비티를 위한 XML 레이아웃 정의 -그리드뷰 추가 정의 메인 액티비티 코드 작성 -메인 액티비티 코드에서 참조하 여 사용 8. 월별 캘린더 만들기 - 84 - 레이아웃 만들기 • 화면 위쪽에 월 이동 버튼이 있고 중앙에 캘린더 화면이 있는 레이아웃 정의 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ffffffff" > <Button android:id="@+id/monthPrevious" android:layout_width="30dp" android:layout_height="wrap_content" android:background="@drawable/backward" android:gravity="center_horizontal" 1 이전 월로 이동하기 위한 버튼 추가 android:layout_alignParentLeft="true" /> 8. 월별 캘린더 만들기 Continued.. - 85 - 레이아웃 만들기 (계속) <TextView android:id="@+id/monthText" android:layout_width="200dp" android:layout_height="wrap_content" android:text="MonthView" 2 현재 월을 표시하기 위한 텍스트뷰 추가 android:gravity="center_horizontal" android:layout_centerInParent="true" /> <Button android:id="@+id/monthNext" android:layout_width="30dp" android:layout_height="wrap_content" android:background="@drawable/forward" 3 이후 월로 이동하기 위한 버튼 추가 android:gravity="center_horizontal" android:layout_alignParentRight="true" /> </RelativeLayout> 8. 월별 캘린더 만들기 Continued.. - 86 - 레이아웃 만들기 (계속) <org.androidtown.calendar.month.CalendarMonthView android:id="@+id/monthView" 4 android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> 8. 월별 캘린더 만들기 - 87 - 새로 정의한 월별 캘린더 뷰 추가 그리드뷰를 상속한 새로운 클래스 정의 package org.androidtown.calendar.month; public class CalendarMonthView extends GridView { private OnDataSelectionListener selectionListener; CalendarMonthAdapter adapter; public CalendarMonthView(Context context) { super(context); 1 그리드뷰를 상속하여 클래스 정의 init(); } public CalendarMonthView(Context context, AttributeSet attrs) { super(context, attrs); init(); } Continued.. 8. 월별 캘린더 만들기 - 88 - 그리드뷰를 상속한 새로운 클래스 정의 (계속) private void init() { setBackgroundColor(Color.GRAY); setVerticalSpacing(1); setHorizontalSpacing(1); 2 뷰를 초기화하는 메소드를 만들고 그 안에서 칼럼의 개수 설정 setStretchMode(GridView.STRETCH_COLUMN_WIDTH); setNumColumns(7); setOnItemClickListener(new OnItemClickAdapter()); } ... } 8. 월별 캘린더 만들기 - 89 - 어댑터 클래스 정의 package org.androidtown.calendar.month; ... public class CalendarMonthAdapter extends BaseAdapter { ... public CalendarMonthAdapter(Context context) { super(); mContext = context; init(); 1 BaseAdapter를 상속하여 새로운 어댑터 정의 } public CalendarMonthAdapter(Context context, AttributeSet attrs) { super(); mContext = context; init(); } Continued.. 8. 월별 캘린더 만들기 - 90 - 어댑터 클래스 정의 (계속) private void init() { 2 한 개 월의 일별 데이터를 담고 있을 수 있는 MonthItem의 배열 객체 생성 items = new MonthItem[7 * 6]; mCalendar = Calendar.getInstance(); recalculate(); resetDayNumbers(); } public void recalculate() { mCalendar.set(Calendar.DAY_OF_MONTH, 1); int dayOfWeek = mCalendar.get(Calendar.DAY_OF_WEEK); firstDay = getFirstDay(dayOfWeek); Log.d(TAG, "firstDay : " + firstDay); mStartDay = mCalendar.getFirstDayOfWeek(); curYear = mCalendar.get(Calendar.YEAR); curMonth = mCalendar.get(Calendar.MONTH); lastDay = getMonthLastDay(curYear, curMonth); int diff = mStartDay - Calendar.SUNDAY - 1; startDay = getFirstDayOfWeek(); } 8. 월별 캘린더 만들기 Continued.. - 91 - 어댑터 클래스 정의 (계속) public void setPreviousMonth() { mCalendar.add(Calendar.MONTH, -1); recalculate(); resetDayNumbers(); 3 이전 월로 이동 시 일별 데이터 새로 계산 selectedPosition = -1; } public void setNextMonth() { mCalendar.add(Calendar.MONTH, 1); recalculate(); resetDayNumbers(); 4 다음 월로 이동 시 일별 데이터 새로 계산 selectedPosition = -1; } Continued.. 8. 월별 캘린더 만들기 - 92 - 어댑터 클래스 정의 (계속) public void resetDayNumbers() { for (int i = 0; i < 42; i++) { int dayNumber = (i+1) - firstDay; if (dayNumber < 1 || dayNumber > lastDay) { dayNumber = 0; 5 지정한 월의 일별 데이터를 새로 계산하는 메소드 정의 } items[i] = new MonthItem(dayNumber); } } private int getFirstDay(int dayOfWeek) { int result = 0; if (dayOfWeek == Calendar.SUNDAY) { result = 0; } else if (dayOfWeek == Calendar.MONDAY) { result = 1; } else if (dayOfWeek == Calendar.TUESDAY) { result = 2; Continued.. 8. 월별 캘린더 만들기 - 93 - 어댑터 클래스 정의 (계속) public View getView(int position, View convertView, ViewGroup parent) { MonthItemView itemView; if (convertView == null) { itemView = new MonthItemView(mContext); } else { itemView = (MonthItemView) convertView; } GridView.LayoutParams params = new GridView.LayoutParams( GridView.LayoutParams.MATCH_PARENT, 70); int rowIndex = position / countColumn; int columnIndex = position % countColumn; Continued.. 8. 월별 캘린더 만들기 - 94 - 어댑터 클래스 정의 (계속) itemView.setItem(items[position]); itemView.setLayoutParams(params); itemView.setPadding(2, 2, 2, 2); itemView.setGravity(Gravity.LEFT); if (columnIndex == 0) { itemView.setTextColor(Color.RED); } else { itemView.setTextColor(Color.BLACK); } 6 if (position == getSelectedPosition()) { itemView.setBackgroundColor(Color.YELLOW); } else { itemView.setBackgroundColor(Color.WHITE); } return itemView; } ... } 8. 월별 캘린더 만들기 - 95 - getView() 메소드에서 리턴하는 뷰의 속성 설정 메인 액티비티 코드 만들기 monthView = (CalendarMonthView) findViewById(R.id.monthView); monthViewAdapter = new CalendarMonthAdapter(this); monthView.setAdapter(monthViewAdapter); 1 레이아웃에 정의된 월별 캘린더 뷰 객체 참조 2 어댑터 객체 생성 후 월별 캘린더 뷰 객체에 설정 monthView.setOnDataSelectionListener(new OnDataSelectionListener() { public void onDataSelected(AdapterView parent, View v, int position, long id) { MonthItem curItem = (MonthItem) monthViewAdapter.getItem(position); int day = curItem.getDay(); 3 월별 캘린더 뷰에 리스너 설정 } }); monthText = (TextView) findViewById(R.id.monthText); setMonthText(); Continued.. 8. 월별 캘린더 만들기 - 96 - 메인 액티비티 코드 만들기 (계속) Button monthPrevious = (Button) findViewById(R.id.monthPrevious); monthPrevious.setOnClickListener(new OnClickListener() { public void onClick(View v) { 4 이전 월 [이동] 버튼 클릭 시 일별 데이터를 다시 계산하는 메소드 호출하고 화면 갱신 monthViewAdapter.setPreviousMonth(); monthViewAdapter.notifyDataSetChanged(); setMonthText(); } }); Button monthNext = (Button) findViewById(R.id.monthNext); monthNext.setOnClickListener(new OnClickListener() { public void onClick(View v) { 5 다음 월 [이동] 버튼 클릭 시 일별 데이터를 다시 계산하는 메소드 호출하고 화면 갱신 monthViewAdapter.setNextMonth(); monthViewAdapter.notifyDataSetChanged(); setMonthText(); } }); } 8. 월별 캘린더 만들기 Continued.. - 97 - 메인 액티비티 코드 만들기 (계속) private void setMonthText() { curYear = monthViewAdapter.getCurYear(); curMonth = monthViewAdapter.getCurMonth(); monthText.setText(curYear + "년 " + (curMonth+1) + "월"); } } [월별 캘린더와 날짜 선택] 8. 월별 캘린더 만들기 - 98 - 둘째 마당 – CH4. 선택 위젯의 사용과 커스텀뷰 만들기 9. 멀티터치 이미지뷰어 만들기 멀티터치 이벤트 처리하기 – X, Y 좌표 [API] public final int getPointerCount () public final float getX(int pointerIndex) public final float getY(int pointerIndex) • 첫 번째 메소드인 getPointerCount()는 몇 개의 손가락이 터치되었는지를 알 수 있도록 해주는 것 • 이벤트 처리에 자주 사용되는 getX()와 getY() 메소드는 손가락이 하나일 때 X와 Y의 좌표값을 가져오지만 getX(int pointerIndex)와 getY(int pointerIndex) 메소드는 여러 개의 손가락이 터치되었을 때 각각의 손가락이 가지는 인덱스의 값을 이용해 좌표값을 확인할 수 있도록 함 9. 멀티터치 이미지뷰어 만들기 - 100 - 멀티터치 이미지뷰어 예제 멀티터치 이미지뷰어 만들기 예제 -멀티터치로 이미지를 조작하는 기능 새로운 뷰 클래스 정의 메인 액티비티 코드 작성 -멀티터치 이벤트를 처리하는 새 -메인 액티비티 코드에서 참조하 로운 뷰 클래스 정의 여 사용 9. 멀티터치 이미지뷰어 만들기 - 101 - 새로운 뷰 클래스 정의 public class ImageDisplayView extends View implements OnTouchListener { … protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1 뷰를 상속하면서 OnTouchListener 인터페이스를 구현 하는 클래스 정의 if (w > 0 && h > 0) { 2 뷰가 초기화되고 나서 화면에 보이기 전 크기가 정해지면 호출되는 메소드 안에서 메모리 상에 새로운 비트맵 객체 생성 newImage(w, h); redraw(); } } protected void onDraw(Canvas canvas) { if (mBitmap != null) { canvas.drawBitmap(mBitmap, 0, 0, null); 3 뷰가 화면에 그려지는 메소드 안에서 메모리 상의 비트맵 객체 그리기 } } ... 4 뷰를 터치할 때 호출되는 메소드 다시 정의 public boolean onTouch(View v, MotionEvent ev) { final int action = ev.getAction(); Continued.. 9. 멀티터치 이미지뷰어 만들기 - 102 - 새로운 뷰 클래스 정의 (계속) int pointerCount = ev.getPointerCount(); Log.d(TAG, "Pointer Count : " + pointerCount); 5 터치했을 때 몇 개의 손가락으로 터치하는지 개수 확인 switch (action) { case MotionEvent.ACTION_DOWN: if (pointerCount == 1) { float curX = ev.getX(); float curY = ev.getY(); startX = curX; 6 손가락으로 눌렀을 때의 기능 추가 startY = curY; } else if (pointerCount == 2) { oldDistance = 0.0F; isScrolling = true; } return true; Continued.. 9. 멀티터치 이미지뷰어 만들기 - 103 - 새로운 뷰 클래스 정의 (계속) case MotionEvent.ACTION_MOVE: if (pointerCount == 1) { if (isScrolling) { return true; } float curX = ev.getX(); float curY = ev.getY(); if (startX == 0.0F) { 7 손가락으로 움직일 때의 기능 추가 startX = curX; startY = curY; return true; } float offsetX = startX - curX; float offsetY = startY - curY; if (oldPointerCount == 2) { } else { Log.d(TAG, "ACTION_MOVE : " + offsetX + ", " + offsetY); if (totalScaleRatio > 1.0F) { 9. 멀티터치 이미지뷰어 만들기 Continued.. - 104 - 새로운 뷰 클래스 정의 (계속) moveImage(-offsetX, -offsetY); 8 } 한 손가락으로 움직이고 있을 때는 moveImage() 메소드 호출 startX = curX; startY = curY; } } else if (pointerCount == 2) { float x1 = ev.getX(0); float y1 = ev.getY(0); float x2 = ev.getX(1); float y2 = ev.getY(1); float dx = x1 - x2; float dy = y1 - y2; float distance = FloatMath.sqrt(dx * dx + dy * dy); float outScaleRatio = 0.0F; if (oldDistance == 0.0F) { oldDistance = distance; break; } 9. 멀티터치 이미지뷰어 만들기 Continued.. - 105 - 새로운 뷰 클래스 정의 (계속) scaleImage(outScaleRatio); 9 } 두 손가락으로 움직이고 있을 때는 scaleImage() 메소드 호출 oldDistance = distance; } oldPointerCount = pointerCount; break; case MotionEvent.ACTION_UP: if (pointerCount == 1) { float curX = ev.getX(); float curY = ev.getY(); float offsetX = startX - curX; 10 손가락을 떼었을 때의 기능 추가 float offsetY = startY - curY; if (oldPointerCount == 2) { } else { moveImage(-offsetX, -offsetY); } Continued.. 9. 멀티터치 이미지뷰어 만들기 - 106 - 새로운 뷰 클래스 정의 (계속) } else { isScrolling = false; } return true; } return true; } private void scaleImage(float inScaleRatio) { Log.d(TAG, "scaleImage() called : " + inScaleRatio); mMatrix.postScale(inScaleRatio, inScaleRatio, bitmapCenterX, bitmapCenterY); mMatrix.postRotate(0); 11 매트릭스 객체를 이용 해 이미지 크기 변경 totalScaleRatio = totalScaleRatio * inScaleRatio; redraw(); } Continued.. 9. 멀티터치 이미지뷰어 만들기 - 107 - 새로운 뷰 클래스 정의 (계속) private void moveImage(float offsetX, float offsetY) { Log.d(TAG, "moveImage() called : " + offsetX + ", " + offsetY); 12 mMatrix.postTranslate(offsetX, offsetY); redraw(); } } 9. 멀티터치 이미지뷰어 만들기 - 108 - 매트릭스 객체를 이용해 이미지 이동 postScale() 메소드 [API] public boolean postScale (float sx, float sy, float px, float py) public boolean postTranslate (float dx, float dy) public boolean postRotate (float degrees) • postScale() 메소드를 이용하면 비트맵 이미지를 확대 또는 축소할 수 있으며, 첫 번째 파라미터는 X축을 기준으로 확대하는 비율, 두 번째 파라미터는 Y축을 기준으로 확대하는 비율을 의미함 • 세 번째와 네 번째 파라미터는 확대 또는 축소할 때 기준이 되는 위치가 되는데 일반적으로는 비트맵 이미지의 중심점을 지정함 • postTranslate() 메소드는 비트맵 이미지를 이동시킬 때 사용함 • postRotate() 메소드는 비트맵 이미지를 회전시킬 때 사용함 9. 멀티터치 이미지뷰어 만들기 - 109 - 메인 액티비티 코드 만들기 package org.androidtown.events.multitouch; ... public class MainActivity extends Activity { LinearLayout viewerContainer; ImageDisplayView displayView; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { viewerContainer = (LinearLayout) findViewById(R.id.viewerContainer); Bitmap sourceBitmap = loadImage(); 1 XML 레이아웃에 정의한 리니어 레이아웃 객체 참조 if (sourceBitmap != null) { Continued.. 9. 멀티터치 이미지뷰어 만들기 - 110 - 메인 액티비티 코드 만들기 (계속) displayView = new ImageDisplayView(this); 2 새로 정의한 ImageDisplayView 객체 생성 displayView.setImageData(sourceBitmap); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); viewerContainer.addView(displayView, params); 3 addView()를 이용해 리니어 레이아웃 객체에 ImageDisplayView 객체 추가 } } private Bitmap loadImage() { Resources res = getResources(); Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.beach); return bitmap; } } 9. 멀티터치 이미지뷰어 만들기 - 111 - 실행 화면 9. 멀티터치 이미지뷰어 만들기 - 112 - 디버깅 메시지 확인하기 [두 손가락으로 터치할 때의 좌표값 디버깅] 9. 멀티터치 이미지뷰어 만들기 - 113 - 참고 문헌 [ References] • 기본 서적 2013, 정재곤, “Do it! 안드로이드 앱 프로그래밍(개정판)”, 이지스퍼블리싱(주) • Android Website http://www.android.com/ • Google Developer’s Conference http://code.google.com/events/io/ • Android SDK Documentation References - 114 -