Android MVVM框架使用(十二)记事本功能增强:视图类型、批量删除、搜索笔记

简介: Android MVVM框架使用(十二)记事本功能增强:视图类型、批量删除、搜索笔记

前言


 在上一篇文章中完成了记事本基本功能,就是增删改查,本文对一些功能进行增强,对用户的体验就会更好。完成这一篇文章就可以实现下面的效果图

546c7151636f4b88b0353257b055adcb.gif


3cb90ed206fd4abba6a02c35da957f76.gif

f3a0dcb2eb114203ac960fad2886c2cd.gif


正文


 功能一个一个来写,首先是视图类型,默认展示列表视图,增加一个宫格视图。


一、增加宫格视图


在menu包下创建notebook_settings.xml,里面的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/item_view_type"
        android:title="宫格视图" />
</menu>


下面在NotebookActivity中新增变量

//菜单Item
    private MenuItem itemViewType;
    @Inject
    MVUtils mvUtils;

9cdfcf3b4bca4490b9f7d9d5ef8ac973.png

使用ActionBar,设置菜单。因为有两种视图:列表视图和宫格视图,所以我们可以用一个缓存去处理,在Constant中增加一个常量:


/**
     * 笔记页面视图方式
     */
    public static final String NOTEBOOK_VIEW_TYPE = "notebookViewType";


然后我们通过缓存的方式去显示哪种视图,修改代码如下图所示:


455a91ae4eca402ca79b5931317864c8.png


下面在NotebookActivity中创建菜单,代码如下:


@Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.notebook_settings, menu);
        itemViewType = menu.findItem(R.id.item_view_type).setTitle(mvUtils.getInt(Constant.NOTEBOOK_VIEW_TYPE) == 1 ? "列表视图" : "宫格视图");
        return super.onCreateOptionsMenu(menu);
    }


是一个item,根据不同的类型设置不同的item标题。


然后就是菜单item点击事件,增加代码如下:


@Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        // 0 是列表视图 1 是宫格视图
        int viewType = mvUtils.getInt(Constant.NOTEBOOK_VIEW_TYPE);
        if (item.getItemId() == R.id.item_view_type) {//视图方式
            if (viewType == 0) {
                viewType = 1;
                itemViewType.setTitle("列表视图");
                binding.rvNotebook.setLayoutManager(new GridLayoutManager(context, 2));
            } else {
                viewType = 0;
                itemViewType.setTitle("宫格视图");
                binding.rvNotebook.setLayoutManager(new LinearLayoutManager(context));
            }
            mvUtils.put(Constant.NOTEBOOK_VIEW_TYPE, viewType);
        } 
        return true;
    }


最后为了使item显示出来比较好看,需要修改一下布局,修改activity_notebook.xml中,如下图所示:


92b7c1bd8e2242ab9a1425c43f771c79.png


然后在item_notebook.xml中修改布局的边距


534152599b754622898a714da0d117fd.png


下面运行一下:


546c7151636f4b88b0353257b055adcb.gif


二、批量删除


 现在虽说也有删除,是一个一个删,假如我有一天心情不好了,我有一百个笔记,一个一个删就不太友好,用户会疯狂叼程序员,什么玩意,一点都不够人性化,那么针对这一点我们也需要弄一个批量删除,这是很有必要的。那么怎么去做呢?首先要做的是UI上的变化,这是第一点。


① 菜单修改


 菜单修改,首先是增加一个批量删除的入口,目前的菜单只有一个item,用于切换列表视图,这里在notebook_settings.xml中增加一个item,如下图所示:


73240b55b7d24ae9aba875affb1cdf6b.png


然后回到NotebookActivity中,


96203ec3a4bb486b85c15abca76081a0.png


增加点击的处理,这里的点击之后就切换当前页面的模式,布局UI修改。


② UI修改


在activity_notebook.xml中增加data中的变量数据,因为需要通过xml去改变布局的图标,文字,文字颜色。


10fa3e5578ea4dd1bff9fa8ad59c45e1.png


当触发批量删除时,首先修改标题:


978e6ac5932a4b2885d55de11089fec4.png


其次隐藏掉浮动按钮:


31b58a7e4c4f4450af202a5bbe0cca88.png


最后在页面底部增加一个布局,这个布局里面是删除和多选:


<!--批量删除模式下显示的布局-->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:layout_alignParentBottom="true"
            android:background="@color/white"
            android:gravity="center_vertical"
            android:visibility="@{isBatchDeletion ? View.VISIBLE : View.GONE}">
            <TextView
                android:id="@+id/tv_delete"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:drawableTop="@mipmap/ic_delete"
                android:gravity="center"
                android:text="删除"
                android:textColor="@color/black" />
            <TextView
                android:id="@+id/tv_all_selected"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:drawableTop="@{isAllSelected ? ContextCompat.getDrawable(tvAllSelected.getContext(),R.mipmap.ic_all_selected) : ContextCompat.getDrawable(tvAllSelected.getContext(),R.mipmap.ic_all_select)}"
                android:gravity="center"
                android:text="@{isAllSelected ? `取消全选` : `全选`}"
                android:textColor="@{isAllSelected ? @color/purple_500 : @color/black}" />
        </LinearLayout>

31b58a7e4c4f4450af202a5bbe0cca88.png

图标去我的源码中拿,添加位置如下图所示:


13debad5bc214ca3901898cc600dd883.png


现在是页面修改完了,还有item的布局中也需要改动,打开item_notebook.xml,增加变量数据代码:


e6c6c3ef820f424184b0a074ad05b2a9.png


这里你会看到我把这个item的点击事件去掉了,这部分代码我将会挪到NoteActivity中,因此这里要修改一下item_notebook.xml中的代码:


<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="notebook"
            type="com.llw.mvvm.db.bean.Notebook" />
        <!--是否批量删除-->
        <variable
            name="isBatchDeletion"
            type="Boolean" />
        <!--视图,用于控制显示隐藏-->
        <import type="android.view.View" />
        <!--用于加载mipmap中的图标-->
        <import type="androidx.core.content.ContextCompat"/>
        <!--R文件-->
        <import type="com.llw.mvvm.R"/>
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="@dimen/dp_4"
        android:background="@drawable/shape_bg_white_radius_12"
        android:foreground="?attr/selectableItemBackground"
        android:padding="12dp">
        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toStartOf="@+id/iv_check"
            android:ellipsize="end"
            android:singleLine="true"
            android:text="@{notebook.title}"
            android:textColor="@color/black"
            android:textSize="16sp" />
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv_title"
            android:layout_marginTop="4dp"
            android:layout_toStartOf="@+id/iv_check"
            android:ellipsize="end"
            android:maxLines="3"
            android:text="@{notebook.content}"
            android:textSize="14sp" />
        <ImageView
            android:id="@+id/iv_check"
            android:layout_width="24dp"
            android:layout_height="24dp"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="6dp"
            android:src="@{notebook.select ? ContextCompat.getDrawable(ivCheck.getContext(),R.mipmap.ic_selected) : ContextCompat.getDrawable(ivCheck.getContext(),R.mipmap.ic_select)}"
            android:visibility="@{isBatchDeletion ? View.VISIBLE : View.GONE}" />
    </RelativeLayout>
</layout>


还是老样子,图标去我源码里面拿。


③ 适配器修改


item布局改完了,下面是NotebookAdapter适配器。


3daa2fd33e9a43ce94ee065ea45d768b.png


这里我去掉了之前写的点击事件内部类。


④ 修改删除方法


 之前写的NotebookDao中的删除方法是删除单个笔记的,那如果要删除多个呢?这里我们改成动态参数就行了。


208e645ff3b34a0a9cbf3d22ce517d71.png


就在后面加三个点就行了,这个表示你一个笔记可以,多个笔记也行。下面修改NotebookRepository中的deleteNotebook方法的参数,


1fff96aa75d8476fa6a91a007a4c7ada.png


然后再修改NotebookViewModel中的deleteNotebook方法中的参数。


ecb6586711d34fd79b5a08f01629702b.png


⑤ 列表处理


 现在就只剩下NotebookActivity中的代码没写了,首先在NotebookActivity增加变量,代码如下:


//笔记适配器
    private NotebookAdapter notebookAdapter;
    //笔记列表
    private final List<Notebook> mList = new ArrayList<>();
    //是否为批量删除
    private boolean isBatchDeletion = false;
    //是否全选
    private boolean isAllSelected;


实现页面的点击监听。


57bb5acaeafd430e8e2bfc8d187251e5.png


控件监听


4edbe7c7677242a5a1d26aec6c47a205.png


重写onClick方法。


@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv_delete:
                break;
            case R.id.tv_all_selected:
                break;
            default:
                break;
        }
    }


这里针对页面中底部布局中的两个TextView的点击,一个用于删除,一个用于全选和取消全选。这两个按钮的处理事件先不管它,先弄别的,这里我们先修改一下onResume中的代码:


baa241b41301459980c138d20445ffa0.png


这里之前的代码,我写到一个方法里面去了,新建一个initList方法。


/**
     * 初始化列表
     */
    private void initList() {
        //适配器
        notebookAdapter = new NotebookAdapter(mList);
        //设置适配器
        binding.rvNotebook.setAdapter(notebookAdapter);
        binding.rvNotebook.setLayoutManager(mvUtils.getInt(Constant.NOTEBOOK_VIEW_TYPE) == 1 ?
                new GridLayoutManager(context, 2) : new LinearLayoutManager(context));
        //item点击事件
        notebookAdapter.setOnItemClickListener((adapter, view, position) -> {
            if (isBatchDeletion) {
                //选中设备
                mList.get(position).setSelect(!mList.get(position).isSelect());
                notebookAdapter.notifyDataSetChanged();
                //修改页面标题
                changeTitle();
            } else {
                Intent intent = new Intent(NotebookActivity.this, EditActivity.class);
                intent.putExtra("uid", mList.get(position).getUid());
                startActivity(intent);
            }
        });
    }


这里还有一个修改页面标题的方法,也就是说当你选择了笔记时会记录选择个数。


/**
     * 修改标题
     */
    private void changeTitle() {
        int selectedNum = 0;
        for (Notebook notebook : mList) {
            if (notebook.isSelect()) {
                selectedNum++;
            }
        }
        Log.e(TAG, "changeTitle: " + selectedNum);
        binding.tvTitle.setText("已选择 "+ selectedNum +" 项");
        binding.setIsAllSelected(selectedNum == mList.size());
    }


刚才的initList方法,在onCreate中调用。


5016768728404767957640050abe0e79.png


下面就是通过点击菜单的批量删除item时调用的方法,新增方法如下:


/**
     * 设置批量删除模式
     */
    private void setBatchDeletionMode() {
        //进入批量删除模式
        isBatchDeletion = !isBatchDeletion;
        //设置当前页面
        binding.setIsBatchDeletion(isBatchDeletion);
        if (!isBatchDeletion) {
            //取消所有选中
            for (Notebook notebook : mList) {
                notebook.setSelect(false);
            }
        }
        //设置适配器
        notebookAdapter.setBatchDeletion(isBatchDeletion);
        notebookAdapter.notifyDataSetChanged();
    }


 这个方法首先是改变当前的是否批量删除变量,然后设置到xml中,如果是false,则说明你退出了批量删除模式,则之前有过选中的笔记也要取消选中。最后设置适配器中的变化,再刷新适配器。方法调用的地方如下图所示:


b2304c1d9ccf43259a6f1c6a30286a79.png


因为页面会进入到批量删除模式,如果这个时候页面返回了则也调用一下这个方法。


e35bdf422cec43f38fafdb1e996f52c9.png


下面就是删除和全选/取消全选的方法要写一下了,首先是删除吧。


⑥ 删除笔记


 因为是删除多个笔记,因此我们需要弹窗提示一下用户,新增代码如下:


/**
     * 显示确认删除弹窗
     */
    private void showConfirmDelete() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this).setMessage("确定要删除所选的笔记吗?")
                .setPositiveButton("确定", (dialog, which) -> {
                    dialog.dismiss();
                    List<Notebook> notebookList = new ArrayList<>();
                    //删除所选中的笔记
                    for (Notebook notebook : mList) {
                        if (notebook.isSelect()) {
                            notebookList.add(notebook);
                        }
                    }
                    Notebook[] notebooks = notebookList.toArray(new Notebook[0]);
                    viewModel.deleteNotebook(notebooks);
                    //设置批量删除模式
                    setBatchDeletionMode();
                    //请求数据
                    viewModel.getNotebooks();
                })
                .setNegativeButton("取消", (dialog, which) -> dialog.dismiss());
        builder.create().show();
    }


 这里的方法主要就是将选择的笔记添加到列表中,然后列表再转笔记数组,数组可以作为动态参数传入到方法中。删除之后当然就要关闭这个批量删除模式,最后我们再查询一下数据库中的笔记。


⑦ 全选


/**
     * 全选/取消全选
     */
    private void allSelected() {
        isAllSelected = !isAllSelected;
        //设置适配器
        for (Notebook notebook : mList) {
            notebook.setSelect(isAllSelected);
        }
        //修改页面标题
        changeTitle();
        //设置当前页面
        binding.setIsAllSelected(isAllSelected);
        //刷新适配器
        notebookAdapter.notifyDataSetChanged();
    }


最后就是方法调用的地方:


55a297791b3842d99f1552270bf99857.png


代码基本上就写完了,你写的时候最后自己捋一捋这个逻辑,下面运行一下。


3cb90ed206fd4abba6a02c35da957f76.gif


这个批量删除就OK了,相对来说麻烦一些。


三、搜索笔记


 当笔记很多的时候,搜索就有必要了,你让我一条一条的去找,那是不存在的。那么事已至此,就开始写吧,写之前理一下思路啊,首先我们需要一个输入框,当输入内容之后显示删除按钮,点击按钮需要清除输入框内容,然后就是通过内容进行模糊搜索,目标字段是标题和内容,也就是说当你的输入内容和搜索的数据中标题和内容只要有一个可以匹配就可以。Emm…


① 输入布局


开始吧。首先在activity_notebook.xml中的data标签中增加变量,如下所示:

<!--是否显示输入布局-->
        <variable
            name="showSearchLay"
            type="Boolean" />
        <!--输入框是否有内容-->
        <variable
            name="isSearch"
            type="Boolean" />


然后增加一个搜索布局,代码如下:


<!--搜索笔记布局,有笔记时显示-->
            <LinearLayout
                android:id="@+id/lay_search"
                android:layout_width="match_parent"
                android:layout_height="46dp"
                android:background="@color/white"
                android:gravity="center_vertical"
                android:paddingStart="8dp"
                android:paddingEnd="8dp"
                android:visibility="@{showSearchLay ? View.VISIBLE : View.GONE}">
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="36dp"
                    android:background="@drawable/shape_search_bg"
                    android:paddingStart="12dp"
                    android:paddingEnd="12dp">
                    <ImageView
                        android:layout_width="20dp"
                        android:layout_height="20dp"
                        android:layout_centerVertical="true"
                        android:src="@mipmap/ic_search" />
                    <EditText
                        android:id="@+id/et_search"
                        android:layout_width="match_parent"
                        android:layout_height="36dp"
                        android:layout_centerVertical="true"
                        android:layout_marginStart="32dp"
                        android:layout_marginEnd="32dp"
                        android:background="@null"
                        android:hint="搜索笔记"
                        android:textColor="@color/black"
                        android:textCursorDrawable="@drawable/custom_cursor"
                        android:textSize="@dimen/sp_14" />
                    <ImageView
                        android:id="@+id/iv_clear"
                        android:layout_width="20dp"
                        android:layout_height="20dp"
                        android:layout_alignParentEnd="true"
                        android:layout_centerVertical="true"
                        android:src="@mipmap/ic_clear"
                        android:visibility="@{isSearch ? View.VISIBLE : View.GONE}" />
                </RelativeLayout>
            </LinearLayout>


图标在我的源码里,这个布局的位置我需要说明一下:


6f4575c2e2dc43069ac7dc52c3e5a68f.png


这里注意一点就是这个搜索布局是在列表的上方,而不再没有数据布局的上方。


② 模糊搜索


 Room中的模糊搜索和常规的SQL语句有一点区别,首先打开NotebookDao,在里面增加如下代码:


// ||相当于+号
    @Query("SELECT * FROM notebook WHERE title LIKE '%' || :input || '%' OR content LIKE '%' || :input || '%' ")
    Flowable<List<Notebook>> searchNotebook(String input);


这里你看到where后面就是要搜索的字段条件,这里的|| 就等于 + ,or用来匹配另一个字段,这里有标题和内容两个字段。因为模糊搜索返回的数据也会是多条,因此用List包裹起来。下面就是调用的地方了,打开NotebookRepository,在里面增加如下代码:


/**
     * 搜索笔记
     */
    public MutableLiveData<List<Notebook>> searchNotebook(String input) {
        Flowable<List<Notebook>> listFlowable = BaseApplication.getDb().notebookDao().searchNotebook(input);
        CustomDisposable.addDisposable(listFlowable, notebooks -> {
            if (notebooks.size() > 0) {
                notebooksMutableLiveData.postValue(notebooks);
            } else {
                notebooksMutableLiveData.postValue(emptyList);
                failed.postValue("暂无数据");
            }
        });
        return notebooksMutableLiveData;
    }


这里的代码就没啥好说的,和之前的获取全部笔记方法类似。然后就是在NotebookViewModel中调用了,在NotibookViewModel中增加如下方法:


/**
     * 搜索笔记
     * @param input 输入内容
     */
    public void searchNotebook(String input) {
        notebooks = notebookRepository.searchNotebook(input);
        failed = notebookRepository.failed;
    }


③ 逻辑处理


 首先是处理搜索布局是否显示,没有数据并且搜索输入框没有内容的时候不显示。


网络异常,图片无法展示
|


处理页面的输入框监听:


//输入框监听
        binding.etSearch.addTextChangedListener(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) {
            }
            @Override
            public void afterTextChanged(Editable s) {
    if (s.length() > 0) {
                    binding.setIsSearch(true);
                    //搜索笔记
                    viewModel.searchNotebook(s.toString());
                } else {
                    //获取全部笔记
                    binding.setIsSearch(false);
                    viewModel.getNotebooks();
                }
            }
        });


处理清空图标的监听:


binding.ivClear.setOnClickListener(this);


点击时清空输入框内容,并且隐藏按钮。


5e52829d34724bd297cdcfcc0cfe91ad.png


鉴于onCreate中代码太多了,现在封装成initView方法。


696a5abe51cd448bbf58caf82ebda7d8.png


现在代码就写完了,运行一下看看:


f3a0dcb2eb114203ac960fad2886c2cd.gif

相关文章
|
2月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
46 1
|
3月前
|
前端开发 JavaScript 测试技术
android做中大型项目完美的架构模式是什么?是MVVM吗?如果不是,是什么?
android做中大型项目完美的架构模式是什么?是MVVM吗?如果不是,是什么?
161 2
|
3月前
|
存储 前端开发 Java
Android MVVM架构模式下如何避免内存泄漏
Android采用MVVM架构开发项目,如何避免内存泄漏风险?怎样避免内存泄漏?
130 1
|
2月前
|
前端开发 JavaScript 测试技术
android做中大型项目完美的架构模式是什么?是MVVM吗?如果不是,是什么?
在 Android 开发中,选择合适的架构模式对于构建中大型项目至关重要。常见的架构模式有 MVVM、MVP、MVI、Clean Architecture 和 Flux/Redux。每种模式都有其优缺点和适用场景,例如 MVVM 适用于复杂 UI 状态和频繁更新,而 Clean Architecture 适合大型项目和多平台开发。选择合适的架构应考虑项目需求、团队熟悉度和可维护性。
70 6
|
2月前
|
搜索推荐 前端开发 Android开发
安卓应用开发中的自定义视图实现
【10月更文挑战第30天】在安卓开发的海洋中,自定义视图是那抹不可或缺的亮色,它为应用界面的个性化和交互体验的提升提供了无限可能。本文将深入探讨如何在安卓平台创建自定义视图,并展示如何通过代码实现这一过程。我们将从基础出发,逐步引导你理解自定义视图的核心概念,然后通过一个实际的代码示例,详细讲解如何将理论应用于实践,最终实现一个美观且具有良好用户体验的自定义控件。无论你是想提高自己的开发技能,还是仅仅出于对安卓开发的兴趣,这篇文章都将为你提供价值。
|
3月前
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
73 3
|
3月前
|
存储 前端开发 测试技术
Android kotlin MVVM 架构简单示例入门
Android kotlin MVVM 架构简单示例入门
54 1
|
2月前
|
前端开发 Java 测试技术
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
android MVP契约类架构模式与MVVM架构模式,哪种架构模式更好?
98 0
|
6天前
|
缓存 前端开发 Android开发
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程
【04】flutter补打包流程的签名过程-APP安卓调试配置-结构化项目目录-完善注册相关页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程

热门文章

最新文章