Custom View kopiert Werte auf alle anderen Views.

L

Lybrial

Neues Mitglied
0
Hallo,

mir ist heute etwas sehr merkwürdiges begegnet und ich finde einfach
nicht die Lösung für das Problem, ich verstehe nicht mal im geringsten
warum es zu folgendem Verhalten kommt:

Ich habe eine Ansicht in meiner App, in der der User per Knopfdruck
weitere "Zeilen" bestehend aus zwei Spinnern, einer TextView und einem
Button, zur Ansicht hinzufügen kann. Dort kann er dann individuelle Werte
setzen. Sobald nun die View zerstört und neu aufgebaut wird, werden die
Werte aller so hinzugefügten Zeilen auf die Werte der zuletzt hinzugefügten
Zeile gesetzt. Dazu ein Beispiel: Bild links vor der Rotation, Bild rechts danach:



Zum speichern der Werte nutze ich ein Parcelable:

Code:
 	public class AMRState implements Parcelable {
		private List<AMRRowState> rowStates = null;

		public AMRState() {
			this.rowStates = new ArrayList<AMRRowState>();
		}

		public AMRState(List<AMRRowState> rowStates) {
			this.rowStates = rowStates;
		}
		
		public AMRState(Parcel in) {
			this.rowStates = new ArrayList<AMRRowState>();
			
			in.readList(rowStates, null);
		}

		@Override
		public int describeContents() {
			return 0;
		}

		@Override
		public void writeToParcel(Parcel dest, int flags) {
			dest.writeList(this.rowStates);
		}
		
		public static final Parcelable.Creator<AMRState> CREATOR = new Parcelable.Creator<AMRState>() {

			public AMRState createFromParcel(Parcel in) {
				return new AMRState(in); 
			}

			public AMRState[] newArray(int size) {
				return new AMRState[size];
			}
		};

		public List<AMRRowState> getRowStates() {
			return rowStates;
		}

		public void setRowStates(List<AMRRowState> rowStates) {
			this.rowStates = rowStates;
		}
	}
	
	public class AMRRowState implements Parcelable {
		private int activity = -1;
		private int hour = -1;
		private String pal = null;
		
		public AMRRowState(int activity, int hour, String pal) {
			this.activity = activity;
			this.hour = hour;
			this.pal = pal;
		}
		
		public AMRRowState(Parcel in) {
			this.activity = in.readInt();
			this.hour = in.readInt();
			this.pal = in.readString();
		}

		@Override
		public int describeContents() {
			return 0;
		}

		@Override
		public void writeToParcel(Parcel dest, int flags) {
			dest.writeInt(this.activity);
			dest.writeInt(this.hour);
			dest.writeString(this.pal);
		}
		
		public static final Parcelable.Creator<AMRRowState> CREATOR = new Parcelable.Creator<AMRRowState>() {
			public AMRRowState createFromParcel(Parcel in) {
				return new AMRRowState(in); 
			}

			public AMRRowState[] newArray(int size) {
				return new AMRRowState[size];
			}
		};

		public int getActivity() {
			return activity;
		}

		public void setActivity(int activity) {
			this.activity = activity;
		}

		public int getHour() {
			return hour;
		}

		public void setHour(int hour) {
			this.hour = hour;
		}

		public String getPal() {
			return pal;
		}

		public void setPal(String pal) {
			this.pal = pal;
		}
	}

Welches hier in meiner View zum Einsatz kommt:

Code:
	public class AMRFragment extends Fragment implements OnClickListener {
		private FragmentManager fragmentManager = null;
		private LinearLayout layoutRowParent = null;
		private Button buttonAddRow = null;
		private AMRState amrState = null;
		private int rowID = 1;
		
		@Override
		public void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);
			
			this.fragmentManager = this.getActivity().getSupportFragmentManager();
		}

		@Override
		public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
			return inflater.inflate(R.layout.activity_calc_amr_fragment, container, false);
		}

		@Override
		public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
			super.onViewCreated(view, savedInstanceState);
			
			this.layoutRowParent = (LinearLayout) view.findViewById(R.id.calc_amr_row_parent);
			this.buttonAddRow = (Button) view.findViewById(R.id.calc_amr_button_add_row);
			this.buttonAddRow.setOnClickListener(this);

			this.restoreState(savedInstanceState);
		}
		
		@Override
		public void onSaveInstanceState(Bundle outState) {
			super.onSaveInstanceState(outState);
			
			outState.putParcelable(Globals.KEY_AMR_STATE, this.amrState);
		}
		
		@Override
		public void onClick(View view) {
			switch (view.getId()) {
				case R.id.calc_amr_button_calc : {
					this.performCalcAMR();
				} break;
				case R.id.calc_amr_button_add_row : {
					this.performAddRow(view);
				} break;
				case R.id.calc_amr_row_button_delete : {
					this.performDeleteRow(view);
				}
			}
		}
		
		private void restoreState(Bundle inState) {
			if(inState != null) {
				this.amrState = (AMRState) inState.getParcelable(Globals.KEY_AMR_STATE);
				this.inflateEditRows();
			} else {
				this.amrState = new AMRState();
				this.inflateEditRow(-1, -1, -1, null, true);
			}
		}
		
		private void performCalcAMR() {}
		
		private void performAddRow(View view){
			if(this.layoutRowParent.getChildCount() < 25) {
				this.inflateEditRow(-1, -1, -1, null, true);
			}
		}
		
		private void performDeleteRow(View view) {
			View parent = (View) view.getParent();
			int parentIndex = this.layoutRowParent.indexOfChild(parent);
			
			this.amrState.getRowStates().remove(parentIndex);
			this.layoutRowParent.removeView(parent);
		}
		
		@SuppressLint("InflateParams")
		private void inflateEditRow(int id, int activity, int hour, String pal, boolean addToAMRRowState) {
			Log.v("InflateEditRow", "" + id + " - " + activity + " - " + hour + " - " + pal);
			
			final LayoutInflater inflater = (LayoutInflater) this.getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			final View rowView = inflater.inflate(R.layout.activity_calc_amr_fragment_row, null);
			final Spinner spinnerActivities = (Spinner) rowView.findViewById(R.id.calc_amr_row_spinner_activity);
			final Spinner spinnerHours = (Spinner) rowView.findViewById(R.id.calc_amr_row_spinner_hours);
			final TextView textViewPal = (TextView) rowView.findViewById(R.id.calc_amr_row_textview_pal);
			final ImageButton deleteButton = (ImageButton) rowView.findViewById(R.id.calc_amr_row_button_delete);
			
			spinnerActivities.setOnItemSelectedListener(this.listenerActivity);
			spinnerHours.setOnItemSelectedListener(this.listenerHour);
			
			if(id != -1) {
				rowView.setId(id);
			}
			
			if(activity != -1) {
				spinnerActivities.setSelection(activity);
			}
			
			if(hour != -1) {
				spinnerHours.setSelection(hour);
			}
			
			if(pal != null) {
				textViewPal.setText(pal);
			}
			
			if(this.layoutRowParent.getChildCount() == 0) {
				deleteButton.setVisibility(View.INVISIBLE);
			} else {
				deleteButton.setOnClickListener(this);
			}
			
			if(addToAMRRowState) {
				this.amrState.getRowStates().add(new AMRRowState(this.rowID++, activity, hour, pal));
			}
			
			Log.v("InflateEditRow", "rowID: " + rowView.getId() + ", spinnerac: " + spinnerActivities.getSelectedItemPosition() + ", spinnerhours: " + spinnerHours.getSelectedItemPosition() + ", pal: " + textViewPal.getText().toString());
			
			this.layoutRowParent.addView(rowView, this.layoutRowParent.getChildCount());
		}
		
		private void inflateEditRows() {
			for(AMRRowState amrRowState : this.amrState.getRowStates()) {
				this.inflateEditRow(amrRowState.getId(), amrRowState.getActivity(), amrRowState.getHour(), amrRowState.getPal(), false);
			}
		}
		
		private OnItemSelectedListener listenerActivity = new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
				View rowParentView = (View) parent.getParent();
				ViewGroup rowParentGroup = (ViewGroup) parent.getParent();
				int rowIndex = layoutRowParent.indexOfChild(rowParentView);
				String pal = null;
				
				switch (position) {
				case 1: pal = "" + 0.95;
					break;
				case 2: pal = "" + 1.2;
					break;
				case 3: pal = "" + 1.5;
					break;
				case 4: pal = "" + 1.7;
					break;
				case 5: pal = "" + 1.9;
					break;
				case 6: pal = "" + 2.4;
					break;
				default: pal = "";
					break;
				}
				
				for(int i = 0; i < rowParentGroup.getChildCount(); i++) {
					if(rowParentGroup.getChildAt(i) instanceof TextView) {
						((TextView) rowParentGroup.getChildAt(i)).setText(pal);
					}
				}
				
				amrState.getRowStates().get(rowIndex).setActivity(position);
				amrState.getRowStates().get(rowIndex).setPal(pal);
			}

			@Override
			public void onNothingSelected(AdapterView<?> parent) {}
		};
		
		private OnItemSelectedListener listenerHour = new OnItemSelectedListener() {

			@Override
			public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
				View rowParentView = (View) parent.getParent();
				int rowIndex = layoutRowParent.indexOfChild(rowParentView);
				
				amrState.getRowStates().get(rowIndex).setHour(position);
			}

			@Override
			public void onNothingSelected(AdapterView<?> parent) {}
		};
	}

Die Log-Ausgaben zeigen es, wenn ich zwei Zeilen hinzufüge ist die Log-Ausgabe die Folgende:

Code:
12-01 17:49:04.194: V/InflateEditRow(15547): 1 - 1 - 1 - 0.95
12-01 17:49:04.198: V/InflateEditRow(15547): rowID: 1 spinnerac: 1, spinnerhours: 1, pal: 0.95
12-01 17:49:04.198: V/InflateEditRow(15547): 2 - 2 - 2 - 1.2
12-01 17:49:04.202: V/InflateEditRow(15547): rowID: 2spinnerac: 2, spinnerhours: 2, pal: 1.2

Zunächst alles so wie es sein soll. Aber so sieht das nach der Rotation also nach der Zerstörung und Neubildung der View:

Code:
12-01 17:49:18.271: V/InflateEditRow(15547): 1 - 2 - 2 - 1.2
12-01 17:49:18.276: V/InflateEditRow(15547): rowID: 1 spinnerac: 2, spinnerhours: 2, pal: 1.2
12-01 17:49:18.276: V/InflateEditRow(15547): 2 - 2 - 2 - 1.2
12-01 17:49:18.280: V/InflateEditRow(15547): rowID: 2 spinnerac: 2, spinnerhours: 2, pal: 1.2

Das einzige was hier nicht verändert wurde ist die id, welche ich auch nur
zum Testen reingenommen hatte. Ich hatte kurz vermutet, dass das
Layout eventuell zwei Views als identisch betrachtet, wenn sie keine
unterschiedliche ID haben.

Wie gesagt habe ich keine Ahnung wie es zu dem Verhalten kommt
und bin mit meinem Latein völlig am Ende.
 
Die id werden in der R Klasse für das Mapping gebraucht. Du solltest es vermeiden, ids doppelt zu vergeben. Der Bereich zwischen 0x00000001 und 0x00FFFFFF kann von Programmierer frei verwaltet werden, alles was größer ist wird vom System vergeben. Am besten du benutzt atomare Werte für deine Ids.


Code:
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);

public static int generateId() {
    for (;;) {
        final int result = sNextGeneratedId.get();

        int newValue = result + 1;
        if (newValue > 0x00FFFFFF)
            newValue = 1;
        if (sNextGeneratedId.compareAndSet(result, newValue)) {
            return result;
        }
    }
}
 
Vielen Dank für deine Antwort. Ich habe das mal in mein Programm eingebaut
und getestet. Leider löst das das Problem nicht. Das beschriebene Verhalten
bleibt.
 
Ich verstehe den ganzen Ansatz nicht so ganz. Warum nimmt du nicht einfach einen ListView?
 
Ich habe vor etwa drei Wochen begonnen mit der Android Programmierung.
Ich bin bisher recht weit gekommen, meine Anwendung kann Bluetooth-Daten,
NFC-Daten, empfangen und verarbeiten. Sie kann Bilder aufnehmen und mit
einem Server synchronisieren. Sie enthält eine Registration, einen Log-In
und vieles mehr. Es ist im Prinzip eine Allround-App die ich erstellen wollte,
um mir Android Programming anzueignen.

Das ich keine ListView verwendet habe liegt ganz einfach daran, dass ich
noch nie davon gehört habe :D. Wenn ich etwas bestimmtes umsetzen wollte,
habe ich drauf los gegooglet und das Erste, das vernünftig erklärt und einleuchtend war, habe ich dann versucht umzusetzen.

So kam es auch zu der Art und Weise, wie ich meine Zeilen erzeuge und
hinzufüge. Ich habe mir dazu ein Tutorial durchgelesen, versucht es zu
verstehen und es umzusetzen. Dies hat für einen anderen Teil der App
wunderbar funktioniert, in der ich eine Übersicht von Messdaten ebenso
dynamisch generiere, wie die oben beschriebene Liste von Aktivitätswerten.

Wenn es eine bessere Möglichkeit gibt, immer her damit :D, allerdings
würde ich trotzdem gerne verstehen, wie es zu solch einem Fehler kommen kann.
 
Ich tendiere auch zu einem Listview.

Zur Not konfigurierst du den halt so, dass der keine Linien anzeigt (wenn dir das wichtig ist).

Beschäftige dich mit dem Thema "Rotation" und Android (google!). Das ist ein Thema mit 1000+1 Tutorials.

Wenn das checkst, musst du bei der Drehung nicht alles parceln bis zum Abgasen.
 
Jo ne ListView hat das Problem komplett gelöst. Vielen Dank.
 

Ähnliche Themen

5
Antworten
22
Aufrufe
1.420
590239
5
R
  • RalfKahl
Antworten
10
Aufrufe
299
RalfKahl
R
B
Antworten
4
Aufrufe
491
bb321
B
Zurück
Oben Unten