英文:
Perform/Add search functionality to a custom ListView
问题
我创建了一个自定义的ListView来显示所有安装的应用程序,但我不知道如何添加搜索功能,因为它有点复杂(..我的应用程序),有人可以帮我吗?
[(应用程序的图片)][1]
App.java - 应用程序构造函数
```java
public class App {
private int number;
private String name;
private String version;
private Drawable drawable;
public App(int number, String name, String version, Drawable drawable){
this.number = number;
this.name = name;
this.version = version;
this.drawable = drawable;
}
//获取器和设置器...
}
AppAdapter.java - ListView 适配器
public class AppAdapter extends ArrayAdapter<App> {
Context context;
List<App> objects;
public AppAdapter(Context context, int resources, int textViewResources, List<App> objects){
super(context, resources, textViewResources, objects);
this.context = context;
this.objects = objects;
}
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater layoutInflater = ((Activity)context).getLayoutInflater();
View view = layoutInflater.inflate(R.layout.custom_card, parent, false);
TextView tvName = (TextView)view.findViewById(R.id.tvName);
TextView tvVersion = (TextView)view.findViewById(R.id.tvVersion);
TextView tvNumber = (TextView)view.findViewById(R.id.tvNumber);
ImageView ivImage = (ImageView)view.findViewById(R.id.ivImage);
App current = objects.get(position);
tvName.setText(String.valueOf(current.getName()));
tvVersion.setText(String.valueOf(current.getVersion()));
tvNumber.setText(String.valueOf(current.getNumber()));
ivImage.setImageDrawable(current.getDrawable());
return view;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
ArrayList<App> appList;
ListView lv;
AppAdapter appAdapter;
App lastSelected;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText etSearch = findViewById(R.id.etSearch);
PackageManager packageManager = getPackageManager();
List<PackageInfo> mApps = packageManager.getInstalledPackages(0);
//所有包、名称和版本的字符串数组
final String[] arrPackages = new String[mApps.size()];
final String[] arrVersion = new String[mApps.size()];
String[] arrName = new String[mApps.size()];
//图标的Drawable数组...
Drawable[] arrIcons = new Drawable[mApps.size()];
App[] arrApps = new App[mApps.size()];
appList = new ArrayList<>();
//将所有应用程序的包和版本读入数组
for (int i = 0; i < mApps.size(); i++){
arrVersion[i] = mApps.get(i).versionName;
arrPackages[i] = mApps.get(i).packageName;
}
for (int i = 0; i < mApps.size(); i++){
try {//从包中获取应用程序的名称
arrName[i] = (String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(arrPackages[i], PackageManager.GET_META_DATA));
} catch (PackageManager.NameNotFoundException e) {
arrName[i] = "Unknown";
}
try {//与名称类似,获取图标
arrIcons[i] = packageManager.getApplicationIcon(arrPackages[i]);
} catch (PackageManager.NameNotFoundException e) {
arrIcons[i] = getDrawable(R.drawable.placeholder);
}
arrApps[i] = new App(i + 1, "Name: "+arrName[i], "Version: "+arrVersion[i], arrIcons[i]);
appList.add(arrApps[i]);
}
//点击条目打开应用程序
appAdapter = new AppAdapter(this, 0, 0, appList);
lv = findViewById(R.id.lv);
lv.setAdapter(appAdapter);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lastSelected = appAdapter.getItem(position);
Intent launchIntent = getPackageManager().getLaunchIntentForPackage(arrPackages[position]);
if (launchIntent != null) {
startActivity(launchIntent);//在找不到包名的情况下进行空指针检查
}
}
});
//向EditText添加文本更改监听器
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 使用当前字符调用适配器进行过滤
MainActivity.this.appAdapter.getFilter().filter(s.toString());
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
}
(当我尝试搜索时,什么都没有显示...)
<details>
<summary>英文:</summary>
i created a custom listView of all installed apps but i don't know how to add a search functionality because it's a little complicated(..my app) can anyone help me with that?
[(picture of the app )][1]
App.java - app constructor
public class App {
private int number;
private String name;
private String version;
private Drawable drawable;
public App(int number, String name, String version, Drawable drawable){
this.number = number;
this.name = name;
this.version = version;
this.drawable = drawable;
}
//Getters & Setters...
}
AppAdapter.java - listView Adapter
public class AppAdapter extends ArrayAdapter<App> {
Context context;
List<App> objects;
public AppAdapter(Context context, int resources, int textViewResources, List<App> objects){
super(context, resources, textViewResources, objects);
this.context = context;
this.objects = objects;
}
public View getView(int position, View convertView, ViewGroup parent){
LayoutInflater layoutInflater = ((Activity)context).getLayoutInflater();
View view = layoutInflater.inflate(R.layout.custom_card,parent,false);
TextView tvName = (TextView)view.findViewById(R.id.tvName);
TextView tvVersion = (TextView)view.findViewById(R.id.tvVersion);
TextView tvNumber = (TextView)view.findViewById(R.id.tvNumber);
ImageView ivImage = (ImageView)view.findViewById(R.id.ivImage);
App current = objects.get(position);
tvName.setText(String.valueOf(current.getName()));
tvVersion.setText(String.valueOf(current.getVersion()));
tvNumber.setText(String.valueOf(current.getNumber()));
ivImage.setImageDrawable(current.getDrawable());
return view;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
ArrayList<App> appList;
ListView lv;
AppAdapter appAdapter;
App lastSelected;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText etSearch = findViewById(R.id.etSearch);
PackageManager packageManager = getPackageManager();
List<PackageInfo> mApps = packageManager.getInstalledPackages(0);
//array strings to all packages, names and version
final String[] arrPackages = new String[mApps.size()];
final String[] arrVersion = new String[mApps.size()];
String[] arrName = new String[mApps.size()];
//array of Drawables for icons...
Drawable[] arrIcons = new Drawable[mApps.size()];
App[] arrApps = new App[mApps.size()];
appList = new ArrayList<>();
//reading all app's packages and version to the arrays
for (int i = 0; i < mApps.size(); i++){
arrVersion[i] = mApps.get(i).versionName;
arrPackages[i] = mApps.get(i).packageName;
}
for (int i = 0; i < mApps.size(); i++){
try {//getting app's names from theres packages
arrName[i] = (String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(arrPackages[i], PackageManager.GET_META_DATA));
} catch (PackageManager.NameNotFoundException e) {
arrName[i] = "Unknown";
}
try {//same as names for icons
arrIcons[i] = packageManager.getApplicationIcon(arrPackages[i]);
} catch (PackageManager.NameNotFoundException e) {
arrIcons[i] = getDrawable(R.drawable.placeholder);
}
arrApps[i] = new App(i + 1, "Name: "+arrName[i], "Version: "+arrVersion[i], arrIcons[i]);
appList.add(arrApps[i]);
}
//on item click open app
appAdapter = new AppAdapter(this,0,0,appList);
lv = findViewById(R.id.lv);
lv.setAdapter(appAdapter);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
lastSelected = appAdapter.getItem(position);
Intent launchIntent = getPackageManager().getLaunchIntentForPackage(arrPackages[position]);
if (launchIntent != null) {
startActivity(launchIntent);//null pointer check in case package name was not found
}
}
});
//(trying to..) Add Text Change Listener to EditText
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Call back the Adapter with current character to Filter
MainActivity.this.appAdapter.getFilter().filter(s.toString());
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
}
(when i try to search something it's gives nothing...)
[1]: https://i.stack.imgur.com/nRZUP.jpg
</details>
# 答案1
**得分**: 0
将这一行代码
App current = objects.get(position);
替换为这一行代码
App current = (App) getItem(position);
你不需要在适配器内部保留 `List<App> objects;`,它在构造函数的 `super` 调用中传递(最后一个参数),并在内部为您保存。请查看 [ArrayAdapter 源代码][1] - 传递的 `ArrayList` 被保存为 `mObjects`,并在某些方法中进一步使用,例如 `getPosition`、`getCount` 和 `getItem`。
`ArrayAdapter` 已经实现了 `Filterable` 接口,因此有一个重写的 `getFilter` 方法,返回 `ArrayFilter` - 这个内部类在底部声明。当你调用 `getFilter().filter(` 时,`performFiltering` 会被调用(在一个单独的线程中),并且遍历本地数据的副本(第588行)。它使用 `values.get(i).toString().toLowerCase()` 来将数组中的对象与传递的 `String`(实际上是 `CharSequence`)进行比较。所以在你自定义的 `App` 类中重写 `toString` 方法,并在其中返回一些可搜索的值,例如:
@Override
public String toString(){
return name;
}
这并不是最好的方法,因为 `toString` 可能会在许多机制中使用(它是 [Object][2] 的基本方法),而使用上面的 `toString` 实现时,具有相同名称但不同 `version` 或 `number` 的两个 `App` 将被视为相同的对象,这是不正确的... 也许更好的方式是 `return name+version+number;`,但是你的类中也有 `Drawable`。这就是为什么我之前建议(在编辑之前的回答中)创建一个扩展了 `BaseAdapter` 并实现自己的过滤逻辑的自定义类,或者至少使用 `ArrayAdapter`,但是重写 `getFilter` 方法并返回你自己的比较变量的 `Filter` 实现。这样就不需要重写这个类,保留它的原样。默认情况下,它返回一种类似内存地址的值,因此每个新的 `App` 实例都是唯一的,即使使用完全相同的变量创建。
此外,在[这个回答中][3],你可以找到一个很好的示例,展示了如何实现这个 `Filter`。
[1]: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/ArrayAdapter.java
[2]: http://developer.classpath.org/doc/java/lang/Object-source.html
[3]: https://stackoverflow.com/questions/10532194/filter-listview-with-arrayadapter
<details>
<summary>英文:</summary>
exchange this line
App current = objects.get(position);
to this
App current = (App) getItem(position);
you don't need to keep `List<App> objects;` inside your adapter, it is passed in constructors `super` call (last param) and kept under the hood for you. check out [ArrayAdapter source][1] - passed `ArrayList` is kept as `mObjects` and further used in some methods, e.g. `getPosition`, `getCount` and `getItem`
`ArrayAdapter` already `implements Filterable`, so there is an overriden method `getFilter`, which returns `ArrayFilter` - this inner class is declared on the bottom. when you call `getFilter().filter(` then `performFiltering` gets called (in a separated thread) and iterates through local copy of your data (line 588). It is using `values.get(i).toString().toLowerCase()` to compare objects from array with passed `String` (`CharSequence` in fact). so in your custom `App` class override `toString` method and return in there some searchable value, e.g.
@Override
public String toString(){
return name;
}
this is not best approach, because `toString` may be used in a lot of mechanisms (its base method of [Object][2]) and with above `toString` implementation two `App`s with same name, but different `version` or `number` are threated as same object, which isn't true... maybe better way would be to `return name+version+number;`, but still you have also `Drawable` in there. thats why I've suggested (in this answer before edit) to make own class extends `BaseAdapter` and implement own filtering or at least use `ArrayAdapter`, but override `getFilter` method and return your own `Filter` implementation comparing variables instead of using `toString`. Then won't be needed to override this class, leave it as it is. By default it returns kind-of memory address, so it is unique for every new `App` instance, even when created with exacly same variables
also in [THIS][3] answer you can find nice example how to implement that `Filter`
[1]: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/ArrayAdapter.java
[2]: http://developer.classpath.org/doc/java/lang/Object-source.html
[3]: https://stackoverflow.com/questions/10532194/filter-listview-with-arrayadapter
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论