基于Android的本地电子书阅读器的设计与实现Ebook(2)
接着上回,我们继续分说下面步骤。
用户登录后,映入眼帘的就是我们实现阅读器的主要三个界面,分别是书架、感悟、分类。
首先这三个界面的切换是通过Fragment实现的底部导航:
activity_main_login.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainLogin">
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
</androidx.viewpager.widget.ViewPager>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
app:menu="@menu/menu_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bookbg"
app:itemIconTint="@drawable/fgbtn"
app:labelVisibilityMode="auto"/>
</LinearLayout>
这里的menu是在res文件夹下创建的menu文件夹,具体存放图标和文字
menu_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/item1"
android:title="书架"
android:icon="@drawable/book"/>
<item
android:id="@+id/item2"
android:title="感悟"
android:icon="@drawable/note"/>
<item
android:id="@+id/item3"
android:title="分类"
android:icon="@drawable/sort"/>
</menu>
接下来是java文件MainLogin:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_login);
viewPager=findViewById(R.id.viewPager);
bottomNavigationView=findViewById(R.id.bottomNavigationView);
//初始化fragments数组
list=new ArrayList<Fragment>();
list.add(new LoginFragment1());
list.add(new LoginFragment2());
list.add(new LoginFragment3());
//设置底部导航点击和不点击时颜色
Resources resource = getResources();
@SuppressLint("ResourceType")
ColorStateList csl = resource.getColorStateList(R.drawable.fgbtn);
bottomNavigationView.setItemTextColor(csl);
viewPager.setAdapter(new MyAdapter(getSupportFragmentManager(),list));
//点击底部导航项,显示对应的页面
bottomNavigationView.setOnNavigationItemSelectedListener(
new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.item1:
viewPager.setCurrentItem(0);
break;
case R.id.item2:
viewPager.setCurrentItem(1);
break;
case R.id.item3:
viewPager.setCurrentItem(2);
break;
}
return true;
}
});
//页面左右滑动时,让底部的导航项和显示的页面保持一致
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if(menuItem==null){
menuItem=bottomNavigationView.getMenu().getItem(0);
}
//将上次的选择设置为false,等待下次的选择
menuItem.setChecked(false);
menuItem=bottomNavigationView.getMenu().getItem(position);
menuItem.setChecked(true);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
//作为fragment的适配器
private class MyAdapter extends FragmentPagerAdapter
{
List list;
public MyAdapter(@NonNull FragmentManager fm, List list) {
super(fm);
this.list=list;
}
@NonNull
@Override
public Fragment getItem(int position)
{
return (Fragment) list.get(position);
}
@Override
public int getCount()
{
return list.size();
}
}
}
现在我们的底部导航已经建好了,接下来需要我们新建三个fragment页面,分别实现书架、感悟、分类三个部分。
首先是书架部分,先上图看看效果:
fragment_login1.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".LoginFragment1">
<LinearLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical">
<com.example.ebook.MarqueTextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:id="@+id/mtv"
android:ellipsize="marquee"
android:textSize="25dp"
android:singleLine="true"
android:marqueeRepeatLimit="marquee_forever"
android:background="@drawable/bookbg"/>
<ListView
android:id="@+id/lg3_listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bookbg2"/>
</LinearLayout>
</LinearLayout>
在这里说明一下,页面的顶部本质是一个TextView控件,但文字是可以横向滚动的,所以得自己写一个基于TextView的子类。
java文件MarqueTextView:
public class MarqueTextView extends androidx.appcompat.widget.AppCompatTextView {
public MarqueTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MarqueTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MarqueTextView(Context context) {
super(context);
}
@Override
public boolean isFocused() {
//就是把这里返回true即可
return true;
}
}
然后是实现书架这一部分的java文件LoginFragment1:
public class LoginFragment1 extends Fragment implements AdapterView.OnItemClickListener{
//变量名注册
ListView listView;
SimpleAdapter simpleAdapter;
public View onCreateView
(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_login1, container, false);
//id注册
listView = view.findViewById(R.id.lg3_listView);
MarqueTextView as = view.findViewById(R.id.mtv);
simpleAdapter = new SimpleAdapter(getActivity(),getData(),R.layout.item,
new String[]{"title","image"},new int[]{R.id.item_tv,R.id.item_image});
//设置适配器监听器
listView.setAdapter(simpleAdapter);
listView.setOnItemClickListener(this);
//将文件由assets转移到手机空间
copyFilesFromAssets(getActivity(),"ml","/data/data/com.example.ebook/files");
as.setText(load());
return view;
}
//设置listview图文
private List<Map<String,Object>> getData() {
String [] titles={"射雕英雄传\n简介:南宋末年,金兵入侵,朝廷奸臣当道。全真派丘处机因杀死汉奸王道乾,被官兵追杀,逃到江南一个小村牛家庄,与隐居在此处的爱国义士郭啸天和杨铁心一见如故,以“靖、康”为他俩即将出生的孩子命名,并留下一对短剑作为信物……",
"神雕侠侣\n简介:南宋末年,江南少年杨过被郭靖送去全真教学武。全真教教规森严,天性叛逆的杨过在教中吃尽苦头,忍无可忍,终于逃出全真教。被活死人墓中的小龙女收留为徒。师徒二人在墓中一起练武、一起长大,渐生情愫……",
"倚天屠龙记\n简介:少林寺觉远禅师看护《楞伽经》不力,导致经书被尹克西和潇湘子盗走。潇湘子和尹克西互相猜忌残杀,临终之前幡然醒悟,委托何足道向觉远转达经书的下落。何足道接受委托,并向少林寺发出挑战。适逢郭靖、黄蓉的女儿郭襄前往少林寺寻找杨过……",
"绝对娇宠\n简介:作者:陈三年\n斯文霸道总裁装逼乘骄纵美人,爱让人勇敢,双向奔赴,梦会成真。",
"斗罗大陆\n简介:外门弟子唐三,因偷学内门绝学为唐门所不容,跳崖明志时却发专现没有死,反而以另属外一个身份来到了另一个世界,名叫斗罗大陆。这里没有魔法,没有斗气,没有武术,却有神奇的武魂……",
"末日游戏场\n简介:系统:“你们的任务是活下去,倾尽所有,不择手段地活下去,完成一场挑战,即可获得一张通行证。\n星际333年,地球环境无法生存 开启全民末日游戏场,提过游戏得到通行证,获取生存资格。",
"完美世界\n简介:一粒尘可填海,一根草斩尽日月星辰,弹指间天翻地覆。\n群雄并起,万族林立,诸圣争霸,乱天动地。问苍茫大地,谁主沉浮?!\n一个少年从大荒中走出,一切从这里开始\n"};
int [] images={R.drawable.im1,R.drawable.im2,R.drawable.im3,
R.drawable.im4,R.drawable.im5,
R.drawable.im6,R.drawable.im7};
List<Map<String,Object>> list= new ArrayList<>();
for(int i=0;i<7;i++){
Map map = new HashMap();
map.put("title",titles[i]);
map.put("image",images[i]);
list.add(map);
}
return list;
}
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
//设置listview点击触发事件
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position){
case 0:
Intent intent0 = new Intent(getActivity(),ReadBook.class);
intent0.putExtra("extra","0.txt");
startActivity(intent0);
break;
case 1:
Intent intent1 = new Intent(getActivity(),ReadBook.class);
intent1.putExtra("extra","1.txt");
startActivity(intent1);
break;
case 2:
Intent intent2 = new Intent(getActivity(),ReadBook.class);
intent2.putExtra("extra","2.txt");
startActivity(intent2);
break;
case 3:
Intent intent3 = new Intent(getActivity(),ReadBook.class);
intent3.putExtra("extra","3.txt");
startActivity(intent3);
break;
case 4:
Intent intent4 = new Intent(getActivity(),ReadBook.class);
intent4.putExtra("extra","4.txt");
startActivity(intent4);
break;
case 5:
Intent intent5 = new Intent(getActivity(),ReadBook.class);
intent5.putExtra("extra","5.txt");
startActivity(intent5);
break;
case 6:
Intent intent6 = new Intent(getActivity(),ReadBook.class);
intent6.putExtra("extra","6.txt");
startActivity(intent6);
break;
default:
throw new IllegalStateException("Unexpected value: " + position);
}
}
// 从assets目录中复制整个文件夹内容到新的路径下
// @param context Context 使用CopyFiles类的Activity
// @param oldPath String 原文件路径 如:Data(assets文件夹下文件夹名称)
// @param newPath String 复制后路径 如:data/data/(手机内部存储路径名称)
public void copyFilesFromAssets(Context context, String oldPath, String newPath) {
try {
String fileNames[] = context.getAssets().list(oldPath);//获取assets目录下的所有文件及目录名
if (fileNames.length > 0) {//如果是目录
File file = new File(newPath);
file.mkdirs();//如果文件夹不存在,则递归
for (String fileName : fileNames) {
copyFilesFromAssets(context,oldPath + "/" + fileName,newPath+"/"+fileName);
}
} else {//如果是文件
InputStream is = context.getAssets().open(oldPath);
FileOutputStream fos = new FileOutputStream(new File(newPath));
byte[] buffer = new byte[1024];
int byteCount=0;
while((byteCount=is.read(buffer))!=-1) {//循环从输入流读取 buffer字节
fos.write(buffer, 0, byteCount);//将读取的输入流写入到输出流
}
fos.flush();//刷新缓冲区
is.close();
fos.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
//如果捕捉到错误则通知UI线程
//MainActivity.handler.sendEmptyMessage(COPY_FALSE);
}
}
//设置顶部文字并循环播放
String load(){
String tit=" 欢迎来到Ebook阅读器,这是您打开知识宝库的钥匙!\n" +
" 欢迎来到Ebook阅读器,这是您打开知识宝库的钥匙!\n" ;
return tit;
}
}
其实现在看来当时写的还是非常简单的,在书架展示这一块是靠代码后台输入文字介绍,以及用简单地Switch现实跳转,换成现在有更好的解决方式。不过这是后话,我想能力的提升也是循序渐进的。我还是来解释一些核心的代码。
书的内容是以txt格式存放在之前提到的asset文件夹里面,copyFilesFromAssets函数便是实现对asset文件内容的读取。这样就实现了文件可以随着代码一起保存并放到手机上,不用创建数据库(现在觉得创建了数据库实现的功能可能可以更加完善)是一种投机取巧的方式
这里的适配器simpleAdapter里的item:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/item_image"
android:layout_margin="10dp"
android:layout_width="280px"
android:layout_height="580px" />
<TextView
android:textSize="15dp"
android:layout_margin="10dp"
android:padding="10px"
android:textColor="@color/black"
android:layout_width="match_parent"
android:layout_height="580px"
android:id="@+id/item_tv"/>
</LinearLayout>
当用户点进一本书后会看到类似界面:
activity_read_book.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/rbookbg3"
tools:context=".ReadBook">
<TextView
android:layout_width="wrap_content"
android:layout_height="650dp"
android:textSize="20dp"
android:id="@+id/sd"
android:layout_margin="30dp"
android:textColor="@color/black"
android:scrollbars="vertical"
/>
</LinearLayout>
java文件ReadBook:
public class ReadBook extends AppCompatActivity {
String txt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_book);
Intent intent=getIntent();
txt=intent.getStringExtra("extra");
Log.d("ReadBook",txt);
TextView sb = findViewById(R.id.sd);
sb.setMovementMethod(ScrollingMovementMethod.getInstance());
sb.setSelected(true);
sb.setText(load());
}
//获取手机空间中的txt文件
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput(txt);
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append("\n");
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
}
这里的Log是用来实现两个活动直接的信息传递,load函数就是基本的读取txt文件操作了。
到此为止作为一个阅读器最重要的阅读功能就可以实现了,接下来的感悟和分类,均使用到了数据库,待下回分解。