【问题标题】:Android ListView Selection ProblemAndroid ListView 选择问题
【发布时间】:2010-01-11 14:04:57
【问题描述】:

大家好,

我为以下冗长的问题道歉......

我有一个 LinearLayout,其中包含一个 ListView 和一些其他项目。至于 ListView,它的每一行都是一个 LinearLayout,它包含 3 个视图 - Checkbox、ImageView 和 TextView(从左到右 - 水平)。 由于我希望在使用轨迹球时选择整行(以背景颜色突出显示),因此我将 LinearLayout 行内的所有三个视图都设置为不可聚焦,并且它起作用了。

现在我有两个关于这个 ListView 的问题。 首先,我希望每当我(用我的手指)触摸 ListView 中的一行时,都能获得与使用轨迹球时相同的行为——这意味着我希望选择该行(突出显示)。现在发生的情况是,当我触摸该行时,它真的被选中了,但是当我松开手指时,选择就消失了(就像发生在设备的联系人列表中一样)。

第二 - 从菜单中,我可以显示一个新的 LinearLayout,而不是包含 ListView(不同应用程序的屏幕)的那个。发生这种情况时,我仍然存储包含 ListView 的 LinearLayout 的对象,因为我希望以后能够重新显示它,而无需从头开始创建它。 问题是当我用 ListView 重新显示 LinearLayout 时,没有选择 ListView 的任何行,即使当带有 ListView 的 LinearLayout “离开”屏幕时选择了某个特定行。

再次为这篇长文感到抱歉。

谢谢!

【问题讨论】:

    标签: android


    【解决方案1】:

    是的,从 iOS 开发人员的角度来看,我发现很难应用诸如“启动时设置默认选择”和“用户点击行后记住选择状态等功能强>”到 ListView。

    所以让我们先从“记住选择”开始。问题是即使你知道 您可以使用选择器 xml 来定义突出显示/按下/聚焦样式。但该样式不会 在用户单击该行后保留。例如,我有一个像这样的突出显示选择器 xml(res/drawable 文件夹下的 list_selector.xml)(但您可能有其他字段需要突出显示,例如行中 textview 的文本颜色):

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:drawable="@drawable/list_selector_pressed" android:state_pressed="true" />
        <item android:drawable="@drawable/list_selector_pressed" android:state_selected="true" />
    </selector>    
    

    和 list_selector_pressed.xml 定义了高亮样式——设置背景颜色 变成灰色:

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item>
            <shape xmlns:android="http://schemas.android.com/apk/res/android">
                <solid android:color="@color/dark_gray" />
            </shape>
        </item>
    </layer-list>
    

    正如@David Hedlund 建议的那样:

    相反,分配一个 OnItemClickListener,并让它将所选项目的 id 存储到某个变量中。

    你需要在你的类之上创建一个实例变量:

        private View currentSelectedView;
    

    然后去

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        if (currentSelectedView != null && currentSelectedView != v) {
            unhighlightCurrentRow(currentSelectedView);
        }
    
        currentSelectedView = v;
        highlightCurrentRow(currentSelectedView);
        //other codes 
        }
    

    非常简单:我们检查 currentSelectedView 是否为 null 或当前单击的视图。我们首先通过调用方法 unhighlightCurrentRow(currentSelectedView) 来取消突出显示任何样式---你可能想知道为什么我们将即时变量 currentSelectedView 作为参数传递,我稍后会解释。然后我们将视图分配给 currentSelectedView 并突出显示当前行;这样样式在用户单击完成后将保持不变。

    private void unhighlightCurrentRow(View rowView) {
        rowView.setBackgroundColor(Color.TRANSPARENT);
        TextView textView = (TextView) rowView.findViewById(R.id.menuTitle);
        textView.setTextColor(getResources().getColor(R.color.white));
    }
    
    private void highlightCurrentRow(View rowView) {
        rowView.setBackgroundColor(getResources().getColor(
                R.color.dark_gray));
        TextView textView = (TextView) rowView.findViewById(R.id.menuTitle);
        textView.setTextColor(getResources().getColor(R.color.yellow));
    
    } 
    

    啊哈,就是这样。这就是我们为列表视图实现“记住选择”的方式。如你所见, 我们必须在 xml 和 java 代码中复制样式代码——非常愚蠢:(

    接下来关于“设置默认选择”。你可能认为你可以做到这一点

    listView.setAdapter(adatper)
    listView.setSelection(0);
    currentSelectedView = listView.getChildAt(0);
    highlightCurrentRow(currentSelectedView);
    

    在活动中的 onCreate() 或片段中的 onActivityCreated() 中。
    但是如果你运行它,你会得到 NullPointer 异常,为什么? 因为此时列表视图尚未呈现,Android 不喜欢具有 viewWillAppear 的 iOS。所以你必须创建一个即时变量来记住它是否是第一次呈现列表视图单元格并在 onListItemClick 中取消设置该变量:

    所以在currentSelectedView声明下:

    private Boolean firstTimeStartup = true;
    

    然后添加方法:假设我们要突出显示列表视图中的第一行:

    public class HomeAdapter extends ArrayAdapter<String> {
        int layoutResourceId;
    
        public HomeAdapter(Context context, int textViewResourceId,
                ArrayList<String> objects) {
            super(context, textViewResourceId, objects);
            layoutResourceId = textViewResourceId;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                convertView = LayoutInflater.from(getContext()).inflate(
                        layoutResourceId, null);
            }
    
            if (firstTimeStartup && postion == 0) {
                highlightCurrentRow(convertView);
            } else {
                unhighlightCurrentRow(convertView);
            }
    
            TextView title = (TextView) convertView
                    .findViewById(R.id.menuTitle);
            title.setText(getItem(position));
            return convertView;
        }
    }
    

    很简单。 但是你需要对 onListItemClick 方法做一些改变:

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
    
        if (firstTimeStartup) {// first time  highlight first row
            currentSelectedView = l.getChildAt(0);
        }
        firstTimeStartup = false; 
        if (currentSelectedView != null && currentSelectedView != v) {
            unhighlightCurrentRow(currentSelectedView);
        }
    
        currentSelectedView = v;
        highlightCurrentRow(currentSelectedView);
    
         //other codes
    }
    

    给你!享受 Android :)

    【讨论】:

    • 我在这里关于记住/突出显示代码部分的经验是不需要复制 xml 和编程设置。更确切地说,xml 不起作用,而以编程方式起作用。从这个意义上说,可以跳过list_selector.xmllist_selector_pressed.xml 的创建,而只需编写您描述的代码
    【解决方案2】:
    private void setListviewSelection(final ListView list, final int pos) {
    list.post(new Runnable() 
       {
        @Override
        public void run() 
          {
            list.setSelection(pos);
            View v = list.getChildAt(pos);
            if (v != null) 
            {
                v.requestFocus();
            }
        }
    });
    }
    

    上面可以帮助我在列表中设置一行。

    【讨论】:

    • 嗯,这是不正确的。 setSelection 将选择列表的第 i 个元素,但 getChildAt 返回 visible 子元素,即它取决于当前滚动。因此,如果列表滚动,这将不起作用。
    【解决方案3】:
    1. 这是他的默认行为和预期行为。 强烈建议不要对此进行篡改。
    2. 这在某种程度上遵循 1.。选定状态不是持久的。相反,分配一个 OnItemClickListener,并将所选项目的 id 存储到某个变量中。回来后如果需要重新选择项目,可以使用setSelection()

    【讨论】:

    • 我已经尝试在回来时使用 setSelection(),但它并不好 - 首先该行根本没有突出显示(没有背景颜色),其次该行位于屏幕顶部,虽然我离开时它位于中间的某个地方。谢谢。
    • 另外一件事——在设备的联系人列表中,当使用轨迹球选择一个联系人然后按下轨迹球查看他的详细信息时,联系人列表的状态被保存,所以当返回联系人列表时联系人仍处于选中状态,并且其在屏幕中的位置相同。因此我认为这种行为是可能实现的。谢谢。
    • 是的,选择状态将被保存,但如果您在任何时候触摸屏幕,您将切换 触摸模式,当您处于触摸模式时,正如您已经注意到的那样,被选中的项目没有视觉反馈(只有在项目被触摸时的简短反馈,当您返回列表视图时不会发生这种情况)。这是默认行为。不要改变它。
    【解决方案4】:

    我已经解决了在notifyDataSetChanged() ; 之后发送消息并在我的handleMessage 函数中调用setSelection(0) 的问题。它 看起来很丑,但在 SDK 行为异常时帮助了我好几次。

    【讨论】:

      【解决方案5】:

      对我来说,一个更简洁的解决方案集中在这样一个事实,即如果您在ListView 中设置android:choiceMode="singleChoice"android:choiceMode="multipleChoice",那么ListView 将尝试为单选或多选保持选中状态ListView。问题是它依赖于实现Checkable的列表单元@

      因此,您首先实现CheckableLinearLayout(或FrameLayout 可能更好),以便android 为您保持选中状态,并且您可以保持Drawable 的同步。这里的关键是refreshDrawableStateonCreateDrawableState 他们更新了Drawable,所以背景是否会被突出显示。

      public class CheckableLinearLayout extends LinearLayout implements Checkable {
          boolean checked;
      
          public CheckableLinearLayout(Context context) {
              super(context);
          }
      
          public CheckableLinearLayout(Context context, AttributeSet attrs) {
              super(context, attrs);
          }
      
          public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
              super(context, attrs, defStyleAttr);
          }
      
          @Override
          public void setChecked(boolean checked) {
              this.checked = checked;
              refreshDrawableState();
          }
      
          @Override
          public boolean isChecked() {
              return checked;
          }
      
          @Override
          public void toggle() {
              setChecked(!isChecked());
          }
      
          private static final int[] CheckedStateSet = { android.R.attr.state_checked };
      
          @Override
          protected int[] onCreateDrawableState(int extraSpace) {
              final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
              if (isChecked()) {
                  mergeDrawableStates(drawableState, CheckedStateSet);
              }
              return drawableState;
          }
      }
      

      然后,一个简单的selector 用于显示选中状态的背景可绘制对象:

      <selector xmlns:android="http://schemas.android.com/apk/res/android">
          <item android:drawable="#ccc" android:state_checked="true" />
          <item android:drawable="@android:color/transparent" />
      </selector>
      

      现在我可以在任何想要显示持久选择状态(多个或单个)的列表中使用 CheckableLinearLayout:

      <CheckableLinearLayout
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="@drawable/checkable_cell"
          >
      
          <TextView
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:id="@+id/text"
              android:layout_margin="10dp"
              android:maxLines="8"
              />
      </CheckableLinearLayout>
      

      我不需要Fragment 中的任何特殊代码来更新选中状态,并且我可以通过以下方式保持选定的旋转位置:

      state.putInt("selection", listView.getCheckedItemPosition());
      

      listView.setItemChecked(state.getInt("selection"), true);
      

      同样,我可以使用setItemChecked 设置初始选择的项目,或使用getCheckedItemPosition(或getCheckedItemPositions() 进行多选)来获取当前选择。

      【讨论】:

      • 在我的用例中工作。谢谢。
      【解决方案6】:

      这应该会有所帮助:

      private void setListviewSelection(final ListView list, final int pos) {
          list.post(new Runnable() {
              @Override
              public void run() {
                  list.setSelection(pos);
                  View v = list.getChildAt(pos);
                  if (v != null) {
                      v.requestFocus();
                  }
              }
          });
      }
      

      【讨论】:

        【解决方案7】:

        我正在使用适配器并且不想设置自定义背景颜色,但使用可绘制 xml 中的 android:state_selected。 SetSelection 对我不起作用,但也许这也是因为我需要 SetNotifyDataChanged 这表明 Selected State 不是持久的。

        我还发现 ListView 中某个项目的 Selected 状态不是持久的,因为 SetNotifyDataChanged 会导致更新 ListView 布局,从而将它们全部清除。在 Adapter 的 GetView 中将项目设置为 Selected 也为时过早。

        最终,在更改列表视图的布局后,我为所选项目的视图设置了 Selected 状态,这是触发 LayoutChange 事件的时间(在 Java 中,它可能将 a 附加到 ListView 的 OnLayoutChangeListener)。

        为了方便起见,我将所选项目的视图存储为适配器的 SelectedItemView。 在 ListView 的 LayoutChange 事件处理程序中,我只是将适配器的 SelectedItemView.Selected 设置为 true。

        这是我的 Activity 中的代码,我在其中为 ListView 设置了 Adapter 并订阅了 LayoutChange(或在 Java 中附加一个 OnLayoutChangeListener)

                ringTonesListView.Adapter = ringTonesListAdapter;
                ringTonesListView.LayoutChange += (s, layoutChangeArgs) => {
                    //At this stage the layout has been updated and the Selected can be set to true for the view of the selected item. This will result in android:state_selected logic to be applied as desired and styling can be completely done per layout in Resources.
                    ringTonesListAdapter.SelectedItemView.Selected = true;
                };
        

        这是我的适配器代码:

        public class RingTonesListAdapter : BaseAdapter<RingToneItem>
        {
            List<RingTone> Items { get; set; }
        
            public override View GetView(int position, View convertView, ViewGroup parent)
            {
                View view = convertView;
        
                // re-use an existing view, if one is available
                // otherwise create a new one
                if (view == null)
                {
                    view = Context.LayoutInflater.Inflate(Resource.Layout.AlertSoundItem, parent, false);
                    view.Click += SelectRingTone;
                }
        
                RingTone ringTone = this[position];
                if (ringTone.Selected)
                {
                    //==> Important
                    //Store this view since it's the view for the Selected Item
                    SelectedItemView = view;
                    //Setting view.Selected to true here doesn't help either, since Selected will be cleared after.
                }
        
                return view;
            }
        
            private void SelectRingTone(object sender, EventArgs args)
            {
                View view = (View)sender;
                string title = view.FindViewById<TextView>(Resource.Id.ringToneTitle).Text;
                RingToneItem ringToneItem = Items.First(rt => rt.Title == title);
                if (!ringToneItem.Selected)
                {
                    //The RingTone was not selected and is selected now
                    //Deselect Old and Select new
                    foreach (RingToneItem oldItem in Items.Where(rt => rt.Selected))
                    {
                        oldItem.Selected = false;
                    }
        
                    // Select New RingTone
                    ringToneItem.Selected = true;
                    //Update the ListView. 
                    //This will result in removal of Selected state for all Items when the ListView updates it's layout
                    NotifyDataSetChanged();
                }
        
                //Now play the test sound
                NotifierService.TestSound(Context, ringToneItem);
            }
        
            public View SelectedItemView { get; set; }
        }
        

        【讨论】:

          【解决方案8】:

          只需将下面的代码行粘贴到 XML 中的 ListView。希望它可以帮助某人:)

          android:listSelector="@android:color/ANY_COLOR"
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-10-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-31
            • 1970-01-01
            相关资源
            最近更新 更多