我有一个FragmentActivity,可以包含用户想要的ListFragments。 ListFragments在HorizontalScrollView内并排排列(在ActionBar中选择一个选项时)。每个ListFragment项都包含TextView和Button。单击按钮可更改Button的ViewParent的背景颜色 - 包含Button和TextView的LinearLayout。
现在,单击每个ListFragment中的Button会更改其相应列表项的背景颜色,但是在未单击的列表项中滚动按钮时,其背景颜色也会更改。此行为是意外的,因为只有单击的项目需要通过相应的单击按钮更改其背景颜色。
ListFragment的数据来自一个自定义的SimpleAdapter,它覆盖了getView(int position,View convertView,ViewGroup parent)方法,我将setOnClickListener附加到Button,它在单击时更改其ViewParent的背景颜色。
我在这个问题上花了很多时间,但是我无法找到解决方案。有助于理解此行为的根本原因的解释非常有用。此外,欢迎任何有助于在点击时设置一个项目的背景颜色的指导。
以下是我的自定义适配器MyCustomAdapter的代码,它扩展了SimpleAdapter:
package com.example.androidlistfragmenttest;
import java.util.List;
import java.util.Map;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.SimpleAdapter;
public class MyAdapter extends SimpleAdapter{
Context context;
public MyAdapter(Context context, List<? extends Map<String, ?>> data,
int resource, String[] from, int[] to) {
super(context, data, resource, from, to);
this.context = context;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return false;
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
if(view == null){
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.fragment_layout, null);
Button button = (Button) view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.v("GOJIRA","ATOMIC BREATH");
// Changes parent view's background color
View parent = (View) v.getParent();
parent.setBackgroundColor(Color.GREEN);
}
});
}
return view;
}
}
ListFragment:
package com.example.androidlistfragmenttest;
import java.util.ArrayList;
import java.util.HashMap;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MyFragment extends ListFragment {
private ArrayList<HashMap<String,String>> arraylist;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_layout, container, false);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
arraylist = dataGenerator();
MyAdapter adapter = new MyAdapter(getActivity().getApplicationContext(), arraylist, R.layout.fragment_layout,new String[]{"KEY"},new int[]{R.id.text_id});
setListAdapter(adapter);
}
/*
* Method to populate an adapter's data list.
*/
public ArrayList<HashMap<String,String>> dataGenerator(){
HashMap<String,String> hashMap1 = new HashMap<String,String>();
hashMap1.put("KEY", "A");
HashMap<String,String> hashMap2 = new HashMap<String,String>();
hashMap2.put("KEY", "B");
HashMap<String,String> hashMap3 = new HashMap<String,String>();
hashMap3.put("KEY", "C");
HashMap<String,String> hashMap4 = new HashMap<String,String>();
hashMap4.put("KEY", "D");
HashMap<String,String> hashMap5 = new HashMap<String,String>();
hashMap5.put("KEY", "E");
ArrayList<HashMap<String,String>> arraylist = new ArrayList<HashMap<String,String>>();
arraylist.add(hashMap1);
arraylist.add(hashMap2);
arraylist.add(hashMap3);
arraylist.add(hashMap4);
arraylist.add(hashMap5);
return arraylist;
}
}
以及ListActivity:
package com.example.androidlistfragmenttest;
import java.util.Stack;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends FragmentActivity {
private Stack<String> tagStack;
private Integer last_tag_number;
public MainActivity(){
last_tag_number = new Integer("0");
tagStack = new Stack<String>();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_fragment:
addColumn();
return true;
case R.id.remove_column:
removeColumn();
return true;
case android.R.id.home:
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
/*
* This function adds a column pane to the screen
*
*/
public void addColumn(){
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
MyFragment fragment = new MyFragment();
fragmentTransaction.add(R.id.fragment_activity, fragment,tagGenerator());
fragmentTransaction.commit();
}
/*This function removes a column pane from the screen
*
*
*/
public void removeColumn(){
if(tagStack.size() != 0){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tagStack.pop());
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();
}
}
/*
* This function generates tags for each fragment that is displayed on the screen
* The tags pose as unique identifiers for each fragment
*/
public String tagGenerator(){
Integer tag_number;
if(last_tag_number.intValue() == 0){
tag_number = last_tag_number;
int temp = last_tag_number.intValue();
temp+=1;
last_tag_number = Integer.valueOf(temp);
}
else{
tag_number = new Integer(last_tag_number.intValue());
int temp = last_tag_number.intValue();
temp+=1;
last_tag_number = Integer.valueOf(temp);
}
String tag = tag_number.toString();
tagStack.push(tag);
return tag;
}
}
最后是布局。对于ListFragment:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1"
android:layout_margin="5dp" >
<ListView android:id="@id/android:list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label"
android:layout_gravity="start"
/>
<TextView
android:id="@+id/text_id"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="end"
/>
<Button
android:id="@+id/button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@string/button"
android:layout_margin="2dp"
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:clickable="true"
/>
</LinearLayout>
对于FragmentActivity:
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/fragment_activity"
android:layout_width="fill_parent"
android:layout_height = "fill_parent"
android:orientation = "horizontal"
android:gravity="center"
>
</LinearLayout>
</HorizontalScrollView>
答案 0 :(得分:2)
回答我的问题。
魔鬼在细节中。 convertView是回收的View。覆盖的getView(int position,View convertView,ViewGroup parent)的if(view == null)块中的任何代码也将被回收。将Button及其关联的侦听器移动到此块之外将提供对绝对列表项位置的访问,而不是回收位置。然后可以存储这些位置并且可以保持相应的列表项背景颜色/状态。这是我的自定义适配器的getView方法:
编辑:此解决方案基于@ matiash的建议,他也提供了此问题的答案。
@Override
public View getView(int position, View convertView, ViewGroup parent){
//convertView is the recycled view.
View view = convertView;
final int pos = position;
/* Views are recycled within this block.
* Only recycled relative list item positions accessible here.
*/
if(view == null){
final View viewClick = view;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.fragment_layout, null);
}
/*
* Button moved out of if(view == null) block.
* Views are not recycled here. Absolute list item positions accessible.
* Absolute list positions saved in an ArrayList (coloredItems).
*/
Button button = (Button) view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
//This is always a refreshed position and never an absolute list item position.
Log.d("CLICKED POSITION",Integer.valueOf(pos).toString());
View parent = (View) v.getParent();
parent.setBackgroundColor(Color.GREEN);
coloredItems.add(Integer.valueOf(pos));
}
});
/*
* This ensures that only list item positions saved
* in the ArrayList 'coloredItems' have a green background.
* All other list items have a transparent background.
*/
if(coloredItems.contains(Integer.valueOf(position)))
view.setBackgroundColor(Color.GREEN);
else
view.setBackgroundColor(Color.TRANSPARENT);
return view;
}
答案 1 :(得分:1)
ListView
视图重用是一个常见的陷阱。
当您在ListView
或GridView
上滚动时,查看屏幕在回收器中的放置位置。每当需要创建新行时,如果有可用的循环视图将被使用。这旨在使滚动更快,因为从头创建视图是昂贵的。但是,如果您不小心,可能会导致细微的错误。
如果您更改其中一行的状态(即背景颜色),并且该行被重复使用,它将保持状态(即绿色背景)。此外,当重绘原始行时,它可能会回收不同的视图,并且它也不会变为绿色。
您需要做的是存储哪些行应该和不应该具有绿色背景的信息。您可以使用ViewHolder
模式或其他方法(例如HashSet
和&amp; c)。
然后,您的getView()
方法应该是这样的:
@Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
if(view == null)
{
<Initialize a new row, same as before>
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
<change background color>
<Store that row = position must have a green background>
}
});
}
if <this position should be green>
parent.setBackgroundColor(Color.GREEN);
else
parent.setBackgroundColor(Color.TRANSPARENT); // or whatever the original color was.
return view;
}
答案 2 :(得分:0)
我们想要更改ListView项目背景颜色,当单击该行中的按钮时,此代码有用。
@Override
public View getView(int position, View convertView, ViewGroup parent){
View view = convertView;
final int pos = position;
Button button = (Button) view.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
Log.d("CLICKED POSITION",Integer.valueOf(pos).toString());
View parent = (View) v.getParent();
parent.setBackgroundColor(Color.GREEN);
}
});