如何在 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);
你可能知道我可以轻松处理 OnClick
由 GridView
生成的事件。但只有当我点击卡片本身时它才会起作用:
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(...)
的方法。它将生成我的自定义 CardView
s 的列表 (LinkedList
)(它还会将 titles\images 和听众设置为 CardView
s)。
3) 然后我会将我的 CardView
生成的 LinkedList
传递给 GridViewAdapter
.
这个系统使得我在应用程序中的所有卡片网格只能使用一个适配器。它还可以对 Adapter.
中的点击、界面、侦听器和其他内容执行任何操作
我正在尝试找到处理 OnClick
事件的最佳解决方案,该事件由 GridView
.
如您所见,我只有一个普通的 GridView
,其单元格由我的自定义卡片制成。
我刚刚初始化 GridView
和它的适配器:
mGrid = (GridView) findViewById(R.id.grid);
mAdapter = new ImageTopicsAdapter(..blah blah blah..);
mGrid.setAdapter(mAdapter);
你可能知道我可以轻松处理 OnClick
由 GridView
生成的事件。但只有当我点击卡片本身时它才会起作用:
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(...)
的方法。它将生成我的自定义 CardView
s 的列表 (LinkedList
)(它还会将 titles\images 和听众设置为 CardView
s)。
3) 然后我会将我的 CardView
生成的 LinkedList
传递给 GridViewAdapter
.
这个系统使得我在应用程序中的所有卡片网格只能使用一个适配器。它还可以对 Adapter.
中的点击、界面、侦听器和其他内容执行任何操作