模仿網易新聞客戶端(RSS版)(二)


一、摘要

繼上一篇博客《模仿網易新聞客戶端(一)》之后,筆者繼續開發我們自己的“網易新聞客戶端”,由於找不到現成的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

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

View Code
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的問題,自定義旋轉圖片的時候,以前總是不知道怎樣讓它勻速循環轉動,現在好像也解決了,反正收獲不少吧。此“項目”權當閑來練手,沒什么實際用處,希望各位大牛多多指點。
(附上源代碼 下載地址,訪問密碼:qDPzYL)






 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM