简易的Android新闻客户端

学完Android基础之后不知道该怎么办?现在开始实战吧!
现在来看看一款简易的Android新闻客户端是怎么做的,当然,获取网络数据的这一部分我是使用别人做好的本地客户端,然后通过组建本地数据库来使用的,这一部分我就不详细介绍了。
本文Demo的地址
https://github.com/liaozhoubei/NetNewsDemo

本篇文章的技术要点:

  • ListView适配器的使用
  • Handler的消息发送
  • SQLite的使用
  • JSon数据的解析
  • 网络与IO流的使用

做个简易新闻客户端的难度并不大,只要想通了其中的几个要点,做起来就很简单。

制作流程

1、首先需要确定一个listView的布局
2、然后找到listView,设置条目的点击事件
3、获取网络数据给listView做展示
4、创建一个BaseAdapter的子类,接收获取的新闻数据
5、将adapter设置给ListView
现在让我们开始做这个新闻客户端吧!

客户端布局

我们先看看说要获取的JSon数据中拥有什么条目:

{
    "newss": [
        {
            "id": 2,
            "time": "2015-08-07",
            "des": "7月29日,历经9个月数百万人内测完善之后,微软终于发布Win10正式版系统。但是可能对于部分用户而言,Win7仍然是绝对的经典、游戏玩家的不二之选,为何非要升级到Win10系统呢?Windows10性能和功能相比Windows7,有提升吗?下面IT之家就为大家带来Win7与Win10功能与性能的正面PK,相信还在犹豫不决的用户看完本文心里就会有了答案。",
            "title": "升还是不升:Win7、Win10全面对比评测",
            "news_url": "http://toutiao.com/a5229867988/",
            "icon_url": "http://p2.pstatp.com/large/6850/6105376239",
            "comment": 5000,
            "type": 1
        }
    ]
}

OK,我们发现JSon新闻数据中要包含这几个类别:

  • id:新闻的ID号
  • time:新闻发布的时间
  • des: 新闻的内容
  • time:新闻的标题
  • news_url:新闻的链接地址
  • icon_url:新闻图片的链接地址
  • comment: 新闻的评论数
  • type:新闻是属于那种类别,0 是头条新闻, 1为娱乐新闻,2为体育新闻

因此我们的布局界面应该如下:


news.png

在开始写ListView之前我们需要先写出获取新闻的bean对象,每个对象都有get和set方法,由于篇幅有限就省略了一部分,代码如下:

public class NewsBean {
private int id;
private int comment;
private int type;
private String time;
private String title;
private String news_url;
private String icon_url;
private String des;
public int getId() {
    return id;
}
public void setId(int id) {
    this.id = id;
}
·····
}

然后是ListView每个Item的布局,相信布局这方面的内容难不倒大家,所以也缩写了一部分如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"    
android:orientation="horizontal" >

<com.example.netnewsdemo.myview.MyImageView
    android:id="@+id/item_img_icon"
    ··· />

<LinearLayout
   ···
    android:orientation="vertical" >

    <TextView
        android:id="@+id/item_tv_title"
        ··· />

    <TextView
        android:id="@+id/item_tv_des"
       ···/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center" >

        <TextView
            android:id="@+id/item_tv_comment"
          ···/>

        <TextView
            android:id="@+id/item_tv_type"
           ··· />
    </RelativeLayout>
</LinearLayout>
</LinearLayout>

其中有个<com.example.netnewsdemo.myview.MyImageView>的控件,这个是自定义的从网络中获取图片的视图控件。
然后我们要设置adapter适配器放置到ListView中去,所以我们先去完成适配器吧!

NewsAdapter

继承BaseAdapter的NewsAdapter需要有这两个成员变量

    private LayoutInflater mLayoutInflater;
    private List<NewsBean> mDatas; // 获取到的新闻集合

还需要一个构造器获取传递过来的数据

    public NewsAdapter(Context context, List<NewsBean> listNewsBean){
    this.mLayoutInflater = LayoutInflater.from(context);
    this.mDatas = listNewsBean;
}

然后在重写继承的4个方法

    @Override
public int getCount() {
    return mDatas.size();
}

@Override
public Object getItem(int position) {
    return mDatas.get(position);
}

@Override
public long getItemId(int position) {
    return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder;
    if (convertView == null) {
        convertView = mLayoutInflater.inflate(R.layout.layout_item, null);
        viewHolder = new ViewHolder();
        viewHolder.item_img_icon = (MyImageView) convertView.findViewById(R.id.item_img_icon);;
        viewHolder.item_tv_des = (TextView) convertView.findViewById(R.id.item_tv_des);
        viewHolder.item_tv_title = (TextView) convertView.findViewById(R.id.item_tv_title);
        viewHolder.item_tv_comment = (TextView) convertView.findViewById(R.id.item_tv_comment);
        viewHolder.item_tv_type = (TextView) convertView.findViewById(R.id.item_tv_type);
        convertView.setTag(viewHolder);
    } else{
        viewHolder = (ViewHolder) convertView.getTag();
    }
    NewsBean newsBean= mDatas.get(position);
    viewHolder.item_img_icon.setImageUrl(newsBean.getIcon_url());
    viewHolder.item_tv_des.setText(newsBean.getDes());
    viewHolder.item_tv_title.setText(newsBean.getTitle());
    viewHolder.item_tv_comment.setText(newsBean.getComment() + "");
    //0 :头条 1 :娱乐 2.体育
    switch (newsBean.getType()) {
    case 0:
        viewHolder.item_tv_type.setText("头条");
        break;
    case 1:
        viewHolder.item_tv_type.setText("娱乐 ");
        break;
    case 2:
        viewHolder.item_tv_type.setText("体育");
        break;
    default:
        break;
    }
    return convertView;
}
class ViewHolder{
    MyImageView item_img_icon;
    TextView item_tv_des;
    TextView item_tv_title;
    TextView item_tv_comment;
    TextView item_tv_type;
}

这样整个ListView的adapter对象就弄好了,现在只需要在activity_main.xml中加入ListView视图,然后在MainActivity.java绑定ListView就可以了。

自定义视图

布局现在还没写好,因为我们还有一个获取网络图片的自定义视图没搞好,现在来解决它吧。
自定义视图继承自ImageView,需要继承两个构造方法:

public MyImageView(Context context) {
    super(context);
}

public MyImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

我们从json数据中获取的只是网络图片的url地址,因此我们还需要将网络流转换为地址,然后通过Message将图片发送到主线程中,以避免在子线程更新UI线程

    private Handler mHandler = new Handler() {
      public void handleMessage(android.os.Message msg) {
        Bitmap bitmap = (Bitmap) msg.obj;
        MyImageView.this.setImageBitmap(bitmap);
    };
};
public void setImageUrl(final String urlString) {
    new Thread(new Runnable() {

        @Override
        public void run() {
            try {
                URL url = new URL(urlString);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5 * 1000);
                int code = conn.getResponseCode();
                if (code == 200) {
                    InputStream is = conn.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(is);
                    Message message = Message.obtain();
                    message.obj = bitmap;
                    mHandler.sendMessage(message);
                }
                

            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }).start();;

}    

详细的MyImageView类还是看我的项目代码吧,哈哈!

好了,现在整个视图的布局就完成了,剩下的就是将获取网络数据放在适配器中就可以了

获取网络数据

在获取网络数据之前,我们先编写一个工具类,将InputStream流直接转换为String字符串输出的工具类,代码如下:

public class StreamUtils {
public static String convertStream(InputStream is) {
    String result = "";
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    byte[] bt = new byte[1024];
    int len = 0;
    try {
        while ((len = is.read(bt)) != -1) {
            bos.write(bt, 0, len);
            bos.flush();
        }
        result = new String(bos.toByteArray(), "utf-8");
        result = bos.toString();
        bos.close();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }       
    return result;      
}
}

现在终于可以正式的获取网络中的json数据了,这个类中写一个getNetNews方法,传出Context和URL参数,返回ArrayList<NewsBean>集合类型。
对于JSon数据,我们研究可以得知,它是一个JSONObject,里面包含一个newss的JSONArray数组,数组里面在包含着一个个新闻对象。
整个获取网络数据并解析json数据的方法如下:

    public static ArrayList<NewsBean> getNetNews(Context context, String urlString) {
    ArrayList<NewsBean> arraylistNews = new ArrayList<NewsBean>();
    try {
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(20 * 1000);
        int responseCode = conn.getResponseCode();          
        if (responseCode == 200) {
            // 获取请求到的流信息
            InputStream is = conn.getInputStream();
            // 通过之前的建立的StreamUtils工具类转换流信息
            String result = StreamUtils.convertStream(is);
            JSONObject root_json = new JSONObject(result);
            JSONArray jsonArray  = root_json.getJSONArray("newss");
            for (int i = 0; i < jsonArray .length(); i ++ ){
                JSONObject news_json = jsonArray.getJSONObject(i);
                NewsBean newsBean = new NewsBean();
                newsBean.setId(news_json.getInt("id"));
                newsBean.setTime(news_json.getString("time"));
                newsBean.setDes(news_json.getString("des"));
                newsBean.setTitle(news_json.getString("title"));
                newsBean.setNews_url(news_json.getString("news_url"));
                newsBean.setIcon_url(news_json.getString("icon_url"));
                newsBean.setComment(news_json.getInt("comment"));
                newsBean.setType(news_json.getInt("type"));
                arraylistNews.add(newsBean);                    
            }           
            is.close();             
        }           
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return arraylistNews;
}

补全MainActivity

所有的工具都写完了,我们可以返回补全MainAcitivty了,在MainActivity中有个Handler,它将网络中返回的json数据反正NewsAdapter总,然后更新ListView中的数据。

    private static String result = "傳入包含Json數據的網頁URL";
    private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
        listNewsBean = (List<NewsBean>) msg.obj;
        NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this, listNewsBean);
        listview.setAdapter(newsAdapter);
    };
};
    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mContext = MainActivity.this;
    listview = (ListView) findViewById(R.id.list_news);
    newsUtils = new NewsUtils();
    new Thread(new Runnable() {
        @Override
        public void run() {
            listNewsBean = newsUtils.getNetNews(mContext, result);
            Message message = Message.obtain();
            message.obj = listNewsBean;
            mHandler.sendMessage(message);
        }
    }).start();     
    listview.setOnItemClickListener(this); // 设置listView的点击事件,略过

}

基本的新闻客户端设置就到此结束了,但是这只是一个非常简单的客户端,如果是真实的新闻客户端还有很多事情需要做,如设置新闻缓存,当客户端没有联网的时候从数据库中获取新闻等。
现在也顺便写一下如何用数据库缓存新闻吧!

使用SQLite数据库缓存新闻。

使用SQLite需要一个SQLiteOpenHelper类,然后自己封装一个SQLite操作类。
SQLiteOpenHelper类用于建立数据库,并且设置数据库的列表形式,SQLite操作类这是负责数据库的增删查改等工作。
SQLiteOpenHelper类代码如下:

public class NewsDBHelper extends SQLiteOpenHelper{

public NewsDBHelper(Context context) {
    super(context, "NetNews", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
    
    db.execSQL("create table news (_id integer, title varchar(200), des varchar(300), "
            + "icon_url varchar(100), news_url varchar(200), type integer, time varchar(100), comment integer)");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // TODO Auto-generated method stub      
}
}

SQLite操作类代码如下:

public class NewsDBUtils {
private NewsDBHelper dbHelper;  
public NewsDBUtils (Context context) {
    dbHelper = new NewsDBHelper(context);
}   
// 保存新闻到数据库中
public void saveNews(ArrayList<NewsBean> arrayList) {
    SQLiteDatabase sqLite = dbHelper.getWritableDatabase();
    for(NewsBean newsBean : arrayList) {
        ContentValues value = new ContentValues();
        value.put("_id", newsBean.getId());
        value.put("time", newsBean.getTime());
        value.put("des", newsBean.getDes());
        value.put("title", newsBean.getTitle());
        value.put("news_url", newsBean.getNews_url());
        value.put("icon_url", newsBean.getIcon_url());
        value.put("comment", newsBean.getComment());
        value.put("type", newsBean.getType());
        sqLite.insert("news", null, value);
    }
    sqLite.close();
}   

// 删除数据库数据
public void deleteNews (){
    SQLiteDatabase db = dbHelper.getReadableDatabase();
    db.delete("news", null, null);
    db.close();
}

// 从数据库中获取存储的行为
public ArrayList<NewsBean> getNews() {
    ArrayList<NewsBean> arrayList = new ArrayList<NewsBean>();
    SQLiteDatabase db = dbHelper.getReadableDatabase();
    Cursor cursor = db.query("news", null, null, null, null, null, null, null);
    if (cursor != null && cursor.getCount() > 0) {
        while (cursor.moveToNext()) {
            NewsBean newsBean = new NewsBean();
            newsBean.setId(cursor.getInt(0));
            newsBean.setTime(cursor.getString(1));
            newsBean.setDes(cursor.getString(2));
            newsBean.setTitle(cursor.getString(3));
            newsBean.setNews_url(cursor.getString(4));
            newsBean.setIcon_url(cursor.getString(5));
            newsBean.setComment(cursor.getInt(6));
            newsBean.setType(cursor.getInt(7));
            arrayList.add(newsBean);
        }
    }
    
    return arrayList;
}
}

返回修改获取网络数据类NetUtils和MainActivity类

修改NetUtils在getNetNews的InpuStream流关闭之前(is.close())添加两行代码:

// 如果获取到网络上的数据,就删除之前获取的新闻数据,保存新的新闻数据
            new NewsDBUtils(context).deleteNews();
            new NewsDBUtils(context).saveNews(arraylistNews);

然后给NetUtils添加一个获取数据库缓存新闻的方法:

// 返回数据库缓存到的数据
public static ArrayList<NewsBean> getDBNews(Context context){
    
    return new NewsDBUtils(context).getNews();
}

OK,最后就是修改MainActivity中的代码了,我们需要在onCreate()开启新线程,获取网络数据之前,从数据库中获取到之前缓存的新闻,这样才不会在网速缓慢的时候界面空白一片,增加一些代码:

        // 1.先去数据库中获取缓存的新闻数据展示到listview
    ArrayList<NewsBean> allnews_database = NewsUtils.getDBNews(mContext);

    if (allnews_database != null && allnews_database.size() > 0) {
        // 创建一个adapter设置给listview
        NewsAdapter newsAdapter = new NewsAdapter(mContext, allnews_database);
        listview.setAdapter(newsAdapter);
    }

总结

一个简易的网络新闻客户端的制作流程就写到这里,可能凌乱了一下,但是结合我在github的代码还是能够看明白的。
另外如果需要这个新闻客户端获取本地web服务方面的代码,也可以联系我。
最后就是如果哪位高手发现我的代码还有改进的地方,还请不吝赐教~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,488评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  • 冷却心阅读 120评论 0 0
  • 可想而知,让你每天早上的饮品就是牛奶,一年365天毫无变化,生活该变得多没意思。可是如果告别牛奶,还真不知道自己能...
    晃悠的老刘忙阅读 565评论 1 4
  • 当我又交到一个好朋友的时候,幸福就在眼前。 当我会折纸青蛙的时候,幸福就在眼前。 当我去红梅公园玩的时候,幸福就在...
    徐寅博阅读 180评论 2 2