Android新闻客户端(仿网易)--下

一、摘要继上一篇博客《模仿网易新闻客户端(上)》之后,笔者继续开发我们自己的“网易新闻客户端”,由于找不到现成的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() {
         
        @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的问题,自定义旋转图片的时候,以前总是不知道怎样让它匀速循环转动,现在好像也解决了,反正收获不少吧。此“项目”权当闲来练手,没什么实际用处,希望各位大牛多多指点。

猜你喜欢

转载自leleguaiguai.iteye.com/blog/1997207