I can't seem to figure out why I haven't been able to load any sort of image (I've tried multiple URLs). I've tried using an AsyncTask class, and the issue still hasn't been resolved. My adapter class is listed below, any sort of help would be greatly appreciated.
I'm trying to load an image (in this test case, the same image) for each RecyclerView entry. The default view (an orange square) appears when I don't attempt to set an image derived from URL, but if I do attempt it, the ImageView is simply left blank.
EntryAdapter
public class EntryAdapter extends RecyclerView.Adapter<EntryAdapter.EntryViewHolder> {
private Entry[] mDataset;
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public static class EntryViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public String source;
public TextView textContent, title, label, author;
public ImageView thumbnail;
public CardView topCard, mainCard;
public EntryViewHolder(View v) {
super(v);
title = v.findViewById(R.id.title);
textContent = v.findViewById(R.id.textContent);
topCard = v.findViewById(R.id.top_card);
mainCard = v.findViewById(R.id.main_card);
thumbnail = mainCard.findViewById(R.id.thumbnail);
author = mainCard.findViewById(R.id.author);
label = topCard.findViewById(R.id.label);
}
}
// Provide a suitable constructor (depends on the kind of dataset)
public EntryAdapter(Entry[] myDataset) {
mDataset = myDataset;
}
// Create new views (invoked by the layout manager)
#Override
public EntryAdapter.EntryViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.entry_item_constraints, parent, false);
EntryViewHolder vh = new EntryViewHolder(v);
return vh;
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(EntryViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.title.setText(mDataset[position].getTitle());
holder.label.setText(mDataset[position].getLabel());
holder.textContent.setText(mDataset[position].getTextContent());
holder.thumbnail.setImageDrawable(LoadImageFromWebOperations(mDataset[position].getThumbnail()));
holder.author.setText(mDataset[position].getAuthor());
//new DownloadImageTask(holder.thumbnail).execute(mDataset[position].getThumbnail());
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return mDataset.length;
}
public static Drawable LoadImageFromWebOperations(String url) {
try {
InputStream is = (InputStream) new URL(url).getContent();
Drawable d = Drawable.createFromStream(is, "reddit_thumbnail");
return d;
} catch (Exception e) {
return null;
}
}
}
Try to use Glide to show images in RecyclerView which handles lazy loading for scrolling the list smoother. Also it cancels loading operation when the list is scrolled to make the performance and resource consumption better.
Glide.with(holder.thumbnail.getContext())
.load(mDataset[position].getThumbnail())
.into(holder.thumbnail);
In app level build.gradle:
repositories {
mavenCentral()
google()
}
dependencies {
implementation 'com.github.bumptech.glide:glide:4.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
}
You can also use placeholder() method to specify an image (it may point to a drawable using resource id for example) to show it when the target image is not downloaded or unavailable.
Related
I'm making an app using TMDB API and have gotten stuck at a small issue.
TMDB API shows seasons and episodes which are empty, basically, those are yet to air but since those are empty, the app shows a blank item that I'm trying to get rid of.
Here's my adapter:
public class SeasonAdapter extends RecyclerView.Adapter<SeasonAdapter.ViewHolder> {
private final List<Season> seasons;
private final Context context;
private final RequestOptions requestOptions;
public SeasonAdapter(List<Season> seasons, Context context) {
this.seasons = seasons;
this.context = context;
requestOptions = new RequestOptions().centerCrop().placeholder(R.drawable.poster_placeholder).error(R.drawable.poster_placeholder);
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_season_item, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
final Season season = seasons.get(position);
holder.tvTitle.setText(season.getSeasonTitle());
if (season.getSeasonDate() != null && !season.getSeasonDate().isEmpty()) {
holder.tvDate.setText(context.getResources().getString(R.string.aired_on) + season.getSeasonDate());
} else {
holder.tvDate.setVisibility(View.GONE);
}
if (season.getSeasonEpisodes() == 0) {
seasons.remove(position);
}
holder.tvEpisodes.setText(String.valueOf(season.getSeasonEpisodes()) + context.getResources().getString(R.string.total_episodes));
Glide.with(context).load(season.getSeasonImageURL()).apply(requestOptions).into(holder.ivPoster);
holder.itemView.setOnClickListener(v -> {
Intent intent = new Intent(context, EpisodeActivity.class);
intent.putExtra("title", season.getShowTitle());
intent.putExtra("seasonTitle", season.getSeasonTitle());
intent.putExtra("seasonNo", season.getSeasonNo());
intent.putExtra("tvId", season.getTvId());
v.getContext().startActivity(intent);
});
}
#Override
public int getItemCount() {
return seasons.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView ivPoster;
public TextView tvTitle, tvDate, tvEpisodes;
public ViewHolder(#NonNull View itemView) {
super(itemView);
ivPoster = itemView.findViewById(R.id.ivSeasonPoster);
tvTitle = itemView.findViewById(R.id.tvSeasonTitle);
tvDate = itemView.findViewById(R.id.tvSeasonAired);
tvEpisodes = itemView.findViewById(R.id.tvSeasonEpisodes);
//Poster Corners
ivPoster.setClipToOutline(true);
}
}
}
I tried doing this:
if (season.getSeasonEpisodes() == 0) {
seasons.remove(position);
}
It does seem to hide the season which has no episodes but if a show has multiple seasons without episodes, my app crashes so I figured this isn't the right solution so any help is appreciated.
I suggest performing that removal logic in the constructor of the adapter rather than in onBind. onBind happens as the recycler view is finalising the details of each view holder immediately before it's shown to the user. You want to do as little as possible logic in here to keep the recycler view performant.
Inside the constructor (or even before the list is passed in) you should perform a loop and remove those items that don't meet the criteria before setting the instance variable.
It's been a long time since I wrote code in java and so I'd end up with unhelpful incorrect syntax if I tried to do it here.
I am loading images from URL into horizontal recyclerview using Picasso as:
public class pIndicatorAdaptor extends
RecyclerView.Adapter<pIndicatorAdaptor.IndicatorViewHolder> {
//constructor and other stuff
#Override
public IndicatorViewHolder onCreateViewHolder(ViewGroup parent, int vType) {
View view = layoutInflater.inflate(R.layout.i_item, parent, false);
return new IndicatorViewHolder(view);
}
#Override
public void onBindViewHolder(IndicatorViewHolder holder, int position) {
picasso.load(images.get(position).getSrc()).fit().centerCrop()
.into(holder.imageView, null);
}
class IndicatorViewHolder extends RecyclerView.ViewHolder {
ImageView imageView;
IndicatorViewHolder(#NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.imageView);
}
}
}
But I get the result like this,The bottom horizontal recyclerview is where I am loading images:
when you keep scroll the recycler view like that,it's load the image every time and ofcourse it took alot of time to load the image.i think you should use glide on this because glide is really good on load the image from cache ( if exist) ;)
I'm new to android, but have a good JavaFX experience. I'm trying to create a custom view that i can reuse, but having a hard time figuring out the correct way to do it.
In javafx i could achieve this by: Creating a separate fxml file defining the layout of the custom view, then create a controller class linked to the fxml file, in that class, i'd have a method to retrieve the data model of the controller and use it to fill in the labels, etc.
The custom view i want would be
Constrained Layout
TextView (constrained to right anchor)
Round TextView (constrained to left anchor)
What is the best way to do this in android? Also, Is it possible to achieve this with a RecyclerView? If yes, how can i use a custom view for each item and set its data?
The question is broad. You may need additional research on creating views
Create a recyclerview in the main.xml,
a separate file with an item view.
You have 3 views in your item view - white background with margins (linearlayout?), right textView, and left textview.
The left textview should have android:background="drawable/round_shape" and round_shape.xml defined in your drawables folder. Everything is done in 3 xml files, main.xml for recyclerview, item.xml, round_background.xml. Then, the recyclerview adapter to bind the textviews with your array, and recyclerview initialization
A typical RV adaptor
public class MyRV extends RecyclerView.Adapter<MyRV.ViewHolder> {
private List<MyModelItemWith2Strings> mDataSet; // You may need to setup an array,
// with 2 String objects - for the right and left textviews
// Use an array of class with 2 elements rather than <String>, e.g. List<MyModelItemWith2Strings>
// pass your model here
// this setData will be used to provide the contents for the textviews
void setData(List< /* set your 2 string class here*/ > dataSet) {
mDataSet = dataSet;
}
static class ViewHolder extends RecyclerView.ViewHolder {
// Here you bind item TV's
// first you declare textviews that you will use to fill with data
// Add any other item views you will need to fill in
public TextView tv;
public TextView tv2;
public ViewHolder(LinearLayout v) {
super(v);
// Bind itemview views here. Put R.id.tv from your itemview.xml
tv = v.findViewById(R.id.....);
tv2 = v...
}
}
// Add your itemview layout here
#Override
public MyRV.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(/***R.layout.item_view***/, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder( MyRV.ViewHolder h, int position) {
// get content from your model (the above list) and fill in the the itemview textviews
String a= mDataSet.get(position).getItem1();
String b = mDataSet.get(position). getItem2();
...
h.tv.setText(a);
// set clickers if you want to. The clicker class is below.
h.tv.setOnClickListener(new Click(position));
h.tv2.setText(...)
}
// This is obligatory to pass for your RV to initialize. It won't work if you don' t tell Android how to count your array soze
#Override
public int getItemCount() {
return mDataSet.size();
}
// These are my implementation of clickers. I prefer to put them in the nested class of the adapter.
private class Click implements OnClickListener {
private int pos;
Click(int position) {
pos = position;
}
#Override
public void onClick(View p1) {
// get data from your array on click
mDataSet.get(pos);
// Use pos as position on the array, mData.get(pos)
}
}
}
Then, in your main class set a recyclerview
RecyclerView rv = (RecyclerView) findViewById(R.id.rv_In_Main_Xml);
// just additional tunings.
rv.setHasFixedSize(true);
rv.setLayoutManager(new LinearLayoutManager(context)); // <- context = this, if you are in the Main activity
Then set the adapter
MyRV rva = new MyRV();
rva.setData(myArray_with_2_string_objects_to_fill_tvs);
rv.setAdaptor(rva);
And your recycler view gets filled with data
I have a RecyclerView which gets populated by a RealmRecyclerViewAdapter but somehow there are no animations playing when the data changes.
The adapter class uses multiple ViewHolders for different layouts but that should not affect animations right?
public class DiaryPageEntryAdapter extends RealmRecyclerViewAdapter<Entry, RecyclerView.ViewHolder> {
static class MealEntryViewHolder extends RecyclerView.ViewHolder {
#BindView(R.id.item_diary_entry_drink_title) TextView tvMealEntryTitle;
#BindView(R.id.item_diary_entry_meal_time) TextView tvMealEntryTime;
BindView(R.id.item_diary_entry_meal_bullet_list) RecyclerView rvBulletList;
MealEntryBulletAdapter bulletAdapter;
public MealEntryViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
this.bulletAdapter = new MealEntryBulletAdapter();
rvBulletList.setLayoutManager(new LinearLayoutManager(itemView.getContext()));
rvBulletList.setAdapter(this.bulletAdapter);
}
void bindData(MealEntry mealEntry) {
tvMealEntryTitle.setText(mealEntry.getTitle());
tvMealEntryTime.setText(DateTimeUtils.timeValueToText(itemView.getContext(), mealEntry.getTime()));
this.bulletAdapter.updateData(mealEntry.getConsumedMeals() ,mealEntry.getConsumedDrinks());
}
}
// Other ViewHolders
public DiaryPageEntryAdapter(#Nullable OrderedRealmCollection<Entry> data, boolean autoUpdate) {
super(data, autoUpdate, true);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case MEAL_ENTRY:
// Inflate meal entry layout and then create a new meal view holder with it
View rowMeal = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_diary_entry_meal, parent, false);
return new MealEntryViewHolder(rowMeal);
// Other case options.
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Entry entry = getItem(position);
if(Entry.isMealEntry(entry)) {
MealEntryViewHolder mealHolder = (MealEntryViewHolder) holder;
mealHolder.bindData(Entry.getMealEntryFromEntry(entry));
}
// Other if branches.
}
#Override
public int getItemViewType(int position) {
Entry entry = getItem(position);
if(Entry.isMealEntry(entry)) return MEAL_ENTRY;
// Other if branches.
}
The code for setting up the RecyclerView and adapter looks as following:
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.frag_diary_page_rv_entries);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
entryAdapter = new DiaryPageEntryAdapter(null, true);
recyclerView.setAdapter(entryAdapter);
The code that binds the data to the adapter is stated below:
RealmResults<Entry> sortedEntries = diaryEntry.getEntries()
.where()
.findAllSortedAsync("time");
entryAdapter.updateData(sortedEntries);
The auto-updates work fine but somehow no there is no animation when the data changes. A new entry simply appears but without animations. Furthermore I want a short animation to play once the RecyclerView is populated for the first time - an entrance animation similar to [http://anthony-skr.com/article/recyclerview-items-animation-with-rebound-effect][1].
Note: In my appĀ“s build.gradle file
dependencies {
// Other dependencies
compile 'io.realm:android-adapters:2.1.0'
}
In the Project build.gradle file:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
classpath "io.realm:realm-gradle-plugin:3.1.4"
}
From your code sample, it isn't easy to see where you call entryAdapter.updateDate() from, but calling this method continuously on every update will disable all animations as it just calls notifyDataSetChanged().
A more standard pattern would look like this:
RealmResults<Entry> sortedEntries = diaryEntry.getEntries()
.where()
.findAllSortedAsync("time");
entryAdapter = new DiaryPageEntryAdapter(sortedEntries, true);
recyclerView.setAdapter(entryAdapter);
Animations should work if you do the above.
So, I've been doing xml layout for a project that involves getting a horizontal scrollable row of images on a screen, and did so using just a horizontalscrollview. and a bunch of imagebuttons. I used an include to put this on another xml layout page and another programmer will then populate the images dynamically.
My question is, how would the gallery control benefit us? I haven't done much Java programming and I've seen some instruction online of how to implement this control, but not a lot on WHY you would use this. It looks like this control works mainly via Java insertion via array, but other than that I can't tell what the benefits are from reading over my way of just creating the layout and having this other programmer insert his own images manually.
Another related question - do these images for a gallery need to me imageviews, or can they be imagemaps? Currently they are imagemaps because we want them to be clicable to go to a user's profile, etc.
Thanks!
Gallery is nearly perfect. In one of my projects I do have a LinearLayout with a Gallery in it:
<Gallery
android:id="#+id/gallery"
android:layout_height="0dip"
android:layout_weight="1"
android:layout_width="fill_parent"
android:spacing="2dip" />
An activity implements OnItemClickListener:
public class MyActivity extends Activity implements OnItemClickListener {
A data structure contains all items and is send to an adapter:
private void processGallery() {
adapter = new MyAdapter(this, containers, appName);
if (adapter != null) {
gallery.setAdapter(adapter);
}
}
#Override
public void onItemClick(final AdapterView<?> adapterView, final View view, final int position, final long id) {
if (containers != null) {
container = containers.get(position);
if (container != null) {
// Handle selected image
}
}
}
The adapter is a usual BaseAdapter - nothing magic:
public class MyAdapter extends BaseAdapter {
private ArrayList<Container> containers;
private Context context;
public int getCount() {
return containers.size();
}
public Object getItem(final int position) {
return containers.get(position);
}
public long getItemId(final int position) {
return position;
}
public View getView(final int position, final View contentView, final ViewGroup viewGroup) {
ImageView imageView = new ImageView(context);
Container container = containers.get(position);
if (container != null) {
// Do your image thing here
}
return imageView;
}
public MyAdapter(final Context context, final ArrayList<Container> containers, final String appName) {
this.context = context;
this.containers = containers;
}
}
This simple code gives a horizontal scrolling image gallery with clickable items. The click is send to the activity - no need to do something fancy in the adapter. I removed from the code shown here a DrawableCache that I use because my items do come from the web.