在Button上单击设置ListView项目的背景颜色

时间:2014-05-31 18:33:37

标签: android android-listview android-listfragment

我有一个FragmentActivity,可以包含用户想要的ListFragments。 ListFragments在Horizo​​ntalScrollView内并排排列(在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>

3 个答案:

答案 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视图重用是一个常见的陷阱。

当您在ListViewGridView上滚动时,查看屏幕在回收器中的放置位置。每当需要创建新行时,如果有可用的循环视图将被使用。这旨在使滚动更快,因为从头创建视图是昂贵的。但是,如果您不小心,可能会导致细微的错误。

如果您更改其中一行的状态(即背景颜色),并且该行被重复使用,它将保持状态(即绿色背景)。此外,当重绘原始行时,它可能会回收不同的视图,并且它也不会变为绿色。

您需要做的是存储哪些行应该和不应该具有绿色背景的信息。您可以使用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);
    }
        });