【问题标题】:How to handle card's button OnClick event in GridView? Best practice如何在 GridView 中处理卡片的按钮 OnClick 事件?最佳实践
【发布时间】:2015-06-25 14:51:04
【问题描述】:

我正在尝试找到处理OnClick 事件的最佳解决方案,该事件由GridView 中的我的卡片按钮(见下图)生成。

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

我只是初始化GridView,它是适配器:

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

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

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

我想构建类似的东西(见下面的代码),所以我可以轻松地“实现”我的Activity 来处理我的卡片按钮OnClick 事件:

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

最好的(干净\简单\优雅)方法是什么?

非常感谢任何帮助。亚历克斯。附言对不起我的英语:)

【问题讨论】:

    标签: android button gridview onclick android-cardview


    【解决方案1】:

    由于您想调度到您的活动,我建议您在活动中公开一个方法并直接从您的点击侦听器中调用它。最短的(在我看来也是最干净的):

    • 在您的适配器中,例如 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 选项(设置监听器,或在您的视图创建中进行活动等等),但我避免使用它们,因为它们绝对不能解决任何问题。

    他们只是在你的代码中添加了更多的灰尘。

    任何正确定义的View 上下文都指向包含所有视图结构的活动(因为它也是一个上下文)。通过这种方式,您可以快速且相对轻松地访问您的活动。

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

    评论补充

    您可以稍微调整一下代码,而不是使用适配器,您可以直接从您的单元中调度。换句话说,与其将适配器用作委托,不如创建一个匿名侦听器,然后直接通过您的卡片按钮点击访问并调用活动:

    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;
        }
    }
    

    【讨论】:

    • 嗨!感谢您的回复!请参阅我对 Konstantin Kiriushyn 解决方案的评论。有任何想法吗? :)
    • @AlexMomotov 适配器代码已更新,无需将适配器用作代理即可访问活动
    【解决方案2】:

    适配器应该处理这个。一般来说,假设我们正在谈论省略号按钮,您的适配器应该有类似 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或item id)。否则,您可以只使用 OnClickListener。

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

    【讨论】:

    • 嗨!感谢您的回复。这对我来说已经足够清楚了。我现在正在使用这个解决方案。但是你认为这是最好的方法吗?这种技术稍微打破了封装规则。是的,Adapter 就像 GridView 的视图提供者。只有视图提供者。而已。我觉得让Adapter 能够处理视图点击是错误的(即使其成为implements View.OnClickListener)。也许还有另一种选择?也许我们应该建立我们自己的具有这种能力的GridView 类?谷歌应用程序呢?他们是怎么做到的?
    • 在这种情况下,适配器没有处理视图点击。它只是监听点击事件并将其委托给Activity。所有的逻辑都发生在活动中。
    • 好的,我明白了。我的错。对不起。 :) 但主要思想是一样的。是否有可能让适配器对点击和东西不做任何事情?是否可以不理会 Adapter 而没有它做其他事情?
    • 我认为这几乎是不可能的。因为您有 getView() 方法,例如,在顶部调用 View view = inflate(R.layout.item) 并 return view; i> 在最底部。所以你应该在两者之间绑定点击监听器。
    • @AlexMomotov 我认为您的问题不是使用适配器进行点击分派,这在我的评论补充中。在那里,它使用内联点击并从匿名按钮点击监听器投射/调用活动。您还有另一个选项是定义自定义视图(复合视图或从头开始)并处理其中的点击。请查看添加评论 - 复合视图
    【解决方案3】:

    似乎没有人知道如何做到这一点。所以我在@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)(它还会将标题\图像和听众设置为 CardViews)。

    3) 然后我会将我的CardViews 中生成的LinkedList 传递给GridViewAdapter。

    这个系统可以让我在应用程序中的所有卡片网格只使用一个适配器。它还可以对适配器中的点击、界面、侦听器和其他内容进行任何操作。

    【讨论】:

      猜你喜欢
      • 2017-04-26
      • 2011-08-16
      • 1970-01-01
      • 2011-09-16
      • 2021-08-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-25
      相关资源
      最近更新 更多