如何在 GridView 中处理卡片按钮的 OnClick 事件?最佳实践

How to handle card's button OnClick event in GridView? Best practice

我正在尝试找到处理 OnClick 事件的最佳解决方案,该事件由 GridView.

中我的卡片按钮(见下图)生成

如您所见,我只有一个普通的 GridView,其单元格由我的自定义卡片制成。

我刚刚初始化 GridView 和它的适配器:

mGrid = (GridView) findViewById(R.id.grid);
mAdapter = new ImageTopicsAdapter(..blah blah blah..);
mGrid.setAdapter(mAdapter);

你可能知道我可以轻松处理 OnClickGridView 生成的事件。但只有当我点击卡片本身时它才会起作用:

mGrid.setOnItemClickListener(..blah blah blah..);

我想构建类似的东西(见下面的代码),这样我就可以轻松地 "implement" 我的 Activity 来处理我的卡片按钮 OnClick 事件:

mGrid.setOnItemButtonClickListener(..blah blah blah..);

最好的 (clean\easy\elegant) 方法是什么?

非常感谢您的帮助。亚历克斯。 P.S。对不起我的英语:)

适配器应该处理这个问题。通常你的适配器应该有类似 setOnOptionsClickListener(OnOptionsClickListener listener) 的方法,假设我们正在谈论省略号按钮。

因此在您的 Activity/Fragment 中使用以下代码

public interface OnOptionsClickListener {
      void onOptionsClicked(View view, PictureItem item);
}


mAdapter= new MyGridAdapter();
mAdapter.setOnOptionsClickListener(new OnOptionsClickListener() {
       public void onClick(View view, PictureItem item) {
            //process click
       }
});

并跟随内部适配器

public void setOnOptionsClickListener(OnOptionsClickListener l) {
    mOnOptionsClickListener = l;
}

findViewById(R.id.btn_options).setOnClickListener(new OnClickListener(){
    public void OnClick(View view) {
        mOnOptionsClickListener.onOptionsClicked(view, currentPictureItem);
    }
});

请注意。只有当你需要在OnClick()方法中有额外的参数时才需要声明接口(例如currentPictureItem获取图像url 或物品编号)。否则,您可以只使用 OnClickListener。

编辑 所以这里是解释。 Adapter 就像您的 GridView 的视图提供者一样。它创建视图并配置它的基本状态。这就是为什么在视图初始化期间应在 Adapter 中设置所有点击侦听器的原因。而且,我们不希望有一个乱七八糟的 Activity 和嵌套的 Adapter,而是我们希望 Adapter 作为一个单独的 class。这就是您通常需要创建额外接口才能访问 currentItem 对象以从中提取数据的原因。

既然你想发送到你的 activity,我建议在 activity 中公开一个方法并直接从你的点击侦听器调用它。最短的(从我的角度来看也是最干净的):

  • 在您的适配器中,说 ArrayAdapter

    • 定义监听点击(避免大量匿名监听器实例)
    • 直接向您的 activity 发送呼叫(因为每个视图上下文都是 activity)
    • 上面的
    • context 可以被视为你的 ApplicationActivity 只有 如果你没有手动提供一些其他的上下文,比如应用程序上下文

      private final MyAdapter extends ArrayAdapter implements View.OnClickListener {
          @Override
          public View getView(int position, View convertView, ViewGroup parent) {
              // inflate your card then get a reference to your button
              View card = ....;
              card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(this);
              return card;
          }
      
          @Override
          public void onClick(View view) {
              ApplicationActivity activity = (ApplicationActivity) view.getContext();
              if (activity != null && !activity.isFinishing()) {
                  applicationActivity.onCardButtonClick();
              }
          }
      }
      
      // in your ApplicationActivity
      public final class ApplicationActivity extends Activity {
          ...
      
          public void onCardButtonClick() {
              // deal with your click
          }
      }
      

还有其他 textbook 选项(设置侦听器,或 activity 在您的视图创建中等等)但我避免使用它们,因为它们绝对不能解决任何问题。

他们只会在您的代码中添加更多灰尘。

任何正确定义的 View 上下文都指向包含所有视图结构的 activity(因为它也是一个上下文)。这样您就可以相对轻松地快速访问 activity。

顺便说一句,事件总线不是一个好的选择,因为事件总线非常适合一对多关系(一个调度程序,多个侦听器),但在密集用于一对一调用(调度程序-侦听器)时会增加复杂性)

评论补充

您可以稍微调整一下代码,而不是使用适配器,您可以直接从您的单元发送。换句话说,而不是使用适配器作为委托,创建一个匿名侦听器,然后直接从您的卡片按钮点击并调用 activity:

public final MyAdapter extends ArrayAdapter {
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // inflate your card then get a reference to your button
        View card = ....;
        card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ApplicationActivity activity = (ApplicationActivity) view.getContext();
                if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
                    applicationActivity.onCardButtonClick();
                }
            }
        });
        return card;
    }
}

评论补充 - 复合视图

要封装所有单元格逻辑,您可以从头开始创建自定义视图或使用 compound view。下面的示例使用了复合视图:

public class ApplicationActivity extends Activity {
    ....

    public void onCardButtonClick(Cell cell) {
        // do whatever you want with the model/view
    }
}

// ViewModel instances are used in your adapter
public final class ViewModel {
    public final String description;
    public final String title;

    public ViewModel(String title, String description) {
        this.title = title != null ? title.trim() : "";
        this.description = description != null ? description.trim() : "";
    }
}

public final class Cell extends LinearLayout {
    private View button;
    private ViewModel model;

    // ViewModel is data model and is the list of items in your adapter
    public void update(ViewModel model) {
        this.model = model;
        // update your card with your model
    }

    public ViewModel getModel() {
        return model;
    }

    @Override
    protected void onAttachedToWindow() {
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener {
            @Override
            public void onClick(View view) {
                ApplicationActivity activity = (ApplicationActivity) view.getContext();
                if (model != null && activity != null && !activity.isFinishing() && !activity.isDestroyed() {
                    activity.onCardButtonClick(Cell.this);
                }
            }
        });
    }
}

// then your adapter `getView()` needs to inflate/create your compound view and return it
public final MyAdapter extends ArrayAdapter {
    private final List<ViewModel> items;

    public MyAdapter() {
        // update your models from outside or create on the fly, etc.
        this.items = ...;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {        
            // inflate - say it is a layout file 'cell.xml'
            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cell);
        }

        ((Cell) convertView).update(items.get(position));
        return convertView;
    }
}

看来没人知道该怎么做。所以我在@Dimitar G. 和@Konstantin Kiriushyn 的帮助下自己找到了解决方案。谢谢大家。

1) 我将使用复合视图系统创建自己的自定义 CardView,这将非常简单:LinearLayout + ImageView + TextView + Button.

public class TopicCardView extends LinearLayout {

   private ImageView mImage;
   private Button mButtonMenu;
   private TextView mTitle;

   public TopicCardView (Context context) {
      initializeViews(context);
   }

   private void initializeViews(Context context) {
      LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      inflater.inflate(R.layout.topic_card_view, this);
   }

   private void setTitle(...) {
      ...
   }

   private void setImage(...) {
      ...
   }

   private void setMenuClickListener(...) {
      ...
   }

   // and so on...
}

2) 然后我将在 Activity\Fragment 中创建名为 createListOfGridCardsFromDB(...) 的方法。它将生成我的自定义 CardViews 的列表 (LinkedList)(它还会将 titles\images 和听众设置为 CardViews)。

3) 然后我会将我的 CardView 生成的 LinkedList 传递给 GridViewAdapter.

这个系统使得我在应用程序中的所有卡片网格只能使用一个适配器。它还可以对 Adapter.

中的点击、界面、侦听器和其他内容执行任何操作