Custom ListView für eine Wecker-App

  • Antworten:37
  • OffenNicht stickyNicht beantwortet
  • Forum-Beiträge: 20

15.09.2016 14:36:12 via Website

Hallo Forum,

ich will zum Lernen eine einfache Wecker-App programmieren, die folgendes können soll:
- in einem ListView mehrere Alarme erstellen
- diese Alarme sollen sich durch einen Switch an- und ausschalten lassen
- der Wecker soll dann 1 mal am Tag den Alarm wiederholen
- durch ein langes klicken auf den ListView-Eintrag soll (nach einem Dialog) der Eintrag gelöscht werden

So, nun zu der Stelle, an der ich gerade festhänge:

Ich habe eine Custom ListView erstellt (list_view_item.xml). Dort habe ich dann den Switch hinzugefügt.

Um den Switch irgendwie anzusprechen, hab ich einen CustomAdapter erstellt. Der Alarm soll später über den "RingService" ausgelöst werden.

Wie kann ich jetzt den Switch am besten ansprechen? Also wie würdet ihr es machen?

Problem 2: Irgendwie funktioniert der OnItemLongClickListener bei meinem ListView Item nicht

Hier das Projekt auf Github: [github].../pixelfehler1996/Alarm_Clock
(ich kann noch keine Links posten...)

Vielen Dank schon mal im Voraus!

— geändert am 15.09.2016 14:43:24

  • Forum-Beiträge: 2.214

15.09.2016 14:50:48 via Website

Hallo Norman,

willkommen im Forum

es gibt bekanntlich viele Verfahren ...

Du kannst z.B. Dieses hier anwenden

http://stackoverflow.com/questions/8527101/how-to-know-which-view-inside-a-specific-listview-item-that-was-clicked

Um den Status des switches ausserhalb abzufragen , übergibst du dem Adapter am besten eine dynamische ArrayStruktur und ermittelst innerhalb des Adapters das true / false flag auf den Switch

Ergo : du baust dir einen onClickListener für das entsprechende View in deinen Adapter ein
.
.

Problem 2 :

Hier baust du dir einen onTouchListener in deinen Adapter ein , reagierst auf DOWN & UP und misst die Zeit dazwischen.

http://stackoverflow.com/questions/23793345/find-duration-between-touch-events-in-android
http://stackoverflow.com/questions/4324362/detect-touch-press-vs-long-press-vs-movement

lg
Stefan

— geändert am 15.09.2016 15:21:59

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

15.09.2016 15:04:28 via Website

Danke für die Antwort.

Eine Frage: Was genau meinst du mit "dynamische ArrayStruktur"?

Grüße
Norman

  • Forum-Beiträge: 2.214

15.09.2016 15:12:48 via Website

Eine Frage: Was genau meinst du mit "dynamische ArrayStruktur"?

Im Prinzip eine ArrayList indem du nicht nur ein Element verwaltest, sondern eine Klassenstruktur

public class mystruct
{
public Boolean mySwitch;
public String myString;
}
ArrayList < mystruct > foo = new ArrayList();

Diese füllst du dann mit add und übergibst die dann dem Adapter
Innerhalb diesem kannst du dann der Struktur die Stati setzen

http://www.vogella.com/tutorials/AndroidListView/article.html

lg
Stefan

— geändert am 15.09.2016 15:18:19

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

15.09.2016 16:47:21 via Website

Nur, damit ich das richtig verstehe (mir fehlen da wohl noch einige Java-Kenntnisse):

In der ArrayList sind die Objekte der Klasse "mystruct" gespeichert, die dann jeweils die Eigenschaften "mySwitch" für den Status des Switches und "myString" für - in dem Fall - die dargestellte Uhrzeit besitzen, soweit richtig?

Der Adapter ist dann dafür zuständig aus der Übergebenen Liste an Objekten, die entsprechenden Eigenschaften auszulesen und in der ListView darzustellen. Korrekt?

Und noch eine Frage: erbt meine Custom Adapter-Klasse jetzt am besten von ArrayAdapter oder BaseAdapter? Weil, es wird ja jetzt kein String mehr übergeben, sondern die "mystruct"-Klasse.

— geändert am 15.09.2016 16:50:00

  • Forum-Beiträge: 2.214

15.09.2016 17:00:27 via Website

Hallo Norman,

ich leite bei so etwas immer von einem BaseAdapter ab.
So wie ich es vorgeschlagen habe, ist es zwar ein wenig "erweiternd" - du bist aber bei weitem flexibler

Innerhalb von BaseAdapter deklarierst du z.b

private ArrayList mData = new ArrayList();

dann erweiterst/änderst du das Ganze mit

public void addItem(final myStruct item)
{
mData.add(item);
notifyDataSetChanged(); <-- nur wenn unbedingt notwendig , sonst macht der das bei jedem Add
}

und

@Override
public myStruct getItem(int position) {return (myStruct) mData.get(position);}

im getView kannst du dann folgendes machen

myStruct pg_tmp = (myStruct) mData.get(position);

und kannst somit auf alle element zugreiffen

in der Activity addest du die Einträge recht simple

myStruct foo = new myStruct();
foo.mySwitch = false;
foo.myString ="Erster Eintrag";
adapter.addItem (foo);

Kannst also rein theoretisch die oben erwähnte ArrayList weglassen

— geändert am 15.09.2016 22:57:59

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

16.09.2016 08:10:22 via Website

Du bist echt super hilfreich, danke! :)

— geändert am 16.09.2016 08:28:35

  • Forum-Beiträge: 20

16.09.2016 08:28:49 via Website

Aber irgendwas ist noch verkehrt. Die neue Uhrzeit wird bei "showDialog" noch nicht hinzugefügt.

MainActivity:

Calendar calendar;
Button btnShowDialog;
String timeString;
ArrayList alarmList;
CustomAdapter customAdapter;
ListView alarmListView;
Intent alarmIntent;
AlarmManager alarmManager;
PendingIntent pendingIntent;
SharedPreferences sharedPreferences;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    sharedPreferences = getSharedPreferences("com.example.laudien.wecker", MODE_PRIVATE);

    // Initialize variables
    btnShowDialog = (Button) findViewById(R.id.btnShowDialog);
    calendar = Calendar.getInstance();
    alarmListView = (ListView) findViewById(R.id.listView);
    timeString = "";
    alarmIntent = new Intent(MainActivity.this, RingService.class);
    alarmManager = (AlarmManager)MainActivity.this.getSystemService(Context.ALARM_SERVICE);

    // initialize ListView
    Set set = sharedPreferences.getStringSet("alarmList", null);
    if(set != null)
        alarmList = new ArrayList(set);
    else
        alarmList = new ArrayList<CustomItemStructure>();
    customAdapter = new CustomAdapter(MainActivity.this, alarmList, R.layout.list_view_item);
    alarmListView.setAdapter(customAdapter);
}

@Override
protected void onPause() {
    super.onPause();
    Set set = new HashSet();
    set.addAll(alarmList);
    sharedPreferences.edit().putStringSet("alarmList", set).apply();
}

public void showDialog(View view){
    new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() {
        @Override
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            Calendar timeCalendar = Calendar.getInstance();
            timeCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
            timeCalendar.set(Calendar.MINUTE, minute);
            timeString = DateUtils.formatDateTime(MainActivity.this, timeCalendar.getTimeInMillis(), DateUtils.FORMAT_SHOW_TIME);

            CustomItemStructure structure = new CustomItemStructure();
            structure.alarmSwitch = true;
            structure.alarmTimeInMillis = timeCalendar.getTimeInMillis();
            customAdapter.add(structure);

            Log.i("TimePicker", "The time was set to " + timeString);
        }
    }, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), DateFormat.is24HourFormat(MainActivity.this)).show();
}`

CustomAdapter:

public class CustomAdapter extends BaseAdapter {

Context userContext;
int layoutID;
private ArrayList<CustomItemStructure> alarmList = new ArrayList<>();

public CustomAdapter(Context context, ArrayList<CustomItemStructure> arrayList, @LayoutRes int resource) {
    layoutID = resource;
    userContext = context;
    alarmList = arrayList;
}

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

@Override
public Object getItem(int position) {
    return null;
}

@Override
public long getItemId(int position) {
    return 0;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    CustomItemStructure itemNow = alarmList.get(position);
    TextView alarmTextView = (TextView) convertView.findViewById(R.id.alarmTextView);
    Switch alarmSwitch = (Switch) convertView.findViewById(R.id.alarmSwitch);

    alarmTextView.setText(DateUtils.formatDateTime(userContext, itemNow.alarmTimeInMillis, DateUtils.FORMAT_SHOW_TIME));
    alarmSwitch.setChecked(itemNow.alarmSwitch);

    return convertView;
}
public void add(CustomItemStructure item){
    alarmList.add(item);
}

}`

CustomItemStructure:

public class CustomItemStructure{
public boolean alarmSwitch;
public long alarmTimeInMillis;
}`

— geändert am 16.09.2016 08:29:57

  • Forum-Beiträge: 2.214

16.09.2016 08:39:06 via Website

auf den ersten blick sehe ich , dass du es nicht so ganz amchst , wie ich oben geschrieben habe , denn jetzt vermischst du zwei ArrayList ..

einmal die , die du aus den Prefs ausliest, die übergibst du den Baseadpater - alles klar,
aber da hast du wiederum eine new Arraylist stehen

zweimal new kommt nicht gut :-)

und dann sehe ich nirgendwo , das du "add" vom Baseadapter benutzt - jetzt hast du ordentlich mischmasch drin :-)

Mache es so , wie ich oben beschrieben habe , dann liesst du die prefs aus und übergribst mit additem ( oder add)
Nimm nur EINE Arraylist , entweder ausserhalb oder innerhalb Baseadapter , nicht beides

EDIT : und getCount gibt bei dir immer 0 zurück

Und dann übergibst du dem Baseadapter nach dem Pref nicht deine Structur sondern ein Stringset

Tip : mach dir erst mal ein DemoProjekt , wo du ausschliesslich mit der List und Baseadapter rumspielst um genau zu erfahren , wie das Ganze funktioniert ..

— geändert am 16.09.2016 08:55:50

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

16.09.2016 09:16:18 via Website

Okay, das werd ich auch machen (mit dem DemoProjekt).

Im Prinzip hast du mir ja alles gegeben was ich brauche^^

Trotzdem eine Frage: Ich hab noch nicht so recht verstanden, was dieses "inflate" bedeutet, bzw. was ein "Inflater" genau macht.

  • Forum-Beiträge: 2.214

16.09.2016 09:21:35 via Website

Du kannst einer Reihe in einem ListView ein eigenes Layout hinterlegen.
Das wäre bei dir der Fall , einmal Text , den Switch und irgendwas was anderes dazu ( also z.b. noch einen Button)

in getView kannst du dann sowas hier machen

myView = mInflater.inflate(R.layout.myRowView, null);

Und hast dadurch dann Zugriff auf die Layoutelemente ( findViewById ) der Reihe

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

16.09.2016 10:02:34 via Website

Hast du ein einfaches Beispiel von einem funktionierenden CustomAdapter, der vom BaseAdapter extended?

— geändert am 16.09.2016 10:02:51

  • Forum-Beiträge: 20

16.09.2016 10:08:01 via Website

Nö, da extended man ja immer ArrayAdapter. Oder hab ich was übersehen?

EDIT: Schon gut, ich habs hinbekommen! Mir fehlte nur noch das "notifyDataSetChanged()" bei der "add"-Methode im CustomAdapter!

— geändert am 16.09.2016 10:20:27

  • Forum-Beiträge: 2.214

16.09.2016 10:18:02 via Website

und wie wäre es mit dem Punkt 2.3 ? :-)

Also ich finde X Beispiele im netz ..... mir ist es schon fast ein Rätsel, warum du keine findest :-)
http://abhiandroid.com/ui/baseadapter-tutorial-example.html

Hinweis : ArrayAdapter leitet sich von Basadapter ab (extends)

— geändert am 16.09.2016 10:19:17

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 2.214

16.09.2016 10:22:27 via Website

EDIT: Schon gut, ich habs hinbekommen! Mir fehlte nur das "notifyDataSetChanged()" bei der "add"-Methode im CustomAdapter!

Das Notify führt man am besten erst dann aus , wenn die Daten alle drin sind .
Notify bewirkt, dass ein Refresh durchgeführt wird . Haust du da X einträge hintereinander rein,
gibt es Performanceprobleme

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

16.09.2016 11:31:58 via Website

Wie würdest du denn die ganzen Alarme, die ja in einer ArrayList aus "CustomItemStructure"-Klassen gespeichert sind, am besten dauerhaft speichern? SharedPreferences oder doch schon eine kleine SQLite Datenbank? Oder JSON?

  • Forum-Beiträge: 2.214

16.09.2016 11:35:23 via Website

JSON ist eher eine Übergabeform.

Wenn du es vernünftig machen magst , dann würde ich zu einer sql raten .
Eine schöne lokale Datei, die ist auch noch da , wenn man die App deiinstalliert und das Backup ist auch
recht einfach zu gestalten ..

Um den Bogen zu überspannen , könnte man diese SQL auch noch im G-Drive unter dem Account sichern :-)

— geändert am 16.09.2016 11:36:41

Liebe Grüße - Stefan
[ App - Entwicklung ]

  • Forum-Beiträge: 20

19.09.2016 13:39:41 via Website

Soo...
Datenbank funktioniert (finally!)

Jetzt möchte ich den Alarm in der richtigen Zeit und jeden Tag klingeln lassen. Da muss ich beim AlarmManager "setRepeating", "RTC_WAKEUP" und "INTERVAL_DAY" nehmen, wenn ich das richtig verstanden habe, korrekt?