小說閱讀器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); } }); }
別踩白塊
推箱子