当搜索栏中的文本写入 Recyclerview 时调用 notifyItemRangechanged android
Call notifyItemRangechanged when text in searchbar is written Recyclerview android
每当用户在 SearchBar
中键入内容时,我想在我的 RecyclerView
上添加一个 animation
。我已经实现了一个 Filter
方法来过滤项目,但是在 OnQuerytextChanged
中调用 notifyItemrangechanged
失败,所以出现了。我已经尝试过这样的事情:
@Override
public boolean onQueryTextChange(String newText) {
istyping = true;
ArrayList<String> templist = new ArrayList<>();
mSearchQuery = newText;
//this line --> adapter.notifyItemRangeChanged(0, namelistwithnumber.size()); <---
for (String temp : namelistwithnumber) {
if (temp.toLowerCase().contains(newText.toLowerCase())) {
templist.add(temp);
}
}
if (newText.isEmpty()){
mainlist.setAdapter(null);
adapter = new MyRecyclerViewAdapter(MainActivity.this, namelist);
mainlist.setAdapter(adapter);
adapter.notifyDataSetChanged();
istyping = false;
}
if (templist.size() == 0) {
mainlist.setAdapter(null);
noresults.setVisibility(View.VISIBLE);
} else {
if (!newText.isEmpty()){
adapter = new MyRecyclerViewAdapter(MainActivity.this, templist);
mainlist.setAdapter(adapter);
noresults.setVisibility(View.INVISIBLE);
adapter.setClickListener(MainActivity.this);
}
noresults.setVisibility(View.INVISIBLE);
}
return true;
}
这就是我想要实现的:
感谢任何帮助!!
你每次都创建一个新的适配器,这意味着没有以前的数据......这反过来意味着不能有任何动画;-)
您需要做的是在 Activity 中创建一个 ArrayList,在 Adapter
中引用它
然后在方法中 post 修改 ArrayList 中的项目然后调用 adapter.notifyItemRangeChanged(0, list.size());
即
...
private ArrayList<String> list = new ArrayList<>();
private MyRecyclerViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
//create your adapter and reference the ArrayList
adapter = new MyRecyclerViewAdapter(MainActivity.this, list);
...
@Override
public boolean onQueryTextChange(String newText) {
istyping = true;
ArrayList<String> templist = new ArrayList<>();
mSearchQuery = newText;
for (String temp : namelistwithnumber) {
if (temp.toLowerCase().contains(newText.toLowerCase())) {
templist.add(temp);
}
}
//no need to set a new Adapter, the Adapter already has the ref to the ArrayList
//so just modify it and tell the adapter it has changed animation will be handled
//automatically
list.clear();
list.addAll(templist);
adapter.notifyItemRangeChanged(0, list.size());
...
这是一个完整的工作示例。我从示例列表中查询了单词,但您可以从数据库或 Web API 中进行查询。我为 recyclerView 使用了一个固定的高度,这样所有的动画都会被看到。这里有很多概念在起作用:MVVM 设计模式、LiveData、DataBinding 等。为了获得最佳结果,没有简单的答案。如果不熟悉这些概念,请一一研究。
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private SearchAdapter searchAdapter;
private MainViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
searchAdapter = new SearchAdapter();
binding.recycler.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
binding.recycler.setAdapter(searchAdapter);
observeData();
viewModel.queryWord("");
binding.search.setOnQueryTextListener((new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
viewModel.queryWord(query);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
viewModel.queryWord(newText);
return false;
}
}));
}
private void observeData() {
viewModel.getResult().observe(this, result -> {
searchAdapter.submitList(result);
});
}
}
MainViewModel.java:
public class MainViewModel extends AndroidViewModel {
private ArrayList<String> namelistwithnumber;
MutableLiveData<List<SearchResult>> result = new MutableLiveData<>();
public MainViewModel(@NonNull Application application) {
super(application);
namelistwithnumber = new ArrayList(Arrays.asList("aa", "ab", "ac", "ad", "ba", "bb", "bc", "bd", "ca", "cb", "cc", "cd", "da", "db", "dc", "dd"));
}
public void queryWord(String word) {
ArrayList<SearchResult> templist = new ArrayList<>();
int id = 0;
if (word.equals("")) {
for (String temp : namelistwithnumber) {
id++;
SearchResult anItem = new SearchResult(id, temp);
templist.add(anItem);
}
} else {
for (String temp : namelistwithnumber) {
if (temp.toLowerCase().contains(word.toLowerCase())) {
id++;
SearchResult anItem = new SearchResult(id, temp);
templist.add(anItem);
}
}
}
result.setValue(templist);
}
public MutableLiveData<List<SearchResult>> getResult() {
return result;
}
}
SearchAdapter.java:
public class SearchAdapter extends ListAdapter<SearchResult, SearchAdapter.ViewHolder> {
private LayoutInflater mInflater;
private Context context;
public SearchAdapter() {
super(DIFF_CALLBACK);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
context = parent.getContext();
this.mInflater = LayoutInflater.from(context);
ResultItemBinding binding = ResultItemBinding.inflate(mInflater, parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
SearchResult searchResult= getItem(position);
holder.bind(searchResult);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ResultItemBinding binding;
ViewHolder(ResultItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(SearchResult item) {
binding.setResult(item);
}
}
public static final DiffUtil.ItemCallback<SearchResult> DIFF_CALLBACK =
new DiffUtil.ItemCallback<SearchResult>() {
@Override
public boolean areItemsTheSame(
@NonNull SearchResult oldUser, @NonNull SearchResult newUser) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldUser.getWord().equals(newUser.getWord());
}
@Override
public boolean areContentsTheSame(
@NonNull SearchResult oldUser, @NonNull SearchResult newUser) {
// NOTE: if you use equals, your object must properly override Object#equals()
// Incorrectly returning false here will result in too many animations.
return oldUser.getWord().equals(newUser.getWord());
}
};
}
SearchResult.java:
public class SearchResult {
private long id;
private String word;
public SearchResult(long id, String word) {
this.id = id;
this.word = word;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="@+id/search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="600dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</layout>
result_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="result"
type="com.example.recyclerviewtest.SearchResult" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<TextView
android:id="@+id/word"
android:layout_width="match_parent"
android:layout_height="25dp"
android:gravity="start|center_vertical"
android:maxLines="1"
android:text="@{result.word}"
android:textColor="#000"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.recyclerviewtest"
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
dataBinding true
// for view binding:
// viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'org.jetbrains:annotations-java5:15.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
}
新浪的回答看起来不错,它有很好的做法(除了冗余数据绑定),但既然你不接受它,我假设你不想用那些额外的概念重写你的代码。所以这是简单的解决方案:
- 将
setHasStableIds(true)
设置为适配器
- 覆盖
getItemId(int position)
内部适配器
- 将
DefaultItemAnimator()
添加到 RecyclerView
分步指南:
1.添加默认的 itemAnimator 到你的 RecyclerView
mainlist.setItemAnimator(new DefaultItemAnimator());
2。向适配器添加功能以更新列表
您不应在每次更改项目列表时都重新初始化 MyRecyclerViewAdapter
。从构造函数中删除列表!而是在你的 dapter class 中声明成员 varialbe List<NameListwithNumber> nameListWithNumber = new ArrayList<>();
并向你的适配器添加一个函数:
public void setItems(List<NameListwithNumber> newItems){
nameListWithNumber.clear();
nameListWithNumber.addAll(newItems);
notifydatasetChanged();
}
3。初始化适配器并设置 setHasStableIds(true)
在您的 Activity
中将您的适配器声明为全局变量
private MyRecyclerViewAdapter myAdapter;
(我不确定你为什么需要适配器内的 activity 实例)
并在 onCreate()
方法中初始化它
myAdapter = new MyRecyclerViewAdapter(MainActivity.this);
myAdapter.setHasStableIds(true);
mainList.setAdapter(myAdapter);
4。覆盖 getItemId(int position) inside adapter
在您的适配器中添加此功能:
@Override
public long getItemId(int position) {
return nameListWithNumber.get(position).id;
}
如果您的模型没有 唯一 ID,只需使用 return nameListWithNumber.get(position).hashCode();
终于
在你的 public boolean onQueryTextChange(String newText)
里面
你需要改变
if (newText.isEmpty()) {
adapter.setItems(nameList)
istyping = false;
}
else {
adapter.setItems(tempList)
noresults.setVisibility(templist.size() == 0 ? View.VISIBLE : View.INVISIBLE)
}
这是完全相同方法的结果,只是过滤方式不同。
每当用户在 SearchBar
中键入内容时,我想在我的 RecyclerView
上添加一个 animation
。我已经实现了一个 Filter
方法来过滤项目,但是在 OnQuerytextChanged
中调用 notifyItemrangechanged
失败,所以出现了。我已经尝试过这样的事情:
@Override
public boolean onQueryTextChange(String newText) {
istyping = true;
ArrayList<String> templist = new ArrayList<>();
mSearchQuery = newText;
//this line --> adapter.notifyItemRangeChanged(0, namelistwithnumber.size()); <---
for (String temp : namelistwithnumber) {
if (temp.toLowerCase().contains(newText.toLowerCase())) {
templist.add(temp);
}
}
if (newText.isEmpty()){
mainlist.setAdapter(null);
adapter = new MyRecyclerViewAdapter(MainActivity.this, namelist);
mainlist.setAdapter(adapter);
adapter.notifyDataSetChanged();
istyping = false;
}
if (templist.size() == 0) {
mainlist.setAdapter(null);
noresults.setVisibility(View.VISIBLE);
} else {
if (!newText.isEmpty()){
adapter = new MyRecyclerViewAdapter(MainActivity.this, templist);
mainlist.setAdapter(adapter);
noresults.setVisibility(View.INVISIBLE);
adapter.setClickListener(MainActivity.this);
}
noresults.setVisibility(View.INVISIBLE);
}
return true;
}
这就是我想要实现的:
感谢任何帮助!!
你每次都创建一个新的适配器,这意味着没有以前的数据......这反过来意味着不能有任何动画;-)
您需要做的是在 Activity 中创建一个 ArrayList,在 Adapter
中引用它然后在方法中 post 修改 ArrayList 中的项目然后调用 adapter.notifyItemRangeChanged(0, list.size()); 即
...
private ArrayList<String> list = new ArrayList<>();
private MyRecyclerViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
//create your adapter and reference the ArrayList
adapter = new MyRecyclerViewAdapter(MainActivity.this, list);
...
@Override
public boolean onQueryTextChange(String newText) {
istyping = true;
ArrayList<String> templist = new ArrayList<>();
mSearchQuery = newText;
for (String temp : namelistwithnumber) {
if (temp.toLowerCase().contains(newText.toLowerCase())) {
templist.add(temp);
}
}
//no need to set a new Adapter, the Adapter already has the ref to the ArrayList
//so just modify it and tell the adapter it has changed animation will be handled
//automatically
list.clear();
list.addAll(templist);
adapter.notifyItemRangeChanged(0, list.size());
...
这是一个完整的工作示例。我从示例列表中查询了单词,但您可以从数据库或 Web API 中进行查询。我为 recyclerView 使用了一个固定的高度,这样所有的动画都会被看到。这里有很多概念在起作用:MVVM 设计模式、LiveData、DataBinding 等。为了获得最佳结果,没有简单的答案。如果不熟悉这些概念,请一一研究。
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private SearchAdapter searchAdapter;
private MainViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
searchAdapter = new SearchAdapter();
binding.recycler.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
binding.recycler.setAdapter(searchAdapter);
observeData();
viewModel.queryWord("");
binding.search.setOnQueryTextListener((new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
viewModel.queryWord(query);
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
viewModel.queryWord(newText);
return false;
}
}));
}
private void observeData() {
viewModel.getResult().observe(this, result -> {
searchAdapter.submitList(result);
});
}
}
MainViewModel.java:
public class MainViewModel extends AndroidViewModel {
private ArrayList<String> namelistwithnumber;
MutableLiveData<List<SearchResult>> result = new MutableLiveData<>();
public MainViewModel(@NonNull Application application) {
super(application);
namelistwithnumber = new ArrayList(Arrays.asList("aa", "ab", "ac", "ad", "ba", "bb", "bc", "bd", "ca", "cb", "cc", "cd", "da", "db", "dc", "dd"));
}
public void queryWord(String word) {
ArrayList<SearchResult> templist = new ArrayList<>();
int id = 0;
if (word.equals("")) {
for (String temp : namelistwithnumber) {
id++;
SearchResult anItem = new SearchResult(id, temp);
templist.add(anItem);
}
} else {
for (String temp : namelistwithnumber) {
if (temp.toLowerCase().contains(word.toLowerCase())) {
id++;
SearchResult anItem = new SearchResult(id, temp);
templist.add(anItem);
}
}
}
result.setValue(templist);
}
public MutableLiveData<List<SearchResult>> getResult() {
return result;
}
}
SearchAdapter.java:
public class SearchAdapter extends ListAdapter<SearchResult, SearchAdapter.ViewHolder> {
private LayoutInflater mInflater;
private Context context;
public SearchAdapter() {
super(DIFF_CALLBACK);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
context = parent.getContext();
this.mInflater = LayoutInflater.from(context);
ResultItemBinding binding = ResultItemBinding.inflate(mInflater, parent, false);
return new ViewHolder(binding);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
SearchResult searchResult= getItem(position);
holder.bind(searchResult);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ResultItemBinding binding;
ViewHolder(ResultItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(SearchResult item) {
binding.setResult(item);
}
}
public static final DiffUtil.ItemCallback<SearchResult> DIFF_CALLBACK =
new DiffUtil.ItemCallback<SearchResult>() {
@Override
public boolean areItemsTheSame(
@NonNull SearchResult oldUser, @NonNull SearchResult newUser) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldUser.getWord().equals(newUser.getWord());
}
@Override
public boolean areContentsTheSame(
@NonNull SearchResult oldUser, @NonNull SearchResult newUser) {
// NOTE: if you use equals, your object must properly override Object#equals()
// Incorrectly returning false here will result in too many animations.
return oldUser.getWord().equals(newUser.getWord());
}
};
}
SearchResult.java:
public class SearchResult {
private long id;
private String word;
public SearchResult(long id, String word) {
this.id = id;
this.word = word;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.SearchView
android:id="@+id/search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="600dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/search" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</layout>
result_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="result"
type="com.example.recyclerviewtest.SearchResult" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<TextView
android:id="@+id/word"
android:layout_width="match_parent"
android:layout_height="25dp"
android:gravity="start|center_vertical"
android:maxLines="1"
android:text="@{result.word}"
android:textColor="#000"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.recyclerviewtest"
minSdkVersion 28
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
dataBinding true
// for view binding:
// viewBinding true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'org.jetbrains:annotations-java5:15.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
}
新浪的回答看起来不错,它有很好的做法(除了冗余数据绑定),但既然你不接受它,我假设你不想用那些额外的概念重写你的代码。所以这是简单的解决方案:
- 将
setHasStableIds(true)
设置为适配器 - 覆盖
getItemId(int position)
内部适配器 - 将
DefaultItemAnimator()
添加到 RecyclerView
分步指南:
1.添加默认的 itemAnimator 到你的 RecyclerView
mainlist.setItemAnimator(new DefaultItemAnimator());
2。向适配器添加功能以更新列表
您不应在每次更改项目列表时都重新初始化 MyRecyclerViewAdapter
。从构造函数中删除列表!而是在你的 dapter class 中声明成员 varialbe List<NameListwithNumber> nameListWithNumber = new ArrayList<>();
并向你的适配器添加一个函数:
public void setItems(List<NameListwithNumber> newItems){
nameListWithNumber.clear();
nameListWithNumber.addAll(newItems);
notifydatasetChanged();
}
3。初始化适配器并设置 setHasStableIds(true)
在您的 Activity
中将您的适配器声明为全局变量
private MyRecyclerViewAdapter myAdapter;
(我不确定你为什么需要适配器内的 activity 实例)
并在 onCreate()
方法中初始化它
myAdapter = new MyRecyclerViewAdapter(MainActivity.this);
myAdapter.setHasStableIds(true);
mainList.setAdapter(myAdapter);
4。覆盖 getItemId(int position) inside adapter
在您的适配器中添加此功能:
@Override
public long getItemId(int position) {
return nameListWithNumber.get(position).id;
}
如果您的模型没有 唯一 ID,只需使用 return nameListWithNumber.get(position).hashCode();
终于
在你的 public boolean onQueryTextChange(String newText)
里面
你需要改变
if (newText.isEmpty()) {
adapter.setItems(nameList)
istyping = false;
}
else {
adapter.setItems(tempList)
noresults.setVisibility(templist.size() == 0 ? View.VISIBLE : View.INVISIBLE)
}
这是完全相同方法的结果,只是过滤方式不同。