Java tutorial
/** * 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.apache.fastandroid.novel.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.apache.fastandroid.novel.R; import com.apache.fastandroid.novel.find.bean.BookMixAToc; import com.apache.fastandroid.novel.support.mananger.SettingManager; import com.apache.fastandroid.novel.support.util.ChapterFileUtil; import com.apache.fastandroid.novel.support.util.NovelLog; import com.tesla.framework.applike.FrameworkApplication; import com.tesla.framework.common.util.dimen.DimensUtil; import com.tesla.framework.common.util.dimen.ScreenUtil; 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, ScreenUtil.getScreenWidth(FrameworkApplication.getContext()), ScreenUtil.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 = DimensUtil.dp2px(context, 16); marginWidth = DimensUtil.dp2px(context, 15); marginHeight = DimensUtil.dp2px(context, 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(FrameworkApplication.getContext(), 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 = ChapterFileUtil.getChapterFile(bookId, chapter); if (file != null && file.length() > 10) { // ?? charset = ChapterFileUtil.getCharset(file.getAbsolutePath()); } NovelLog.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 - DimensUtil.dp2px(mContext, 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) { NovelLog.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(DimensUtil.dp2px(mContext, 26), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(DimensUtil.dp2px(mContext, 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; NovelLog.d("mBookPageBg recycle"); } if (batteryBitmap != null && !batteryBitmap.isRecycled()) { batteryBitmap.recycle(); batteryBitmap = null; NovelLog.d("batteryBitmap recycle"); } } }