Im using a custom recyclerViewCursorAdapter that i copied from a stackOverflow post. I used this to build up my app successfully, however, whenever, i scroll, its very slow. I did a lot of research on it and found many solutions but none of them seems to be working. I decided to use a listView, so i used CursorAdapter, it was the worse. I used a custom layout manager that precahes but nothing changed. Please i don't know what to do again. Does anyone have an idea whats causing this?
Activity
public class FlashCardActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
ListView listView;
String SET_ID;
int CARD_LOADER_ID = 0;
Uri currentSetUri;
FloatingActionButton addNewCard, userStats, openFullScreen;
FlashCardCursorAdapter cardCursorAdapter;
RecyclerView recyclerView;
FlashcardRecyclerViewAdapter recyclerViewAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flash_card);
//loadViews from the xml layout
loadViews();
//initialize the listview
// initListView();
// initialize the recycerView
initRecyclerView();
//get the data from the setActivity
getDataFromIntent();
recyclerView.smoothScrollToPosition(0);
//create new card
addNewCard.setOnClickListener(v -> {
setAddNewCard();
recyclerView.smoothScrollToPosition(recyclerViewAdapter.getItemCount());
// listView.smoothScrollToPosition(cardCursorAdapter.getCount());
});
getSupportLoaderManager().initLoader(CARD_LOADER_ID,null,this);
}
private void loadViews(){
recyclerView = findViewById(R.id.recycler);
listView = findViewById(R.id.CardListView);
addNewCard = findViewById(R.id.add);
userStats = findViewById(R.id.stats);
openFullScreen = findViewById(R.id.fullscreen);
}
void initListView(){
cardCursorAdapter = new FlashCardCursorAdapter(this,null);
listView.setAdapter(cardCursorAdapter);
listView.smoothScrollToPosition(cardCursorAdapter.getCount());
}
void initRecyclerView(){
recyclerViewAdapter = new FlashcardRecyclerViewAdapter(this,null);
recyclerViewAdapter.setHasStableIds(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,true);
PreCachingLayoutManager preCachingLayoutManager = new PreCachingLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(preCachingLayoutManager);
recyclerView.setAdapter(recyclerViewAdapter);
}
void setAddNewCard (){
long time = Calendar.getInstance().getTimeInMillis();
String term = getString(R.string.example_front_text);
String definition = getString(R.string.example_back_text);
ContentValues contentValues = new ContentValues();
contentValues.put(FlashContract.CardEntry.CARD_SET_ID,SET_ID);
contentValues.put(FlashContract.CardEntry.COLUMN_TERM,term);
contentValues.put(FlashContract.CardEntry.COLUMN_DEFINITION,definition);
contentValues.put(FlashContract.CardEntry.COLUMN_DATE_CREATED,time);
Uri rowAdded = getContentResolver().insert(FlashContract.CardEntry.CONTENT_URI,contentValues);
if(rowAdded != null){
Log.d("FlashCardListActivity", "inserted "+rowAdded);
}else {
Log.d("FlashCardListActivity", "insert Failed "+rowAdded);
}
recyclerViewAdapter.notifyDataSetChanged();
// cardCursorAdapter.notifyDataSetChanged();
}
void getDataFromIntent(){
final Intent intent = getIntent();
currentSetUri = intent.getData();
SET_ID = intent.getExtras().getString("set_id");
FlashContract.CardEntry.TABLE_NAME = SET_ID;
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
String[] projection = {
FlashContract.CardEntry._ID,
FlashContract.CardEntry.COLUMN_DATE_CREATED,
FlashContract.CardEntry.CARD_SET_ID,
FlashContract.CardEntry.COLUMN_IMAGE_AVAILABLE,
FlashContract.CardEntry.COLUMN_URI,
FlashContract.CardEntry.COLUMN_DEFINITION,
FlashContract.CardEntry.COLUMN_TAG,
FlashContract.CardEntry.COLUMN_TERM,
FlashContract.CardEntry.COLUMN_FRIDAY,
FlashContract.CardEntry.COLUMN_MONDAY,
FlashContract.CardEntry.COLUMN_SATURDAY,
FlashContract.CardEntry.COLUMN_SUNDAY,
FlashContract.CardEntry.COLUMN_THURSDAY,
FlashContract.CardEntry.COLUMN_TUESDAY,
FlashContract.CardEntry.COLUMN_WEDNESDAY
};
return new CursorLoader(this, FlashContract.CardEntry.CONTENT_URI,projection,null,null,null);
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
// cardCursorAdapter.swapCursor(data);
recyclerViewAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
recyclerViewAdapter.swapCursor(null);
// cardCursorAdapter.swapCursor(null);
}
}
CustomCursorRecyclerViewAdapter
public abstract class CursorRecyclerViewAdapter <ViewHolder extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<ViewHolder> {
private DataSetObserver dataSetObserver;
private boolean dataValid;
private int rowIdColumn;
private Context context;
private Cursor mCursor;
public CursorRecyclerViewAdapter(Context context , Cursor cursor){
this.context = context;
this.mCursor = cursor;
dataValid = cursor != null;
rowIdColumn = dataValid? cursor.getColumnIndexOrThrow(FlashContract.CardEntry._ID) : -1;
dataSetObserver = new NotifyingDataSetObserver(this);
if(cursor != null){
cursor.registerDataSetObserver(dataSetObserver);
}
}
public Cursor getmCursor(){
return mCursor;
}
@Override
public long getItemId(int position) {
if(dataValid && mCursor != null && mCursor.moveToPosition(position))
return mCursor.getLong(rowIdColumn);
return 0;
}
@Override
public int getItemCount() {
if(dataValid && mCursor != null) return mCursor.getCount();
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
public static final String TAG = FlashCardCursorAdapter2.class.getSimpleName();
public abstract void onBindViewHolder(ViewHolder holder, Cursor cursor1);
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if(!dataValid){
throw new IllegalStateException("The cursor is invalid, Cannot call method");
}
if(!mCursor.moveToPosition(position)){throw new IllegalStateException(" Cannot move cursor to position "+position);
}
onBindViewHolder(holder, mCursor);
}
public void changeCursor(Cursor cursor){
Cursor oldCursor = swapCursor(cursor);
if(oldCursor != null) oldCursor.close();
}
public Cursor swapCursor(Cursor cursor){
if(cursor == null){
return null;
}
final Cursor oldCursor = mCursor;
if(oldCursor != null && dataSetObserver != null){
oldCursor.unregisterDataSetObserver(dataSetObserver);
}
mCursor = cursor;
if(mCursor != null){
if(dataSetObserver != null){
mCursor.registerDataSetObserver(dataSetObserver);
}
rowIdColumn = cursor.getColumnIndexOrThrow("_id");
dataValid = true;
}else {rowIdColumn = -1;
dataValid = false ;
}
notifyDataSetChanged();
return oldCursor;
}
public void setDataValid(boolean dataValid) {
this.dataValid = dataValid;
}
private class NotifyingDataSetObserver extends DataSetObserver {
private RecyclerView.Adapter adapter;
public NotifyingDataSetObserver(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public void onChanged() {
super.onChanged();
((CursorRecyclerViewAdapter) adapter).setDataValid(true);
adapter.notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
((CursorRecyclerViewAdapter) adapter).setDataValid(false);
adapter.notifyDataSetChanged();
}
}
}
MyRecyclerViewAdapater
public class FlashcardRecyclerViewAdapter extends CursorRecyclerViewAdapter{
public static Context context;
public static String TABLE_ID = "";
public Cursor cursor;
static AnimatorSet rightOut;
static AnimatorSet leftIn;
public String term;
public String definition;
public static long _ID;
public static boolean isBackVisible = false;
public static boolean isFront = true;
public FlashcardRecyclerViewAdapter(Context context, Cursor cursor) {
super(context, cursor);
this.context = context;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.card_view_list_item,parent,false);
cardViewHolder holder = new cardViewHolder(itemView);
holder.termView = itemView.findViewById(R.id.term);
holder.definitionView = itemView.findViewById(R.id.definition_text_field);
holder.addButton = itemView.findViewById(R.id.edit_front);
holder.addButton2 = itemView.findViewById(R.id.edit2_front);
holder.closeButton = itemView.findViewById(R.id.remove_card);
holder.rotateButton = itemView.findViewById(R.id.rotate_card);
holder.rotateButton2 = itemView.findViewById(R.id.rotate2_card);
holder.frontView = itemView.findViewById(R.id.frontImage);
holder.backView = itemView.findViewById(R.id.backImage);
holder.frontCardView = itemView.findViewById(R.id.cardfrontView);
holder.backCardView = itemView.findViewById(R.id.cardBackView);
loadAnimations();
changeCameraDistance(holder);
if(holder.definitionView.getText().toString().length()>50){
holder.definitionView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM);
}
holder.rotateButton.setOnClickListener(v -> flipCard(holder));
holder.rotateButton2.setOnClickListener(v -> flipCard(holder));
holder.addButton.setOnClickListener(v -> {
PopupMenu popupMenu = new PopupMenu( FlashcardRecyclerViewAdapter.context, holder.addButton);
popupMenu.inflate(R.menu.menu_card);
popupMenu.setOnMenuItemClickListener(item -> {
switch (item.getItemId()){
case R.id.add_text:
editTextDialog(parent,true);
break;
case R.id.paint:
startPaintIntent(_ID,true);
break;
case R.id.take_photo:
startCamera(_ID,true);
break;
case R.id.upload:
FlashcardRecyclerViewAdapter.isFront = true;
new Chooser().startChooser(FlashcardRecyclerViewAdapter.context);
break;
default:
throw new IllegalStateException("Unexpected value: " + item.getItemId());
}
return false;
});
popupMenu.show();
});
holder.addButton2.setOnClickListener(v -> {
PopupMenu popupMenu = new PopupMenu( FlashcardRecyclerViewAdapter.context, holder.addButton2);
popupMenu.inflate(R.menu.menu_card);
popupMenu.setOnMenuItemClickListener(item -> {
switch (item.getItemId()){
case R.id.add_text:
editTextDialog(parent,false);
break;
case R.id.paint:
startPaintIntent(_ID,false
);
break;
case R.id.take_photo:
startCamera(_ID,false);
break;
case R.id.upload:
FlashcardRecyclerViewAdapter.isFront =false;
new Chooser().startChooser(FlashcardRecyclerViewAdapter.context);
break;
default:
throw new IllegalStateException("Unexpected value: " + item.getItemId());
}
return false;
});
popupMenu.show();
});
holder.closeButton.setOnClickListener(v -> deleteCard(_ID));
return holder;
}
@Override
public long getItemId(int position) {
return super.getItemId(position);
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, Cursor cursor1) {
cardViewHolder viewHolder = (cardViewHolder) holder;
cursor1.moveToPosition(cursor1.getPosition());
viewHolder.setData(cursor1);
}
@Override
public int getItemCount() {
return super.getItemCount();
}
public boolean IsWellFormedUri(String uriString ){
File file = new File(uriString);
Uri uri = Uri.parse(uriString);
return uri.isAbsolute() || file.isFile();
}
public void deleteCard(long id){
FlashContract.CardEntry.TABLE_NAME = TABLE_ID;
Uri uri = ContentUris.withAppendedId(FlashContract.CardEntry.CONTENT_URI,id);
int rowDeleted = context.getContentResolver().delete(uri,null,null);
if(rowDeleted != 0){
Log.d("Adapter",rowDeleted+" Successfully");
}else {
Log.d("Adapter",rowDeleted+" Unsuccessful");
}
notifyDataSetChanged();
}
public void changeCameraDistance(cardViewHolder holder){
int distance = 8000;
float scale = context.getResources().getDisplayMetrics().density * distance;
holder.frontCardView.setCameraDistance(scale);
holder.backCardView.setCameraDistance(scale);
}
private void loadAnimations(){
rightOut = (AnimatorSet) AnimatorInflater.loadAnimator(context,R.animator.out_animation);
leftIn = (AnimatorSet) AnimatorInflater.loadAnimator(context,R.animator.in_animation);
rightOut.setDuration(1000);
leftIn.setDuration(1000);
}
public void flipCard(cardViewHolder holder){
if(!isBackVisible){
rightOut.setTarget(holder.frontCardView);
leftIn.setTarget(holder.backCardView);
rightOut.start();
leftIn.start();
leftIn.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
holder.frontCardView.setVisibility(View.GONE);
holder.backCardView.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
isBackVisible = true;
}else {
rightOut.setTarget(holder.backCardView);
leftIn.setTarget(holder.frontCardView);
leftIn.start();
rightOut.start();
rightOut.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
holder.backCardView.setVisibility(View.GONE);
holder.frontCardView.setVisibility(View.VISIBLE);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
isBackVisible = false;
}
}
public void writeToDatabase(long id, String key, String value){
FlashContract.CardEntry.TABLE_NAME = TABLE_ID;
ContentValues values = new ContentValues();
Uri uri = ContentUris.withAppendedId(FlashContract.CardEntry.CONTENT_URI,id);
values.put(key,value);
context.getContentResolver().update(uri,values,null,null);
notifyDataSetChanged();
}
public void startCamera(long id, boolean isFront){
Intent cameraIntent = new Intent(context, CameraXActivity.class);
Uri uri = ContentUris.withAppendedId(FlashContract.CardEntry.CONTENT_URI,id);
cameraIntent.setData(uri);
cameraIntent.putExtra("isFront",isFront);
context.startActivity(cameraIntent);
notifyDataSetChanged();
}
public void startPaintIntent(long id, boolean isFront){
Intent paintIntent = new Intent(context, PaintActivity.class);
Uri uri = ContentUris.withAppendedId(FlashContract.CardEntry.CONTENT_URI,id);
paintIntent.setData(uri);
paintIntent.putExtra("isFront",isFront);
context.startActivity(paintIntent);
notifyDataSetChanged();
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public void getImageUri(Uri uri){
if(isFront) {
writeToDatabase(_ID, FlashContract.CardEntry.COLUMN_TERM, String.valueOf(uri));
}else {
writeToDatabase(_ID, FlashContract.CardEntry.COLUMN_DEFINITION, String.valueOf(uri));
}
File sourceFile = new File(String.valueOf(uri));
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/Android/data/"+context.getPackageName()+".uploads");
myDir.mkdirs();
String filename = sourceFile.getName()+ Calendar.getInstance().getTimeInMillis();
String destinationPath = myDir+filename;
File destinationFile = new File(destinationPath);
try{
copyFile(sourceFile,destinationFile);
} catch (IOException e) {
e.printStackTrace();
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
public void copyFile(File source, File destination) throws
IOException{
FileUtils.copy(new FileInputStream(source), new FileOutputStream(destination));
}
public static void reduceTextSize(@NotNull TextView textView, @NotNull View parentView){
while (textView.getHeight() > parentView.getHeight()){
float textSize = textView.getTextSize();
float newSize = (float) (0.5*parentView.getHeight());
textSize = textSize - newSize;
textView.setTextSize(textSize);
}
}
public void editTextDialog(ViewGroup viewGroup, boolean isFront){
View dialog_view = LayoutInflater.from(context).inflate(R.layout.add_text_dialog,viewGroup,false);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setView(dialog_view);
TextInputEditText addText = dialog_view.findViewById(R.id.addText);
if(isFront == true){
addText.setHint("Enter a question or a term or a word");
}else {
addText.setHint("Enter the Definition / Theorem / Answer to the Question or the Explanation of the word ");
}
builder.setPositiveButton("done", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = addText.getText().toString().trim();
if(isFront == true){
writeToDatabase(_ID, FlashContract.CardEntry.COLUMN_TERM,text);
notifyDataSetChanged();
}else {
writeToDatabase(_ID, FlashContract.CardEntry.COLUMN_DEFINITION,text);
notifyDataSetChanged();
}
}
});
builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(dialog != null){
dialog.dismiss();
}
}
});
final AlertDialog alertDialog = builder.create();
alertDialog.show();
}
}
MyViewHolder
public class cardViewHolder extends RecyclerView.ViewHolder {
public TextView termView, definitionView;
public ImageButton addButton, closeButton, rotateButton, rotateButton2, addButton2;
public ImageView frontView, backView;
public View frontCardView, backCardView;
public String term;
public String definition;
@RequiresApi(api = Build.VERSION_CODES.O)
public cardViewHolder(@NonNull View itemView) {
super(itemView);
}
@RequiresApi(api = Build.VERSION_CODES.O)
public void setData(Cursor cursor){
int idColumnIndex = cursor.getColumnIndexOrThrow(FlashContract.CardEntry._ID);
int setIDColumnIndex = cursor.getColumnIndexOrThrow(FlashContract.CardEntry.CARD_SET_ID);
int termColumnIndex = cursor.getColumnIndexOrThrow(FlashContract.CardEntry.COLUMN_TERM);
int definitionColumnIndex = cursor.getColumnIndexOrThrow(FlashContract.CardEntry.COLUMN_DEFINITION);
_ID = cursor.getLong(idColumnIndex);
String setId = cursor.getString(setIDColumnIndex);
String term = cursor.getString(termColumnIndex);
String definition = cursor.getString(definitionColumnIndex);
TABLE_ID = setId;
if(IsWellFormedUri(term)){
frontView.setVisibility(View.VISIBLE);
termView.setVisibility(View.GONE);
Picasso.get().load(term).fit().into(frontView);
}else {
if(term.length() <= 50){
termView.setTextSize(22);
}else {
termView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
}
frontView.setVisibility(View.GONE);
termView.setVisibility(View.VISIBLE);
termView.setText(term);
}
if(IsWellFormedUri(definition)){
if(definition.length() <= 50){
definitionView.setTextSize(22);
}else {
definitionView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM);
}
backView.setVisibility(View.VISIBLE);
definitionView.setVisibility(View.GONE);
Picasso.get().load(definition).fit().into(backView);
}else {
backView.setVisibility(View.GONE);
definitionView.setVisibility(View.VISIBLE);
definitionView.setText(definition);
}
}
public boolean IsWellFormedUri(String uriString ){
File file = new File(uriString);
Uri uri = Uri.parse(uriString);
return uri.isAbsolute() || file.isFile();
}
}
itemView_layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:id="@+id/card_layout"
android:layout_height="500dp">
<include
android:id="@+id/cardBackView"
layout="@layout/flashcard_back_view"
/>
<include
android:id="@+id/cardfrontView"
layout="@layout/flashcard_front_view"/>
</RelativeLayout>