Prevent RecyclerView showing previous content when scrolling - java

I have a RecyclerView with a GridLinearLayout and a custom adapter. The content of each item is a picture downloaded using a json and parsing it.
Basically is a grid of pictures.
Everything works almost fine, but however, when scrolling down the content, and the going up again, it shows the previous views in each item for less than a second, and then show the proper picture again.
What could I do to prevent or fix this? Thanks in advance for any help and/or guidance you could provide.
This is the adapter code:
package jahirfiquitiva.project.adapters;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import jahirfiquitiva.project.activities.WallpapersActivity;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.Ion;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import jahirfiquitiva.project.R;
public class WallpapersAdapter extends RecyclerView.Adapter<WallpapersAdapter.WallsHolder> {
public interface ClickListener {
void onClick(WallsHolder view, int index, boolean longClick);
}
private ArrayList<HashMap<String, String>> data;
private final Context context;
private boolean usePalette = true;
private final ClickListener mCallback;
private final Map<String, Palette> mPaletteCache = new WeakHashMap<>();
public WallpapersAdapter(Context context, ClickListener callback) {
this.context = context;
this.data = new ArrayList<>();
this.mCallback = callback;
}
public void setData(ArrayList<HashMap<String, String>> data) {
this.data = data;
notifyDataSetChanged();
}
#Override
public WallsHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
return new WallsHolder(inflater.inflate(R.layout.wallpaper_item, parent, false));
}
#Override
public void onBindViewHolder(final WallsHolder holder, int position) {
Animation anim = AnimationUtils.loadAnimation(context, android.R.anim.fade_in);
HashMap<String, String> jsondata = data.get(position);
holder.name.setText(jsondata.get(WallpapersActivity.NAME));
final String wallurl = jsondata.get(WallpapersActivity.WALL);
holder.wall.startAnimation(anim);
holder.wall.setTag(wallurl);
Ion.with(context)
.load(wallurl)
.asBitmap()
.setCallback(new FutureCallback<Bitmap>() {
#Override
public void onCompleted(Exception e, Bitmap result) {
holder.progressBar.setVisibility(View.GONE);
if (e != null) {
e.printStackTrace();
} else if (holder.wall.getTag() != null && holder.wall.getTag().equals(wallurl)) {
holder.wall.setImageBitmap(result);
if (usePalette) {
Palette p;
if (mPaletteCache.containsKey(wallurl)) {
p = mPaletteCache.get(wallurl);
} else {
p = new Palette.Builder(result).generate();
mPaletteCache.put(wallurl, p);
}
if (p != null) {
Palette.Swatch wallSwatch = p.getVibrantSwatch();
if (wallSwatch != null) {
holder.titleBg.setBackgroundColor(wallSwatch.getRgb());
holder.titleBg.setAlpha(1);
holder.name.setTextColor(wallSwatch.getTitleTextColor());
holder.name.setAlpha(1);
}
}
}
}
}
});
}
#Override
public int getItemCount() {
return data.size();
}
public class WallsHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
public final View view;
public final ImageView wall;
public final TextView name;
public final ProgressBar progressBar;
public final LinearLayout titleBg;
WallsHolder(View v) {
super(v);
view = v;
wall = (ImageView) v.findViewById(R.id.wall);
name = (TextView) v.findViewById(R.id.name);
progressBar = (ProgressBar) v.findViewById(R.id.progress);
titleBg = (LinearLayout) v.findViewById(R.id.titlebg);
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
#Override
public void onClick(View v) {
int index = getLayoutPosition();
if (mCallback != null)
mCallback.onClick(this, index, false);
}
#Override
public boolean onLongClick(View v) {
int index = getLayoutPosition();
if (mCallback != null)
mCallback.onClick(this, index, true);
return false;
}
}
}

Overwrite onViewRecycled (VH holder) to set the image to null.
Like this:
public void onViewRecycled (VH holder) {
holder.wall.setImageBitmap(null);
}

As the name suggests RecyclerView recycles the views to optimize memory so it displays the content of previous view. Since you are loading the image from internet it takes little time to load the image so the content of previous image could be observed. You can do one of the following things.
1) Set a default image from local resource before loading actual image, preferably a small size image to preserve memory.something like this
//load default image first
holder.wall.setImageResource(R.id.your_default_image_resource);
//load actual image
Ion.with(context)
.load(wallurl)
.asBitmap()
.setCallback(new FutureCallback<Bitmap>() {
#Override
public void onCompleted(Exception e, Bitmap result) {
holder.progressBar.setVisibility(View.GONE);
if (e != null) {
e.printStackTrace();
} else if (holder.wall.getTag() != null && holder.wall.getTag().equals(wallurl)) {
holder.wall.setImageBitmap(result);
if (usePalette) {
Palette p;
if (mPaletteCache.containsKey(wallurl)) {
p = mPaletteCache.get(wallurl);
} else {
p = new Palette.Builder(result).generate();
mPaletteCache.put(wallurl, p);
}
if (p != null) {
Palette.Swatch wallSwatch = p.getVibrantSwatch();
if (wallSwatch != null) {
holder.titleBg.setBackgroundColor(wallSwatch.getRgb());
holder.titleBg.setAlpha(1);
holder.name.setTextColor(wallSwatch.getTitleTextColor());
holder.name.setAlpha(1);
}
}
}
}
}
});
2) Set the ImageView visibility to GONE/INVISIBLE before loading the image and make it VISIBLE again after the image is loaded.
//hide the imageview
holder.wall.setVisibility(View.INVISIBLE);
Ion.with(context)
.load(wallurl)
.asBitmap()
.setCallback(new FutureCallback<Bitmap>() {
#Override
public void onCompleted(Exception e, Bitmap result) {
holder.progressBar.setVisibility(View.GONE);
if (e != null) {
e.printStackTrace();
} else if (holder.wall.getTag() != null && holder.wall.getTag().equals(wallurl)) {
//show the imageview and set bitmap
holder.wall.setVisibility(View.VISIBLE);
holder.wall.setImageBitmap(result);
if (usePalette) {
Palette p;
if (mPaletteCache.containsKey(wallurl)) {
p = mPaletteCache.get(wallurl);
} else {
p = new Palette.Builder(result).generate();
mPaletteCache.put(wallurl, p);
}
if (p != null) {
Palette.Swatch wallSwatch = p.getVibrantSwatch();
if (wallSwatch != null) {
holder.titleBg.setBackgroundColor(wallSwatch.getRgb());
holder.titleBg.setAlpha(1);
holder.name.setTextColor(wallSwatch.getTitleTextColor());
holder.name.setAlpha(1);
}
}
}
}
}
});

I think you should add place holder like this:
Ion.with(context).load("http://example.com/image.png")
.withBitmap()
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.error_image)
.intoImageView(imageView);
or set default image at first.
holder.wall.setImageResource(R.drawable.placeholder_image);

Related

How to manage exoplayer with java in android studio?

I am searching Exoplayer using with ViewPager in java but can not find the full tutorial.
I have issue for stopping and pausing exoplayer while swiping another video.
Is there any tutorial please suggest me.
Adapter.java
package com.daasuu.gpuvideoandroid.Adapter;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.daasuu.gpuvideoandroid.Activity.OtherUser.OtherUserProfileActivity;
import com.daasuu.gpuvideoandroid.Model.MainVideoDataModel;
import com.daasuu.gpuvideoandroid.R;
import com.daasuu.gpuvideoandroid.Utils.VideoCache;
import com.daasuu.gpuvideoandroid.databinding.ItemMainVideoBinding;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.ui.PlayerView;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import java.util.List;
public class MainVideoAdapter2 extends RecyclerView.Adapter<MainVideoAdapter2.MyViewHolder> {
private Context context;
private List<MainVideoDataModel> data;
private boolean likeFlag = false;
private boolean followFlag = false;
private boolean isPlaying = false;
ExoPlayer exoplayer;
StyledPlayerView styledPlayerView;
public MainVideoAdapter2(Context context, List<MainVideoDataModel> data) {
this.context = context;
this.data = data;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemMainVideoBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.item_main_video, parent, false);
return new MyViewHolder(binding);
}
#Override
public void onBindViewHolder(MainVideoAdapter2.MyViewHolder holder, int position) {
MainVideoDataModel model = data.get(position);
holder.setExoplayerStyled(model.getVideo_url());
}
#Override
public int getItemCount() {
return data.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ItemMainVideoBinding binding;
public MyViewHolder(ItemMainVideoBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.imageUserMainVideo.setOnClickListener(this);
binding.videoView.setOnClickListener(this);
}
// styled exoplayer with cache
void setExoplayerStyled(String video_url){
binding.progressCircular.setVisibility(View.VISIBLE);
exoplayer = new ExoPlayer.Builder(context).build();
styledPlayerView = binding.videoStyledPlayerView;
styledPlayerView.hideController();
styledPlayerView.setUseController(false);
ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(
new CacheDataSource.Factory()
.setCache(VideoCache.getInstance2(context))
.setUpstreamDataSourceFactory(new DefaultHttpDataSource.Factory()
.setUserAgent("rgcache"))
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
).createMediaSource(MediaItem.fromUri(video_url));
exoplayer.seekTo(0);
exoplayer.prepare();
exoplayer.pause();
styledPlayerView.setPlayer(exoplayer);
exoplayer.setPlayWhenReady(false);
binding.videoStyledPlayerView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
#Override
public void onViewAttachedToWindow(View view) {
styledPlayerView.setKeepScreenOn(true);
exoplayer.addMediaSource(mediaSource);
exoplayer.prepare();
exoplayer.setPlayWhenReady(true);
exoplayer.setRepeatMode(exoplayer.REPEAT_MODE_ONE);
if(binding.videoStyledPlayerView.getPlayer() != null) {
binding.videoStyledPlayerView.getPlayer().play();
}
}
#Override
public void onViewDetachedFromWindow(View view) {
binding.videoStyledPlayerView.getPlayer().stop();
binding.videoStyledPlayerView.getPlayer().clearMediaItems();
exoplayer.seekTo(0);
exoplayer.setPlayWhenReady(false);
exoplayer.stop();
if(binding.videoStyledPlayerView.getPlayer() != null) {
binding.videoStyledPlayerView.getPlayer().pause();
}
}
});
//buffering
exoplayer.addListener(new Player.Listener() {
#Override
public void onIsLoadingChanged(boolean isLoading) {
Player.Listener.super.onIsLoadingChanged(isLoading);
// Log.e("onIsLoadingChanged", String.valueOf(isLoading));
}
#Override
public void onPlaybackStateChanged(int playbackState) {
Player.Listener.super.onPlaybackStateChanged(playbackState);
Log.e("onPlaybackStateChanged", String.valueOf(playbackState));
binding.imgVideoStop.setVisibility(View.GONE);
if (playbackState == 3){
binding.progressCircular.setVisibility(View.GONE);
}
}
#Override
public void onPlayWhenReadyChanged(boolean playWhenReady, int reason) {
Player.Listener.super.onPlayWhenReadyChanged(playWhenReady, reason);
}
});
// touch listener play & pause
binding.videoStyledPlayerView.getVideoSurfaceView().setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (isPlaying == false){
binding.imgVideoStop.setVisibility(View.VISIBLE);
isPlaying = true;
exoplayer.setPlayWhenReady(false);
}else{
binding.imgVideoStop.setVisibility(View.GONE);
isPlaying = false;
styledPlayerView.setKeepScreenOn(true);
styledPlayerView.setPlayer(exoplayer);
exoplayer.addMediaSource(mediaSource);
exoplayer.prepare();
exoplayer.setPlayWhenReady(true);
exoplayer.setRepeatMode(exoplayer.REPEAT_MODE_ONE);
}
}
});
}
//on click event
#Override
public void onClick(View v) {
if (v == binding.imageUserMainVideo) {
context.startActivity(new Intent(context, OtherUserProfileActivity.class));
}
if (v == binding.videoView) {
Log.e("isPlaying = " , String.valueOf(isPlaying));
if (isPlaying) {
binding.videoView.pause();
// binding.imgVideoStop.setVisibility(View.VISIBLE);
isPlaying = false;
} else {
binding.videoView.start();
// binding.imgVideoStop.setVisibility(View.GONE);
isPlaying = true;
}
}
}
}
}
item.xml
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="#+id/videoStyledPlayerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:animation_enabled="true"/>
Issues:
After a specific position, it plays previous videos when swipe to next video.
Plays multiple videos on swipe
On swipe back(previous videos) don't stops previous video.
How to stop when to redirect another activity.
Please suggest the solution.
You can use addOnPageChangeListener for getting the user interaction for the view pager. add this code snippet in the onCreate method.In my project ,I handle it like this ::
binding!!.mediaViewPager.addOnPageChangeListener(object : OnPageChangeListener {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
if (exoPlayer.isPlaying) {
exoPlayer.pause()
}
}
override fun onPageSelected(position: Int) {
if (exoPlayer.isPlaying) {
exoPlayer.pause()
}
}
override fun onPageScrollStateChanged(state: Int) {
exoPlayer.pause()
}
})
Play Exo Player With PageViwer
Link:
https://github.com/google/ExoPlayer/issues/7947
Example:
https://github.com/kakajika/PlayerPagerExample

How to set Item position in order after using ItemTouchHelper.Callback

I am creating a document arrangement activity with RecyclerView. I want to arrange the document with drag and drop. It was done by using ItemTouchHelper.Callbackbut after that I can't set the page number after the OnItemMove callback. what should I do?
EDIT: added code snippet
package adapters;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.pdf.PdfRenderer;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import com.mobilix.docscanner.R;
import java.util.ArrayList;
import java.util.Collections;
import helper.ItemTouchHelperAdapter;
import helper.ItemTouchHelperViewHolder;
import helper.OnStartDragListener;
import helper.SimpleItemTouchHelperCallback;
public class PageAjdustAdapter extends RecyclerView.Adapter<PageAjdustAdapter.PageAdjustHolder> implements ItemTouchHelperAdapter, OnStartDragListener {
private final String TAG = getClass().getName();
Context mContext;
ArrayList<PdfPage> pdfPages = new ArrayList<>();
private ItemTouchHelper itemTouchHelper;
public PageAjdustAdapter(Context context, ArrayList<PdfPage> pages) {
this.mContext = context;
pdfPages = pages;
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(this);
itemTouchHelper = new ItemTouchHelper(callback);
itemTouchHelper.attachToRecyclerView(((Activity) context).findViewById(R.id.rcvPageArrange));
}
#NonNull
#Override
public PageAdjustHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_page_adajustment, parent, false);
return new PageAdjustHolder(view);
}
#Override
public void onBindViewHolder(#NonNull PageAdjustHolder holder, int position) {
Log.d(TAG, "onBindViewHolder: ");
holder.ivPage.setImageBitmap(pdfPages.get(position).bitmap);
holder.cbPage.setChecked(pdfPages.get(position).isSelected);
holder.tvPageNo.setText(String.valueOf(position + 1));
holder.cbPage.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
pdfPages.get(holder.getAdapterPosition()).isSelected = isChecked;
}
});
holder.ivRotate.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int pos = holder.getAdapterPosition();
pdfPages.get(pos).bitmap = Bitmap.createBitmap(//
pdfPages.get(pos).bitmap, 0, 0, pdfPages.get(pos).bitmap.getWidth(),//
pdfPages.get(pos).bitmap.getHeight(), pdfPages.get(pos).matrix, true);//
notifyItemChanged(pos);
}
});
}
#Override
public int getItemCount() {
return pdfPages.size();
}
#Override
public boolean onItemMove(int fromPosition, int toPosition) {
Log.d(TAG, "onItemMove: ->fp " + (fromPosition + 1) + " tp-> " + (toPosition + 1));
Collections.swap(pdfPages, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
return false;
}
#Override
public void onItemDismiss(int position) {
pdfPages.remove(position);
notifyItemRemoved(position);
}
#Override
public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
itemTouchHelper.startDrag(viewHolder);
}
public static class PageAdjustHolder extends RecyclerView.ViewHolder implements
ItemTouchHelperViewHolder {
ImageView ivPage, ivRotate;
CheckBox cbPage;
TextView tvPageNo;
public PageAdjustHolder(#NonNull View itemView) {
super(itemView);
ivPage = itemView.findViewById(R.id.ivPage);
ivRotate = itemView.findViewById(R.id.ivRotate);
cbPage = itemView.findViewById(R.id.cbPage);
tvPageNo = itemView.findViewById(R.id.tvPageNo);
}
#Override
public void onItemSelected() {
}
#Override
public void onItemClear() {
}
}
public static class PdfPage {
PdfRenderer.Page page;
Bitmap bitmap;
boolean isSelected;
Matrix matrix;
int rotate = 0;
public PdfPage(PdfRenderer.Page page, Bitmap bitmap) {
this.page = page;
this.bitmap = bitmap;
matrix = new Matrix();
rotate += 90;
matrix.postRotate(90);//martix work on +=90
}
}
}
You have to call notifyDataSetChanged() when an item position is changed. The easiest way it to used onItemClear(), it will be called when an item is de-selected. Add it like following.
#Override
public void onItemClear() {
notifyDataSetChanged();
}
One thing you have to add is check weather the position is actually changed after the drag operation or not other wise it will always update the whole dataset whenever an item is selected and than de-selected.
Edit
Create a local variable in view-holder class. Than you just have to set it in onItemSelected() and check it in onItemClear();
#Override
public void onItemSelected() {
lastpos = getAdapterPosition();
}
#Override
public void onItemClear() {
if(lastpos != getAdapterPosition())
notifyDataSetChanged();
}

pagination in recyclerview in android

I'm trying to implement pagination in recyclerview.
Following are my java classes:
MyAdapter.java
package com.example.opinion;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by manish on 7/18/2016.
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private ArrayList<String> movieName = new ArrayList();
public MyAdapter(ArrayList film)
{
this.movieName = film;
}
#Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
// create a new view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_items, null);
// set the view's size, margins, paddings and layout parameters
ViewHolder vh = new ViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
holder.movieText.setText(movieName.get(position));
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount()
{
System.out.print(movieName.size());
return movieName.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
// each data item is just a string in this case
public TextView movieText;
public ViewHolder(View v) {
super(v);
movieText = (TextView) v.findViewById(R.id.movieName);
}
}
}
viewfragment.java : here i'm fetching the data on my local network. In this file the loadMore(current_page) function only fetches 3 values at time. The first time when it is called, those three values are only visible in the cardview inside recyclerview. I don't know where the problem is!
package com.example.opinion;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Text;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.content.Intent;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Path;
public class ViewFragment extends Fragment {
private static final String PREFS_NAME = "Prefname";
public int j =0;
static String quesText;
//public static final String BASE_URL = "http://192.168.0.104/";
public interface PostquesApi{
#GET("ques/{i}")
Call<quesGetResponse> quesget(#Path("i") String i);
}
static class quesGetResponse {
String ques1;
String ques2;
String ques3;
String quid1;
String quid2;
String quid3;
public quesGetResponse(String ques1, String ques2, String ques3, String u1, String u2,String u3){
this.ques1 = ques1;
this.ques2 = ques2;
this.ques3 = ques3;
this.quid1 = u1;
this.quid2 = u2;
this.quid3 = u3;
}
}
//View rootView;
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private ArrayList movieList = new ArrayList();
private static int current_page = 1;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_view, container, false);
for(int k=0; k<2; k++) {
loadData(current_page);
}
System.out.println("contents of movie list: "+ movieList);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.my_recycler_view);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
// use a linear layout manager
LinearLayoutManager feedLayoutManager = new LinearLayoutManager(getActivity());
mAdapter = new MyAdapter(movieList);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(feedLayoutManager);
// specify an adapter (see also next example)
mRecyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(
feedLayoutManager) {
#Override
public void onLoadMore(int current_page) {
// do somthing...
loadMoreData(current_page);
}
});
/*qt2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent comm = new Intent(getActivity(), Comments.class);
quesText = qt2.getText().toString();
//comm.putExtra(quesText, Text );
startActivity(comm);
}
});
qt3.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent comm = new Intent(getActivity(), Comments.class);
quesText = qt3.getText().toString();
//comm.putExtra(quesText, Text );
startActivity(comm);
}
});*/
return rootView;
}
public void loadData(int page) {
// Send an API request to retrieve appropriate data using the offset value as a parameter.
// --> Deserialize API response and then construct new objects to append to the adapter
// --> Notify the adapter of the changes
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Login.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
PostquesApi apiService = retrofit.create(PostquesApi.class);
Call<quesGetResponse> call = apiService.quesget(Integer.toString(j));
call.enqueue(new Callback<quesGetResponse>() {
#Override
public void onResponse(Call<quesGetResponse> call, Response<quesGetResponse> response) {
quesGetResponse decodedResponse = response.body();
if (decodedResponse != null) {
if (decodedResponse.ques1 != null && decodedResponse.quid1.compareTo(Login.user_id) != 0)
{
movieList.add(decodedResponse.ques1);
}
if (decodedResponse.ques2 != null && decodedResponse.quid2.compareTo(Login.user_id) != 0)
{
movieList.add(decodedResponse.ques2);
}
if (decodedResponse.ques3 != null && decodedResponse.quid3.compareTo(Login.user_id) != 0)
{
movieList.add(decodedResponse.ques3);
}
}
if (decodedResponse.ques1 == null || decodedResponse.ques2 == null || decodedResponse.ques3 == null) {
Context context = getActivity().getApplicationContext();
CharSequence text = "Sorry!! no more questions in the database...";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
j = j + 3;
}
#Override
public void onFailure(Call<quesGetResponse> call, Throwable t) {
}
});
}
private void loadMoreData(int current_page) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Login.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
PostquesApi apiService = retrofit.create(PostquesApi.class);
Call<quesGetResponse> call = apiService.quesget(Integer.toString(j));
call.enqueue(new Callback<quesGetResponse>() {
#Override
public void onResponse(Call<quesGetResponse> call, Response<quesGetResponse> response) {
quesGetResponse decodedResponse = response.body();
if (decodedResponse != null) {
if (decodedResponse.ques1 != null && decodedResponse.quid1.compareTo(Login.user_id) != 0)
{
movieList.add(decodedResponse.ques1);
}
if (decodedResponse.ques2 != null && decodedResponse.quid2.compareTo(Login.user_id) != 0)
{
movieList.add(decodedResponse.ques2);
}
if (decodedResponse.ques3 != null && decodedResponse.quid3.compareTo(Login.user_id) != 0)
{
movieList.add(decodedResponse.ques3);
}
}
if (decodedResponse.ques1 == null || decodedResponse.ques2 == null || decodedResponse.ques3 == null) {
Context context = getActivity().getApplicationContext();
CharSequence text = "Sorry!! no more questions in the database...";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
}
j = j + 3;
}
#Override
public void onFailure(Call<quesGetResponse> call, Throwable t) {
}
});
mAdapter.notifyDataSetChanged();
}
}
EndlessRecyclerOnScrollListener.java: The file that implements the logic for pagination.
package com.example.opinion;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
/**
* Created by manish on 7/20/2016.
*/
public abstract class EndlessRecyclerOnScrollListener extends
RecyclerView.OnScrollListener {
public static String TAG = EndlessRecyclerOnScrollListener.class
.getSimpleName();
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 3;
int firstVisibleItem, visibleItemCount, totalItemCount;
private int current_page = 1;
private LinearLayoutManager mLinearLayoutManager;
public EndlessRecyclerOnScrollListener(
LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = mLinearLayoutManager.getItemCount();
firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading
&& (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
current_page++;
onLoadMore(current_page);
loading = true;
}
}
public abstract void onLoadMore(int current_page);
}
If someone can review it and tell me the corrections needed in the above files or can direct me to some resource, it will be really appreciated.
I would suggest instead of using EndlessRecyclerOnScrollListener class and listening to the Recyclerview's scroll event you should watch inside MyAdapter class.
1. First add one interface
public interface GetNewDataEvents {
void getNewAdapterData();
}
2. Initialize that interface in MyAdapter class from where you are adding your data by implementing it.
3. you can easily find which item is being loading currently in bindViewHolder
using
if(position==(movieName.size()-1)
and in it you can easily call that interface method and load new data in it.
This thing denotes that second last item is being currently loading and you should add new data if any.
If you get new data you can easily load it into your adapter and call notifyItemRangeChanged with it to get performance and implementing best practices by not loading whole adapter with only few items changed.
if you want to take reference for it you can go here

RecyclerView lags on scrolling

I'm having an issue where when the RecyclerView has a big amount of items (say 2000) the scrolling is really laggy.
I would be really really thankful if someone helps me improving it. Thanks in advance.
EDIT: The lag may be caused when using this FastScroll library. If someone is able to do some pull requests to improve it, I'm sure the dev will be really thankful. https://github.com/plusCubed/recycler-fast-scroll
Here's the Fragment code:
package jahirfiquitiva.apps.iconshowcase.fragments;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import java.util.ArrayList;
import java.util.Locale;
import jahirfiquitiva.apps.iconshowcase.R;
import jahirfiquitiva.apps.iconshowcase.adapters.IconsAdapter;
import jahirfiquitiva.apps.iconshowcase.utilities.Preferences;
import jp.wasabeef.recyclerview.adapters.AlphaInAnimationAdapter;
import jp.wasabeef.recyclerview.adapters.ScaleInAnimationAdapter;
public class IconsFragment extends Fragment {
private IconsAdapter mAdapter;
private Preferences mPrefs;
private ArrayList<String> iconsNames, filteredIconsList;
private ArrayList<Integer> iconsInts, filteredIconsInts;
private ViewGroup layout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mPrefs = new Preferences(getActivity());
if (layout != null) {
ViewGroup parent = (ViewGroup) layout.getParent();
if (parent != null) {
parent.removeView(layout);
}
}
try {
layout = (ViewGroup) inflater.inflate(R.layout.icons_grid, container, false);
} catch (InflateException e) {
}
RecyclerFastScroller fastScroller =
(RecyclerFastScroller) layout.findViewById(R.id.rvFastScroller);
fastScroller.setVisibility(View.GONE);
RecyclerView iconsGrid = (RecyclerView) layout.findViewById(R.id.iconsGrid);
iconsGrid.setHasFixedSize(true);
iconsGrid.setLayoutManager(new GridLayoutManager(getActivity(),
getResources().getInteger(R.integer.icon_grid_width)));
mAdapter = new IconsAdapter(getActivity(), new ArrayList<String>(), new ArrayList<Integer>());
if (getArguments() != null) {
iconsNames = getArguments().getStringArrayList("iconsNamesList");
iconsInts = getArguments().getIntegerArrayList("iconsArray");
mAdapter.setIcons(iconsNames, iconsInts);
}
iconsGrid.setAdapter(mPrefs.getAnimationsEnabled() ? animAdapter(mAdapter) : mAdapter);
fastScroller.setRecyclerView(iconsGrid);
fastScroller.setHideDelay(500);
fastScroller.setVisibility(View.VISIBLE);
return layout;
}
public static IconsFragment newInstance(ArrayList<String> iconsNames, ArrayList<Integer> iconsArray) {
IconsFragment fragment = new IconsFragment();
Bundle args = new Bundle();
args.putStringArrayList("iconsNamesList", iconsNames);
args.putIntegerArrayList("iconsArray", iconsArray);
fragment.setArguments(args);
return fragment;
}
public void performSearch(String query) {
filter(query, mAdapter);
}
private synchronized void filter(CharSequence s, IconsAdapter adapter) {
if (s == null || s.toString().trim().isEmpty()) {
if (filteredIconsList != null) {
filteredIconsList = null;
}
if (filteredIconsInts != null) {
filteredIconsList = null;
}
adapter.clearIconsList();
adapter.setIcons(iconsNames, iconsInts);
adapter.notifyDataSetChanged();
} else {
if (filteredIconsList != null) {
filteredIconsList.clear();
}
if (filteredIconsInts != null) {
filteredIconsList = null;
}
filteredIconsList = new ArrayList<String>();
filteredIconsInts = new ArrayList<Integer>();
for (int i = 0; i < iconsNames.size(); i++) {
String name = iconsNames.get(i);
if (name.toLowerCase(Locale.getDefault())
.startsWith(s.toString().toLowerCase(Locale.getDefault()))) {
filteredIconsList.add(iconsNames.get(i));
filteredIconsInts.add(iconsInts.get(i));
}
}
adapter.clearIconsList();
adapter.setIcons(filteredIconsList, filteredIconsInts);
adapter.notifyDataSetChanged();
}
}
private ScaleInAnimationAdapter animAdapter(IconsAdapter iconsAdapter) {
AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(iconsAdapter);
ScaleInAnimationAdapter scaleAdapter = new ScaleInAnimationAdapter(alphaAdapter);
scaleAdapter.setFirstOnly(true);
return scaleAdapter;
}
}
And RecyclerView adapter:
package jahirfiquitiva.apps.iconshowcase.adapters;
import android.content.Context;
import android.content.res.Resources;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.afollestad.materialdialogs.MaterialDialog;
import java.util.ArrayList;
import java.util.Locale;
import jahirfiquitiva.apps.iconshowcase.R;
import jahirfiquitiva.apps.iconshowcase.utilities.Util;
public class IconsAdapter extends RecyclerView.Adapter<IconsAdapter.IconsHolder> implements View.OnClickListener {
private final Context context;
private ArrayList<String> iconsList = new ArrayList<>();
private ArrayList<Integer> iconsArray = new ArrayList<>();
public IconsAdapter(Context context, ArrayList<String> iconsList, ArrayList<Integer> iconsArray) {
this.context = context;
this.iconsList = iconsList;
this.iconsArray = iconsArray;
}
public void setIcons(ArrayList<String> iconsList, ArrayList<Integer> iconsArray) {
this.iconsList.addAll(iconsList);
this.iconsArray.addAll(iconsArray);
this.notifyItemRangeInserted(0, iconsList.size() - 1);
}
public void clearIconsList() {
this.iconsList.clear();
this.iconsArray.clear();
}
#Override
public IconsHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
return new IconsHolder(inflater.inflate(R.layout.item_icon, parent, false));
}
#Override
public void onBindViewHolder(IconsHolder holder, int position) {
if (iconsArray.size() > 0) {
holder.icon.setImageResource(iconsArray.get(position));
}
holder.view.setTag(position);
holder.view.setOnClickListener(this);
setAnimation(holder.icon, position);
}
private int lastPosition = -1;
private void setAnimation(View viewToAnimate, int position) {
if (position > lastPosition) {
viewToAnimate.setHasTransientState(true);
lastPosition = position;
}
}
#Override
public int getItemCount() {
return iconsList == null ? 0 : iconsList.size();
}
#Override
public void onClick(View v) {
int position = (Integer) v.getTag();
int resId = iconsArray.get(position);
String name = iconsList.get(position).toLowerCase(Locale.getDefault());
MaterialDialog dialog = new MaterialDialog.Builder(context)
.customView(R.layout.dialog_icon, false)
.title(Util.makeTextReadable(name))
.positiveText(R.string.close)
.show();
if (dialog.getCustomView() != null) {
ImageView dialogIcon = (ImageView) dialog.getCustomView().findViewById(R.id.dialogicon);
dialogIcon.setImageResource(resId);
}
}
class IconsHolder extends RecyclerView.ViewHolder {
final View view;
final ImageView icon;
IconsHolder(View v) {
super(v);
view = v;
icon = (ImageView) v.findViewById(R.id.icon_img);
}
}
}
From your comment you have mentioned that, you are not using any library for loading images, that's might be the issue, since libraries like Picasso ,glide... Use an asynctask to load images load on the main that is refused. You can do the same by writing it yourself but you will end up re-inventing the wheel again
How big are the images? Try to downscale them (for example resize through Picasso)

How to add a listview item to a whole other listview

So in my android app, I have two scrollable tabs which each contain Listview using a Fragment. One a list of apps and the other is blank. What I am aiming to do is add a plus button in place of where my checkbox is and duplicate that item in the other listview which is blank.
I have done research on this, but I have not found any successful examples on how to implement this.
Android - Add an item from one ListView to another ListView?
Here is my fragment that returns the apps
package com.spicycurryman.getdisciplined10.app;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import com.ibc.android.demo.appslist.app.ApkAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class InstalledAppActivity extends Fragment
implements OnItemClickListener {
PackageManager packageManager;
ListView apkList;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View rootView = inflater.inflate(R.layout.user_installed, container, false);
packageManager = getActivity().getPackageManager();
/*To filter out System apps*/
apkList = (ListView) rootView.findViewById(R.id.applist);
new LoadApplications(getActivity().getApplicationContext()).execute();
return rootView;
}
/**
* Return whether the given PackageInfo represents a system package or not.
* User-installed packages (Market or otherwise) should not be denoted as
* system packages.
*
* #param pkgInfo
* #return boolean
*/
private boolean isSystemPackage(PackageInfo pkgInfo) {
return ((pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ? true
: false;
}
private boolean isSystemPackage1(PackageInfo pkgInfo) {
return ((pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) ? false
: true;
}
// Don't need in Fragment
/*#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.block, menu);
// super.onCreateOptionsMenu(menu,inflater);
}*/
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
}
private class LoadApplications extends AsyncTask<Void, Void, Void> {
Context mContext;
private ProgressDialog pDialog;
List<PackageInfo> packageList1 = new ArrayList<PackageInfo>();
public LoadApplications(Context context){
Context mContext = context;
}
#Override
protected Void doInBackground(Void... params) {
List<PackageInfo> packageList = packageManager
.getInstalledPackages(PackageManager.GET_PERMISSIONS);
/* List<ApplicationInfo> list = mContext.getPackageManager().getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
for(int n = 0;n<list.size();n++){
if ((list.get(n).flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP))
}*/
for(PackageInfo pi : packageList) {
boolean b = isSystemPackage(pi);
boolean c = isSystemPackage1(pi);
if(!b || !c ) {
packageList1.add(pi);
}
}
//sort by application name
final PackageItemInfo.DisplayNameComparator comparator = new PackageItemInfo.DisplayNameComparator(packageManager);
Collections.sort(packageList1, new Comparator<PackageInfo>() {
#Override
public int compare(PackageInfo lhs, PackageInfo rhs) {
return comparator.compare(lhs.applicationInfo, rhs.applicationInfo);
}
});
return null;
}
#Override
protected void onCancelled() {
super.onCancelled();
}
#Override
protected void onPreExecute() {
pDialog = new ProgressDialog(InstalledAppActivity.this.getActivity());
pDialog.setMessage("Loading your apps...");
pDialog.show();
}
#Override
protected void onPostExecute(Void result) {
apkList.setAdapter(new ApkAdapter(getActivity(), packageList1, packageManager));
if (pDialog.isShowing()){
pDialog.dismiss();
}
super.onPostExecute(result);
}
#Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
}
}
And here is my adapter class:
package com.ibc.android.demo.appslist.app;
import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.TextView;
import com.spicycurryman.getdisciplined10.app.R;
import java.util.List;
public class ApkAdapter extends BaseAdapter {
List<PackageInfo> packageList;
Activity context;
PackageManager packageManager;
boolean[] itemChecked;
public ApkAdapter(Activity context, List<PackageInfo> packageList,
PackageManager packageManager) {
super();
this.context = context;
this.packageList = packageList;
this.packageManager = packageManager;
itemChecked = new boolean[packageList.size()];
}
private class ViewHolder {
TextView apkName;
CheckBox ck1;
}
public int getCount() {
return packageList.size();
}
public Object getItem(int position) {
return packageList.get(position);
}
public long getItemId(int position) {
return 0;
}
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
LayoutInflater inflater = context.getLayoutInflater();
if (convertView == null) {
convertView = inflater.inflate(R.layout.installed_apps, null);
holder = new ViewHolder();
holder.apkName = (TextView) convertView
.findViewById(R.id.appname);
holder.ck1 = (CheckBox) convertView
.findViewById(R.id.checkBox1);
convertView.setTag(holder);
//holder.ck1.setTag(packageList.get(position));
} else {
holder = (ViewHolder) convertView.getTag();
}
// ViewHolder holder = (ViewHolder) convertView.getTag();
PackageInfo packageInfo = (PackageInfo) getItem(position);
Drawable appIcon = packageManager
.getApplicationIcon(packageInfo.applicationInfo);
String appName = packageManager.getApplicationLabel(
packageInfo.applicationInfo).toString();
appIcon.setBounds(0, 0, 75, 75);
holder.apkName.setCompoundDrawables(appIcon, null, null, null);
holder.apkName.setCompoundDrawablePadding(15);
holder.apkName.setText(appName);
holder.ck1.setChecked(false);
if (itemChecked[position])
holder.ck1.setChecked(true);
else
holder.ck1.setChecked(false);
holder.ck1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (holder.ck1.isChecked())
itemChecked[position] = true;
else
itemChecked[position] = false;
}
});
return convertView;
}
}
How would I go about achieving this?
So what you are doing is, you are populating a listview of installed apps using package manager. Add a plus button in place of the checkbox. Now get installed apps similar to this -
List<PackageInfo> packageList1 = packageManager.getInstalledPackages(0);
final PackageItemInfo.DisplayNameComparator comparator = new PackageItemInfo.DisplayNameComparator(packageManager);
Collections.sort(packageList1, new Comparator<PackageInfo>() {
#Override
public int compare(PackageInfo lhs, PackageInfo rhs) {
return comparator.compare(lhs.applicationInfo, rhs.applicationInfo);
}
});
for (int i = 0; i < packageList1.size(); i++) {
PackageInfo PackInfo = packageList1.get(i);
if (((PackInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) != true) {
//Add to adapter
}
}
}
Now, create a public string array in the activity which is holding the tabs. When you click on the plus button add the packagename to the array.
Now on the other tab use the same adapter, but here before adding it to the adapter check if it is found in the string array using the index. Something like -
List<PackageInfo> packageList1 = packageManager.getInstalledPackages(0);
final PackageItemInfo.DisplayNameComparator comparator = new PackageItemInfo.DisplayNameComparator(packageManager);
Collections.sort(packageList1, new Comparator<PackageInfo>() {
#Override
public int compare(PackageInfo lhs, PackageInfo rhs) {
return comparator.compare(lhs.applicationInfo, rhs.applicationInfo);
}
});
for (int i = 0; i < packageList1.size(); i++) {
PackageInfo PackInfo = packageList1.get(i);
if (((PackInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) != true) {
if (Mainactivity.array contains String PackageName= PackInfo.packageName)
{
//Add to adapter
}
}
}
}
To persist the selected apps, add their package name to shared_preferences.
edit :
In your fragment's LoadApplications class, you retrieve a list of installed apps. On the second fragment use the same code, but just add one more condition
for(PackageInfo pi : packageList) {
boolean b = isSystemPackage(pi);
boolean c = isSystemPackage1(pi);
if(!b || !c ) {
if (array contains packagename){
packageList1.add(pi);
}
}
List<UserApps> packageListBlocked = new ArrayList<UserApps>();
holder.ck1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (holder.ck1.isChecked())
itemChecked[position] = true;
packageListBlocked.add(packageList.get(position));
else
itemChecked[position] = false;
}
});
ArrayList can hold duplicate data, so be cautious while adding.
Now you have all checkedItems in packageListBlocked pass it on to next tab and set data, make sure you call adapter.notifyDataSetChanged();

Categories