一、摘要继上一篇博客《模仿网易新闻客户端(上)》之后,笔者继续开发我们自己的“网易新闻客户端”,由于找不到现成的url新闻链接地址,所以这里就用RSS订阅所提供的url,这里所用到的链接仍然是网易新闻中心的RSS地址http://www.163.com/rss/,然后通过解析xml内容,以ListView的方式呈现在手机界面上。还有一个问题,因为RSS所提供的xml资源里面,没有对应item的图片,所以,ListView里面每一个Item都没有图片,这点有点遗憾,但也没事,实现其功能就行了。废话不多说,老惯例,先看效果图
三、解析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() {???? ???????? [email protected]???? ???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() {???? ?? ?? [email protected]???? ?? ?? ?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() {????? [email protected]???? ???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 {????? [email protected]???? ???public int getCount() {???? ?? ?? ?return list.size();???? ???}????? [email protected]???? ???public Object getItem(int position) {???? ?? ?? ?return null;???? ???}????? [email protected]???? ???public long getItemId(int position) {???? ?? ?? ?return 0;???? ???}????? [email protected]???? ???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() {????? [email protected]???? ???public void onPageFinished(WebView view, String url) {???? ?? ?? ?super.onPageFinished(view, url);???? ?? ?? ?viewSwitcher.showPrevious();???? ???}????? [email protected]???? ???public void onPageStarted(WebView view, String url, Bitmap favicon) {???? ?? ?? ?super.onPageStarted(view, url, favicon);???? ?? ?? ?viewSwitcher.showNext();???? ???}????? [email protected]???? ???public boolean shouldOverrideUrlLoading(WebView view, String url) {???? ?? ?? ?return super.shouldOverrideUrlLoading(view, url);???? ???}???? };???? private OnClickListener listener = new OnClickListener() {????? [email protected]???? ???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的问题,自定义旋转图片的时候,以前总是不知道怎样让它匀速循环转动,现在好像也解决了,反正收获不少吧。此“项目”权当闲来练手,没什么实际用处,希望各位大牛多多指点。