网络资源模板--基于Android Studio 实现的小说阅读App

目录

一、测试环境说明

二、项目简介

三、项目演示

四、部设计详情(部分))

小说页面

五、项目源码


一、测试环境说明

电脑环境

Windows 11

编写语言

JAVA

开发软件

Android Studio (2020)

开发软件只要大于等于测试版本即可(近几年官网直接下载也可以),若是版本低于测试版本请自行测试。项目需要根据你的软件自行适配

二、项目简介

该项目简介来自网络,具体内容需要自行测试

本项目是一个基于Android平台的电子书阅读应用,采用Java语言开发,使用Android Studio作为开发工具。应用实现了完整的电子书阅读功能,包括启动页倒计时、书籍列表展示、在线阅读等功能模块。系统采用MVC架构设计,通过异步网络请求获取书籍数据,使用本地文件缓存机制存储下载的书籍内容,并实现了流畅的翻页效果。

该应用的主要特点包括:精美的启动页倒计时动画、清晰的书籍分类列表、支持在线下载和本地阅读两种模式、自定义翻页效果、阅读进度保存等功能。在技术实现上,项目运用了Handler消息机制、AsyncHttpClient网络请求框架、Gson数据解析、文件读写操作等关键技术。阅读界面实现了基于贝塞尔曲线的仿真翻页效果,提供接近真实书籍的阅读体验。应用还考虑了内存管理问题,使用WeakReference防止内存泄漏,优化了大文件读取性能。

该项目由编程乐学团队介入,优化布局完善功能

三、项目演示

网络资源模板--基于Android studio 小说阅读App

四、部设计详情(部分)

小说页面

  1. 页面的结构

小说阅读页面采用全屏沉浸式设计,顶部预留状态栏空间,中间为主要内容显示区域,底部显示阅读进度百分比。

页面左右两侧各设置20%宽度为触控翻页区域,中央60%为内容展示区。

背景使用单一纯色,与文字形成鲜明对比确保可读性。整个界面去除了所有非必要元素,最大化内容展示空间,提供专注的阅读环境。

  1. 使用到的技术

该页面采用贝塞尔曲线算法实现仿真翻页效果,通过计算触摸点位置动态生成翻页动画。

使用内存映射技术高效读取大文本文件,避免内存溢出。采用双缓冲绘图技术消除页面刷新时的闪烁现象。

通过精确的触摸事件处理识别用户翻页手势。后台线程预加载机制确保翻页流畅不卡顿,提升用户体验。

  1. 页面详细介绍

阅读页提供完整的电子书浏览功能,支持左右滑动翻页操作。智能分页系统根据屏幕尺寸自动计算每页显示内容量。

阅读进度自动保存功能精确记录用户最后阅读位置。页面背景采用单一配色方案,文字与背景对比度经过优化确保阅读舒适性。

翻页动画模拟真实书籍的物理特性,包含自然的弯曲效果和阴影变化,提供沉浸式阅读体验。

java 复制代码
package com.example.book.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Vector;

public class BookPageBezierHelper {

    public static final String GBK = "GBK";
    public static final String UTF_8 = "UTF-8";
    public static final String UNICODE = "Unicode";
    public static final String UTF_16_BE = "UTF-16BE";
    public static final String UTF_16_LE = "UTF-16LE";
    private int mWidth;
    private int mHeight;
    private File mBookFile;
    private int mBookBufferLength;
    private MappedByteBuffer mBookBuffer;
    private String mBookCharsetName = GBK;
    private int mPageLineCount; // 每页可以显示的行数
    private int mBufferBegin;
    private int mBufferEnd;
    private float mVisibleHeight; // 绘制内容的高
    private float mVisibleWidth; // 绘制内容的宽
    private Paint mPaint;
    private boolean mIsFirstPage, mIsLastPage;
    private Vector<String> mLinesVector = new Vector<>();
    private boolean mIsUserBg = false;

    private OnProgressChangedListener onProgressChangedListener;

    private Bitmap mBookBgBitmap;
    private int mBackGroundColor = 0xffff9e85; // 背景颜色
    private int mMarginWidth = 35; // 左右与边缘的距离
    private int mMarginHeight = 80; // 上下与边缘的距离
    private int mFontSize = 60;
    private int mLineMargin = 5;
    private int mTextColor = Color.BLACK;

    public BookPageBezierHelper(int width, int height, int progress) {
        this(width, height, Color.WHITE, Color.BLACK, 50, 60, progress);
    }

    public BookPageBezierHelper(int width, int height, int backGroundColor, int textColor, int lineMargin, int fontSize, int progress) {
        mWidth = width;
        mHeight = height;
        mTextColor = textColor;
        mBackGroundColor = backGroundColor;
        mLineMargin = lineMargin;
        mFontSize = fontSize;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setTextAlign(Paint.Align.LEFT);
        mPaint.setTextSize(mFontSize);
        mPaint.setColor(mTextColor);
        mPaint.setTypeface(null);
        mVisibleWidth = mWidth - mMarginWidth * 2;
        mVisibleHeight = mHeight - mMarginHeight * 2 + 108;
        mPageLineCount = (int) (mVisibleHeight / (mFontSize + mLineMargin)); // 可显示的行数
        mBufferBegin = mBufferEnd = progress;
    }

    /**
     * 打开文件
     *
     * @param filePath file路径
     * @throws IOException
     */
    public void openBook(String filePath) throws IOException {
        mBookFile = new File(filePath);
        long length = mBookFile.length();
        mBookBufferLength = (int) length;
        mBookBuffer = new RandomAccessFile(mBookFile, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, length);
        mBookCharsetName = codeString(filePath);
    }


    /**
     * 判断文件的编码格式
     *
     * @param fileName :file
     * @return 文件编码格式
     */
    public static String codeString(String fileName) {
        BufferedInputStream bufferedInputStream;
        int fileCode = 0;
        String code;
        try {
            bufferedInputStream = new BufferedInputStream(new FileInputStream(fileName));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            bufferedInputStream = null;
        }
        if (bufferedInputStream != null) {
            try {
                fileCode = (bufferedInputStream.read() << 8) + bufferedInputStream.read();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        switch (fileCode) {
            case 0xefbb:
                code = UTF_8;
                break;
            case 0xfffe:
                code = UNICODE;
                break;
            case 0xfeff:
                code = UTF_16_BE;
                break;
            default:
                code = GBK;
        }
        return code;
    }


    /**
     * 读取后一段落
     *
     * @return
     */
    protected byte[] readParagraphBack(int nextPosition) {
        int end = nextPosition;
        int i;
        byte b0, b1;
        if (mBookCharsetName.equals(UTF_16_LE)) {
            i = end - 2;
            while (i > 0) {
                b0 = mBookBuffer.get(i);
                b1 = mBookBuffer.get(i + 1);
                if (b0 == 0x0a && b1 == 0x00 && i != end - 2) {
                    i += 2;
                    break;
                }
                i--;
            }

        } else if (mBookCharsetName.equals(UTF_16_BE)) {
            i = end - 2;
            while (i > 0) {
                b0 = mBookBuffer.get(i);
                b1 = mBookBuffer.get(i + 1);
                if (b0 == 0x00 && b1 == 0x0a && i != end - 2) {
                    i += 2;
                    break;
                }
                i--;
            }
        } else {
            i = end - 1;
            while (i > 0) {
                b0 = mBookBuffer.get(i);
                if (b0 == 0x0a && i != end - 1) {
                    i++;
                    break;
                }
                i--;
            }
        }
        if (i < 0)
            i = 0;
        int nParaSize = end - i;
        int j;
        byte[] buf = new byte[nParaSize];
        for (j = 0; j < nParaSize; j++) {
            buf[j] = mBookBuffer.get(i + j);
        }
        return buf;
    }


    /**
     * 读取前一段落
     *
     * @param previousPosition
     * @return
     */
    protected byte[] readParagraphForward(int previousPosition) {
        int start = previousPosition;
        int i = start;
        byte b0, b1;
        // 根据编码格式判断换行
        if (mBookCharsetName.equals(UTF_16_LE)) {
            while (i < mBookBufferLength - 1) {
                b0 = mBookBuffer.get(i++);
                b1 = mBookBuffer.get(i++);
                if (b0 == 0x0a && b1 == 0x00) {
                    break;
                }
            }
        } else if (mBookCharsetName.equals(UTF_16_BE)) {
            while (i < mBookBufferLength - 1) {
                b0 = mBookBuffer.get(i++);
                b1 = mBookBuffer.get(i++);
                if (b0 == 0x00 && b1 == 0x0a) {
                    break;
                }
            }
        } else {
            while (i < mBookBufferLength) {
                b0 = mBookBuffer.get(i++);
                if (b0 == 0x0a) {
                    break;
                }
            }
        }
        int nParaSize = i - start;
        byte[] buf = new byte[nParaSize];
        for (i = 0; i < nParaSize; i++) {
            buf[i] = mBookBuffer.get(previousPosition + i);
        }
        return buf;
    }


    protected Vector<String> pageDown() {
        String strParagraph = "";
        Vector<String> lines = new Vector<>();
        // 一段一段的读取,一行一行的添加,一直到读取超过一页的行数。
        while (lines.size() < mPageLineCount && mBufferEnd < mBookBufferLength) {
            byte[] paragraphForward = readParagraphForward(mBufferEnd); // 读取一个段落
            mBufferEnd += paragraphForward.length;
            try {
                strParagraph = new String(paragraphForward, mBookCharsetName);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            String strReturn = "";
            if (strParagraph.indexOf("\r\n") != -1) {
                strReturn = "\r\n";
                strParagraph = strParagraph.replaceAll("\r\n", "");
            } else if (strParagraph.indexOf("\n") != -1) {
                strReturn = "\n";
                strParagraph = strParagraph.replaceAll("\n", "");
            }

            if (strParagraph.length() == 0) {
                lines.add(strParagraph);
            }
            while (strParagraph.length() > 0) {
                // 计算一行的字数。
                int nSize = mPaint.breakText(strParagraph, true, mVisibleWidth, null);
                // 添加一行
                lines.add(strParagraph.substring(0, nSize));
                // 将段落减去这一行,变成剩下的一部分,除非超过一页,才会跳出
                strParagraph = strParagraph.substring(nSize);
                if (lines.size() >= mPageLineCount) {
                    break;
                }
            }
            if (strParagraph.length() != 0) {
                try {
                    //位置向后移
                    mBufferEnd -= (strParagraph + strReturn).getBytes(mBookCharsetName).length;
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        }
        return lines;
    }

    protected void pageUp() {
        if (mBufferBegin < 0)
            mBufferBegin = 0;
        Vector<String> lines = new Vector<>();
        String strParagraph = "";
        while (lines.size() < mPageLineCount && mBufferBegin > 0) {
            Vector<String> paraLines = new Vector<>();
            byte[] paraBuf = readParagraphBack(mBufferBegin);
            mBufferBegin -= paraBuf.length;
            try {
                strParagraph = new String(paraBuf, mBookCharsetName);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            strParagraph = strParagraph.replaceAll("\r\n", "");
            strParagraph = strParagraph.replaceAll("\n", "");

            if (strParagraph.length() == 0) {
                paraLines.add(strParagraph);
            }
            while (strParagraph.length() > 0) {
                int nSize = mPaint.breakText(strParagraph, true, mVisibleWidth,
                        null);
                paraLines.add(strParagraph.substring(0, nSize));
                strParagraph = strParagraph.substring(nSize);
            }
            lines.addAll(0, paraLines);
        }
        while (lines.size() > mPageLineCount) {
            try {
                mBufferBegin += lines.get(0).getBytes(mBookCharsetName).length;
                lines.remove(0);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        mBufferEnd = mBufferBegin;
        return;
    }

    protected void prePage() throws IOException {
        if (mBufferBegin <= 0) {
            mBufferBegin = 0;
            mIsFirstPage = true;
            return;
        } else
            mIsFirstPage = false;
        mIsLastPage = false;
        mLinesVector.clear();
        pageUp();
        mLinesVector = pageDown();
    }

    public void nextPage() throws IOException {
        if (mBufferEnd >= mBookBufferLength) {
            mIsLastPage = true;
            return;
        } else
            mIsLastPage = false;
        mIsFirstPage = false;
        mLinesVector.clear();
        mBufferBegin = mBufferEnd;
        mLinesVector = pageDown();
    }

    public void draw(Canvas canvas) {
        if (mLinesVector.size() == 0)
            mLinesVector = pageDown();
        if (mLinesVector.size() > 0) {
            if (mIsUserBg && mBookBgBitmap != null)
                canvas.drawBitmap(mBookBgBitmap, 0, 0, null);
            else {
                canvas.drawColor(mBackGroundColor);
            }
            int yHeight = mMarginHeight + mFontSize;
            // 一行行绘制文字
            for (String strLine : mLinesVector) {
                canvas.drawText(strLine, mMarginWidth, yHeight, mPaint);
                yHeight += mFontSize + mLineMargin;
            }
        }

        // 设置进度
        if (onProgressChangedListener!= null) {
            onProgressChangedListener.setProgress(mBufferBegin, mBookBufferLength);
        }
    }

    public void setBackground(Context context, int resourceID){
        if(context != null && resourceID != 0){
            mIsUserBg = true;
            setBgBitmap(BitmapFactory.decodeResource(context.getResources(),resourceID));
        }
    }
    public void setBgBitmap(Bitmap bitmap) {
        mBookBgBitmap = bitmap;

        Matrix matrix = new Matrix();
        int width = mBookBgBitmap.getWidth();// 获取资源位图的宽
        int height = mBookBgBitmap.getHeight();// 获取资源位图的高
        float w = (float) mWidth / (float) mBookBgBitmap.getWidth();
        float h = (float) mHeight / (float) mBookBgBitmap.getHeight();
        matrix.postScale(w, h);// 获取缩放比例
        mBookBgBitmap = Bitmap.createBitmap(mBookBgBitmap, 0, 0, width, height, matrix,true);// 根据缩放比例获取新的位图
    }

    public void setUseBg(boolean useBg) {
        mIsUserBg = useBg;
    }

    public boolean isFirstPage() {
        return mIsFirstPage;
    }

    public boolean isLastPage() {
        return mIsLastPage;
    }

    public int getEnd() {
        return mBufferEnd;
    }

    public float getProgress() {
        return (float) (mBufferBegin * 1.0 / mBookBufferLength);
    }

    public void setBookProgress(int progress) {
        if (progress < 0) {
            mBufferBegin = mBufferEnd = 0;
        } else if (progress > mBookBufferLength) {
            mBufferBegin = mBufferEnd = mBookBufferLength;
        } else {
            mBufferBegin = mBufferEnd = progress;
        }
        pageUp();
        if (mBufferBegin != 0) {
            mLinesVector.clear();
            mLinesVector = pageDown();

            mBufferBegin = mBufferEnd;
        }
        mLinesVector.clear();
        mLinesVector = pageDown();
    }

    public void setProgress(int progress) {

        if (progress < 0) {
            mBufferBegin = mBufferEnd = 0;
        } else if (progress > 100) {
            mBufferBegin = mBufferEnd = mBookBufferLength;
        } else {
            mBufferBegin = mBufferEnd = (int) (((float) progress / 100) * mBookBufferLength);
        }

        pageUp();
        if (mBufferBegin != 0) {
            mLinesVector.clear();
            mLinesVector = pageDown();

            mBufferBegin = mBufferEnd;
        }
        mLinesVector.clear();
        mLinesVector = pageDown();
    }

    public void setTextColor(int color) {
        mPaint.setColor(color);
    }

    /**
     * 设置接口
     *
     * @param onProgressChangedListener
     */
    public void setOnProgressChangedListener(OnProgressChangedListener onProgressChangedListener) {
        this.onProgressChangedListener = onProgressChangedListener;
    }

    /**
     * 设置阅读进度的接口
     */
    public interface OnProgressChangedListener {
        /**
         * 设置进度方法
         */
        void setProgress(int currentLength, int totalLength);
    }

    public String getCurrentPageContent(){
        return mLinesVector.toString();
    }
}

五、项目源码

👇👇👇👇👇快捷方式👇👇👇👇👇