Android 侦听器/观察者实现RecyclerView列表和详细信息活动的逻辑模式

Android 侦听器/观察者实现RecyclerView列表和详细信息活动的逻辑模式,android,android-recyclerview,onclicklistener,observer-pattern,android-viewholder,Android,Android Recyclerview,Onclicklistener,Observer Pattern,Android Viewholder,实现RecyclerView列表的方法似乎多种多样,有些方法比其他方法更符合逻辑。从一个简单的列表扩展到一个数据变化的列表会增加复杂性。额外的复杂性来自于实现查看列表项详细信息的功能 虽然我在以这种方式实现列表方面取得了一些成功,但我觉得我所提出的不是有效的,也不是框架设计时的意图。看着我使用的各种方法,我一直在说“这不可能是他们希望我这样做的方式” 我想检查的基本应用程序是一个在可滚动列表中显示SQLite数据库记录的应用程序,让用户从列表中选择项目以查看详细信息,并让用户长时间单击以切换项目

实现
RecyclerView
列表的方法似乎多种多样,有些方法比其他方法更符合逻辑。从一个简单的列表扩展到一个数据变化的列表会增加复杂性。额外的复杂性来自于实现查看列表项详细信息的功能

虽然我在以这种方式实现列表方面取得了一些成功,但我觉得我所提出的不是有效的,也不是框架设计时的意图。看着我使用的各种方法,我一直在说“这不可能是他们希望我这样做的方式”

我想检查的基本应用程序是一个在可滚动列表中显示
SQLite
数据库记录的应用程序,让用户从列表中选择项目以查看详细信息,并让用户长时间单击以切换项目的属性。当然,通过各种视图、滚动、重新显示等,显示应该保持一致

此图显示了一个基本用例,它没有任何底层数据更改。蓝色的文字是需要实施细节的领域。在这种情况下,“单击”将需要将模型和位置放入详细信息活动中,可能需要使用
intent.putExtra()

与必须管理数据更改相比,上述功能相当简单。在下面的场景中,我们仍处于相同的活动中,但用户通过长时间单击来更新数据:

听众或观察家的最佳地点在哪里?什么东西需要注意?当然,视图需要更新(如何更新)?如何管理对数据库的更新?我们如何确保视图将被正确地重新绘制

下面我们有两个动作。长单击与上面的长单击类似,但在“详细信息”活动中,我们是否使用模型列表的序列化副本进行操作?如果是这样,那么这些更改如何回到“真实”模型和数据库

谁在侦听,传递了哪些参数,以及如何使用这些参数来保持数据同步?哪里是放置侦听器代码(保持数据库和视图有序的代码)的最合理和可维护的地方

框架设计者打算用什么逻辑方法来处理这个看似直接的功能?应用程序类中是否应该有一些单实例霸王函数?我还没有看到类似的例子,但可能是一种选择


如果结果证明这是“容易的”,我会感到惊讶,但它必须比我所经历的错综复杂的混乱更容易

在应用程序中从多个位置管理数据库的最简单方法之一是使用
ContentProvider
并为数据库中的每个表指定
content://
URI

要维护“主”列表视图,请执行以下操作:

  • 假设您有一个名为“animals”的数据库表,并且通过
    ContentProvider访问该表的URI是
    content://myPackage/animals
    。对于您的
    RecyclerView
    活动,在
    onCreate
    中,您将在
    content://myPackage/animals
    URI
  • 假设您正确地设计了
    ContentProvider
    (即插入、删除和更新调用以
    ContentProvider.notifyChange()
    )结束),则加载程序将在表更改时自动查询和重新查询数据库。从加载程序的
    onLoadFinished()
    回调中,获取它返回的光标,并用它更新回收器视图适配器。虽然这是一个稍微简化的解释,但这几乎是保持主列表更新的基本要素,即使在应用程序的其他部分中对数据库进行了更改
要处理列表中的单击/长时间单击,请执行以下操作:

  • 不一定有“最佳方法”可以做到这一点,但在适配器的
    onCreateViewHolder()
    方法中,我通常采用项目的基本视图,并将包含该视图的
    ViewHolder
    设置为其单击侦听器。这是因为当单击项目时,
    ViewHolder
    知道关于它在列表中的位置的重要信息(使用
    getAdapterPosition()
    getItemId()
  • 例如,如果用户长时间单击ID为3的项目,我将使用
    ContentResolver.update()
    和URI
    content://mypackage/animals/3
    。更新成功后,主列表将自动重新查询数据库,并使用ID为3的项目的新状态刷新列表

    • 这里有一种实现这组功能的方法。原始问题中描述的所有功能都已实现,并可在此处使用:。此外,如果您有改进建议,请在此处或GitHub上提供建议

      通常,列表活动中有一个模型,该模型作为额外模型传递到幻灯片活动中。幻灯片活动模型的更改很容易反映在那里和数据库中,当用户按下“上一步”按钮时,需要付出一些努力才能使幻灯片活动中的更改显示在列表活动中

      主要活动
      onCreate
      创建一个SQLite数据库适配器并将一些数据放入其中。它创建一个
      ArrayList
      ,并使用它来构建
      RecyclerView.Adapter

      public class RecyclerSqlListActivity extends AppCompatActivity {
          ....    
          @Override
          public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_recycler_list);
      
              recyclerView = (RecyclerView) findViewById(R.id.recyclerView1);
              recyclerView.setLayoutManager(new LinearLayoutManager(this));
      
              try {
                  repository = new DbSqlliteAdapter(this);
                  repository.open();
                  //repository.deleteAll();
                  //repository.insertSome();
      
                  modelList = repository.loadModelFromDatabase();// <<< This is select * from table into the modelList
                  Log.v(TAG, "The modelList has " + modelList.size() + " entries.");
      
                  recyclerViewAdapter = new MyRecyclerViewAdapter(this, modelList);
                  recyclerView.setAdapter(recyclerViewAdapter);
                  recyclerView.hasFixedSize();
      
              } catch (Exception e) {
                  Log.v(TAG, "Exception in onCreate " + e.getMessage());
              }
          }
      ...
      }
      
      我不会把所有的代码都放在这里,而是放一个子集,更多细节可以通过上面的github链接找到。但最后一条评论是关于该应用程序是如何设计来获取幻灯片活动中更改的项目列表的。每次幻灯片活动中发生更改时,都会保存该位置
      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="fill_parent" android:layout_height="fill_parent"
                    android:orientation="vertical">
      
          <android.support.v7.widget.RecyclerView
              android:id="@+id/recyclerView1"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:scrollbars="vertical" />
      
      </LinearLayout>
      
      class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {
      
          static class ViewHolder extends RecyclerView.ViewHolder {
              ImageView iconImageView;
              TextView nameTextView;
              TextView secondLineTextView;
              TextView dbItemTextView;
              TextView hiddenTextView;
              TextView descriptionTextView;
      
              ViewHolder(View itemView) {
                  super(itemView);
      
                  iconImageView = (ImageView) itemView.findViewById(R.id.bIcon);
                  nameTextView = (TextView) itemView.findViewById(R.id.bName);
                  secondLineTextView = (TextView) itemView.findViewById(R.id.bSecondLine);
                  dbItemTextView = (TextView) itemView.findViewById(R.id.bDbItem);
                  hiddenTextView = (TextView) itemView.findViewById(R.id.bHidden);
                  descriptionTextView = (TextView) itemView.findViewById(R.id.bDescription);
              }
          }
          private List<Model> mModelList;
          private Context mContext;
      
          MyRecyclerViewAdapter(Context context, List<Model> modelList) {
              mContext = context;
              mModelList = new ArrayList<>(modelList);
          }
      
          @Override
          public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              Context context = parent.getContext();
              LayoutInflater inflater = LayoutInflater.from(context);
              View modelView = inflater.inflate(R.layout.list_item, parent, false);
              return new ViewHolder(modelView);
          }
      
          @Override
          public void onBindViewHolder(ViewHolder viewHolder, int position) {
              final Model model = mModelList.get(position);
      
              viewHolder.nameTextView.setText(model.getName());
              viewHolder.secondLineTextView.setText(model.getSecond_line());
              viewHolder.dbItemTextView.setText(model.getId() + "");
              viewHolder.hiddenTextView.setText(model.getHidden());
              viewHolder.descriptionTextView.setText(model.getDescription());
      
              if ("F".equals(model.getHidden())) {
                  viewHolder.secondLineTextView.setVisibility(View.VISIBLE);
                  viewHolder.iconImageView.setVisibility(View.INVISIBLE);
              } else {
                  viewHolder.secondLineTextView.setVisibility(View.INVISIBLE);
                  viewHolder.iconImageView.setVisibility(View.VISIBLE);
              }
      
              // DEFINE ACTIVITY THAT HAPPENS WHEN ITEM IS CLICKED
              viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View view) {
                      Log.v(TAG, "setOnClickListener fired with view " + view); // view is RelativeLayout from list_item.xml
      
                      int position = mModelList.indexOf(model);
                      Log.v(TAG, "position was : "  + position);
      
                      Intent intent = new Intent(mContext, DetailSlideActivity.class);
                      intent.putExtra(DetailSlideActivity.EXTRA_LIST_MODEL, (Serializable)mModelList);
                      intent.putExtra(DetailSlideActivity.EXTRA_POSITION, position);
                      ((Activity)mContext).startActivityForResult(intent, RecyclerSqlListActivity.DETAIL_REQUEST);
                  }
              });
      
              // If the item is long-clicked, we want to change the icon in the model and in the database
              viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                  @Override
                  public boolean onLongClick(View view) {
                      Log.v(TAG, "setOnLongClickListener fired with view " + view); // view is RelativeLayout from list_item.xml
                      Log.v(TAG, "setOnLongClickListener getTag method gave us position: " + view.getTag());
      
                      int position = mModelList.indexOf(model);
                      Log.v(TAG, "position was : "  + position);
      
                      String hidden = model.getHidden();
                      Log.v(TAG, "hidden string was : "  + hidden);
      
      
                      if ("F".equals(hidden)) {
                          model.setHidden("T");
                          DbSqlliteAdapter.update(model);
                          view.findViewById(R.id.bIcon).setVisibility(View.INVISIBLE);
                      } else {
                          model.setHidden("F");
                          view.findViewById(R.id.bIcon).setVisibility(View.VISIBLE);
                      }
                      Log.v(TAG, "updating the database");
                      DbSqlliteAdapter.update(model);
                      Log.v(TAG, "notifyItemChanged being called");
                      notifyItemChanged(position);
      
                      boolean longClickConsumed = true;  // no more will happen :)
                      return longClickConsumed;
                  }
              });
          }
      
      public class RecyclerSqlListActivity extends AppCompatActivity {
          ....
          @Override
          public void onActivityResult(int requestCode, int resultCode, Intent intent) {
              if(requestCode == RecyclerSqlListActivity.DETAIL_REQUEST){
                  Log.v(TAG, "onActivityResult fired <<<<<<<<<< resultCode:" + resultCode);
                  //String[] changedItems = intent.getStringArrayExtra(RecyclerSqlListActivity.DETAIL_RESULTS); // We could return things we learned, such as which items were altered.  Or we could just update everything
                  //modelList = repository.loadModelFromDatabase();// <<< This is select * from table into the modelList
                  Integer[] changedPositions = DbSqlliteAdapter.getChangedPositions();
                  for (Integer changedPosition : changedPositions) {
                      Model aModel = modelList.get(changedPosition);
                      DbSqlliteAdapter.loadModel(aModel);
                      recyclerViewAdapter.notifyItemChanged(changedPosition);
                  }
              }
          }
          ....
      }
      
      class DbSqlliteAdapter {
          ....    
          static Model loadModel(Model model) {
              Model dbModel = DbSqlliteAdapter.getById(model.getId() + "");
              Log.v(TAG, "looked up " + model.getId() + " and it found " + dbModel.toString());
              model.setId(dbModel.getId());
              model.setName(dbModel.getName());
              model.setSecond_line(dbModel.getSecond_line());
              model.setDescription(dbModel.getDescription());
              model.setHidden(dbModel.getHidden());
              return model;
          }
      
          private static class DatabaseHelper extends SQLiteOpenHelper {....}
      
          ....
      
          static void update(Model model) {
              ContentValues values = fillModelValues(model);
              Log.v(TAG, "Working on model that looks like this: " + model);
              Log.v(TAG, "Updating record " + values.get(Model.ID) + " in the database.");
              mDb.update(SQLITE_TABLE, values, Model.ID + "=?", new String[] {"" + values.get(Model.ID)});
              Model resultInDb = getById("" + values.get(Model.ID));
              Log.v(TAG, "after update, resultInDb: " + resultInDb);
          }
      
          static void update(Model model, int position) {
              positionSet.add(position);
              update(model);
          }
      
          static Integer[] getChangedPositions() {
              Integer[] positions = positionSet.toArray(new Integer[0]);
              positionSet.clear();
              return positions;
          }
          ....    
      }