개요
Feed의 각 컴포넌트는 다음과 같이 구분합니다.
FeedToolbarHolder, AdsAdapter, FeedHeaderViewAdapter class의 custom class를 생성할때 다음 조건중 하나를 사용해야합니다.
각 클래스는 inner class 가 아니어야 합니다.
Inner class로 생성을 해야할 경우, public static class로 선언이 되어야 합니다.
위의 조건에 맞지 않는 경우, class를 찾지못하는 현상이 발생하여 customization이 적용되지 않습니다.
툴바 커스터마이징
Feed 상단의 툴바를 커스터마이징할 수 있습니다.
FeedToolbarHolder
interface를 구현하는 Class 를 생성합니다.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
를 설정합니다.final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID") ...생략... .feedToolbarHolderClass(CustomFeedToolbarHolder.class) .build();
툴바 Height 변경
커스터마이징한 FeedToolbarHolder
가 들어가는 layout의 height값이 기본값으로 고정되어 있습니다.
아래 가이드를 통해 툴바의 height 값을 수정할 수 있습니다.
// AndroidManifest.xml ...생략... <activity android:name="com.buzzvil.buzzad.benefit.presentation.feed.FeedBottomSheetActivity" android:theme="@style/MyAppTheme" tools:replace="android:theme"/> ...생략...
// styles.xml <style name="MyAppTheme" parent=...> ...생략... <!-- Customize your theme here. --> <item name="actionBarSize">DESIRED_ACTION_BAR_HEIGHT</item> ...생략... </style>
헤더 커스터마이징
Feed 상단의 헤더 영역을 커스터마이징하여, 유저에게 피드가 어떤 공간인지 상세히 안내할 수 있습니다.
FeedHeaderViewAdapter
interface 를 구현하는 Class 를 생성합니다.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
를 설정합니다.final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID") ...생략... .feedHeaderViewAdapterClass(CustomFeedHeaderViewAdapter.class) .build();
광고 아이템 영역 커스터마이징 - 일반 광고
아래
NativeAdView
을 포함하는 레이아웃(your_feed_ad.xml
)을 구현합니다.
NativeAdView의 레이아웃// 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 등을 추가할 수 있습니다.
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
를 설정합니다.
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID") ...생략... .adsAdapterClass(CustomAdsAdapter.class) .build();
광고 아이템 영역 커스터마이징 - 쇼핑 적립 광고 (CPS)
커스터마이징 방식은 일반 광고와 같습니다. 하지만 쇼핑 적립 광고는 일반 광고에 비해 많은 정보를 제공합니다. 일반 광고용 레이아웃에서 priceText
, originalPriceText
, discountPercentageText
, categoryText
가 추가되었습니다. 따라서 위 4가지 컴포넌트에 대해 추가로 데이터를 처리해야합니다.
NativeAdView(쇼핑 적립 광고) 의 레이아웃
// 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>
위에서 안내한 AdsAdapter 를 참고하여 수정합니다.
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
를 설정합니다.
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID") ...생략... .cpsAdsAdapterClass(CustomCPSAdsAdapter.class) .build();
Tab 커스터마이징
Feed Tab이 활성화되어 있는 경우, 아래의 방법으로 Tab의 UI를 바꿀 수 있습니다.
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 를 바꿀 수 있습니다.
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 및 처리 로직을 사용하지 않고 기획에 맞게 구현할 경우 다음과 같이 진행할 수 있습니다.
위에서 안내한 AdsAdapter 의 onBindViewHolder
에서, CtaView 부분을 아래와 같이 수정합니다.
@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
를 상속받는 클래스를 생성합니다.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
를 넘겨줍니다.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 활성화 버튼은 아래 가이드에 따라 커스터마이징 가능합니다.
활성화 버튼의 색상과 아이콘은 테마 적용을 통해 변경할 수 있습니다.
활성화 버튼의 텍스트는
DefaultOptInAndShowPopButtonHandler
를 상속받는 클래스를 생성하여 변경할 수 있습니다.
class CustomOptInAndShowPopButtonHandler extends DefaultOptInAndShowPopButtonHandler { // FAB에 보여지는 스트링입니다. @Override public String getOptInAndShowPopButtonText(Context context) { return "YOUR_BUTTON_TEXT" } }
FeedConfig
에 구현한 CustomOptInAndShowPopButtonHandler
를 설정합니다.
final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID") ...생략... .optInAndShowPopButtonHandler(CustomOptInAndShowPopButtonHandler.class) .build();