zoukankan      html  css  js  c++  java
  • 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运行结果

    别踩白块

    推箱子

  • 相关阅读:
    LeetCode——Generate Parentheses
    LeetCode——Best Time to Buy and Sell Stock IV
    LeetCode——Best Time to Buy and Sell Stock III
    LeetCode——Best Time to Buy and Sell Stock
    LeetCode——Find Minimum in Rotated Sorted Array
    Mahout实现基于用户的协同过滤算法
    使用Java对文件进行解压缩
    LeetCode——Convert Sorted Array to Binary Search Tree
    LeetCode——Missing Number
    LeetCode——Integer to Roman
  • 原文地址:https://www.cnblogs.com/yuchao123/p/10877459.html
Copyright © 2011-2022 走看看