I have a listview with custom cells that hold a chronometer, a textview, and a switch. This listview is populated with dumby data from an ArrayList. Whenever a switch is clicked, it always affects the last item in the listview rather than the one I intended to click.
Here's my custom 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;
}
}
Here is my timeTrackCell class, which has all my gets and sets for my adapter
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;
}
}
Here is the xml file for my cells, 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>
And here is the java class that creates the listview and populates the data. 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));
}
}
I'm fairly sure my problem is with my onClick within my timeTrackCellAdapter class, but if it isn't I can provide more code. Any help is vastly appreciated!!
You are referencing THE SAME tView
variable in every onView
. Because the variable is on class scope and not in method scope. If you create 5 rows, then the first row is using the class variable, also the second row (loosing the reference from first one), and so on.
That's why when you click on any row, you are modifying the last added row.
The solution could be just make a local variable inside the method. BUT, I reccomend you to use RecyclerView, which is the succesor of ListView.
Anyway, first remove variable from class:
public class timeTrackCellAdapter extends ArrayAdapter {
private final Activity activity;
private final List timeParams;
View rowView;
....
And then, create it inside the method:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
rowView = convertView;
TimeView tView;
call 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();
}
@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());
}
}
});
}
Setting up listeners in the getView method is probably the biggest blunder you could to your app. This methods gets implicitly called, even when a single update happens to any item, you will probably create a MEMORY-BLACKHOLE, because getView(int,View,ViewGroup) gets called everytime you scroll or make an update, so you would have probably declared your listener a thousand times in this case. Try some other code fragment for setting up your listener, and this is not a solution but a strict recommendation
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.