小说阅读器java
注册登陆
用户信息、用户密码、用户图像修改
书籍分类
书架
书籍搜索(做者名或书籍名)
书籍阅读(仅txt格式,暂不支持PDF等其余格式)
阅读字体、背景颜色、翻页效果等设置
意见反馈(反馈信息发送到个人邮箱)数据库
本项目主要是一个小说阅读软件,我所作的小说阅读软件是一个能够根据用户的阅读兴趣、爱好、来进行完成的。第一,个人项目功能里面有注册用户的,用户能够进行注册和登陆。第二,用户信息功能,用户能够查看本身的信息,修改本身的密码,修改用户图像等。第三,我还根据书的特色进行了书架的分类,分为了玄幻、奇幻、武侠、仙侠、都市、职场、历史、军事等。第四,书籍搜索功能,也便是用户能够根据小说的名字或者做者来进行搜索相应的小说。第五,书籍的阅读仅限于txt格式的阅读。第六,用户本身能够设置用户的阅读字体、背景颜色、翻页效果等功能。第七,用户能够进行信息的反馈,且发送到个人邮箱里面。json
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 -在基础上修改api
1-1代码模块截图网络
Java代码
app
资源代码
框架
配置文件代码
ide
以上是本项目的总体代码结构工具
1-2代码模块分析
api:该包主要是用于网络接口的代码
db:该包主要是数据库链接,数据存储代码
event:该包主要是用于事件回调后的通知
Intenfces:该包主要是用于接口的编写
Model:该包主要是用于实体类的编写,如小说,用户等实体类
Util:该包主要是用于工具类代码的编写
View:该包主要是用于app界面的编写
Viewmodel:该包主要是用于界面数据显示的实体
Widget:该包用于app界面装置代码的编写单元测试
Anim:主要存放自定义按钮样式
Color:主要是用于存放颜色代码
Drawable:主要是用于存放图片等静态文件
Layout:主要是用于存放界面的代码
Mipmap-*:用于适配各类分辨的图标
Raw:存放文件 如txt
Values:存放各类文本变量
Xml:存放配置文件 如文件路径等
主要使用Android mvp开发模式进行开发,该模式有如下几个优势
一、模型与视图彻底分离,咱们能够修改视图而不影响模型
二、能够更高效地使用模型,由于全部的交互都发生在一个地方——Presenter内部
三、咱们能够将一个Presenter用于多个视图,而不须要改变Presenter的逻辑。这个特性很是的有用,由于视图的变化老是比模型的变化频繁。
四、若是咱们把逻辑放在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
/**
* 一、判断本地数据库有没有收藏书籍的数据。
* 二、本地数据库没有收藏书籍数据就网络请求。不然就取本地数据
@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); } }); }
别踩白块
推箱子