My stack is really overflow, I mean currently my brain can't think of a way to solve the below problem (Although I'm through trial-and-errors many days). All I want to do is a sreen for deletion purpose like (http://asset0.cbsistatic.com/cnwk.1d/i/tim/2010/11/18/Foreman_11654166_1073_com.probosoft.masscontactsdelete0_257x386.png)
I have a layout of a list-view-item like this (log_view.xml):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp" >
<TextView
android:id="@+id/date_view"
android:gravity="top|left"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true" />
<TextView
android:id="@+id/value_view"
android:gravity="top|left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/date_view"
android:layout_alignParentLeft="true" />
<CheckBox
android:id="@+id/del_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:visibility="gone"
android:focusable="false"
android:focusableInTouchMode="false" />
</RelativeLayout>
And this is my custom Adapter:
class LogViewAdapter extends ArrayAdapter<Log> {
public ArrayList<Boolean> checkList;
private int layoutResourceId;
private List<Log> logList;
Context context;
public LogViewAdapter(Context context, int layoutResourceId, List<Log> data) {
super(context, R.layout.log_view, data);
this.layoutResourceId = layoutResourceId;
this.logList = data;
this.context = context;
checkList = new ArrayList<Boolean>();
for (int i = 0; i < logList.size(); i ++) {
checkList.add(false);
}
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
LogHolder logholder = null;
// Unidentified view
if(convertView == null) {
LayoutInflater inflater = ((Activity)context).getLayoutInflater();
convertView = inflater.inflate(layoutResourceId, parent, false);
// Create a new holder
logholder = new LogHolder();
logholder.del_box = (CheckBox)convertView.findViewById(R.id.del_box);
logholder.del_box.setChecked(false);
final int iFinal = position;
logholder.del_box.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
checkList.set(iFinal, buttonView.isChecked());
}
});
logholder.date_view = (TextView)convertView.findViewById(R.id.date_view);
logholder.value_view = (TextView)convertView.findViewById(R.id.value_view);
convertView.setTag(logholder);
}
// Identified view
else {
logholder = (LogHolder) convertView.getTag();
}
if (null != logList) {
if (null == checkList.get(position)) {
android.util.Log.e("getView", "Checklist null at " + position);
}
//logholder.del_box.setChecked((boolean)checkList.get(position));
logholder.date_view.setText(logList.get(position).date);
logholder.value_view.setText(logList.get(position).value);
}
android.util.Log.e("getView", " at " + position + ", value = " + logholder.value_view.getText().toString());
return convertView;
}
static class LogHolder {
TextView date_view;
TextView value_view;
CheckBox del_box;
}
}
Here 's my Activity:
public class LoggingActivity extends Activity {
//private EditText edt_name;
//private EditText edt_value;
private List<Log> logList;
private LogViewAdapter lw_adapter;
private ListView listView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.log_list);
//setContentView(android.R.layout.simple_list_item_multiple_choice);
logList = loadLog();
listView = (ListView)findViewById(R.id.log_list);
lw_adapter = new LogViewAdapter(this, R.layout.log_view, logList);
//lw_adapter = new LogViewAdapter(this, android.R.layout.simple_list_item_multiple_choice, logList);
listView.setAdapter(lw_adapter);
}
/** implements for the MENU soft-key. Android 2.3.x */
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.layout.log_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// IMPORTANT: listView.getChildAt(index) will only return the
// VISIBLE views in our current screen
if (null == listView) {
android.util.Log.e("onOptionsItemSelected", "list view null");
return true;
}
for (int i = 0; i < listView.getChildCount(); i++) {
if (null == listView.getChildAt(i)) continue;
CheckBox cb = (CheckBox) listView.getChildAt(i).findViewById(R.id.del_box);
if (null == cb) {
android.util.Log.e("CB", "CheckBox at i = " + i + " null");
return true;
}
final int iFinal = i + listView.getFirstVisiblePosition();
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listView.setItemChecked(iFinal, buttonView.isChecked());
lw_adapter.checkList.set(iFinal, buttonView.isChecked());
}
});
SparseBooleanArray selectedItems = listView.getCheckedItemPositions();
for (int j = 0; j < selectedItems.size(); j++) {
if (selectedItems.get(j)) {
android.util.Log.e("CHECKED", "Row "+ j + ", value = " + logList.get(j).value);
}
else {
android.util.Log.e("CHECKED", "Row "+ j + " not checked");
}
}
cb.setVisibility(View.VISIBLE);
cb.setEnabled(true);
}
File folder = new File(Environment.getExternalStorageDirectory(), "50802566/logs");
// Handle item selection
switch (item.getItemId()) {
case R.id.log_sort:
Collections.sort(logList, Log.COMPARE_BY_VALUE);
lw_adapter.notifyDataSetChanged();
return true;
case R.id.log_del:
for (int i = 0; i < logList.size(); i++) {
if (null == lw_adapter.checkList.get(i)) {
android.util.Log.e("DEL", "checklist["+ i + "] = null");
continue;
}
if (true == lw_adapter.checkList.get(i)) {
// Delete correlative xml file
Date date = null;
try {
date = new SimpleDateFormat("dd/MM/yyyy - HH:mm:ss").parse(logList.get(i).date);
} catch (ParseException e) {
e.printStackTrace();
}
String filename = new SimpleDateFormat("dd_MM_yyyy - HH_mm_ss").format(date) + ".xml";
File toDelete = new File(folder, filename);
if (!toDelete.delete()) {
android.util.Log.e("DEL", "File " + filename + ": not deleted or not existed");
}
// Update the UI
//lw_adapter.remove(logList.get(i));
//lw_adapter.checkList.remove(i);
logList.set(i, null);
lw_adapter.checkList.set(i, null);
android.util.Log.e("DEL", "Deleted " + i + ", size now = " + logList.size());
}
}
for (int i = 0; i < logList.size(); i ++) {
if (null == logList.get(i)) {
logList.remove(i);
lw_adapter.checkList.remove(i);
}
}
lw_adapter.notifyDataSetChanged();
// Refresh the checklist
for (int i = 0; i < lw_adapter.checkList.size(); i ++) {
lw_adapter.checkList.set(i, false);
}
for (int i = 0; i < listView.getChildCount(); i++) {
if (null == listView.getChildAt(i)) continue;
CheckBox cb = (CheckBox) listView.getChildAt(i).findViewById(R.id.del_box);
cb.setChecked(false);
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private ArrayList<Log> loadLog() {
/** Access folder "my_logs" in external memory */
File baseDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator +
"50802566/logs");
if (!baseDir.exists()) { baseDir.mkdirs(); }
File[] list_file = baseDir.listFiles();
/** For internal memory */
//File[] list_file = this.getFilesDir().listFiles();
for (int i = 0; i < list_file.length; i ++) {
//android.util.Log.e("PRINTLN", list_file[i].getPath());
}
int l = list_file.length;
ArrayList<Log> list = new ArrayList<Log>();
while (l-- > 0) {
File file = list_file[l];
FileInputStream fin = null;
LogReader logr = new LogReader();
try {
fin = new FileInputStream(file);
list.add(list_file.length - l - 1, logr.read(fin));
fin.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
}
The problems are:
I can't keep track of the boolean values of each CheckBox. For example if I sort the logList (my database) then "notifyDataSetChanged" to the listView, but my checkList (which contains booleans of CheckBoxes) is still the same, and strange things happen. How can I apply EXACTLY all the sorting-operation of one list to another one? (IMO, "synchronized sorting" or something like that...)
As you can see, I've try to solve #1 by getting the del_box (my CheckBox id) from its row, then set its "OnCheckedChangeListener" to do the assigning (once checked, it will update relative value in the boolean List "checkList"). But strange things happen again:
- When checked a row and scroll down, I see another one is (automatically) checked.
- When scrolling back, the previous checked one... is unchecked (automatically, of course).
Maybe the Adapter is re-using the wrong Views/values but I have no clue of how to make it do things right... >"<
Your helps and guides are great. Sample codes of yours will be more than perfect to me... (I'm really exhausted right now >"<)
UPDATE:
I've find a way to (somehow) solve it, but maybe it's against API behavior of Android... If you have safer solution, plz let me know ^^
As mentioned in my second comment, here's the code:
@Override
public void onCheckedChanged(CompoundButton cb, boolean isChecked) {
int graphicalPos = -1;
// This is top view on the screen for sure
if (((View)cb.getParent()).getTop() < 0) {
graphicalPos = 0;
}
else if (((View)cb.getParent()).getTop() == 0) {
graphicalPos = (int) (((View)cb.getParent()).getTop() / ((View)cb.getParent()).getHeight());
}
else if (((View)cb.getParent()).getTop() > 0) {
graphicalPos = (int) (((View)cb.getParent()).getTop() / ((View)cb.getParent()).getHeight());
if (listView.getChildAt(0).getTop() < 0) {
graphicalPos = graphicalPos + 1;
}
else {
android.util.Log.e("NOT PLUS", "First Top = " + listView.getChildAt(0).getTop());
}
}
int dataPos = graphicalPos + listView.getFirstVisiblePosition();
lw_adapter.checkList.set(dataPos, isChecked);
android.util.Log.e("onCheckedChanged", "Checked row " + dataPos + ", graphicalPos = " + graphicalPos + ", top = " + ((View)cb.getParent()).getTop() + ", firstVisible = " + listView.getFirstVisiblePosition());
}