首页 > 代码库 > Android应用之《宋词三百首》(一)

Android应用之《宋词三百首》(一)

今天我们通过一个实际的案例来综合运用一下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,以避免这样的问题。


其次就是几个知识点的预备工作(如果你已经熟悉这些知识,请跳过):

SAX解析XML

PULL解析XML

那么下面我们开始对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的界面截图:


更多内容,下回分解......


Android应用之《宋词三百首》(一)