# 네이티브(Native)

Source: https://docs.buzzvil.com/docs/buzzbenefit-android/v6/native
Platform: Android
SDK major version: v6
Language: Korean (ko)

네이티브를 추가하고 설정하고 SDK 인터페이스를 사용해 부가 기능을 구현하는 방법을 알아보세요.

## 주요 특징

Buzzvil SDK가 제공하는 네이티브는 앱 화면과 자연스럽게 임베드되는 앱 맞춤형 광고를 게재할 수 있는 인벤토리입니다. 또한 유저가 광고에 참여한 후 자동으로 다음 광고를 노출하여, 하나의 네이티브 지면에서 끊임없이 광고를 송출하여 앱 수익화를 가속할 수 있도록 진화된 기능입니다. 유저가 더 많은 광고에 참여할 수 있도록 유도하는 다이나믹한 기능들도 제공합니다.

![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/M-native_01.png)

## 연동

앱에 네이티브를 연동하는 방법을 확인해 보세요.

### 1 단계. 준비하기

- [시작하기](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/getting-started) 적용 완료
- 네이티브 지면에 사용할 Unit ID (이하 `YOUR_NATIVE_UNIT_ID`)

### 2 단계. 광고 레이아웃 구성하기

네이티브 광고 레이아웃을 구성하는 요소와 UI에 대해서는 아래의 그림과 표를 참고하세요. ![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/A-native_ui.png)

| 구성 요소            | 설명                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 광고 제목 (_필수_)     | 광고의 제목입니다. 최대 10자까지 권장하며, 필요에 따라 글자 수에 상관 없이 일정 부분은 생략 부호도 대체할 수 있습니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| 광고 소재 (_필수_)     | - 이미지, 동영상 등 광고 소재입니다. 광고 소재의 종횡비는 반드시 유지해야 합니다.
- `com.buzzvil.buzzbenefit.buzznative.BuzzMediaView`를 사용해서 표시해야 합니다.
- 사이즈: 1,200 x 627 (px)
- 여백을 추가할 수 있습니다. 자세한 내용은 버즈빌 담당 매니저에게 문의하세요.                                                                                                                                                                                                                                                                                                                                     |
| 광고 설명 (_필수_)     | 광고에 대한 상세 설명입니다. 최대 40자까지 권장하며, 필요에 따라 글자 수에 상관 없이 일정 부분은 생략 부호로 대체할 수 있습니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| 광고주 아이콘 (_필수_)   | * 광고주 아이콘 이미지입니다. 아이콘의 종횡비는 반드시 유지해야 합니다.
* 사이즈: 80 x 80 (px)                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| CTA 버튼 (_필수_)    | - 광고의 참여를 유도하는 버튼입니다. 최대 7자까지 권장하며, 필요에 따라 글자 수에 상관 없이 일정 부분은 생략 부호로 대체할 수 있습니다.
- `com.buzzvil.buzzbenefit.DefaultBuzzCtaView`를 사용해서 표시해야 합니다.
- CTA 버튼의 색상을 변경하려면 [주요 색상](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/ui-configuration#%EC%A3%BC%EC%9A%94-%EC%83%89%EC%83%81) 토픽을 참고하세요. 이 외의 디자인을 추가적으로 변경하려면 [CTA 버튼 자체 구현하기](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/ui-configuration#cta-%EB%B2%84%ED%8A%BC-%EC%9E%90%EC%B2%B4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0) 토픽을 참고해 자체적으로 구현한 디자인으로 교체하세요. |
| 베네핏허브 진입점 (_필수_) | 유저가 클릭하면 바로 베네핏허브로 이동하는 진입점 입니다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| 광고 알림 문구 (_권장_)  | 광고임을 명시하는 문구입니다. (예: `광고`, `ad`, `스폰서`, `Sponsored`)                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |

네이티브 광고 레이아웃은 액티비티 또는 프래그먼트 레이아웃에 아래 구조에 맞게 구성해야 합니다. 네이티브 광고 지면을 구현하려면 `BuzzNativeAdView`의 규격에 맞는 레이아웃(`your_native_ad.xml`)을 구현하세요.

다음은 `BuzzNativeAdView`의 레이아웃 예시입니다.

> ✏️  **참고**\
> 베네핏허브 진입점의 레이아웃은 자유롭게 구성할 수 있습니다. 그림과 같이 베네핏허브 이동 안내 텍스트와 화살표 아이콘을 적용하는 것을 추천합니다.

```xml
<com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdView
    android:id="@+id/buzzNativeAdView">
    
      
    <com.buzzvil.buzzbenefit.buzznative.BuzzMediaView
        android:id="@+id/buzzMediaView"
        ...생략... />
    <TextView
        android:id="@+id/textTitle"
        ...생략... />
    <TextView
        android:id="@+id/textDescription"
        ...생략... />
    <ImageView
        android:id="@+id/imageIcon"
        ...생략... />
    
    <com.buzzvil.buzzbenefit.DefaultBuzzCtaView
        android:id="@+id/buzzCtaView"
        ...생략... />
    <View
        android:id="@+id/yourNativeToBenefitHubEntryPoint">
        
        ...생략...
    </View>
</com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdView>
```

코드에 베네핏허브 진입점을 구현하세요.

```kotlin
import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHub

val yourNativeToBenefitHubEntryPoint: View = ...

yourNativeToBenefitHubEntryPoint.setOnClickListener {
    BuzzBenefitHub.show(context)
}
```

> ✏️ **참고**\
> 액티비티 또는 프래그먼트 이외의 다른 방식으로 네이티브 지면을 구현하려면 버즈빌 담당자(<help@buzzvil.com>)에게 문의하세요.

### 3 단계. 광고 보여주기

광고 레이아웃에 광고를 보여주기 위해 예시 코드를 참고하여 다음의 절차를 따르세요.

1. `YOUR_NATIVE_UNIT_ID`를 이용하여 `BuzzNative` 객체를 생성하세요

2. `BuzzNativeViewBinder.Builder`를 사용하여 `BuzzNativeAdView`, `BuzzMediaView` 등 광고를 보여주기 위해 필요한 뷰를 모두 설정하고 `build()`를 호출하여 `BuzzNativeViewBinder` 객체를 생성하세요.

3. `BuzzNative`의 `load()`를 호출하여 광고를 할당받아옵니다. 이때 `onSuccess`와 `onFailure`를 통해 광고할당의 성공 및 실패시에 해당하는 기능을 추가할 수 있습니다.

4. (Optional) 광고 요청 상태에 따른 UI를 구현하려면 `BuzzNative`에 `BuzzNativeAdEventsListener`를 등록하세요.

   - 광고 할당에 실패하면 레이아웃에 광고가 표시되지 않습니다. 실패시 동작을 정의하려면 `BuzzNativeRefreshEventsListener`를 등록하여 사용하세요.
   - 유저가 광고 참여를 완료(CTA View가 `참여 완료`로 변경)하고 네이티브 지면으로 돌아오면 자동으로 다음 광고로 갱신됩니다.

5. 생성된 `BuzzNativeViewBinder` 객체의 `bind()`를 호출하여 할당받은 광고를 레이아웃에 표시합니다. 광고를 할당받지 않고 `bind()`를 호출할 경우 `BuzzNativeRefreshEventsListener`의 `onFailure` 이벤트가 발생합니다.

> ✏️ **참고**
>
> - 참여 완료 상태가 존재하지 않는 논리워드 콘텐츠, 이미 참여 완료된 광고는 클릭 후 랜딩 페이지에서 네이티브 지면으로 돌아올 때 다음 광고로 갱신됩니다.
> - 유저가 광고에 참여 중인 상태(CTA View가 '참여 확인 중'인 경우)에는 다음 광고로 갱신되지 않습니다.
> - 자동 광고 갱신을 비활성화하려면 버즈빌 담당자(<help@buzzvil.com>)에게 문의하세요.

```kotlin
import com.buzzvil.buzzbenefit.BuzzAdError
import com.buzzvil.buzzbenefit.buzznative.BuzzNative
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAd
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeRefreshEventsListener
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeViewBinder

val buzzNativeAdView = findViewById(R.id.buzzNativeAdView)
val buzzMediaView = findViewById(R.id.buzzMediaView)
val titleTextView = findViewById(R.id.textTitle)
val descriptionTextView = findViewById(R.id.textDescription)
val iconImageView = findViewById(R.id.imageIcon)
val buzzCtaView = findViewById(R.id.buzzCtaView)

// 광고 레이아웃을 설정합니다.
val binder: BuzzNativeViewBinder = BuzzNativeViewBinder.Builder()
    .buzzNativeAdView(buzzNativeAdView)
    .buzzMediaView(buzzMediaView)
    .titleTextView(titleTextView)
    .descriptionTextView(descriptionTextView)
    .iconImageView(iconImageView)
    .buzzCtaView(buzzCtaView)
    .build()

// BuzzNative 객체를 생성합니다.
val buzzNative = BuzzNative(Constant.YOUR_NATIVE_UNIT_ID)


// BuzzNative에서 광고를 할당받습니다.
buzzNative.load(
    onSuccess = { nativeAd ->
        // 광고 할당에 성공하면 호출됩니다.
        // 이후에 광고 갱신 시 onRequest()가 다시 호출됩니다.
        // 광고 자동 갱신을 성공할 때마다 반복적으로 호출됩니다.
        Toast.makeText(this@MainActivity, "onSuccess : 광고 할당 성공 ${nativeAd.title}", Toast.LENGTH_SHORT).show()
        
        // 할당받은 광고를 화면에 표시합니다.
        binder.bind(buzzNative)
    },
    onFailure = { adError ->
        // 최초 광고 할당에 실패하면 호출됩니다.
        Toast.makeText(this@MainActivity, "onFailure : 광고 할당 실패 ${adError.type.name}", Toast.LENGTH_SHORT).show()
    }
)

// (Optional) 광고 요청 상태에 따른 UI를 구현합니다. load() 이후에 Listener 등록을 시도해주세요
buzzNative.setRefreshEventsListener(object : BuzzNativeRefreshEventsListener {
    override fun onRequest() {
        // 광고 할당을 요청한 상태입니다.
        // 이후에는 onSuccess() 또는 onFailure() 중 하나가 호출됩니다.
        // 광고 자동 갱신을 시도할 때마다 반복적으로 호출됩니다.
        // 로딩 화면 등을 구현할 수 있습니다.
        Toast.makeText(this@MainActivity, "onRequest", Toast.LENGTH_SHORT).show()
    }

    override fun onSuccess(buzzNativeAd: BuzzNativeAd) {
        // 광고 할당에 성공하면 호출됩니다.
        // 이후에 광고 갱신 시 onRequest()가 다시 호출됩니다.
        // 광고 자동 갱신을 성공할 때마다 반복적으로 호출됩니다.
        // 로딩 화면 등을 구현한 경우, 여기에서 로딩을 종료합니다.
        Toast.makeText(this@MainActivity, "onSuccess", Toast.LENGTH_SHORT).show()
    }

    override fun onFailure(adError: BuzzAdError) {
        // 광고 할당에 실패하면 호출됩니다.
        // 로딩 화면 등을 구현한 경우, 여기에서 로딩을 종료합니다.
        Toast.makeText(this@MainActivity, "onFailure: ${adError.type.name}", Toast.LENGTH_SHORT).show()
    }
})
```

> ✏️ **참고**
>
> - 최초 광고 할당 요청에 실패했을 때는 `load()`의 `onFailure()`가 호출되고 광고가 노출되지 않습니다. 광고 미할당 시 발생하는 `BuzzAdError` 오류 코드에 대한 자세한 내용은 [오류 코드가 나타납니다](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/troubleshooting#%EC%98%A4%EB%A5%98-%EC%BD%94%EB%93%9C%EA%B0%80-%EB%82%98%ED%83%80%EB%82%A9%EB%8B%88%EB%8B%A4) 토픽을 참고하세요.
> - 광고 갱신 시 할당 받을 수 있는 광고가 없을 때는 `BuzzNativeRefreshEventsListener의` `onFailure()`가 호출되고 이전 광고가 유지됩니다.

> ✏️ **참고**\
> Q. 최초 요청 성공/실패와 이후 요청 성공/실패를 구분하는 이유가 무엇인가요?\
> A. 최초 요청 실패 시에는 보여줄 수 있는 광고가 없기 때문에 관련 레이아웃을 숨김 처리하고, 이후 요청 실패 시에는 이전에 할당받은 광고를 계속 노출할 수 있기 때문에 최초 실패와 이후 실패를 구분합니다.

### 4 단계. 기본 연동 테스트하기

모든 기본 연동 단계를 완료한 후 광고가 정상적으로 할당되고 표시되는지 확인하세요. 그리고 `NativeToFeedLayout`(네이티브 광고 지면에 베네핏허브로 이동하는 레이아웃)이 제대로 동작하여 클릭하면 베네핏허브로 이동하는지 확인하세요.

### 광고 운영 정책 (필수 준수 사항)

#### 1. 광고 리프레시 금지

- 광고 요청은 앱 화면이 켜지는 시점에 최초 1회만 진행해주세요.
- 이후 광고 재요청(재할당)은 화면 전환 등 명확한 사용자 액션이 있을 때만 허용됩니다.
- 일정 시간 간격으로 자동으로 광고를 리프레시하거나, 지속적으로 새 광고를 요청하는 방식은 금지됩니다.

> 과도한 광고 요청은 사용자 경험을 저해하고, 광고 효율 및 수익에 악영향을 미칠 수 있습니다.

#### 2. 노출형 광고만의 선택적 사용 금지

- 버즈빌은 다양한 광고 타입을 조합하여 최적화된 광고 경험을 제공합니다.
- API 연동 시, 특정 광고 타입(예: 노출형 광고)만 선택적으로 사용하는 방식은 허용되지 않습니다.
- SDK 연동의 경우, 노출형 광고가 없을 때 다른 플랫폼의 광고를 호출하는 방식은 금지됩니다.
- 반드시 버즈빌 광고 로직에 따라 전체 광고 타입을 균형 있게 운영해주시기 바랍니다.

> 광고 타입 제한 및 선별적 사용은 버즈빌의 광고 운영 전략과 수익 최적화에 부정적인 영향을 줍니다.

## 추가 구현

네이티브에 추가 기능을 구현하는 방법을 안내합니다.

### 네이티브 오버레이 사용하기

> ✏️ **참고**\
> 네이티브 오버레이의 UI를 설정하려면 [네이티브 오버레이 UI](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/ui-configuration#%EB%84%A4%EC%9D%B4%ED%8B%B0%EB%B8%8C-%EC%98%A4%EB%B2%84%EB%A0%88%EC%9D%B4-ui) 를 참고하세요.

네이티브 오버레이는 별도의 연동이나 구현할 필요 없는 기능으로, 유저가 네이티브 광고에 참여한 후 앱으로 되돌아오면 참여 완료된 광고 위에 베네핏허브로의 진입을 유도하는 오버레이 UI를 제공합니다. 오버레이 UI는 텍스트와 아이콘으로 구성되어 있으며, 네이티브 전체 영역(`NativeAdView`)을 덮어 유저의 클릭을 유도합니다. 유저가 닫기 버튼을 클릭하거나 오버레이를 클릭한 후 다시 돌아오면 오버레이는 자동으로 사라집니다.

![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/M-native_feed_overlay.png) ![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/M-native_feed_overlay_size.png)

네이티브 지면 구현 크기에 따른 오버레이 UI 를 제공하며 네이티브 오버레이에서는 베네핏허브 진입 비율을 높이기 위해 유저의 과거 참여 이력을 기반으로 선호하는 광고나 콘텐츠 광고 상품의 UI가 다양하게 노출됩니다.

![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/M-native_feed_overlay_type.png)

### 광고 이벤트 리스너 등록하기

> ✏️ **참고**\
> 리워드 적립 결과(`BuzzRewardResult`) 종류는 [리워드 적립 결과(`BuzzRewardResult`) 종류](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/native#%EB%A6%AC%EC%9B%8C%EB%93%9C-%EC%A0%81%EB%A6%BD-%EA%B2%B0%EA%B3%BCrewardresult-%EC%A2%85%EB%A5%98)를 참고하세요.

`BuzzNativeAdEventsListener` 를 통해 광고 콜백 이벤트를 수신할 수 있습니다. 광고 콜백 이벤트를 수신하려면 `BuzzNativeViewBinder.bind()` 함수를 호출하기 전에 `BuzzNative`에 리스너를 등록해야 합니다.

> ❗️ **주의**
>
> - 광고 이벤트 리스너로 베네핏허브 진입 슬라이드의 노출 또는 클릭 이벤트를 수신할 수는 없습니다.
> - 로그 기록, 단순 알림 외에 다른 동작을 추가하는 것을 권장하지 않습니다. 직접 구현한 동작이 네이티브에서 제공하는 기능(광고 자동 갱신 등)과 충돌할 수 있습니다.

다음은 `BuzzNative`에 광고 이벤트 리스너를 등록하는 예시입니다.

```kotlin
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdEventsListener
import com.buzzvil.buzzbenefit.buzznative.BuzzRewardResult
import com.buzzvil.buzzbenefit.buzznative.BuzzNative
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAd

// (Optional) 광고 이벤트 리스너를 등록합니다.
// 로그 기록, 단순 알림 외에 다른 동작을 추가하는 것을 권장하지 않습니다. 직접 구현한 동작이 네이티브에서 제공하는 기능(광고 자동 갱신 등)과 충돌할 수 있습니다.
buzzNative.setAdEventsListener(object : BuzzNativeAdEventsListener {
    override fun onImpressed(buzzNativeAd: BuzzNativeAd) {
        // Native 광고가 유저에게 노출되었을 때 호출됩니다.
    }

    override fun onClicked(buzzNativeAd: BuzzNativeAd) {
        // 유저가 Native 광고를 클릭했을 때 호출됩니다.
    }

    override fun onRewardRequested(buzzNativeAd: BuzzNativeAd) {
        // 리워드 적립을 요청했을 때 호출됩니다.
    }

    override fun onParticipated(buzzNativeAd: BuzzNativeAd) {
        // 유저가 광고 참여를 완료하였을 때 호출됩니다.
    }

    override fun onRewarded(buzzNativeAd: BuzzNativeAd, buzzRewardResult: BuzzRewardResult) {
        // 리워드가 적립되었을 때 호출됩니다.
    }
})

buzzNative.load(...)
```

#### 리워드 적립 결과(`BuzzRewardResult`) 종류

| `BuzzRewardResult`                      | 결과    | 설명                                |
| --------------------------------------- | ----- | --------------------------------- |
| `SUCCESS`                               | 적립성공  | 적립에 성공했습니다.                       |
| `ALREADY_PARTICIPATED`                  | 적립 실패 | 이미 적립된 광고입니다.                     |
| `MISSING_REWARD`                        | 적립 실패 | 리워드가 없는 광고입니다.                    |
| `TOO_SHORT_TO_PARTICIPATE`              | 적립 실패 | 광고 랜딩 페이지에서 너무 빨리 돌아왔습니다.         |
| `CONNECTION_TIMEOUT`                    | 적립 실패 | 네트워크 타임 아웃이 발생했습니다.               |
| `CONNECTION_TIMEOUT_IN_POWER_SAVE_MODE` | 적립 실패 | 단말의 `절전모드`로 인해 네트워크 타임아웃이 발생했습니다. |
| `BROWSER_NOT_LAUNCHED`                  | 적립 실패 | 광고 랜딩 페이지를 보기위한 브라우저가 실행되지 않았습니다. |
| `REQUEST_ERROR`                         | 적립 실패 | 잘못된 요청입니다.                        |
| `UNKNOWN_NETWORK_ERROR`                 | 적립 실패 | 알 수 없는 네트워크 오류가 발생했습니다.           |
| `SERVER_ERROR`                          | 적립 실패 | 알 수 없는 서버 오류가 발생했습니다.             |
| `CLIENT_ERROR`                          | 적립 실패 | 알 수 없는 클라이언트 오류가 발생했습니다.          |
| `UNKNOWN`                               | 적립 실패 | 알 수 없는 오류가 발생했습니다.                |

- 유저가 단말의 `절전모드`를 활성화하면 백그라운드 실행 제한으로 인해 클릭형 광고(CPC, CPM)의 적립이 높은 확률로 실패합니다. `BuzzRewardResult.CONNECTION_TIMEOUT_IN_POWER_SAVE_MODE` 를 활용하면 `절전모드`를 활성화 한 유저에게 적립 실패 사유를 안내를 할 수 있습니다.

## 캐러셀 구현하기

캐러셀(Carousel)은 여러 개의 광고를 가로로 스크롤 하여 유저에게 노출할 수 있는 UI입니다. Buzzvil SDK를 사용하여 네이티브 지면에 캐러셀을 생성하고 필수 기능을 구현해 캐러셀을 구현할 수 있습니다. 또한 선택에 따라 구현할 수 있는 부가 기능들을 통해 유저 경험을 최적화할 수 있습니다.

![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/M-native_carousel.png)

> ✏️ **참고**\
> 전체 코드를 확인하려면 [GitHub 샘플 코드](https://github.com/Buzzvil/buzz-sdk-samples/tree/master/buzzvil-sdk-v6-android)를 참고하세요.

### 캐러셀의 기본 기능 구현하기

#### 1 단계. 광고 할당 받기

네이티브에서 여러 개의 광고를 할당 받기 위해서는 광고 중복 할당을 제어하는 클래스인 `BuzzNativeGroup`을 사용해야 합니다. `YOUR_NATIVE_ID`를 이용하여 `BuzzNativeGroup` 객체를 생성하세요. 해당 화면이 destory될 때 `BuzzNativeGroup`도 dispose 해야 합니다.

`BuzzNativeGroup.load()`을 호출하여 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.

> ✏️ **참고**
>
> - 최대 10개의 광고를 요청할 수 있습니다.
> - 1개 이상의 광고가 할당된 경우 `onSuccess()`가 호출됩니다. 할당에 성공한 광고의 갯수는 요청한 광고의 갯수보다 적을 수 있습니다.
> - 최초 광고 할당 요청에 실패했을 때는 `onFailure()`가 호출되고 광고가 노출되지 않습니다. 광고 미할당 시 발생하는 `BuzzAdError` 오류 코드에 대한 자세한 내용은 [오류 코드가 나타납니다](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/troubleshooting#%EC%98%A4%EB%A5%98-%EC%BD%94%EB%93%9C%EA%B0%80-%EB%82%98%ED%83%80%EB%82%A9%EB%8B%88%EB%8B%A4) 토픽을 참고하세요.

```kotlin
import com.buzzvil.buzzbenefit.BuzzAdError
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeGroup

// 캐러셀 광고에서 광고 중복 할당을 방지하기 위한 BuzzNativeGroup 인스턴스입니다.
// Fragment의 생명 주기에 맞추어 생성 및 해제하세요.
private var buzzNativeGroup: BuzzNativeGroup? = null

private fun initCarouselNativeGroup() {
    // 광고 중복 할당을 막기 위해 하나의 캐러셀에 하나의 BuzzNativeGroup 인스턴스를 생성하여 사용합니다.
    buzzNativeGroup = BuzzNativeGroup(YOUR_NATIVE_UNIT_ID)
    val REQUEST_AD_COUNT = 5

    // 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.
    buzzNativeGroup?.let {
        it.load(
            REQUEST_AD_COUNT,
            onSuccess = { adCount ->
                // TODO 할당 받은 광고 갯수(adCount)만큼 아이템 리스트를 만들어 RecyclerView 어댑터를 초기화 합니다.
            },
            onFailure = { adError ->
                // TODO 광고 레이아웃을 숨김 처리 하는 등 적절한 에러 처리를 할 수 있습니다.
            }
        )
    }
}

override fun onDestroyView() {
    super.onDestroyView()

    buzzNativeGroup?.dispose()
}
```

#### 2 단계. RecyclerView 생성하기

Buzzvil SDK에서는 아이템 뷰를 화면에 가로 레이아웃으로 표시하는 `RecyclerView`와 이를 처리하는 어댑터를 사용하여 네이티브 지면에 캐러셀을 구현할 수 있습니다. `RecyclerView`를 생성하려면 다음의 절차를 따르세요.

1. 캐러셀을 넣고 싶은 영역에 `RecyclerView`를 추가하세요.

   ```xml
       
       <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/your_native_ad_view" />
   ```

2. 뷰 레이아웃 파일(`your_native_ad_view.xml`)에 `RecyclerView`의 각 아이템에 대한 레이아웃을 작성하세요.

   > ✏️ **참고**\
   > 네이티브 기본 연동에서 사용한 `BuzzNativeAdView` 레이아웃을 그대로 사용할 수 있습니다. 자세한 내용은 [GitHub 샘플 코드](https://github.com/Buzzvil/buzz-sdk-samples/tree/master/buzzvil-sdk-v6-android)를 참고해주세요.

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

   <com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdView 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/buzzNativeAdView"
       android:layout_width="match_parent"
       android:layout_height="wrap_content">
       ...
   </com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdView>
   ```

#### 3 단계. RecyclerView 어댑터 구현하기

RecyclerView 어댑터와 ViewHolder를 구현하기 위해 다음의 절차를 따르세요.

1. BuzzNative를 바인딩 하는 `BuzzNativeCarouselViewHolder`를 구현하세요.

   ```kotlin
   import com.buzzvil.buzzbenefit.buzznative.BuzzNative
   import com.buzzvil.buzzbenefit.buzznative.BuzzNativeViewBinder

   class BuzzNativeCarouselViewHolder(
       binding: BuzzNativeAdViewBinding
   ) : RecyclerView.ViewHolder(binding.root) {
       private val buzzNativeViewBinder: BuzzNativeViewBinder

       init {
           val buzzNativeAdView = binding.buzzNativeAdView
           val buzzMediaView = binding.buzzMediaView
           val titleTextView = binding.textTitle
           val descriptionTextView = binding.textDescription
           val iconImageView = binding.imageIcon
           val buzzCtaView = binding.buzzCtaView

           // BuzzNativeView와 하위 컴포넌트를 바인드합니다.
           buzzNativeViewBinder = BuzzNativeViewBinder.Builder()
               .buzzNativeAdView(buzzNativeAdView)
               .buzzMediaView(buzzMediaView)
               .titleTextView(titleTextView)
               .descriptionTextView(descriptionTextView)
               .iconImageView(iconImageView)
               .buzzCtaView(buzzCtaView)
               .build()
       }

       fun bind(native: BuzzNative) {
           // BuzzNativeViewBinder의 bind()를 호출하면 광고 갱신이 자동으로 수행됩니다.
           // (Optional) BuzzNativeRefreshEventsListener, BuzzNativeAdEventsListener를 bind 하기 전 연결해야 합니다.
           buzzNativeViewBinder.bind(native)
       }

       fun unbind() {
           // unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
           buzzNativeViewBinder.unbind()
       }
   }
   ```

2. 캐러셀을 처리하기 위한 어댑터(`BuzzNativeCarouselAdapter`)를 구현하세요.

   > ❗️ **주의**\
   > 광고의 상태가 여러 뷰와 섞이지 않도록 반드시 `onViewRecycled()`에서 `BuzzNativeAdView`를 해제해주어야 합니다. `BuzzNativeCarouselViewHolder`에 선언한 `unbind()`을 통해 `BuzzNativeViewBinder.unbind()`를 호출하세요.

   ```kotlin
   import androidx.recyclerview.widget.RecyclerView
   import com.buzzvil.buzzbenefit.buzznative.BuzzNative

   class BuzzNativeCarouselAdapter(
       private val list: List<BuzzNative>
   ) : RecyclerView.Adapter<BuzzNativeCarouselViewHolder>() {

       override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BuzzNativeCarouselViewHolder {
           val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
           val binding = BuzzNativeAdViewBinding.inflate(inflater, parent, false)
           return BuzzNativeCarouselViewHolder(binding)
       }

       override fun getItemCount(): Int {
           return list.size
       }

       override fun onBindViewHolder(holder: BuzzNativeCarouselViewHolder, position: Int) {
           val item = list[position]
           holder.bind(item)
       }

       override fun onViewRecycled(holder: BuzzNativeCarouselViewHolder) {
           super.onViewRecycled(holder)

           // unbind를 반드시 호출하여 뷰를 재사용할 때 문제가 발생하지 않게 합니다.
           holder.unbind()
       }
   }
   ```

#### 4 단계. 액티비티 또는 프래그먼트에 결합하기

다음은 위에서 구현한 `BuzzNativeCarouselAdapter`를 액티비티(`BuzzNativeCarouselActivity`)에서 생성하는 예시입니다.

1. 할당 받은 광고 갯수(`adCount`) 크기의 아이템 리스트를 만들기 위해 `buildCarouselItems()`를 구현합니다.
2. `BuzzNativeGroup.load(onSuccess)`에서 `BuzzNativeCarouselAdapter`를 생성하고 RecyclerView에 설정합니다.

다음은 `BuzzNativeCarouselActivity` 구현 코드 예시입니다.

```kotlin
    import com.buzzvil.buzzbenefit.BuzzAdError
    import com.buzzvil.buzzbenefit.buzznative.BuzzNative
    import com.buzzvil.buzzbenefit.buzznative.BuzzNativeGroup

    private var buzzNativeGroup: BuzzNativeGroup? = null

    private fun initCarouselNativeGroup(nativeUnitId: String) {
        // 광고 중복 할당을 막기 위해 하나의 캐러셀에 하나의 BuzzNativeGroup 인스턴스를 생성하여 사용합니다.
        buzzNativeGroup = BuzzNativeGroup(nativeUnitId)

        val REQUEST_AD_COUNT = 5

        // 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.
        buzzNativeGroup?.let {
            it.load(
                REQUEST_AD_COUNT,
                onSuccess = { adCount ->
                    // 어댑터를 초기화 합니다.
                    initCarouselAdapter(it.natives, adCount)
                },
                onFailure = { adError ->
                    // TODO 광고 레이아웃을 숨김 처리 하는 등 적절한 에러 처리를 할 수 있습니다.
                }
            )
        }
    }

    private fun initCarouselAdapter(natives: List<BuzzNative>, adCount: Int) {
        // 할당 받은 광고 리스트로 BuzzNativeCarouselAdapter를 생성합니다.
        val adapter = BuzzNativeCarouselAdapter(natives)

        // RecyclerView에 어댑터를 설정합니다.
        binding.carouselRecyclerView.adapter = adapter
    }
```

이제 앱을 실행하면 캐러셀이 동작하는 모습을 확인할 수 있습니다. 이어서 아래 [캐러셀의 필수 기능 구현하기](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/native#%EC%BA%90%EB%9F%AC%EC%85%80%EC%9D%98-%ED%95%84%EC%88%98-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0)를 참고하여 캐러셀 연동을 완료하세요.

### 캐러셀의 필수 기능 구현하기

Buzzvil SDK는 다양한 광고 및 유저가 취향에 맞게 광고를 필터링할 수 있는 베네핏허브 지면 진입 경로인 슬라이드 페이지와 진입점을 제공합니다.

> ✅ **중요**\
> 기존에 베네핏허브 지면을 연동하고 있거나 연동할 예정이 있는 경우, SDK 연동의 효용성을 극대화하기 위해 베네핏허브 진입점을 반드시 구현하시기 바랍니다.

#### 베네핏허브 진입점

캐러셀에서 베네핏허브로 진입할 수 있는 UI인 진입점을 캐러셀의 상단 또는 하단에 구현해야 합니다.

![](https://docs.buzzvil.com/api/content-image/buzzbenefit-android/v6/image/M-nativecarousel_feed_entry_link.png)

베네핏허브 진입점을 구현하려면 다음의 절차를 따르세요.

1. 캐러셀로 사용할 RecyclerView 아래에 레이아웃을 추가하세요.

   `activity_native_ad2_carousel.xml`의 전체 코드는 [GitHub 샘플 코드](https://github.com/Buzzvil/buzz-sdk-samples/tree/master/buzzvil-sdk-v6-android)에서 확인할 수 있습니다.

   ```xml

   <androidx.recyclerview.widget.RecyclerView
       android:id="@+id/carouselRecyclerView"
       ... />



   <LinearLayout
       android:id="@+id/carouselToBenefitHubEntryView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="horizontal">

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="포인트 더 받으러 가기 >" />
   </LinearLayout>
   ```

2. 광고 할당 요청에 대한 응답 상태에 따른 베네핏허브 진입점의 표시 여부를 설정하세요.

   - 캐러셀 및 베네핏허브 진입점 표시: 보여줄 광고가 있는 경우
   - 캐러셀 및 베네핏허브 진입점 미표시: 광고가 아직 로드되지 않은 경우, 오류가 발생하는 경우, 광고가 없는 경우

   다음은 `BuzzNativeCarouselActivity`에서 광고 할당 요청 성공 여부에 따라 `RecyclerView`(캐러셀)와 `carouselToFeedLink`의 visibility를 변경하는 예시입니다.

   ```kotlin
   import com.buzzvil.buzzbenefit.BuzzAdError
   import com.buzzvil.buzzbenefit.buzznative.BuzzNativeGroup

   private fun initCarouselNativeGroup(nativeUnitId: String) {
       // 광고 중복 할당을 막기 위해 하나의 캐러셀에 하나의 BuzzNativeGroup 인스턴스를 생성하여 사용합니다.
       buzzNativeGroup = BuzzNativeGroup(nativeUnitId)

       val REQUEST_AD_COUNT = 5

       // 현재 할당 받을 수 있는 광고의 갯수를 확인합니다.
       buzzNativeGroup?.let {
           it.load(
               REQUEST_AD_COUNT,
               onSuccess = { adCount ->
                   // 어댑터를 초기화 합니다.
                   initCarouselAdapter(it.natives, adCount, nativeUnitId)

                   binding.carouselRecyclerView.visibility = View.VISIBLE
                   binding.carouselToFeedLink.visibility = View.VISIBLE
               },
               onFailure = { adError ->
                   // TODO 광고 레이아웃을 숨김 처리 하는 등 적절한 에러 처리를 할 수 있습니다.
                   binding.carouselRecyclerView.visibility = View.GONE
                   binding.carouselToFeedLink.visibility = View.GONE
               }
           )
       }
   }
   ```

3. 베네핏허브 진입점 위치를 고정하세요.

   각 광고 아이템의 설명 텍스트(`textDescription`) 길이 차이로 인해 라인 수가 상이하면 각 슬라이드의 베네핏허브 진입점의 위치가 계속 변경되는 현상이 발생할 수 있습니다. 이러한 현상의 발생을 방지하기 위해 광고 설명 텍스트를 표시하는 뷰의 `lines` 값을 고정하여 항상 일정한 위치를 유지할 수 있습니다.

   다음은 광고 설명 텍스트를 표시하는 뷰의 lines 값을 `2`로 설정한 예시입니다.

   ```xml
   <TextView
       android:id="@+id/textDescription"
       android:ellipsize="end"
       android:lines="2" />
   ```

4. 코드에 베네핏허브 진입점을 구현하세요.

   ```kotlin
   import com.buzzvil.buzzbenefit.benefithub.BuzzBenefitHub

   val carousleToFeedLint: View = ...

   carouselToFeedLink.setOnClickListener {
       BuzzBenefitHub.show(context)
   }
   ```

이제 앱을 실행하면 캐러셀의 마지막 페이지에 베네핏허브 진입 슬라이드가 표시되고, 캐러셀 하단에 베네핏허브 진입점이 추가된 모습을 확인할 수 있습니다. 이어서 아래 [캐러셀의 부가 기능 구현하기](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/native#%EC%BA%90%EB%9F%AC%EC%85%80%EC%9D%98-%EB%B6%80%EA%B0%80-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0)를 참고하여 유저 친화적인 UI/UX를 제공해보세요.

### 캐러셀의 부가 기능 구현하기

이 항목에서는 캐러셀에 부가 기능을 구현하기 위한 다양한 방법 중 일부를 제시합니다. 필요에 따라 적절하게 코드를 변형하여 사용해주세요.

> ✏️ **참고**\
> 이 문서에서는 설명에 필요한 코드의 일부만 제공합니다. 전체 코드를 확인하려면 [GitHub 샘플 코드](https://github.com/Buzzvil/buzz-sdk-samples/tree/master/buzzvil-sdk-v6-android)를 참고하세요.

#### 무한 루프 구현하기

캐러셀에서 유한한 아이템을 끊임 없이 스크롤할 수 있는 무한 루프 기능을 구현할 수 있습니다. 캐러셀의 무한 루프를 구현하는 방법는 매우 다양하나, 이 가이드에서는 Adapter의 `getItemCount()`가 매우 큰 값을 반환하게 하는 간단한 방법을 제시합니다.

무한 루프를 구현하려면 다음의 절차를 따르세요.

1. 어댑터(`BuzzNativeCarouselAdapter`)가 `isInfiniteLoopEnabled` 파라미터를 받도록 변경하세요.

   ```kotlin
   class BuzzNativeCarouselAdapter(
       ...
       private val isInfiniteLoopEnabled: Boolean,
   ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
       ...
   }
   ```

2. `BuzzNativeCarouselAdapter`에서 `getItemCount()`의 구현을 변경하고, position을 사용할 때 실제 인덱스를 가져오기 위해 리스트의 크기로 나눈 나머지를 사용하세요.

- `getItemCount()`: RecyclerView가 무한한 데이터를 가진 것처럼 표시되도록, `isInfiniteLoopEnabled`가 `true`이고 보여줄 아이템이 있는 경우에는 `Integer.MAX_VALUE`를 반환하도록 변경하세요.

- `getItemPosition()`: 무한 루프의 `position`을 나누어 실제 index 값을 반환합니다.

  ```kotlin
  class BuzzNativeCarouselAdapter(
      private val list: List<BuzzNative>,
      private val isInfiniteLoopEnabled: Boolean,
  ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

      override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
          val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
          val binding = CarouselItemBinding.inflate(inflater, parent, false)
          return BuzzNativeCarouselViewHolder(binding)
      }

      override fun getItemCount(): Int {
          val actualItemCount = list.size
          return if (isInfiniteLoopEnabled && actualItemCount > 0) {
              // 무한 루프를 쉽게 구현하는 방법으로 매우 큰 수를 여기서 반환합니다.
              Int.MAX_VALUE
          } else actualItemCount
      }

      override fun onBindViewHolder(holder: BuzzNativeCarouselViewHolder, position: Int) {
          // (무한 루프 구현 시) position 대신 itemPosition을 사용합니다.
          val itemPosition = getItemPosition(position)
          val item = list[itemPosition]

          // 해당 position에 해당하는 buzzAdBenefitViewBinder가 carouselPool을 사용하도록 합니다.
          holder.bind(item, itemPosition)
      }
   
      override fun onViewRecycled(holder: BuzzNativeCarouselViewHolder) {
          super.onViewRecycled(holder)
          
          holder.unbind()
      }

      private fun getItemPosition(position: Int): Int {
          return if (isInfiniteLoopEnabled) {
              position % list.size
          } else {
              position
          }
      }
  }
  ```

3. 액티비티(`BuzzNativeCarouselActivity`)에 `BuzzNativeCarouselAdapter(isInfiniteLoopEnabled)`를 추가하세요.

```kotlin
import com.buzzvil.buzzbenefit.buzznative.BuzzNative

private fun initCarouselAdapter(natives: List<BuzzNative>, adCount: Int) {
    // 무한 스크롤을 적용합니다.
    val isInfiniteLoopEnabled = true
    // 할당 받은 광고 리스트로 BuzzNativeCarouselAdapter를 생성합니다.
    val adapter = BuzzNativeCarouselAdapter(natives, isInfiniteLoopEnabled)
    // RecyclerView에 어댑터를 설정합니다.
    binding.carouselRecyclerView.adapter = adapter
    if (isInfiniteLoopEnabled) {
        // 적당히 큰 수의 position으로 이동하여 무한 스크롤 효과를 구현합니다.
        binding.carouselRecyclerView.layoutManager?.scrollToPosition(adCount * 10000)
    }
}
```

#### 로딩 화면 구현하기

`setRefreshEventsListener`를 통해 광고 참여 후 갱신 시 로딩 화면을 구현하여 네트워크 요청에 따른 딜레이를 시각적으로 안내합니다.

> ✏️ **참고**\
> RecyclerView에서는 뷰 홀더가 재활용 되면서 로딩 뷰가 visible 상태로 남아있을 수 있습니다. 아이템 타입 별 `bind()`에서 적절하게 초기화를 수행해주세요.

```kotlin
import com.buzzvil.buzzbenefit.BuzzAdError
import com.buzzvil.buzzbenefit.buzznative.BuzzNative
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeRefreshEventsListener

class BuzzNativeCarouselViewHolder(
    private val binding: CarouselItemBinding
) : RecyclerView.ViewHolder(binding.root) {
    ...

    init {
        ...
    }

    ...

    fun bind(native: BuzzNative) {
        ...

        native.setRefreshEventsListener(
            object : BuzzNativeRefreshEventsListener {
                override fun onSuccess(buzzNativeAd: BuzzNativeAd) {
                    setLoadingView(false)
                }

                override fun onFailure(adError: BuzzAdError) {
                    setLoadingView(false)
                }

                override fun onRequest() {
                    setLoadingView(true)
                }
            }
        )

        ...
        buzzNativeViewBinder.bind(native)
    }

    private fun setLoadingView(isLoading: Boolean) {
        if (isLoading) {
            binding.loadingView.visibility = View.VISIBLE
        } else {
            binding.loadingView.visibility = View.GONE
        }
    }
}
```

#### 광고 이벤트 리스너 등록하기

캐러셀에 포함된 광고가 유저에게 노출되거나 유저가 클릭하는 등의 광고 이벤트가 발생하는 시점을 기록하여 유저 행동을 파악할 수 있습니다.

> ✏️  **참고**\
> 리워드 적립 결과(`RewardResult`) 종류는 [리워드 적립 결과(`RewardResult`) 종류](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/native#%EB%A6%AC%EC%9B%8C%EB%93%9C-%EC%A0%81%EB%A6%BD-%EA%B2%B0%EA%B3%BCrewardresult-%EC%A2%85%EB%A5%98)를 참고하세요.

> ❗️ **주의**
>
> - 광고 이벤트 리스너로 베네핏허브 진입 슬라이드의 노출 또는 클릭 이벤트를 수신할 수는 없습니다.
> - 로그 기록, 단순 알림 외에 다른 동작을 추가하는 것을 권장하지 않습니다. 직접 구현한 동작이 네이티브에서 제공하는 기능(광고 자동 갱신 등)과 충돌할 수 있습니다.

```kotlin
import com.buzzvil.buzzbenefit.buzznative.BuzzNative
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAd
import com.buzzvil.buzzbenefit.buzznative.BuzzNativeAdEventsListener
import com.buzzvil.buzzbenefit.buzznative.BuzzRewardResult

class BuzzNativeCarouselViewHolder(
    private val binding: CarouselItemBinding
) : RecyclerView.ViewHolder(binding.root) {
    private val buzzNativeViewBinder: BuzzNativeViewBinder
...

    init {
        ...
    }

    ...

    fun bind(native: BuzzNative) {
        ...

        native.setAdEventsListener(object : BuzzNativeAdEventsListener {
            override fun onClicked(buzzNativeAd: BuzzNativeAd) {}

            override fun onImpressed(buzzNativeAd: BuzzNativeAd) {}

            override fun onParticipated(buzzNativeAd: BuzzNativeAd) {}

            override fun onRewardRequested(buzzNativeAd: BuzzNativeAd) {}

            override fun onRewarded(buzzNativeAd: BuzzNativeAd, buzzRewardResult: BuzzRewardResult) {}
        })

        ...
        buzzNativeViewBinder.bind(native)
    }
}
```

#### 앞뒤 광고 아이템을 부분적으로 노출하기

RecyclerView의 `padding`과 `clipToPadding` 속성을 사용하여 아이템 크기를 줄이고 앞뒤 광고를 부분적으로 노출하면 유저의 스크롤을 유도할 수 있습니다.

```xml


<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" />
```

#### 광고 아이템 사이의 여백 설정하기

RecyclerView에서 광고 아이템 사이의 여백을 설정하려면 다양한 방법을 적용할 수 있습니다. 이 가이드에서는 `ItemDecoration`을 사용하는 방법을 제시합니다. 다음의 절차를 따르세요.

1. `RecyclerView.ItemDecoration`를 상속받아 `PaddingDividerDecoration`를 구현합니다.

   ```kotlin
   class PaddingDividerDecoration(private val paddingDp: Int) : RecyclerView.ItemDecoration() {
       override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
           super.getItemOffsets(outRect, view, parent, state)
           // 여백을 한 쪽만 설정하면 PagerSnapHelper가 약간 어긋나게 동작하므로, 양쪽에 동일하게 여백을 넣습니다.
           val paddingPx = paddingDp.dpToPx() / 2
           outRect.left = paddingPx
           outRect.right = paddingPx
       }

       private fun Int.dpToPx(): Int {
           return (this * Resources.getSystem().displayMetrics.density).toInt()
       }
   }
   ```

2. RecyclerView에 `PaddingDividerDecoration`을 추가하세요.

```kotlin
import com.buzzvil.buzzbenefit.buzznative.BuzzNative

private fun initCarouselAdapter(natives: List<BuzzNative>, adCount: Int) {
    ...

    // 양 옆 여백 설정
    binding.carouselRecyclerView.addItemDecoration(PaddingDividerDecoration(16))

    ...
}
```

## 자주 하는 질문

| 질문                                  | 답변                                                                                                                                                                                                                                                                                                                   |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 액션형 광고에 유저 문의 버튼을 추가해야하는 이유가 궁금합니다. | 액션형 광고를 할당할 경우, 안정적인 서비스 운영을 위해 유저의 문의 기능이 반드시 구현되어야 합니다. 자세한 내용은 [액션형 광고의 유저 문의 기능 추가하기](https://docs.buzzvil.com/docs/buzzbenefit-android/v6/faq#%EC%95%A1%EC%85%98%ED%98%95-%EA%B4%91%EA%B3%A0%EC%9D%98-%EC%9C%A0%EC%A0%80-%EB%AC%B8%EC%9D%98-%EA%B8%B0%EB%8A%A5-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0) 토픽을 참고하세요. |
