上下文菜单无法从 ListView 行中删除文件项
Context Menu Fails To Remove A File Item From A ListView Row
我的上下文菜单无法从 ListView 行中删除文件项。
我的代码运行并且 Toast 指示方法正在运行。
没有任何 logcat 错误。我的 ListView 工作正常。
我的问题是:我的代码有什么问题?
请提供正确的代码来解决这个问题。
谢谢。
Java代码:classTestActivity
public class TestActivity extends ListActivity {
private String root;
private List<String> item = null;
private ListView listview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
registerForContextMenu(getListView());
root = Environment.getExternalStorageDirectory() + File.separator + "/ListTestFiles";
getDir(root);
// 01-Nov-2017 For reference added 6 lines of my existing Java code.
listview = getListView();
listview.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
File file = new File(path.get(position));
String fullPath = new String(path.get(position));
} // End of onCreate code.
// 02-Nov-2017 For reference added 28 lines of my existing Java code.
private void getDir(String dirPath)
{
currentPath = dirPath;
item = new ArrayList<String>();
path = new ArrayList<String>();
File f = new File(dirPath);
File[] files = f.listFiles();
if(!dirPath.equals(root))
{
item.add(root);
path.add(root);
item.add("../");
path.add(f.getParent());
}
for(i2=0; i2 < files.length; i2++)
{
File file = files[i2];
if(!file.isHidden() && file.canRead()){
path.add(file.getPath());
if(file.isDirectory()) {
item.add(file.getName() + "/");
}else {
item.add(file.getName());
}
}
}
ArrayAdapter<String> fileList = new ArrayAdapter<String>(this, R.layout.layout_item_view, R.id.rowtext, item);
setListAdapter(fileList);
} // End of getDir method
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
switch (item.getItemId()) {
case R.id.context_menu_rename:
Toast.makeText(this, "Rename", Toast.LENGTH_SHORT).show();
return true;
case R.id.context_menu_delete:
// TEST #1 the line of code below runs but will Not delete the selected listview row item.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(TouchActivity.this, R.layout.layout_item_view, R.id.rowtext);
// TEST #2 the line of code below runs but will Not delete the selected listview row item.
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(TouchActivity.this, R.layout.layout_item_view);
// 01-Nov-2017 Added 2 New lines of code.
listview.setAdapter(adapter);
adapter.remove(String.valueOf(info.position));
// 01-Nov-2017 Commented out 2 lines of code.
// String index = valueOf(info.position);
// adapter.remove(index);
adapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "position = " + info.position, Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Delete", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onContextItemSelected(item);
}
}
}
XML代码:layout_item_view.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="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:background="#231f20">
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rowtext"
android:layout_width="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_height="60sp"
android:textSize="18sp"
android:layout_marginTop="10dp"/>
</RelativeLayout>
XML代码:row.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView
android:id="@+id/rowtext"
android:layout_width="fill_parent"
android:layout_height="60sp"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:layout_marginBottom="1dp"
xmlns:android="http://schemas.android.com/apk/res/android" />
XML代码:activity_test.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:longClickable="true"
android:clickable="true"
android:orientation="vertical"
android:layout_alignParentBottom="true"
android:background="#231f20">
<FrameLayout
android:layout_width="fill_parent"
android:layout_above="@+id/header"
android:layout_height="wrap_content" >
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_weight="1" xmlns:android="http://schemas.android.com/apk/res/android"
android:fillViewport="true" />
</FrameLayout>
</LinearLayout>
Logcat 输出:
// Test 3 I run the app with this code.
case R.id.context_menu_delete:
fileList.remove(fileList.getItem(info.position));
fileList.notifyDataSetChanged();
return true;
// Test 4 I run the app with String.valueOf code because the adapter is an ArrayAdapter<String>
case R.id.context_menu_delete:
fileList.remove(String.valueOf(fileList.getItem(info.position)));
fileList.notifyDataSetChanged();
return true;
// Logcat output is the same for both Test 3 and 4, the app crashes with the error below.
--------- beginning of crash
11-03 17:40:02.840 18362-18362/com.testing.listapp E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.testing.listapp, PID: 18362
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.widget.ArrayAdapter.getItem(int)' on a null object reference
at com.testing.listapp.TestActivity.onContextItemSelected(TestActivity.java:5413)
at android.app.Activity.onMenuItemSelected(Activity.java:2905)
at com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback.onMenuItemSelected(PhoneWindow.java:4701)
at com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:761)
at com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:904)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:894)
at com.android.internal.view.menu.MenuDialogHelper.onClick(MenuDialogHelper.java:167)
at com.android.internal.app.AlertController$AlertParams.onItemClick(AlertController.java:1082)
at android.widget.AdapterView.performItemClick(AdapterView.java:305)
at android.widget.AbsListView.performItemClick(AbsListView.java:1146)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3053)
at android.widget.AbsListView.run(AbsListView.java:3860)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Now when I do a long press on a Row item of the ListView, then press Delete on the context menu, all ListView Rows are removed from the view.
让我们看一下onContextItemSelected()
中的一些行:
下面这行给你一个全新的ArrayAdapter
。它的 getCount()
将 return 0 因为你没有将任何数据列表传递给它:
`ArrayAdapter<String> adapter = new ArrayAdapter<String>(TouchActivity.this, R.layout.layout_item_view, R.id.rowtext);`
现在您将此 "empty" 适配器设置为 ListView
适配器:
listview.setAdapter(adapter);
旧适配器将被丢弃。当前的数据列表为空。所以现在 ListView
是空的。
因为当前的适配器是"empty",所以下面这行将没有效果:
adapter.remove(String.valueOf(info.position));
I restart the activity and the ListView is shown with all rows still present. Nothing was permanently removed.
那是因为你用原来的适配器重新初始化了ListView
。
编辑
如何删除选中的项目?
您需要做的第一件事是使原始 ListView
适配器成为托管 ListView
的 Activity
或 Fragment
的字段:
private ArrayAdapter<String> fileList;
然后在初始化数据列表后将其实例化并设置为 ListView
的适配器,例如在 getDir()
结束时:
//...
fileList = new ArrayAdapter<String>(this, R.layout.layout_item_view, R.id.rowtext, item);
setListAdapter(fileList);
数据将像以前一样显示。现在您可以在整个 Activity
中访问此适配器,例如 onContextItemSelected()
:
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
switch (item.getItemId()) {
case R.id.context_menu_rename:
Toast.makeText(this, "Rename", Toast.LENGTH_SHORT).show();
return true;
case R.id.context_menu_delete:
fileList.remove(fileList.getItem(info.position));
fileList.notifyDataSetChanged();
return true;
default:
return super.onContextItemSelected(item);
}
}
请注意 fileList.getItem(info.position)
必须是唯一值,因为在引擎盖下 ArrayAdapter#remove()
表示 List#remove()
,因此第一个匹配的 List
条目将被删除,不要管它指数。如果你想绝对确定你删除了正确的条目,那么你必须使你的数据列表成为一个字段,并通过调用 item.remove(info.position)
删除索引 info.position 处的条目
以及如何使更改永久化?
我们现在实现的是用户可以从数据列表中删除一个条目,ListView
将通过不再显示删除的条目来反映这一点。如果您离开应用程序 ListView
/ 适配器将保留它们的状态,并在一段时间后 return 进入它,例如通过按 "HOME" 按钮。
但如果您倾斜设备并导致方向改变(从纵向到横向,反之亦然),则 onCreate()
将被执行并且删除的数据将恢复。如果用户按下 "BACK" 然后点击启动器图标,情况也是如此。
因此,如果您想永久更改,则必须以某种方式持久化它们。 (但我认为这超出了这个问题的范围。)例如参见 [=40=]
上的指南
我的上下文菜单无法从 ListView 行中删除文件项。 我的代码运行并且 Toast 指示方法正在运行。 没有任何 logcat 错误。我的 ListView 工作正常。
我的问题是:我的代码有什么问题? 请提供正确的代码来解决这个问题。
谢谢。
Java代码:classTestActivity
public class TestActivity extends ListActivity {
private String root;
private List<String> item = null;
private ListView listview;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
registerForContextMenu(getListView());
root = Environment.getExternalStorageDirectory() + File.separator + "/ListTestFiles";
getDir(root);
// 01-Nov-2017 For reference added 6 lines of my existing Java code.
listview = getListView();
listview.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
File file = new File(path.get(position));
String fullPath = new String(path.get(position));
} // End of onCreate code.
// 02-Nov-2017 For reference added 28 lines of my existing Java code.
private void getDir(String dirPath)
{
currentPath = dirPath;
item = new ArrayList<String>();
path = new ArrayList<String>();
File f = new File(dirPath);
File[] files = f.listFiles();
if(!dirPath.equals(root))
{
item.add(root);
path.add(root);
item.add("../");
path.add(f.getParent());
}
for(i2=0; i2 < files.length; i2++)
{
File file = files[i2];
if(!file.isHidden() && file.canRead()){
path.add(file.getPath());
if(file.isDirectory()) {
item.add(file.getName() + "/");
}else {
item.add(file.getName());
}
}
}
ArrayAdapter<String> fileList = new ArrayAdapter<String>(this, R.layout.layout_item_view, R.id.rowtext, item);
setListAdapter(fileList);
} // End of getDir method
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
switch (item.getItemId()) {
case R.id.context_menu_rename:
Toast.makeText(this, "Rename", Toast.LENGTH_SHORT).show();
return true;
case R.id.context_menu_delete:
// TEST #1 the line of code below runs but will Not delete the selected listview row item.
ArrayAdapter<String> adapter = new ArrayAdapter<String>(TouchActivity.this, R.layout.layout_item_view, R.id.rowtext);
// TEST #2 the line of code below runs but will Not delete the selected listview row item.
// ArrayAdapter<String> adapter = new ArrayAdapter<String>(TouchActivity.this, R.layout.layout_item_view);
// 01-Nov-2017 Added 2 New lines of code.
listview.setAdapter(adapter);
adapter.remove(String.valueOf(info.position));
// 01-Nov-2017 Commented out 2 lines of code.
// String index = valueOf(info.position);
// adapter.remove(index);
adapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "position = " + info.position, Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Delete", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onContextItemSelected(item);
}
}
}
XML代码:layout_item_view.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="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:background="#231f20">
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rowtext"
android:layout_width="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_height="60sp"
android:textSize="18sp"
android:layout_marginTop="10dp"/>
</RelativeLayout>
XML代码:row.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView
android:id="@+id/rowtext"
android:layout_width="fill_parent"
android:layout_height="60sp"
android:textSize="18sp"
android:layout_marginTop="10dp"
android:layout_marginBottom="1dp"
xmlns:android="http://schemas.android.com/apk/res/android" />
XML代码:activity_test.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:longClickable="true"
android:clickable="true"
android:orientation="vertical"
android:layout_alignParentBottom="true"
android:background="#231f20">
<FrameLayout
android:layout_width="fill_parent"
android:layout_above="@+id/header"
android:layout_height="wrap_content" >
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_weight="1" xmlns:android="http://schemas.android.com/apk/res/android"
android:fillViewport="true" />
</FrameLayout>
</LinearLayout>
Logcat 输出:
// Test 3 I run the app with this code.
case R.id.context_menu_delete:
fileList.remove(fileList.getItem(info.position));
fileList.notifyDataSetChanged();
return true;
// Test 4 I run the app with String.valueOf code because the adapter is an ArrayAdapter<String>
case R.id.context_menu_delete:
fileList.remove(String.valueOf(fileList.getItem(info.position)));
fileList.notifyDataSetChanged();
return true;
// Logcat output is the same for both Test 3 and 4, the app crashes with the error below.
--------- beginning of crash
11-03 17:40:02.840 18362-18362/com.testing.listapp E/AndroidRuntime﹕ FATAL EXCEPTION: main
Process: com.testing.listapp, PID: 18362
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.widget.ArrayAdapter.getItem(int)' on a null object reference
at com.testing.listapp.TestActivity.onContextItemSelected(TestActivity.java:5413)
at android.app.Activity.onMenuItemSelected(Activity.java:2905)
at com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback.onMenuItemSelected(PhoneWindow.java:4701)
at com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:761)
at com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:904)
at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:894)
at com.android.internal.view.menu.MenuDialogHelper.onClick(MenuDialogHelper.java:167)
at com.android.internal.app.AlertController$AlertParams.onItemClick(AlertController.java:1082)
at android.widget.AdapterView.performItemClick(AdapterView.java:305)
at android.widget.AbsListView.performItemClick(AbsListView.java:1146)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3053)
at android.widget.AbsListView.run(AbsListView.java:3860)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Now when I do a long press on a Row item of the ListView, then press Delete on the context menu, all ListView Rows are removed from the view.
让我们看一下onContextItemSelected()
中的一些行:
下面这行给你一个全新的ArrayAdapter
。它的 getCount()
将 return 0 因为你没有将任何数据列表传递给它:
`ArrayAdapter<String> adapter = new ArrayAdapter<String>(TouchActivity.this, R.layout.layout_item_view, R.id.rowtext);`
现在您将此 "empty" 适配器设置为 ListView
适配器:
listview.setAdapter(adapter);
旧适配器将被丢弃。当前的数据列表为空。所以现在 ListView
是空的。
因为当前的适配器是"empty",所以下面这行将没有效果:
adapter.remove(String.valueOf(info.position));
I restart the activity and the ListView is shown with all rows still present. Nothing was permanently removed.
那是因为你用原来的适配器重新初始化了ListView
。
编辑
如何删除选中的项目?
您需要做的第一件事是使原始 ListView
适配器成为托管 ListView
的 Activity
或 Fragment
的字段:
private ArrayAdapter<String> fileList;
然后在初始化数据列表后将其实例化并设置为 ListView
的适配器,例如在 getDir()
结束时:
//...
fileList = new ArrayAdapter<String>(this, R.layout.layout_item_view, R.id.rowtext, item);
setListAdapter(fileList);
数据将像以前一样显示。现在您可以在整个 Activity
中访问此适配器,例如 onContextItemSelected()
:
@Override
public boolean onContextItemSelected(MenuItem item) {
final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
switch (item.getItemId()) {
case R.id.context_menu_rename:
Toast.makeText(this, "Rename", Toast.LENGTH_SHORT).show();
return true;
case R.id.context_menu_delete:
fileList.remove(fileList.getItem(info.position));
fileList.notifyDataSetChanged();
return true;
default:
return super.onContextItemSelected(item);
}
}
请注意 fileList.getItem(info.position)
必须是唯一值,因为在引擎盖下 ArrayAdapter#remove()
表示 List#remove()
,因此第一个匹配的 List
条目将被删除,不要管它指数。如果你想绝对确定你删除了正确的条目,那么你必须使你的数据列表成为一个字段,并通过调用 item.remove(info.position)
以及如何使更改永久化?
我们现在实现的是用户可以从数据列表中删除一个条目,ListView
将通过不再显示删除的条目来反映这一点。如果您离开应用程序 ListView
/ 适配器将保留它们的状态,并在一段时间后 return 进入它,例如通过按 "HOME" 按钮。
但如果您倾斜设备并导致方向改变(从纵向到横向,反之亦然),则 onCreate()
将被执行并且删除的数据将恢复。如果用户按下 "BACK" 然后点击启动器图标,情况也是如此。
因此,如果您想永久更改,则必须以某种方式持久化它们。 (但我认为这超出了这个问题的范围。)例如参见 [=40=]
上的指南