Android - 列表视图项上的 OnClickListener 仅影响最后一行

Android - OnClickListener on listview item only affects last row

我有一个带有自定义单元格的列表视图,其中包含计时器、文本视图和开关。这个列表视图填充了来自 ArrayList 的哑数据。每当单击一个开关时,它总是会影响列表视图中的最后一项,而不是我打算单击的那一项。

这是我的自定义 timeTrackCellAdapter class

public class timeTrackCellAdapter extends ArrayAdapter {
    private final Activity activity;
    private final List timeParams;
    TimeView tView = null;
    View rowView;
    //Constructor
    public timeTrackCellAdapter(Activity activity, List objects){
        super(activity, R.layout.cell_layout, objects);
        this.activity = activity;
        this.timeParams = objects;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
         rowView = convertView;


        if(rowView == null)
        {
            // Get a new instance of the row layout view
            LayoutInflater inflater = activity.getLayoutInflater();
            rowView = inflater.inflate(R.layout.cell_layout, null);

            // Hold the view objects in an object,
            // so they don't need to be re-fetched
            tView = new TimeView();
            tView.timer = (Chronometer) rowView.findViewById(R.id.timeTracker);
            tView.jobText = (TextView) rowView.findViewById(R.id.secondaryRowText);
            tView.jobSwitch = (Switch) rowView.findViewById(R.id.timeSwitch);

            // Cache the view objects in the tag,
            // so they can be re-accessed later
            rowView.setTag(tView);
        } else {
            tView = (TimeView) rowView.getTag();
        }

        // Transfer the job/time from the data object
        // to the view objects
        final timeTrackCell currentTime = (timeTrackCell) timeParams.get(position);

        tView.timer.setBase(currentTime.getChronometerTime());
        tView.jobText.setText(currentTime.getJobString());
        tView.jobSwitch.setChecked(currentTime.getSwitchPosition());


        //OnClick for switch toggle
        tView.jobSwitch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                Boolean newCheck = tView.jobSwitch.isChecked();
                System.out.println(tView.jobText.getText());

                //If newCheck returns true, the switch is being turned on
                //If newCheck returns false, the switch is being turned off
                tView.timer.stop();
                System.out.println(newCheck);
                if(newCheck){

                    tView.jobSwitch.setChecked(true);
                    tView.jobText.setText(currentTime.getJobString());
                    tView.timer.setBase(currentTime.getChronometerTime());
                    tView.timer.start();


                }else{

                    tView.timer.stop();
                    tView.jobSwitch.setChecked(false);
                    tView.jobText.setText(currentTime.getJobString());
                    tView.timer.setBase(currentTime.getChronometerTime());

                }


            }
        });

        return rowView;

    }


    protected static class TimeView {
        protected Chronometer timer;
        protected TextView jobText;
        protected Switch jobSwitch;
    }
}

这是我的 timeTrackCell class,其中包含我为适配器

获取和设置的所有内容
public class timeTrackCell {
    private boolean switchPosition;
    private long chronometerTime;
    private String jobString;

    public timeTrackCell(boolean switchPosition, long chronometerTime, String jobString){
        this.switchPosition = switchPosition;
        this.chronometerTime = chronometerTime;
        this.jobString = jobString;
    }
    //sets
    public void setSwitchPosition(boolean switchPosition){
        this.switchPosition = switchPosition;
    }
    public void setChronometerTime(long chronometerTime){
        this.chronometerTime = chronometerTime;
    }
    public void setJobString(String jobString){
        this.jobString = jobString;
    }
    //gets
    public boolean getSwitchPosition(){
        return switchPosition;
    }
    public long getChronometerTime(){
        return chronometerTime;
    }
    public String getJobString(){
        return jobString;
    }


}

这是我的单元格的 xml 文件,cell_layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:minHeight="140px"
    >
    <!--140px Seems to be the right height for 7 cells per page-->
    <!-- Block for custom listview items -->
    <Chronometer
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/timeTracker"
        android:layout_gravity="left"
        android:textSize="25sp"
        android:paddingLeft="10px"
        android:layout_centerVertical="true">
    </Chronometer>

    <TextView
        android:id="@+id/secondaryRowText"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/timeTracker"
        android:textSize="15sp"
        android:paddingLeft="10px"
        android:paddingTop="30px"
        >
    </TextView>

    <Switch
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/timeSwitch"
        android:gravity="right"
        android:layout_centerVertical="true"
        android:focusable="false"
        android:clickable="false"
        >
    </Switch>
</RelativeLayout>

这里是创建列表视图并填充数据的 java class。 timeKeeping.java

public class timeKeeping extends AppCompatActivity {
    public String empName = "Zach";
    private ListView lv;
    //tempchange
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /////
        //Button that shows who is logged in
        setContentView(R.layout.activity_time_keeping2);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setImageResource(R.drawable.ic_temp_profile_image);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String greetingString = "Welcome back, " + empName + "!";
                Snackbar.make(view, greetingString, Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        /////
        lv = (ListView) findViewById(R.id.timeList);
        //DUMBY DATA TO TEST WITH
        final List timeData = new ArrayList();
        Long testData = (long) 1000000;
        String tempJobTest = "test job ";
        for(int i = 0; i<5;i++){
            String nTempJobTest = tempJobTest + i;
            timeData.add(new timeTrackCell(false, testData, nTempJobTest));

        }


        lv.setAdapter(new timeTrackCellAdapter(this, timeData));

    }
}

我相当确定我的问题出在我的 timeTrackCellAdapter class 中的 onClick,但如果不是,我可以提供更多代码。非常感谢任何帮助!!

 @Override
 public View getView(int position, View convertView, ViewGroup parent
 {
    //OnClick for switch toggle
    tView.jobSwitch.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            Boolean newCheck = tView.jobSwitch.isChecked();
            System.out.println(tView.jobText.getText());

            //If newCheck returns true, the switch is being turned on
            //If newCheck returns false, the switch is being turned off
            tView.timer.stop();
            System.out.println(newCheck);
            if(newCheck){

                tView.jobSwitch.setChecked(true);
                tView.jobText.setText(currentTime.getJobString());
                tView.timer.setBase(currentTime.getChronometerTime());
                tView.timer.start();


            }else{

                tView.timer.stop();
                tView.jobSwitch.setChecked(false);
                tView.jobText.setText(currentTime.getJobString());
                tView.timer.setBase(currentTime.getChronometerTime());

            }


        }
    });

 }

在 getView 方法中设置侦听器可能是您对应用程序可能犯的最大错误。这个方法被隐式调用,即使任何项目发生一次更新,你也可能会创建一个 MEMORY-BLACKHOLE,因为每次滚动或更新时都会调用 getView(int,View,ViewGroup),所以你会有在这种情况下,可能会向您的听众声明一千次。尝试一些其他的代码片段来设置你的监听器,这不是一个解决方案,而是一个严格的建议

您在每个 onView 中引用了相同的 tView 变量。因为变量在 class 范围内而不在方法范围内。如果您创建 5 行,那么第一行使用 class 变量,第二行也是(丢失第一行的引用),依此类推。

这就是为什么当您单击任何行时,您正在修改最后添加的行。

解决方案可能只是在方法内部创建一个局部变量。但是,我建议您使用 RecyclerView,它是 ListView 的继承者。

无论如何,首先从 class 中删除变量:

public class timeTrackCellAdapter extends ArrayAdapter {
    private final Activity activity;
    private final List timeParams;
    View rowView;
    ....

然后,在方法中创建它:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
     rowView = convertView;
     TimeView tView;

致电notifyDataSetChanged();

tView.jobSwitch.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            Boolean newCheck = tView.jobSwitch.isChecked();
            System.out.println(tView.jobText.getText());

            //If newCheck returns true, the switch is being turned on
            //If newCheck returns false, the switch is being turned off
            tView.timer.stop();
            System.out.println(newCheck);
            if(newCheck){

                tView.jobSwitch.setChecked(true);
                tView.jobText.setText(currentTime.getJobString());
                tView.timer.setBase(currentTime.getChronometerTime());
                tView.timer.start();


            }else{

                tView.timer.stop();
                tView.jobSwitch.setChecked(false);
                tView.jobText.setText(currentTime.getJobString());
                tView.timer.setBase(currentTime.getChronometerTime());

            }
            //call this method
            notifyDataSetChanged();

        }