滚动浏览提要时 RecyclerView 跳转到顶部

Posted

技术标签:

【中文标题】滚动浏览提要时 RecyclerView 跳转到顶部【英文标题】:RecyclerView Jumps to Top when scrolling through feed 【发布时间】:2019-05-04 03:51:09 【问题描述】:

我正在开发一个西班牙新闻应用程序,请看这里:

该应用的问题在于,每当任何用户点击喜欢、播放音频或翻译按钮时,recyclerView 都会跳到顶部。

数据是从 Firebase 实时数据库中提取的片段。请帮忙。

@覆盖 public View onCreateView(LayoutInflater inflater, ViewGroup 容器, 捆绑保存的InstanceState) // 膨胀这个片段的布局 mView = inflater.inflate(R.layout.fragment_feed, 容器, 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);
        
    );

【问题讨论】:

应用链接在这里:play.google.com/store/apps/details?id=com.aaveti.corto.corto 【参考方案1】:

每当您设置新适配器时,RecyclerView 都会删除并分离旧适配器。相反,您可以尝试只更新当前适配器上的数据,一个简单的方法是在您的适配器上创建一个公共方法来重新分配数据:

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

然后在onDataChange 回调中更新您的适配器:

@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);
    

【讨论】:

是 FeedAdapter.notifyOnDataChanged();或 FeedAdapter.notifyDataSetChanged(); 抱歉一定是错字;它应该是 notifyDataSetChanged。 此解决方案适用于快照侦听器,尤其是在单击 recyclerview 项目上的按钮更新后端时。【参考方案2】:

可能的原因是因为更新帖子会触发 ValueEventListener 中的回调,用于填充此行中的 RecyclerView

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

由于视图被完全清除并重新加载,它自然看起来好像已经滚动到顶部。有多种方法可以避免这种情况,具体选择取决于您的应用程序的更新需求。我能想到的一些是:

    最好的选择是使用DiffUtil 来进行就地更新(使用动画),而不需要清除整个列表,尽管在编码方面有点棘手。您应该能够找到一些关于如何使用在线课程的简单教程。

    使用addListenerForSingleValueEvent 以便对子项的更新不会触发清除列表。这可能是一个糟糕的选择,因为您的应用会显示经常变化的新闻。但是,如果用户经常切换屏幕(例如阅读新闻文章),那么刷新将发生足够多次并使其不那么明显。获取文章时分页还可以确保最新批次是最新的,而旧批次可能有未同步的更改。

第一个选项是我推荐的,但第二个更易于编码,可用于解决问题,同时获得一些时间来正确实施 DiffUtil。

【讨论】:

以上是关于滚动浏览提要时 RecyclerView 跳转到顶部的主要内容,如果未能解决你的问题,请参考以下文章

jQuery Mobile - 窗口加载后跳转到顶部

Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附

Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附

Android 嵌套滚动NestedScrollView+TabLayout+ViewPager+Fragment+RecyclerView 实现京东美团首页效果Tab页滚动到顶部时自动吸附

调用 notifydatasetchanged 后 Recyclerview 滚动到顶部

解决RecyclerView&ListView滚动到顶部或者底部边界出现阴影问题