制作单词记录App(三)

制作单词记录App(三)

  • 制作单词记录App(三)
      • 改造Adapter为ListAdapter(用以处理后台提交的列表数据)
      • 修正视图序号上的改变(变更为数据层面的序号改变)
      • 制作添加数据时的数据信息界面反馈(添加数据自动跳转到新添加的条目位置)
      • 处理因LiveData造成的添加数据后的搜索观察(observer)失误
    • 制作滑动删除
      • 完善删除功能
      • 小插曲——简单讲述一下拖动的功能实现

本文为学习类文档,通过学习B站up主longway777的视频,再加上自己的总结与理解的学习类文章,如有侵权,请联系博主进行删除

制作单词记录App(三)

紧接着上次做的单词记录app,完善删除的功能,实现向左或向右滑动时,从单词列表下方显示删除图标,并持续滑动删除。在用户误删的时候实现一个撤销的操作

处理上次遗留问题:插入数据的跳变反应(不够平滑),是由于添加数据时,LiveData直接呼叫notifyDataSetChanged()函数直接刷新所有函数,造成大的开销。

改造Adapter为ListAdapter(用以处理后台提交的列表数据)

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.sql.RowId;
import java.util.ArrayList;
import java.util.List;

//继承ListAdapter并改写构造方法
public class MyAdapter extends ListAdapter { //<>中表示使用的ViewHolder为自己写的静态内部类MyViewHolder

    private boolean useCardView;  //是否使用卡片布局视图
    private WordViewModel wordViewModel;

    MyAdapter(boolean useCardView,WordViewModel wordViewModel) {
        super(new DiffUtil.ItemCallback() {  //列表数据的差异化处理,是后台异步进行的
            @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.wordViewModel = wordViewModel;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        /*
        * 当创建ViewHolder时
        * 从Layout文件中
        * 加载View
        * */
        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);  //普通模式界面
        }

        final MyViewHolder holder = new MyViewHolder(itemView);
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("http://dict.youdao.com/w/" + holder.textViewEnglish.getText() + "/#keyfrom=dict2.top");
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(uri);
                holder.itemView.getContext().startActivity(intent);
            }
        });

        holder.aSwitchChineseInvisible.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                Word word = (Word) holder.itemView.getTag(R.id.word_for_view_holder);//广义object要强制转换
                if (isChecked) {
                    holder.textViewChinese.setVisibility(View.GONE);
                    word.setChineseInvisible(true);
                    wordViewModel.updateWords(word);
                } else {
                    holder.textViewChinese.setVisibility(View.VISIBLE);
                    word.setChineseInvisible(false);
                    wordViewModel.updateWords(word);
                }
            }
        });
        return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
        //绑定逻辑上的关联
        final Word word = getItem(position);

        //itemView的setTag方法可以获取对象并在其他地方通过getTag获取对应对象
        holder.itemView.setTag(R.id.word_for_view_holder,word); //用资源封装保证不冲突
        holder.textViewNumber.setText(String.valueOf(position + 1));
        holder.textViewEnglish.setText(word.getWord());
        holder.textViewChinese.setText(word.getChineseMeaning());
        if (word.isChineseInvisible()) {
            holder.textViewChinese.setVisibility(View.GONE); //中文不可视
            holder.aSwitchChineseInvisible.setChecked(true);//设置开关为开
        } else {
            holder.textViewChinese.setVisibility(View.VISIBLE);
            holder.aSwitchChineseInvisible.setChecked(false);
        }
    }

    static class MyViewHolder extends RecyclerView.ViewHolder {
        TextView textViewNumber,textViewEnglish,textViewChinese;
        Switch aSwitchChineseInvisible;
        MyViewHolder(@NonNull View itemView) {  //自带View类型的itemView
            super(itemView);
            textViewNumber = itemView.findViewById(R.id.textViewNumber);
            textViewEnglish = itemView.findViewById(R.id.textViewEnglish);
            textViewChinese = itemView.findViewById(R.id.textViewChinese);
            aSwitchChineseInvisible  = itemView.findViewById(R.id.switchChineseInvisible);
        }
    }
}

由于改写了Adapter,因此Fragement也需要修改:

filteredWords.observe(requireActivity(), new Observer>() {
            @Override
            public void onChanged(List words) {
                int temp = myAdapter1.getItemCount();

                if (temp!=words.size()) {
                    myAdapter1.submitList(words);
                    myAdapter2.submitList(words);
                }
            }
        });

修正视图序号上的改变(变更为数据层面的序号改变)

可以在recyclerView中设置回调
在Fragement中

 @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
...
        //设置recyclerView的回调
        recyclerView.setItemAnimator(new DefaultItemAnimator() {
            //复写回调中的函数onAnimationFinished
            @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.textViewNumber.setText(String.valueOf(i + 1));
                        }
                    }
                }
            }
        }); //当添加数据动画结束,设置回调
...
    }

为了防止因数据过多数据条目的项目不稳定
还要在Adapter中再复写一个onViewAttachedToWindow方法

@Override
    public void onViewAttachedToWindow(@NonNull MyViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        holder.textViewNumber.setText(String.valueOf(holder.getAdapterPosition() + 1));
    }

制作添加数据时的数据信息界面反馈(添加数据自动跳转到新添加的条目位置)

filteredWords.observe(requireActivity(), new Observer>() {
            @Override
            public void onChanged(List words) {
                int temp = myAdapter1.getItemCount();
                if (temp != words.size()) {
                    recyclerView.smoothScrollBy(0,-200);  //设置滑动界面下移200个像素点
                    myAdapter1.submitList(words);
                    myAdapter2.submitList(words);
                }
            }
        });

处理因LiveData造成的添加数据后的搜索观察(observer)失误

找到LiveData的观察代码

 /*
 * 对于filteredWords.observe观察
 * fragement在观察过程中依赖requireActivity
 * 由于Activity不曾被摧毁,
 * 所以将Activity作为观察的owner
 * 会出现界面上的一个重叠
 * 需要解决将getActivity()修改采用getViewLifecycleOwner()
 * */
                filteredWords.observe(getViewLifecycleOwner(), new Observer>() {
                    @Override
                    public void onChanged(List words) {
                        int temp = myAdapter1.getItemCount();  //观察的代码与下面的一样,可复制

                        if (temp != words.size()) {
                            myAdapter1.submitList(words);
                            myAdapter2.submitList(words);
                        }
                    }
                });

制作滑动删除

制作一个列表变动的Helper

       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) {
                Word wordToDelete = allWords.get(viewHolder.getAdapterPosition());  //用allWords代替LiveData避免警告
                wordViewModel.deleteWords(wordToDelete); //完成删除操作
            }
        }).attachToRecyclerView(recyclerView);  //附加recyclerView生效

为了防止LiveData异步机制造成的无法实时查看信息,使用

private List allWords; //为了处理LiveData造成的异步获取数据造成的无法实时返回结果

并将其带入观察和查询中

allWords = words;

运行如下:
制作单词记录App(三)_第1张图片
制作单词记录App(三)_第2张图片
搜索中也可以进行删除

完善删除功能

制作撤消功能要使用到Snackbar

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 wordToDelete = allWords.get(viewHolder.getAdapterPosition());  //用allWords代替LiveData避免警告,获取删除节点
                wordViewModel.deleteWords(wordToDelete); //完成删除操作
                Snackbar.make(requireActivity().findViewById(R.id.wordsFragementView),"删除了一个词汇",Snackbar.LENGTH_SHORT)
                        .setAction("撤销", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                wordViewModel.insertWords(wordToDelete);  //将上面的数据重新插入回数据库中
                            }
                        }).show();
            }
        }).attachToRecyclerView(recyclerView);  //附加recyclerView生效

效果如下:
制作单词记录App(三)_第3张图片
点击撤销按钮即可恢复数据:
制作单词记录App(三)_第4张图片
对于撤销信息与添加按钮之间的协调问题
可以将布局改为CoordinatorLayout(协调布局)即可
制作单词记录App(三)_第5张图片
制作单词记录App(三)_第6张图片
制作删除背景下的删除图标,人性化提示删除功能:

new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,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 wordToDelete = allWords.get(viewHolder.getAdapterPosition());  //用allWords代替LiveData避免警告,获取删除节点
                wordViewModel.deleteWords(wordToDelete); //完成删除操作
                Snackbar.make(requireActivity().findViewById(R.id.wordsFragementView),"删除了一个词汇",Snackbar.LENGTH_SHORT)
                        .setAction("撤销", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                undoAction = true;
                                wordViewModel.insertWords(wordToDelete);  //将上面的数据重新插入回数据库中
                            }
                        }).show();
            }
            //制作滑动下的删除图标
            Drawable icon = ContextCompat.getDrawable(requireActivity(),R.drawable.ic_delete_black_24dp);
            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;
                int iconMargin = (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
                int iconLeft,iconRight,iconTop,iconBottom;
                int backTop,backBottom,backLeft,backRight;
                backTop = itemView.getTop();
                backBottom = itemView.getBottom();
                iconTop = itemView.getTop() + (itemView.getHeight() - icon.getIntrinsicHeight()) / 2;
                iconBottom = iconTop + icon.getIntrinsicHeight();
                if (dX > 0) {
                    backLeft = itemView.getLeft();
                    backRight = itemView.getLeft() + (int) dX;
                    background.setBounds(backLeft,backTop,backRight,backBottom);
                    iconLeft = itemView.getLeft() + iconMargin;
                    iconRight = iconLeft + icon.getIntrinsicWidth();
                    icon.setBounds(iconLeft,iconTop,iconRight,iconBottom);
                } else if (dX < 0){
                    backRight = itemView.getRight();
                    backLeft = itemView.getRight() + (int) dX;
                    background.setBounds(backLeft,backTop,backRight,backBottom);
                    iconRight = itemView.getRight() - iconMargin;
                    iconLeft = iconRight - icon.getIntrinsicWidth();
                    icon.setBounds(iconLeft,iconTop,iconRight,iconBottom);
                } else {
                    background.setBounds(0,0,0,0);
                    icon.draw(c);
                }
                background.draw(c);
                icon.draw(c);
                //滑动删除完成
            }
        }).attachToRecyclerView(recyclerView);  //附加recyclerView生效

小插曲——简单讲述一下拖动的功能实现

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) {
                //先获取位置对象的两个参数ViewHolder,target
                Word worfFrom = allWords.get(viewHolder.getAdapterPosition());
                Word wordTo = allWords.get(target.getAdapterPosition());
                /*
                * ID交换三部曲
                * */
                int idTemp = worfFrom.getId();
                worfFrom.setId(wordTo.getId());
                wordTo.setId(idTemp);
                wordViewModel.updateWords(worfFrom,wordTo);
                myAdapter1.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
                myAdapter2.notifyItemMoved(viewHolder.getAdapterPosition(),target.getAdapterPosition());
                return false;
            }

但是使用上述方法时,对ViewModel层面上的直接操作,会导致因操作上的不正当使用(快速拖动等)造成数据上的错乱,对于拖动排序的功能:平时应该在菜单栏中设置排序,并通过右侧的排序按钮(类似三条杠杠)来进行操作。

你可能感兴趣的:(滑动删除)