三、解析RSS
首先,先大概地看一下RSS所提供XML的数据结构,下面是一个RSS文件结构示例
<?xml version="1.0" encoding="GBK"?> <?xml-stylesheet type="text/css" ?> <rss version="2.0"> <channel> <title>网易头条新闻</title> <link><a href="\"http://news.163.com/</link\"" target="\"_blank\"">http://news.163.com/</link></a> <description>网易头条新闻</description> <pubDate>Mon, 2 Apr 2012 01:07:10 GMT</pubDate> <lastBuildDate>Mon, 2 Apr 2012 01:07:10 GMT</lastBuildDate> <item id="1"> <title>...</title> <link><a href="\"http://news.163.com/12/0402/08/7U2TBKQF0001124J.html</link\"" target="\"_blank\"">http://news.163.com/12/0402/08/7U2TBKQF0001124J.html</link></a> <description>......</description> <pubDate>2012-04-02 09:07:10</pubDate> </item> </channel> </rss>
了解了有哪些节点,下面来编写元数据类RSSItem.java
package com.and.netease.rss; public class RSSItem { private String title; private String link; private String description; private String pubDate; public RSSItem() { super(); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getPubDate() { return pubDate; } public void setPubDate(String pubDate) { this.pubDate = pubDate; } @Override public String toString() { return "RSSItem [title=" + title + ", link=" + link + ", description=" + description + ", pubDate=" + pubDate + "]"; } }
紧接着编写解析XML的Handler类:RSSHandler.java
package com.and.netease.rss; import java.util.ArrayList; import java.util.List; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.text.Html; public class RSSHandler extends DefaultHandler { private List<RSSItem> list; private RSSItem item; private String tag = ""; private StringBuffer buffer; @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); if(item!=null){ String data = new String(ch,start,length); if(tag.equals("title")){ item.setTitle(data); }else if(tag.equals("link")){ item.setLink(data); }else if(tag.equals("description")){ // item.setDescription(data); buffer.append(Html.fromHtml(data)); }else if(tag.equals("pubDate")){ item.setPubDate(data); } } } @Override public void endDocument() throws SAXException { super.endDocument(); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); if(localName.equals("item")){ item.setDescription(buffer.toString()); list.add(item); item = null; buffer = null; } tag = ""; } @Override public void startDocument() throws SAXException { super.startDocument(); list = new ArrayList<RSSItem>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if(localName.equals("item")){ item = new RSSItem(); buffer = new StringBuffer(); } tag = localName; } public List<RSSItem> getData(){ return list; } }
注意:在存description的值的时候,可能会出问题,有可能读取出来的全是省略号,因为第一次读取到数据,存入了相应的RSSItem实例变量中,第二次又读取到省略号,再存入当前的RSSItem实例中,造成最后只有第二次存入的值,因为它前面的文字和后面的省略号不是一次读取完的,所以我这里用了一个StringBuffer来存它,之后一次性地存入到当拉RSSItem中。当然有些提供的RSS源代码中不是这样的。具体需要自己测试一下才好。
当然,这里所解析的所有URL地址放在一个常量类里面的,开始本想放String.xml文件中,但是地址里面有特殊字符,为了简便,就专门定义了一个常量类用来存放URL地址,如下CONST.java
View Code package com.and.netease; public class CONST { public static final String URL_NEWS_TOP = "http://news.163.com/special/00011K6L/rss_newstop.xml"; public static final String URL_NEWS_SPORT = "http://sports.163.com/special/00051K7F/rss_sportslq.xml"; public static final String URL_NEWS_PLAY = "http://ent.163.com/special/00031K7Q/rss_toutiao.xml"; public static final String URL_NEWS_FINANCE = "http://money.163.com/special/00252EQ2/yaowenrss.xml"; public static final String URL_NEWS_SCIENCE = "http://tech.163.com/special/000944OI/headlines.xml"; //国内 public static final String URL_NEWS_DOMESTIC = "http://news.163.com/special/00011K6L/rss_gn.xml"; //军事 public static final String URL_NEWS_MILITARY = "http://news.163.com/special/00011K6L/rss_war.xml"; //国际 public static final String URL_NEWS_INTERNATIONAL = "http://news.163.com/special/00011K6L/rss_gj.xml"; //社会 public static final String URL_NEWS_COMMUNITY = "http://news.163.com/special/00011K6L/rss_sh.xml"; //深度 public static final String URL_NEWS_DEPTH = "http://news.163.com/special/00011K6L/rss_hotnews.xml"; //彩票 public static final String URL_NEWS_TICKET = "http://sports.163.com/special/00051K7F/rss_sportscp.xml"; //电影 public static final String URL_NEWS_FILM = "http://ent.163.com/special/00031K7Q/rss_entmovie.xml"; //音乐 public static final String URL_NEWS_MUSIC = "http://ent.163.com/special/00031K7Q/rss_entmusic.xml"; //IT public static final String URL_NEWS_IT = "http://tech.163.com/special/000944OI/kejiyejie.xml"; //汽车 public static final String URL_NEWS_CAR = "http://auto.163.com/special/00081K7D/rsstoutiao.xml"; //数码 public static final String URL_NEWS_DIGITAL = "http://tech.163.com/digi/special/00161K7K/rss_digixj.xml"; //网易话题 public static final String URL_TOPIC = "http://news.163.com/special/00011K6L/rss_newsspecial.xml"; //网易图片 public static final String URL_PICTURE = "http://news.163.com/special/00011K6L/rss_photo.xml"; //网易跟帖 public static final String URL_FOLLOW = ""; //网易投票 public static final String URL_VOTE = ""; }
后面的网易跟帖和投票,我实在找不到合适的URL地址了,所以就都用的网易图片的URL,因为里面我没有涉及到跟帖和投票的操作。如上最后一张图片所示。
四、关于Tab(新闻)页面的讲解
由于RSS格式的限制,所以里面的各个页面大体框架类似,下面只大概讲解一下“新闻”页面。它是TabHost里面的其中一个页面,在这个页面中涉及到另外几个页面,如“头条”、“体育”、“娱乐”、“财经”等一些,所以这个页面让它继承自ActivityGroup类,在这个ActivityGroup类里面可以管理很多的Activity。那要怎样把一个Activity添加到ActivityGroup中来呢?
intent = new Intent(TabNewsActivity.this, TabNewsTopActivity.class); page = getLocalActivityManager().startActivity("activity1", intent).getDecorView(); LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); layout_news_main.addView(page, params);
这样,就可以把一个Activity转化成View,然后添加到当前的ActivityGroup中。
另外,这里涉及到一个Progress的处理,当请求数据的时候,让它显示一个旋转的进度提示。这里面用到了ViewSwitcher这个类,在它里面添加两个视图View,然后在不同的时候控件它具体显示哪一个View而达到目的,我这里在ViewSwitcher里面分别添加了一个ListView和一个ProgressBar,当请求网络的时候,让它显示ProgressBar界面,请求完成,让它显示ListView。
viewSwitcher = (ViewSwitcher) findViewById(R.id.viewswitcher_news_top); listView = new MyListView(this); ... viewSwitcher.addView(listView); viewSwitcher.addView(getLayoutInflater().inflate(R.layout.layout_progress_page, null)); viewSwitcher.showNext();
完整代码TabNewsTopActivity.java
package com.and.netease; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.util.List; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import com.and.netease.MyListView.OnRefreshListener; import com.and.netease.rss.RSSHandler; import com.and.netease.rss.RSSItem; import android.app.Activity; import android.content.Intent; import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import android.widget.ViewSwitcher; public class TabNewsTopActivity extends Activity { MyListView listView; List<RSSItem> list; RSSHandler rssHandler; MyAdapter adapter; ViewSwitcher viewSwitcher; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_news_top); setTheme(android.R.style.Theme_Translucent_NoTitleBar); initViews(); rssHandler = new RSSHandler(); requestRSSFeed(); } private void initViews() { viewSwitcher = (ViewSwitcher) findViewById(R.id.viewswitcher_news_top); listView = new MyListView(this); listView.setCacheColorHint(Color.argb(0, 0, 0, 0)); ImageView testView = new ImageView(this); testView.setImageResource(R.drawable.temp); listView.addHeaderView(testView); listView.setonRefreshListener(refreshListener); viewSwitcher.addView(listView); viewSwitcher.addView(getLayoutInflater().inflate(R.layout.layout_progress_page, null)); viewSwitcher.showNext(); listView.setOnItemClickListener(listener); } private OnRefreshListener refreshListener = new OnRefreshListener() { @Override public void onRefresh() { // TODO Auto-generated method stub new AsyncTask<Void, Void, Void>() { protected Void doInBackground(Void... params) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void result) { adapter.notifyDataSetChanged(); listView.onRefreshComplete(); } }.execute(null); } }; private void requestRSSFeed() { Thread t = new Thread() { @Override public void run() { super.run(); try { URL url = new URL(CONST.URL_NEWS_TOP); URLConnection con = url.openConnection(); con.connect(); InputStream input = con.getInputStream(); SAXParserFactory fac = SAXParserFactory.newInstance(); SAXParser parser = fac.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setContentHandler(rssHandler); Reader r = new InputStreamReader(input, Charset.forName("GBK")); reader.parse(new InputSource(r)); list = rssHandler.getData(); // for (RSSItem rss : list) { // System.out.println(rss); // } if (list.size() == 0) { handler.sendEmptyMessage(-1); } else { handler.sendEmptyMessage(1); } } catch (Exception e) { e.printStackTrace(); } } }; t.start(); } Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { if (msg.what == 1) { adapter = new MyAdapter(); listView.setOnItemClickListener(listener); listView.setAdapter(adapter); viewSwitcher.showPrevious(); listView.onRefreshComplete(); } }; }; private OnItemClickListener listener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if(position==1){ return; } Intent intent = new Intent(TabNewsTopActivity.this, NewsContentActivity.class); intent.putExtra("content_url", list.get(position-2).getLink()); TabNewsTopActivity.this.startActivityForResult(intent, position); } }; private class MyAdapter extends BaseAdapter { @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { holder = new ViewHolder(); convertView = getLayoutInflater().inflate(R.layout.layout_news_top_item, null); holder.tv_date = (TextView) convertView.findViewById(R.id.tv_date_news_top_item); holder.tv_title = (TextView) convertView.findViewById(R.id.tv_title_news_top_item); holder.tv_Description = (TextView) convertView.findViewById(R.id.tv_description_news_top_item); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.tv_date.setText(list.get(position).getPubDate()); holder.tv_title.setText(list.get(position).getTitle()); holder.tv_Description.setText(list.get(position).getDescription()); return convertView; } } public static class ViewHolder { TextView tv_date; TextView tv_title; TextView tv_Description; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { System.out.println("返回"); super.onActivityResult(requestCode, resultCode, data); } }
几点说明:
1、细心的你有可能会发现,里面我用的是MyListView,它是自定义的一个ListView,为了实现下拉刷新而写的。如果不需要下拉刷新,直接换成ListView即可。但是,要注意,这里自定义MyListView之后,设置它的OnItemClickListener点击事件,Item里面的position从1开始,而非从0开始,下拉列表是从网上复制一的段,还没怎么研究,我猜的话,估计那个下拉出现的东西,有可能position就是0。这个之后再研究。关于下拉刷新,我会在另外一个博客中说明。现在我也还没弄明白。
2、这个类里面的ListView添加的Header,是一张静态图片,因为RSS代码里面没有提供图片链接,为了达到效果,暂且用一张图片代替。
五、具体新闻内容
具体的新闻内容,暂且用一个WebView来加载它,碍于RSS的限制,具体某一条新闻的Link链接地址,是一个完整的网页。当然请求的时候,照样显示旋转的进度条,跟之前的一个ViewSwitcher一样处理。新闻内容NewsContentActivity.java
package com.and.netease; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ImageButton; import android.widget.ViewSwitcher; public class NewsContentActivity extends Activity { ImageButton btn_back; WebView webView; String content_url; ViewSwitcher viewSwitcher; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_newscontent); initViews(); } private void initViews() { btn_back = (ImageButton) findViewById(R.id.btn_newscontent_back); btn_back.setOnClickListener(listener); viewSwitcher = (ViewSwitcher) findViewById(R.id.viewSwitcher); content_url = getIntent().getStringExtra("content_url"); webView = new WebView(this); // 向ViewSwitcher中添加两个View,用来切换 viewSwitcher.addView(webView); viewSwitcher.addView(getLayoutInflater().inflate( R.layout.layout_progress_page, null)); WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); webView.setWebViewClient(client); webView.loadUrl(content_url); } private WebViewClient client = new WebViewClient() { @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); viewSwitcher.showPrevious(); } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); viewSwitcher.showNext(); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { return super.shouldOverrideUrlLoading(view, url); } }; private OnClickListener listener = new OnClickListener() { @Override public void onClick(View v) { NewsContentActivity.this.setResult(RESULT_OK); NewsContentActivity.this.finish(); } }; public void onBackPressed() { if (webView.canGoBack()) { webView.goBack(); } else { NewsContentActivity.this.setResult(RESULT_OK); NewsContentActivity.this.finish(); } }; }
至此,这个版本算是基本上完成了。除了第一个Tab(新闻)页面稍微复杂点之外,其它页面基本一样,因为目前我所发现的RSS所能提供的仅是这些。
模仿网易新闻客户端的开发就告一段落了,如果找到合适的URL再继续吧,目前还有很多功能没有实现,比如天气、分享、跟帖等等很多东西。
六、总结
小小地总结一下,模仿网易新闻客户端,虽然没碰到很大的难点,但是还是有不少 的收获的,比如在自定义TabHost控件的时候,以前也遇到过点击不能切换背景的问题,当时在代码里面控制的。通过做这个东西,现在解决了,还有一个就是ProgressBar的问题,自定义旋转图片的时候,以前总是不知道怎样让它匀速循环转动,现在好像也解决了,反正收获不少吧。此“项目”权当闲来练手,没什么实际用处,希望各位大牛多多指点。