【问题标题】:Setting the background color of ListView items on Button click在按钮单击时设置 ListView 项目的背景颜色
【发布时间】:2014-05-31 18:33:36
【问题描述】:

我有一个 FragmentActivity,它可以包含用户想要的任意数量的 ListFragment。 ListFragments 在Horizo​​ntalScrollView 内并排布置(在ActionBar 中选择一个选项时)。每个 ListFragment 项都包含一个 TextView 和一个 Button。单击 Button 会更改 Button 的 ViewParent 的背景颜色 - 一个包含 Button 和 TextView 的 LinearLayout。

现在,单击每个 ListFragment 中的 Button 会更改其相应列表项的背景颜色,但是在滚动列表项中未单击的按钮时,它们的背景颜色也会更改。这种行为是出乎意料的,因为只有被点击的项目才需要通过相应地点击其 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>

【问题讨论】:

    标签: android android-listview android-listfragment


    【解决方案1】:

    回答我的问题。

    魔鬼在细节中。 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;
    }
    

    【讨论】:

    • 当我开始使用 Android 时,ListView 回收导致了我无穷无尽的问题:P。
    • 我应该在哪里声明 ArrayList colouredItems,所以每次调用 getview() 时它都不会被初始化?
    【解决方案2】:

    这是ListView 视图重用的常见缺陷。

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

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

    您需要做的是存储有关哪些行应该和不应该有绿色背景的信息。为此,您可以使用 ViewHolder 模式或其他一些方法(例如 HashSet,&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;
    }
    

    【讨论】:

    • 感谢您的建议。 getView 方法的 if 块中的任何代码都会被回收,这意味着只能访问列表项的回收位置。将 Button 及其 OnClickListener 移到 if(view == null) 块之外后,我能够访问绝对列表项位置。这有助于我访问存储在 ArrayList 中的绝对列表项位置,并使用这些存储位置设置特定列表项的背景颜色。
    【解决方案3】:

    我们想改变 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);
        }
            });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-06
      • 2015-01-17
      • 1970-01-01
      • 2014-10-09
      • 2021-03-28
      • 2017-08-02
      • 2014-10-10
      相关资源
      最近更新 更多