/
3.3. 디자인 커스터마이징

3.3. 디자인 커스터마이징

개요

Feed의 각 컴포넌트는 다음과 같이 구분합니다.

FeedToolbarHolder, AdsAdapter, FeedHeaderViewAdapter class의 custom class를 생성할때 다음 조건중 하나를 사용해야합니다.

  • 각 클래스는 inner class 가 아니어야 합니다.

  • Inner class로 생성을 해야할 경우, public static class로 선언이 되어야 합니다.

위의 조건에 맞지 않는 경우, class를 찾지못하는 현상이 발생하여 customization이 적용되지 않습니다.

툴바 커스터마이징

Feed 상단의 툴바를 커스터마이징할 수 있습니다.

  1. FeedToolbarHolderinterface를 구현하는 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 } }

     

  2. 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 상단의 헤더 영역을 커스터마이징하여, 유저에게 피드가 어떤 공간인지 상세히 안내할 수 있습니다.

  1. 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 } }

     

  2. FeedConfigFeedHeaderViewAdapter를 설정합니다.

    final FeedConfig feedConfig = new FeedConfig.Builder(context, "YOUR_FEED_UNIT_ID") ...생략... .feedHeaderViewAdapterClass(CustomFeedHeaderViewAdapter.class) .build();

광고 아이템 영역 커스터마이징 - 일반 광고

  1. 아래 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>

     

  2. onCreateViewHolder에서 NativeAdViewHolderNativeAdView를 설정합니다.

  3.  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 커스터마이징

베이스 리워드 지급시 유저에게 제공되는 피드백을 커스터마이즈 할 수 있습니다.

  1. 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) { // 베이스 리워드 지급시 브릿지 포인트 연동 중일 경우 호출되는 콜백입니다. } }
  2. 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();

Related content