회사는 정말 싫어욧

JTBC의 RSS 서비스 링크

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


<RSS 서비스 메인>


<속보 RSS xml>


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

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

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


실행 결과 화면




프로젝트 파일 구조



[XML 파일]


1. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_weight="1"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="@string/app_name">

</android.support.v7.widget.Toolbar>

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>

<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

2. fragment_jtbc.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>

3. news_item.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>

[JAVA 파일]

코드만 붙여넣기 할 경우 맨 위에 package로 시작하는 한줄만 남겨놓고 그 밑에 내용 전체를 이 코드로 변경해준다


1. MainActivity

import android.os.AsyncTask;
import android.support.design.widget.TabLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import android.widget.TextView;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TabLayout tabLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

mViewPager = (ViewPager) findViewById(R.id.container);
tabLayout = (TabLayout) findViewById(R.id.tabs);

tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);

//어플이 실행되면 RSS서비스 메인 주소에 접근한다
String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx";
new CategoryTask().execute(rssURL);

}


class CategoryTask extends AsyncTask<String,Void,Void> {
List<Category> categories; //읽어온 데이터를 리스트로 저장

//실제 CategoryTask의 주요 작업
@Override
protected Void doInBackground(String... strings) {
String rssURL = strings[0];

try {
Document document = Jsoup.connect(rssURL).get();
Elements elements = document.select("table.common_table tbody tr");
Category category;

//한줄씩 가져와서 Category에 저장
for (Element element : elements) {
//title
category = new Category();
category.setTitle(element.select("th").get(0).text());
category.setUrl(element.select("a").get(0).text());

//한줄을 다 읽었으면 categories에 다시 보관
categories.add(category);
}

Log.d("목록 다 읽었는지 찍어볼게요",categories.toString());
} catch (IOException e) {
e.printStackTrace();
}

return null;
}

//가장 먼저 실행
@Override
protected void onPreExecute() {
super.onPreExecute();

categories = new ArrayList<>();
}

//doInBackground가 끝나면 실행
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);

//Category 개수만큼 반복
for (Category c : categories) {
tabLayout.addTab(tabLayout.newTab());
}

TabPagerAdapter adapter = new TabPagerAdapter
(getSupportFragmentManager(),tabLayout.getTabCount(),categories);

mViewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(mViewPager);


}
}

}

2. Category

public class Category {
private String title;
private String url;

//Getter&Setter 추가
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }

//toString 추가(생략 가능)
@Override
public String toString() { return title+": "+url; }
}


3. TabPagerAdapter

import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;

import java.util.List;

public class TabPagerAdapter extends FragmentStatePagerAdapter {
private int tabCount;
private List<Category> categories;

public TabPagerAdapter(FragmentManager fm, int tabCount, List<Category> categories) {
super(fm);
this.tabCount = tabCount;
this.categories = categories;
}

@Override
public Fragment getItem(int position) {
Category category = categories.get(position);
Fragment fragment = JTBCFragment.newInstance(category.getTitle(),category.getUrl());
return fragment;
}

@Override
public int getCount() {
return tabCount;
}

@Nullable
@Override
public CharSequence getPageTitle(int position) {
return categories.get(position).getTitle();
}
}

4. JTBCFragment

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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);
}
});
}


}

}

5. NewsDTO

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 + " 발행일";
}
}


6. 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("");
}
}


7. 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;
}
}


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를 사용하는 방법이 중요하다



JTBC의 RSS 서비스 링크

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


<RSS 서비스 메인>


<속보 RSS xml>


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

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

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




[예제 따라하기 2 ~ Jsoup을 이용해 rss서비스 메뉴이름을 탭으로 만든다]


1. Jsoup으로 가져와 찍을 탭의 형태 생각하기


RSS 서비스 메인(http://news.jtbc.joins.com/Etc/RssService.aspx)에는 RSS 서비스 주소와 이름을 제공한다


속보http://fs.jtbc.joins.com/RSS/newsflash.xml


위와 같이 이름에 해당하는 속보, 그리고 실제로 속보 뉴스정보를 볼 수 있는 xml주소로 이루어져있다

이 사이트의 html을 전체 읽어와서 내가 원하는 텍스트인 '속보', 'http://fs.jtbc.joins.com/RSS/newsflash.xml' 만 읽어올 수 있게 만들 예정이다

사용하기 편하게 이 카테고리 제목과 URL주소를 Category라는 하나의 클래스로 만들어서 속보~정치부회의까지 제목과 URL주소를 List<Category>의 객체에 담자

클래스를 가지고 만든 변수명은 객체라고 부른다 (int a 같은 거는 변수 a라고 부른다)


2. 사용하기 편하게 데이터를 담을 Category 클래스 만들기

public class Category {
private String title;
private String url;

//Getter&Setter 추가
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }

//toString 추가(생략 가능)
@Override
public String toString() { return title+": "+url; }
}

'속보'가 들어갈 title과 'http://fs.jtbc.joins.com/RSS/newsflash.xml'가 들어갈 link를 만들고 이에 대해 Getter와 Setter를 추가했다

대부분은 Getter, Setter는 우클릭해서 생성할수 있는 기능들이 있으니까 그걸 활용하면 편하다

Android Studio에서는

private String title;

private String url;

만 써놓고 그냥 우클릭, Generate를 누르면 메뉴중에 Getter and Setter라고 있다

그걸 누르고 title하고 url 둘다 선택해서 ok하면 알아서 써진다


3. 주소에 접근해 데이터를 읽어오는 CategoryTask 클래스를 내부클래스로 만든다

public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TabLayout tabLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

mViewPager = (ViewPager) findViewById(R.id.container);
tabLayout = (TabLayout) findViewById(R.id.tabs);

tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);

//어플이 실행되면 RSS서비스 메인 주소에 접근한다
String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx";
new CategoryTask().execute(rssURL);

}
}

class CategoryTask extends AsyncTask<String,Void,Void> {

}

tabLayout의 탭메뉴에 설정을 바꾼다

같은비율로 배치하고    tabLayout.setTabGravity(TabLayout.GRAVITY_FILL)

스크롤할 수 있게 한다    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE)


rssURL주소를 넣어주고    String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx

rssURL주소에 접근한다 new CategoryTask().execute(rssURL)


** 인터넷 주소에 접근할 때는 반드시 쓰레드로 접근해야하는데 이 예제에서는 AsyncTask를 상속받은 클래스를 execute하는 것으로 해결한다


4. rssURL주소의 소스를 jsoup을 이용해 읽어오고 원하는 값을 저장할 List<Category> 객체를 만든다

class CategoryTask extends AsyncTask<String,Void,Void> {
List<Category> categories; //읽어온 데이터를 리스트로 저장

//실제 CategoryTask의 주요 작업
@Override
protected Void doInBackground(String... strings) {
return null;
}

//가장 먼저 실행
@Override
protected void onPreExecute() {
super.onPreExecute();

categories = new ArrayList<>();
}
}


사실 3번까지 하게 되면 맨 밑에

class CategoryTask extends AsyncTask<String,Void,Void> {

}


이 코드가 빨간색으로 뜬다 반드시 작성해야 하는 DoInBackground메소드가 안만들어져서 그렇다

Android Studio에서는 빨간 밑줄 그어진 부분 마지막 위치에서 Alt+Enter하고 Enter를 한번 더 누르면 알아서 만들어진다


이때 AsyncTask 뒤에 입력되는 값의 의미

    // 첫번째가 doInBackground로 전달되는 자료형

    // 두번째는 onProgressUpdate로 전달되는 자료형

    // 세번째는 onPostExecute로 전달되는 자료형




이 CategoryTask라는 클래스에서는 rssURL에 있는 많은 정보 중에서 속보~정치부회의까지의 제목과 그에 따른 xml주소만 가져올건데

이걸 담기 위한 Category 클래스를 이미 만들었다


하나의 Category클래스에는 하나의 제목과 하나의 주소를 저장할 수 있으므로 이걸 다시 List로 묶어준다면

이 이미지상에 있는 정보를 List<Category> categories 객체 하나로 접근할 수 있게 된다


5. doInBackground메소드를 수정하여 본격적으로 Jsoup으로 읽어와서 categories에 전체 정보 저장하기

//실제 CategoryTask의 주요 작업
@Override
protected Void doInBackground(String... strings) {
String rssURL = strings[0];

try {
Document document = Jsoup.connect(rssURL).get();
Elements elements = document.select("table.common_table tbody tr");
Category category;

//한줄씩 가져와서 Category에 저장
for (Element element : elements) {
//title
category = new Category();
category.setTitle(element.select("th").get(0).text());
category.setUrl(element.select("a").get(0).text());

//한줄을 다 읽었으면 categories에 다시 보관
categories.add(category);
}

Log.d("목록 다 읽었는지 찍어볼게요",categories.toString());
} catch (IOException e) {
e.printStackTrace();
}

return null;
}


<아래 Run을 누르면 이런식으로 로그가 찍혀있는걸 볼 수 있다



이번 예제의 가장 중요한 부분이라고 볼 수 있다

실제로 rssURL의 html소스를 분석해서 Jsoup으로 rssURL을 연결하고 select로 원하는 태그만 읽어와서 값을 얻어내는 부분으로

html소스보기를 통해 내가 원하는 값이 있는 태그를 분석할 줄 알아야 한다 (매우매우 중요)

이때 Elements, Element는 모두 Jsoup의 클래스를 import 해야한다


먼저 우리가 3번에서 new CategoryTask().execute(rssURL) 를 하여 rssURL을 가지고 이 클래스에 들어왔다

doInBackground로 전달되는 값은 매개변수에 있는 strings에 순서대로 들어오는데 우리는 String을 하나만 넘겼다(rssURL)

따라서 rssURL값은 아래와 같이 이동되어 CategoryTask안에서 쓸 수 있도록 rssURL을 다시 저장했다


rss메인 주소값을 가진 rssURL    String rssURL = "http://news.jtbc.joins.com/Etc/RssService.aspx;

이 rssURL을 CategoryTask를 전달하여 실행    new CategoryTask().execute(rssURL);

doInBackground의 전달받은 값은 strings에 순차적으로 저장된다    strings[0]에 "http://news.jtbc.joins.com/Etc/RssService.aspx"값이 들어있는 것

클래스내부에 rssURL을 만들어 전달받은 rssURL을 저장한다 String rssURL = strings[0];




이제 doInBackground에서 rssURL과 Jsoup을 이용해서 URL에 접근하고 값을 얻어내자


rssURL에 접근해 html문서를 읽어오고    Document document = Jsoup.connect(rssURL).get()

밖의 태그를 먼저 제거한다고 생각하자    Elements elements = document.select("table.common_table tbody tr")


여기까지 하면 elements에는 위의 이미지에서

<tr>

<th>제목</th>

<td><a href="~~~~">링크</a></td>

</tr>

이렇게 생긴게 다 들어가있다고 생각하면 된다

그럼 tr 하나씩 돌아가면서 그 안에 있는 th의 제목 가져오고 a의 주소를 가져오면 우리가 원하는 값만 가져오는 것이다


이제 한줄씩 가져와서 Category에 저장하고 이 값을 categories에 차곡차곡 담자


<tr>를 하나씩 돌아가면서 접근하자    for (Element element : elements) {

저장할 Category 객체를 할당하자    category = new Category()

category에 title 값을 저장하자     category.setTitle(element.select("th").get(0).text()

category에 url 값을 저장하자     category.setURL(element.select("a").get(0).text()

한줄의 정보를 모두 저장한 category를 categories에 추가한다    categories.add(category)


여기서 쓰는 select는 반환형이 Elements로 되어있다

그래서 <th>나 <a>를 select하여 그안의 제목 값이나 주소값을 가져올때 사이에 get(0)을 써주는 것이다

현재 <tr>안에는 <th>나 <a> 둘다 태그가 하나씩 밖에 없으므로 0번째 값을 가져오면 우리가 원하는 값인 것이다

(만약 여러개 있었다면 해당 index를 get(index)해준다)


6. categories를 받아서 탭으로 만드는 TabPagerAdapter 클래스를 만든다

public class TabPagerAdapter extends FragmentStatePagerAdapter {
private int tabCount;
private List<Category> categories;

public TabPagerAdapter(FragmentManager fm, int tabCount, List<Category> categories) {
super(fm);
this.tabCount = tabCount;
this.categories = categories;
}

@Override
public Fragment getItem(int position) {
return null;
}

@Override
public int getCount() {
return 0;
}

@Nullable
@Override
public CharSequence getPageTitle(int position) {
return super.getPageTitle(position);
}
}


이제 데이터는 categories에 저장되어 있으므로 이걸로 탭을 만드는 클래스를 만들어보자

가장 먼저 FragmentStatePagerAdapter를 상속받아주고 tabCount와 categories 선언한다

tabCount와 categories 전부 받는 생성자를 만들고 getPageTitle메소드를 오버라이딩 한다


getItem과 getCount는

public class TabPagerAdapter extends FragmentStatePagerAdapter {

}

여기까지만 쓰면 빨간줄 뜨니까 Alt+Enter로 오버라이딩 해주면 된다


7. CategoryTask에서 만든 TabPagerAdapter를 사용한다


//doInBackground가 끝나면 실행
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);

//Category 개수만큼 반복
for (Category c : categories) {
tabLayout.addTab(tabLayout.newTab());
}

TabPagerAdapter adapter = new TabPagerAdapter
(getSupportFragmentManager(),tabLayout.getTabCount(),categories);

mViewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(mViewPager);

}


CategoryTask에 onPostExecute를 오버라이딩 한다


Category 개수만큼    for (Category c : categories) {

탭을 추가해주고    tabLayout.addTab(tabLayout.newTab())


TabPagerAdapter adapter를 만들어서 mViewPager에 어뎁터를 붙인다    mViewPager.setAdapter(adapter)


개인적인 생각으로는 TabLayout을 사용하면 탭이 위에 순서대로 나오고 이 탭을 눌렀을 때의 화면이 나와야 하기 때문에

이 탭을 관리하는게 TabLayout, 탭을 눌렀을 때 나오는 화면을 관리하는게 ViewPager인 것 같다


그래서 TabLayout에 ViewPager를 연결해주고    tabLayout.setupWithViewPager(mViewPager)

이 ViewPager가 실질적으로 보여줄 화면(View)은 Adapter로 다시 연결해주는 것 같다    mViewPager.setAdapter(adapter)


8. mViewPager가 실질적으로 보여줄 화면을 Fragment로 만든다



<MainActivity>


/**

 * A simple {@link Fragment} subclass.

 * Activities that contain this fragment must implement the

 * {@link JTBCFragment.OnFragmentInteractionListener} interface

 * to handle interaction events.

 * Use the {@link JTBCFragment#newInstance} factory method to

 * create an instance of this fragment.

 */

public class JTBCFragment extends Fragment {

    // TODO: Rename parameter arguments, choose names that match

    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER

    private static final String ARG_PARAM1 = "param1";

    private static final String ARG_PARAM2 = "param2";


    // TODO: Rename and change types of parameters

    private String mParam1;

    private String mParam2;


    private OnFragmentInteractionListener mListener;


    public JTBCFragment() {

        // Required empty public constructor

    }


    /**

     * Use this factory method to create a new instance of

     * this fragment using the provided parameters.

     *

     * @param param1 Parameter 1.

     * @param param2 Parameter 2.

     * @return A new instance of fragment JTBCFragment.

     */

    // TODO: Rename and change types and number of parameters

    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) {

        // Inflate the layout for this fragment

        return inflater.inflate(R.layout.fragment_jtbc, container, false);

    }


    // TODO: Rename method, update argument and hook method into UI event

    public void onButtonPressed(Uri uri) {

        if (mListener != null) {

            mListener.onFragmentInteraction(uri);

        }

    }


    @Override

    public void onAttach(Context context) {

        super.onAttach(context);

        if (context instanceof OnFragmentInteractionListener) {

            mListener = (OnFragmentInteractionListener) context;

        } else {

            throw new RuntimeException(context.toString()

                    + " must implement OnFragmentInteractionListener");

        }

    }


    @Override

    public void onDetach() {

        super.onDetach();

        mListener = null;

    }


    /**

     * This interface must be implemented by activities that contain this

     * fragment to allow an interaction in this fragment to be communicated

     * to the activity and potentially other fragments contained in that

     * activity.

     * <p>

     * See the Android Training lesson <a href=

     * "http://developer.android.com/training/basics/fragments/communicating.html"

     * >Communicating with Other Fragments</a> for more information.

     */

    public interface OnFragmentInteractionListener {

        // TODO: Update argument type and name

        void onFragmentInteraction(Uri uri);

    }

}


[삭제 후 메인]

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;

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) {
return inflater.inflate(R.layout.fragment_jtbc, container, false);
}
}

9. TabPagerAdapter에 실제로 탭 제목을 표시하고 누르면 JTBCFragment로 연결되게 한다

public class TabPagerAdapter extends FragmentStatePagerAdapter {
private int tabCount;
private List<Category> categories;

public TabPagerAdapter(FragmentManager fm, int tabCount, List<Category> categories) {
super(fm);
this.tabCount = tabCount;
this.categories = categories;
}

@Override
public Fragment getItem(int position) {
Category category = categories.get(position);
Fragment fragment = JTBCFragment.newInstance(category.getTitle(),category.getUrl());
return fragment;
}

@Override
public int getCount() {
return tabCount;
}

@Nullable
@Override
public CharSequence getPageTitle(int position) {
return categories.get(position).getTitle();
}
}

getPageTitle메소드는 각 탭의 제목을 표시하게 해준다

탭은 0번째부터 시작하며 여기에선 position으로 몇번째 탭의 제목인지가 위치가 넘어오므로

categories.get(position)을 이용하여 0번째 탭에서는 categories[0]번째 정보("속보","http://~~.xml")를 사용하겠다


여기에서 return값으로 주는 문자열이 탭에 표시되는 제목이므로 categories.get(position).getTitle()로

0번째 탭에는 "속보"가 제목으로 표시되고 1번째 탭에는 "정치" 순으로 제목이 표시된다


getItem은 탭 메뉴를 눌렀을 때 처리되는 내용을 쓴다

이때 몇번째 탭을 눌렀는지가 position으로 넘어오기 때문에 이를 이용하여 categories의 정보를 활용한다


0번째 탭을 누르면 0번째 탭의 title과 url을 전달받아 만든 JTBCFragment를 띄운다는 내용이다

Fragment fragment = JTBCFragment.newInstance(category.getTitle(),category.getUrl());



여기까지 진행했다면 실행결과를 아래와 같이 얻을 수 있다



각 탭을 누르면 밑에가 움직인다

아직까지는 탭을 눌렀을때 JTBCFragment화면이 나오게만 한 것이지 JTBCFragment에 뉴스가 찍히게 코드를 수정한게 아니기 때문에

탭에 제목만 표시되고 이 탭을 누르면 밑에 Hello blank fragment만 나오게 된다


이제 다음 예제를 통해 실제로 탭을 눌렀을 때 해당 rss의 뉴스까지 나오게 할 예정이다

기본은 예제 2번의 Jsoup을 connect하고 select하여 html 태그를 잘라내는것과 동일하며

xml에서 뉴스가 보여질 화면을 꾸미고 이 xml을 이용해 View 클래스를 생성하고 View에 붙일 Adapter클래스를 만든다

JTBC의 RSS 서비스 링크

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


<RSS 서비스 메인>


<속보 RSS xml>


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

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

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




[예제 따라하기 1 ~ 기본 액티비티 및 Dependencies 설정]


1. Tabbed Activity - Action Bar Tabs (with ViewPager)로 액티비티 설정하여 만든다




2. 기본으로 만들어진 프로젝트에 있는 주석과 안쓰는 부분, fragment xml을 삭제한다


[MainActivity]


- mSectionsPagerAdapter 선언 삭제 ( 빨간색 뜨는 부분 다 삭제)

- 메소드 onCreate 외에 다 삭제

- onCreate메소드 안의 Adapter부분, Listener부분, FloatingActionButton부분 삭제


public class MainActivity extends AppCompatActivity {


    /**

     * The {@link android.support.v4.view.PagerAdapter} that will provide

     * fragments for each of the sections. We use a

     * {@link FragmentPagerAdapter} derivative, which will keep every

     * loaded fragment in memory. If this becomes too memory intensive, it

     * may be best to switch to a

     * {@link android.support.v4.app.FragmentStatePagerAdapter}.

     */

    private SectionsPagerAdapter mSectionsPagerAdapter;


    /**

     * The {@link ViewPager} that will host the section contents.

     */

    private ViewPager mViewPager;


    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);


        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

        setSupportActionBar(toolbar);

        // Create the adapter that will return a fragment for each of the three

        // primary sections of the activity.

        mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());


        // Set up the ViewPager with the sections adapter.

        mViewPager = (ViewPager) findViewById(R.id.container);

        mViewPager.setAdapter(mSectionsPagerAdapter);


        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);


        mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));

        tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));


        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);

        fab.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)

                        .setAction("Action", null).show();

            }

        });


    }



    @Override

    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.

        getMenuInflater().inflate(R.menu.menu_main, menu);

        return true;

    }


    @Override

    public boolean onOptionsItemSelected(MenuItem item) {

        // Handle action bar item clicks here. The action bar will

        // automatically handle clicks on the Home/Up button, so long

        // as you specify a parent activity in AndroidManifest.xml.

        int id = item.getItemId();


        //noinspection SimplifiableIfStatement

        if (id == R.id.action_settings) {

            return true;

        }


        return super.onOptionsItemSelected(item);

    }


    /**

     * A placeholder fragment containing a simple view.

     */

    public static class PlaceholderFragment extends Fragment {

        /**

         * The fragment argument representing the section number for this

         * fragment.

         */

        private static final String ARG_SECTION_NUMBER = "section_number";


        public PlaceholderFragment() {

        }


        /**

         * Returns a new instance of this fragment for the given section

         * number.

         */

        public static PlaceholderFragment newInstance(int sectionNumber) {

            PlaceholderFragment fragment = new PlaceholderFragment();

            Bundle args = new Bundle();

            args.putInt(ARG_SECTION_NUMBER, sectionNumber);

            fragment.setArguments(args);

            return fragment;

        }


        @Override

        public View onCreateView(LayoutInflater inflater, ViewGroup container,

                                 Bundle savedInstanceState) {

            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            TextView textView = (TextView) rootView.findViewById(R.id.section_label);

            textView.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER)));

            return rootView;

        }

    }


    /**

     * A {@link FragmentPagerAdapter} that returns a fragment corresponding to

     * one of the sections/tabs/pages.

     */

    public class SectionsPagerAdapter extends FragmentPagerAdapter {


        public SectionsPagerAdapter(FragmentManager fm) {

            super(fm);

        }


        @Override

        public Fragment getItem(int position) {

            // getItem is called to instantiate the fragment for the given page.

            // Return a PlaceholderFragment (defined as a static inner class below).

            return PlaceholderFragment.newInstance(position + 1);

        }


        @Override

        public int getCount() {

            // Show 3 total pages.

            return 3;

        }

    }

}



[삭제 후 메인]
public class MainActivity extends AppCompatActivity {
private ViewPager mViewPager;
private TabLayout tabLayout;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

mViewPager = (ViewPager) findViewById(R.id.container);

//TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
//전역변수로 tabLayout을 선언(위에 private TabLayout tabLayout;)
tabLayout = (TabLayout) findViewById(R.id.tabs);

}
}

3. activity_main.xml에서 안쓰는 요소 삭제하기


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

<android.support.design.widget.CoordinatorLayout 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:id="@+id/main_content"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true"

    tools:context=".MainActivity">


    <android.support.design.widget.AppBarLayout

        android:id="@+id/appbar"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:paddingTop="@dimen/appbar_padding_top"

        android:theme="@style/AppTheme.AppBarOverlay">


        <android.support.v7.widget.Toolbar

            android:id="@+id/toolbar"

            android:layout_width="match_parent"

            android:layout_height="?attr/actionBarSize"

            android:layout_weight="1"

            android:background="?attr/colorPrimary"

            app:layout_scrollFlags="scroll|enterAlways"

            app:popupTheme="@style/AppTheme.PopupOverlay"

            app:title="@string/app_name">


        </android.support.v7.widget.Toolbar>


        <android.support.design.widget.TabLayout

            android:id="@+id/tabs"

            android:layout_width="match_parent"

            android:layout_height="wrap_content">


            <android.support.design.widget.TabItem

                android:id="@+id/tabItem"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="@string/tab_text_1" />


            <android.support.design.widget.TabItem

                android:id="@+id/tabItem2"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="@string/tab_text_2" />


            <android.support.design.widget.TabItem

                android:id="@+id/tabItem3"

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="@string/tab_text_3" />


        </android.support.design.widget.TabLayout>

    </android.support.design.widget.AppBarLayout>


    <android.support.v4.view.ViewPager

        android:id="@+id/container"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        app:layout_behavior="@string/appbar_scrolling_view_behavior" />


    <android.support.design.widget.FloatingActionButton

        android:id="@+id/fab"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="end|bottom"

        android:layout_margin="@dimen/fab_margin"

        app:srcCompat="@android:drawable/ic_dialog_email" />


</android.support.design.widget.CoordinatorLayout>



[삭제 후 activity_main.xml]

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/appbar_padding_top"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_weight="1"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title="@string/app_name">

</android.support.v7.widget.Toolbar>

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.design.widget.AppBarLayout>

<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

4. xml 읽어올 때 사용할 라이브러리 Jsoup을 추가



왼쪽의 안드로이드 프로젝트의 app를 우클릭하고 F12하면 Open Module Settings가 뜬다


여기에서 Dependencies 탭메뉴로 이동해서 오른쪽에 +로 Libraby 검색을 하고 jsoup 입력하고 엔터 누르면 알아서 찾아준다

다른 jsoup이 선택되지 않도록 org.jsoup:jsoup으로 시작하는게 맞는지 확인


5. AndroidManifest.xml에 인터넷 사용권한 추가

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="패키지이름">

<uses-permission android:name="android.permission.INTERNET"/>

<application
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"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

예제 2에서는 탭 메뉴를 만드는 부분을 따라한다