회사는 정말 싫어욧

JTBC의 RSS 서비스 링크

http://news.jtbc.joins.com/Etc/RssService.aspx


<RSS 서비스 메인>


<속보 RSS xml>


Jsoup을 이용해 rss 서비스 주소와 이름을 가져오고 이걸로 안드로이드에 Tab layout(탭)으로 만든다

원하는 메뉴(속보, 정치, 경제, 사회, 국제, 문화, 연예, 스포츠, 풀영상, 뉴스랭킹, 뉴스룸, 아침&, 뉴스현장, 정치부회의)를 누르면

해당 서비스 주소에 나오는 뉴스를 ListView(리스트)로 보여준다




[예제 따라하기 3 ~ 탭을 눌렀을 때 나오는 내용을 만들자(Fragment)]


1. Jsoup으로 가져올 데이터의 형태 생각하기


예제 따라하기 2까지 했다면 위에 탭이 생겼을 것이고 이 탭을 눌렀을 때 Category 클래스에 있는 title과 url을 가지고 JTBCFragment로 넘어간다 라는걸 알 수 있다

이번에는 가지고 넘어간 url에 있는 뉴스정보를 다시 한번 접근해서 원하는 모양으로 화면에 나오게 만들어 볼 예정이다


우선 url에 접근했을 때 우리가 확인할 수 있는 데이터를 Category 클래스를 만들었을 때처럼 다시 분석해본다


title이 '속보'이고 url이 'http://fs.jtbc.joins.com//RSS/newsflash.xml'인 Category탭을 눌렀다면 우리는 url의 xml에서 뉴스정보만 가져와야 한다



다행히 속보 외에 다른 카테고리도 기본 데이터는 위와 같이 <item>안에 있는 <title>,<link>,<description>,<pubDate>에 있는 것을 확인할 수 있다

만약에 속보 외에 다른 카테고리에서 title이 없거나 description이 없거나 1개 이상의 항목이 없을 경우가 있기 때문에

DTO에 가지고 올때나 화면에 내용을 찍을 때 해당 정보가 null인 경우 혹은 size가 0인 경우 등을 예외처리를 해주는 편이 좋다

public class NewsDTO {
private String title;
private String link;
private String description;
private String pubDate;

public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getLink() { return link; }
public void setLink(String link) { this.link = link; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getPubDate() { return pubDate; }
public void setPubDate(String pubDate) { this.pubDate = pubDate; }

@Override
public String toString() {
return "뉴스제목" + title + " " + pubDate + " 발행일";
}
}

Category클래스를 만들 때와 마찬가지로 Jsoup으로 읽어올 정보들을 저장할 NewsDTO 클래스를 작성한다

위와 같이 아까 html에 있던 데이터 중 원하는 데이터 항목만 가져올 예정이다

DTO이기 때문에 변수 선언한 후 Getter&Setter만 추가해주면 되고 toString을 오버라이딩 하면 중간중간 로그를 찍을 때 값을 보기 편하다


2. 뉴스 하나의 모양을 결정하는 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="match_parent"
android:orientation="vertical">

<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>

<TextView
android:id="@+id/titleTV"
android:layout_alignParentLeft="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:textSize="10pt"
android:text="뉴스 제목"/>

<TextView
android:id="@+id/dateTV"
android:layout_alignParentRight="true"
android:layout_alignBottom="@+id/titleTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="10dp"
android:textSize="6pt"
android:text="뉴스 날짜"/>


</RelativeLayout>
<TextView
android:id="@+id/descTV"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="25dp"
android:textSize="8pt"
android:text="뉴스 간략 설명"/>

<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_gravity="center"
android:background="#cad8e6"/>

</LinearLayout>

뉴스 하나의 모양을 결정해주는 news_item.xml을 만들었다

간단하게 오른쪽 위에 보이는 것처럼 만들었다 여기에서는 데이터가 바뀌는 항목(TextView 3개)에 id를 각각 부여했다

이후 탭을 눌렀을 때 넘어오는 url에 접근해서 원하는 뉴스를 읽어오고 해당 뉴스의 제목,날짜,간략설명으로 내용을 바꾸기 위해서이다


3. news_item.xml을 가지고 1개의 뉴스를 보여주는 NewsView클래스를 만든다

import android.content.Context;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.TextView;

public class NewsView extends LinearLayout {
//뉴스 1개 보여질 xml에서 사용하는 요소
TextView titleTV, dateTV, descTV;


public NewsView(Context context, NewsDTO data) {
super(context);

LayoutInflater inflater = (LayoutInflater) context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.news_item,this,true);

titleTV = findViewById(R.id.titleTV);
dateTV = findViewById(R.id.dateTV);
descTV = findViewById(R.id.descTV);

setNews(data);


}

public void setNews(NewsDTO data) {
//titleTV 세팅
if(data.getTitle()!=null && data.getTitle().trim().length()>0)
titleTV.setText(data.getTitle());
else
titleTV.setText("");

//dateTV 세팅
if(data.getPubDate()!=null && data.getPubDate().trim().length()>0)
dateTV.setText(data.getPubDate());
else
dateTV.setText("");

//descTV 세팅
if(data.getDescription()!=null && data.getDescription().trim().length()>0)
descTV.setText(data.getDescription());
else
descTV.setText("");
}
}

이 NewsView클래스의 경우 화면에 표시되는 모양과 실제 넘어온 데이터(NewsDTO)로 값을 변경하는 내용이다

우선 LinearLayout으로 상속받은 NewsView클래스를 작성하고 2번에서 만든 xml에서 id를 주었던 요소들을 찾는다(findViewById)

이 때 xml파일을 실제로 가져와야하는데 inflater로 가져온다


쉽게 xml파일이 붕어빵 틀이면 inflater로 틀에다가 반죽을 부어 실물을 만드는 것이고

이 NewsView는 그 실물로 만든 붕어빵에 내용물을 팥인지 슈크림인지와 같이 내용을 setNews메소드를 통해 바꿔주는 것이다

(xml = 모양 틀, inflater = xml을 읽어와서 실제로 실물을 만드는 부분)


여기에서는 setNews를 메소드로 만들어서 2번에서 id를 주었던 값이 변하는 TextView요소 3개의 값을 실제로 변경해주는 내용이다

이 setNews는 NewsView가 처음 만들어질때 각 요소를 findViewById(id값) 한 후 값을 변경해주는 용도로 사용하기도 하고

이후에 나올 Adapter클래스를 작성할 때 convertView가 이미 있을 경우 NewsView를 새로 만드는 것이 아닌 값만 바꾸는 용도로 사용할 예정이다


4. 뉴스 전체가 나올 JTBCFragment에서 사용할 xml을 수정한다

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".JTBCFragment">

<ListView
android:id="@+id/NewsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</FrameLayout>

뉴스는 밑으로 스크롤해가면서 읽을 수 있게 보여줄 예정이라 ListView를 사용했다

xml은 모양틀이기 때문에 원하는 모양으로 변경할 수 있다



5. NewsView를 사용한 Adapter인 NewsAdapter를 작성하자

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

import java.util.List;

public class NewsAdapter extends BaseAdapter {
private Context context;
private List<NewsDTO> list;

public NewsAdapter(Context context, List<NewsDTO> list) {
this.context = context;
this.list = list;
}

@Override
public int getCount() {
return list.size();
}

@Override
public Object getItem(int position) {
return list.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
NewsDTO data = list.get(position);
NewsView view = null;

//이전에 만들어 놓은 View가 없으면
if (convertView == null) {
//NewsView모양으로 새로 만들고
view = new NewsView(context, data);

//이전에 만들어 놓은 View가 있으면
} else {
//만들어놨던 View(convertView)를 재활용해서 내용만 바꾼다
view = (NewsView) convertView;
view.setNews(data);
}
return view;
}
}

BaseAdapter를 상속받아 NewsAdapter를 만든다

BaseAdapter를 상속받으면 빨간줄이 뜨면서 오버라이딩을 해야하는데 이때 getCount, getItem, getItemId, getView를 메소드 오버라이딩 해주게 된다


메소드 오버라이딩만 먼저 해준 뒤 변수 선언을 해준다

BaseAdapter를 상속받는 경우 Context가 없기 때문에 Context와 실제 데이터를 가지고 있을 List<NewsDTO>를 만들고

이 두가지를 매개변수로 전달받는 생성자를 만들어준다

Context와 List<ViewDTO>를 전달받는 생성자를 만들어 놓을 경우 기본생성자는 없어도 된다 (이 경우 기본생성자는 사용 불가능)


오버라이딩한 메소드에는 각각 알맞은 데이터를 리턴해주고 getView의 내용을 작성한다 (매우 중요)


getView가 실제로 우리가 화면에서 볼 데이터를 결정해준다


우리가 ListView로 만들어진 뉴스를 본다고 생각할 때 한 화면에 보여지는 뉴스 갯수는 제한적이다 (많아야 7-8개 정도?)

다만 우리가 스크롤을 할때 아래로 내리면 아래쪽에 있는 뉴스 정보가 보이고 위로 다시 올리면 위쪽에 있는 뉴스 정보가 보이기 때문에

일반적으로는 전체 뉴스에 대해서 NewsView가 만들어져 있다 라고 생각하기 쉽다

실제로는 안드로이드가 모든 뉴스에 대해서 NewsView를 만들어 놓은게 아니라 맨 처음 화면을 띄울 때 빈 화면일 경우 NewsView를 9-10개 정도 만들고

우리가 아래로 스크롤 할 때는 위로 올라가버려 화면에 안나오는 NewsView를 밑으로 가져와서 데이터만 바꾸고

다시 위로 스크롤 할 때는 아래로 내려가버려 화면에 안나오는 NewsView를 위로 가져와서 데이터만 바꾸고 하는 형태이다


그래서 getView 메소드에서는 선택한 속보 탭의 뉴스 하나하나를 읽어오고 읽어온 뉴스정보를 화면에 보여줘야 하는데

이전에 만들어놓은 NewsView가 이동해서 화면에 안나올 경우 만들어놓은 View를 재활용해서 내용만 바꿔서 보여주고

만들어놓은 NewsView가 없는 경우에는 새로 NewsView를 만들어서 보여주는 것이다


6. 이제 탭을 누르면 해당 탭의 url에 접속해서 뉴스 정보를 가져오는 내부 클래스 NewsTask를 만든다

class NewsTask extends AsyncTask<Void,Void,Void>{
List<NewsDTO> newsList;

@Override
protected Void doInBackground(Void... voids) {

try {
Document document = Jsoup.connect(mParam2).get();
Elements elements = document.select("item");
for (Element element : elements) {
NewsDTO data = new NewsDTO();

Elements e = element.select("title");
if (e != null && e.size()>0)
data.setTitle(e.get(0).text());

e = element.select("link");
if (e != null && e.size()>0)
data.setLink(e.get(0).text());

e = element.select("description");
if (e != null && e.size()>0)
data.setDescription(e.get(0).text());

e = element.select("pubDate");
if (e != null && e.size()>0)
data.setPubDate(e.get(0).text());

newsList.add(data);
}
Log.d("뉴스 다 읽었는지 찍어볼게요",newsList.toString());
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPreExecute() {
super.onPreExecute();
newsList = new ArrayList<>();
}
}

예제 따라하기 2에서 만든 CategoryTask와 비슷하지만 여기서는 url 정보가 이미 mParam2에 들어가 있기 때문에 strings[0]을 사용하지 않고 mParam2를 사용한다

그래서 extends AsyncTask<Void,Void,Void>가 된다(execute할 때 넘겨줄 정보가 없기 때문에)


내용은 CategoryTask와 마찬가지로 url에 Jsoup.connect(URL).get()으로 접근하고

document.select("item")을 통해 뉴스만 선택한다

이 뉴스묶음을 하나씩 접근하면서 그 안에 있는 title, link, description, pubDate값을 가져오는 내용이다

이 때 element.select("title")을 먼저 하고 이 값이 null이 아니거나 size가 0 이상일 때 data.setTitle을 하였는데

아까 위에서 적었듯이 어떤 카테고리에는 이 title태그값이 없을 수도 있기 때문에 무조건 값을 넣는 코드를 작성하면

title태그값이 없어서 읽어온 값이 없으면 에러가 생길 수 있다


7. JTBCFragment에 4에서 만든 xml에 있는 listView 요소를 찾아준다

public class JTBCFragment extends Fragment {
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
private String mParam1;
private String mParam2;

private ListView listView;

public JTBCFragment() {
}
public static JTBCFragment newInstance(String param1, String param2) {
JTBCFragment fragment = new JTBCFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);

return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View view = inflater.inflate(R.layout.fragment_jtbc, container, false);

listView = view.findViewById(R.id.NewsList);

new NewsTask().execute();

return view;


}

class NewsTask extends AsyncTask<Void,Void,Void>{
List<NewsDTO> newsList;

@Override
protected Void doInBackground(Void... voids) {

try {
Document document = Jsoup.connect(mParam2).get();
Elements elements = document.select("item");
for (Element element : elements) {
NewsDTO data = new NewsDTO();

Elements e = element.select("title");
if (e != null && e.size()>0)
data.setTitle(e.get(0).text());

e = element.select("link");
if (e != null && e.size()>0)
data.setLink(e.get(0).text());

e = element.select("description");
if (e != null && e.size()>0)
data.setDescription(e.get(0).text());

e = element.select("pubDate");
if (e != null && e.size()>0)
data.setPubDate(e.get(0).text());

newsList.add(data);
}
Log.d("뉴스 다 읽었는지 찍어볼게요",newsList.toString());
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPreExecute() {
super.onPreExecute();
newsList = new ArrayList<>();
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
NewsAdapter adapter = new NewsAdapter(getContext(),newsList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(newsList.get(position).getLink()));
startActivity(intent);
}
});
}


}

}

값이 변경될 요소인 ListView를 선언해주고 findViewByID로 이 요소를 찾아서 연결해놓는다

이때 findViewByID를 사용하려면 3번에서 했듯이 xml파일을 우리가 만들었기 때문에 inflater를 사용해야 한다

이부분이 onCreateView에서 inflater로 우리가 만든 xml파일을 이미 실물로 만들어놓았기 때문에

이 밑에서 findViewByID로 NewsList ID를 찾으면 연결이 끝난다


그리고 방금 만든 NewsTask를 실행하는 내용을 써준다    new NewsTask().execute()




8. NewsTask에서 뉴스를 읽어오고 나면 NewsAdapter를 사용하여 화면에 값이 보이게 해준다


@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
NewsAdapter adapter = new NewsAdapter(getContext(),newsList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(newsList.get(position).getLink()));
startActivity(intent);
}
});
}

onPostExecute메소드를 오버라이딩 하여 doInBackground가 끝나면 4번에서 만든 xml에 있는 ListView에 NewsAdapter를 붙인다

listView에 setAdapter를 통해 만든 NewsAdapter를 붙일 수 있다

그 밑의 setOnItemClickListener는 뉴스를 클릭했을 때 해당 뉴스기사로 이동하는 내용이다




완성한 화면은 아래와 같다



위에 탭이 슬라이드 되며 탭을 누를 경우 해당 탭에 대한 뉴스정보가 아래에 나온다

뉴스를 누를 경우 실제 뉴스기사 링크로 웹으로 연결된다



Jsoup을 이용해 웹주소에 접근하고 select로 가져오는 방법,

데이터를 담아올 DTO클래스를 작성하고 View, Adapter를 사용하는 방법이 중요하다