標籤: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 {/** * 啟動時最先執行的回調方法 */@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//設定介面沒有標題列requestWindowFeature(Window.FEATURE_NO_TITLE);//指定介面的布局檔案setContentView(R.layout.activity_splash);//初始化一個HandlerHandler handler = new Handler();//Runnable是一個線程,在1500毫秒以後執行線程對象handler.postDelayed(new Runnable() {@Overridepublic void run() {//從SplashActivity跳轉到MainActivityIntent intent = new Intent(SplashActivity.this, MainActivity.class);startActivity(intent);//在後台關閉掉SplashActivitySplashActivity.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的鏈表 */@Overridepublic void parse(InputStream is, List<SongCi> scList) throws Exception {SongCi sc = null;if(scList != null){scList.clear();}//擷取XmlPullParser執行個體XmlPullParser xpp = Xml.newPullParser();//為XmlPullParser執行個體設定輸入資料流,並設定輸入資料流的字元集是utf-8xpp.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 nothingbreak;//如果當前的事件類型是開始元素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傳遞給scsc.setTitle(xpp.nextText());}else if(xpp.getName().equals(TAG_AUTH)){//如果遇到<auth>就將<auth>後面的text傳遞給scsc.setAuth(xpp.nextText());}else if(xpp.getName().equals(TAG_DESC)){//如果遇到<desc>就將<desc>後面的text傳遞給scsc.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() {@Overridepublic 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;}@Overridepublic int getCount() {return scList.size();}@Overridepublic Object getItem(int position) {return scList.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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應用之《宋詞三百首》(一)