I am making an android app in which RecyclerView consist list of GIFs, Now I want to develop such functionality like when GIF item is completely visible to the user then and then it starts loading, once it slightly looses the focus of user it should stop loading.
I have displayed GIF in recyclerview now the only part remain is how to get that such GIF is fully visible or looses focus. I want such code inside adapter which indicated that this item is fully visible and this item looses focus.
MY Adapter class
public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.MyViewHolder> {
private List<FeedModel> feedList;
Context context;
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.single_item, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
holder.setData(feedList.get(position));
new GifDataDownloader() {
#Override
protected void onPostExecute(final byte[] bytes) {
holder.gifImageView.setBytes(bytes);
holder.gifImageView.startAnimation();
}
}.execute(feedList.get(position).getUrl());
}
public FeedAdapter(Context context, List<FeedModel> feedList) {
this.feedList = feedList;
this.context = context;
}
#Override
public int getItemCount() {
return feedList.size();
}
public void setGridData(ArrayList<FeedModel> feedList) {
this.feedList = feedList;
notifyDataSetChanged();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
FeedModel item;
GifImageView gifImageView;
public MyViewHolder(View itemView) {
super(itemView);
gifImageView = itemView.findViewById(R.id.gifImageView);
}
public void setData(FeedModel item) {
this.item = item;
}
}
My activity
public class MainActivity extends AppCompatActivity {
ImageView imageView;
ImageView imageView1;
private PendingIntent pendingIntent;
RecyclerView recyclerView;
FeedAdapter mAdapter;
ProgressBar progressBar;
GridLayoutManager manager;
Spinner countrySpinner;
ArrayList<FeedModel> feedList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
feedList = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
FeedModel feedModel = new FeedModel();
feedModel.setUrl("https://media.tenor.com/images/925bfbaad2f947987bcf18b9167b3326/tenor.gif");
feedList.add(feedModel);
}
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mAdapter = new FeedAdapter(getApplicationContext(), feedList);
manager = new GridLayoutManager(getApplicationContext(), 1, GridLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(manager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setVisibility(View.VISIBLE);
recyclerView.setAdapter(mAdapter);
}
Better way to used glide to show image and gif etc. it is take all senior that you define .
Add below dependency into app level gradle file
implementation 'com.github.bumptech.glide:glide:4.7.1'
then after used in adapter like this way..
private Context context;
// define context into constructor
public RecyclerviewAdapter(List<StepsItem> stepsItems, Context context) {
this.stepsItems = stepsItems;
this.context = context;
}
Glide.with(context).load(yoururl).into(holder.mIvIcon);
Start gif after your view is created like in onstart() method
And when you want to stop the gif when user is no more interact with app , stop the gif in onpause() method.
Related
I have this code in MainActivity
public class MainActivity extends AppCompatActivity implements MainViewInterface{
RecyclerView rvMovies;
private String TAG = "MainActivity";
MoviesAdapter adapter;
MainPresenter mainPresenter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupMVP();
mainPresenter.getMovies();
rvMovies = (RecyclerView)findViewById(R.id.rvMovies);
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
rvMovies.setLayoutManager(manager);
//rvMovies.setHasFixedSize(true);
}
private void setupMVP() {
mainPresenter = new MainPresenter(this);
}
#Override
public void showToast(String s) {
Toast.makeText(MainActivity.this,s,Toast.LENGTH_LONG).show();
}
#Override
public void displayMovies(MovieResponse moviesResponse) {
if(moviesResponse!=null) {
Log.d(TAG,moviesResponse.getResults().get(1).getTitle());
adapter = new MoviesAdapter(moviesResponse.getResults(), MainActivity.this);
rvMovies.setAdapter(adapter);
adapter.notifyDataSetChanged();
}else{
Log.d(TAG,"Movies response null");
}
}
#Override
public void displayError(String s) {
showToast(s);
}
}
But I get this
E/RecyclerView: No adapter attached; skipping layout
I know other people asked the same question. I tried their solutions but didn't work. What am I missing?
This is my adapter code.
public class MoviesAdapter extends RecyclerView.Adapter<MoviesAdapter.MoviesHolder> {
List<Result> movieList;
Context context;
public MoviesAdapter(List<Result> movieList, Context context){
this.movieList = movieList;
this.context = context;
}
#Override
public MoviesHolder onCreateViewHolder( ViewGroup parent, int viewType) {
View v = LayoutInflater.from(context).inflate(R.layout.row_movies,parent,false);
MoviesHolder mh = new MoviesHolder(v);
return mh;
}
#Override
public void onBindViewHolder( MoviesHolder holder, int position) {
holder.tvTitle.setText(movieList.get(position).getTitle());
holder.tvOverview.setText(movieList.get(position).getOverview());
holder.tvReleaseDate.setText(movieList.get(position).getReleaseDate());
Glide.with(context).load("https://image.tmdb.org/t/p/w500/"+movieList.get(position).getPosterPath()).into(holder.ivMovie);
}
#Override
public int getItemCount() {
return movieList.size();
}
public class MoviesHolder extends RecyclerView.ViewHolder {
TextView tvTitle,tvOverview,tvReleaseDate;
ImageView ivMovie;
public MoviesHolder(View itemView) {
super(itemView);
tvTitle = (TextView) itemView.findViewById(R.id.tvTitle);
tvOverview = (TextView) itemView.findViewById(R.id.tvOverView);
tvReleaseDate = (TextView) itemView.findViewById(R.id.tvReleaseDate);
ivMovie = (ImageView) itemView.findViewById(R.id.ivMovie);
}
}
Thanks,
Theo.
SOLVED
In my activivity_main.xml I had both height and with equal to 0dp!!! I honestly don't know how and who put those values in!! That's why the recycler view was not visible!!!
I think you should call rvMovies.setAdapter(new MoviesAdapter(new ArrayList<>())) on your RecyclerView during onCreate - there is nothing bad about setting an empty (from items perspective) adapter on RecyclerView, it will simply show an empty list. Then once you have your data ready (downloaded/loaded from any source you are loading from) you will simply call something like adapter.setItems(newItemsList) and adapter.notifyDataSetChanged().
Also one more note: you should not store Context in class member fields as leaks might occur (I am pretty sure your Android Studio must complain about that as well in a form of a Lint Warning check). You actually don't even need that Context instance passing to your adapter as you can simply retrieve a Context instance from parent View in onCreateViewHolder - rewrite it like this:
#Override
public MoviesHolder onCreateViewHolder( ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_movies,parent,false);
MoviesHolder mh = new MoviesHolder(v);
return mh;
}
These 2 lines:
setupMVP();
mainPresenter.getMovies();
must be written after:
rvMovies = (RecyclerView)findViewById(R.id.rvMovies);
you need to set adapter after you set the views.
rvMovies = (RecyclerView)findViewById(R.id.rvMovies);
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
rvMovies.setLayoutManager(manager);
//rvMovies.setHasFixedSize(true);
//call after setting the view
mainPresenter.getMovies();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupMVP();
rvMovies = (RecyclerView)findViewById(R.id.rvMovies);
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
rvMovies.setLayoutManager(manager);
rvMovies.setVisibility(View.VISIBLE);
mainPresenter.getMovies(); <== put this line in last
}
I am making a mobile application using React Native and included list components didn't have high enough performance for it so I started using Android's RecyclerView as the list component. There is a problem though with it. The RecyclerView doesn't update its contents views until I scroll or change RecyclerView's size. What could cause this problem and how I can fix it? I have tried notifyDatasetChanged, notifyItemChanged, forceLayout, invalidate, postInvalidate and many different variations with each.
Try this one this.setIsRecyclable(true);
It will referesh your views
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private ArrayList<String> mSingleItemLists = new ArrayList<>();
private SingleListItemAdapter mSingleListItemAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view_single_item);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(linearLayoutManager);
setDummyData();
}
private void setDummyData() {
for (int i = 0; i <= 30; i++)
mSingleItemLists.add("item" + i);
}
#Override
protected void onResume() {
super.onResume();
mSingleListItemAdapter = new SingleListItemAdapter(mSingleItemLists);
mRecyclerView.setAdapter(mSingleListItemAdapter);
}
class SingleListItemAdapter extends RecyclerView.Adapter<SingleListItemAdapter.SingleListItemHolder> {
private ArrayList<String> mSingleItemLists;
private SingleListItemAdapter(ArrayList<String> singleItemLists) {
mSingleItemLists = singleItemLists;
//You can do notifydatasetchange if u r having any saved value
}
#Override
public SingleListItemAdapter.SingleListItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View inflatedView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.row_recyclerview, parent, false);
return new SingleListItemHolder(inflatedView);
}
#Override
public void onBindViewHolder(SingleListItemAdapter.SingleListItemHolder holder, int position) {
holder.mItemDate.setText(mSingleItemLists.get(position));
}
#Override
public int getItemCount() {
return mSingleItemLists.size();
}
class SingleListItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mItemDate;
SingleListItemHolder(View v) {
super(v);
mItemDate = (TextView) v.findViewById(R.id.textview_recycler_list_item);
v.setOnClickListener(this);
this.setIsRecyclable(true); // This will help u
}
#Override
public void onClick(View v) {
//do your stuff
notifyDataSetChanged();
}
}
}
}
For showing my strings in a list I used RecyclerView, my strings are big and persian so for beatiful UI I want to show them justified. For that I used Text-Justify library. Now I have a beautiful justified text.
BUT because of heavy calculation of DocumentView (in simple: a justified TextView) my RecyclerView doesn't scroll smoothly and has lagging. So I decided to put these calculation before showing RecyclerView and instead show a progress. So I put setAdapter in an AsyncTask and create DocumentView from my strings but in runtime I get error that I don't be allowed to add view in another thread instead of main thread.
If I set the width and Height of DocumentView to somthing constant my RecyclerView scrolls very beautifully. How Can I calculate DocumentView width and height in another Thread so I can show a progress to user while calculating width and height.
private class myAdapter extends RecyclerView.Adapter<ItemViewHolder> {
private Context context;
private List<Item> items;
private ArrayList<DocumentView> justifiedBodies;
public ItemAdapter(Context context, List<Item> items){
this.items = items;
this.context = context;
justifiedBodies = new ArrayList<>();
for(Item item:Items){
Spanned span = Html.fromHtml(item.getBody());
justifiedBodies.add(createDocumentView(span, DocumentView.FORMATTED_TEXT));
}
}
#Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.
from(parent.getContext()).
inflate(R.layout.my_layout, parent, false);
return new ItemViewHolder(itemView);
}
#Override
public void onBindViewHolder(final ItemViewHolder holder, int position) {
final Item item = items.get(position);
DocumentView justifiedDocumentView = justifiedBodies.get(position);
if(justifiedDocumentView.getParent() != null){
((ViewGroup)justifiedDocumentView.getParent()).removeView(justifiedDocumentView);
}
holder.justifiedBody.removeAllViews();
holder.justifiedBody.addView(justifiedDocumentView);
}
#Override
public int getItemCount() {
return items.size();
}
}
public DocumentView createDocumentView(Spanned article, int type) {
final DocumentView documentView = new DocumentView(getActivity(), type);
documentView.getDocumentLayoutParams().setAntialias(true);
documentView.getDocumentLayoutParams().setHyphenated(false);
documentView.getDocumentLayoutParams().setMaxLines(100);
documentView.getDocumentLayoutParams().setOffsetX(10);
documentView.getDocumentLayoutParams().setOffsetY(10);
documentView.getDocumentLayoutParams().setTextSubPixel(true);
documentView.getDocumentLayoutParams().setWordSpacingMultiplier(5.0f);
documentView.getDocumentLayoutParams().setTextColor(0xff000000);
documentView.getDocumentLayoutParams().setTextTypeface(ResourceManager.iranTF);
documentView.getDocumentLayoutParams().setTextSize(23);
documentView.getDocumentLayoutParams().setTextAlignment(TextAlignment.JUSTIFIED);
documentView.getDocumentLayoutParams().setInsetPaddingLeft(30f);
documentView.getDocumentLayoutParams().setInsetPaddingRight(30f);
documentView.getDocumentLayoutParams().setInsetPaddingTop(30f);
documentView.getDocumentLayoutParams().setInsetPaddingBottom(30f);
documentView.getDocumentLayoutParams().setLineHeightMultiplier(1f);
documentView.getDocumentLayoutParams().setReverse(true);
documentView.setText(article);
// I want to calculate width and height and write them here
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(CALCULATED_WIDTH,
CALCULATED_HEIGHT);
documentView.setLayoutParams(layoutParams);
documentView.setCacheConfig(DocumentView.CacheConfig.AUTO_QUALITY);
return documentView;
}
public static class ItemViewHolder extends RecyclerView.ViewHolder
{
protected FrameLayout justifiedBody;
public ItemViewHolder(View itemView) {
super(itemView);
justifiedBody = (FrameLayout) itemView.findViewById(R.id.justified_body);
}
}
And this is my AsyncTask which fails:
class uiDecore extends AsyncTask<String, String, String>{
private ItemAdapter itemAdapter;
private ArrayList<Item> items;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressView.start();
}
#Override
protected String doInBackground(String... params) {
// init Cards
items = DataSrc.getSelectedItem(); // query from my database
recyclerView = (RecyclerView) getActivity().findViewById(R.id.cards_recyclerview);
llm = new LinearLayoutManager(getActivity());
llm.setOrientation(LinearLayoutManager.VERTICAL);
// I don't allow this because I'm not in main thread!
recyclerView.setLayoutManager(llm);
itemAdapter = new ItemAdapter(getActivity(), items);
// in run time i get error becuse I allocate a new DocumentView
recyclerView.setAdapter(itemAdapter);
recyclerView.setHasFixedSize(true);
return null;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
progressView.stop();
}
}
I call uiDecore.execute in OnActivityCreated of my fragment.
I am using RecyclerView with GridLayoutManager as a layout for the bus ticket booking. Everything works fine but except the place where I stuck with the logics that I have to use to leave more spacing between 3 and 4 column item in all the rows. Basically my layout should be like as the screenshot attached below.
My Requirement:
Current Output:
I am also posting my complete RecyclerView GridLayoutManager for your reference as follows
MultiSelectRecyclerViewAdapter.java
public class MultiSelectRecyclerViewAdapter extends SelectableAdapter<MultiSelectRecyclerViewAdapter.ViewHolder> {
private ArrayList<String> mArrayList;
private Context mContext;
private ViewHolder.ClickListener clickListener;
public MultiSelectRecyclerViewAdapter (Context context, ArrayList<String> arrayList,ViewHolder.ClickListener clickListener) {
this.mArrayList = arrayList;
this.mContext = context;
this.clickListener = clickListener;
}
// Create new views
#Override
public MultiSelectRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(
R.layout.row_multiselect, null);
ViewHolder viewHolder = new ViewHolder(itemLayoutView,clickListener);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.tvName.setText(mArrayList.get (position));
viewHolder.selectedOverlay.setVisibility(isSelected(position) ? View.VISIBLE : View.INVISIBLE);
}
#Override
public int getItemCount() {
return mArrayList.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener,View.OnLongClickListener {
public TextView tvName;
private ClickListener listener;
private final View selectedOverlay;
public ViewHolder(View itemLayoutView,ClickListener listener) {
super(itemLayoutView);
this.listener = listener;
tvName = (TextView) itemLayoutView.findViewById(R.id.tvName);
selectedOverlay = (View) itemView.findViewById(R.id.selected_overlay);
itemLayoutView.setOnClickListener(this);
itemLayoutView.setOnLongClickListener (this);
}
#Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClicked(getAdapterPosition ());
}
}
#Override
public boolean onLongClick (View view) {
if (listener != null) {
return listener.onItemLongClicked(getAdapterPosition ());
}
return false;
}
public interface ClickListener {
public void onItemClicked(int position);
public boolean onItemLongClicked(int position);
}
}
}
MultiSelectRecyclerViewActivity.java
public class MultiSelectRecyclerViewActivity extends AppCompatActivity implements MultiSelectRecyclerViewAdapter.ViewHolder.ClickListener {
private Toolbar toolbar;
private RecyclerView mRecyclerView;
private MultiSelectRecyclerViewAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private ArrayList<String> mArrayList = new ArrayList<String>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView(R.layout.activity_multiselect);
toolbar = (Toolbar) findViewById(R.id.toolbar);
for (int i = 1; i <= 48; i++) {
mArrayList.add ( ""+ i);
}
if (toolbar != null) {
setSupportActionBar(toolbar);
getSupportActionBar().setTitle("MultiSelectRecylcerView");
}
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new GridLayoutManager (this,4));
mAdapter = new MultiSelectRecyclerViewAdapter (MultiSelectRecyclerViewActivity.this,mArrayList,this);
mRecyclerView.setAdapter (mAdapter);
}
#Override
public void onItemClicked (int position) {
toggleSelection(position);
}
#Override
public boolean onItemLongClicked (int position) {
toggleSelection(position);
return true;
}
private void toggleSelection(int position) {
mAdapter.toggleSelection (position);
}
}
What I have tried in coding:
I have followed this gist to assign the spacing for my grid items using ItemDecoration as below. But unfortunately I could able to assign spacing only equally between all the items.
mRecyclerView.setLayoutManager(new GridLayoutManager(context, NUM_COLUMNS);
ItemOffsetDecoration itemDecoration = new ItemOffsetDecoration(context, R.dimen.item_offset);
mRecyclerView.addItemDecoration(itemDecoration);
Kindly please help me with any kind of tips and solutions to solve my case. I am completely stuck up how to proceed with this requirement and logic using RecyclerView which I considered should be the optimized approach. Thanks in advance.
Using item decoration here could create some problems. If you are planning to handle click on the item, clicking on blank space would be interpreted as a click on items located in inner columns.
You can configure GridLayoutManager for handling one more column and handle space as a specific view type. Here I put code implementing this approach.
Hope this could help.
Here is my adapter class
public class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.ViewHolder> {
List<CategoryItem> mItems;
public CategoryAdapter(Context context) {
super();
Resources res = context.getResources();
mItems = new ArrayList<>();
mItems.add(new CategoryItem(res.getString(R.string.category_book), R.drawable.books));
// ... few items
mItems.add(new CategoryItem(res.getString(R.string.category_clothing), R.drawable.clothes));
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_category_selection, viewGroup, false);
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
CategoryItem category = mItems.get(i);
viewHolder.title.setText(category.getTitle());
viewHolder.thumbnail.setImageResource(category.getThumbnail());
}
#Override
public int getItemCount() {
return mItems.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public ImageView thumbnail;
public CardView card;
public ViewHolder(View itemView){
super(itemView);
card = (CardView)itemView.findViewById(R.id.card_view);
title = (TextView)itemView.findViewById(R.id.category_title);
thumbnail = (ImageView)itemView.findViewById(R.id.category_thumbnail);
}
}
}
My fragment
public class CategorySelectionFragment extends Fragment {
RecyclerView mRecyclerView;
RecyclerView.LayoutManager mLayoutManager;
RecyclerView.Adapter mAdapter;
public CategorySelectionFragment() { }
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_category_selection, container, false);
mRecyclerView = (RecyclerView)v.findViewById(R.id.recycler_view);
setUpGrid();
return v;
}
private void setUpGrid() {
mRecyclerView.setHasFixedSize(true);
colNum = 3;
mLayoutManager = new GridLayoutManager(getActivity(), colNum);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new CategoryAdapter(getActivity());
mRecyclerView.setAdapter(mAdapter);
}
When fragment just created (onCreateView called) recyclerview works perfectly, but when app goes to background and then restore scroll start to freeze on the first few items and then continue to work smoothly.
When app restore from background onCreateView method is not called so I try to set up adapter for recyclerview in onStart(), like this:
#Override
public void onStart(){
super.onStart();
// Adapter setting here to avoid freezes, when start to scroll after switching to an app from background.
mRecyclerView.setAdapter(mAdapter);
}
This approach has solved the problem, but it seems like not realy clear way to solve that issue. Since for example, restoring the fragment leads to reset last scrolled position.
Thanks for your help!