问题
So i have a list of alarms and i bind that to the listview, lstAlarms. In my custom ListView layout, i also have a switch, which i want to be set programmatically according to the status of the alarm. I want to do this right after the ListView is just displayed.
Please see my codes below. The method that display the ListView is DisplayAlarmList(). The method that im trying to use to set the states of the switches is InitSwitches(), which is being called inside of DisplayAlarmList(). DisplayAlarmList() is called in the onCreate() method.
public void DisplayAlarmList()
{
final String[] columns = {Database.getAlarmID(), Database.getAlarmTime(), Database.getAlarmName(), Database.getAlarmStatus(), Database.getAlarmRepeats()};
Cursor c = Database.selectAlarm(db, Database.getTableName(), columns, null, null, null, null, null);
int[] to = new int[]{
R.id.alarmID,
R.id.alarmTime,
R.id.alarmName,
R.id.alarmStatus,
R.id.alarmRepeats,
};
SimpleCursorAdapter ca = new SimpleCursorAdapter(this,
R.layout.alarm_info,
c,
columns,
to,
0);
lstAlarm.setAdapter(ca);
InitSwitches();
lstAlarm.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> listView, View view, int position, long id)
{
Alarm alarm = new Alarm();
Cursor selectedCursor = (Cursor) listView.getItemAtPosition(position);
Switch s = (Switch) view.findViewById(R.id.alarmSwitch);
String whereArgs = Integer.toString(selectedCursor.getInt(selectedCursor.getColumnIndexOrThrow(Database.getAlarmID())));
Cursor data = Database.RawQuery(db, "SELECT * FROM " + Database.getTableName() + " WHERE " + Database.getAlarmID() + " = " + whereArgs);
if (data.moveToFirst())
{
alarm.setAlarmID(data.getString(data.getColumnIndexOrThrow(Database.getAlarmID())));
alarm.setAlarmName(data.getString(data.getColumnIndexOrThrow(Database.getAlarmName())));
alarm.setAlarmTime(data.getString(data.getColumnIndexOrThrow(Database.getAlarmTime())));
alarm.setAlarmSound(data.getString(data.getColumnIndexOrThrow(Database.getAlarmSound())));
alarm.setAlarmRepeats(data.getString(data.getColumnIndexOrThrow(Database.getAlarmRepeats())));
alarm.setAlarmStatus(data.getString(data.getColumnIndexOrThrow(Database.getAlarmStatus())));
}
Intent i = new Intent(view.getContext(), ScreenEdit.class);
i.putExtra("editAlarm", new Gson().toJson(alarm));
startActivityForResult(i, EDIT_ALARM);
}
});
}
public void InitSwitches()
{
View v;
Switch s;
int index = 0;
String query = "Select " + Database.getAlarmID() + ", " + Database.getAlarmStatus() + " FROM " + Database.getTableName();
Cursor statuses = Database.RawQuery(db, query);
while (statuses.moveToNext())
{
v = lstAlarm.getAdapter().getView(index++, null, null);
s = (Switch) v.findViewById(R.id.alarmSwitch);
if (statuses.getString(statuses.getColumnIndexOrThrow(Database.getAlarmStatus())).equals("ON")){
s.toggle();
}
else{
}
}
}
ScreenShot of app
As you can see from the screen shot, the switch in the second row is supposed to be ON, but it's not. I tried InvilidateView() too but didnt work. Please help.
回答1:
The problem is that ListView is one of those views that is being constantly redrawn, meaning that this is bad, becuase it will lose the state when redrawn
if (statuses.getString(statuses.getColumnIndexOrThrow(Database.getAlarmStatus())).equals("ON")){
s.toggle();
}
First create AlarmItem... you should make it according to your needs. Here is mine
public class AlarmItem {
private String alarmName;
private String alarmDescption;
private boolean state;
private long id;
public AlarmItem(String alarmName, String alarmDescption, long id, boolean state) {
this.alarmName = alarmName;
this.alarmDescption = alarmDescption;
this.id = id;
this.state = state;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getAlarmName() {
return alarmName;
}
public void setAlarmName(String alarmName) {
this.alarmName = alarmName;
}
public String getAlarmDescption() {
return alarmDescption;
}
public void setAlarmDescption(String alarmDescption) {
this.alarmDescption = alarmDescption;
}
public boolean getState() {
return state;
}
public void setState(boolean state) {
this.state = state;
}
}
Now we will need a CustomSwitch class because of this: LINK
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Switch;
public class CustomSwitch extends Switch {
private OnCheckedChangeListener mListener;
public CustomSwitch(Context context) {
super(context);
}
public CustomSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomSwitch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
// Do not call supper method
mListener = listener;
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
if (mListener != null) {
mListener.onCheckedChanged(this, checked);
}
}
public void setCheckedProgrammatically(boolean checked) {
// You can call super method, it doesn't have a listener... he he :)
super.setChecked(checked);
}
}
create a layout file and name it alarm_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="vertical">
<com.example.alarm.list.CustomSwitch
android:id="@+id/alarmSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="10dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_toLeftOf="@id/alarmSwitch"
android:orientation="vertical">
<TextView
android:id="@+id/tvAlarmName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="Alarm Name"
android:textSize="18sp"/>
<TextView
android:id="@+id/tvAlarmDesc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="Alarm description"
android:textSize="15sp"/>
</LinearLayout>
</RelativeLayout>
Now the main layout -> activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lvAlarms"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Now we need an adapter that is responsible for drawing the items and their handling. Name the class AlarmAdapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.List;
public class AlarmAdapter extends BaseAdapter {
private List<AlarmItem> listOfAlarms;
private Context context;
private OnAlarmCheckedChangeListener mCallback;
// avoid constant allocation
private View tmpView;
private AlarmItemViewHolder mHolder;
private AlarmItem tmpItem;
public AlarmAdapter(List<AlarmItem> listOfAlarms, Context context, OnAlarmCheckedChangeListener callBack) {
this.listOfAlarms = listOfAlarms;
this.context = context;
mCallback = callBack;
}
@Override
public int getCount() {
return listOfAlarms == null ? 0 : listOfAlarms.size();
}
@Override
public AlarmItem getItem(int i) {
return listOfAlarms == null ? null : listOfAlarms.get(i);
}
@Override
public long getItemId(int i) {
return 0;
}
@Override
public View getView(final int i, View view, ViewGroup viewGroup) {
tmpItem = listOfAlarms.get(i);
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(context);
tmpView = inflater.inflate(R.layout.alarm_list_item, null, false);
mHolder = new AlarmItemViewHolder(tmpView);
tmpView.setTag(mHolder);
}
else {
tmpView = view;
mHolder = (AlarmItemViewHolder) view.getTag();
}
mHolder.getAlarmNameTextView().setText(tmpItem.getAlarmName());
mHolder.getAlarmDescriptionTextView().setText(tmpItem.getAlarmDescption());
mHolder.getSwitch().setCheckedProgrammatically(tmpItem.getState());
mHolder.getSwitch().setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
listOfAlarms.get(i).setState(b);
mCallback.onAlarmStateChanged(listOfAlarms.get(i), i);
}
});
return tmpView;
}
public void clear() {
listOfAlarms.clear();
notifyDataSetChanged();
}
public void refill(List<AlarmItem> listOfAlarms) {
this.listOfAlarms = listOfAlarms;
notifyDataSetChanged();
}
public void toggleAllSwitches() {
for (AlarmItem item : listOfAlarms) {
item.setState(!item.getState());
}
notifyDataSetChanged();
}
public interface OnAlarmCheckedChangeListener {
public void onAlarmStateChanged(AlarmItem item, int postionInList);
}
private class AlarmItemViewHolder {
View base;
CustomSwitch mSwitch;
TextView mAlarmName;
TextView mAlarmDescription;
public AlarmItemViewHolder(View base) {
this.base = base;
}
public CustomSwitch getSwitch() {
if (mSwitch == null) {
mSwitch = (CustomSwitch) base.findViewById(R.id.alarmSwitch);
}
return mSwitch;
}
public TextView getAlarmNameTextView() {
if (mAlarmName == null) {
mAlarmName = (TextView) base.findViewById(R.id.tvAlarmName);
}
return mAlarmName;
}
public TextView getAlarmDescriptionTextView() {
if (mAlarmDescription == null) {
mAlarmDescription = (TextView) base.findViewById(R.id.tvAlarmDesc);
}
return mAlarmDescription;
}
}
}
And now finally the MainActivity
import android.app.ActionBar;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener, AlarmAdapter.OnAlarmCheckedChangeListener {
private ListView listView;
private AlarmAdapter adapter;
private Toast toast;
private Handler handler;
private Runnable handlerRunnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Make a nice actionBar title
ActionBar ab = getActionBar();
if (ab != null) {
ab.setTitle("Alarm List");
}
listView = (ListView) findViewById(R.id.lvAlarms);
// Simulating alarms from database. You need to convert your items to these
List<AlarmItem> alarmsFromDb = new ArrayList<>();
alarmsFromDb.add(new AlarmItem("Alarm 1", "Lalalala", 1, true));
alarmsFromDb.add(new AlarmItem("Alarm 2", "something", 2, false));
alarmsFromDb.add(new AlarmItem("Alarm 3", "gfdgdf", 3, true));
alarmsFromDb.add(new AlarmItem("Alarm 4", "sda", 4, true));
alarmsFromDb.add(new AlarmItem("Alarm 5", "yxcxyc", 5, false));
alarmsFromDb.add(new AlarmItem("Alarm 6", "dsfsd", 6, false));
adapter = new AlarmAdapter(alarmsFromDb, this, this);
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
// Toggle all switches after 5s... this is what you need, right?
handlerRunnable = new Runnable() {
@Override
public void run() {
adapter.toggleAllSwitches();
showToast("All switches toggeled :)");
}
};
handler = new Handler(Looper.getMainLooper());
handler.postDelayed(handlerRunnable, 5 * 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacks(handlerRunnable);
handler = null;
handlerRunnable = null;
}
}
private void showToast(String str) {
if (toast != null) {
toast.cancel();
}
toast = Toast.makeText(this, str, Toast.LENGTH_SHORT);
toast.show();
}
@Override
public void onAlarmStateChanged(AlarmItem item, int postionInList) {
String onOff = item.getState() ? "ON" : "OFF";
showToast("Alarm " + item.getAlarmName() + " is: " + onOff);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
AlarmItem item = adapter.getItem(position);
showToast("Alarm " + item.getAlarmName() + " clicked");
}
}
Here is the complete android studio project, if you have any problems: LINK
Now all you need to do is convert the results from your database to this list or modify the AlarmItem and the UI. This solution will work and you already have some helpful methods in the adapter.
Happy coding!
回答2:
does your adapter use the viewholder pattern? If not, the Views that go off screen are reused and may have old switch status that the new row coming in when scrolling should not have.
来源:https://stackoverflow.com/questions/29658499/trying-to-programmatically-set-the-state-of-a-switch-inside-a-listview-after-the