/** * Copyright 2016 JustWayward Team * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.justwayward.reader.view.readview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.widget.ProgressBar; import com.justwayward.reader.R; import com.justwayward.reader.bean.BookMixAToc; import com.justwayward.reader.manager.SettingManager; import com.justwayward.reader.utils.AppUtils; import com.justwayward.reader.utils.FileUtils; import com.justwayward.reader.utils.LogUtils; import com.justwayward.reader.utils.ScreenUtils; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Vector; public class PageFactory { private Context mContext; /** * 屏幕宽高 */ private int mHeight, mWidth; /** * 文字区域宽高 */ private int mVisibleHeight, mVisibleWidth; /** * 间距 */ private int marginHeight, marginWidth; /** * 字体大小 */ private int mFontSize, mNumFontSize; /** * 每页行数 */ private int mPageLineCount; /** * 行间距 **/ private int mLineSpace; /** * 字节长度 */ private int mbBufferLen; /** * MappedByteBuffer:高效的文件内存映射 */ private MappedByteBuffer mbBuff; /** * 页首页尾的位置 */ private int curEndPos = 0, curBeginPos = 0, tempBeginPos, tempEndPos; private int currentChapter, tempChapter; private Vector<String> mLines = new Vector<>(); private Paint mPaint; private Paint mTitlePaint; private Bitmap mBookPageBg; private DecimalFormat decimalFormat = new DecimalFormat("#0.00"); private SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm"); private int timeLen = 0, percentLen = 0; private String time; private int battery = 40; private Rect rectF; private ProgressBar batteryView; private Bitmap batteryBitmap; private String bookId; private List<BookMixAToc.mixToc.Chapters> chaptersList; private int chapterSize = 0; private int currentPage = 1; private OnReadStateChangeListener listener; private String charset = "UTF-8"; public PageFactory(Context context, String bookId, List<BookMixAToc.mixToc.Chapters> chaptersList) { this(context, ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight(), //SettingManager.getInstance().getReadFontSize(bookId), SettingManager.getInstance().getReadFontSize(), bookId, chaptersList); } public PageFactory(Context context, int width, int height, int fontSize, String bookId, List<BookMixAToc.mixToc.Chapters> chaptersList) { mContext = context; mWidth = width; mHeight = height; mFontSize = fontSize; mLineSpace = mFontSize / 5 * 2; mNumFontSize = ScreenUtils.dpToPxInt(16); marginWidth = ScreenUtils.dpToPxInt(15); marginHeight = ScreenUtils.dpToPxInt(15); mVisibleHeight = mHeight - marginHeight * 2 - mNumFontSize * 2 - mLineSpace * 2; mVisibleWidth = mWidth - marginWidth * 2; mPageLineCount = mVisibleHeight / (mFontSize + mLineSpace); rectF = new Rect(0, 0, mWidth, mHeight); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mPaint.setTextSize(mFontSize); mPaint.setTextSize(ContextCompat.getColor(context, R.color.chapter_content_day)); mPaint.setColor(Color.BLACK); mTitlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); mTitlePaint.setTextSize(mNumFontSize); mTitlePaint.setColor(ContextCompat.getColor(AppUtils.getAppContext(), R.color.chapter_title_day)); timeLen = (int) mTitlePaint.measureText("00:00"); percentLen = (int) mTitlePaint.measureText("00.00%"); // Typeface typeface = Typeface.createFromAsset(context.getAssets(),"fonts/FZBYSK.TTF"); // mPaint.setTypeface(typeface); // mNumPaint.setTypeface(typeface); this.bookId = bookId; this.chaptersList = chaptersList; time = dateFormat.format(new Date()); } public File getBookFile(int chapter) { File file = FileUtils.getChapterFile(bookId, chapter); if (file != null && file.length() > 10) { // 解决空文件造成编码错误的问题 charset = FileUtils.getCharset(file.getAbsolutePath()); } LogUtils.i("charset=" + charset); return file; } public void openBook() { openBook(new int[]{0, 0}); } public void openBook(int[] position) { openBook(1, position); } /** * 打开书籍文件 * * @param chapter 阅读章节 * @param position 阅读位置 * @return 0:文件不存在或打开失败 1:打开成功 */ public int openBook(int chapter, int[] position) { this.currentChapter = chapter; this.chapterSize = chaptersList.size(); if (currentChapter > chapterSize) currentChapter = chapterSize; String path = getBookFile(currentChapter).getPath(); try { File file = new File(path); long length = file.length(); if (length > 10) { mbBufferLen = (int) length; // 创建文件通道,映射为MappedByteBuffer mbBuff = new RandomAccessFile(file, "r") .getChannel() .map(FileChannel.MapMode.READ_ONLY, 0, length); curBeginPos = position[0]; curEndPos = position[1]; onChapterChanged(chapter); mLines.clear(); return 1; } } catch (IOException e) { e.printStackTrace(); } return 0; } /** * 绘制阅读页面 * * @param canvas */ public synchronized void onDraw(Canvas canvas) { if (mLines.size() == 0) { curEndPos = curBeginPos; mLines = pageDown(); } if (mLines.size() > 0) { int y = marginHeight + (mLineSpace << 1); // 绘制背景 if (mBookPageBg != null) { canvas.drawBitmap(mBookPageBg, null, rectF, null); } else { canvas.drawColor(Color.WHITE); } // 绘制标题 canvas.drawText(chaptersList.get(currentChapter - 1).title, marginWidth, y, mTitlePaint); y += mLineSpace + mNumFontSize; // 绘制阅读页面文字 for (String line : mLines) { y += mLineSpace; if (line.endsWith("@")) { canvas.drawText(line.substring(0, line.length() - 1), marginWidth, y, mPaint); y += mLineSpace; } else { canvas.drawText(line, marginWidth, y, mPaint); } y += mFontSize; } // 绘制提示内容 if (batteryBitmap != null) { canvas.drawBitmap(batteryBitmap, marginWidth + 2, mHeight - marginHeight - ScreenUtils.dpToPxInt(12), mTitlePaint); } float percent = (float) currentChapter * 100 / chapterSize; canvas.drawText(decimalFormat.format(percent) + "%", (mWidth - percentLen) / 2, mHeight - marginHeight, mTitlePaint); String mTime = dateFormat.format(new Date()); canvas.drawText(mTime, mWidth - marginWidth - timeLen, mHeight - marginHeight, mTitlePaint); // 保存阅读进度 SettingManager.getInstance().saveReadProgress(bookId, currentChapter, curBeginPos, curEndPos); } } /** * 指针移到上一页页首 */ private void pageUp() { String strParagraph = ""; Vector<String> lines = new Vector<>(); // 页面行 int paraSpace = 0; mPageLineCount = mVisibleHeight / (mFontSize + mLineSpace); while ((lines.size() < mPageLineCount) && (curBeginPos > 0)) { Vector<String> paraLines = new Vector<>(); // 段落行 byte[] parabuffer = readParagraphBack(curBeginPos); // 1.读取上一个段落 curBeginPos -= parabuffer.length; // 2.变换起始位置指针 try { strParagraph = new String(parabuffer, charset); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } strParagraph = strParagraph.replaceAll("\r\n", " "); strParagraph = strParagraph.replaceAll("\n", " "); while (strParagraph.length() > 0) { // 3.逐行添加到lines int paintSize = mPaint.breakText(strParagraph, true, mVisibleWidth, null); paraLines.add(strParagraph.substring(0, paintSize)); strParagraph = strParagraph.substring(paintSize); } lines.addAll(0, paraLines); while (lines.size() > mPageLineCount) { // 4.如果段落添加完,但是超出一页,则超出部分需删减 try { curBeginPos += lines.get(0).getBytes(charset).length; // 5.删减行数同时起始位置指针也要跟着偏移 lines.remove(0); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } curEndPos = curBeginPos; // 6.最后结束指针指向下一段的开始处 paraSpace += mLineSpace; mPageLineCount = (mVisibleHeight - paraSpace) / (mFontSize + mLineSpace); // 添加段落间距,实时更新容纳行数 } } /** * 根据起始位置指针,读取一页内容 * * @return */ private Vector<String> pageDown() { String strParagraph = ""; Vector<String> lines = new Vector<>(); int paraSpace = 0; mPageLineCount = mVisibleHeight / (mFontSize + mLineSpace); while ((lines.size() < mPageLineCount) && (curEndPos < mbBufferLen)) { byte[] parabuffer = readParagraphForward(curEndPos); curEndPos += parabuffer.length; try { strParagraph = new String(parabuffer, charset); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } strParagraph = strParagraph.replaceAll("\r\n", " ") .replaceAll("\n", " "); // 段落中的换行符去掉,绘制的时候再换行 while (strParagraph.length() > 0) { int paintSize = mPaint.breakText(strParagraph, true, mVisibleWidth, null); lines.add(strParagraph.substring(0, paintSize)); strParagraph = strParagraph.substring(paintSize); if (lines.size() >= mPageLineCount) { break; } } lines.set(lines.size() - 1, lines.get(lines.size() - 1) + "@"); if (strParagraph.length() != 0) { try { curEndPos -= (strParagraph).getBytes(charset).length; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } paraSpace += mLineSpace; mPageLineCount = (mVisibleHeight - paraSpace) / (mFontSize + mLineSpace); } return lines; } /** * 获取最后一页的内容。比较繁琐,待优化 * * @return */ public Vector<String> pageLast() { String strParagraph = ""; Vector<String> lines = new Vector<>(); currentPage = 0; while (curEndPos < mbBufferLen) { int paraSpace = 0; mPageLineCount = mVisibleHeight / (mFontSize + mLineSpace); curBeginPos = curEndPos; while ((lines.size() < mPageLineCount) && (curEndPos < mbBufferLen)) { byte[] parabuffer = readParagraphForward(curEndPos); curEndPos += parabuffer.length; try { strParagraph = new String(parabuffer, charset); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } strParagraph = strParagraph.replaceAll("\r\n", " "); strParagraph = strParagraph.replaceAll("\n", " "); // 段落中的换行符去掉,绘制的时候再换行 while (strParagraph.length() > 0) { int paintSize = mPaint.breakText(strParagraph, true, mVisibleWidth, null); lines.add(strParagraph.substring(0, paintSize)); strParagraph = strParagraph.substring(paintSize); if (lines.size() >= mPageLineCount) { break; } } lines.set(lines.size() - 1, lines.get(lines.size() - 1) + "@"); if (strParagraph.length() != 0) { try { curEndPos -= (strParagraph).getBytes(charset).length; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } paraSpace += mLineSpace; mPageLineCount = (mVisibleHeight - paraSpace) / (mFontSize + mLineSpace); } if (curEndPos < mbBufferLen) { lines.clear(); } currentPage++; } //SettingManager.getInstance().saveReadProgress(bookId, currentChapter, curBeginPos, curEndPos); return lines; } /** * 读取下一段落 * * @param curEndPos 当前页结束位置指针 * @return */ private byte[] readParagraphForward(int curEndPos) { byte b0; int i = curEndPos; while (i < mbBufferLen) { b0 = mbBuff.get(i++); if (b0 == 0x0a) { break; } } int nParaSize = i - curEndPos; byte[] buf = new byte[nParaSize]; for (i = 0; i < nParaSize; i++) { buf[i] = mbBuff.get(curEndPos + i); } return buf; } /** * 读取上一段落 * * @param curBeginPos 当前页起始位置指针 * @return */ private byte[] readParagraphBack(int curBeginPos) { byte b0; int i = curBeginPos - 1; while (i > 0) { b0 = mbBuff.get(i); if (b0 == 0x0a && i != curBeginPos - 1) { i++; break; } i--; } int nParaSize = curBeginPos - i; byte[] buf = new byte[nParaSize]; for (int j = 0; j < nParaSize; j++) { buf[j] = mbBuff.get(i + j); } return buf; } public boolean hasNextPage() { return currentChapter < chaptersList.size() || curEndPos < mbBufferLen; } public boolean hasPrePage() { return currentChapter > 1 || (currentChapter == 1 && curBeginPos > 0); } /** * 跳转下一页 */ public BookStatus nextPage() { if (!hasNextPage()) { // 最后一章的结束页 return BookStatus.NO_NEXT_PAGE; } else { tempChapter = currentChapter; tempBeginPos = curBeginPos; tempEndPos = curEndPos; if (curEndPos >= mbBufferLen) { // 中间章节结束页 currentChapter++; int ret = openBook(currentChapter, new int[]{0, 0}); // 打开下一章 if (ret == 0) { onLoadChapterFailure(currentChapter); currentChapter--; curBeginPos = tempBeginPos; curEndPos = tempEndPos; return BookStatus.NEXT_CHAPTER_LOAD_FAILURE; } else { currentPage = 0; onChapterChanged(currentChapter); } } else { curBeginPos = curEndPos; // 起始指针移到结束位置 } mLines.clear(); mLines = pageDown(); // 读取一页内容 onPageChanged(currentChapter, ++currentPage); } return BookStatus.LOAD_SUCCESS; } /** * 跳转上一页 */ public BookStatus prePage() { if (!hasPrePage()) { // 第一章第一页 return BookStatus.NO_PRE_PAGE; } else { // 保存当前页的值 tempChapter = currentChapter; tempBeginPos = curBeginPos; tempEndPos = curEndPos; if (curBeginPos <= 0) { currentChapter--; int ret = openBook(currentChapter, new int[]{0, 0}); if (ret == 0) { onLoadChapterFailure(currentChapter); currentChapter++; return BookStatus.PRE_CHAPTER_LOAD_FAILURE; } else { // 跳转到上一章的最后一页 mLines.clear(); mLines = pageLast(); onChapterChanged(currentChapter); onPageChanged(currentChapter, currentPage); return BookStatus.LOAD_SUCCESS; } } mLines.clear(); pageUp(); // 起始指针移到上一页开始处 mLines = pageDown(); // 读取一页内容 onPageChanged(currentChapter, --currentPage); } return BookStatus.LOAD_SUCCESS; } public void cancelPage() { currentChapter = tempChapter; curBeginPos = tempBeginPos; curEndPos = curBeginPos; int ret = openBook(currentChapter, new int[]{curBeginPos, curEndPos}); if (ret == 0) { onLoadChapterFailure(currentChapter); return; } mLines.clear(); mLines = pageDown(); } /** * 获取当前阅读位置 * * @return index 0:起始位置 1:结束位置 */ public int[] getPosition() { return new int[]{currentChapter, curBeginPos, curEndPos}; } public String getHeadLineStr() { if (mLines != null && mLines.size() > 1) { return mLines.get(0); } return ""; } /** * 设置字体大小 * * @param fontsize 单位:px */ public void setTextFont(int fontsize) { LogUtils.i("fontSize=" + fontsize); mFontSize = fontsize; mLineSpace = mFontSize / 5 * 2; mPaint.setTextSize(mFontSize); mPageLineCount = mVisibleHeight / (mFontSize + mLineSpace); curEndPos = curBeginPos; nextPage(); } /** * 设置字体颜色 * * @param textColor * @param titleColor */ public void setTextColor(int textColor, int titleColor) { mPaint.setColor(textColor); mTitlePaint.setColor(titleColor); } public int getTextFont() { return mFontSize; } /** * 根据百分比,跳到目标位置 * * @param persent */ public void setPercent(int persent) { float a = (float) (mbBufferLen * persent) / 100; curEndPos = (int) a; if (curEndPos == 0) { nextPage(); } else { nextPage(); prePage(); nextPage(); } } public void setBgBitmap(Bitmap BG) { mBookPageBg = BG; } public void setOnReadStateChangeListener(OnReadStateChangeListener listener) { this.listener = listener; } private void onChapterChanged(int chapter) { if (listener != null) listener.onChapterChanged(chapter); } private void onPageChanged(int chapter, int page) { if (listener != null) listener.onPageChanged(chapter, page); } private void onLoadChapterFailure(int chapter) { if (listener != null) listener.onLoadChapterFailure(chapter); } public void convertBetteryBitmap() { batteryView = (ProgressBar) LayoutInflater.from(mContext).inflate(R.layout.layout_battery_progress, null); batteryView.setProgressDrawable(ContextCompat.getDrawable(mContext, SettingManager.getInstance().getReadTheme() < 4 ? R.drawable.seekbar_battery_bg : R.drawable.seekbar_battery_night_bg)); batteryView.setProgress(battery); batteryView.setDrawingCacheEnabled(true); batteryView.measure(View.MeasureSpec.makeMeasureSpec(ScreenUtils.dpToPxInt(26), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(ScreenUtils.dpToPxInt(14), View.MeasureSpec.EXACTLY)); batteryView.layout(0, 0, batteryView.getMeasuredWidth(), batteryView.getMeasuredHeight()); batteryView.buildDrawingCache(); //batteryBitmap = batteryView.getDrawingCache(); // tips: @link{https://github.com/JustWayward/BookReader/issues/109} batteryBitmap = Bitmap.createBitmap(batteryView.getDrawingCache()); batteryView.setDrawingCacheEnabled(false); batteryView.destroyDrawingCache(); } public void setBattery(int battery) { this.battery = battery; convertBetteryBitmap(); } public void setTime(String time) { this.time = time; } public void recycle() { if (mBookPageBg != null && !mBookPageBg.isRecycled()) { mBookPageBg.recycle(); mBookPageBg = null; LogUtils.d("mBookPageBg recycle"); } if (batteryBitmap != null && !batteryBitmap.isRecycled()) { batteryBitmap.recycle(); batteryBitmap = null; LogUtils.d("batteryBitmap recycle"); } } }