安卓学习05---单词记录本小结
安卓学习的一个小结。主要使用了Room、navigation导航等知识点。
小程序功能有:单词的添加、一键删除所有单词、切换卡片视图或普通视图、搜索单词并实时刷新单词列表、单词右滑或左滑删除。
1、数据库的设计及封装(room)
1、entity的设计
由于是简单的单词记录,所以表的设计并不复杂,只需要知道单词及其汉语意思即可,不过因为功能的需要,添加一个boolean来判断是否需要隐藏单词的汉语意思,以及每个表都需要的id。
package com.example.roombasic; import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.PrimaryKey; @Entity public class Word { @PrimaryKey(autoGenerate = true) private int id; @ColumnInfo(name = "english_word") private String word; @ColumnInfo(name = "chinese_mean") private String chineseMean; @ColumnInfo(name = "is_ok") private boolean isOk; public boolean isOk() { return isOk; } public void setOk(boolean ok) { isOk = ok; } public Word(String word, String chineseMean) { this.word = word; this.chineseMean = chineseMean; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public String getChineseMean() { return chineseMean; } public void setChineseMean(String chineseMean) { this.chineseMean = chineseMean; } }
2、dao
由功能的需要可分析出,dao需要实现对entity的增加、删除、获取所有、根据条件获取相关单词、删除所有。
package com.example.roombasic; import androidx.lifecycle.LiveData; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; import androidx.room.Query; import androidx.room.Update; import java.util.List; @Dao public interface WordDao { //增加单词 @Insert void addWords(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") LiveData<List<Word>> getAllWordsLive(); //根据关键词查询单词 @Query("select * from word where english_word like :pattern order by id desc") LiveData<List<Word>> getWordsByPattern(String pattern); }
3、database
获取dao的工具。
package com.example.roombasic; import android.content.Context; import androidx.annotation.NonNull; import androidx.room.Database; import androidx.room.Room; import androidx.room.RoomDatabase; import androidx.room.migration.Migration; import androidx.sqlite.db.SupportSQLiteDatabase; @Database(entities = {Word.class},version = 2,exportSchema = false) public abstract class WordDatabase extends RoomDatabase { private static WordDatabase INSTANCE; static synchronized WordDatabase getDatabase(Context context){ if (INSTANCE == null) { INSTANCE = Room.databaseBuilder(context.getApplicationContext(),WordDatabase.class,"word_database") .addMigrations(MIGRATION_1_2) .build(); } return INSTANCE; } public abstract WordDao getWordDao(); //之前学习数据库版本迁移所用的迁移方式 private static final Migration MIGRATION_1_2 = new Migration(1,2) { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE word ADD COLUMN is_ok INTEGER NOT NULL DEFAULT 0"); } }; }
4、repository
为了使用方便放入了entity的工厂,以供viewmodel使用。
package com.example.roombasic; import android.content.Context; import android.os.AsyncTask; import androidx.lifecycle.LiveData; import java.util.List; public class WordRepository { private LiveData<List<Word>> listLiveData; private WordDao wordDao; public WordRepository(Context context) { WordDatabase wordDatabase = WordDatabase.getDatabase(context.getApplicationContext()); wordDao = wordDatabase.getWordDao(); listLiveData = wordDao.getAllWordsLive(); } //获取所有的单词 public LiveData<List<Word>> getListLiveData() { return listLiveData; } public void setListLiveData(LiveData<List<Word>> listLiveData) { this.listLiveData = listLiveData; } //根据条件查询 public LiveData<List<Word>> getWordsByPattern(String pattern){ return wordDao.getWordsByPattern("%" + pattern + "%"); } //异步添加(不在主线程) public void insertWords(Word... words){ new InsertAsyncTask(wordDao).execute(words); } //异步更新(不在主线程) public void updateWords(Word... words){ new UpdateAsyncTask(wordDao).execute(words); } //异步删除(不在主线程) public void deleteWords(Word... words){ new DeleteAsyncTask(wordDao).execute(words); } //异步清除所有(不在主线程) public void clearWords(){ new ClearAsyncTask(wordDao).execute(); } //添加类 static class InsertAsyncTask extends AsyncTask<Word,Void,Void> { private WordDao wordDao; InsertAsyncTask(WordDao wordDao) { this.wordDao = wordDao; } @Override protected Void doInBackground(Word... words) { wordDao.addWords(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 ClearAsyncTask extends AsyncTask<Void,Void,Void>{ private WordDao wordDao; ClearAsyncTask(WordDao wordDao) { this.wordDao = wordDao; } @Override protected Void doInBackground(Void... voids) { wordDao.deleteAllWords(); 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; } } }
2、ViewModel
ViewModel来进行代码中的各种对数据的操作。
package com.example.roombasic; import android.app.Application; import android.os.AsyncTask; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import java.util.List; public class WordViewModel extends AndroidViewModel { private WordRepository wordRepository; public WordViewModel(@NonNull Application application) { super(application); wordRepository = new WordRepository(application); } //获取所有单词 public LiveData<List<Word>> getListLiveData() { return wordRepository.getListLiveData(); } //根据查询条件获得单词 public LiveData<List<Word>> getWordsByPattern(String pattern){ return wordRepository.getWordsByPattern(pattern); } //增加 public void insertWords(Word... words){ wordRepository.insertWords(words); } //更新 public void updateWords(Word... words){ wordRepository.updateWords(words); } //删除 public void deleteWords(Word... words){ wordRepository.deleteWords(words); } //清空 public void clearWords(){ wordRepository.clearWords(); } }
3、界面的设计
1、单词列表界面
1、主界面
主界面:
-
界面中加入一个recylerView,是可滑动的列表。
-
加入一个floatingActionButton,用来跳转到单词添加界面。
2、菜单界面的设计
在res中添加menu资源,模板为:
-
搜索的item把属性设为:
2、单词添加界面
该界面比较简单。
3、卡片和普通列表视图
1、卡片视图
-
switch组件的高为占满,宽用padding属性来占满,start 为 30dp,end 为 15dp。
-
在左侧的容器属性中,onclick设为true,背景设置为selectableItemBackground,波纹颜色。
-
层次为结构如图。
2、普通视图
与卡片视图设计相似,不过最外层去掉了cardView。
4、界面之间的navigation
4、主要逻辑代码
1、单词列表逻辑代码
package com.example.roombasic; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.SearchView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProviders; import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import java.util.List; /** * A simple {@link Fragment} subclass. */ public class WordsFragment extends Fragment { private WordViewModel wordViewModel; private MyAdapter myAdapter1,myAdapter2; private RecyclerView recyclerView; private LiveData<List<Word>> filterdWords; private static final String VIEW_STYLE_SHP = "view_style_shp"; private static final String IS_CARD_VIEW = "is_card_view"; private List<Word> allWords; private boolean undoAction = false; public WordsFragment() { // Required empty public constructor //将菜单栏设置为可见 setHasOptionsMenu(true); } //此处是菜单部分的逻辑代码,需要采用switch方式来获得用户选择的功能 @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { switch (item.getItemId()){ //清空单词功能 case R.id.menu_clearWords: AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()); builder.setTitle("确定要清空单词吗?"); builder.setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { wordViewModel.clearWords(); } }); builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } }); builder.create(); builder.show(); break; //切换视图功能(卡片视图和普通视图) case R.id.menu_changeView: SharedPreferences sharedPreferences = requireActivity().getSharedPreferences(VIEW_STYLE_SHP, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); boolean isCard = sharedPreferences.getBoolean(IS_CARD_VIEW, false); if (isCard){ recyclerView.setAdapter(myAdapter1); editor.putBoolean(IS_CARD_VIEW,false); }else { recyclerView.setAdapter(myAdapter2); editor.putBoolean(IS_CARD_VIEW,true); } editor.apply(); } return super.onOptionsItemSelected(item); } //搜索单词菜单 @Override public void onCreateOptionsMenu( Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.menu_words,menu); //获取搜索item SearchView searchView = (SearchView) menu.findItem(R.id.app_bar_search).getActionView(); searchView.setMaxWidth(600); //设置监听 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { return false; } @Override public boolean onQueryTextChange(String newText) { //获得用户输入的内容 String pattern = newText.trim(); //将之前主页面的监听删除 filterdWords.removeObservers(getViewLifecycleOwner()); filterdWords = wordViewModel.getWordsByPattern(pattern); //搜索的单词的监听 filterdWords.observe(getViewLifecycleOwner(), new Observer<List<Word>>() { @Override public void onChanged(List<Word> words) { int wordNumber = myAdapter1.getItemCount(); allWords =words; //刷新列表 if (wordNumber != words.size()){ myAdapter1.submitList(words); myAdapter2.submitList(words); } } }); return false; } }); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_words, container, false); } //主要的逻辑代码都写在此处 @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); wordViewModel = ViewModelProviders.of(requireActivity()).get(WordViewModel.class); recyclerView = requireView().findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity())); //列表前方的数字的刷新 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 myViewHolder = (MyAdapter.MyViewHolder) recyclerView.findViewHolderForAdapterPosition(i); if (myViewHolder != null){ myViewHolder.textViewNumber.setText(String.valueOf(i + 1)); } } } } }); //界面列表的视图的偏好选择 myAdapter1 = new MyAdapter(false,wordViewModel); myAdapter2 = new MyAdapter(true,wordViewModel); SharedPreferences sharedPreferences = requireActivity().getSharedPreferences(VIEW_STYLE_SHP, Context.MODE_PRIVATE); SharedPreferences.Editor editor = sharedPreferences.edit(); boolean isCard = sharedPreferences.getBoolean(IS_CARD_VIEW, false); if (isCard){ recyclerView.setAdapter(myAdapter2); }else { recyclerView.setAdapter(myAdapter2); } //左右滑动删除功能 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) { return false; } @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { final Word word = allWords.get(viewHolder.getAdapterPosition()); wordViewModel.deleteWords(word); Snackbar.make(requireView().findViewById(R.id.WordsShowFrameView),"删除此词汇",Snackbar.LENGTH_LONG) .setAction("取消", new View.OnClickListener() { @Override public void onClick(View v) { wordViewModel.insertWords(word); undoAction = true; } }) .show(); } }).attachToRecyclerView(recyclerView); //单词列表的监听,当增加后刷新列表 filterdWords = wordViewModel.getListLiveData(); filterdWords.observe(getViewLifecycleOwner(), new Observer<List<Word>>() { @Override public void onChanged(List<Word> words) { int wordNumber = myAdapter1.getItemCount(); allWords = words; if (wordNumber != words.size()){ if (wordNumber < words.size() && !undoAction){ recyclerView.smoothScrollBy(0,-1000); } undoAction = false; myAdapter1.submitList(words); myAdapter2.submitList(words); } } }); //下方的悬浮按钮,跳转到单词添加页面 FloatingActionButton floatingActionButton = requireActivity().findViewById(R.id.floatingActionButton); floatingActionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { NavController navController = Navigation.findNavController(v); navController.navigate(R.id.action_wordsFragment_to_addFragment); } }); } }
2、单词添加页面逻辑代码
package com.example.roombasic; import android.content.Context; import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProviders; import androidx.navigation.NavController; import androidx.navigation.Navigation; /** * A simple {@link Fragment} subclass. */ public class AddFragment extends Fragment { private Button buttonSubmilt; private EditText editTextEnglish,editTextChinese; private WordViewModel wordViewModel; public AddFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_add, container, false); } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); buttonSubmilt = requireActivity().findViewById(R.id.buttonSubmilt); editTextChinese = requireActivity().findViewById(R.id.editTextChinese); editTextEnglish = requireActivity().findViewById(R.id.editTextEnglish); //将按钮设为不可点击 buttonSubmilt.setEnabled(false); wordViewModel = ViewModelProviders.of(requireActivity()).get(WordViewModel.class); //设置两个文字输入框的监听,当两个输入框的内容都存在时才可以点击添加按钮 TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String english = editTextEnglish.getText().toString().trim(); String chinese = editTextChinese.getText().toString().trim(); if (!english.isEmpty() && !chinese.isEmpty()){ buttonSubmilt.setEnabled(true); } } @Override public void afterTextChanged(Editable s) { } }; //加入监听 editTextEnglish.addTextChangedListener(textWatcher); editTextChinese.addTextChangedListener(textWatcher); //聚焦到英文输入框 editTextEnglish.requestFocus(); ////设置调用出用户的键盘并聚焦到英文输入框 final InputMethodManager inputMethodManager = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.showSoftInput(editTextEnglish,0); //添加单词 buttonSubmilt.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String english = editTextEnglish.getText().toString().trim(); String chinese = editTextChinese.getText().toString().trim(); Word word = new Word(english,chinese); wordViewModel.insertWords(word); //添加完毕后跳转到单词列表页面 NavController navController = Navigation.findNavController(v); navController.navigateUp(); //将键盘收起 inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(),0); } }); } }
3、主进程页面逻辑代码
package com.example.roombasic; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.navigation.ui.NavigationUI; import android.content.Context; import android.os.Bundle; import android.view.inputmethod.InputMethodManager; public class MainActivity extends AppCompatActivity { private NavController navController; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //界面的返回按钮的添加 navController = Navigation.findNavController(findViewById(R.id.fragment)); NavigationUI.setupActionBarWithNavController(this,navController); } //点击界面返回按钮后的动作,应该是先收起用户的键盘然后再跳转到主界面。 @Override public boolean onSupportNavigateUp() { InputMethodManager inputMethodManager = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(findViewById(R.id.fragment).getWindowToken(),0); navController.navigateUp(); return super.onSupportNavigateUp(); } }
4、适配器
这个主要是对recyclerView的处理。
package com.example.roombasic; import android.content.Intent; import android.net.Uri; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.Switch; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class MyAdapter extends ListAdapter<Word, MyAdapter.MyViewHolder> { private boolean isCard; private WordViewModel wordViewModel; //构造函数 public MyAdapter(boolean isCard,WordViewModel wordViewModel) { super(new DiffUtil.ItemCallback<Word>() { //判断列表是否刷新的条件--id是否一致 @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.getChineseMean().equals(newItem.getChineseMean()) && oldItem.isOk() == newItem.isOk()); } }); this.isCard = isCard; this.wordViewModel = wordViewModel; } //初始化适配器 @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); View itemView ; if (isCard){ itemView = layoutInflater.inflate(R.layout.cell_card_2,parent,false); }else { itemView = layoutInflater.inflate(R.layout.cell_normal_2,parent,false); } //点击单词后跳转到查询网页 final MyViewHolder holder = new MyViewHolder(itemView); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Uri uri = Uri.parse("http://m.youdao.com/dict?le=eng&q=" + holder.textViewEnglish.getText()); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(uri); holder.itemView.getContext().startActivity(intent); } }); //监听列表的switch点击 holder.aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { Word word = (Word) holder.itemView.getTag(R.id.word_for_view); if (isChecked){ holder.textViewChinese.setVisibility(View.GONE); word.setOk(true); wordViewModel.updateWords(word); }else { holder.textViewChinese.setVisibility(View.VISIBLE); word.setOk(false); wordViewModel.updateWords(word); } } }); return holder; } //将数据与recyclerview进行绑定 @Override public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) { final Word word = getItem(position); holder.itemView.setTag(R.id.word_for_view,word); holder.textViewNumber.setText(String.valueOf(position+1)); holder.textViewEnglish.setText(word.getWord()); holder.textViewChinese.setText(word.getChineseMean()); if (word.isOk()){ holder.textViewChinese.setVisibility(View.GONE); holder.aSwitch.setChecked(true); }else { holder.textViewChinese.setVisibility(View.VISIBLE); holder.aSwitch.setChecked(false); } } //刷新视图后重新编号 @Override public void onViewAttachedToWindow(@NonNull MyViewHolder holder) { super.onViewAttachedToWindow(holder); holder.textViewNumber.setText(String.valueOf(holder.getAdapterPosition() + 1)); } //控件的获取 class MyViewHolder extends RecyclerView.ViewHolder { TextView textViewNumber,textViewEnglish,textViewChinese; Switch aSwitch; public MyViewHolder(@NonNull View itemView) { super(itemView); textViewChinese = itemView.findViewById(R.id.textViewChinese); textViewEnglish = itemView.findViewById(R.id.textViewEnglish); textViewNumber = itemView.findViewById(R.id.textViewNumber); aSwitch = itemView.findViewById(R.id.switchIs_ok); } } }