이번에는 앞에서 다루었던 기본 컨트롤을 이용하는 간단한 앱을 만들어 봅시다. 소재는 도서 관리 앱입니다.
도서 관리 앱은 도서 추가, 도서 목록, 도서 검색 기능을 제공합니다. 앱의 화면은 세 개의 탭인 HOME(도서 목록), ADD(도서 추가), FIND(도서 검색)으로 원하는 기능을 선택하여 사용할 수 있습니다.
먼저 도서 관리자에서 관리할 도서 개체를 Book 클래스로 정의합시다. 도서 개체는 제목, 저자, 보유 개수, 장르를 멤버 필드로 갖고 있고 이들 값을 가져오기 할 수 있는 접근자 메서드 및 생성자로 구성합니다. 참고로 장르는 인문, 자연, 과학, 기타로 정하기로 할게요.
package com.example.ehclub.ex_bookmanager; /** * Created by ehclub on 2017-06-06. */ public class Book { public enum BookType{ HUMAN, NATURE, SCIENCE, ETC } String title; String author; int count; BookType bt; public String getTitle(){ return title; } public String getAuthor(){ return author; } public int getCount(){ return count; } public BookType getBookType(){ return bt; } public Book(String title, String author, int counnt, BookType bt){ this.title = title; this.author = author; this.count = counnt; this.bt = bt; } }
이번에는 리스트 뷰에 하나의 도서 정보(여기에서는 제목과 저자 정보만 출력함)를 출력할 때의 Layout을 추가합시다. 여기에서는 1행 2열의 GridLayout에 두 개의 TextView를 배치하는 형태로 정의할게요.
<?xml version="1.0" encoding="utf-8"?> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:useDefaultMargins="true" android:rowCount="1" android:columnCount="2"> <TextView android:id="@+id/tv_title" android:textColor="#00FF00" android:layout_row="0" android:layout_column="0" android:layout_gravity="center"/> <TextView android:id="@+id/tv_author" android:textColor="#0000FF" android:layout_row="0" android:layout_column="1" android:layout_gravity="center"/> </GridLayout>
이번에는 ListView와 매핑할 BookAdapter 클래스를 정의합시다. BookAdapter클래스는 BaseAdapter 클래스를 기반의 파생 클래스입니다.
public class BookAdapter extends BaseAdapter{ }
도서 개체들을 보관할 컬렉션과 Context, LayoutInflater, layout 번호를 멤버 필드로 선언하세요.
private ArrayList<Book> books = new ArrayList<>(); Context context; LayoutInflater inflacter; int layout;
BookAdapter 클래스 생성자에서는 Context와 layout 번호를 입력 인자로 받아 멤버 필드를 설정합니다. 그리고 inflacter 개체를 참조합니다.
public BookAdapter(Context context, int layout){ this.context = context; inflacter = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.layout = layout; }
getCount, getItem, getItemId, getView 메서드를 재정의하세요.
@Override public int getCount() { return books.size(); } @Override public Object getItem(int position) { return books.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView == null){ convertView = inflacter.inflate(layout,parent,false); } TextView tv_title = (TextView)convertView.findViewById(R.id.tv_title); tv_title.setText(books.get(position).title); TextView tv_author = (TextView)convertView.findViewById(R.id.tv_author); tv_author.setText(books.get(position).author); return convertView; }
도서 개체를 추가하는 메서드와 도서 개체를 검색하는 메서드를 제공하세요.
public void addBook(Book book){ books.add(book); } public Book findBook(String title){ int i = 0; for(i=0; i<books.size();i++){ if(books.get(i).title.compareTo(title)==0){ return books.get(i); } } return null; }
다음은 BookAdapter.java 소스 파일의 내용입니다.
package com.example.ehclub.ex_bookmanager; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.ArrayList; /** * Created by ehclub on 2017-06-06. */ public class BookAdapter extends BaseAdapter{ private ArrayList<Book> books = new ArrayList<>(); Context context; LayoutInflater inflacter; int layout; public BookAdapter(Context context, int layout){ this.context = context; inflacter = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.layout = layout; } @Override public int getCount() { return books.size(); } @Override public Object getItem(int position) { return books.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView == null){ convertView = inflacter.inflate(layout,parent,false); } TextView tv_title = (TextView)convertView.findViewById(R.id.tv_title); tv_title.setText(books.get(position).title); TextView tv_author = (TextView)convertView.findViewById(R.id.tv_author); tv_author.setText(books.get(position).author); return convertView; } public void addBook(Book book){ books.add(book); } public Book findBook(String title){ int i = 0; for(i=0; i<books.size();i++){ if(books.get(i).title.compareTo(title)==0){ return books.get(i); } } return null; } }
이제 activity_main.xml 파일에 컨트롤을 배치합시다. 여러분께서는 실행 화면과 앞에 소개한 기본 컨트롤 내용을 보면서 배치를 해 보세요.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.ehclub.ex_bookmanager.MainActivity" android:orientation="vertical"> <TabHost android:id="@+id/th" android:layout_width="match_parent" android:layout_height="match_parent"> <TabWidget android:id="@android:id/tabs" android:layout_gravity="top" android:layout_width="match_parent" android:layout_height="wrap_content"/> <FrameLayout android:id="@android:id/tabcontent" android:paddingTop="70sp" android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/tab_view1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/lv" android:layout_gravity="center_vertical" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> <GridLayout android:id="@+id/tab_view2" android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="4" android:rowCount="10"> <TextView android:text="제목:" android:layout_row="0"/> <EditText android:id="@+id/et_title" android:layout_row="0" android:layout_column="1" android:layout_columnSpan="3" android:layout_gravity="fill_horizontal"/> <TextView android:text="저자:" android:layout_row="1" android:layout_column="0" android:layout_gravity="fill_horizontal" /> <EditText android:id="@+id/et_author" android:layout_row="1" android:layout_column="1" android:layout_columnSpan="3" android:layout_gravity="fill_horizontal"/> <CheckBox android:id="@+id/cb_count" android:layout_row="2" android:layout_column="0" android:onClick="countCheckBoxClick" android:text="보유" android:layout_gravity="fill_horizontal"/> <TextView android:id="@+id/tv_count" android:text="0" android:layout_row="3" android:layout_column="0" android:gravity="right" android:layout_gravity="fill_horizontal"/> <TextView android:text="개" android:layout_row="3" android:layout_column="1" android:layout_gravity="fill_horizontal"/> <SeekBar android:id="@+id/sb_count" android:layout_row="3" android:layout_column="2" android:layout_columnSpan="2" android:layout_width="match_parent" android:max="100" android:layout_gravity="fill_horizontal"/> <RadioGroup android:layout_row="4" android:layout_rowSpan="2" android:id="@+id/rg_genre" android:layout_gravity="fill_horizontal"> <RadioButton android:id="@+id/rd_human" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="인문"/> <RadioButton android:id="@+id/rd_nature" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="자연"/> <RadioButton android:id="@+id/rd_science" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="과학"/> <RadioButton android:id="@+id/rd_etc" android:layout_width="match_parent" android:layout_height="wrap_content" android:checked="true" android:text="기타"/> </RadioGroup> <TableLayout android:layout_row="9" android:layout_column="0" android:layout_columnSpan="4" android:layout_gravity="fill_horizontal"> <TableRow> <Button android:onClick="addBtnClick" android:text="추가"/> <Button android:onClick="cancelBtnClick" android:text="취소"/> </TableRow> </TableLayout> </GridLayout> <GridLayout android:id="@+id/tab_view3" android:layout_width="match_parent" android:layout_height="match_parent" android:columnCount="4" android:rowCount="10"> <TextView android:text="제목:" android:layout_row="0"/> <EditText android:id="@+id/et_title2" android:layout_row="0" android:layout_column="1" android:layout_columnSpan="2" android:layout_gravity="fill_vertical"/> <Button android:id="@+id/btn_search" android:onClick="findBtnClick" android:layout_row="0" android:layout_column="3" android:text="검색" android:layout_gravity="right"/> <TextView android:text="저자:" android:layout_row="1" android:layout_column="0" android:layout_gravity="right"/> <TextView android:id="@+id/tv_author2" android:layout_row="1" android:layout_column="1" android:layout_columnSpan="3" android:layout_gravity="center"/> <TextView android:text="보유개수:" android:layout_row="2" android:layout_column="0" android:layout_gravity="right"/> <TextView android:id="@+id/tv_count2" android:layout_row="2" android:layout_column="1" android:layout_columnSpan="3" android:layout_gravity="center"/> <TextView android:text="장르:" android:layout_row="3" android:layout_column="0" android:layout_gravity="right"/> <TextView android:id="@+id/tv_booktype" android:layout_row="3" android:layout_column="1" android:layout_columnSpan="3" android:layout_gravity="center"/> </GridLayout> </FrameLayout> </TabHost> </LinearLayout>
이제 MainActivity.java 소스 파일을 편집합시다. 먼저 배치한 컨트롤을 참조할 멤버 필드를 선언합시다.
SeekBar sb; TextView tv_count; EditText et_title, et_author; CheckBox cb_count; RadioButton rd_etc; BookAdapter books; ListView lv; RadioGroup rg_genre;
onCreate 메서드에서는 findViewById 메서드를 호출하여 배치한 컨트롤을 멤버 필드가 참조하게 합니다. 그리고 BookAdapter 개체를 생성하고 ListView와 매핑하세요. 또한 탭 호스트를 setup합니다.
et_title = (EditText)findViewById(R.id.et_title); et_author = (EditText)findViewById(R.id.et_author); tv_count = (TextView)findViewById(R.id.tv_count); cb_count = (CheckBox)findViewById(R.id.cb_count); rd_etc = (RadioButton)findViewById(R.id.rd_etc); rg_genre = (RadioGroup)findViewById(R.id.rg_genre); lv = (ListView)findViewById(R.id.lv); books = new BookAdapter(this,R.layout.item); lv.setAdapter(books); sb = (SeekBar)findViewById(R.id.sb_count); sb.setEnabled(false); TabHost th = (TabHost)findViewById(R.id.th); th.setup();
세 개의 탭 페이지를 추가합니다. 그리고 첫 번째 탭을 현재 탭으로 설정합니다.
TabHost.TabSpec ts1 = th.newTabSpec("Tab1"); ts1.setIndicator("Home"); ts1.setContent(R.id.tab_view1); th.addTab(ts1); TabHost.TabSpec ts2 = th.newTabSpec("Tab2"); ts2.setIndicator("Add"); ts2.setContent(R.id.tab_view2); th.addTab(ts2); TabHost.TabSpec ts3 = th.newTabSpec("Tab3"); ts3.setIndicator("Find"); ts3.setContent(R.id.tab_view3); th.addTab(ts3); th.setCurrentTab(0);
FIND 탭을 선택하였을 때에는 컨트롤 정보를 초기화(ClearBookInfo 호출)하세요.
th.setOnTabChangedListener(new TabHost.OnTabChangeListener() { @Override public void onTabChanged(String tabId) { if(tabId.compareTo("Tab3")==0){ ClearBookInfo(); } } });
ClearBookInfo 메서드에서는 세 번째 탭에서 보여줄 컨트롤의 속성을 초기 값으로 설정합니다.
private void ClearBookInfo(){ EditText et = (EditText)findViewById(R.id.et_title2); TextView tv_author2 = (TextView)findViewById(R.id.tv_author2); TextView tv_count2 = (TextView)findViewById(R.id.tv_count2); TextView tv_booktype = (TextView)findViewById(R.id.tv_booktype); et.setText(""); tv_author2.setText(""); tv_count2.setText(""); tv_booktype.setText(""); }
SeekBar의 값을 변경하면 보유 개수를 나타내는 text 속성을 변경합니다.
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { tv_count.setText(String.valueOf((progress))); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } });
보유 체크 박스를 선택하면 SeekBar를 활성화하고 선택 해제하면 비활성화합니다. 그리고 비활성화할 때는 보유 개수를 0으로 설정합니다.
public void countCheckBoxClick(View view){ CheckBox cb = (CheckBox)view; boolean checked =cb.isChecked(); sb.setEnabled(checked); if(checked==false){ sb.setProgress(0); tv_count.setText("0"); } }
도서 추가 버튼을 클릭하면 입력 정보를 얻어와서 도서 개체를 생성한 후에 BookAdapter 개체에 추가합니다. 그리고 컨트롤의 속성은 초기값으로 리셋합니다.
public void addBtnClick(View view){ String title = et_title.getText().toString(); String author = et_author.getText().toString(); int count = sb.getProgress(); Book.BookType bt = Book.BookType.ETC; switch(rg_genre.getCheckedRadioButtonId()){ case R.id.rd_human: bt = Book.BookType.HUMAN; break; case R.id.rd_nature: bt = Book.BookType.NATURE; break; case R.id.rd_science: bt = Book.BookType.SCIENCE; break; case R.id.rd_etc: bt = Book.BookType.ETC; break; } Book book = new Book(title, author,count, bt); books.addBook(book); Reset(); }
입력 컨트롤의 값을 리셋하는 Reset 메서드를 정의합시다.
private void Reset(){ et_title.setText(""); et_author.setText(""); cb_count.setChecked(false); sb.setEnabled(false); sb.setProgress(0); rd_etc.setChecked(true); et_title.requestFocus(); }
취소 버튼을 클릭하면 Reset 메서드를 호출합니다.
public void cancelBtnClick(View view){ Reset(); }
검색 버튼을 누르면 입력한 도서 제목을 얻어온 후에 BookAdapter 개체의 findBook 메서드를 호출하여 도서 개체를 검색합니다. 그리고 검색한 도서 개체의 정보를 컨트롤에 나타나게 합니다.
public void findBtnClick(View view){ EditText et = (EditText)findViewById(R.id.et_title2); String title = et.getText().toString(); Book book = books.findBook(title); TextView tv_author2 = (TextView)findViewById(R.id.tv_author2); TextView tv_count2 = (TextView)findViewById(R.id.tv_count2); TextView tv_booktype = (TextView)findViewById(R.id.tv_booktype); if(book != null){ tv_author2.setText(book.getAuthor()); tv_count2.setText(String.valueOf(book.getCount())); tv_booktype.setText(book.getBookType().toString()); } else{ ClearBookInfo(); } }
다음은 MainActivity.java 소스 파일의 내용입니다.
package com.example.ehclub.ex_bookmanager; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ListView; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.SeekBar; import android.widget.TabHost; import android.widget.TextView; import java.util.List; public class MainActivity extends AppCompatActivity { SeekBar sb; TextView tv_count; EditText et_title, et_author; CheckBox cb_count; RadioButton rd_etc; BookAdapter books; ListView lv; RadioGroup rg_genre; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_title = (EditText)findViewById(R.id.et_title); et_author = (EditText)findViewById(R.id.et_author); tv_count = (TextView)findViewById(R.id.tv_count); cb_count = (CheckBox)findViewById(R.id.cb_count); rd_etc = (RadioButton)findViewById(R.id.rd_etc); rg_genre = (RadioGroup)findViewById(R.id.rg_genre); lv = (ListView)findViewById(R.id.lv); books = new BookAdapter(this,R.layout.item); lv.setAdapter(books); sb = (SeekBar)findViewById(R.id.sb_count); sb.setEnabled(false); TabHost th = (TabHost)findViewById(R.id.th); th.setup(); TabHost.TabSpec ts1 = th.newTabSpec("Tab1"); ts1.setIndicator("Home"); ts1.setContent(R.id.tab_view1); th.addTab(ts1); TabHost.TabSpec ts2 = th.newTabSpec("Tab2"); ts2.setIndicator("Add"); ts2.setContent(R.id.tab_view2); th.addTab(ts2); TabHost.TabSpec ts3 = th.newTabSpec("Tab3"); ts3.setIndicator("Find"); ts3.setContent(R.id.tab_view3); th.addTab(ts3); th.setCurrentTab(0); th.setOnTabChangedListener(new TabHost.OnTabChangeListener() { @Override public void onTabChanged(String tabId) { if(tabId.compareTo("Tab3")==0){ ClearBookInfo(); } } }); sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { tv_count.setText(String.valueOf((progress))); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); } public void countCheckBoxClick(View view){ CheckBox cb = (CheckBox)view; boolean checked =cb.isChecked(); sb.setEnabled(checked); if(checked==false){ sb.setProgress(0); tv_count.setText("0"); } } public void addBtnClick(View view){ String title = et_title.getText().toString(); String author = et_author.getText().toString(); int count = sb.getProgress(); Book.BookType bt = Book.BookType.ETC; switch(rg_genre.getCheckedRadioButtonId()){ case R.id.rd_human: bt = Book.BookType.HUMAN; break; case R.id.rd_nature: bt = Book.BookType.NATURE; break; case R.id.rd_science: bt = Book.BookType.SCIENCE; break; case R.id.rd_etc: bt = Book.BookType.ETC; break; } Book book = new Book(title, author,count, bt); books.addBook(book); Reset(); } public void cancelBtnClick(View view){ Reset(); } private void Reset(){ et_title.setText(""); et_author.setText(""); cb_count.setChecked(false); sb.setEnabled(false); sb.setProgress(0); rd_etc.setChecked(true); et_title.requestFocus(); } public void findBtnClick(View view){ EditText et = (EditText)findViewById(R.id.et_title2); String title = et.getText().toString(); Book book = books.findBook(title); TextView tv_author2 = (TextView)findViewById(R.id.tv_author2); TextView tv_count2 = (TextView)findViewById(R.id.tv_count2); TextView tv_booktype = (TextView)findViewById(R.id.tv_booktype); if(book != null){ tv_author2.setText(book.getAuthor()); tv_count2.setText(String.valueOf(book.getCount())); tv_booktype.setText(book.getBookType().toString()); } else{ ClearBookInfo(); } } private void ClearBookInfo(){ EditText et = (EditText)findViewById(R.id.et_title2); TextView tv_author2 = (TextView)findViewById(R.id.tv_author2); TextView tv_count2 = (TextView)findViewById(R.id.tv_count2); TextView tv_booktype = (TextView)findViewById(R.id.tv_booktype); et.setText(""); tv_author2.setText(""); tv_count2.setText(""); tv_booktype.setText(""); } }