캐러셀 구현
캐러셀(Carousel)은 여러 개의 광고를 가로로 스크롤 하여 사용자에게 노출할 수 있는 UI입니다. BuzzAd Android용 SDK를 사용하여 네이티브 2.0 지면에 캐러셀을 생성하고 필수 기능을 구현해 캐러셀을 구현할 수 있습니다. 또한 선택에 따라 구현할 수 있는 부가 기능들을 통해 사용자 경험을 최적화할 수 있습니다.
✏️ 참고
- 모든 예제는 Java로 제공합니다.
- 전체 코드를 확인하려면 샘플 앱을 참고하세요.
캐러셀의 기본 기능 구현하기
광고 할당 받기
네이티브 2.0에서 여러 개의 광고를 할당 받기 위해서는 광고 중복 할당을 제어하는 클래스인 NativeAd2Pool
을 사용해야 합니다.
NativeAd2Pool.init()
을 호출하여 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.
✏️ 참고
- 최대 10개의 광고를 요청할 수 있습니다.
- 1개 이상의 광고가 할당된 경우
onLoaded()
가 호출됩니다. 할당에 성공한 광고의 갯수는 요청한 광고의 갯수보다 적을 수 있습니다.- 최초 광고 할당 요청에 실패했을 때는
onError()
가 호출되고 광고가 노출되지 않습니다. 광고 미할당 시 발생하는AdError
오류 코드에 대한 자세한 내용은 오류 코드가 나타납니다 토픽을 참고하세요.
public class NativeAd2CarouselActivity extends AppCompatActivity {
private final String unitId = App.UNIT_ID_NATIVE_AD;
// 최대 10개의 광고를 요청할 수 있습니다.
private final int REQUEST_AD_COUNT = 5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
initCarousel();
}
private void initCarousel() {
// 광고 중복 할당을 막기 위해 하나의 캐러셀에 하나의 NativeAd2Pool 인스턴스를 생성하여 사용합니다.
NativeAd2Pool carouselPool = new NativeAd2Pool(unitId);
// 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.
carouselPool.init(REQUEST_AD_COUNT, new NativeAd2PoolInitListener() {
@Override
public void onLoaded(int adCount) {
// TODO 할당 받은 광고 갯수(adCount)만큼 아이템 리스트를 만들어 RecyclerView 어댑터를 초기화 합니다.
}
@Override
public void onError(@NonNull AdError adError) {
// TODO 광고 레이아웃을 숨김 처리 하는 등 적절한 에러 처리를 할 수 있습니다.
}
});
}
}
RecyclerView 생성하기
BuzzAd Android용 SDK에서는 아이템 뷰를 화면에 가로 레이아웃으로 표시하는 RecyclerView
와 이를 처리하는 어댑터를 사용하여 네이티브 2.0 지면에 캐러셀을 구현할 수 있습니다.
RecyclerView
를 생성하려면 다음의 절차를 따르세요.
캐러셀을 넣고 싶은 영역에
RecyclerView
를 추가한 액티비티 레이아웃 파일(activity_native_carousel.xml
)을 구현하세요.<!-- activity_native_ad2_carousel.xml -->
...
<!-- 광고를 표시할 Carousel -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/carouselRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/view_native_ad2_item" />
...뷰 레이아웃 파일(
view_native_ad2_item.xml
)에RecyclerView
의 각 아이템에 대한 레이아웃을 작성하세요.✏️ 참고
네이티브 2.0 기본 연동에서 사용한NativeAd2View
레이아웃을 그대로 사용할 수 있습니다. 자세한 내용은 샘플 코드를 참고해주세요.<?xml version="1.0" encoding="utf-8"?>
<!-- 네이티브 2.0 기본 예제와 캐러셀 예제에서 함께 사용하는 레이아웃 리소스 파일입니다. -->
<com.buzzvil.buzzad.benefit.nativead2.api.NativeAd2View xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nativeAd2View"
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
</com.buzzvil.buzzad.benefit.nativead2.api.NativeAd2View>RecyclerView
의 각 아이템을 담당할NativeAd2CarouselItem
클래스를 선언하세요.✏️ 참고
RecyclerView
에는 피드 지면으로의 진입점도 추가할 수 있습니다. 그러므로 확장성을 고려하여NativeAd2CarouselItem
을 기반 클래스로 선언하고, 실제 각 아이템은 이를 상속하여 선언하는 것을 권장합니다. 피드 지면으로의 진입점을 추가하는 방법에 대한 자세한 내용은 아래 피드 진입 슬라이드를 참고하세요.다음은 기반 클래스인
NativeAd2CarouselItem
을 선언하고, 이를 확장해서NativeAd2Item
클래스를 선언하는 예시입니다.public class NativeAd2CarouselItem {
// 리사이클러 뷰에서 해당 아이템이 네이티브 2.0인지 타입을 구분하기 위한 클래스입니다.
static class NativeAd2Item extends NativeAd2CarouselItem {
}
// TODO 피드 진입 슬라이드를 위한 클래스 추가
}
RecyclerView 어댑터 구현하기
RecyclerView 어댑터와 ViewHolder를 구현하기 위해 다음의 절차를 따르세요.
NativeAd2View를 바인딩 하는
NativeAd2CarouselViewHolder
를 구현하세요.public class NativeAd2CarouselViewHolder extends RecyclerView.ViewHolder {
private NativeAd2ViewBinder nativeAd2ViewBinder;
public NativeAd2CarouselViewHolder(String unitId, NativeAd2View nativeAd2View) {
super(nativeAd2View);
MediaView mediaView = nativeAd2View.findViewById(R.id.mediaView);
ImageView imageIcon = nativeAd2View.findViewById(R.id.imageIcon);
TextView textTitle = nativeAd2View.findViewById(R.id.textTitle);
TextView textDescription = nativeAd2View.findViewById(R.id.textDescription);
DefaultCtaView ctaView = nativeAd2View.findViewById(R.id.ctaView);
// NativeAd2View와 하위 컴포넌트를 바인드합니다.
nativeAd2ViewBinder = new NativeAd2ViewBinder.Builder()
.nativeAd2View(nativeAd2View)
.mediaView(mediaView)
.iconImageView(imageIcon)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.ctaView(ctaView)
.build(unitId);
}
public void setPool(NativeAd2Pool carouselPool, int adKey) {
// 해당 position(adKey)에 해당하는 NativeAd2ViewBinder가 carouselPool을 사용하도록 합니다.
nativeAd2ViewBinder.setPool(adKey, carouselPool);
}
public void bind(int position) {
// NativeAd2ViewBinder의 bind()를 호출하면 광고 할당 및 갱신이 자동으로 수행됩니다.
nativeAd2ViewBinder.bind(position);
}
public void unbind(int position) {
// unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
nativeAd2ViewBinder.unbind(position);
}
}캐러셀을 처리하기 위한 어댑터(
NativeAd2CarouselAdapter
)를 구현하세요.✅ 중요
광고 중복 할당을 방지하기 위해서는 캐러셀에 포함 된 모든NativeAd2ViewBinder
에 동일한NativeAd2Pool
인스턴스를 설정해주어야 합니다.onBindViewHolder()
에서NativeAd2CarouselViewHolder
에 선언한setPool()
을 통해NativeAd2ViewBinder.setPool()
를 호출하세요.⚠️ 주의
광고의 상태가 여러 뷰와 섞이지 않도록 반드시onViewRecycled()
에서NativeAd2View
를 해제해주어야 합니다.NativeAd2CarouselViewHolder
에 선언한unbind()
을 통해NativeAd2ViewBinder.unbind()
를 호출하세요.public class NativeAd2CarouselAdapter extends RecyclerView.Adapter<NativeAd2CarouselViewHolder> {
private String unitId;
private List<NativeAd2CarouselItem> list;
private NativeAd2Pool carouselPool;
public NativeAd2CarouselAdapter(String unitId, List<NativeAd2CarouselItem> list, NativeAd2Pool carouselPool) {
this.unitId = unitId;
this.list = list;
this.carouselPool = carouselPool;
}
@NonNull
@Override
public NativeAd2CarouselViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
NativeAd2View nativeAd2View = (NativeAd2View) LayoutInflater.from(parent.getContext()).inflate(R.layout.view_native_ad2_item, parent, false);
return new NativeAd2CarouselViewHolder(unitId, nativeAd2View);
}
@Override
public void onBindViewHolder(@NonNull NativeAd2CarouselViewHolder holder, int position) {
// 해당 position에 해당하는 NativeAd2ViewBinder가 carouselPool을 사용하도록 합니다.
holder.setPool(carouselPool, position);
holder.bind(position);
}
@Override
public void onViewRecycled(NativeAd2CarouselViewHolder holder) {
super.onViewRecycled(holder);
// unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
holder.unbind(holder.getAdapterPosition());
}
@Override
public int getItemCount() {
return list.size();
}
}
액티비티 또는 프래그먼트에 결합하기
다음은 위에서 구현한 NativeAd2CarouselAdapter
를 액티비티(NativeAd2CarouselActivity
)에서 생성하는 예시입니다.
- 할당 받은 광고 갯수(
adCount
) 크기의 아이템 리스트를 만들기 위해buildCarouselItems()
를 구현합니다. NativeAd2PoolInitListener.onLoaded()
에서buildCarouselItems()
를 사용하여NativeAd2CarouselAdapter
를 생성하고 RecyclerView에 설정합니다.
다음은 NativeAd2CarouselActivity
구현 코드 예시입니다.
public class NativeAd2CarouselActivity extends AppCompatActivity {
private final String unitId = App.UNIT_ID_NATIVE_AD;
// 최대 10개의 광고를 요청할 수 있습니다.
private final int REQUEST_AD_COUNT = 5;
private RecyclerView carouselRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native_ad2_carousel);
initCarousel();
}
private void initCarousel() {
// 광고 중복 할당을 막기 위해 하나의 캐러셀에 하나의 NativeAd2Pool 인스턴스를 생성하여 사용합니다.
NativeAd2Pool carouselPool = new NativeAd2Pool(unitId);
// 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.
carouselPool.init(REQUEST_AD_COUNT, new NativeAd2PoolInitListener() {
@Override
public void onLoaded(int adCount) {
// 어댑터를 초기화합니다.
initAdapter(adCount, carouselPool);
}
@Override
public void onError(@NonNull AdError adError) {
// TODO 광고 레이아웃을 숨김 처리 하는 등 적절한 에러 처리를 할 수 있습니다.
}
});
}
private void initAdapter(int adCount, NativeAd2Pool carouselPool) {
// 할당 받은 광고 갯수(adCount) 크기의 아이템 리스트를 만들어 NativeAd2CarouselAdapter를 생성합니다.
List<NativeAd2CarouselItem> list = buildCarouselItems(adCount);
NativeAd2CarouselAdapter adapter = new NativeAd2CarouselAdapter(unitId, list, carouselPool);
// RecyclerView에 어댑터를 설정합니다.
carouselRecyclerView.setAdapter(adapter);
}
private List<NativeAd2CarouselItem> buildCarouselItems(int adCount) {
// adCount 크기의 배열을 생성합니다.
NativeAd2CarouselItem[] carouselItems = new NativeAd2CarouselItem[adCount];
// 배열을 NativeAd2Item으로 채웁니다.
Arrays.fill(carouselItems, new NativeAd2CarouselItem.NativeAd2Item());
return Arrays.asList(carouselItems);
}
}
이제 앱을 실행하면 캐러셀이 동작하는 모습을 확인할 수 있습니다. 이어서 아래 캐러셀의 필수 기능 구현하기를 참고하여 캐러셀 연동을 완료하세요.
캐러셀의 필수 기능 구현하기
BuzzAd Android용 SDK는 다양한 광고 및 사용자가 취향에 맞게 광고를 필터링할 수 있는 피드 지면 진입 경로인 슬라이드 페이지와 엔트리 포인트를 제공합니다.
✅ 중요
기존에 피드 지면을 연동하고 있거나 연동할 예정이 있는 경우, SDK 연동의 효용성을 극대화하기 위해 피드 진입 슬라이드 및 피드 엔트리 포인트를 반드시 구현하시기 바랍니다.
피드 진입 슬라이드
피드로 진입할 수 있는 슬라이드(Carousel To Feed Slide)를 캐러셀에 구현할 수 있습니다. 캐러셀 내에서 원하는 순서에 피드 진입 슬라이드를 추가할 수 있으며 추가 갯수 제한도 없어 광고 사이사이에 앱 수익화 효과가 가장 높은 피드로의 진입점을 자유롭게 추가할 수 있습니다. 이를 위해 BuzzAd Android용 SDK는 아래 표에 나열된 클래스를 제공합니다.
코드 | 설명 |
---|---|
FeedPromotion | 피드 진입 슬라이드에 표시할 리소스(이미지, 문구)를 가지고 있는 객체입니다. |
FeedPromotionFactory | FeedPromotion 객체를 생성합니다.
|
FeedPromotionViewBinder | NativeAd2View 에 FeedPromotion 을 보여줄 수 있도록 연결합니다.
|
피드 진입 슬라이드를 구현하려면 다음의 절차를 따르세요.
NativeAd2CarouselItem
에서 피드 진입 슬라이드를 보여주기 위한 클래스의 선언을 추가하세요.다음은 기존의
NativeAd2CarouselItem
구현에서CarouselToFeedSlideItem
클래스의 선언을 추가하는 예시입니다.class NativeAd2CarouselItem {
// RecyclerView에서 해당 아이템이 네이티브 2.0인지 타입을 구분하기 위한 클래스입니다.
static class NativeAd2Item extends NativeAd2CarouselItem {
}
// RecyclerView에서 해당 아이템이 피드 진입 슬라이드인지 타입을 구분하기 위한 클래스입니다.
static class CarouselToFeedSlideItem extends NativeAd2CarouselItem {
public final FeedPromotion feedPromotion;
CarouselToFeedSlideItem(final FeedPromotion feedPromotion) {
this.feedPromotion = feedPromotion;
}
}
}CarouselToFeedSlideItem
에 맞는 동작을 구현하기 위해 어댑터(NativeAd2CarouselAdapter
)와 뷰 홀더(NativeAd2CarouselViewHolder
)를 구현하세요.i.
NativeAd2CarouselViewHolder
에서 아이템 타입에 따라FeedPromotionViewBinder
를 연결합니다.public class NativeAd2CarouselViewHolder extends RecyclerView.ViewHolder {
...
private FeedPromotionViewBinder carouselToFeedViewBinder;
NativeAd2CarouselViewHolder(String unitId, NativeAd2View nativeAd2View) {
super(nativeAd2View);
...
// CarouselToFeedSlideItem을 위한 바인더를 빌드합니다.
carouselToFeedViewBinder = new FeedPromotionViewBinder.Builder(nativeAd2View, mediaView)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.iconImageView(imageIcon)
.ctaView(ctaView)
.build();
}
...
public void bind(int position, FeedPromotion feedPromotion) {
// FeedPromotion 객체를 뷰에 바인드합니다.
carouselToFeedViewBinder.bind(feedPromotion);
}
public void unbind(int position) {
// unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
carouselToFeedViewBinder.unbind();
}
}다음은
NativeAd2CarouselViewHolder
전체 코드 예시입니다.public class NativeAd2CarouselViewHolder extends RecyclerView.ViewHolder {
private NativeAd2ViewBinder nativeAd2ViewBinder;
private FeedPromotionViewBinder carouselToFeedViewBinder;
NativeAd2CarouselViewHolder(String unitId, NativeAd2View nativeAd2View) {
super(nativeAd2View);
MediaView mediaView = nativeAd2View.findViewById(R.id.mediaView);
ImageView imageIcon = nativeAd2View.findViewById(R.id.imageIcon);
TextView textTitle = nativeAd2View.findViewById(R.id.textTitle);
TextView textDescription = nativeAd2View.findViewById(R.id.textDescription);
DefaultCtaView ctaView = nativeAd2View.findViewById(R.id.ctaView);
// NativeAd2View와 하위 컴포넌트를 바인드합니다.
nativeAd2ViewBinder = new NativeAd2ViewBinder.Builder()
.nativeAd2View(nativeAd2View)
.mediaView(mediaView)
.iconImageView(imageIcon)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.ctaView(ctaView)
.build(unitId);
// CarouselToFeedSlideItem을 위한 바인더를 빌드합니다.
carouselToFeedViewBinder = new FeedPromotionViewBinder.Builder(nativeAd2View, mediaView)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.iconImageView(imageIcon)
.ctaView(ctaView)
.build();
}
public void setPool(NativeAd2Pool carouselPool, int adKey) {
// 해당 position(adKey)에 해당하는 NativeAd2ViewBinder가 carouselPool을 사용하도록 합니다.
nativeAd2ViewBinder.setPool(adKey, carouselPool);
}
public void bind(int position) {
// NativeAd2ViewBinder의 bind()를 호출하면 광고 할당 및 갱신이 자동으로 수행됩니다.
nativeAd2ViewBinder.bind(position);
}
public void bind(int position, FeedPromotion feedPromotion) {
// FeedPromotion 객체를 뷰에 바인드합니다.
carouselToFeedViewBinder.bind(feedPromotion);
}
public void unbind(int position) {
// unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
nativeAd2ViewBinder.unbind(position);
carouselToFeedViewBinder.unbind();
}
}ii.
NativeAd2CarouselAdapter
의onBindeViewHolder
에서CarouselToFeedSlideItem
일 때 호출할 bind 함수를 연결하세요.public class NativeAd2CarouselAdapter extends RecyclerView.Adapter<NativeAd2CarouselViewHolder> {
...
@Override
public void onBindViewHolder(@NonNull NativeAd2CarouselViewHolder holder, int position) {
...
// 아이템의 타입을 구분하여 적절한 bind() 함수를 호출합니다.
NativeAd2CarouselItem item = list.get(position);
if (item instanceof NativeAd2CarouselItem.NativeAd2Item) {
holder.bind(position);
} else if (item instanceof NativeAd2CarouselItem.CarouselToFeedSlideItem) {
holder.bind(position, ((NativeAd2CarouselItem.CarouselToFeedSlideItem) item).feedPromotion);
}
}
}다음은
NativeAd2CarouselAdapter
전체 코드 예시입니다.public class NativeAd2CarouselAdapter extends RecyclerView.Adapter<NativeAd2CarouselViewHolder> {
private String unitId;
private List<NativeAd2CarouselItem> list;
private NativeAd2Pool carouselPool;
public NativeAd2CarouselAdapter(String unitId, List<NativeAd2CarouselItem> list, NativeAd2Pool carouselPool) {
this.unitId = unitId;
this.list = list;
this.carouselPool = carouselPool;
}
@NonNull
@Override
public NativeAd2CarouselViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
NativeAd2View nativeAd2View = (NativeAd2View) LayoutInflater.from(parent.getContext()).inflate(R.layout.view_native_ad2_item, parent, false);
return new NativeAd2CarouselViewHolder(unitId, nativeAd2View);
}
@Override
public void onBindViewHolder(@NonNull NativeAd2CarouselViewHolder holder, int position) {
// 해당 position에 해당하는 NativeAd2ViewBinder가 carouselPool을 사용하도록 합니다.
holder.setPool(carouselPool, position);
// 아이템의 타입을 구분하여 적절한 bind() 함수를 호출합니다.
NativeAd2CarouselItem item = list.get(position);
if (item instanceof NativeAd2CarouselItem.NativeAd2Item) {
holder.bind(position);
} else if (item instanceof NativeAd2CarouselItem.CarouselToFeedSlideItem) {
holder.bind(position, ((NativeAd2CarouselItem.CarouselToFeedSlideItem) item).feedPromotion);
}
}
@Override
public void onViewRecycled(NativeAd2CarouselViewHolder holder) {
super.onViewRecycled(holder);
// unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
holder.unbind(holder.getAdapterPosition());
}
}어댑터에 전달하는 아이템 리스트를 생성하는
buildCarouselItems()
에서 원하는 순서에FeedPromotion
을 붙여 넣으세요.다음은 피드 진입 슬라이드가 캐러셀의 마지막 아이템으로 구현되도록 작성한 코드 예제입니다.
public class NativeAd2CarouselActivity extends AppCompatActivity {
...
private List<NativeAd2CarouselItem> buildCarouselItems(int adCount) {
// 마지막 페이지에 CarouselToFeedSlideItem을 추가하기 위해 1개 더 큰 사이즈로 배열을 생성합니다.
NativeAd2CarouselItem[] carouselItems = new NativeAd2CarouselItem[adCount + 1];
// 배열을 NativeAd2Item으로 채웁니다.
Arrays.fill(carouselItems, new NativeAd2CarouselItem.NativeAd2Item());
// 마지막 아이템은 CarouselToFeedSlideItem으로 설정합니다.
carouselItems[carouselItems.length - 1] = new NativeAd2CarouselItem.CarouselToFeedSlideItem(new FeedPromotionFactory(unitId).buildForCarousel());
return Arrays.asList(carouselItems);
}
}
피드 엔트리 포인트
캐러셀에서 피드로 진입할 수 있는 UI인 엔트리 포인트(Carousel To Feed Link)를 캐러셀의 하단에 구현해야 합니다.
피드 엔트리 포인트를 구현하려면 다음의 절차를 따르세요.
캐러셀로 사용할 RecyclerView 아래에
FeedEntryView
를 추가하세요.✅ 중요
app:buzzvilFeedEntryViewName
은 아래 예제 코드처럼"carousel_to_feed_link"
로 설정하세요.activity_native_ad2_carousel.xml
의 전체 코드는 여기에서 확인할 수 있습니다.<!-- activity_native_ad2_carousel.xml -->
<!-- 광고를 표시할 Carousel -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/carouselRecyclerView"
... />
<!-- Carousel 하단에서 Feed를 열 수 있게 하는 Carousel To Feed Link -->
<!-- buzzvilFeedEntryViewName의 값은 "carousel_to_feed_link"를 사용해야 합니다. -->
<com.buzzvil.buzzad.benefit.presentation.feed.entrypoint.FeedEntryView
android:id="@+id/toFeedLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginTop="16dp"
android:layout_marginEnd="36dp"
app:buzzvilFeedEntryViewName="carousel_to_feed_link">
<!-- 내부 레이아웃은 자유롭게 설정할 수 있습니다. 아래는 LinearLayout으로 구현한 예시입니다. -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="@style/NativeCarouselToFeedLink"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="포인트 더 받으러 가기" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:scaleType="centerInside"
android:src="@drawable/ic_chevron_right" />
</LinearLayout>
</com.buzzvil.buzzad.benefit.presentation.feed.entrypoint.FeedEntryView>광고 할당 요청에 대한 응답 상태에 따른 피드 엔트리 포인트의 표시 여부를 설정하세요.
캐러셀 및 피드 엔트리 포인트 표시: 보여줄 광고가 있는 경우
캐러셀 및 피드 엔트리 포인트 미표시: 광고가 아직 로드되지 않은 경우, 오류가 발생하는 경우, 광고가 없는 경우
다음은
NativeAd2CarouselActivity
에서 광고 할당 요청 성공 여부에 따라RecyclerView
(캐러셀)와FeedEntryView
의 visibility를 변경하는 예시입니다.public class NativeAd2CarouselActivity extends AppCompatActivity {
...
private RecyclerView carouselRecyclerView;
private FeedEntryView toFeedLink;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native_ad2_carousel);
carouselRecyclerView = findViewById(R.id.carouselRecyclerView);
toFeedLink = findViewById(R.id.toFeedLink);
initCarousel();
}
private void initCarousel() {
carouselRecyclerView.setVisibility(View.GONE);
toFeedLink.setVisibility(View.GONE);
...
carouselPool.init(REQUEST_AD_COUNT, new NativeAd2PoolInitListener() {
@Override
public void onLoaded(int adCount) {
carouselRecyclerView.setVisibility(View.VISIBLE);
toFeedLink.setVisibility(View.VISIBLE);
...
}
@Override
public void onError(@NonNull AdError adError) {
// 광고 레이아웃을 숨김 처리 하는 등 적절한 에러 처리를 할 수 있습니다.
carouselRecyclerView.setVisibility(View.GONE);
toFeedLink.setVisibility(View.GONE);
}
});
}
...
}
광고 아이템의 높이 차이와 상관 없이 피드 엔트리 포인트 위치 고정하기
각 광고 아이템의 설명 텍스트(
textDescription
) 길이 차이로 인해 라인 수가 상이하면 각 슬라이드의 피드 엔트리 포인트의 위치가 계속 변경되는 현상이 발생할 수 있습니다. 이러한 현상의 발생을 방지하기 위해 광고 설명 텍스트를 표시하는 뷰의lines
값을 고정하여 항상 일정한 위치를 유지할 수 있습니다.다음은 광고 설명 텍스트를 표시하는 뷰의 lines 값을 2로 설정한 예시입니다.
<!-- view_native_ad2_item.xml -->
...
<TextView
android:id="@+id/textDescription"
android:ellipsize="end"
android:lines="2"
...
/>
이제 앱을 실행하면 캐러셀의 마지막 페이지에 피드 진입 슬라이드가 표시되고, 캐러셀 하단에 피드 진입점이 추가된 모습을 확인할 수 있습니다. 이어서 아래 캐러셀의 부가 기능 구현하기를 참고하여 유저 친화적인 UI/UX를 제공해보세요.
캐러셀의 부가 기능 구현하기
이 항목에서는 캐러셀에 부가 기능을 구현하기 위한 다양한 방법 중 일부를 제시합니다. 필요에 따라 적절하게 코드를 변형하여 사용해주세요.
✏️ 참고
이 문서에서는 설명에 필요한 코드의 일부만 제공합니다. 전체 코드를 확인하려면 샘플 앱을 참고하세요.
무한 루프 구현하기
캐러셀에서 유한한 아이템을 끊임 없이 스크롤할 수 있는 무한 루프 기능을 구현할 수 있습니다. 캐러셀의 무한 루프를 구현하는 방법는 매우 다양하나, 이 가이드에서는 Adapter의 getItemCount()
가 매우 큰 값을 반환하게 하는 간단한 방법을 제시합니다.
무한 루프를 구현하려면 다음의 절차를 따르세요.
어댑터(
NativeAd2CarouselAdapter
)가isInfiniteLoopEnabled
파라미터를 받도록 변경하세요.public class NativeAd2CarouselAdapter extends RecyclerView.Adapter<NativeAd2CarouselViewHolder> {
...
private boolean isInfiniteLoopEnabled;
public NativeAd2CarouselAdapter(String unitId, List<NativeAd2CarouselItem> list, NativeAd2Pool carouselPool, boolean isInfiniteLoopEnabled) {
...
this.isInfiniteLoopEnabled = isInfiniteLoopEnabled;
}
...
}NativeAd2CarouselAdapter
에서getItemCount()
의 구현을 변경하고, position을 사용할 때 실제 인덱스를 가져오기 위해 리스트의 크기로 나눈 나머지를 사용하세요.
getItemCount()
: RecyclerView가 무한한 데이터를 가진 것처럼 표시되도록,isInfiniteLoopEnabled
가true
이고 보여줄 아이템이 있는 경우에는Integer.MAX_VALUE
를 반환하도록 변경하세요.getItemPosition()
: 무한 루프의position
을 나누어 실제 index 값을 반환합니다.public class NativeAd2CarouselAdapter extends RecyclerView.Adapter<NativeAd2CarouselViewHolder> {
...
@Override
public void onBindViewHolder(@NonNull NativeAd2CarouselViewHolder holder, int position) {
int itemPosition = getItemPosition(position);
// position 대신 itemPosition을 사용합니다.
holder.setPool(carouselPool, itemPosition);
NativeAd2CarouselItem item = list.get(itemPosition);
if (item instanceof NativeAd2CarouselItem.NativeAd2Item) {
holder.bind(itemPosition);
} else if (item instanceof NativeAd2CarouselItem.CarouselToFeedSlideItem) {
holder.bind(itemPosition, ((NativeAd2CarouselItem.CarouselToFeedSlideItem) item).feedPromotion);
}
}
@Override
public void onViewRecycled(NativeAd2CarouselViewHolder holder) {
super.onViewRecycled(holder);
int itemPosition = getItemPosition(holder.getAdapterPosition());
// holder.getAdapterPosition() 대신 itemPosition을 사용합니다.
holder.unbind(itemPosition);
}
@Override
public int getItemCount() {
int actualItemCount = list.size();
if (isInfiniteLoopEnabled && actualItemCount > 0) {
// 무한 루프를 쉽게 구현하는 방법으로 매우 큰 수를 여기서 반환합니다.
return Integer.MAX_VALUE;
}
return actualItemCount;
}
private int getItemPosition(int position) {
if (isInfiniteLoopEnabled) {
return position % list.size();
} else {
return position;
}
}
}
액티비티(
NativeAd2CarouselActivity
)에NativeAd2CarouselAdapter(isInfiniteLoopEnabled)
를 추가하세요.public class NativeAd2CarouselActivity extends AppCompatActivity {
...
// 무한 스크롤을 적용합니다.
boolean isInfiniteLoopEnabled = true;
...
private void initAdapter(int adCount, NativeAd2Pool carouselPool) {
...
// 어댑터를 설정합니다.
NativeAd2CarouselAdapter adapter = new NativeAd2CarouselAdapter(unitId, list, carouselPool, isInfiniteLoopEnabled);
carouselRecyclerView.setAdapter(adapter);
if (isInfiniteLoopEnabled) {
// 적당히 큰 수의 position으로 이동하여 무한 스크롤 효과를 구현합니다.
carouselRecyclerView.getLayoutManager().scrollToPosition(list.size() * 10000);
}
}
...
}
로딩 화면 구현하기
NativeAd2StateChangedListener
를 통해 광고 참여 후 갱신 시 로딩 화면을 구현하여 네트워크 요청에 따른 딜레이를 시각적으로 안내합니다.
✏️ 참고
RecyclerView에서는 뷰 홀더가 재활용 되면서 로딩 뷰가 visible 상태로 남아있을 수 있습니다. 아이템 타입 별bind()
에서 적절하게 초기화를 수행해주세요.
public class NativeAd2CarouselViewHolder extends RecyclerView.ViewHolder {
private LinearLayout loadingView;
private NativeAd2ViewBinder nativeAd2ViewBinder;
private FeedPromotionViewBinder carouselToFeedViewBinder;
NativeAd2CarouselViewHolder(String unitId, NativeAd2View nativeAd2View) {
super(nativeAd2View);
this.loadingView = nativeAd2View.findViewById(R.id.loadingView);
MediaView mediaView = nativeAd2View.findViewById(R.id.mediaView);
ImageView imageIcon = nativeAd2View.findViewById(R.id.imageIcon);
TextView textTitle = nativeAd2View.findViewById(R.id.textTitle);
TextView textDescription = nativeAd2View.findViewById(R.id.textDescription);
DefaultCtaView ctaView = nativeAd2View.findViewById(R.id.ctaView);
// NativeAd2View와 하위 컴포넌트를 바인드합니다.
nativeAd2ViewBinder = new NativeAd2ViewBinder.Builder()
.nativeAd2View(nativeAd2View)
.mediaView(mediaView)
.iconImageView(imageIcon)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.ctaView(ctaView)
.build(unitId);
// 필요에 따라 광고 갱신 상태를 받아올 수 있는 NativeAd2StateChangedListener를 설정하세요.
nativeAd2ViewBinder.addNativeAd2StateChangedListener(new NativeAd2StateChangedListener() {
@Override
public void onRequested() {
setLoadingView(true);
}
@Override
public void onNext(@NonNull NativeAd2 nativeAd2) {
setLoadingView(false);
}
@Override
public void onComplete() {
setLoadingView(false);
}
@Override
public void onError(@NonNull AdError adError) {
setLoadingView(false);
}
});
// CarouselToFeedSlideItem을 위한 바인더를 빌드합니다.
carouselToFeedViewBinder = new FeedPromotionViewBinder.Builder(nativeAd2View, mediaView)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.iconImageView(imageIcon)
.ctaView(ctaView)
.build();
}
public void bind(int position, FeedPromotion feedPromotion) {
// FeedPromotion 객체를 뷰에 바인드합니다.
carouselToFeedViewBinder.bind(feedPromotion);
// 피드 진입 슬라이드는 로딩 뷰가 필요 없기 때문에 false로 설정합니다.
setLoadingView(false);
}
private void setLoadingView(boolean isLoading) {
if (isLoading) {
loadingView.setVisibility(View.VISIBLE);
} else {
loadingView.setVisibility(View.GONE);
}
}
}
광고 이벤트 리스너 등록하기
캐러셀에 포함된 광고가 사용자에게 노출되거나 사용자가 클릭하는 등의 광고 이벤트가 발생하는 시점을 기록하여 유저 행동을 파악할 수 있습니다.
✏️ 참고
리워드 적립 결과(RewardResult
) 종류는 리워드 적립 결과(RewardResult
) 종류를 참고하세요.
❗️ 주의
- 광고 이벤트 리스너로 피드 진입 슬라이드의 노출 또는 클릭 이벤트를 수신할 수는 없습니다.
- 로그 기록, 단순 알림 외에 다른 동작을 추가하는 것을 권장하지 않습니다. 직접 구현한 동작이 네이티브에서 제공하는 기능(광고 자동 갱신 등)과 충돌할 수 있습니다.
public class NativeAd2CarouselViewHolder extends RecyclerView.ViewHolder {
private NativeAd2ViewBinder nativeAd2ViewBinder;
NativeAd2CarouselViewHolder(String unitId, NativeAd2View nativeAd2View) {
super(nativeAd2View);
TextView eventTextView = nativeAd2View.findViewById(R.id.eventTextView);
MediaView mediaView = nativeAd2View.findViewById(R.id.mediaView);
ImageView imageIcon = nativeAd2View.findViewById(R.id.imageIcon);
TextView textTitle = nativeAd2View.findViewById(R.id.textTitle);
TextView textDescription = nativeAd2View.findViewById(R.id.textDescription);
DefaultCtaView ctaView = nativeAd2View.findViewById(R.id.ctaView);
// NativeAd2View와 하위 컴포넌트를 바인드합니다.
nativeAd2ViewBinder = new NativeAd2ViewBinder.Builder()
.nativeAd2View(nativeAd2View)
.mediaView(mediaView)
.iconImageView(imageIcon)
.titleTextView(textTitle)
.descriptionTextView(textDescription)
.ctaView(ctaView)
.build(unitId);
// 필요에 따라 광고 이벤트 상태를 받아올 수 있는 NativeAd2EventListener를 설정하세요.
// 로그 기록, 단순 알림 외에 다른 동작을 추가하는 것을 권장하지 않습니다. 자동 갱신 등 네이티브 2.0의 기능과 직접 구현한 동작이 충돌할 수 있습니다.
nativeAd2ViewBinder.addNativeAd2EventListener(new NativeAd2EventListener() {
@Override
public void onImpressed(@NonNull NativeAd2 nativeAd2) {
eventTextView.setText("event: onImpressed");
}
@Override
public void onClicked(@NonNull NativeAd2 nativeAd2) {
eventTextView.setText("event: onClicked");
}
@Override
public void onRewardRequested(@NonNull NativeAd2 nativeAd2) {
eventTextView.setText("event: onRewardRequested");
}
@Override
public void onRewarded(@NonNull NativeAd2 nativeAd2, @NonNull RewardResult rewardResult) {
eventTextView.setText("event: onRewarded, result: " + rewardResult.name());
}
@Override
public void onParticipated(@NonNull NativeAd2 nativeAd2) {
eventTextView.setText("event: onParticipated");
}
});
}
}
앞뒤 광고 아이템을 부분적으로 노출하기
RecyclerView의 padding
과 clipToPadding
속성을 사용하여 아이템 크기를 줄이고 앞뒤 광고를 부분적으로 노출하면 사용자의 스크롤을 유도할 수 있습니다.
<!-- activity_native_ad2_carousel.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".nativead2.carousel.NativeAd2CarouselActivity">
<!-- 광고를 표시할 Carousel -->
<!-- clipToPadding, paddingHorizontal: 이전과 이후 아이템을 살짝 보여주기 위해 설정 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/carouselRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingHorizontal="32dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/view_native_ad2_item" />
</LinearLayout>
광고 아이템 사이의 여백 설정하기
RecyclerView에서 광고 아이템 사이의 여백을 설정하려면 다양한 방법을 적용할 수 있습니다. 이 가이드에서는 ItemDecoration
을 사용하는 방법을 제시합니다. 다음의 절차를 따르세요.
RecyclerView.ItemDecoration
를 상속받아PaddingDividerDecoration
를 구현합니다.public class PaddingDividerDecoration extends RecyclerView.ItemDecoration {
private final int paddingDp;
public PaddingDividerDecoration(int paddingDp) {
this.paddingDp = paddingDp;
}
@Override
public void getItemOffsets(
@NonNull Rect outRect,
@NonNull View view,
@NonNull RecyclerView parent,
@NonNull RecyclerView.State state
) {
// 여백을 한 쪽만 설정하면 PagerSnapHelper가 약간 어긋나게 동작하므로, 양쪽에 동일하게 여백을 넣습니다.
int paddingPx = dpToPx(paddingDp / 2);
outRect.left = paddingPx;
outRect.right = paddingPx;
}
private int dpToPx(int dp) {
return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
}
}RecyclerView에
PaddingDividerDecoration
을 추가하세요.
public class NativeAd2CarouselActivity extends AppCompatActivity {
...
private RecyclerView carouselRecyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_native_ad2_carousel);
carouselRecyclerView = findViewById(R.id.carouselRecyclerView);
initCarousel();
}
private void initCarousel() {
...
carouselPool.init(REQUEST_AD_COUNT, new NativeAd2PoolInitListener() {
@Override
public void onLoaded(int adCount) {
// 어댑터를 초기화합니다.
initAdapter(adCount, carouselPool);
}
...
});
}
private void initAdapter(int adCount, NativeAd2Pool carouselPool) {
// 아이템 간에 여백을 넣는 Decoration을 추가합니다.
int itemPaddingDp = 16;
carouselRecyclerView.addItemDecoration(new PaddingDividerDecoration(itemPaddingDp));
...
}
}