【问题标题】:RecyclerView not scrolling smoothlyRecyclerView 滚动不顺畅
【发布时间】:2016-09-08 07:15:57
【问题描述】:

我有一个应用程序,其中在 RecyclerView 中有多个视图(不同类型的列表项)。从数据库中获取数据后,每个项目都会更新。我在滚动 RecyclerView 中的项目时遇到了粘滞滚动的问题。我确信这是由于数据库访问操作而发生的,我试图解决这个问题。我参考了 StackOverflow 上的几个答案,还阅读了官方的 android 帖子(Making ListView Scrolling Smooth)。正如 android 帖子中所建议的那样,我使用 AsyncTask 将所有数据库访问操作移至后台线程,但仍然没有运气。 下面是我在onBindViewHolder 中编写的代码,用于更新其中一项。

new AsyncTask<RecyclerView.ViewHolder, Void, TaskPhysicalActivity>(){
                private RecyclerView.ViewHolder v;
                PhysicalActivityItemHolder pAItemHolder;
                DBHelper mDbHelper;
                User mUser;
                @Override
                protected TaskPhysicalActivity doInBackground(RecyclerView.ViewHolder... params) {
                    pAItemHolder = (PhysicalActivityItemHolder)params[0];
                    Calendar cal = Calendar.getInstance();
                    Calendar today = Calendar.getInstance();
                    Calendar earlier = Calendar.getInstance();
                    earlier.add(Calendar.DATE, -6);
                    mDbHelper = DBHelper.getInstance(mContext);
                    mUser = User.getDefaultUser(mContext);
                    try {
                        List<PhysicalActivity> physicalActivities = mDbHelper.getPhysicalActivityInRange(mUser, cal, cal);
                        PhysicalActivityReport physicalActivityReport = new PhysicalActivityReport(earlier.getTime(), today.getTime());
                        physicalActivityReport.makeReport(mContext, mUser);
                        int userStepGoal = PhysicalActivity.getDailyStepsGoal(mContext);
                        return new TaskPhysicalActivity(userStepGoal, physicalActivityReport.getPhysicalActivityList(), physicalActivities,physicalActivityReport);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    return null;
                }


                @Override
                protected void onPostExecute(TaskPhysicalActivity taskPhysicalActivity) {
                    int stepsCount = 0;
                    Calendar cal = Calendar.getInstance();
                    TaskPhysicalActivity taskPhysicalActivityObject = taskPhysicalActivity;
                    List<PhysicalActivity> physicalActivities = taskPhysicalActivityObject.getPhysicalActivities();
                    List<PhysicalActivity> activityList = taskPhysicalActivityObject.getActivityList();
                    PhysicalActivityReport physicalActivityReport = taskPhysicalActivityObject.getPhysicalActivityReport();

                    pAItemHolder.tvActivityEmptyText.setVisibility(View.GONE);

                    if (physicalActivities.size() > 0) {
                        for (int i = 0; i < physicalActivities.size(); i++) {
                            stepsCount = stepsCount
                                    + physicalActivities.get(i).getValue();
                        }

                        final int finalStepsCount = stepsCount > 0 ? stepsCount : 0;
                        pAItemHolder.tvActivityValue.setText(String.valueOf(finalStepsCount));
                        pAItemHolder.tvActivityValueText.setText(mContext.getResources().getString(R.string.steps_unit));
                        pAItemHolder.tvActivityEmptyText.setText("");
                        pAItemHolder.vActivityCardEmptyView.setVisibility(View.GONE);
                        pAItemHolder.vActivityCardView.setVisibility(View.VISIBLE);

                    } else {

                        pAItemHolder.tvActivityValue.setText("--");
                        pAItemHolder.tvActivityValueText.setText(mContext.getResources().getString(R.string.steps_unit));
                        pAItemHolder.tvActivityEmptyText.setText("");
                        pAItemHolder.vActivityCardEmptyView.setVisibility(View.VISIBLE);
                        pAItemHolder.vActivityCardView.setVisibility(View.GONE);

                    }

                    pAItemHolder.tvActivityGoal.setText(String.format(mContext.getResources().getString(R.string.activity_goal_display_string),
                            taskPhysicalActivityObject.getUserStepsGoal(), mContext.getResources().getString(R.string.steps_unit), mContext.getResources().getString(R.string.per_day)));
                    pAItemHolder.vActivityGraph.setActivityValues(activityList);

                    if (physicalActivityReport.getPhysicalActivityAverage() > 0) {
                        pAItemHolder.tvPercentInRange.setText("Average steps " + physicalActivityReport.getPhysicalActivityAverage());
                    }
                    if (activityList.size() == 0) {
                        pAItemHolder.vActivityCardEmptyView.setVisibility(View.VISIBLE);
                        pAItemHolder.vActivityCardView.setVisibility(View.GONE);
                    } else {
                        pAItemHolder.vActivityCardEmptyView.setVisibility(View.GONE);
                        pAItemHolder.vActivityCardView.setVisibility(View.VISIBLE);
                    }
                }
            }.execute(holder);

在上面的代码中,doInBackground 内的以下四行执行数据库访问:

List<PhysicalActivity> physicalActivities = mDbHelper.getPhysicalActivityInRange(mUser, cal, cal);
PhysicalActivityReport physicalActivityReport = new PhysicalActivityReport(earlier.getTime(), today.getTime());
physicalActivityReport.makeReport(mContext, mUser);
int userStepGoal = PhysicalActivity.getDailyStepsGoal(mContext);

我使用这些操作的结果来更新 onPostExecute 方法中的 UI。
我使用 AsyncTask 的方式有问题吗?我错过了什么吗?

【问题讨论】:

  • onbindToViewHolder 方法旨在快速获取对象,加载其内容并将此内容加载到视图,而不是执行逻辑。 AcyncTask 或 Thread 应该在片段或活动中。只有结果应该传递给适配器,所以当绑定到视图持有者时,你的数据准备好了。
  • @Rafal 你的回答听起来很棒!你有任何可以证明相同的样本 sn-p 吗?即使我在我的 FirebaseRecyclerAdapter 中也面临着粘性和滞后滚动的问题。帮助将不胜感激。谢谢!

标签: android listview android-asynctask android-recyclerview


【解决方案1】:

问题是,您在onBindViewHolder 中调用了此代码。这意味着,对于RecyclerView 中每个新显示的行都会执行此代码(并在滚动时为同一行执行多次!!)。

如何将此代码移至ActivityFragment(无论列表中包含什么)?这样,代码将被执行一次。然后将您在此处解析的数据包装到自定义 POJO 对象中:

public class POJO {
    String activityValue;
    String activityValueText;
    String activityEmptyText;
    boolean activityCardEmptyViewVisibility;
    boolean activityCardViewVisibility;
}

将此类作为您传递给RecyclerView 的对象。然后,在onBindViewHolder 中使用getItem(position) 并将数据从POJO 传递给Holder

Activity(或任何控制器,pressenter,任何用于业务逻辑的东西)中执行所有逻辑。这就是它注定的目的。 Adapter 仅用于显示结果,而不是为您的应用程序创建业务逻辑。

【讨论】:

    【解决方案2】:

    感谢@Rafal 和@r-zagórski 的宝贵建议。我尝试了保持数据准备就绪并将其传递给 onBindViewHolder 的方法,但由于某种原因没有帮助。 然后我将 onBindViewHolder 中的所有代码移动到 onCreateViewHolder 并将一个对象传递给适配器构造函数,其中所有必需的参数都已经计算并打包在该对象中。所以列出步骤:

    1. 创建一个类(比如 DataClass)来保存所有必需的值及其获取器,以便稍后在更新期间在 recyclerView 适配器中使用。
    2. 由于数据库访问是内存密集型的,所以使用 AsyncTask 在doInBackground 方法中执行所有数据库访问操作,并使其返回一个 DataClass 的对象。

       protected DataClass doInBackground(Params... params) {
       //perform database or other operations here
       // Let's say DataClass constructor takes in 3 parameters which are later used to update recycler view 
          return new DataClass(data1, data2, data3);
       }
      
    3. 现在在onPostExecute 方法中更新您的recyclerView,该方法在doInBackground 完成执行后被调用。我确信这不是最佳做法,但每次我想更新时都必须重新初始化适配器,因为我需要将 DataClass 对象传递给它。

      protected void onPostExecute(DataClass dataObject) {
       mRecyclerViewAdapter = new RecyclerViewAdapter(dataObject, .....);
       mRecylerView.setAdapter(mRecyclerViewAdapter);
       //Notify Data set or Item changed if required
      }
      
    4. 不要忘记为您的 recyclerViewAdapter 编写自定义构造函数,以便将 dataObject 传递给它。

    我这样解决了我的问题。除了第一次创建片段时,滚动是平滑的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-15
      • 2019-05-25
      • 2016-01-13
      • 2012-04-08
      • 1970-01-01
      相关资源
      最近更新 更多