不幸的是,我真的认为我必须广泛地解释一下我的情况。
我正在编写一个关于古代拉丁语的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与相关的项目字符串一起存储)也会发生这种情况,以及如何从不正确的位置变量中分离我的项目?
以下是我填充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,并且我需要在其他地方做一些事情。
答案 0 :(得分:1)
你说得对,这是一个回收问题,它与稳定的ID无关。正如samgak所指出的那样,你不应该调用超级调用:
View v = super.getView(position, convertView, parent);
这将创建一个全新的View
,然后您将修改背景颜色。但是,由于永远不会返回View
,因此当方法调用完成时,它会被抛到风中。所以基本上,你的getView
方法正在做一些从未使用过的额外工作。你应该完全处理convertView
。但是,这并没有引起您所看到的回收问题。
您似乎也试图为sel_vb
显示红色背景。我假设这意味着选择动词?知道有一个内置的机制来做到这一点。 ListViews
支持名为setItemChecked(position, boolean)的方法。基本上,您可以使用该方法调用突出显示任何位置。请注意,您需要先启用a choice mode。您可以通过样式更改默认的蓝色突出显示,也可以创建自己的布局,而不是使用Android提供的布局。
不幸的是,没有太多文档可以真正解释如何使稳定的ID影响ListView
。它只在启用选择模式时使用。它有助于确保在突出显示/检查某些内容时,在屏幕配置或适配器突变等过程中突出显示/检查正确的项目。否则,稳定的id与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回收并消除膨胀,然后修改逻辑。
答案 1 :(得分:1)
感谢@JaySoyer和他关于性能问题的精彩提示,我实际上设法解决了我的问题,并且我认为在我的更正之后有用的共享代码。
据我所知,问题在于我尝试过多地以编程方式定义功能,而且我对xml部分的支持太少了。 解决方案实际上与viewTypes有关。覆盖 getViewTypeCount()和 getItemViewType()并为每个viewType填充一个布局是我的问题的关键。
在我的情况下,我需要4个viewTypes(由于存在不同的分隔符,不同的textStyles等),现在我的适配器看起来像这样:
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;
}
}
正如您所看到的,我只需创建与viewTypes数量一样多的布局(通过xml正确管理所有需要的设置),并在对实际viewType进行简单检查后对其进行充气,并且&#39是的。
希望这很有用!