【问题标题】:How can I eliminate the dependency from of a ListView's onItemClick/getView and its row types?如何消除 ListView 的 onItemClick/getView 及其行类型的依赖关系?
【发布时间】:2012-03-15 14:27:24
【问题描述】:

作为一个简化示例,考虑一个ListView,它可以包含子类别和书名。如果单击书名,则应启动一个显示封面图像的新活动。如果单击子类别,则会显示新的书籍和类别列表。

Row 接口定义如下。

interface Row {
   void onClick();
   void draw(View v);
}

我想知道如何防止ListViewArrayAdapter 以及onItemClickListener 的实现者对Row 实现者的依赖(例如,BookCategory) .

推动这一要求的动力之一是“不要重复自己”(DRY)原则:当引入新的行类型时,ArrayAdapter 实现不需要更改。

【问题讨论】:

    标签: android listview dependencies dry separation-of-concerns


    【解决方案1】:

    请原谅我在下面被鸡划伤的“UML”,但这就是我的做法。

    class ListAdapter extends ArrayAdapter<Row<?>> {
    ...
        public View getView(int position, View convertView, ViewGroup parent) {
            View view = listViewRowViewRecycler.getView(convertView);
            rowProvider.get().getRow(position).draw(view);
            return view;
        }
    }
    

    OnItemClickListener 的实现者有这样一个方法:

    void onItemClick(AdapterView<?> adapterView, View view, int rowIndex, long rowId)
    {
       rowProvider.onItemClick(rowIndex);
    }
    

    那么RowProvider就有这样的方法:

    void onItemClick(int rowIndex) {
        rowList.get(rowIndex).onClick();
    }
    

    然后Row 接口有一个带有签名void onClick() 的方法,并且RowCategoryBook 实现提供了必要的行为。

    【讨论】:

    • 现在一行需要了解点击次数以及如何处理它们。为什么 Category 或 Book 应该对 UI 有所了解?
    • Row 实现者需要提供一种辅助显示的方法以及选择它们时的操作。这两项职责是他们的唯一目的。如果您认为它们被命名为 CategoryRowBookRow 会有帮助吗?
    • 啊。我的印象是 Category 和 Book 是模型类,而不是 UI 类。尽管如此,在通常的 Android 架构中,显示数据和响应事件是不同的关注点。你现在已经把它们混为一谈了。如果在另一种情况下您需要有相同项目但具有不同响应的列表视图,您会怎么做?根据我对您的架构的理解,您需要一个全新的 Row 类层次结构,该层次结构将复制表示逻辑,只是为了获得不同的 onClick() 响应。
    • @TedHopp RowCategoryBook 当然可以划分为两棵树:RowClickHandlerRowDisplayer。我认为设计这个超出了原始问题的范围。设计中没有任何固有的东西可以防止这种情况发生,当然也没有任何东西需要复制任何逻辑。如果您愿意,可以就这两个问题中的一个或两个发表后续问题,我会回答。
    【解决方案2】:

    另一种可能性是在列表中每个项目的视图上使用 setTag/getTag 方法 - 这允许您将“书”或“类别”附加到相应的行。

    为此,“列出”的每个项目都应实现一个公共接口,该接口具有在单击时调用的适当方法。

    例如:

    interface Selectable {
        void onSelected();  // Add parameters if necessary...
    }
    

    在您的列表适配器中:

    public View getView(int position, View convertView, ViewGroup parent) {    
        View itemView;  // Logic to get the appropriate view for this row.
    
        Selectable theObjectBeingRepresented;
        //
        // Logic to assign a Book/Category, etc to theObjectBeingRepresented.
        // In this example assume there is a magic Book being represented by 
        // this row.
        //
        Book aMagicBook = new Book();
        theObjectBeingRepresented = aMagicBook;
    
        // If all objects that will be contained in your list implement the 
        // 'Selectable' interface then you can simply call setTag(...) with
        // the object you are working with (otherwise you may need some conditional logic)
        //
        itemView.setTag( SELECTABLE_ITEM_KEY, theObjectBeingRepresented );
    }
    

    然后当一个项目被点击时:

    void onItemClick(AdapterView<?> adapterView, View view, int rowIndex, long rowId) 
    { 
       Selectable item = (Selectable)view.getTag( SELECTABLE_ITEM_KEY );
    
       // Depending on how you are populating the list, you may need to check for null
       // before operating on the item..
       if( null != item ) {
           item.onClick();  // Appropriate implementation called...
       }
    }
    

    关于 setTag 方法的更多细节: setTag documentation

    希望这会有所帮助...

    【讨论】:

    • 在这个设计中,类型已经泄漏到 ListAdapter 中:Book aMagicBook = new Book() 这并没有按照要求分离关注点,是吗?
    • 我把配置一个新的Book作为例子。这部分逻辑将取决于他从哪里获取实际数据以及如何显示数据。如果他有一些所有“可选”的列表,那么只需调用 setTag(...) 就可以了。我可以想象,当他填充视图时,他需要知道实际显示的是什么。
    • 对,但是这个例子不是打破了关注点分离的约束吗?绝对可以将列表填充代码与 ArrayAdapter 类分开,因为这就是我实现它的方式。我会在我的回答中详细说明。
    猜你喜欢
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 2014-10-24
    • 2011-01-06
    • 2015-06-26
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    相关资源
    最近更新 更多