一、摘要
继上一篇博客《模仿网易新闻客户端(一)》之后,笔者继续开发我们自己的“网易新闻客户端”,由于找不到现成的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" href="http://news.163.com/css/allrss.css"?> <rss version="2.0"> <channel> <title>网易头条新闻</title> <link>http://news.163.com/</link> <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>http://news.163.com/12/0402/08/7U2TBKQF0001124J.html</link> <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
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);
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); } }
五、具体新闻内容
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(); } }; }