1. 配置应用
说明: Jetpack 组件实现 (Room,ViewModel,LiveData,Repository,AsyncTack)
1.1 build.gradle 文件引用库
dependencies {
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation 'androidx.navigation:navigation-fragment:2.5.1'
implementation 'androidx.navigation:navigation-ui:2.5.1'
}
1.2 ids.xml 文件下添加 id
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="word_for_view_holder" type="id" />
</resources>
1.3 添加矢量图标文件, 如: ic_baseline_search_24.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
</vector>
1.4 添加其他矢量图标,依次从 Android Studio 系统自动图标中导入
1. ic_baseline_add_24.xml
2. ic_baseline_chevron_right_24.xml
3. ic_baseline_delete_forever_24.xml
1.5 menu 文件夹添加菜单布局文件: main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/clear_data"
android:title="清空数据" />
<item
android:id="@+id/switch_type"
android:title="切换视图" />
<item
android:id="@+id/app_bar_search"
android:icon="@android:drawable/ic_menu_search"
android:title="Search"
app:actionViewClass="android.widget.SearchView"
app:showAsAction="always" />
</menu>
1.6 navigation 文件夹添加 Fragment 导航文件: navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation"
app:startDestination="@id/wordsFragment">
<fragment
android:id="@+id/wordsFragment"
android:name="com.example.words.WordsFragment"
android:label="Words"
tools:layout="@layout/fragment_words">
<action
android:id="@+id/action_wordsFragment_to_addFragment"
app:destination="@id/addFragment"
app:enterAnim="@anim/nav_default_enter_anim" />
</fragment>
<fragment
android:id="@+id/addFragment"
android:name="com.example.words.AddFragment"
android:label="Add"
tools:layout="@layout/fragment_add" />
</navigation>
2. 搭建数据库
2.1 创建实体类, Word.java
@Entity
public class Word {
@PrimaryKey(autoGenerate = true)
private int id; //主键
@ColumnInfo(name = "english_word")
private String word;
@ColumnInfo(name = "chinese_meaning")
private String chineseMeaning;
@ColumnInfo(name = "chinese_invisible")
private boolean chineseInvisible;
// @ColumnInfo(name = "foo_data")
// private boolean foo;
// @ColumnInfo(name = "bar_data")
// private boolean bar;
public Word(String word, String chineseMeaning) {
this.word = word;
this.chineseMeaning = chineseMeaning;
}
public boolean isChineseInvisible() {
return chineseInvisible;
}
public void setChineseInvisible(boolean chineseInvisible) {
this.chineseInvisible = chineseInvisible;
}
// public boolean isBar() {
// return bar;
// }
//
// public void setBar(boolean bar) {
// this.bar = bar;
// }
//
// public boolean isFoo() {
// return foo;
// }
//
// public void setFoo(boolean foo) {
// this.foo = foo;
// }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getWord() {
return word;
}
public String getChineseMeaning() {
return chineseMeaning;
}
}
2.2 操作数据库接口类, WordDao.java
@Dao //Database access object
public interface WordDao {
@Insert
void insertWords(Word... words);
@Update
void updateWords(Word... words);
@Delete
void deleteWords(Word... words);
@Query("DELETE FROM WORD")
void deleteAllWords();
@Query("SELECT * FROM WORD ORDER BY ID DESC")
//List<Word> getAllWords();
LiveData<List<Word>> getAllWordsLive();
@Query("SELECT * FROM WORD WHERE english_word LIKE :pattern ORDER BY ID DESC ")
LiveData<List<Word>>findWordsWithPattern(String pattern);
}
2.3 创建维护数据库与表类, WordDatabase.java
//singleton 只允许生成一个实例
@Database(entities = {Word.class}, version = 5, exportSchema = false)
public abstract class WordDatabase extends RoomDatabase {
private static WordDatabase INSTANCE;
//synchronized: 解决多个线程下调用,创建多个实例问题
static synchronized WordDatabase getDatabase(Context context) {
if (INSTANCE == null) {
//fallbackToDestructiveMigration 把当前的数据清空,创建新的库
//addMigrations 添加迁移的策略
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), WordDatabase.class, "word_database")
.addMigrations(migration_4_5)
.build();
}
return INSTANCE;
}
public abstract WordDao getWordDao();
//添加字段操作
static final Migration migration_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE word ADD COLUMN bar_data INTEGER NOT NULL DEFAULT 1");
}
};
//删除字段操作
static final Migration migration_3_4 = new Migration(3, 4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE word_temp (id INTEGER PRIMARY KEY NOT NULL , english_word TEXT," +
"chinese_meaning TEXT)");
database.execSQL("INSERT INTO word_temp (id,english_word,chinese_meaning) " +
"SELECT id,english_word,chinese_meaning FROM word");
database.execSQL("DROP TABLE word");
database.execSQL("ALTER TABLE word_temp RENAME to word");
}
};
//添加字段操作
private static final Migration migration_4_5 = new Migration(4, 5) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE word ADD COLUMN chinese_invisible INTEGER NOT NULL DEFAULT 0");
}
};
}
2.4 创建数据库, 操作数据库类, WordRepository.java
//Repository 仓库 获取数据的意思
class WordRepository {
private WordDao wordDao;
private LiveData<List<Word>> allWordsLive;
WordRepository(Context context) {
WordDatabase wordDatabase = WordDatabase.getDatabase(context);
wordDao = wordDatabase.getWordDao();
allWordsLive = wordDao.getAllWordsLive();
}
void insertWords(Word... words) {
new InsertAsyncTask(wordDao).execute(words);
}
void updateWords(Word... words) {
new UpdateAsyncTask(wordDao).execute(words);
}
void deleteWords(Word... words) {
new DeleteAsyncTask(wordDao).execute(words);
}
void deleteAllWords() {
new DeleteAllAsyncTask(wordDao).execute();
}
LiveData<List<Word>> getAllWordsLive() {
return allWordsLive;
}
LiveData<List<Word>> findWordsWithPattern(String pattern) {
return wordDao.findWordsWithPattern("%" + pattern + "%");
}
static class InsertAsyncTask extends AsyncTask<Word, Void, Void> {
private WordDao wordDao;
InsertAsyncTask(WordDao wordDao) {
this.wordDao = wordDao;
}
@Override
protected Void doInBackground(Word... words) {
wordDao.insertWords(words);
return null;
}
}
static class UpdateAsyncTask extends AsyncTask<Word, Void, Void> {
private WordDao wordDao;
UpdateAsyncTask(WordDao wordDao) {
this.wordDao = wordDao;
}
@Override
protected Void doInBackground(Word... words) {
wordDao.updateWords(words);
return null;
}
}
static class DeleteAsyncTask extends AsyncTask<Word, Void, Void> {
private WordDao wordDao;
DeleteAsyncTask(WordDao wordDao) {
this.wordDao = wordDao;
}
@Override
protected Void doInBackground(Word... words) {
wordDao.deleteWords(words);
return null;
}
}
static class DeleteAllAsyncTask extends AsyncTask<Void, Void, Void> {
private WordDao wordDao;
DeleteAllAsyncTask(WordDao wordDao) {
this.wordDao = wordDao;
}
@Override
protected Void doInBackground(Void... voids) {
this.wordDao.deleteAllWords();
return null;
}
}
}
3. 创建 ViewModel 类, 处理数据操作, WordViewModel.java
public class WordViewModel extends AndroidViewModel {
private WordRepository wordRepository = null;
public WordViewModel(@NonNull Application application) {
super(application);
wordRepository = new WordRepository(application);
}
public LiveData<List<Word>> getAllWordsLive() {
return wordRepository.getAllWordsLive();
}
public LiveData<List<Word>> findWordsWithPattern(String pattern) {
return wordRepository.findWordsWithPattern(pattern);
}
void insertWords(Word... words) {
wordRepository.insertWords(words);
}
void updateWords(Word... words) {
wordRepository.updateWords(words);
}
void deleteWords(Word... words) {
wordRepository.deleteWords(words);
}
void deleteAllWords() {
wordRepository.deleteAllWords();
}
}
4. 词汇列表页
4.1 创建列表内容管理器布局1: cell_card_2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="70dp">
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:foreground="?attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/divider2"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline9"
app:layout_constraintStart_toEndOf="@+id/guideline9"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline11"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.15" />
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline11"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<TextView
android:id="@+id/tv_english"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="TextView"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/tv_chinese"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/guideline11"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_chinese"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_english"
app:layout_constraintTop_toBottomOf="@+id/tv_english" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline9"
app:layout_constraintTop_toTopOf="parent">
<Switch
android:id="@+id/switchChineseInvisible"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingStart="30dp"
android:paddingEnd="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.8" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
4.2 创建列表内容管理器布局2: cell_normal_2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#FFFFFF"
android:foreground="?attr/selectableItemBackground">
<View
android:id="@+id/divider"
android:layout_width="1dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/constraintLayout2"
app:layout_constraintStart_toStartOf="@+id/guideline7"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.15" />
<TextView
android:id="@+id/tv_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/guideline10"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
<TextView
android:id="@+id/tv_english"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="TextView"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@+id/tv_chinese"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/guideline10"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_chinese"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/tv_english"
app:layout_constraintTop_toBottomOf="@+id/tv_english" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline7"
app:layout_constraintTop_toTopOf="parent">
<Switch
android:id="@+id/switchChineseInvisible"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingStart="30dp"
android:paddingEnd="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="UseSwitchCompatOrMaterialXml" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.8" />
</androidx.constraintlayout.widget.ConstraintLayout>
4.3 创建列表内容管理器, MyAdapter.java
//内容管理器
public class MyAdapter extends ListAdapter<Word, MyAdapter.MyViewHolder> {
private final boolean useCardView;
private final WordViewModel viewModel;
public MyAdapter(boolean useCardView, WordViewModel viewModel) {
super(new DiffUtil.ItemCallback<Word>() {
@Override
public boolean areItemsTheSame(@NonNull Word oldItem, @NonNull Word newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Word oldItem, @NonNull Word newItem) {
return (oldItem.getWord().equals(newItem.getWord()) && oldItem.getChineseMeaning().equals(newItem.getChineseMeaning()) && oldItem.isChineseInvisible() == newItem.isChineseInvisible());
}
});
this.useCardView = useCardView;
this.viewModel = viewModel;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View itemView;
if (useCardView) {
itemView = layoutInflater.inflate(R.layout.cell_card_2, parent, false);
} else {
itemView = layoutInflater.inflate(R.layout.cell_normal_2, parent, false);
}
MyViewHolder holder = new MyViewHolder(itemView);
holder.itemView.setOnClickListener(view -> {
Uri uri = Uri.parse("https://m.youdao.com/m/result?lang=en&word=" + holder.tvEnglish.getText());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
holder.itemView.getContext().startActivity(intent);
});
holder.aSwitchChineseInvisible.setOnCheckedChangeListener((compoundButton, isChecked) -> {
Word word = (Word) holder.itemView.getTag(R.id.word_for_view_holder);
if (isChecked) {
holder.tvChinese.setVisibility(View.GONE);
word.setChineseInvisible(true);
} else {
holder.tvChinese.setVisibility(View.VISIBLE);
word.setChineseInvisible(false);
}
viewModel.updateWords(word);
});
return holder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
Word word = getItem(position);
holder.itemView.setTag(R.id.word_for_view_holder, word);
holder.tvNumber.setText(String.valueOf(position + 1));
holder.tvEnglish.setText(word.getWord());
holder.tvChinese.setText(word.getChineseMeaning());
if (word.isChineseInvisible()) {
holder.tvChinese.setVisibility(View.GONE);
holder.aSwitchChineseInvisible.setChecked(true);
} else {
holder.tvChinese.setVisibility(View.VISIBLE);
holder.aSwitchChineseInvisible.setChecked(false);
}
}
@Override
public void onViewAttachedToWindow(@NonNull MyViewHolder holder) {
super.onViewAttachedToWindow(holder);
holder.tvNumber.setText(String.valueOf(holder.getAdapterPosition() + 1));
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvNumber, tvEnglish, tvChinese;
@SuppressLint("UseSwitchCompatOrMaterialCode")
Switch aSwitchChineseInvisible;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tvNumber = itemView.findViewById(R.id.tv_number);
tvEnglish = itemView.findViewById(R.id.tv_english);
tvChinese = itemView.findViewById(R.id.tv_chinese);
aSwitchChineseInvisible = itemView.findViewById(R.id.switchChineseInvisible);
}
}
}
4.4 创建词汇列表布局页, fragment_words.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/words_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="20dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_baseline_add_24"
tools:ignore="SpeakableTextPresentCheck" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
4.5 创建实现列表页, WordsFragment.java
public class WordsFragment extends Fragment {
private WordViewModel viewModel;
private RecyclerView recyclerView;
private MyAdapter myAdapter1, myAdapter2;
private LiveData<List<Word>> filteredWords;
private static final String VIEW_TYPE_SHP = "view_type_shp";
private static final String IS_USING_CARD_VIEW = "is_using_card_view";
private List<Word> allWords;
private int temp;
private boolean undoAction;
private DividerItemDecoration dividerItemDecoration;//边线
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
return inflater.inflate(R.layout.fragment_words, container, false);
}
@SuppressLint("NonConstantResourceId")
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.clear_data:
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
builder.setTitle("清空数据");
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
viewModel.deleteAllWords();
}
});
builder.setNegativeButton("取消", null);
builder.create();
builder.show();
break;
case R.id.switch_type:
SharedPreferences shp = requireActivity().getSharedPreferences(VIEW_TYPE_SHP, Context.MODE_PRIVATE);
boolean viewType = shp.getBoolean(IS_USING_CARD_VIEW, false);
SharedPreferences.Editor editor = shp.edit();
if (viewType) {
recyclerView.setAdapter(myAdapter1);
recyclerView.addItemDecoration(dividerItemDecoration);
editor.putBoolean(IS_USING_CARD_VIEW, false);
} else {
recyclerView.setAdapter(myAdapter2);
recyclerView.removeItemDecoration(dividerItemDecoration);
editor.putBoolean(IS_USING_CARD_VIEW, true);
}
editor.apply();
break;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_menu, menu);
SearchView searchView = (SearchView) menu.findItem(R.id.app_bar_search).getActionView();
searchView.setMaxWidth(720);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
String pattern = newText.trim();
filteredWords.removeObservers(getViewLifecycleOwner()); //...
filteredWords = viewModel.findWordsWithPattern(pattern);
filteredWords.observe(getViewLifecycleOwner(), words -> {
int temp = myAdapter1.getItemCount();
allWords = words;
if (temp != words.size()) {
myAdapter1.submitList(words);
myAdapter2.submitList(words);
}
});
return true;
}
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(requireActivity()).get(WordViewModel.class);
recyclerView = view.findViewById(R.id.recycle_view);
recyclerView.setLayoutManager(new LinearLayoutManager((requireActivity())));
myAdapter1 = new MyAdapter(false, viewModel);
myAdapter2 = new MyAdapter(true, viewModel);
recyclerView.setItemAnimator(new DefaultItemAnimator() {
@Override
public void onAnimationFinished(@NonNull RecyclerView.ViewHolder viewHolder) {
super.onAnimationFinished(viewHolder);
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
if (linearLayoutManager != null) {
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition();
int lastPosition = linearLayoutManager.findLastVisibleItemPosition();
for (int i = firstPosition; i <= lastPosition; i++) {
MyAdapter.MyViewHolder holder = (MyAdapter.MyViewHolder) recyclerView.findViewHolderForAdapterPosition(i);
if (holder != null) {
holder.tvNumber.setText(String.valueOf(i + 1));
}
}
}
}
});
SharedPreferences shp = requireActivity().getSharedPreferences(VIEW_TYPE_SHP, Context.MODE_PRIVATE);
boolean viewType = shp.getBoolean(IS_USING_CARD_VIEW, false);
dividerItemDecoration = new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL);
if (viewType) {
recyclerView.setAdapter(myAdapter2);
} else {
recyclerView.setAdapter(myAdapter1);
recyclerView.addItemDecoration(dividerItemDecoration);
}
filteredWords = viewModel.getAllWordsLive();
filteredWords.observe(getViewLifecycleOwner(), words -> {
temp = myAdapter1.getItemCount();
allWords = words;
if (temp != words.size()) {
if (temp < allWords.size() && !undoAction) {
//recyclerView.smoothScrollToPosition(0);
recyclerView.smoothScrollBy(0, -200);
}
undoAction = false;
//recyclerView.smoothScrollBy(0, -200);
//提交的数据列表 会在后台进行差异化比较 根据比对结果, 来刷新界面
myAdapter1.submitList(words);
myAdapter2.submitList(words);
}
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) {
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
// Word wordFrom = allWords.get(viewHolder.getAdapterPosition());
// Word wordTo = allWords.get(target.getAdapterPosition());
// int idTemp = wordFrom.getId();
// wordFrom.setId(wordTo.getId());
// wordTo.setId(idTemp);
// viewModel.updateWords(wordFrom, wordTo);
// myAdapter1.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
// myAdapter2.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
Word wordToData = allWords.get(viewHolder.getAdapterPosition());
viewModel.deleteWords(wordToData);
Snackbar.make(requireActivity().findViewById(R.id.words_layout), "删除了一个词汇", Snackbar.LENGTH_SHORT)
.setAction("撤销", view12 -> {
undoAction = true;
viewModel.insertWords(wordToData);
})
.show();
}
final Drawable icon = ContextCompat.getDrawable(requireActivity(), R.drawable.ic_baseline_delete_forever_24);
final Drawable background = new ColorDrawable(Color.LTGRAY);
@Override
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
View itemView = viewHolder.itemView;
assert icon != null;
int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
int iconStart, iconEnd, iconTop, iconBottom;
int backStart, backEnd, backTop, backBottom;
backTop = itemView.getTop();
backBottom = itemView.getBottom();
iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
iconBottom = iconTop + icon.getIntrinsicHeight();
if (dX > 0) {
backStart = itemView.getLeft();
backEnd = itemView.getLeft() + (int) dX;
background.setBounds(backStart, backTop, backEnd, backBottom);
iconStart = itemView.getLeft() + iconMargin;
iconEnd = iconStart + icon.getIntrinsicWidth();
icon.setBounds(iconStart, iconTop, iconEnd, iconBottom);
} else if (dX < 0) {
backEnd = itemView.getRight();
backStart = itemView.getRight() + (int) dX;
background.setBounds(backStart, backTop, backEnd, backBottom);
iconEnd = itemView.getRight() - iconMargin;
iconStart = iconEnd - icon.getIntrinsicWidth();
icon.setBounds(iconStart, iconTop, iconEnd, iconBottom);
} else {
background.setBounds(0, 0, 0, 0);
icon.setBounds(0, 0, 0, 0);
}
background.draw(c);
icon.draw(c);
}
}).attachToRecyclerView(recyclerView);
FloatingActionButton floatingActionButton = view.findViewById(R.id.floatingActionButton);
floatingActionButton.setOnClickListener(view1 -> {
NavController navController = Navigation.findNavController(view1);
navController.navigate(R.id.action_wordsFragment_to_addFragment);
});
}
}
5. 创建添加词汇页
5.1 添加词汇布局文件, fragment_add.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/addLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AddFragment" >
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加单词"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.1" />
<EditText
android:id="@+id/et_english"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:ems="10"
android:hint="English Word"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2" />
<EditText
android:id="@+id/et_chinese"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:hint="中文释义"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/et_english"
app:layout_constraintStart_toStartOf="@+id/et_english"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
<Button
android:id="@+id/bt_submit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="确定"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/et_english"
app:layout_constraintStart_toStartOf="@+id/et_english"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.4" />
</androidx.constraintlayout.widget.ConstraintLayout>
5.2 创建添加词汇页, AddFragment.java
public class AddFragment extends Fragment {
private Button btSubmit;
private EditText etEnglish, etChinese;
private WordViewModel viewModel;
private InputMethodManager imm;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_add, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
FragmentActivity activity = requireActivity();
btSubmit = view.findViewById(R.id.bt_submit);
etEnglish = view.findViewById(R.id.et_english);
etChinese = view.findViewById(R.id.et_chinese);
btSubmit.setEnabled(false);
etEnglish.requestFocus();
imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(etEnglish, 0);
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
String english = etEnglish.getText().toString().trim();
String chinese = etChinese.getText().toString().trim();
btSubmit.setEnabled(!english.isEmpty() && !chinese.isEmpty());
}
@Override
public void afterTextChanged(Editable editable) {
}
};
etEnglish.addTextChangedListener(textWatcher);
etChinese.addTextChangedListener(textWatcher);
viewModel = new ViewModelProvider(activity).get(WordViewModel.class);
btSubmit.setOnClickListener(view1 -> {
String english = etEnglish.getText().toString().trim();
String chinese = etChinese.getText().toString().trim();
Word word = new Word(english, chinese);
viewModel.insertWords(word);
imm.hideSoftInputFromWindow(view1.getWindowToken(), 0);
NavController navController = Navigation.findNavController(view1);
navController.navigateUp();
});
}
}
6. 主页 Activity
6.1 布局文件, activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
6.2 创建主页,MainActivity.java
public class MainActivity extends AppCompatActivity {
NavController navController;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView);
assert navHostFragment != null;
navController = navHostFragment.getNavController();
NavigationUI.setupActionBarWithNavController(this, navController);
}
@Override
public boolean onSupportNavigateUp() {
navController.navigateUp();
return super.onSupportNavigateUp();
}
@Override
public void onBackPressed() {
super.onBackPressed();
navController.navigateUp();
}
}
7. 效果图