带有复选框的类似 Gmail 的 ListView(并使用 ActionBar)

Posted

技术标签:

【中文标题】带有复选框的类似 Gmail 的 ListView(并使用 ActionBar)【英文标题】:Gmail-like ListView with checkboxes (and using the ActionBar) 【发布时间】:2012-02-09 02:35:51 【问题描述】:

我正在尝试重现 Google 对 Gmail 应用中的 ListView 所做的操作。特别是,我希望每个列表项都包含一个 CheckBox 和两个 TextView(一个在另一个之上)。当 CheckBox 被选中(或单击)以及单击列表项上的其他任何位置时,我需要侦听器。最后,我希望 ActionBar 反映已选择的项目并提供全选、全选等选项(请参阅this screenshot)。

到目前为止,这是我想出的布局。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    android:orientation="horizontal" >

    <CheckBox android:id="@+id/checkBox"
        android:layout_
        android:layout_
        android:layout_gravity="center_vertical|center_horizontal" />

    <LinearLayout android:id="@+id/linearLayout1"
        android:layout_
        android:layout_
        android:orientation="vertical"
        android:padding="6dp"
        android:focusable="true"
        android:clickable="true" >

        <TextView android:id="@+id/titleTextView"
            android:layout_
            android:layout_
            android:textSize="16sp" />

        <TextView android:id="@+id/dateTextView"
            android:layout_
            android:layout_
            android:textSize="12sp" />

    </LinearLayout>

</LinearLayout>

这会正确显示所有内容,但我需要有关如何为两个视图(@+id/checkBox@+id/linearLayout1)设置侦听器的指针.我查看了List16 API demo,但他们使用的是 simple_list_item_activated_1 布局,我不确定 XML 的样子。正如他们的代码所示,我创建了一个实现 ListView.MultiChoiceModeListenerModeCallback 类,并将 ListView 的选择模式设置为 CHOICE_MODE_MULTIPLE_MODAL,但我不知道如何获取 CheckBox在我的布局中使用它。

是否有人成功复制了 Gmail 应用的 ListView 行为?我进行了相当多的搜索,但无法提出任何建议(尽管其他几个人提出了类似的问题,like this one - 大多数答案都指向同一个 API 演示)。

另外,就上下文而言,我正在将 SQLite 数据库中的数据加载到列表中,并且我创建了自己的光标适配器(工作正常)。我感觉我需要在 newView() 和 bindView() 方法中在这个类中设置监听器,但是我尝试过的所有方法都没有奏效。

有什么想法吗?

【问题讨论】:

我需要更好地解释我的问题,还是我问的太多了? :// 【参考方案1】:

这适用于 SDK 7 (android 2.1) 及更高版本。 (与 SherlockActionBar 一起)

完全模仿 gmail 列表框体验。

我覆盖了 onTouchEvent,所以按下左角将激活选择模式; 比尝试点击小复选框更好。

我覆盖了 performItemClick,所以在左侧按 not 将作为常规按动作。

我重写了 setItemChecked,所以它会根据需要更新 mActionMode。

public class SelectListView extends ListView 

    private SherlockFragmentActivity mActivity;
    ActionMode mActionMode;

      public SelectListView(Context context) 
    this( context, null, 0); 


public SelectListView(Context context, AttributeSet attrs)    
    this( context, attrs, 0); 
   


public SelectListView(Context context, AttributeSet attrs, int defStyle) 
    super( context, attrs, defStyle ); 
    setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
    mActivity = (SherlockFragmentActivity) context;


    @Override
    public boolean performItemClick(View view, int position, long id) 
        OnItemClickListener mOnItemClickListener = getOnItemClickListener();
        if (mOnItemClickListener != null) 
            playSoundEffect(SoundEffectConstants.CLICK);
            if (view != null)
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            mOnItemClickListener.onItemClick(this, view, position, id);
            return true;
        
        return false;
    

    boolean mSelectionMode = false;
    int mStartPosition;

    @Override
    public boolean onTouchEvent(MotionEvent ev) 

        final int action = ev.getAction();
        final int x = (int) ev.getX();
        final int y = (int) ev.getY();

        if (action == MotionEvent.ACTION_DOWN && x < getWidth() / 7) 
            mSelectionMode = true;
            mStartPosition = pointToPosition(x, y);
        
        if (!mSelectionMode)
            return super.onTouchEvent(ev);
        switch (action) 
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            if (pointToPosition(x, y) != mStartPosition)
                mSelectionMode = false;
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
        default:
            mSelectionMode = false;
            int mItemPosition = pointToPosition(x, y);
            if (mStartPosition != ListView.INVALID_POSITION)
                setItemChecked(mItemPosition, !isItemChecked(mItemPosition));
        

        return true;
    

    @Override
    public void setItemChecked(int position, boolean value) 
        super.setItemChecked(position, value);
        // boolean r = getAdapter().hasStableIds();
        int checkedCount = getCheckItemIds().length;

        if (checkedCount == 0) 
            if (mActionMode != null)
                mActionMode.finish();
            return;
        
        if (mActionMode == null)
            mActionMode = mActivity.startActionMode(new ModeCallback());

        mActionMode.setTitle(checkedCount + " selected");

    

    class ModeCallback implements ActionMode.Callback 

        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) 

            menu.add(getResources().getString(R.string.aBar_remove)).setIcon(R.drawable.ic_action_trash)
                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);

            return true;
        

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) 
            return true;
        

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) 
            Toast.makeText(mActivity, "Delted  items", Toast.LENGTH_SHORT).show();
            mode.finish();
            return true;
        

        @Override
        public void onDestroyActionMode(ActionMode mode) 
            mActionMode = null;
            clearChecked();
        

    

    public void clearChecked() 
        SparseBooleanArray CItem = getCheckedItemPositions();
        for (int i = 0; i < CItem.size(); i++)
            if (CItem.valueAt(i))
                super.setItemChecked(CItem.keyAt(i), false);
    


您可以使用任何您需要的列表框适配器。

如果您的 list_item_layout 上有一个复选框,则需要像这样扩展您的适配器:

ArrayAdapter<String> mAdapter = new ArrayAdapter<String>(this, R.layout.simple_list_item_multiple_choice,
            R.id.text1, Cheeses.sCheeseStrings) 

        @Override
        public View getView(int position, View convertView, ViewGroup parent) 
            View v = super.getView(position, convertView, parent);
            CheckBox checkBox = (CheckBox) v.findViewById(R.id.CheckBox);
            checkBox.setChecked(((ListView)parent).isItemChecked(position));
            return v;
        

    ;

不要忘记在您的 list_item_layout.xml 上使用 android:background="?attr/activatedBackgroundIndicator"

【讨论】:

我在滚动列表视图时使用复选框实现上下文操作栏,然后不维护操作栏上设置的复选框计数。有什么帮助吗?【参考方案2】:

图中的模式称为动作模式。如果您使用自定义行视图和自定义适配器,则不必使用 CHOICE_MODE_MULTIPLE_MODAL 来启动操作模式。我试了一次,失败了,怀疑是用于内置适配器的。

为了在您的代码中自己调用动作模式,请在您的任何视图的任何侦听器方法中调用方法 startActionMode,让它是复选框、文本视图或类似的东西。然后您将 ModeCallback 作为参数传递给它,并在 ModeCallback 类中执行您想要执行的任何操作。

我不认为这个可以在 3.0 之前的 Android 上运行。

这里是一个简短的例子。我有一个可扩展的列表活动,并希望在用户选中/取消选中复选框时调出操作模式菜单,这是我的自定义适配器类中的 getChildView 方法:

public View getChildView(int groupPosition, int childPosition,
            boolean isLastChild, View convertView, ViewGroup parent) 

        if (convertView == null) 
            LayoutInflater inflater =  (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(R.layout.childrow, null);
        
        CheckBox cb = (CheckBox)convertView.findViewById(R.id.row_check);
        cb.setChecked(false);   // Initialize
        cb.setTag(groupPosition + "," + childPosition);
        cb.setFocusable(false); // To make the whole row selectable
        cb.setOnCheckedChangeListener(new OnCheckedChangeListener() 
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
                String tag = (String)buttonView.getTag();
                String[] pos = tag.split(",");
                if (isChecked) 
                    if (mActionMode == null) mActionMode = startActionMode(mMultipleCallback);                      
                
                else 
                    if (mActionMode != null)   // Only operate when mActionMode is available

                        mActionMode.finish();
                        mActionMode = null;
                    
                
            
        );

        TextView tvTop = (TextView)convertView.findViewById(R.id.row_text_top);
        TextView tvBottom = (TextView)convertView.findViewById(R.id.row_text_bottom);
        tvTop.setText(mChildren.get(groupPosition).get(childPosition));
        tvBottom.setText(mChildrenSize.get(groupPosition).get(childPosition));
        return convertView;
    

【讨论】:

谢谢!今晚我会试试这个,看看我能不能让它工作。在此之前将其标记为答案。【参考方案3】:

除了cnbuff410的回答,使用以下代码更新检查项目计数

public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) 
if (isChecked) 
    ++checkBoxCount;
  else 
    --checkBoxCount;
 
...

if (mActionMode != null) 
    mActionMode.setSubtitle(checkBoxCount + " item(s) selected.");

我可以确认以下代码不适用于自定义列表视图的复选框。但是,长按仍然有效:

listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
listView.setMultiChoiceModeListener(new ModeCallback());

选择自定义 ListView 上的复选框不会触发动作模式。仅内置 ListView,例如扩展 ListActivity 会自动做到这一点。

【讨论】:

以上是关于带有复选框的类似 Gmail 的 ListView(并使用 ActionBar)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 ListView 内的复选框中获取位置

如何在 Google Keep 中添加复选框 ListView?

JavaFX 8,带有复选框的 ListView

带有复选框的自定义 ListView 检查未选中的项目

带有复选框和所有可点击的Android ListView [重复]

自定义 listView 行,带有复选框和旋转时的储蓄复选标记