** XML Parsing
1. XML
=> eXtensible Markup Language의 약자
=> HTML이 구조적이지 못하고 해석을 웹 브라우저가하는 문제 때문에 데이터 포맷으로 부적합
=> 구조적이고 엄격한 문법을 적용하고 해석을 브라우저가 아닌 곳에서 할 수 있는 문자열 포맷을 고안했는데 이 문자열 포맷이 XML
=> 최근에 사용되던 HTML은 HTML에 XML 문법을 적용한 XHTML이었고 이것을 HTML4.01 이라고 했다.
요즘은 HTML5문법을 많이 사용
=> Web이 대중화되던 시절에 RSS(Rich Sit eSummary, Really Simple Syndication - 실시간으로 데이터를 제공)가 등장했고 SOAP(Simple Object Access Protocol - 서버에서 클라이언트에게 객체를 단순하게 전달하는 프로토콜)가 등장을 했는데 이 때 사용한 데이터 표준이 XML이다.
=> 자바스크립트에서 서버에게 데이터를 비동기적으로 요청을 해서 일부분의 UI만 갱신할 필요가 생겼는데 이 때 채택한 기술이 AJAX (Asyncronous Javacript And XML 의 약자)
=> 최근에는 REST API의 등장으로 json을 데이터 포맷으로 더 많이 이용한다.
=> 순수하게 넘겨주는 데이터는 json을 많이 사용하지만 사람이 직접 설정하는 부분은 아직까지 대부분은 XML이다.
2. XML의 기본 문법
<xml 구조나 인코딩 방식><!-- 없으면 에러인데 설정 파일의 경우는 프레임워크가 대신 추가해주기도 한다.-->
<DTD> <!-- xml 파일의 내용을 해석해서 다른 코드로 변경해주는 위치를 설정하는 것으로 설정 파일일 때는 필수이지만 데이터 일 때는 생략-->
<Root 태그>
데이터를 의미하는 태그와 내용
</Root 태그>
=> XML을 생성하는 것은 직접 작성하지 않고 라이브러리를 이용해서 데이터만 설정하면 된다.
3. XML 파싱
=> 2가지가 있는데 DOM Parsing과 SAX Parsing 2가지
=> 거의 대다수의 프로그래밍 언어가 2가지 방법을 동일하게 지원한다.
1) DOM(Document Object Model)
=> HTML이나 XML의 내용을 메모리에 펼쳐서 사용할 수 있도록 한 것
2) DOM Parser
=> XML 내용을 메모리에 전부 펼쳐놓고 필요한 DOM을 찾아서 파싱하는 방식
=> 처음부터 내용을 메모리에 전부 저자앟기 때문에 메모미 사용량이 많지만 속도는 빠르다.
=> 편집도 가능하다
=> Root까지 찾기
DocumentBuilderFactory factory = new DocumentBuilderFactory().newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream inputStream = new ByteArrayInputStream(문자열.getBytes("인코딩방식"));
Document document = builder.parse(inputStream);
Element root = document.getDocumentElement();
=> 파싱하고자 하는 태그 찾기
NodeList items = root.getElementsByTagName("태그이름");
for(int i =0; i<items.length(); i=i+1){
Node item = items.get(i);
Node text = item.getFirstChild();
String content = text.getNodeValue();
}
3) SAX Parser
=> 태그 하나하나를 읽어가면서 파싱하는 방식
=> 하나의 태그를 읽을 때 호출되는 콜백 메소드를 이용해서 파싱
=> 내용을 전부 메모리에 저장하지 않기 때문에 메모리 사용량이 적지만 속도는 느리다.
=> 편집도 불가능
DefaultHandler로 부터 상속받는 클래스를 생성해서 메소드를 재정의 한 후 사용
public void startDocument() : 문서의 시작을 만나면 호출되는 메소드
public void endDocument() : 문서의 끝을 만나면 호출되는 메소드
public void startElement(String uri, String localName, String qName, Attributes attires) : 열리는 태그를 만나면 호출되는 메소드 - qName이 태그이름이고 attrs 가 태그의 속성들의 집합
public void endElement(String uri, String localName, String qName) : 닫는 태그를 만나면 호출되는 메소드
public void characters(char [] chars, int start, int length) : 여는 태그와 닫는 태그 사이의 문자열을 만나면 호출되는 메소드 - chars는 내용이고 start는 시작위치 그리고 length는 길이이다.
태그 이름은 하나의 패킷에 전부 전송이 가능하지만 태그 안의 내용은 하나의 패킷을 초과하는 경우도 있을 수 있다.
이 메소드는 유일하게 하나의 태그에 여러번 호출될 수 있다.
긴 문자열의 데이터를 가져올 때는 이 메소드를 사용해야 한다.
=> 파싱 수행
SAXParserFactory factory = SAXParserFActory.newInstance();
SaxParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
DefaultHandler클래스객체 = new DefaultHandler클래스();
Reader.setContentHandler(객체);
InputStream inputStream = new ByteArrayInputStream(문자열.getByte("인코딩방식");
4. 한겨례 신문사의 전체 기사를 파싱해서 title과 link의 내용을 가져와서 출력
=> 단 첫번째 title과 link는 제외
1) 프로젝트 생성 - 실행가능한 Activitty를 추가해도 된다.
2) 웹에서 가져와야 하므로 인터넷 권한을 설정하고 프로토콜을 확인해서 Application에 설정을 추가
http://www.hani.co.kr/rss/
=> AndroidManifest.xml 파일에 권한 추가
<uses-permission android:name="android.permission.INTERNET"/>
=> application 태그에 설정 추가
android:usesClearTextTraffic="true"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android0729">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3) activity_main.xml 파일에 화면디자인
=> 화면 전체를 사용하는 TextView 배치
<?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=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/hanidisplay"/>
</ScrollView>
</LinearLayout>
4) MainActivity 클래스에서 디자인 뷰를 전부 찾아오기
=> 인스턴스 변수 선언
TextView haniDisplay;
=> onCreate 메소드에 뷰를 찾아오는 코드를 작성
haniDisplay = (TextView)findViewById(R.id.hanidisplay);
5) 데이터 다운로드를 위한 Thread클래스를 생성
6) 다운로드 받아서 파싱한 결과를 출력할 Handler를 생성
=> 여기서 화면 출력을 할게 아니라면 Handler는 필요가 없다.
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
public class MainActivity extends AppCompatActivity {
TextView haniDisplay;
//title 태그의 내용을 저장할 List
ArrayList<String> titleList = new ArrayList<>();
//link 태그의 내용을 저장할 List
ArrayList<String> linkList = new ArrayList<>();
//데이터를 다운로드 받아서 파싱할 스레드 클래
class ThreadEx extends Thread{
String xml;
public void run(){
//웹 서버에서 문자열 다운로드 받기
try{
//다운로드 받을 URL 생성
URL url = new URL("http://www.hani.co.kr/rss/");
//연결 객체 생성
HttpURLConnection con = (HttpURLConnection)url.openConnection();
//연결 옵션을 설정
con.setRequestMethod("GET");
con.setConnectTimeout(30000);
con.setUseCaches(false);
con.setDoOutput(true);
con.setDoInput(true);
//파일을 업로드하는 코드가 있으면 설정을 추가
//파라미터를 추가 - GET 일 때는 url에 바로 추가해도 됩니다.
//다운로드 받기 - 문자열 : BufferedReader, 파일 : BuffredInputStream
BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
//문자열을 가지고 + 연산을 하면 메모리 낭비가 발생할 수 있어서 StringBuilder를 이용
//문자열은 + 연산을 하면 현재 객체에 하는 것이 아니고 복사해서 수행
StringBuilder sb = new StringBuilder();
while(true){
String line = br.readLine();
if(line == null){
break;
}
//출력할 때 보기좋게 하기 위해서 \n을 추가
//실제 서비스를 할 때는 \n은 제거
sb.append(line + "\n");
}
//정리
br.close();
con.disconnect();
//다운로드 받은 내용을 문자열로 변환
xml = sb.toString();
Log.e("xml", xml);
}catch(Exception e){
//이 예외가 보이면 권한 설정 부분과 URL을 확인
Log.e("다운로드 예외", e.getMessage());
}
//파싱하는 부분
try{
if(xml != null){
//DOM 파싱을 위한 준비
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputStream inputStream = new ByteArrayInputStream(xml.getBytes("utf-8"));
Document document = builder.parse(inputStream);
Element root = document.getDocumentElement();
//title 태그 전부 가져오기
NodeList titles = root.getElementsByTagName("title");
NodeList links = root.getElementsByTagName("link");
for(int i=1; i<titles.getLength(); i=i+1){
//각각의 태그에 접근해서 문자열을 추출해서 저장
Node title = titles.item(i);
Node text = title.getFirstChild();
titleList.add(text.getNodeValue());
Node link = links.item(i);
text = link.getFirstChild();
linkList.add(text.getNodeValue());
}
//핸들러에게 출력을 요청
Message message = new Message();
//전송할 데이터가 있으면 message.obj에 대입
handler.sendMessage(message);
}
}catch(Exception e){
//이 예외가 보이면 파싱 알고리즘을 확인
Log.e("파싱 예외", e.getMessage());
}
}
}
//파싱한 결과를 받아서 출력할 핸들러 객체
Handler handler = new Handler(Looper.getMainLooper()){
public void handleMessage(Message message){
//titleList의 내용을 텍스트 뷰에 출력
StringBuilder sb = new StringBuilder();
for(String title : titleList){
sb.append(title + "\n");
}
haniDisplay.setText(sb.toString());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
haniDisplay = (TextView)findViewById(R.id.hanidisplay);
}
@Override
public void onResume(){
super.onResume();
//스레드 시작
new ThreadEx().start();
}
}
** Adapt View
=> 여러 개의 데이터를 출력하기 위한 View
=> 3개의 요소를 가지고 데이터를 출력
Data : 배열이나 List - 출력되는 내용은 기본적으로 각 요소의 toString의 결과이다.
=> View : 데이터를 출력학 위한 View - 기본값은 ListView
Adapter : Data와 View를 연결해주기 위한 Controller이다.
** 구동 원리를 알아보기 위한 실습
1. 실습 가능한 Activity 추가 - ListViewActivity
2. 화면 디자인
<?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=".ListViewActivity"
android:orientation="vertical">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listview"/>
</LinearLayout>
3.Activity.java 파일에 작성
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class ListViewActivity extends AppCompatActivity {
//출력할 데이터
String [] data;
//출력할 뷰
ListView listview;
//연결할 Adapter
ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);
//데이터 생성
data = new String[4];
data[0] = "SI";
data[1] = "SM";
data[2] = "QA";
data[3] = "DevOps";
//출력할 View 생성
listview = (ListView)findViewById(R.id.listview);
//adapter 생성
//첫번째양는 출력을 위한 Context
//두번째는 ListView의 행 모양
//android.R.layout에 기본 모양이 제공
//세번째는 출력할 데이터
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, data);
//뷰에 Adapter를 설정
listview.setAdapter(adapter);
}
}
** 배열이나 List 대신에 array.xml 파일에 만든 리소스도 출력이 가능
1.res/values 디렉토리에 array.xml 파일을 추가하고 배열을 생성
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pl">
<item>Machine Language</item>
<item>Assembly</item>
<item>C&C++</item>
<item>Java</item>
<item>Python</item>
<item>JavaScript</item>
<item>C#</item>
<item>R</item>
<item>Objective-C</item>
<item>Swift</item>
<item>Kotlin</item>
<item>Go</item>
<item>Scala</item>
<item>Ruby</item>
<item>VB</item>
<item>Closure</item>
<item>Haskell</item>
<item>SQL</item>
</string-array>
</resources>
2.Adapter 객체를 생성하는 코드를 변경
ArrayAdapter.createFromResource(Context context, int resourceId, int 행모양);
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
public class ListViewActivity extends AppCompatActivity {
//출력할 데이터
String [] data;
//출력할 뷰
ListView listview;
//연결할 Adapter
ArrayAdapter<CharSequence> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_view);
//데이터 생성
data = new String[4];
data[0] = "SI";
data[1] = "SM";
data[2] = "QA";
data[3] = "DevOps";
//출력할 View 생성
listview = (ListView)findViewById(R.id.listview);
//adapter 생성
//첫번째양는 출력을 위한 Context
//두번째는 ListView의 행 모양
//android.R.layout에 기본 모양이 제공
//세번째는 출력할 데이터
//adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, data);
adapter = ArrayAdapter.createFromResource(this, R.array.pl,
android.R.layout.simple_list_item_1);
//뷰에 Adapter를 설정
listview.setAdapter(adapter);
}
}
** AdapterView 의 종류
ListView
GridView
Spinner
Gallery
** Adapter
=> Adapter : 항목들의 집합을 관리하는 기본적인 메소드를 소유한 클래스
=> Adapter를 상속받은 ListAdapter : ListView와 연결에 필요한 메소드 정의
=> Adapter를 상속받은 SpinnerAdapter : Spinner와 연결에 필요한 메소드 정의
=> ListAdapter 와 Spinneradapter를 상속받은 BaseAdapter : 앞의 2개의 인터페이스의 메소드 중 기본적인 것들 구현
=> BaseAdapter를 상속받은 클래스 : ArrayAdapter(배열이나 List 연결), CursorAdapter(데이터베이스 커서 연결), SimpleAdapter
** ListView
=> BaseAdapter 모든 종류를 이용해서 디이터를 출력하는 것이 가능
=> ArrayAdapter는 배열이나 List 인터페이스를구현한 클래스, array.xml 파일에 만들어진 리소스를 주입받을 수 있다.
=> 행의 표시할 리소스 ID
Android.R.layout.simple_list_item_1 : 하나의 텍스트 뷰로 구성
Android.R.layout.simple_list_item_2 : 2개의 텍스트 뷰로 구성
Android.R.layout.simple_list_item_checked : 체크박스가 만들어짐
Android.R.layout.simple_list_item_single_choice: 라디오 버튼을 이용해서 하나의 행만선택하도록 해준다.
Android.R.layout.simple_list_item_multiple_choice : 체크박스를 이용해서 여러 개의 행만 선택하도록 해준다.
=> adapter 연결 메소드
setAdapter(Adapter adapter)
=> 선택 모드 설정
setChoiceMode(int id)
CHOICE_MODE_NONE, CHOICE_MODE_SINGLE, CHOICE_MODE_MULTIPLE로 설정
설정할 때는 적절한 행의 모양도 같이 설정해야 한다.
=> 경계선 모양과 두께 설정
모양을 반드시 먼저 설정한다.
setDivider(Drawable drawable)로 모양 설정
setDividerHeight(int height)로 두께 설정
=> 행을 선택했을 때 호출되는 Listener
AdapterView.OnItemClickListener 의 onItemClick이라는 메소드가 존재하고 첫번째 매개변수는 AdapterView가 전달되고 두번째 매개변수로는 선택한 행의 뷰가 리턴되고 세번째 매개변수는 행의 인덱스가 리턴되고 네번째 매개변수로는 항목의 고유한 ID가 전달된다.
=> 데이터가 변경된 경우 다시 출력을 요청하는 메소드
AdapterView가 notifyDataSetChanged()를 호출하면 된다.
=> 행의 선택 여부를 알려주는 메소드
ListView의 get?Position()을 이용하는데 여러 개 선택한 경우에는 getCheckedItemPosition()을 호출해서 SparseBooleanArray로 리턴받을 수 있는 데 이 배열은 ListView의 모든 항목의 선택 여부를 Boolean 배열로 만들어 준 것이다.
** ListView의 항목을 편집할 수 있고 여러 개 선택해서 삭제할 수 있는 코드 실습
1. 실행 가능한 Activity추가 - MultiActivity
2. 화면 디자인
<?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=".MultiActivity"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:hint="추가할 항목 입력"
android:id="@+id/iteminput"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text="추가"
android:textSize="20sp"
android:id="@+id/addbtn"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text="삭제"
android:textSize="20sp"
android:id="@+id/delbtn"/>
</LinearLayout>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/listview"/>
</LinearLayout>
3. Activity.java에 데이터를 출력
1) 인스턴스
4. 선택 모드와 선 모양 변경
=> Activity.java 파일의 onCreate 메소드에서 설정
//여러 개 선택가능한 모양으로 adapter 생성
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice, data);
listView.setAdapter(adapter);
//선 모양 설정
listView.setDivider(new ColorDrawable(Color.RED));
listView.setDividerHeight(3);
//선택 모드 변경
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
}
5. EditText에 입력을 하고 추가버튼을 누르면 데이터가 삽입되는 코드를 작성
1) Acitivity.java 파일에 2개 뷰에 대한 변수를 선언
2) Activity.java 파일의 onCreate 메소드
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
public class MultiActivity extends AppCompatActivity {
ListView listView;
ArrayList<String> data;
ArrayAdapter<String> adapter;
EditText iteminput;
Button addbtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi);
listView = (ListView)findViewById(R.id.listview);
data = new ArrayList<>();
data.add("Oracle");
data.add("MySQL");
data.add("MongoDB");
data.add("MS-SQL Server");
//여러 개 선택가능한 모양으로 adapter 생성
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice, data);
listView.setAdapter(adapter);
//선 모양 설정
listView.setDivider(new ColorDrawable(Color.RED));
listView.setDividerHeight(3);
//선택 모드 변경
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
iteminput = (EditText) findViewById(R.id.iteminput);
addbtn = (Button)findViewById(R.id.addbtn);
addbtn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
//유효성 검사를 수행
String item = iteminput.getText().toString().trim();
if(item.length() < 1){
Toast.makeText(MultiActivity.this, "입력을 하지 았습니다.", Toast.LENGTH_LONG).show();
return;
}
data.add(item);
//ListView를 재출력
adapter.notifyDataSetChanged();
Toast.makeText(MultiActivity.this, "데이터 추가 성공", Toast.LENGTH_LONG).show();
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(iteminput.getWindowToken(), 0);
//입력 뷰를 초기화
iteminput.setText("");
}
});
}
}
6. 삭제 버튼을 누르면 선택된 항목들을 삭제하는 작업
1)Activity.java 파일에 인스턴스 변수를 추가
Button delbtn;
2)Activity.java 파일의 onCreate 메소드에 추가
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
public class MultiActivity extends AppCompatActivity {
ListView listView;
ArrayList<String> data;
ArrayAdapter<String> adapter;
EditText iteminput;
Button addbtn;
Button delbtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_multi);
listView = (ListView)findViewById(R.id.listview);
data = new ArrayList<>();
data.add("Oracle");
data.add("MySQL");
data.add("MongoDB");
data.add("MS-SQL Server");
//여러 개 선택가능한 모양으로 adapter 생성
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_multiple_choice, data);
listView.setAdapter(adapter);
//선 모양 설정
listView.setDivider(new ColorDrawable(Color.RED));
listView.setDividerHeight(3);
//선택 모드 변경
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
iteminput = (EditText) findViewById(R.id.iteminput);
addbtn = (Button)findViewById(R.id.addbtn);
addbtn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
//유효성 검사를 수행
String item = iteminput.getText().toString().trim();
if(item.length() < 1){
Toast.makeText(MultiActivity.this, "입력을 하지 았습니다.", Toast.LENGTH_LONG).show();
return;
}
data.add(item);
//ListView를 재출력
adapter.notifyDataSetChanged();
Toast.makeText(MultiActivity.this, "데이터 추가 성공", Toast.LENGTH_LONG).show();
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(iteminput.getWindowToken(), 0);
//입력 뷰를 초기화
iteminput.setText("");
}
});
delbtn = (Button)findViewById(R.id.delbtn);
delbtn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
//listview에서 각 행에 대한 선택여부를 가져오기
SparseBooleanArray sba = listView.getCheckedItemPositions();
//여러 개의 인덱스를 삭제할 때는 뒤에서 부터 삭제
for(int i=listView.getCount()-1; i>=0; i=i-1){
if(sba.get(i) == true){
data.remove(i);
}
}
//선택 해제
listView.clearChoices();
adapter.notifyDataSetChanged();
}
});
}
}
7. SimpleAdapter
=> 하나의 행에 여러 개의 데이터를 구분해서 출력하고자 할 때 사용하는 Adapter
=> 데이터는 Arraylist<HashMap<String, String>> 타입으로 구성
=> 생성할 때는
New SimpleAdapter(Context context, List<Map<String, String>> list, int resourceID - 모양, String [] - key의 배열, int [] - 출력할 뷰의 모양)
8. CursorAdapter
=> SQLite의 검색 결과를 출력하기 위한 Adapter
New SimpleAdapter(Context context, int resourceID - 모양, Cursor cursor, String [] - 컬럼이름의 배열, int [] - 출력할 뷰의 모양)
=> 데이터베이스.rawQuery(String select 구문)의 결과를 데이터로 설정하면 된다.
** 사용자 정의 항목 뷰 사용
1. Layout Inflation
=> 안드로이드는 뷰를 만드는 방법 중에 XML을 이용하는 방법이 있다.
XML로 만든 뷰를 메모리에 할당을 해서 화면에 출력하는 것을 인플레이션이라고 한다.
=> Activity의 전체뷰로 설정하고자 할 때는 Activity.setContentView(int id)
=> 일부분으로 사용해야 하는 경우에는 Context 객체의 getSystemService메소드를 이용해서 LayoutInflater를 찾아와서 inflate(뷰의 아이디, 부모 뷰, 뷰 그룹 여부)을 호출하면 된다.
2. BaseAdapter로 부터 상속받는 클래스를 생성
=> 메소드를 재정의
getCount : 행의 개수를 설정하는 메소드 - 여기서 리턴한 값이 행의 개수가 된다.
getItem : position 위치의 항목을 리턴하는 메소드
getItemId : position 위치의 항목 뷰에 아이디를 설정하는 메소드 - position을 리턴하는 것이 일반적
getView(int posotion, View convertView, ViewGroup parent) : 여기서 리턴한 뷰가 ListView의 각 항목이 된다.
position은 각 항목의 인덱스
convertView는 이전에 출력된 뷰로 처음 메소드가 호출될 때는 null이지만 두번째 부터는 null이 아니다.
parent는 항목이 놓이게 되는 부모 뷰 - AdapterView
** 셀의 왼쪽에는 이미지를 그리고 가운데에는 TextView를 그리고 오른쪽에는 버튼을 배치해서 버튼을 우르면 현재 선택한 항목의 내용을 선택했다고 Toast 출력
데이터의 구조는 Map을 이용할 것이고 image 키에 출력할 이미지의 id를 title 키에 TextView에 출력할 내용을 content 키에 버튼을 누르면 출력할 내용을 저장
=> Map 대신에 DTO 클래스를 사용해도 된다.
DTO 클래스를 만들거라면 인스턴스 변수를 public 으로 선언해서 사용해도 된다.
서버는 안정성이나 신뢰성을 추구하고 여러 클라이언트가 동시에 사용하기 때문에 인스턴스 변수를 public으로 만드는 경우는 거의 없고 getter $ setter를 이용해서 데이터에 접근하지만 클라이언트는 편리성을 추구하고 여러 클라이언트가 동시에 사용할 이유가 없기 때문에 대부분 변수를 public으로 해서 접근한다.
1. 실행 가능한 Activity를 생성 - CustomCellUseActivity
2.레이아웃 파일에 ListView를 전체 크기로 출력
<?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=".CustomCellUseActivity"
android:orientation="vertical">
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/listview"/>
</LinearLayout>
3. Activity.java 파일에 인스턴스 변수를 추가
ListView listView;
//출력할 데이터
ArrayList<Map<String, Object>> data;
4.Acitivity.java 파일의 onCreate에서 listView를 찾아서 대입하고 데이터 생성 코드 작성
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class CustomCellUseActivity extends AppCompatActivity {
ListView listView;
//출력할 데이터
ArrayList<Map<String, Object>> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_cell_use);
listView = (ListView)findViewById(R.id.listview);
data = new ArrayList<>();
Map<String, Object> map =
new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "SI");
map.put("content", "시스템 개발");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "SM");
map.put("content", "시스템 운영 - 유지보수 및 관리");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "QA");
map.put("content", "품질관리 및 테스트");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "DevOps");
map.put("content", "개발과 운영환경 구축");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "IoT");
map.put("content",
"인터넷 되는 기기 내장 프로그램 - Embedded");
data.add(map);
}
}
5.하나의 항목으로 사용할 뷰의 모양을 생성
=>layout 디렉토리에 layout을 추가 - icontext.xml
<?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="60dp"
android:padding="5dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:id="@+id/image"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textSize="20sp"
android:id="@+id/title"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:textSize="20sp"
android:text="선택"
android:id="@+id/content"
/>
</LinearLayout>
6. Adapter 클래스 만들기 (JobAdapter.java)
=>BaseAdapter로 부터 상속받아서 메소드 재정의
package com.example.android0729;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import java.util.Map;
import java.util.zip.Inflater;
public class JobAdapter extends BaseAdapter {
Context context;
List<Map<String, Object>> list;
//생성자
public JobAdapter(Context context, List<Map<String, Object>> list){
this.context = context;
this.list = list;
}
//행의 개수를 설정하는 메소드
//이 메소드에서 리턴한 값만큼 아래 메소드들을 호출
@Override
public int getCount() {
return list.size();
}
//행의 항목을 만들어주는 메소드
@Override
public Object getItem(int i) {
Map<String, Object> map = list.get(i);
return map.get("title").toString();
}
//각 행의 아이디를 설정하는 메소드
@Override
public long getItemId(int i) {
return i;
}
//첫번째 매개변수는 행 번호
//두번째 매개변수가 출력할 뷰
//세번째 매개변수는 항목이 출력되는 AdapterView
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
final int pos = i;
if(view == null){
//icontext.xml 파일을 전개해서 뷰로 생성
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.icontext, viewGroup,false);
}
if(i % 2 == 0){
view.setBackgroundColor(Color.RED);
}else{
view.setBackgroundColor(Color.BLUE);
}
//이미지 출력
ImageView image = (ImageView)view.findViewById(R.id.image);
int imageid = (Integer)list.get(i).get("image");
image.setImageResource(imageid);
//텍스트 뷰
TextView title = (TextView)view.findViewById(R.id.title);
String titleText = (String)list.get(i).get("title");
title.setText(titleText);
//버튼
Button btn = (Button)view.findViewById(R.id.content);
btn.setOnClickListener(new Button.OnClickListener(){
public void onClick(View view){
String content = (String)list.get(pos).get("content");
Toast.makeText(context, content, Toast.LENGTH_LONG).show();
}
});
return view;
}
}
7. Activity.java 파일의 onCreate 수정
//어댑터 생성
JobAdapter adapter = new JobAdapter(this, data);
listView.setAdapter(adapter);
package com.example.android0729;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class CustomCellUseActivity extends AppCompatActivity {
ListView listView;
//출력할 데이터
ArrayList<Map<String, Object>> data;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_cell_use);
listView = (ListView)findViewById(R.id.listview);
data = new ArrayList<>();
Map<String, Object> map =
new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "SI");
map.put("content", "시스템 개발");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "SM");
map.put("content", "시스템 운영 - 유지보수 및 관리");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "QA");
map.put("content", "품질관리 및 테스트");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "DevOps");
map.put("content", "개발과 운영환경 구축");
data.add(map);
map = new HashMap<>();
map.put("image", R.mipmap.ic_launcher);
map.put("title", "IoT");
map.put("content",
"인터넷 되는 기기 내장 프로그램 - Embedded");
data.add(map);
//어댑터 생성
JobAdapter adapter = new JobAdapter(this, data);
listView.setAdapter(adapter);
}
}
**사용자 정의 셀 만들기
=> 셀로 사용할 뷰를 레이아웃 파일로 생성
=> 데이터를 받아서 레이아웃에 출력할 Adapter 클래스를 생성
=> ListView에 새로 만든 Adapter 클래스의 객체를 설정