개요
Feed의 각 컴포넌트는 다음과 같이 구분합니다.
Note |
---|
FeedToolbarHolder, AdsAdapter, FeedHeaderViewAdapter class의 custom class를 생성할때 다음 조건중 하나를 사용해야합니다. 위의 조건에 맞지 않는 경우, class를 찾지못하는 현상이 발생하여 customization이 적용되지 않습니다. |
툴바 커스터마이징
Feed 상단의 툴바를 커스터마이징할 수 있습니다.
FeedToolbarHolder
interface를 구현하는 Class 를 생성합니다.
Code Block |
---|
|
public class CustomFeedToolbarHolder implements FeedToolbarHolder {
private FeedActivityToolbar toolbar;
@Override
public View getView(final Activity activity, @NonNull final String unitId) {
// FeedActivityToolbar is a default toolbar view that you can only change the title and view's attributes.
// Create a custom view and return it if you would like to add more UI components.
this.toolbar = new FeedActivityToolbar(activity, unitId);
toolbar.setTitle("BuzzAd Feed");
toolbar.setBackgroundColor(Color.parseColor("#1290FF")); // 변경하려는 "색상코드" 를 입력
return toolbar;
}
@Override
public void onTotalRewardUpdated(int totalReward) {
// Use this callback if you want to show the current total points that users can get
}
} |
FeedConfig
에 CustomFeedToolbarHolder
를 설정합니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.feedToolbarHolderClass(CustomFeedToolbarHolder.class)
.build(); |
툴바 Height 변경
커스터마이징한 FeedToolbarHolder
가 들어가는 layout의 height값이 기본값으로 고정되어 있습니다.
아래 가이드를 통해 툴바의 height 값을 수정할 수 있습니다.
Code Block |
---|
|
// AndroidManifest.xml
...생략...
<activity
android:name="com.buzzvil.buzzad.benefit.presentation.feed.FeedBottomSheetActivity"
android:theme="@style/MyAppTheme"
tools:replace="android:theme"/>
...생략... |
Code Block |
---|
|
// styles.xml
<style name="MyAppTheme" parent=...>
...생략...
<!-- Customize your theme here. -->
<item name="actionBarSize">DESIRED_ACTION_BAR_HEIGHT</item>
...생략...
</style> |
헤더 커스터마이징
Feed 상단의 헤더 영역을 커스터마이징하여, 유저에게 피드가 어떤 공간인지 상세히 안내할 수 있습니다.
FeedHeaderViewAdapter
interface 를 구현하는 Class 를 생성합니다.
Code Block |
---|
|
public class CustomFeedHeaderViewAdapter implements FeedHeaderViewAdapter {
@Override
public View onCreateView(final Context context, final ViewGroup parent) {
final LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater.inflate(R.layout.bz_view_feed_header, parent, false);
}
@Override
public void onBindView(final View view, final int reward) {
// Display total reward on the header if needed.
final View rewardLayout = view.findViewById(R.id.rewardLayout);
if (reward == 0) {
rewardLayout.setVisibility(View.GONE);
} else {
rewardLayout.setVisibility(View.VISIBLE);
final TextView rewardTextView = view.findViewById(R.id.rewardText);
final String rewardText = view.getContext().getString(R.string.bz_feed_header_view_reward_text, reward);
rewardTextView.setText(rewardText);
}
}
@Override
public void onDestroyView() {
// Use this this callback for clearing memory
}
} |
FeedConfig
에 FeedHeaderViewAdapter
를 설정합니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.feedHeaderViewAdapterClass(CustomFeedHeaderViewAdapter.class)
.build(); |
광고 아이템 영역 커스터마이징 - 일반 광고
아래 NativeAdView
을 포함하는 레이아웃(your_feed_ad.xml
)을 구현합니다.
NativeAdView의 레이아웃
Code Block |
---|
// your_feed_ad.xml
<com.buzzvil.buzzad.benefit.presentation.nativead.NativeAdView
android:id="@+id/native_ad_view"
...생략... >
<LinearLayout
...생략... >
<com.buzzvil.buzzad.benefit.presentation.media.MediaView
android:id="@+id/mediaView"
...생략... />
<TextView
android:id="@+id/textTitle"
...생략... />
<TextView
android:id="@+id/textDescription"
...생략... />
<ImageView
android:id="@+id/imageIcon"
...생략... />
<com.buzzvil.buzzad.benefit.presentation.media.CtaView
android:id="@+id/ctaView"
...생략... />
...생략...
</LinearLayout>
...생략...
</com.buzzvil.buzzad.benefit.presentation.nativead.NativeAdView> |
onCreateViewHolder
에서 NativeAdViewHolder
에 NativeAdView
를 설정합니다.
onBindViewHolder
에서는 NativeAdView
에 광고 데이터(NativeAd
)를 바인딩합니다.
clickableViews
리스트에 UI 컴포넌트를 추가하여, 클릭 이벤트를 수신할 컴포넌트를 설정합니다. 추가된 UI 컴포넌트를 클릭 시 광고 페이지로 이동하게 됩니다.
CTA 버튼은 CtaPresenter
를 이용하여 상황에 맞는 적절한 문구로 변경됩니다.
CTA 버튼의 UI 혹은 문구의 커스터마이징은 가이드에 따라 진행할 수 있습니다.
OnNativeAdEventListener
를 통해 광고 콜백 이벤트를 수신할 수 있습니다.
기획에 따라 추가로 UI 등을 추가할 수 있습니다.
Code Block |
---|
|
public class CustomAdsAdapter extends AdsAdapter<AdsAdapter.NativeAdViewHolder> {
@Override
public NativeAdViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final NativeAdView feedNativeAdView = (NativeAdView) inflater.inflate(R.layout.your_feed_ad, parent, false);
return new NativeAdViewHolder(feedNativeAdView);
}
@Override
public void onBindViewHolder(NativeAdViewHolder holder, NativeAd nativeAd) {
final NativeAdView view = (NativeAdView) holder.itemView;
final Ad ad = nativeAd.getAd();
// create ad component
final MediaView mediaView = view.findViewById(R.id.mediaView);
final LinearLayout titleLayout = view.findViewById(R.id.titleLayout);
final TextView titleView = view.findViewById(R.id.textTitle);
final ImageView iconView = view.findViewById(R.id.imageIcon);
final TextView descriptionView = view.findViewById(R.id.textDescription);
final CtaView ctaView = view.findViewById(R.id.ctaView);
final CtaPresenter ctaPresenter = new CtaPresenter(ctaView); // CtaView should not be null
// data binding
ctaPresenter.bind(nativeAd);
if (mediaView != null) {
mediaView.setCreative(ad.getCreative());
mediaView.setVideoEventListener(new VideoEventListener() {
// Override and implement methods
@Override
public void onVideoStarted() {}
...생략...
});
}
if (titleView != null) {
titleView.setText(ad.getTitle());
}
if (iconView != null) {
ImageLoader.getInstance().displayImage(ad.getIconUrl(), iconView);
}
if (descriptionView != null) {
descriptionView.setText(ad.getDescription());
}
final Collection<View> clickableViews = new ArrayList<>();
clickableViews.add(ctaView);
clickableViews.add(mediaView);
clickableViews.add(titleLayout);
clickableViews.add(descriptionView);
view.setMediaView(mediaView);
view.setClickableViews(clickableViews);
view.setNativeAd(nativeAd);
view.addOnNativeAdEventListener(new NativeAdView.OnNativeAdEventListener() {
@Override
public void onImpressed(final @NonNull NativeAdView view, final @NonNull NativeAd nativeAd) {
}
@Override
public void onClicked(@NonNull NativeAdView view, @NonNull NativeAd nativeAd) {
ctaPresenter.bind(nativeAd);
}
@Override
public void onRewardRequested(@NonNull NativeAdView view, @NonNull NativeAd nativeAd) {
}
@Override
public void onRewarded(@NonNull NativeAdView nativeAdView, @NonNull NativeAd nativeAd, @Nullable RewardResult rewardResult) {
}
@Override
public void onParticipated(final @NonNull NativeAdView view, final @NonNull NativeAd nativeAd) {
ctaPresenter.bind(nativeAd);
}
});
}
} |
FeedConfig
에 위에서 작성한 CustomAdsAdapter
를 설정합니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.adsAdapterClass(CustomAdsAdapter.class)
.build(); |
광고 아이템 영역 커스터마이징 - 쇼핑 적립 광고 (CPS)
커스터마이징 방식은 일반 광고와 같습니다. 하지만 쇼핑 적립 광고는 일반 광고에 비해 많은 정보를 제공합니다. 일반 광고용 레이아웃에서 priceText
, originalPriceText
, discountPercentageText
, categoryText
가 추가되었습니다. 따라서 위 4가지 컴포넌트에 대해 추가로 데이터를 처리해야합니다.
Code Block |
---|
// your_feed_ad_cps.xml
<?xml version="1.0" encoding="utf-8"?>
<com.buzzvil.buzzad.benefit.presentation.nativead.NativeAdView
android:id="@+id/native_ad_view"
...생략... >
<LinearLayout
...생략... >
<com.buzzvil.buzzad.benefit.presentation.media.MediaView
android:id="@+id/mediaView"
...생략... />
...생략...
<TextView
android:id="@+id/priceText"
...생략... />
<TextView
android:id="@+id/originalPriceText"
...생략... />
<TextView
android:id="@+id/discountPercentageText"
...생략... />
<TextView
android:id="@+id/categoryText"
...생략... />
...생략...
</LinearLayout>
...생략...
</com.buzzvil.buzzad.benefit.presentation.nativead.NativeAdView> |
Info |
---|
위에서 안내한 AdsAdapter 를 참고하여 수정합니다. |
Code Block |
---|
|
public class CustomCPSAdsAdapter extends AdsAdapter<AdsAdapter.NativeAdViewHolder> {
@Override
public NativeAdViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
final NativeAdView feedNativeAdView = (NativeAdView) inflater.inflate(R.layout.your_feed_ad_cps, parent, false);
return new NativeAdViewHolder(feedNativeAdView);
}
@Override
public void onBindViewHolder(NativeAdViewHolder holder, NativeAd nativeAd) {
final NativeAdView view = (NativeAdView) holder.itemView;
final Ad ad = nativeAd.getAd();
// create ad component
...생략...
final TextView priceText = view.findViewById(R.id.discountedPriceText);
final TextView originalPriceText = view.findViewById(R.id.originalPriceText);
final TextView discountPercentageText = view.findViewById(R.id.discountPercentageText);
final TextView categoryText = view.findViewById(R.id.categoryText);
// data binding
...생략...
final Product product = ad.getProduct();
if (product != null) {
if (product.getDiscountedPrice() != null) {
// 할인이 있는 쇼핑 광고
originalPriceText.setPaintFlags(originalPriceText.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
int percentage = 0;
if (product.getPrice() > product.getDiscountedPrice()) {
percentage = Math.round(((product.getPrice() - product.getDiscountedPrice()) / product.getPrice() * 100));
}
if (percentage > 0) {
priceText.setText(getCommaSeparatedPrice(product.getDiscountedPrice().longValue()));
originalPriceText.setText(getCommaSeparatedPrice((long) product.getPrice()));
discountPercentageText.setText(String.format(Locale.ROOT, "%d%%", percentage));
discountPercentageText.setVisibility(View.VISIBLE);
} else {
priceText.setText(getCommaSeparatedPrice((long) product.getPrice()));
originalPriceText.setText("");
discountPercentageText.setVisibility(View.GONE);
}
} else {
// 할인이 없는 쇼핑 광고
priceText.setText(getCommaSeparatedPrice((long) product.getPrice()));
originalPriceText.setText("");
discountPercentageText.setVisibility(View.GONE);
}
categoryText.setText(product.getCategory());
if (!TextUtils.isEmpty(product.getCategory())) {
categoryText.setVisibility(View.VISIBLE);
}
}
final Collection<View> clickableViews = new ArrayList<>();
clickableViews.add(ctaView);
clickableViews.add(mediaView);
clickableViews.add(descriptionView);
view.setMediaView(mediaView);
view.setClickableViews(clickableViews);
view.setNativeAd(nativeAd);
...생략...
}
private String getCommaSeparatedPrice (long price){
return String.format(Locale.getDefault(), "₩%,d", price);
}
} |
FeedConfig
에 구현한 CustomCPSAdsAdapter
를 설정합니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.cpsAdsAdapterClass(CustomCPSAdsAdapter.class)
.build(); |
Tab 커스터마이징
Feed Tab이 활성화되어 있는 경우, 아래의 방법으로 Tab의 UI를 바꿀 수 있습니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.tabUiEnabled(true) // set tabUiEnabled to true
// tabUiEnabled 를 true 로 설정한 경우, 아래의 속성을 사용
.tabDefaultColor(R.color.YOUR_DEFAULT_COLOR) // 탭이 선택되지 않았을 때 색상 (color resource)
.tabSelectedColor(R.color.YOUR_SELECTED_COLOR) // 탭이 선택되었을 때 색상 (color resource)
.tabBackgroundResId(R.color.YOUR_BACKGROUND_COLOR) // 탭의 배경 색 (color resource)
.tabTextArray(new String[] { FIRST_TAB_NAME, SECOND_TAB_NAME }) // 탭에 들어갈 문구
.build(); |
Filter 커스터마이징
Feed Tab과 Filter가 활성화되어 있는 경우, 아래의 방법으로 Filter의 UI 를 바꿀 수 있습니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.tabUiEnabled(true) // set tabUiEnabled to true
.filterUiEnabled(true) // set filterUiEnabled to true
// tabUiEnabled, filterUiEnabled 를 true 로 설정한 경우, 아래의 속성을 사용
.filterBackgroundDefaultColor(R.color.YOUR_DEFAULT_BG_COLOR) // 필터가 선택되지 않았을 때 배경색
.filterBackgroundSelectedColor(R.color.YOUR_SELECTED_BG_COLOR) // 필터가 선택되었을 때 배경색
.filterTextDefaultColor(R.color.YOUR_DEFAULT_TEXT_COLOR) // 필터가 선택되지 않았을 때 글자색
.filterTextSelectedColor(R.color.YOUR_SELECTED_TEXT_COLOR) // 필터가 선택되었을 때 글자색
.build(); |
CtaView (버튼) 커스터마이징
2.21 버전 이상에서는 아래 방법보다는 Theme을 활용하는 방법을 권장합니다.
BuzzAd SDK 에서 제공하는 CtaView UI 및 처리 로직을 사용하지 않고 기획에 맞게 구현할 경우 다음과 같이 진행할 수 있습니다.
Info |
---|
위에서 안내한 AdsAdapter 의 onBindViewHolder 에서, CtaView 부분을 아래와 같이 수정합니다. |
Code Block |
---|
|
@Override
public void onBindViewHolder(NativeAdViewHolder holder, NativeAd nativeAd) {
final NativeAdView view = (NativeAdView) holder.itemView;
// create ad component
...생략...
final YourCtaView ctaView = view.findViewById(R.id.your_cta_view);
updateCtaStatus(ctaView, nativeAd);
...생략...
final List<View> clickableViews = new ArrayList<>();
clickableViews.add(customizedCtaView);
...생략...
nativeAdView.addOnNativeAdEventListener(new NativeAdView.OnNativeAdEventListener() {
...생략...
@Override
public void onClicked(@NonNull NativeAdView view, @NonNull NativeAd nativeAd) {
updateCtaStatus(ctaView, nativeAd);
}
@Override
public void onParticipated(@NonNull NativeAdView view, @NonNull NativeAd nativeAd) {
updateCtaStatus(ctaView, nativeAd);
}
});
}
// 기획에 따라 수정될 수 있는 예시 코드입니다.
void updateCtaStatus(YourCtaView ctaView, NativeAd nativeAd) {
final String callToAction = nativeAd.getAd().getCallToAction();
final int reward = nativeAd.getAvailableReward();
final int totalReward = nativeAd.getAd().getReward();
final boolean participated = nativeAd.isParticipated();
final boolean isClicked = nativeAd.isClicked();
final boolean isActionType = nativeAd.getAd().isActionType();
if (isClicked && isActionType && !participated) {
ctaView.setCtaText("참여 확인 중");
ctaView.setRewardIcon(null);
ctaView.setRewardText(null);
} else {
if (totalReward > 0 && participated) {
ctaView.setRewardIcon(R.drawable.your_reward_received_icon);
ctaView.setRewardText(null);
ctaView.setCtaText("참여 완료");
} else if (reward > 0) {
ctaView.showRewardImage(R.drawable.your_reward_icon);
ctaView.setRewardText(String.format(Locale.US, "+%,d", reward));
ctaView.setCtaText(callToAction);
} else {
ctaView.showRewardImage(null);
ctaView.setRewardText(null);
ctaView.setCtaText(callToAction);
}
}
} |
베이스 리워드 지급 안내 UI 커스터마이징
베이스 리워드 지급시 유저에게 제공되는 피드백을 커스터마이즈 할 수 있습니다.
DefaultFeedFeedbackHandler
를 상속받는 클래스를 생성합니다.
Code Block |
---|
|
public class CustomFeedFeedbackHandler extends DefaultFeedFeedbackHandler {
@NotNull
public View getBaseRewardNotificationView(@NotNull Context context, int reward) {
// 베이스 리워드 지급시 피드 상단에 보여지는 notification view 입니다.
}
public void onBridgePointBaseRewardReceived(@NotNull Context context, int reward) {
// 베이스 리워드 지급시 브릿지 포인트 연동 중일 경우 호출되는 콜백입니다.
}
} |
FeedConfig
을 초기화할 때, 이전 스텝에서 생성한 CustomFeedFeedbackHandler
를 넘겨줍니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.feedFeedbackHandler(CustomFeedFeedbackHandler.class)
.build(); |
Pop 활성화 버튼 커스터마이징
Feed 진입경로 중 Pop을 연동한 경우 Pop 활성화 버튼이 Feed 지면에 보일 수 있습니다.
Pop은 앱 안에서 지면을 보여주는 Feed 지면을 팝을 통해 유저가 앱 밖에서도 Feed 지면을 경험할 수 있게 합니다.
Pop 을 활성화하지 않은 유저에게 Feed 지면의 우측 하단에 활성화 버튼을 보여주어 유저의 Pop 활성화를 유도합니다.
Pop 활성화 버튼은 아래 가이드에 따라 커스터마이징 가능합니다.
Code Block |
---|
|
class CustomOptInAndShowPopButtonHandler extends DefaultOptInAndShowPopButtonHandler {
// FAB에 보여지는 스트링입니다.
@Override
public String getOptInAndShowPopButtonText(Context context) {
return "YOUR_BUTTON_TEXT"
}
} |
FeedConfig
에 구현한 CustomOptInAndShowPopButtonHandler
를 설정합니다.
Code Block |
---|
|
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID")
...생략...
.optInAndShowPopButtonHandler(CustomOptInAndShowPopButtonHandler.class)
.build(); |