Ads in recyclerview

╄→гoц情女王★ 提交于 2020-08-26 13:42:19

问题


I have an app that stores some data in room database. At first, my adapter was like that:

public class ViewCourseAdapter extends ListAdapter<Course, ViewCourseAdapter.ViewCourseHolder> {

private int previousPosition = 0;

public ViewCourseAdapter() {

    super(DIFF_CALLBACK);

}

private static final DiffUtil.ItemCallback<Course> DIFF_CALLBACK = new DiffUtil.ItemCallback<Course>() {
    @Override
    public boolean areItemsTheSame(@NonNull Course oldItem, @NonNull Course newItem) {
        return oldItem.getId() == newItem.getId();
    }

    @Override
    public boolean areContentsTheSame(@NonNull Course oldItem, @NonNull Course newItem) {
        return oldItem.getfName().equals(newItem.getfName());
    }
};

@NonNull
@Override
public ViewCourseHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View itemView = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.view_courses_item, parent, false);
    return new ViewCourseHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull ViewCourseHolder holder, int position) {

    Course currentCourse = getItem(position);

    String fullName = currentCourse.getfName() + " " + currentCourse.getlName();
    SpannableString SfullName = new SpannableString(fullName);
    SfullName.setSpan(new UnderlineSpan(), 0, fullName.length(), 0);

    holder.text_view_firstName_1.setText(SfullName);

    if (position > previousPosition) {
        AnimationUtil.animate(holder, true);
    } else {
        AnimationUtil.animate(holder, false);
    }

    previousPosition = position;

}

class ViewCourseHolder extends RecyclerView.ViewHolder {

    private TextView text_view_firstName_1;

    ViewCourseHolder(@NonNull View itemView) {
        super(itemView);

        text_view_firstName_1 = itemView.findViewById(R.id.text_view_firstName_1);

    }

}

@Override
public int getItemCount() {
    return super.getItemCount();
}

And everything was working perfect. Now I want to show ads inside recyclerview, so I changed my adapter:

public class ViewCourseAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private static final int COURSE_VIEW_TYPE = 0;
private static final int AD_VIEW_TYPE = 1;

private List<Course> data;
private List<Object> ad;
private Context context;
private LayoutInflater layoutInflater;

private int previousPosition = 0;

public ViewCourseAdapter(Context context, List<Object> ad) {
    this.data = new ArrayList<>();
    this.context = context;
    this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    this.ad = ad;
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    switch (viewType ) {
        case COURSE_VIEW_TYPE:
            View courseView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_courses_item, parent, false);
            return new ViewCourseViewHolder(courseView);
        case AD_VIEW_TYPE:
            default:
                View adView = LayoutInflater.from(parent.getContext()).inflate(R.layout.native_ads, parent, false);
                return new AdViewHolder(adView);
    }

}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

    int viewType = getItemViewType(position);

    switch (viewType) {
        case COURSE_VIEW_TYPE:

            ViewCourseViewHolder holder1 = (ViewCourseViewHolder) holder;
            Course currentCourse = (Course) data.get(position);

            String fullName = currentCourse.getfName() + " " + currentCourse.getlName();
            SpannableString SfullName = new SpannableString(fullName);
            SfullName.setSpan(new UnderlineSpan(), 0, fullName.length(), 0);

            holder1.text_view_firstName_1.setText(SfullName);

            break;

        case AD_VIEW_TYPE:

        default:

            AdViewHolder bannerHolder = (AdViewHolder) holder;
            AdView adView = (AdView) ad.get(position);
            ViewGroup adCardView = (ViewGroup) bannerHolder.itemView;

            if (adCardView.getChildCount() > 0) {
                adCardView.removeAllViews();
            }
            if (adView.getParent() != null) {
                ((ViewGroup) adView.getParent()).removeView(adView);
            }

            adCardView.addView(adView);
    }



    if (position > previousPosition) {
        AnimationUtil.animate(holder, true);
    } else {
        AnimationUtil.animate(holder, false);
    }

    previousPosition = position;
}

class AdViewHolder extends RecyclerView.ViewHolder {

    AdViewHolder(View view) {
        super(view);
    }
}

@Override
public int getItemViewType(int position) {
    return (position % ViewCoursesActivity.ITEMS_PER_AD == 0) ? AD_VIEW_TYPE
            : COURSE_VIEW_TYPE;
}

@Override
public int getItemCount() {
    return data.size();
}

public void setData(List<Course> newData) {
    if (data != null) {
        CourseDiffCallback courseDiffCallback = new CourseDiffCallback(data, newData);
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(courseDiffCallback);

        data.clear();
        data.addAll(newData);
        diffResult.dispatchUpdatesTo(this);
    } else {
        data = newData;
    }


}

public class ViewCourseViewHolder extends RecyclerView.ViewHolder {

    private TextView text_view_firstName_1;

    ViewCourseViewHolder(@NonNull View itemView) {
        super(itemView);

        text_view_firstName_1 = itemView.findViewById(R.id.text_view_firstName_1);

    }

}

private class CourseDiffCallback extends DiffUtil.Callback {

    private final List<Course> oldItem, newItem;

    private CourseDiffCallback(List<Course> oldItem, List<Course> newItem) {
        this.oldItem = oldItem;
        this.newItem = newItem;
    }

    @Override
    public int getOldListSize() {
        return oldItem.size();
    }

    @Override
    public int getNewListSize() {
        return newItem.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldItem.get(oldItemPosition).getId() == newItem.get(newItemPosition).getId();
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldItem.get(oldItemPosition).getfName().equals(newItem.get(newItemPosition).getfName());
    }
}

And my activity is:

public class ViewCoursesActivity extends AppCompatActivity {

private CourseViewModel courseViewModel;

// A banner ad is placed in every 8th position in the RecyclerView.
public static final int ITEMS_PER_AD = 8;

private static final String AD_UNIT_ID = "ca-app-pub-3940256099942544/6300978111";

// The RecyclerView that holds and displays banner ads and menu items.
private RecyclerView recyclerView;

// List of banner ads and MenuItems that populate the RecyclerView.
private List<Object> data = new ArrayList<>();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_view_courses);

    recyclerView = findViewById(R.id.recycler_view_3);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));

    addBannerAds();
    loadBannerAds();

    final ViewCourseAdapter adapter = new ViewCourseAdapter(this, data);
    recyclerView.setAdapter(adapter);

    courseViewModel = ViewModelProviders.of(this).get(CourseViewModel.class);
    courseViewModel.setIdForCourse(0);
    //courseViewModel.getCourseByStudentId().observe(this, adapter::submitList);
    courseViewModel.getCourseByStudentId().observe(this, adapter::setData);

}

@Override
protected void onResume() {
    for (Object item : data) {
        if (item instanceof AdView) {
            AdView adView = (AdView) item;
            adView.resume();
        }
    }
    super.onResume();
}

@Override
protected void onPause() {
    for (Object item : data) {
        if (item instanceof AdView) {
            AdView adView = (AdView) item;
            adView.pause();
        }
    }
    super.onPause();
}

@Override
protected void onDestroy() {
    for (Object item : data) {
        if (item instanceof AdView) {
            AdView adView = (AdView) item;
            adView.destroy();
        }
    }
    super.onDestroy();
}

/**
 * Adds banner ads to the items list.
 */
private void addBannerAds() {
    // Loop through the items array and place a new banner ad in every ith position in
    // the items List.
    for (int i = 0; i <= data.size(); i += ITEMS_PER_AD) {
        final AdView adView = new AdView(ViewCoursesActivity.this);
        adView.setAdSize(AdSize.SMART_BANNER);
        adView.setAdUnitId(AD_UNIT_ID);
        data.add(i, adView);
    }
}

/**
 * Sets up and loads the banner ads.
 */
private void loadBannerAds() {
    // Load the first banner ad in the items list (subsequent ads will be loaded automatically
    // in sequence).
    loadBannerAd(0);
}

/**
 * Loads the banner ads in the items list.
 */
private void loadBannerAd(final int index) {

    if (index >= data.size()) {
        return;
    }

    Object item = data.get(index);
    if (!(item instanceof AdView)) {
        throw new ClassCastException("Expected item at index " + index + " to be a banner ad"
                + " ad.");
    }

    final AdView adView = (AdView) item;

    // Set an AdListener on the AdView to wait for the previous banner ad
    // to finish loading before loading the next ad in the items list.
    adView.setAdListener(new AdListener() {
        @Override
        public void onAdLoaded() {
            super.onAdLoaded();
            // The previous banner ad loaded successfully, call this method again to
            // load the next ad in the items list.
            loadBannerAd(index + ITEMS_PER_AD);
        }

        @Override
        public void onAdFailedToLoad(int errorCode) {
            // The previous banner ad failed to load. Call this method again to load
            // the next ad in the items list.
            Log.e("ViewCoursesActivity", "The previous banner ad failed to load. Attempting to"
                    + " load the next banner ad in the items list.");
            loadBannerAd(index + ITEMS_PER_AD);
        }
    });

    // Load the banner ad.
    adView.loadAd(new AdRequest.Builder().build());
}

After that I have two problems: 1. At frist position it display an ad instead of first item from my database, and first item is nowhere. If I check my database first item is there, just not apear in recyclerview. 2. If I want to scroll items, the app crash, and the error is "java.lang.IndexOutOfBoundsException: Index: 8, Size: 1". What am I doing wrong? Please help me. Thank you!


回答1:


Fundamentally, a RecyclerView is used to display a List to the user. Use this to guide your implementation.

Currently, you have two lists:

private List<Course> data;
private List<Object> ad;

You need to find a way to "collate" these down to a single list. Maybe something like this:

private static List<Object> collate(List<Course> data, List<Object> ads) {
    List<Object> collated = new ArrayList<>();

    while (data.size() > 0 || ads.size() > 0) {
        for (int i = 0; i < ITEMS_PER_AD && data.size() > 0; ++i) {
            collated.add(data.remove(0));
        }

        if (ads.size() > 0) {
            collated.add(ads.remove(0));
        }
    }

    return collated;
}

With that method defined, you can replace your existing constructor:

private List<Course> data;
private List<Object> ad;
// ...

public ViewCourseAdapter(Context context, List<Object> ad) {
    this.data = new ArrayList<>();
    this.ad = ad;
    // ...
}

with this:

private List<Object> ad;
private List<Object> collated;
// ...

public ViewCourseAdapter(Context context, List<Object> ad) {
    this.ad = ad;
    this.collated = new ArrayList<>(ad);
    // ...
}

And you can replace your existing setData() method:

public void setData(List<Course> newData) {
    if (data != null) {
        CourseDiffCallback courseDiffCallback = new CourseDiffCallback(data, newData);
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(courseDiffCallback);

        data.clear();
        data.addAll(newData);
        diffResult.dispatchUpdatesTo(this);
    } else {
        data = newData;
    }
}

with this:

public void setData(List<Course> newData) {
    if (data != null) {
        List<Object> newCollated = collate(ad, newData);
        // setup DiffUtil callback here...

        this.collated = newCollated;
        // dispatch DiffUtil result here...
    }
}

Now you will have a single list that you can use in your adapter. Then you can change your adapter's methods to use that one list:

@Override
public int getItemCount() {
    return collated.size();
}
@Override
public int getItemViewType(int position) {
    return (collated.get(position) instanceof Course) 
            ? COURSE_VIEW_TYPE
            : AD_VIEW_TYPE;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    if (holder.getItemViewType() == COURSE_VIEW_TYPE) {
        bindCourseHolder((ViewCourseViewHolder) holder, position);
    } else if (holder.getItemViewType() == AD_VIEW_TYPE) {
        bindAdHolder((AdViewHolder) holder, position);
    }
}

private void bindCourseHolder(ViewCourseViewHolder holder, int position) {
    // ...
}

private void bindAdHolder(AdViewHolder holder, int position) {
    // ...
}

All of these changes are based on the fundamental concept that you just have a single list, and that list holds (potentially) many different types of things.




回答2:


Thank you @Ben P. for advice...I can't believe I didn't see this...so I modify collate() method like this:

while (data.size() > 0) {
        collated.addAll(ad);
        for (int i = 0; i < ITEMS_PER_AD && data.size() > 0; ++i) {
            collated.add(data.remove(0));
        }
}
ad.remove(0);
return collated;

It is there a better way to do this?



来源:https://stackoverflow.com/questions/58626477/ads-in-recyclerview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!