• 20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析


    20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析

    项目名称

    小说阅读器

    项目功能

    注册登录
    用户信息、用户密码、用户图像修改
    书籍分类
    书架
    书籍搜索(作者名或书籍名)
    书籍阅读(仅txt格式,暂不支持PDF等其他格式)
    阅读字体、背景颜色、翻页效果等设置
    意见反馈(反馈信息发送到我的邮箱)

    项目简介

    本项目主要是一个小说阅读软件,我所做的小说阅读软件是一个可以根据用户的阅读兴趣、爱好、来进行完成的。第一,我的项目功能里面有注册用户的,用户可以进行注册和登录。第二,用户信息功能,用户可以查看自己的信息,修改自己的密码,修改用户图像等。第三,我还根据书的特点进行了书架的分类,分为了玄幻、奇幻、武侠、仙侠、都市、职场、历史、军事等。第四,书籍搜索功能,也即是用户可以根据小说的名字或者作者来进行搜索相应的小说。第五,书籍的阅读仅限于txt格式的阅读。第六,用户自己可以设置用户的阅读字体、背景颜色、翻页效果等功能。第七,用户可以进行信息的反馈,且发送到我的邮箱里面。

    项目的运行截图

    使用开源库

    Rx2网络封装 RxHttpUtils
    6.0权限库 RxPermissions
    Glide图片加载库 Glide
    下拉刷新库 SmartRefreshLayout
    RecyclerView简化框架 BaseRecyclerViewAdapterHelper
    MD风格Dialog material-dialogs
    TabLaout选择 NavigationTabStrip
    数据加载动画 Android-SpinKit
    展开折叠TextView ExpandTextView
    流式标签 FlowLayout
    数据库 greenDAO
    版本更新进度条 NumberProgressBar
    图片选择器 TakePhoto
    项目首页- GanK -在基础上修改

    代码组成部分

    1-1代码模块截图

    Java代码

    资源代码

    配置文件代码

    以上是本项目的整体代码结构

    1-2代码模块分析
    api:该包主要是用于网络接口的代码
    db:该包主要是数据库连接,数据存储代码
    event:该包主要是用于事件回调后的通知
    Intenfces:该包主要是用于接口的编写
    Model:该包主要是用于实体类的编写,如小说,用户等实体类
    Util:该包主要是用于工具类代码的编写
    View:该包主要是用于app界面的编写
    Viewmodel:该包主要是用于界面数据显示的实体
    Widget:该包用于app界面装置代码的编写

    Anim:主要存放自定义按钮样式
    Color:主要是用于存放颜色代码
    Drawable:主要是用于存放图片等静态文件
    Layout:主要是用于存放界面的代码
    Mipmap-*:用于适配各种分辨的图标
    Raw:存放文件 如txt
    Values:存放各种文本变量
    Xml:存放配置文件 如文件路径等

    主要使用Android mvp开发模式进行开发,该模式有以下几个优点
    1、模型与视图完全分离,我们可以修改视图而不影响模型
    2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部
    3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
    4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

    代码调用关系

    代码调用关系

    一:用户管理

    0:用户注册

    由VMUserRegisterInfo类中的register()对BookService中的接口进行调用,完成整个的注册功能

    2:登录功能

    由VMUseLoginInfo类中的login方法对BookService中的接口调用 完成登录流程

    3:修改密码功能

    由VMUseLoginInfo类中的updatePassword方法对BookService中的接口调用 完成修改密码流程

    4:更新个人信息

    由VMUseLoginInfo类中的updateUserInfo方法对BookService中的接口调用 完成更新个人信息流程

    5:更新头像

    由VMUseLoginInfo类中的uploadAvatar方法对BookService中的接口调用 完成更新头像流程

    从网络接口获取电子书

    1 获取所有的分类
    由VMBookClassify类中的bookClassify方法对BookService中的接口进行调用,完成整个对分类的获取

    2:获取分类下的书籍
    由VMBookClassify类中的getBooks方法对BookService中的接口进行调用,完成整个对分类的获取

    3:获取书籍信息
    由VMBookClassify类中的bookInfo方法对BookService中的接口进行调用,完成整个对书籍信息的获取

    4 获取书籍目录
    由VMBookClassify类中的setBookInfo方法对BookService中的接口进行调用,完成整个对书籍目录的获取

    核心代码分析

    1:网络爬虫
    该app的书籍信息主要来自于互联网,我们通过fider对追书神器的网络请求进行抓取,获取http请求,随后,我们在本app中使用Rxjava框架,进行对抓取的http链接进行请求,获取数据后封装显示。以下是核心代码

    /**
     * 获取书籍信息
     *
     * @param bookid
     */
    public void bookInfo(String bookid) {
        iBookDetail.showLoading();
        RxHttpUtils.getSInstance().addHeaders(tokenMap())
                .createSApi(BookService.class).bookInfo(bookid)
                .compose(Transformer.switchSchedulers())
                .subscribe(new RxObserver<BookBean>() {
                    @Override
                    protected void onError(String errorMsg) {
                        iBookDetail.stopLoading();
                    }
    
                    @Override
                    protected void onSuccess(BookBean bookBean) {
                        iBookDetail.stopLoading();
                        iBookDetail.getBookInfo(bookBean);
                    }
                });
    }
    
    public void bookClassify() {
        if (!NetworkUtils.isConnected()) {
            if (mIBookClassify != null) {
                mIBookClassify.NetWorkError();
            }
            return;
        }
    
        RxHttpUtils.getSInstance().addHeaders(tokenMap()).createSApi(BookService.class)
       /* RxHttpUtils.createApi(BookService.class)*/
                .bookClassify()
                .compose(Transformer.switchSchedulers())
                .subscribe(new RxObserver<BookClassifyBean>() {
                    @Override
                    protected void onError(String errorMsg) {
                        if (mIBookClassify != null) {
                            mIBookClassify.stopLoading();
                            mIBookClassify.errorData(errorMsg);
                        }
                    }
    
                    @Override
                    protected void onSuccess(BookClassifyBean data) {
                        if (mIBookClassify != null) {
                            mIBookClassify.stopLoading();
                            if (data == null) {
                                mIBookClassify.emptyData();
                                return;
                            }
                            mIBookClassify.getBookClassify(data);
                        }
    
    
                    }
    
                    @Override
                    public void onSubscribe(Disposable d) {
                        addDisposadle(d);
                    }
                });
    }
    
    public void setBookInfo(CollBookBean collBookBean) {
            LoadingHelper.getInstance().showLoading(mContext);
            if (CollBookHelper.getsInstance().findBookById(collBookBean.get_id()) == null) {
                RxHttpUtils.getSInstance().addHeaders(tokenMap()).createSApi(BookService.class)
                        .bookChapters(collBookBean.get_id())
                        .compose(Transformer.switchSchedulers())
                        .subscribe(new RxObserver<BookChaptersBean>() {
                            @Override
                            protected void onError(String errorMsg) {
                                LoadingHelper.getInstance().hideLoading();
                            }
    
                            @Override
                            protected void onSuccess(BookChaptersBean data) {
                                LoadingHelper.getInstance().hideLoading();
                                List<BookChapterBean> bookChapterList = new ArrayList<>();
                                for (BookChaptersBean.ChatpterBean bean : data.getChapters()) {
                                    BookChapterBean chapterBean = new BookChapterBean();
                                    chapterBean.setBookId(data.getBook());
                                    chapterBean.setLink(bean.getLink());
                                    chapterBean.setTitle(bean.getTitle());
    //                                chapterBean.setTaskName("下载");
                                    chapterBean.setUnreadble(bean.isRead());
                                    bookChapterList.add(chapterBean);
                                }
                                collBookBean.setBookChapters(bookChapterList);
                                CollBookHelper.getsInstance().saveBookWithAsync(collBookBean);
                                iBookShelf.bookInfo(collBookBean);
                            }
    
                            @Override
                            public void onSubscribe(Disposable d) {
                                addDisposadle(d);
                            }
                        });
            } else {
                LoadingHelper.getInstance().hideLoading();
                iBookShelf.bookInfo(collBookBean);
            }
    
    
        }
    
    private void getBooksByTag() {
        Map<String, Object> map = new HashMap<>();
        map.put("access-token", SharedPreUtils.getInstance().getString("token", "weyue"));
        map.put("app-type", "Android");
        RxHttpUtils.getSInstance().addHeaders(map).createSApi(BookService.class)
                .booksByTag(tagName, page)
                .compose(Transformer.switchSchedulers())
                .subscribe(new RxObserver<List<BookBean>>() {
                    @Override
                    protected void onError(String errorMsg) {
                        mRefreshLayout.finishLoadmore();
                    }
    
                    @Override
                    protected void onSuccess(List<BookBean> data) {
                        mRefreshLayout.finishLoadmore();
                        mBeans.addAll(data);
                        if (mBeans.size() > 0) {
                            mBookTagsAdapter.notifyDataSetChanged();
                        }
                    }
    
                    @Override
                    public void onSubscribe(Disposable d) {
                        super.onSubscribe(d);
                        mDisposable = d;
                    }
                });
    
    }
    

    自己实现功能分析

    1 :用户管理模块
    该模块主要是有用户登录,用户注册,修改密码,修改个人信息,修改头像这几个功能。
    (1)用户登录
    功能分析:用户在界面输入用户名和密码后,通过http请求后台接口,验证用户名和密码。完成整个登录流程

        @OnClick({R.id.ctv_register, R.id.fab})
        public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.ctv_register:
                    startActivityForResult(new Intent(this, RegisterActivity.class), 10000);
                    break;
                case R.id.fab:
                    String username = mActvUsername.getText().toString();
                    String password = mEtPassword.getText().toString();
    
                    if (TextUtils.isEmpty(username)) {
                        ToastUtils.show("用户名不能为空");
                        return;
                    }
                    if (TextUtils.isEmpty(password)) {
                        ToastUtils.show("密码不能为空");
                        return;
                    }
                    mModel.login(username, password);
                    break;
            }
    
    
        }
    

    (2)用户注册
    功能分析:用户在未登录的情况下,可以查阅电子书,但是无法对喜欢的电子书进行添加到书架的操作,这时,我app会自动跳转到注册页面,提示用户注册后可以使用相映的功能,当用户输入用户名和密码后,请求后台提供的接口,如果用户名存在,则注册失败,否则,注册成功,并对用户密码进行md5加密后存入数据库

        @Override
        protected void initView() {
            super.initView();
            initThemeToolBar("用户注册");
    
            mFab.setOnClickListener(v -> {
                mUsername = mActvUsername.getText().toString();
                mPassword1 = mEtPassword.getText().toString();
                String password2 = mEtPasswordConfirm.getText().toString();
                if (TextUtils.isEmpty(mUsername)) {
                    ToastUtils.show("用户名不能为空");
                    return;
                }
                if (TextUtils.isEmpty(mPassword1) || TextUtils.isEmpty(password2)) {
                    ToastUtils.show("密码不能为空");
                    return;
                }
                if (!mPassword1.equals(password2)) {
                    ToastUtils.show("两次输入密码不一样");
                    return;
                }
                mModel.register(mUsername, mPassword1);
            });
        }
    

    (3)修改密码
    功能分析:用户点击修改密码后,首先对原密码进行验证,验证成功后则修改成功。否则修改失败

    public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.fab_edit_password:
                    mFabMenu.toggle();
                    new MaterialDialog.Builder(this)
                            .title("修改用户密码")
                            .inputRange(2, 20, ThemeUtils.getThemeColor())
    //                        .inputType(InputType.TYPE_TEXT_VARIATION_PASSWORD)
                            .input("请输入新密码", null, (dialog, input) -> {
                                dialog.dismiss();
                                mModel.updatePassword(input.toString());
                            })
                            .show();
                    break;
                case R.id.fab_edit_userinfo:
                    mFabMenu.toggle();
                    startEdit();
                    break;
                case R.id.iv_avatar:
                    /**
                     * 设置内容区域为简单列表项
                     */
                    final String[] items = {"相册", "拍摄"};
                    new MaterialDialog.Builder(this)
                            .title("选择照片方式")
                            .items(items)
                            .itemsCallback((dialog, itemView, position, text) -> {
                                switch (position) {
                                    case 0:
                                        dialog.dismiss();
                                        imageUri = getImageCropUri();
                                        //从相册中选取图片并裁剪
                                        takePhoto.onPickFromGalleryWithCrop(imageUri, cropOptions);
                                        //从相册中选取不裁剪
                                        //takePhoto.onPickFromGallery();
                                        break;
                                    case 1:
                                        dialog.dismiss();
                                        imageUri = getImageCropUri();
                                        //拍照并裁剪
                                        takePhoto.onPickFromCaptureWithCrop(imageUri, cropOptions);
                                        //仅仅拍照不裁剪
                                        //takePhoto.onPickFromCapture(imageUri);
                                        break;
                                }
                            })
                            .show();
                    break;
                case R.id.btn_confirm:
                    new MaterialDialog.Builder(this)
                            .title("修改用户信息")
                            .content("是否确认修改?")
                            .negativeText("取消")
                            .onNegative((dialog, which) -> dialog.dismiss())
                            .positiveText("确定")
                            .onPositive((dialog, which) -> {
                                String nickname = mEtNickName.getText().toString();
                                String brief = mEtBrief.getText().toString();
                                if (TextUtils.isEmpty(nickname)) {
                                    ToastUtils.show("昵称不能为空");
                                    return;
                                }
                                if (TextUtils.isEmpty(brief)) {
                                    ToastUtils.show("我的格言不能为空");
                                    return;
                                }
                                stopEdit();
                                dialog.dismiss();
                                mModel.updateUserInfo(nickname, brief);
                            })
                            .show();
                    break;
            }
        }
    

    (4)修改个人信息
    功能分析:用户可以再app个人系信息界面修改自己的信息,新的信息填写完成后,点击保存,则完成整个信息的修改。

    public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.fab_edit_password:
                    mFabMenu.toggle();
                    new MaterialDialog.Builder(this)
                            .title("修改用户密码")
                            .inputRange(2, 20, ThemeUtils.getThemeColor())
    //                        .inputType(InputType.TYPE_TEXT_VARIATION_PASSWORD)
                            .input("请输入新密码", null, (dialog, input) -> {
                                dialog.dismiss();
                                mModel.updatePassword(input.toString());
                            })
                            .show();
                    break;
                case R.id.fab_edit_userinfo:
                    mFabMenu.toggle();
                    startEdit();
                    break;
                case R.id.iv_avatar:
                    /**
                     * 设置内容区域为简单列表项
                     */
                    final String[] items = {"相册", "拍摄"};
                    new MaterialDialog.Builder(this)
                            .title("选择照片方式")
                            .items(items)
                            .itemsCallback((dialog, itemView, position, text) -> {
                                switch (position) {
                                    case 0:
                                        dialog.dismiss();
                                        imageUri = getImageCropUri();
                                        //从相册中选取图片并裁剪
                                        takePhoto.onPickFromGalleryWithCrop(imageUri, cropOptions);
                                        //从相册中选取不裁剪
                                        //takePhoto.onPickFromGallery();
                                        break;
                                    case 1:
                                        dialog.dismiss();
                                        imageUri = getImageCropUri();
                                        //拍照并裁剪
                                        takePhoto.onPickFromCaptureWithCrop(imageUri, cropOptions);
                                        //仅仅拍照不裁剪
                                        //takePhoto.onPickFromCapture(imageUri);
                                        break;
                                }
                            })
                            .show();
                    break;
                case R.id.btn_confirm:
                    new MaterialDialog.Builder(this)
                            .title("修改用户信息")
                            .content("是否确认修改?")
                            .negativeText("取消")
                            .onNegative((dialog, which) -> dialog.dismiss())
                            .positiveText("确定")
                            .onPositive((dialog, which) -> {
                                String nickname = mEtNickName.getText().toString();
                                String brief = mEtBrief.getText().toString();
                                if (TextUtils.isEmpty(nickname)) {
                                    ToastUtils.show("昵称不能为空");
                                    return;
                                }
                                if (TextUtils.isEmpty(brief)) {
                                    ToastUtils.show("我的格言不能为空");
                                    return;
                                }
                                stopEdit();
                                dialog.dismiss();
                                mModel.updateUserInfo(nickname, brief);
                            })
                            .show();
                    break;
            }
        }
    

    (5)修改头像
    功能分析:用户可以选择自己喜欢的头像,点击头像后会提示用户选择新的照片作为自己的头像,提交后保存到数据库,完成整个模块的修改

    public void onViewClicked(View view) {
            switch (view.getId()) {
                case R.id.fab_edit_password:
                    mFabMenu.toggle();
                    new MaterialDialog.Builder(this)
                            .title("修改用户密码")
                            .inputRange(2, 20, ThemeUtils.getThemeColor())
    //                        .inputType(InputType.TYPE_TEXT_VARIATION_PASSWORD)
                            .input("请输入新密码", null, (dialog, input) -> {
                                dialog.dismiss();
                                mModel.updatePassword(input.toString());
                            })
                            .show();
                    break;
                case R.id.fab_edit_userinfo:
                    mFabMenu.toggle();
                    startEdit();
                    break;
                case R.id.iv_avatar:
                    /**
                     * 设置内容区域为简单列表项
                     */
                    final String[] items = {"相册", "拍摄"};
                    new MaterialDialog.Builder(this)
                            .title("选择照片方式")
                            .items(items)
                            .itemsCallback((dialog, itemView, position, text) -> {
                                switch (position) {
                                    case 0:
                                        dialog.dismiss();
                                        imageUri = getImageCropUri();
                                        //从相册中选取图片并裁剪
                                        takePhoto.onPickFromGalleryWithCrop(imageUri, cropOptions);
                                        //从相册中选取不裁剪
                                        //takePhoto.onPickFromGallery();
                                        break;
                                    case 1:
                                        dialog.dismiss();
                                        imageUri = getImageCropUri();
                                        //拍照并裁剪
                                        takePhoto.onPickFromCaptureWithCrop(imageUri, cropOptions);
                                        //仅仅拍照不裁剪
                                        //takePhoto.onPickFromCapture(imageUri);
                                        break;
                                }
                            })
                            .show();
                    break;
                case R.id.btn_confirm:
                    new MaterialDialog.Builder(this)
                            .title("修改用户信息")
                            .content("是否确认修改?")
                            .negativeText("取消")
                            .onNegative((dialog, which) -> dialog.dismiss())
                            .positiveText("确定")
                            .onPositive((dialog, which) -> {
                                String nickname = mEtNickName.getText().toString();
                                String brief = mEtBrief.getText().toString();
                                if (TextUtils.isEmpty(nickname)) {
                                    ToastUtils.show("昵称不能为空");
                                    return;
                                }
                                if (TextUtils.isEmpty(brief)) {
                                    ToastUtils.show("我的格言不能为空");
                                    return;
                                }
                                stopEdit();
                                dialog.dismiss();
                                mModel.updateUserInfo(nickname, brief);
                            })
                            .show();
                    break;
            }
        }
    

    电子书模块

    该模块主要是从互联网上获取电子书资源

    (1)获取追书神器url
    功能分析:该功能主要是通fiddler抓取追书神器的网络请求 由此可以获取到电子书相关的url
    (2)数据解析
    功能分析:该功能主要是通过http请求获取抓取到的url请求中的数据,然后封装成java对象,用户页面上内容的展示,格式为json
    /**
    * 1、判断本地数据库有没有收藏书籍的数据。
    * 2、本地数据库没有收藏书籍数据就网络请求。否则就取本地数据
    *
    * @param collBookBean
    */

        public void setBookInfo(CollBookBean collBookBean) {
            LoadingHelper.getInstance().showLoading(mContext);
            if (CollBookHelper.getsInstance().findBookById(collBookBean.get_id()) == null) {
                RxHttpUtils.getSInstance().addHeaders(tokenMap()).createSApi(BookService.class)
                        .bookChapters(collBookBean.get_id())
                        .compose(Transformer.switchSchedulers())
                        .subscribe(new RxObserver<BookChaptersBean>() {
                            @Override
                            protected void onError(String errorMsg) {
                                LoadingHelper.getInstance().hideLoading();
                            }
    
                            @Override
                            protected void onSuccess(BookChaptersBean data) {
                                LoadingHelper.getInstance().hideLoading();
                                List<BookChapterBean> bookChapterList = new ArrayList<>();
                                for (BookChaptersBean.ChatpterBean bean : data.getChapters()) {
                                    BookChapterBean chapterBean = new BookChapterBean();
                                    chapterBean.setBookId(data.getBook());
                                    chapterBean.setLink(bean.getLink());
                                    chapterBean.setTitle(bean.getTitle());
    //                                chapterBean.setTaskName("下载");
                                    chapterBean.setUnreadble(bean.isRead());
                                    bookChapterList.add(chapterBean);
                                }
                                collBookBean.setBookChapters(bookChapterList);
                                CollBookHelper.getsInstance().saveBookWithAsync(collBookBean);
                                iBookShelf.bookInfo(collBookBean);
                            }
    
                            @Override
                            public void onSubscribe(Disposable d) {
                                addDisposadle(d);
                            }
                        });
            } else {
                LoadingHelper.getInstance().hideLoading();
                iBookShelf.bookInfo(collBookBean);
            }
    
        public void bookClassify() {
            if (!NetworkUtils.isConnected()) {
                if (mIBookClassify != null) {
                    mIBookClassify.NetWorkError();
                }
                return;
            }
    
            RxHttpUtils.getSInstance().addHeaders(tokenMap()).createSApi(BookService.class)
           /* RxHttpUtils.createApi(BookService.class)*/
                    .bookClassify()
                    .compose(Transformer.switchSchedulers())
                    .subscribe(new RxObserver<BookClassifyBean>() {
                        @Override
                        protected void onError(String errorMsg) {
                            if (mIBookClassify != null) {
                                mIBookClassify.stopLoading();
                                mIBookClassify.errorData(errorMsg);
                            }
                        }
    
                        @Override
                        protected void onSuccess(BookClassifyBean data) {
                            if (mIBookClassify != null) {
                                mIBookClassify.stopLoading();
                                if (data == null) {
                                    mIBookClassify.emptyData();
                                    return;
                                }
                                mIBookClassify.getBookClassify(data);
                            }
    
    
                        }
    
                        @Override
                        public void onSubscribe(Disposable d) {
                            addDisposadle(d);
                        }
                    });
        }
    

    队友项目的app运行结果

    别踩白块

    推箱子

  • 相关阅读:
    django:开发一个下载图片的接口
    django:cbv模式和fbv模式的区别
    java生成二维码/java解析二维码
    java如何台生成二维码详解
    html5 WebSocket的Js实例教程
    vue 组件传参
    Vue路由注意事项
    Vue全家桶
    浅谈vue对seo的影响
    vue打包详情
  • 原文地址:https://www.cnblogs.com/yuchao123/p/10877459.html
Copyright © 2020-2023  润新知