【问题标题】:Android ListView item: stableIds and yet getting incorrect positionAndroid ListView 项目:stableIds 但位置不正确
【发布时间】:2014-12-22 18:21:58
【问题描述】:

不幸的是,我真的认为我必须稍微广泛地解释一下我的情况。

我正在编写一个关于古代拉丁语的 Android 应用程序:我的目标是向用户展示拉丁动词的整个变位,并在他们搜索特定的变形形式时为他们提供正确的语言分析。 这是我的清单。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android_application.app_name"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ConjActivity"
            android:label="@string/app_name"
            android:parentActivityName=".MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.android_application.app_name.MainActivity"/>
        </activity>
    </application>

</manifest>

要知道,为了实现第一个目标,我会自动将每个动词的所有形式创建为字符串,并且每个变位都显示为一个简单的 ListView,其中只有一个 TextView 字符串作为项目:一个项目 = 一个变形的动词形式。

现在,我需要自定义我的项目,有时修改它们的 textStyle,有时修改它们的对齐方式等。为此,我以这种方式创建了我的自定义 ListAdapter:

private static class ConjAdapter extends ArrayAdapter<String> {

    private ArrayList<Long> ids;
    private HashMap<String, Long> mIdMap;

    public ConjAdapter(Context context, int textViewResourceId, List<String> objects) {
        super(context, textViewResourceId, objects);
        ids = new ArrayList<Long>();
        mIdMap = new HashMap<String, Long>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View v = super.getView(position, convertView, parent);
        ViewHolder holder;

        if(convertView==null){
            convertView = LayoutInflater.from(context).inflate(R.layout.simple_list_item_1, parent, false);
            holder = new ViewHolder();
            holder.txt = (TextView) convertView.findViewById(R.id.text1);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        long id = getItemId(position);

        if(sel_vb!=null){
            if(ids.contains(id)){ v.setBackgroundColor(context.getResources().getColor(R.color.row_bckgr_RED));
        } else {
                v.setBackgroundColor(Color.TRANSPARENT);
            }
        }

        for(Map.Entry<String, Long> map : mIdMap.entrySet()){
            if(id==map.getValue()){
                holder.txt.setText(map.getKey());
                break;
            }
        }

        String item = (String) holder.txt.getText();
        if(item.equals(context.getResources().getString(R.string.ind))||
                item.equals(context.getResources().getString(R.string.subj))||
                item.equals(context.getResources().getString(R.string.imp))||
                item.equals(context.getResources().getString(R.string.inf))||
                item.equals(context.getResources().getString(R.string.pt))||
                item.equals(context.getResources().getString(R.string.ger))||
                item.equals(context.getResources().getString(R.string.gerv))||
                item.equals(context.getResources().getString(R.string.sup))){
            holder.txt.setGravity(Gravity.CENTER);
            holder.txt.setTextSize(20f);
            holder.txt.setTypeface(null, Typeface.BOLD);
        } else if(item.equals(context.getResources().getString(R.string.pres))||
                item.equals(context.getResources().getString(R.string.impf))||
                item.equals(context.getResources().getString(R.string.fut))||
                item.equals(context.getResources().getString(R.string.pf))||
                item.equals(context.getResources().getString(R.string.ppf))||
                item.equals(context.getResources().getString(R.string.futant))){
            holder.txt.setTypeface(null, Typeface.ITALIC);
            holder.txt.setTextSize(19f);
        } else {
            holder.txt.setPadding(10, 0, 0, 0);
        }

        return convertView;
    }

    @Override
    public long getItemId(int position) {
        String item = getItem(position);
        return mIdMap.get(item);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    static class ViewHolder {
        TextView txt;
    }

}

问题是,在第 19 项之后出现问题,即我没有收到错误消息,应用程序崩溃,但我的自定义代码不再起作用,而应该具有某些功能的项却相反其他的。 当我上下滚动列表时,这种情况会变得更糟。

经过阅读,我真的认为这个问题与我的自定义适配器的getView()调用的convertview变量的回收目标有关。

这是我的问题:为什么即使使用稳定的 id(我将其与关联的项目字符串一起存储在 mIdMap 中)也会发生这种情况,以及如何将我的项目与不正确的 position 变量分离?

更新

这是我用来填充 mIdMap 和 ids 的代码:

HashMap<String, Long> tempMap = conjadapt.mIdMap;
for(int i=0, j=0; i<displ_conj.size(); i++, j++){
    tempMap.put(displ_conj.get(i), (long) j);
}
if(sel_vb!=null){
    for(Map.Entry<String, Long> map : tempMap.entrySet()){
        if(map.getKey().equals(sel_vb))
            conjadapt.ids.add(map.getValue());
    }
}

其中 displ_conj 是我存储数据的 ArrayList。 mIdMap 存储一个 long 变量,因为 getItemId() 必须返回一个 long 并且我需要在其他地方做一些事情。

【问题讨论】:

  • 能贴出生成ID图的代码吗?或者解释它是如何被填充的。我了解 String 是什么,只是想知道您是如何得出 Integer 值的。
  • 问题可能是因为您正在调用 super.getView,但随后在它下面的代码中重现了它的功能。因此,您最终可能会在 v 中得到一个视图,而在 convertView 中得到另一个视图。尝试摆脱对 super.getView 的调用,并将对 v 变量 (v.setBackgroundColor) 的单个引用更改为 convertView。
  • 感谢您的建议,但没有任何效果。 @JaySoyer:抱歉耽搁了,我刚刚发布了我的更新。

标签: android listview android-listview convertview


【解决方案1】:

你是对的,这是一个回收问题,它与稳定的 ID 无关。正如 samgak 所指出的,您不应该调用 super 调用:

View v = super.getView(position, convertView, parent);

这将创建一个全新的View,然后您将修改背景颜色。但是,由于永远不会返回 View,因此在方法调用完成时它会被抛到一边。所以基本上,您的 getView 方法正在做一些从未使用过的额外工作。您应该只与convertView 打交道。不过,这不会导致您看到的回收问题。

您似乎还试图为sel_vb 显示红色背景。我假设这意味着选定的动词?知道有一个内置的机制来做到这一点。 ListViews 支持一个名为 setItemChecked(position, boolean) 的方法。基本上,您可以使用该方法调用突出显示任何位置。请注意,您需要先启用a choice mode。您可以通过样式或创建自己的布局来更改默认的蓝色突出显示,而不是使用 Android 提供的布局。

不幸的是,没有多少文档能够真正解释启用稳定 ID 如何影响ListView。它仅在启用选择模式时使用。它有助于确保在屏幕配置或适配器突变等事情期间突出显示/检查正确的项目,同时突出显示/检查某些内容。否则stable ids与View代无关。

很难说到底是什么导致了这种古怪的回收行为,但这肯定与您如何通过 Id 映射填充文本有关。特别是这个人:

    for(Map.Entry<String, Long> map : mIdMap.entrySet()){
        if(id==map.getValue()){
            holder.txt.setText(map.getKey());
            break;
        }
    }

我的建议是从 getView 方法中完全删除所有 Id 逻辑。而是通过执行以下操作填充文本:

holder.txt.setText(getItem(position));

另一个性能考虑。我建议研究实现不同的view types。对于您的情况,您可以根据此逻辑拥有 3 种不同的视图类型:

   String item = (String) holder.txt.getText();

    //First type
    if(item.equals(context.getResources().getString(R.string.ind))||
            item.equals(context.getResources().getString(R.string.subj))||
            item.equals(context.getResources().getString(R.string.imp))||
            item.equals(context.getResources().getString(R.string.inf))||
            item.equals(context.getResources().getString(R.string.pt))||
            item.equals(context.getResources().getString(R.string.ger))||
            item.equals(context.getResources().getString(R.string.gerv))||
            item.equals(context.getResources().getString(R.string.sup))){

    //Second type
    } else if(item.equals(context.getResources().getString(R.string.pres))||
            item.equals(context.getResources().getString(R.string.impf))||
            item.equals(context.getResources().getString(R.string.fut))||
            item.equals(context.getResources().getString(R.string.pf))||
            item.equals(context.getResources().getString(R.string.ppf))||
            item.equals(context.getResources().getString(R.string.futant))){

    } else {
       //Third type
    }

然后您可以创建三个自定义布局来膨胀,而不是依赖内置的 simple_list_item_1 布局。这改善了 View 的回收并摆脱了膨胀和后来的修改逻辑。

【讨论】:

  • 我更新了代码,一切正常。即使我用解决方案发布了自己的答案,我也会将您的答案标记为最佳答案。作为进一步的解释,我摆脱了 getView() 方法中的 id 逻辑(如您所建议的)。我也感谢您对 setItemChecked() 方法的建议,但我注意到这是 API 11 提供的调用(我正在尝试将应用程序兼容性扩展到 API 8)。
【解决方案2】:

解决方案

感谢@JaySoyer 和他关于性能问题的精彩提示,我实际上设法解决了我的问题,我认为在我更正后分享代码很有用。

从我了解到的情况来看,问题在于我尝试过多地以编程方式定义功能,而我留给 xml 部分的内容太少。 解决方案确实与视图类型有关。覆盖 getViewTypeCount()getItemViewType() 并为每个 viewType 扩展一个布局是我的问题的关键。

在我的例子中,我需要 4 个视图类型(由于存在不同的分隔线、不同的文本样式等),现在我的适配器看起来像这样:

private static class ConjAdapter extends ArrayAdapter<String> {

    private ArrayList<Long> ids;
    private HashMap<String, Long> mIdMap;
    private ViewHolder holder;

    private final int
            IND_MODE_TYPE = 0,
            ELSE_MODE_TYPE = 1,
            TENSE_TYPE = 2,
            FORM_TYPE = 3;


    public ConjAdapter(Context context, int textViewResourceId, List<String> objects) {
        super(context, textViewResourceId, objects);
        ids = new ArrayList<Long>();
        mIdMap = new HashMap<String, Long>();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if(convertView==null){
            if(getItemViewType(position)==IND_MODE_TYPE){
                convertView = LayoutInflater.from(context).inflate(R.layout.item_mode_ind, parent, false);
                holder = new ViewHolder();
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                convertView.setTag(holder);
            }else if(getItemViewType(position)==ELSE_MODE_TYPE){
                convertView = LayoutInflater.from(context).inflate(R.layout.item_mode_else, parent, false);
                holder = new ViewHolder();
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                holder.divider1 = (View) convertView.findViewById(R.id.conj_divider1);
                convertView.setTag(holder);
            } else if(getItemViewType(position)==TENSE_TYPE){
                convertView = LayoutInflater.from(context).inflate(R.layout.item_tense, parent, false);
                holder = new ViewHolder();
                holder.divider2 = (View) convertView.findViewById(R.id.conj_divider2);
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                convertView.setTag(holder);
            } else {
                convertView = LayoutInflater.from(context).inflate(R.layout.item_form, parent, false);
                holder = new ViewHolder();
                holder.txt = (TextView) convertView.findViewById(R.id.text1);
                convertView.setTag(holder);
            }
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        long id = getItemId(position);

        if(sel_vb!=null){
            if(ids.contains(id)){
                convertView.setBackgroundColor(context.getResources().getColor(R.color.row_bckgr_RED));
            } else {
                convertView.setBackgroundColor(Color.TRANSPARENT);
            }
        }

        holder.txt.setText(getItem(position));

        return convertView;
    }

    @Override
    public long getItemId(int position) {

        String item = getItem(position);
        return mIdMap.get(item);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public int getItemViewType(int position) {
        String item = getItem(position);

        if(item.equals(context.getResources().getString(R.string.ind))){
            return IND_MODE_TYPE;
        } else if(item.equals(context.getResources().getString(R.string.subj))||
                item.equals(context.getResources().getString(R.string.imp))||
                item.equals(context.getResources().getString(R.string.inf))||
                item.equals(context.getResources().getString(R.string.pt))||
                item.equals(context.getResources().getString(R.string.ger))||
                item.equals(context.getResources().getString(R.string.gerv))||
                item.equals(context.getResources().getString(R.string.sup))){
            return ELSE_MODE_TYPE;
        } else if(item.equals(context.getResources().getString(R.string.pres))||
                item.equals(context.getResources().getString(R.string.impf))||
                item.equals(context.getResources().getString(R.string.fut))||
                item.equals(context.getResources().getString(R.string.pf))||
                item.equals(context.getResources().getString(R.string.ppf))||
                item.equals(context.getResources().getString(R.string.futant))){
            return TENSE_TYPE;
        } else {
            return FORM_TYPE;
        }
    }

    @Override
    public int getViewTypeCount() {
        return 4;
    }

    static class ViewHolder {
        TextView txt;
        View divider1, divider2;
    }

}

如您所见,我只需要创建与 viewType 数量一样多的布局(通过 xml 正确管理所有需要的设置)并在对实际 viewType 进行简单检查后膨胀正确的布局,仅此而已。

希望有用!

【讨论】:

  • 很高兴我能提供帮助
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-10
  • 1970-01-01
  • 2012-12-23
  • 2013-04-23
  • 2014-09-08
  • 1970-01-01
相关资源
最近更新 更多