简体   繁体   中英

RecyclerView Jumps to Top when scrolling through feed

I am working on a Spanish news app, see here:

The problem with the app is that whenever any user clicks on like, play audio or translate button, the recyclerView jumps to top.

  • The data is fetched from Firebase Realtime Database in fragment. Please help.

  • @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment mView = inflater.inflate(R.layout.fragment_feed, container, false);

      MobileAds.initialize(mView.getContext(), "XXXXXXXXX"); mRewardedVideoAd = MobileAds.getRewardedVideoAdInstance(mView.getContext()); mRewardedVideoAd.setRewardedVideoAdListener(this); loadRewardedVideoAd(); mAuth = FirebaseAuth.getInstance(); feedRecycler = mView.findViewById(R.id.recycler_feed); feedRecycler.setLayoutManager(new LinearLayoutManager(getContext())); RecyclerView.Adapter adapter = new FeedAdapter(mView.getContext(),mRecyclerViewItems); feedRecycler.setAdapter(adapter); userImage = mView.findViewById(R.id.feed_img); username = mView.findViewById(R.id.feed_name); usercomment = mView.findViewById(R.id.feed_edittext); subMitComment = mView.findViewById(R.id.feed_submit); mUSerCmt = FirebaseDatabase.getInstance().getReference().child("feeds"); mScoreRef = FirebaseDatabase.getInstance().getReference().child("scores"); if(mAuth.getCurrentUser().getPhotoUrl()!= null) { Picasso.get().load(mAuth.getCurrentUser().getPhotoUrl().toString()).into(userImage); username.setText(mAuth.getCurrentUser().getDisplayName()); } else { startActivity(new Intent(mView.getContext(),UserProfile.class)); } subMitComment.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String usercmt = usercomment.getText().toString(); if(!usercmt.equals("")){ writeNewPost(usercmt,mAuth.getUid(),mAuth.getCurrentUser().getDisplayName(),mAuth.getCurrentUser().getPhotoUrl().toString()); Toast.makeText(mView.getContext(),"Comment updated!",Toast.LENGTH_SHORT).show(); } usercomment.setText(""); } }); mUSerCmt.orderByChild("timestamp").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { mRecyclerViewItems.clear(); for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) { Model_Feed epiModel = dataSnapshot1.getValue(Model_Feed.class); mRecyclerViewItems.add(epiModel); } FeedAdapter = new FeedAdapter(mView.getContext(), mRecyclerViewItems); feedRecycler.setAdapter(FeedAdapter); FeedAdapter.notifyDataSetChanged(); } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); return mView; } private void writeNewPost(String sen, String uid, String name, String img) { // Create new post at /user-posts/$userid/$postid and at // /posts/$postid simultaneously String key = mUSerCmt.push().getKey(); long time = System.currentTimeMillis() * (-1); Model_Feed post = new Model_Feed(sen,time,uid, name, img,key); Map<String, Object> postValues = post.toMap(); Map<String, Object> childUpdates = new HashMap<>(); childUpdates.put(key, postValues); mUSerCmt.updateChildren(childUpdates); } public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.FeedViewHolder>{ private Context context; private List<Object> mRecyclerViewItems; public FeedAdapter(Context context, List<Object> mRecyclerViewItems) { this.context = context; this.mRecyclerViewItems = mRecyclerViewItems; } @NonNull @Override public FeedViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { LayoutInflater inflater = LayoutInflater.from(context); View view = inflater.inflate(R.layout.rec_words,viewGroup,false); return new FeedViewHolder(view); } @Override public void onBindViewHolder(@NonNull final FeedViewHolder feedViewHolder, int i) { final Model_Feed modelFeed = (Model_Feed) mRecyclerViewItems.get(i); Picasso.get().load(modelFeed.getImg()).into(feedViewHolder.circleImageView); feedViewHolder.nameWord.setText(modelFeed.getName()); feedViewHolder.statusword.setText(modelFeed.getSen()); feedViewHolder.fireword.setText(String.valueOf(modelFeed.fires.size())); feedViewHolder.playword.setText(String.valueOf(modelFeed.plays.size())); if(modelFeed.fires.containsKey(mAuth.getUid())){ feedViewHolder.fireimage.setImageDrawable(getResources().getDrawable(R.drawable.fire)); } if(modelFeed.plays.containsKey(mAuth.getUid())){ feedViewHolder.playimage.setImageDrawable(getResources().getDrawable(R.drawable.play_button)); } feedViewHolder.fireLinear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { feedViewHolder.fireimage.setImageDrawable(getResources().getDrawable(R.drawable.fire)); onFireClicked(mUSerCmt.child(modelFeed.getKey()),modelFeed.getUid()); } }); feedViewHolder.playLinear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { feedViewHolder.playimage.setImageDrawable(getResources().getDrawable(R.drawable.play_button)); sen_sound = modelFeed.getSen(); mScoreRef.child(mAuth.getUid()).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { final Model_Score modelScore = dataSnapshot.getValue(Model_Score.class); long scoreCheck = modelScore.getScore(); if (scoreCheck < 1 ){ AlertDialog.Builder builder; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder = new AlertDialog.Builder(context, android.R.style.Theme_Material_Dialog_Alert); } else { builder = new AlertDialog.Builder(context); } builder.setTitle("Insufficient Coins") .setMessage("You've insufficient coins in your wallet to listen this audio, Watch a reward video complete and get 50 coins.") .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { long score = modelScore.getScore() + 5; Model_Score modelScore1 = new Model_Score(score, modelScore.getReput(), mAuth.getUid()); mScoreRef.child(mAuth.getUid()).setValue(modelScore1); } scorez = modelScore.getScore(); reputz = modelScore.getReput(); }) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Toast.makeText(mView.getContext(),"Our Apologies for inconvenience",Toast.LENGTH_SHORT).show(); } }) .setIcon(android.R.drawable.ic_dialog_alert) .show(); }else { long score = modelScore.getScore() - 10; Model_Score modelScore1 = new Model_Score(score, modelScore.getReput(), mAuth.getUid()); mScoreRef.child(mAuth.getUid()).setValue(modelScore1); } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } }); } @Override public int getItemCount() { return mRecyclerViewItems.size(); } public class FeedViewHolder extends RecyclerView.ViewHolder{ CircleImageView circleImageView; TextView nameWord, statusword,fireword,playword; LinearLayout fireLinear, playLinear; ImageView fireimage, playimage; public FeedViewHolder(@NonNull View itemView) { super(itemView); circleImageView = itemView.findViewById(R.id.word_image); nameWord = itemView.findViewById(R.id.word_name); statusword = itemView.findViewById(R.id.word_status); fireword = itemView.findViewById(R.id.word_text_fire); playword = itemView.findViewById(R.id.word_text_play); fireLinear = itemView.findViewById(R.id.word_fire_linear); playLinear = itemView.findViewById(R.id.word_play_linear); fireimage = itemView.findViewById(R.id.word_img_fire); playimage = itemView.findViewById(R.id.word_img_play); } } } void sendReward(long scores, long reputs){ long score = scores + 50; Model_Score modelScore1 = new Model_Score(score, reputs, mAuth.getUid()); mScoreRef.child(mAuth.getUid()).setValue(modelScore1); } void showToast(String msg){ Toast.makeText(mView.getContext(),msg,Toast.LENGTH_SHORT).show(); } private void onFireClicked(DatabaseReference postRef, final String cr_uid) { postRef.runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { Model_Feed p = mutableData.getValue(Model_Feed.class); if (p == null) { return Transaction.success(mutableData); } if (p.fires.containsKey(mAuth.getUid())) { // Unstar the post and remove self from stars p.firecount = p.firecount - 1; p.fires.remove(mAuth.getUid()); } else { // Star the post and add self to stars p.firecount = p.firecount + 1; p.fires.put(mAuth.getUid(), true); mScoreRef.child(cr_uid).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { Model_Score modelScore = dataSnapshot.getValue(Model_Score.class); long reput = modelScore.getReput(); Model_Score modelScore1 = new Model_Score(modelScore.getScore(), reput + 10, cr_uid); mScoreRef.child(cr_uid).setValue(modelScore1); } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } // Set value and report transaction success mutableData.setValue(p); return Transaction.success(mutableData); } @Override public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { // Transaction completed //Log.d(TAG, "postTransaction:onComplete:" + databaseError); } }); } private void onPlayClicked(DatabaseReference postRef) { postRef.runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { Model_Feed p = mutableData.getValue(Model_Feed.class); if (p == null) { return Transaction.success(mutableData); } if (p.plays.containsKey(mAuth.getUid())) { // Unstar the post and remove self from stars p.playcount = p.playcount + 1; p.plays.put(mAuth.getUid() + p.playcount,true); new readSen().execute(); } else { // Star the post and add self to stars p.playcount = p.playcount + 1; p.plays.put(mAuth.getUid(), true); } // Set value and report transaction success mutableData.setValue(p); return Transaction.success(mutableData); } @Override public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { // Transaction completed //Log.d(TAG, "postTransaction:onComplete:" + databaseError); } }); } 

The likely reason is because updating the posts triggers a callback in the ValueEventListener being used to populate the RecyclerView in this line

mUSerCmt.orderByChild("timestamp").addValueEventListener( ... )

Since the view is being cleared and reloaded entirely, it naturally appears as though it has scrolled to the top. There can be multiple approaches to avoid this, and the exact choice will depend upon the update needs of your app. Some that I can think of are:

  1. The best option, though slightly tricky coding wise, would be to use DiffUtil in order to make in place updates (with animations), without clearing the entire list. You should be able to find a number of simple tutorials on how to use the class online.

  2. Use addListenerForSingleValueEvent so that updates to the children do not trigger the clearing of the list. This may be a bad choice, since your app displays news that can change often. However, if the user is switching screens often (to read the news article, for instance), then the refresh will happen enough times and make it less noticeable. Pagination of articles while being fetched would also ensure that the latest batch is up-to-date, while the older ones may have unsynced changes.

The first option is what I'd recommend, but the second one is simpler to code, and can be used to fix issues while gaining some time to properly implement DiffUtil.

Whenever you set a new adapter, RecyclerView removes and detaches the old one. Instead, you could try just updating the data on current adapter, and one simple is to create a public method on your adapter to reassign the data:

public class FeedAdapter extends RecyclerView.Adapter<FeedAdapter.FeedViewHolder> {
    // ...
    public void setItems(List<Object> items) {
        mRecyclerViewItems = items;
    }
    // ...
}

Then update your adapter in the onDataChange callback:

@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
    mRecyclerViewItems.clear();
    for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
        Model_Feed epiModel = dataSnapshot1.getValue(Model_Feed.class);
        mRecyclerViewItems.add(epiModel);
    }
    if (FeedAdapter != null) {
        FeedAdapter.setItems(mRecyclerViewItems);
        FeedAdapter. notifyDataSetChanged();
    } else {
        FeedAdapter = new FeedAdapter(mView.getContext(), mRecyclerViewItems);
        feedRecycler.setAdapter(FeedAdapter);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM