애드몹을 쓰던 중, 잘만 나오던 광고가 아래 오류가 어느 순간 갑자기 나오기 시작하면서 테스트 배너 광고가 나오지 않았습니다. 그래서 어떤 오류인가 했더니 아래와 같은 오류였네요.
광고 요청은 성공(그러니까 구현에는 문제가 없다는것)했는데, 인벤토리에 광고가 없어서 광고를 못보여준다고합니다. 어, 하지만 스토어에 있는 내 앱에는 광고가 잘 나오는데요? 저의 증상은 출시 앱에는 광고가 잘 나오고, 오히려 테스트 ID를 써서 앱 개발할때에는 테스트 광고가 나오지 않는 것이었습니다.
구글링을 해보니 광고가 잘 나오다가 이와 같은 증상을 겪은 사람들의 공통점이 어느 순간 갑자기 안나온다는 것이었습니다. 몇가지 조치할 수 있는 방법이 있는데 한번 확인해보세요.
1. 구글 플레이 스토어에 구글 광고가 포함되어있다고 체크했는지 확인
구글 플레이 콘솔 -> 출시 앱 선택 -> 왼쪽 하단 앱 콘텐츠 선택 -> 중간쯤에 광고란 확인
2. 부정클릭이 발생했는지 확인. 이 경우 30일간 광고가 일시 정지되거나 영구정지 될 수도 있다고 하네요. 어떤 경우라고 자신의 광고를 클릭하지 맙시다.
3. app-ads.txt를 추가하고 테스트 기기 등록했는지 확인
저의 경우는 3번이었습니다. 갑자기 안나온것이라 생각했는데, 기억을 더듬어 보니 app-ads.txt를 추가한 이후에 테스트 광고가 안나오는 것이었어요. 역시 나만 갑자기라고 생각했나봐요..
이 방법은 바로 적용이 가능한 방법입니다. 저는 바로 확인을 원하기 때문에 2번, 테스트 기기를 등록하는 방법을 사용할 것입니다.
그래서 이번 포스팅은 안드로이드 테스트 디바이스를 설정하는 방법에 대한 것입니다. 이때 테스트 광고 ID를 쓰는 것이 아니고 발급받은 ID를 사용해야한다는 점입니다.
아래의 코드를 추가하세요.
MobileAds.initialize(this);
//앱 출시시 반드시 주석 처리
List<String> testDeviceIds = Arrays.asList("Your Device ID");
RequestConfiguration configuration =
new RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build();
MobileAds.setRequestConfiguration(configuration);
//앱 출시시 반드시 주석 처리
Arrays.asList에는 자신의 고유 Device ID를 기재해주어야합니다. 어떻게 아냐구요? 안드로이드 스튜디오 Logcat에 나와있습니다.
I/Ads: Use RequestConfiguration.Builder.setTestDeviceIds(Arrays.asList("33BE2250B43518CCDA7DE426D04EE231"))
to get test ads on this device."
Logcat에서 RequestConfiguration을 검색하여 찾아보세요.
다음으로 중요한 것은 Test 광고 ID를 사용하는 것이라고 했죠? adUnitId를 발급받은 광고 ID로 바꿔주세요.
2. 아까 다운 받은 그 폴더를 설정해주고 Finish를 누르면 됩니다. 아래 사진에서는 Module Name이 :nativetemplate이지만 :nativetemplates로 해주셔야해요. 저는 어떤 이유인지는 모르겠지만 Finish가 활성화되어있지 않고 이 방법으로 모듈을 Import할 수 없었습니다.
다른 방법으로 Import
1. 아래와 같이 다운받은 폴더를 같은 프로젝트에 둡니다.
2. settigs.gradle 파일에 nativetemplates를 추가시켜줍니다.
include ':app',':nativetemplates'
3. 한가지 추가적으로 nativetemplates의 build.gradle의 버전을 맞춰줘야합니다. 자신의 기존 프로젝트와 같이 sdk버전을 맞춰주시면 됩니다. 그리고 sync를 눌러주세요.
요즘 어플리케이션들을 보면 아래로 당겨서 화면을 새로 로딩하는 기능이 많죠. 이런 기능을 담당하는 것이 SwipeRefreshLayout입니다. 그래서 아래로 당기면 아래처럼 로딩하는 화면(빙글 빙글 돌아가는 로딩화면)이 나온 이후에 로딩이 완료되면 어플리케이션의 정보를 갱신해줍니다.
SwipreRefreshLayout을 사용하기 위해서는 build.gradle 파일에 아래처럼 종속 항목을 명시해줍니다.
누가봐도 매우 간단한 코드네요. SwipeRefreshLayout에 OnRefreshListener를 추가해주면 새로고침이 시작되었을때 onRefresh 메소드가 호출이 됩니다. 이때 다시 로드하는 코드를 추가하여 주고 setRefreshing에 false를 전달하여 로딩중인 화면을 없애주면 됩니다.
실행하면 화면이 로딩된 이후 TextView에서 로딩이 완료되었다는 메시지와 함께 Refreshing을 중지합니다.
만약 쓰레드를 이용하면 onRefresh에 쓰레드를 실행하여 Handler를 통해 완료 메시지를 받으면 여기서 setRefreshing을 false로 전달해주면 됩니다.
여기서 Calendar 객체를 현재시간으로 미리 설정해두고, set 메소드로 timePicker에서 설정된 시간과 분으로 설정시키는 것입니다. 이때 시간과 분을 TimePicker에서 얻어오려면 @RequiresApi(api = Build.VERSION_CODES.M) 를 메소드위에 추가시켜야합니다.
이제 AlarmManager를 가져옵니다. Intent는 수신자 클래스를 전달하게 됩니다. 여기서는 AlarmReceiver라는 수신자이며 밑에서 이 Receiver를 구현하게 될겁니다. 그리고 PendingIntent를 얻어와서 setRepeating 메소드로 정확한 시간에 알람을 설정시켜줍니다.
여기서 setRepeating메소드는 알람을 반복시키는 메소드입니다. 여기서는 AlarmManager.INTERVAL_DAY를 사용하였고, 이것은 매일 알람이 울릴것을 명시해준것입니다.
굳이 정확한 시간에 알람을 울리지 않을 경우에는 setInexactRepeating 메소드를 사용할 수도 있습니다. 실제로 안드로이드 개발문서에는 이 메소드를 사용하라고 권고하고 있네요.
이제 알람이 발생되면 AlarmReceiver의 onReceive 메소드가 호출되게 됩니다. 이때 아까 우리가 MainActivity에서 넘긴 PendingIntent를 requestCode를 통해서 얻어올 수가 있습니다. 이 requestCode는 알람을 설정했던 것과 같은 코드를 사용해야합니다. 여기서는 1입니다.
오레오(Oreo)버전 이상부터는 NotificationChannel을 명시해주어야합니다. 그 코드도 적용되어 있습니다.
대충 완성하게 되면 아래와 같은 모습입니다. 앱 디자인은 개나 줘버린 모습이지만 구현이 제대로 되었나 확인만 하는 용도입니다.
그리고 원하는 시간에 알람을 저장시켜줍니다. 그래서 9시 15분에 알람을 설정합니다. 그리고 그 시간에 울리는 지 잠깐 기다려주겠습니다. 이때 9시 14분이었고, 1분만 기다리면 됩니다.
다음은 알람이 발생되어 Notification이 발생된 화면입니다.
분명 9시 15분이었는데, 9시 16분에 알람이 발생되었군요. 정확한 시간에 알람이 딱 울리지는 않는 것 같습니다. 제 생각에는 setRepeating이 setInexactRepeating메소드보다 더 정확한 시간에 발생시켜주는 것으로 확인이 됩니다.
알람 취소
그리고 알람을 취소하려면 아래와 같이 코드를 추가하면 되는데요. 위의 코드는 없지만 만약 알람 취소버튼을 추가하고 그 버튼이 눌리면 alarmManager의 cancel 메소드를 이용하면 됩니다.
위의 xml을 파싱하는 방법은 아래의 코드 예제로 이해가 가능합니다. 우선 두가지 클래스 XmlPullParser와 XmlPullParserFactory가 필요합니다. 그리고 예외를 핸들링하려면 XmlPullParserException가 필요합니다. 이것들을 우선 import하고 아래의 코드를 통해 봅시다.
우선 웹에서 응답을 가져오는 코드이기 때문에 Thread를 사용하여야합니다. 아래의 코드는 run을 오버라이드한 코드입니다.
@Override
public void run() {
try {
URL url = new URL(requestUrl);
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
parser = xmlPullParserFactory.newPullParser();
InputStream is = url.openStream();
parser.setInput(new InputStreamReader(is, "UTF-8"));
String tagName="";
//event type얻어오기
int eventType = parser.getEventType();
//xml문서의 끝까지 읽기
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
//태그가 시작
case XmlPullParser.START_TAG:
tagName=parser.getName();
if (parser.getName().equals("busLocationList")) {
//객체 생성
BusLocation location=new BusLocation();
}
break;
//태그의 끝
case XmlPullParser.END_TAG:
if (parser.getName().equals("busLocationList")) {
//객체를 리스트에 추가
locationList.add(location);
}
break;
//태그 안의 텍스트
case XmlPullParser.TEXT:
switch(tagName) {
case "endBus":{
location.endBus=parser.getText();
break;
}
case "lowPlate":{
location.lowPlate=parser.getText();
break;
}
case "plateNo":{
location.plateNo=parser.getText();
break;
}
case "plateType":{
location.plateType=parser.getText();
break;
}
case "remainSeatCnt":{
location.remainSeatCnt=parser.getText();
break;
}
case "routeId":{
location.routeId=parser.getText();
break;
}
case "stationId":{
location.stationId=parser.getText();
break;
}
case "stationSeq":{
location.stationSeq=parser.getText();
break;
}
}
break;
}
//다음으로 이동
eventType = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
}
while을 통해서 xml의 끝까지 돌아야하는데, 이때 XmlPullParser의 END_DOCUMENT를 통해서 알 수 있습니다. 그리고 그 외 시작 태그를 알 수 있는데 XmlPullParser의 START_TAG로 알 수 있습니다. 비슷하게 태그가 닫히는 부분은 END_TAG로 알 수 있습니다. 태그의 이름은 getName으로 알 수 있지요.
태그안의 내용은 XmlPullParser의 TEXT로 알 수 있고, 실제 내용은 getText 메소드로 알 수 있습니다. 위 코드는 응답받은 xml을 busLocationList 항목의 리스트로 저장하는 코드입니다.
로그인을 하고 난 이후에 맨 위 상단에 데이터찾기 -> 데이터목록 을 찾아 클릭 한 후 검색 조건을 지정하여 API를 검색할 것입니다. 이때 우리가 사용할 API는 서울시 버스 도착 정보 조회입니다.
그냥 검색하면 너무 많은 버스 관련 API들이 검색되기 때문에 제공기관별검색을 눌러 자치행정기관 - 서울특별시 - 서울특별시를 선택해줍시다.
그 후 검색창에 "버스 조회" 를 검색하면 중간 부분에 오픈 API가 검색이 되는데 버스도착정보조회 서비스가 우리가 사용할 API가 됩니다.
눌러서 들어가보면 4개의 API를 사용할 수가 있는 것을 볼 수 있습니다.
API에 대한 간단한 설명을 아래의 표로 정리하였습니다.
일련번호
API명(국문)
상세기능명(영문)
상세기능명(국문)
1
버스도착정보조회 서비스
getArrInfoByRouteAllList
경유노선전체정류소별도착예정정보목록조회
2
버스도착정보조회 서비스
getArrInfoByRouteList
정류소노선별도착예정정보목록조회
3
버스도착정보조회 서비스
getLowArrInfoByRouteList
정류소의특정노선교통약자용도착예정정보목록조회
4
버스도착정보조회 서비스
getLowArrInfoByStIdList
정류소별교통약자용도착예정정보목록조회
3. 활용신청
검색했다고 그냥 쓸 수 있는 것은 아니고 활용 신청하면 API를 사용할 수 있습니다. 오른쪽 활용신청을 클릭해줍니다.
여기서 활용목적은 앱개발이며 간단히 어떻게 사용할 것인지만 적어주면 됩니다. 밑에 시스템 유형은 일반으로 지정해주면 됩니다.
그리고 더 아래에는 일일 트래픽 1000을 허용하며 추후에 신청하여 늘릴 수 있다고 합니다.
이렇게 신청했다고 바로 API가 사용가능한 상황은 되지 않고 1~2시간 정도가 지나야 API를 사용할 수 있습니다.
4. API 테스트 해보기
우선 웹사이트상에서 미리보기를 확인할 수 있습니다. 저는 특정 노선 ID를 통해서 경우노선 전체를 확인해보고자 합니다. 110A 고려대 버스의 노선 정보가 궁금합니다. 이때 버스 노선 ID는 100100016입니다. 버스 노선 정보 역시 공공데이터포털에서 잘 검색하면 엑셀표로 된 것이 나오니까 찾아보시고 확인해보세요. 혹은 아래의 파일을 참고하시기 바랍니다.
이것을 샘플데이터에 입력해주고 미리보기를 눌러보면 xml으로 정보를 잘 얻어오는 것을 확인해볼 수 있습니다.
1.5버전부터 추가된 AsyncTask는 개발자가 사용자 인터페이스에 대한 비동기적인 작업을 하게 해준다.
UI스레드에서 AsyncTask의 execute를 호출하면 되는데 이 AsyncTask를 물론 구현해주어야한다.
아래는 AsyncTask를 사용한 현재 시간 타이머
public class MainActivity extends AppCompatActivity {
private TextView time;
private int count=0;
private SimpleDateFormat format=new SimpleDateFormat("yyyy년 MM월 dd일 a hh:mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
time=(TextView)findViewById(R.id.time);
new AsyncCounterTask().execute();
}
class AsyncCounterTask extends AsyncTask<Void, Void, Integer> {
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.e("__LOG__","onPreExecute");
}
@Override
protected Integer doInBackground(Void... integers) {
while(count<100){
try {
Thread.sleep(1000);
}catch(InterruptedException e){}
//publishProgress 호출 후 바로 onProgressUpdate가 호출됨
publishProgress();
}
Log.e("__LOG__","doInBackground End");
return count;
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
Log.e("__LOG__","onProgressUpdate");
long now = System.currentTimeMillis();
time.setText(format.format(new Date(now)));
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
Log.e("__LOG__","onPostExecute");
}
}
}
AsyncTask<Params, Progress, Result>처럼 Generic을 사용한다.
Params : 실행 시 작업에 전달되는 값
Progress : 작업의 진행정도를 나타내는 값
Result : 작업의 결과를 나타내는 값
모두 사용할 필요는 없고 필요에 따라 사용하면 된다.
AsyncTask는 어떤 식으로 동작하게 될까? 다음은 몇안되는 AsyncTask의 메소드들인데 그냥 알아두면 편하다.
실행 순서는 onPreExecute -> doInBackground -> [ publishProgress -> onProgressUpdate ] ... -> onPostExecute
(위의 엑티비티를 실행하고 로그를 보면 알 수 있다. 아 참고로 접두사 pre는 ~전에, post는 ~ 후에 라는 점을 알면 기억하기 쉽겠다)
onPreExecute와 onPostExecute는 doInBackground 실행전, 실행후 한번씩만 실행이 된다. 그래서 doInBackground에서 반환하는 값이 onPostExecute에 전달이 된다.
publishProgress를 호출하면 onProgressUpdate가 실행이 되는데 여기서 UI를 업데이트할 수 있다.
여기서 중요한것은 doInBackground는 무조건 Override해야한다. (필자는 doInBackground가 쓰레드의 run과 같은 메소드와 비슷하다고 생각, 물론 자세히 까보면 다르겠지만....)
아는 사람은 알겠지만 SimpleDateFormat을 사용하여 날짜와 시간에 대한 format을 지정하고 System.currentMillis()를 이용하여 시스템의 현재 시간을 밀리초 단위(정확히 말하면 1970년 1월 1일부터 지금까지 흐른 밀리초)로 구하여 Date의 인자로 집어넣는다.
이것을 format 형식으로 출력하려면 SimpleDateFormat의 format 메소드의 인자로 쏙 넣으면 되쥬.
위에서 정의한 layout을 inflate하는 Fragment1과 Fragment2입니다. 역시 아주 간단하게 onCreateView만 정의하였습니다.
Fragment1
package com.example.minkug.reakwonapp;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class Fragment1 extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1,container,false);
}
}
Fragment2
package com.example.minkug.reakwonapp;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class Fragment2 extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2,container,false);
}
}
4) PagerAdapter
가장 중요한 PagerAdapter를 구현할 차례입니다. 우서 가장 먼저할 것은 FragmentStatePagerAdapter를 상속을 받아야하지요.
이제 1개의 생성자와 2개의 메소드만 Overriding하면 기본적인 Adapter 작성은 끝이납니다.
4-1) FragmentManager를 받는 생성자 구현
4-2) 실제 Fragment를 반환하는 getItem 구현
4-3) 페이지의 개수를 반환하는 getCount구현
여기서는 Adapter가 생성될때 fragments라는 list에 우리가 위에서 정의한 2개의 프래그먼트를 추가합니다. 그렇게 되면 getItem은 리스트의 i번재 아이템을 반환하면 될 것이고, getCount 역시 리스트의 사이즈를 반환하면 되기 때문에 구현이 조금 더 간단해집니다.
package com.example.minkug.reakwonapp;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import java.util.ArrayList;
import java.util.List;
public class PagerAdapter extends FragmentStatePagerAdapter {
List<Fragment> fragments=new ArrayList<>();
public PagerAdapter(FragmentManager fm) {
super(fm);
fragments.add(new Fragment1());
fragments.add(new Fragment2());
}
@Override
public Fragment getItem(int i) {
return fragments.get(i);
}
@Override
public int getCount() {
return fragments.size();
}
}
5) MainActivity
MainActivity가 코드가 조금 길어보이기는 하나 별다른 것이 없습니다. 우선 ViewPager, TabLayout을 얻어오고, TabLayout에는 Tab을 설정해주네요.
조금 눈여겨봐야될 것은 아래 2개의 Listener입니다.
5-1) viewPager.addOnPageChangeListener : Pager가 변경될때 발생하는 이벤트인데, 이때는 TabLayout의 탭까지 변경을 해줘야합니다. Pager를 슬라이딩하여 바꾼다고 하더라도 이 동작을 처리하지 않으면 Tab은 같이 변경되지 않습니다.
5-2) tabLayout.addOnTabSelectedListener : 마찬가지로 tab이 눌려졌다면 page도 같이 변경해주어야합니다. 탭이 선탤될때 발생하는 이벤트는 onTabSelected이며 tab이라는 인자로 선택된 tab의 위치를 알 수 있습니다. 이것을 이용해서 pager를 선택하면 되는 것이죠.
Adpater안에는 반드시 ViewHolder가 있어야하는데 이 ViewHolder는 RecyclerView의 개별 Item의 대한 view를 가지고 있습니다. item_recycler_view가 이 ViewHolder의 View로 생성되며 필요에 따라 custom할 수 있겠죠?
비록 우리는 item_recycler_view에 TextView만 추가했지만 뭐 ImageView도 추가할 수 있고 말이죠.
5-2) 메소드 Overriding
이제 필수적으로 Overriding할 메소드는 다음과 같습니다.
- onCreateViewHolder: ViewHolder를 콘텐츠를 표시하기 위해 사용하는 뷰를 설정하는데, 우리는 이전에 item_recycler_view를 만들었으니 이것을 사용합니다.
- onBindViewHolder: ViewHolder를 binding합니다. 여기서 ViewHolder의 각각 View를 설정할 수 있습니다.
- getItemCount : item의 갯수를 반환합니다. 대부분은 List를 사용하기 때문에 이 List의 size를 반환합니다.
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private List<String> itemList=new ArrayList<>();
public RecyclerAdapter(){
itemList.clear();
for(int i=0;i<10;i++){
itemList.add(i+"번째 아이템");
}
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder{
private TextView item;
public ViewHolder(View itemView){
super(itemView);
item=(TextView)itemView.findViewById(R.id.item);
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view=LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_recycler_view,viewGroup,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int i) {
ViewHolder holder=(ViewHolder)viewHolder;
holder.item.setText(itemList.get(i));
}
@Override
public int getItemCount() {
return itemList.size();
}
}
ViewHolder를 정의하는데 여기서는 아까 만들었던 item_recycler_view.xlm의 View를 갖고 있습니다. 이 ViewHolder는 개별 item을 갖고 있는 것이나 같죠. 이 ViewHolder는 onCreateViewHolder에서 객체가 생성됩니다.
생성자에는 itemList를 초기화하는데 이 itemList에서 adapter가 binding할때 아이템을 꺼내옵니다. 우선 10개의 아이템을 추가하고 있네요.
이후 notifyDataSetChanged()를 호출하면 이제 데이터의 Set이 바뀌었다고 다시 RecyclerAdapter의 데이터를 표시하게됩니다.
즉, itemList의 아이템이 생기거나 제거됐다면 이후 notifyDataSetChanged()를 호출하세요.
onBindViewHolder에는 ViewHolder에 있는 TextView의 문자열 설정을 하고 끝나네요.
여기까지 잘따라왔다면 프로젝트 구성은 이렇게 되겠네요.
결과
이벤트 추가
여기까지만 진행하면 조금 심심하겠죠? 이제 아이템이 클릭이 되면 Toast로 클릭되었다는 메시지를 띄어봅시다.
interface를 사용하여 listener를 설정할때가 왔군요.
6) RecyclerAdapter에 OnItemClickListener 설정
여기서는 OnItemClickListener라는 interface를 정의하고 객체를 생성합니다.
onBindViewHolder에서 ViewHolder의 아이템이 클릭되었을때 onItemClickListener의 onClick 메소드를 호출합니다. 이 onClick메소드는 바로 MainActivity에서 구현하거나 객체 생성해서 넘길테지요.
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private List<String> itemList=new ArrayList<>();
public interface OnItemClickListener{
public void onClick(String str);
}
private OnItemClickListener onItemClickListener=null;
public RecyclerAdapter(){
itemList.clear();
for(int i=0;i<10;i++){
itemList.add(i+"번째 아이템");
}
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder{
private TextView item;
public ViewHolder(View itemView){
super(itemView);
item=(TextView)itemView.findViewById(R.id.item);
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view=LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_recycler_view,viewGroup,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int i) {
ViewHolder holder=(ViewHolder)viewHolder;
holder.item.setText(itemList.get(i));
holder.item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(onItemClickListener!=null){
onItemClickListener.onClick(itemList.get(i)+" 클릭!!");
}
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener){
this.onItemClickListener=onItemClickListener;
}
}