今天我们通过一个实际的案例来综合运用一下Android技术中各方面的知识,模仿《宋词三百首》写一个应用,代码里面所有的资源均来自互联网,仅用于学习,请勿作商业用途。
(1)第一步新建Android工程,修改应用图标,将72x72的app icon拷贝到drawable-hdpi文件夹下,将96x96的app icon拷贝到drawable-xhdpi文件夹下,然后修改AndroidManifest.xml文件里的内容如下:
- <application
- android:icon="@drawable/icon"
然后修改strings.xml的内容如下:
- <string name="app_name">宋词三百首</string>
- <string name="title_activity_main">宋词三百首</string>
修改应用名称,将AndroidManifest.xml文件中的内容修改如下:
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name=".ui.SplashActivity"
- android:label="@string/title_activity_main" >
经过以上的工作,App的图标和名称都已经修改OK;
(2)下面我们来写第一个界面:欢迎界面
首先将背景图片welcome.jpg拷贝到drawable-hdpi下面,然后在layout文件夹下面新建一个activity_splash.xml,内容如下:
- <?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:orientation="vertical"
- android:background="@drawable/welcome">
- </LinearLayout>
然后在src下面新建一个SplashActivity.java文件,代码已经详细注释,内容如下:
- package com.example.songcidemo.ui;
- import com.example.songcidemo.R;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.os.Handler;
- import android.view.Window;
- /**
- * App欢迎界面
- */
- public class SplashActivity extends Activity {
- /**
- * 启动时最先执行的回调方法
- */
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //设置界面没有标题栏
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- //指定界面的布局文件
- setContentView(R.layout.activity_splash);
- //初始化一个Handler
- Handler handler = new Handler();
- //Runnable是一个线程,在1500毫秒以后执行线程对象
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- //从SplashActivity跳转到MainActivity
- Intent intent = new Intent(SplashActivity.this, MainActivity.class);
- startActivity(intent);
- //在后台关闭掉SplashActivity
- SplashActivity.this.finish();
- }
- }, 1500);
- }
- }
运行效果如下图:
(2)接着我们写第二个界面,在写第二个界面之前我们还需要做一些准备工作,就是准备数据,所有的数据都存储在songci.xml这样一个文件中,在这里我截取其一点片段如下:
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
- <root>
- <node>
- <title><![CDATA[洞仙歌·泗州中秋作]]></title>
- <auth><![CDATA[晁补之]]></auth>
- <desc><![CDATA[<p> 洞仙歌·泗州①中秋作 </p>
- <p> <strong>晁补之</strong> </p>
- <p> 青烟幂②处,碧海飞金镜。永夜闲阶卧桂影。 </p>
- <p> 露凉时、零乱多少寒螀③,神京④远,惟有蓝桥⑤路近。 </p>
- <p> 水晶帘不下,云母屏⑥开,冷浸佳人⑦淡脂粉。 </p>
- <p> 待都将许多明,付与金尊,投晓共、流霞⑧倾尽。 </p>
- <p> 更携取、胡床⑨上南楼,看玉做人间,素秋千倾。</p>
- <p><br />【注释】<br /> ①泗州:安徽泗县。 </p>
- <p> ②幂(mì):遮盖。 </p>
- <p> ③寒螀(jiāng):寒蝉。 </p>
- <p> ④ 神京:指北宋京城汴梁。 </p>
- <p> ⑤蓝桥:在陕西蓝田县东南,桥架蓝水之上,故名。世传其地有仙窟,唐裴航遇云英于此桥。 </p>
- <p> ⑥ 云母屏:云母为花岗岩主要成分,可作屏风,艳丽光泽。 </p>
- <p> ⑦佳人:这里指席间的女性 </p>
- <p> ⑧流霞:仙酒名。语意双关,既指酒,也指朝霞 </p>
- <p> ⑨胡床:古代一种轻便坐具,可以折叠。</p>
- <p>【译文】<br /> 青色的烟云,遮住了月影,从碧海般的晴空里飞出一轮金灿灿的明镜。长夜的空阶上卧着挂树的斜影。夜露渐凉之是时,多少秋蝉零乱地嗓鸣思念京都路远,论路近唯有月宫仙境,高卷水晶帘儿,展开云母屏风,美人的淡淡脂粉浸润了夜月的清冷。待我许多月色澄辉,倾入金樽,直到拂晓连同流霞全都倾尽。再携带一张胡床登上南楼,看白玉铺成的人间,领略素白澄洁的千顷清秋。</p>
- ]]></desc>
- </node>
我们将songci.xml放在assets文件夹下面,因为xml文件有点大,在打包成apk文件的时候会被压缩,造成读取的时候产生IOException,关于这个问题的更多详细请参考IOEXception while reading from inputstream,所以我们将songci.xml文件的改名为songci.mp3,以避免这样的问题。
其次就是几个知识点的预备工作(如果你已经熟悉这些知识,请跳过):
那么下面我们开始对XML数据进行解析和封装:
首先创建一个接口ISongCiParser,其内容如下:
- package com.example.songcidemo.data;
- import java.io.InputStream;
- import java.util.List;
- import com.example.songcidemo.bean.SongCi;
- public interface ISongCiParser {
- /**
- * 解析xml输入流
- *
- * @param is 输入流
- * @param scList 装载容器
- * @throws Exception
- */
- public void parse(InputStream is,List<SongCi> scList) throws Exception;
- }
这里插入一点写代码时候遇到的问题:因为之前想用SAX解析器去解析XML,但是做到一半的时候发现有问题,就是<desc></desc>之间的内容包含了很多<p></p><strong></strong><br></br>这样的标签对,SAX解析的时候把里面的内容都当作element进行了分割获取值,但是我想要的是 <desc></desc>之间的所有内容作为一个值,所以用SAX做到一半的时候就果断改用PULL解析器来解析,解析得很顺利,没有出现问题。继续......
接着我们写一个PULL解析实现类SongCiParserImpl,其内容如下:
- package com.example.songcidemo.data;
- import java.io.InputStream;
- import java.util.List;
- import org.xmlpull.v1.XmlPullParser;
- import android.util.Xml;
- import com.example.songcidemo.bean.SongCi;
- public class SongCiParserImpl implements ISongCiParser{
- //定义XML文件标签常量,常量值与XML文件内的标签名一致
- private static final String TAG_NODE = "node";
- private static final String TAG_TITLE = "title";
- private static final String TAG_AUTH = "auth";
- private static final String TAG_DESC = "desc";
- /**
- * 解析xml文件的方法
- *
- * is 输入流
- * scList 装载数据解析完后并封装成SongCi的链表
- */
- @Override
- public void parse(InputStream is, List<SongCi> scList) throws Exception {
- SongCi sc = null;
- if(scList != null){
- scList.clear();
- }
- //获取XmlPullParser实例
- XmlPullParser xpp = Xml.newPullParser();
- //为XmlPullParser实例设置输入流,并设置输入流的字符集是utf-8
- xpp.setInput(is,"utf-8");
- //获取当前事件的类型,比如START_TAG,END_TAG,TEXT等等
- int eventType = xpp.getEventType();
- //如果当前时间的类型不是文件结束的时候执行循环
- while(eventType != XmlPullParser.END_DOCUMENT){
- switch (eventType) {
- case XmlPullParser.START_DOCUMENT:
- //do nothing
- break;
- //如果当前的事件类型是开始元素
- case XmlPullParser.START_TAG:
- if(xpp.getName().equals(TAG_NODE)){
- //如果遇到<node>就新建一个SongCi对象
- sc = new SongCi();
- }else if(xpp.getName().equals(TAG_TITLE)){
- //如果遇到<title>就将<title>后面的text传递给sc
- sc.setTitle(xpp.nextText());
- }else if(xpp.getName().equals(TAG_AUTH)){
- //如果遇到<auth>就将<auth>后面的text传递给sc
- sc.setAuth(xpp.nextText());
- }else if(xpp.getName().equals(TAG_DESC)){
- //如果遇到<desc>就将<desc>后面的text传递给sc
- sc.setDesc(xpp.nextText());
- }
- break;
- case XmlPullParser.END_TAG:
- if(xpp.getName().equals(TAG_NODE)){
- //如果遇到</node>就将sc所关联的对象加入到链表中
- scList.add(sc);
- sc = null;
- }
- break;
- default:
- break;
- }
- //进入下一个元素并触发相应的事件
- eventType = xpp.next();
- }
- }
- }
这一步写好了,我们就可以在Activity里面去直接使用了,在MainActivity当中我已经把SAX的部分注释掉了,其他的代码也做了详细的注释,内容如下:
- package com.example.songcidemo.ui;
- import java.io.InputStream;
- import java.util.ArrayList;
- import javax.xml.parsers.SAXParser;
- import javax.xml.parsers.SAXParserFactory;
- import org.xml.sax.InputSource;
- import org.xml.sax.XMLReader;
- import android.app.Activity;
- import android.content.Intent;
- import android.content.res.AssetManager;
- import android.os.Bundle;
- import android.view.View;
- import android.view.Window;
- import android.widget.AdapterView;
- import android.widget.AdapterView.OnItemClickListener;
- import android.widget.ListView;
- import com.example.songcidemo.R;
- import com.example.songcidemo.bean.SongCi;
- import com.example.songcidemo.data.MainListViewAdapter;
- import com.example.songcidemo.data.SongCiParserImpl;
- import com.example.songcidemo.data.SongCiSaxHandler;
- import com.example.songcidemo.util.Global;
- public class MainActivity extends Activity {
- //声明装载SongCi类型的链表
- private ArrayList<SongCi> scList;
- //声明了一个ListView变量
- private ListView mListView;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.activity_main);
- initData();
- // saxParseXML();
- pullParseXML();
- setupViews();
- }
- private void initData(){
- //初始化scList
- scList = new ArrayList<SongCi>();
- }
- /**
- * 用SAX解析器解析XML文件
- */
- private void saxParseXML(){
- try {
- //获取一个AssetManager对象
- AssetManager assetManager = this.getAssets();
- //通过assetManager的open方法获取到songci.mp3的输入流
- InputStream inputStream = assetManager.open("songci.mp3");
- //将inputstream的内容封装成InputSource
- InputSource inputSource = new InputSource(inputStream);
- //获取SAXParserFactory实例
- SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
- //获取SAXParser对象
- SAXParser saxParser = saxParserFactory.newSAXParser();
- //获取XMLReader对象
- XMLReader xmlReader = saxParser.getXMLReader();
- //初始化scSaxHandler
- SongCiSaxHandler scSaxHandler = new SongCiSaxHandler(scList);
- //将scSaxHandler传递给xmlReader
- xmlReader.setContentHandler(scSaxHandler);
- //开始解析xml文件
- xmlReader.parse(inputSource);
- //关闭流
- inputStream.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 用PULL方式解析XML文件
- */
- private void pullParseXML(){
- try {
- InputStream is = this.getAssets().open("songci.mp3");
- SongCiParserImpl scpi = new SongCiParserImpl();
- scpi.parse(is, scList);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * 初始化视图
- */
- private void setupViews(){
- mListView = (ListView) findViewById(R.id.lv_catelog);
- //初始化自定义类型MainListViewAdapter的实例adapter,将scList传递给adapter的构造器
- MainListViewAdapter adapter = new MainListViewAdapter(this, scList);
- //将adapter传递给mListView
- mListView.setAdapter(adapter);
- mListView.setOnItemClickListener(new OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view,
- int position, long id) {
- Global.currentSongCi = scList.get(position);
- Intent intent = new Intent(MainActivity.this, ContentActivity.class);
- startActivity(intent);
- }
- });
- }
- }
因为这里面有一个自定义的Adapter,所以这里给出MainListViewAdapter的定义,方便大家阅读:(这个类我没有加注释,如果读者感觉阅读困难,建议先看一下这篇文章 自定义ListView)
- package com.example.songcidemo.data;
- import java.util.ArrayList;
- import android.content.Context;
- import android.view.LayoutInflater;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.BaseAdapter;
- import android.widget.TextView;
- import com.example.songcidemo.R;
- import com.example.songcidemo.bean.SongCi;
- public class MainListViewAdapter extends BaseAdapter{
- private ArrayList<SongCi> scList;
- private Context context;
- public MainListViewAdapter(Context context, ArrayList<SongCi> scList){
- this.context = context;
- this.scList = scList;
- }
- @Override
- public int getCount() {
- return scList.size();
- }
- @Override
- public Object getItem(int position) {
- return scList.get(position);
- }
- @Override
- public long getItemId(int position) {
- return position;
- }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ListViewItemHolder holder;
- if(convertView == null){
- LayoutInflater inflater = LayoutInflater.from(context);
- convertView = inflater.inflate(R.layout.list_item, null);
- holder = new ListViewItemHolder();
- holder.titleTextView = (TextView) convertView.findViewById(R.id.tv_title);
- holder.authTextView = (TextView) convertView.findViewById(R.id.tv_auth);
- convertView.setTag(holder);
- }else{
- holder = (ListViewItemHolder) convertView.getTag();
- }
- SongCi sc = scList.get(position);
- String title = sc.getTitle();
- String auth = sc.getAuth();
- holder.titleTextView.setText(title);
- holder.authTextView.setText(auth);
- return convertView;
- }
- private class ListViewItemHolder{
- TextView titleTextView;
- TextView authTextView;
- }
- }
附上一张MainActivity的界面截图:
更多内容,下回分解......