模仿《书吧》的看书界面,基本功能实现,更高级的功能有空再弄。
看书界面的activity:ReadBookActivity
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Environment;
import android.os.Message;
import android.text.Layout;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.os.Handler;
public class ReadBookActivity extends Activity implements OnClickListener,OnGestureListener{
private TextView tvMain; // 一个大的看书页面,由textview完成
private String strTxt = ""; // 用于显示的文本字符串
int position = 0; // 当前阅读位置,取一行的行首
int markPos = 0; // 书签位置
final int BUFFER_SIZE = 1024 * 3*1024; // 此处设置读取文件大小,越大反应越慢
boolean hasBookMark = false;
private MyProcessDialog myProgressDialog;
private String path;
Handler handler = new Handler(){
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
tvMain.setText(strTxt);
removeCProgressDialog();
break;
case 1:
Toast.makeText(ReadBookActivity.this, "不支持此编码,请换成utf-8编码!", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(ReadBookActivity.this, "未找到test.txt文件!", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
};
};
private LinearLayout ll_bottom;
private LinearLayout ll_top;
private LinearLayout ll_setting;
private LinearLayout ll_bookmark;
private int count = 0;
private int num = 0;
private int number = 0;
private int cou = 0;
private TextView tv_daynight;
private TextView tv_setting;
private TextView tv_bookmark;
private OnSeekBarChangeListener osbcl;
private SeekBar sb_fontsize;
private SeekBar sb_light;
private RadioGroup rg_bookmark;
private TextView tv_cancel;
private TextView tv_sure;
private SharedPreferences sp;
private RadioButton rb_addbm;
private RadioButton rb_selectbm;
private RadioGroup rg_bgcolor;
private RadioButton rb_white;
private RadioButton rb_1;
private RadioButton rb_2;
private RadioButton rb_3;
private GestureDetector gd;
private static final int FLING_MIN_DISTANCE = 50;
private static final int FLING_MIN_VELOCITY = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.readbook);
tvMain = (TextView) this.findViewById(R.id.viewtxt_main_view);
path = Environment.getExternalStorageDirectory()+"/test.txt";
showCProgressDialog("加载中。。。");
new Thread(){
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path), "utf-8"));
char[] buf = new char[BUFFER_SIZE];
br.read(buf);
strTxt = new String(buf);
handler.sendEmptyMessage(0);
} catch (UnsupportedEncodingException e1) {
handler.sendEmptyMessage(1);
} catch (IOException e) {
handler.sendEmptyMessage(2);
}
}
}.start();
sp = getSharedPreferences("bookmark", Activity.MODE_PRIVATE);
tvMain.setCursorVisible(false);
//这句话控制textview是否可以像scrollview那样滑动
// tvMain.setMovementMethod(ScrollingMovementMethod.getInstance());
ll_bottom = (LinearLayout) findViewById(R.id.ll_bottom);
ll_top = (LinearLayout) findViewById(R.id.ll_top);
ll_setting = (LinearLayout) findViewById(R.id.ll_setting);
ll_bookmark = (LinearLayout) findViewById(R.id.ll_bookmark);
tv_daynight = (TextView) findViewById(R.id.tv_daynight);
tv_setting = (TextView) findViewById(R.id.tv_setting);
tv_bookmark = (TextView) findViewById(R.id.tv_bookmark);
sb_fontsize = (SeekBar) findViewById(R.id.sb_fontsize);
sb_light = (SeekBar) findViewById(R.id.sb_light);
rg_bookmark = (RadioGroup) findViewById(R.id.rg_bookmark);
rb_addbm = (RadioButton) findViewById(R.id.rb_addbm);
rb_selectbm = (RadioButton) findViewById(R.id.rb_selectbm);
tv_cancel = (TextView) findViewById(R.id.tv_cancel);
tv_sure = (TextView) findViewById(R.id.tv_sure);
rg_bgcolor = (RadioGroup) findViewById(R.id.rg_bgcolor);
rb_white = (RadioButton) findViewById(R.id.rb_white);
rb_1 = (RadioButton) findViewById(R.id.rb_1);
rb_2 = (RadioButton) findViewById(R.id.rb_2);
rb_3 = (RadioButton) findViewById(R.id.rb_3);
gd = new GestureDetector(ReadBookActivity.this, this);
tvMain.setOnClickListener(this);
tvMain.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return gd.onTouchEvent(event);
}
});
tv_daynight.setOnClickListener(this);
tv_setting.setOnClickListener(this);
tv_bookmark.setOnClickListener(this);
// 滑动条改变的监听器
osbcl = new OnSeekBarChangeListener() {
// 拖动中
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
switch (seekBar.getId()) {
case R.id.sb_fontsize:
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.screenBrightness = progress / 10.0f;
getWindow().setAttributes(lp);
break;
case R.id.sb_light:
tvMain.setTextSize((progress + 1) * 5.0f);
break;
}
}
// 开始拖动
public void onStartTrackingTouch(SeekBar seekBar) {
}
// 结束拖动
public void onStopTrackingTouch(SeekBar seekBar) {
}
};
sb_fontsize.setOnSeekBarChangeListener(osbcl);
sb_light.setOnSeekBarChangeListener(osbcl);
tv_cancel.setOnClickListener(this);
tv_sure.setOnClickListener(this);
rg_bgcolor.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
rb_white.setText("");
rb_1.setText("");
rb_2.setText("");
rb_3.setText("");
switch (checkedId) {
case R.id.rb_white:
rb_white.setText("√");
tvMain.setBackgroundColor(getResources().getColor(R.color.white));
break;
case R.id.rb_1:
rb_1.setText("√");
tvMain.setBackgroundColor(getResources().getColor(R.color.c1));
break;
case R.id.rb_2:
rb_2.setText("√");
tvMain.setBackgroundColor(getResources().getColor(R.color.c2));
break;
case R.id.rb_3:
rb_3.setText("√");
tvMain.setBackgroundColor(getResources().getColor(R.color.c3));
break;
default:
break;
}
}
});
}
// 重新回到看书界面
@Override
public void onResume() {
super.onResume();
Layout l = tvMain.getLayout();
if (null != l) {
// 回到上次观看的位置
if (hasBookMark) {
hasBookMark = false;
position = markPos;
} else {
int line = l.getLineForOffset(position);
float sy = l.getLineBottom(line);
tvMain.scrollTo(0, (int) sy);
}
}
}
@SuppressLint("ResourceAsColor")
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.viewtxt_main_view:
// if(count % 2 == 0){
// ll_bottom.setVisibility(View.VISIBLE);
// ll_top.setVisibility(View.VISIBLE);
// }else{
// ll_bottom.setVisibility(View.GONE);
// ll_top.setVisibility(View.GONE);
// }
// if(ll_setting.getVisibility() == View.VISIBLE){
// ll_setting.setVisibility(View.GONE);
// }
// if(ll_bookmark.getVisibility() == View.VISIBLE){
// ll_bookmark.setVisibility(View.GONE);
// }
// count++;
break;
case R.id.tv_daynight:
if(num % 2 == 0){
tv_daynight.setText("夜间");
tvMain.setBackgroundColor(Color.BLACK);
tvMain.setTextColor(Color.WHITE);
Drawable top = getResources().getDrawable(R.drawable.moon);
top.setBounds(0, 0, top.getMinimumWidth(), top.getMinimumHeight());
tv_daynight.setCompoundDrawables(null, top, null, null);
}else{
tv_daynight.setText("日间");
tvMain.setBackgroundColor(Color.WHITE);
tvMain.setTextColor(Color.BLACK);
Drawable top = getResources().getDrawable(R.drawable.sun);
top.setBounds(0, 0, top.getMinimumWidth(), top.getMinimumHeight());
tv_daynight.setCompoundDrawables(null, top, null, null);
}
if(ll_setting.getVisibility() == View.VISIBLE){
ll_setting.setVisibility(View.GONE);
}
num++;
break;
case R.id.tv_setting:
if(number %2 == 0){
ll_setting.setVisibility(View.VISIBLE);
}else{
ll_setting.setVisibility(View.GONE);
}
if(ll_bookmark.getVisibility() == View.VISIBLE){
ll_bookmark.setVisibility(View.GONE);
}
number++;
break;
case R.id.tv_bookmark:
if(cou % 2 == 0){
ll_bookmark.setVisibility(View.VISIBLE);
int bookmark = sp.getInt("bookmark", 0);
if(bookmark <= 0){
rb_selectbm.setEnabled(false);
rb_addbm.setChecked(true);
}else{
rb_selectbm.setEnabled(true);
rb_selectbm.setChecked(true);
}
}else{
ll_bookmark.setVisibility(View.GONE);
}
if(ll_setting.getVisibility() == View.VISIBLE){
ll_setting.setVisibility(View.GONE);
}
cou++;
break;
case R.id.tv_cancel:
ll_bookmark.setVisibility(View.GONE);
break;
case R.id.tv_sure:
ll_bookmark.setVisibility(View.GONE);
if(rb_addbm.isChecked()){
Layout l = tvMain.getLayout();
int line = l.getLineForVertical(tvMain.getScrollY());
int off = l.getOffsetForHorizontal(line, 0);
Editor edit = sp.edit();
edit.putInt("bookmark", off);
edit.commit();
}else if(rb_selectbm.isChecked()){
int mark = sp.getInt("bookmark", 0);
Layout l = tvMain.getLayout();
if (null != l) {
// 去往书签位置
int line = l.getLineForOffset(mark);
float sy = l.getLineBottom(line);
tvMain.scrollTo(0, (int) sy);
}
}
break;
default:
break;
}
}
/**
* 显示自定义进度框
*
* @param message
*/
private void showCProgressDialog(String message) {
// 创建一个显示进度的Dialog
if (myProgressDialog == null) {
myProgressDialog = new MyProcessDialog(this);
myProgressDialog.setMsg(message);
// 点击屏幕Dialog消失
myProgressDialog.setCanceledOnTouchOutside(false);
}
myProgressDialog.show();
}
private void removeCProgressDialog() {
if (myProgressDialog != null) {
myProgressDialog.dismiss();
}
}
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
// TODO Auto-generated method stub
if(count % 2 == 0){
ll_bottom.setVisibility(View.VISIBLE);
ll_top.setVisibility(View.VISIBLE);
}else{
ll_bottom.setVisibility(View.GONE);
ll_top.setVisibility(View.GONE);
}
if(ll_setting.getVisibility() == View.VISIBLE){
ll_setting.setVisibility(View.GONE);
}
if(ll_bookmark.getVisibility() == View.VISIBLE){
ll_bookmark.setVisibility(View.GONE);
}
count++;
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
// TODO Auto-generated method stub
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
// TODO Auto-generated method stub
if (e1.getX()-e2.getX() > FLING_MIN_DISTANCE
&& Math.abs(velocityX) > FLING_MIN_VELOCITY) {
if (tvMain.getScrollY() >= tvMain.getLineCount()
* tvMain.getLineHeight() - tvMain.getHeight() * 2)
tvMain.scrollTo(
0,
tvMain.getLineCount() * tvMain.getLineHeight()
- tvMain.getHeight());
else
tvMain.scrollTo(0, tvMain.getScrollY() + tvMain.getHeight());
} else if (e2.getX()-e1.getX() > FLING_MIN_DISTANCE
&& Math.abs(velocityX) > FLING_MIN_VELOCITY) {
if (tvMain.getScrollY() <= tvMain.getHeight())
tvMain.scrollTo(0, 0);
else
tvMain.scrollTo(0, tvMain.getScrollY() - tvMain.getHeight());
}
if(ll_top.getVisibility() == View.VISIBLE){
ll_top.setVisibility(View.GONE);
}
if(ll_bottom.getVisibility() == View.VISIBLE){
ll_bottom.setVisibility(View.GONE);
}
return false;
}
}
布局文件:readbook.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/viewtxt_main_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:scrollbars="none"
android:singleLine="false"
android:background="@color/white"
android:textSize="20sp"
android:textColor="@color/default_background_color" />
<LinearLayout
android:id="@+id/ll_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@color/default_background_color"
android:orientation="horizontal"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:visibility="gone" >
<TextView
android:id="@+id/tv_daynight"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="日间"
android:drawableTop="@drawable/sun"
android:textColor="@color/white" />
<TextView
android:id="@+id/tv_setting"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="设置"
android:drawableTop="@drawable/font"
android:textColor="@color/white" />
<TextView
android:id="@+id/tv_bookmark"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="书签"
android:drawableTop="@drawable/bookmark"
android:textColor="@color/white" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="@color/default_background_color"
android:orientation="horizontal"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:visibility="gone" >
<TextView
android:id="@+id/tv_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/back"
android:drawablePadding="5dp"
android:gravity="center"
android:text="test.txt"
android:layout_marginLeft="5dp"
android:textSize="20sp"
android:textColor="@color/white" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/ll_bottom"
android:background="@color/default_background_color"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:visibility="gone" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lightl"
android:layout_marginLeft="3dp"
/>
<SeekBar
android:id="@+id/sb_light"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:max="10"
android:progress="0"
android:layout_weight="1"
android:secondaryProgress="10" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lighth"
android:layout_marginRight="3dp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font"
android:layout_marginLeft="3dp"
/>
<SeekBar
android:id="@+id/sb_fontsize"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:max="10"
android:progress="0"
android:secondaryProgress="10" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font"
android:layout_marginRight="3dp"
/>
</LinearLayout>
<RadioGroup
android:id="@+id/rg_bgcolor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<RadioButton
android:id="@+id/rb_white"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:background="@color/white"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textColor="@android:color/holo_red_light"
android:textSize="30sp"
android:gravity="center"
android:button="@null" />
<RadioButton
android:id="@+id/rb_1"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:background="@color/c1"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textColor="@android:color/holo_red_light"
android:textSize="30sp"
android:gravity="center"
android:button="@null" />
<RadioButton
android:id="@+id/rb_2"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:background="@color/c2"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textColor="@android:color/holo_red_light"
android:textSize="30sp"
android:gravity="center"
android:button="@null" />
<RadioButton
android:id="@+id/rb_3"
android:layout_width="0dp"
android:layout_height="80dp"
android:layout_weight="1"
android:background="@color/c3"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textColor="@android:color/holo_red_light"
android:textSize="30sp"
android:gravity="center"
android:button="@null" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_bookmark"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true"
android:orientation="vertical"
android:background="#EEEEEE"
android:visibility="gone" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@color/default_background_color"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:text="书签" />
<RadioGroup
android:id="@+id/rg_bookmark"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical" >
<RadioButton
android:id="@+id/rb_selectbm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@color/default_background_color"
android:text="跳到书签" />
<RadioButton
android:id="@+id/rb_addbm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@color/default_background_color"
android:text="添加书签" />
</RadioGroup>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_weight="1"
android:gravity="center"
android:textColor="@color/default_background_color"
android:text="取消" />
<TextView
android:id="@+id/tv_sure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_weight="1"
android:gravity="center"
android:textColor="@color/default_background_color"
android:text="确定" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
这里还用了一个自定义的progressdialog,加载时用的:
import android.app.Dialog;
import android.content.Context;
import android.widget.TextView;
public class MyProcessDialog extends Dialog {
private TextView txt_info;
public MyProcessDialog(Context context) {
super(context, R.style.MyProgressDialog);
this.setContentView(R.layout.progress_dialog);
//this.setCancelable(false);
this.setCanceledOnTouchOutside(false);
txt_info = (TextView)this.findViewById(R.id.txt_wait);
}
public void setMsg(String msg){
if(null != txt_info){
txt_info.setText(msg);
}
}
}
动画效果:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_loading_white"
android:pivotX="50.0%"
android:pivotY="50.0%"
/>
progressdialog布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/progress_bg"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:focusable="false"
android:indeterminateDrawable="@drawable/ic_progressbar"
/>
<TextView
android:id="@+id/txt_wait"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="8.0dip"
android:focusable="false"
android:singleLine="true"
android:text="数据加载中..."
android:textColor="@android:color/white"
android:textSize="12.0sp" />
</LinearLayout>
各种values文件:
colors:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<color name="default_background_color">#ff000000</color>
<color name="white">#fff</color>
<color name="c1">#E6DBBF</color>
<color name="c2">#BED0BF</color>
<color name="c3">#F0ECE1</color>
</resources>
styles:
<resources>
<!--
Base application theme, dependent on API level. This theme is replaced
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
-->
<style name="AppBaseTheme" parent="android:Theme.Light">
<!--
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
-->
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
<!-- 加载数据时的等待对话框(透明背景色) -->
<style name="MyProgressDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowFrame">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
最后还有一些矢量图。
基本实现了看书的一些常用功能。