簡易的Android新聞客戶端

學(xué)完Android基礎(chǔ)之后不知道該怎么辦?現(xiàn)在開始實(shí)戰(zhàn)吧!
現(xiàn)在來看看一款簡易的Android新聞客戶端是怎么做的,當(dāng)然,獲取網(wǎng)絡(luò)數(shù)據(jù)的這一部分我是使用別人做好的本地客戶端,然后通過組建本地?cái)?shù)據(jù)庫來使用的,這一部分我就不詳細(xì)介紹了。
本文Demo的地址
https://github.com/liaozhoubei/NetNewsDemo

本篇文章的技術(shù)要點(diǎn):

  • ListView適配器的使用
  • Handler的消息發(fā)送
  • SQLite的使用
  • JSon數(shù)據(jù)的解析
  • 網(wǎng)絡(luò)與IO流的使用

做個(gè)簡易新聞客戶端的難度并不大,只要想通了其中的幾個(gè)要點(diǎn),做起來就很簡單。

制作流程

1、首先需要確定一個(gè)listView的布局
2、然后找到listView,設(shè)置條目的點(diǎn)擊事件
3、獲取網(wǎng)絡(luò)數(shù)據(jù)給listView做展示
4、創(chuàng)建一個(gè)BaseAdapter的子類,接收獲取的新聞數(shù)據(jù)
5、將adapter設(shè)置給ListView
現(xiàn)在讓我們開始做這個(gè)新聞客戶端吧!

客戶端布局

我們先看看說要獲取的JSon數(shù)據(jù)中擁有什么條目:

{
    "newss": [
        {
            "id": 2,
            "time": "2015-08-07",
            "des": "7月29日,歷經(jīng)9個(gè)月數(shù)百萬人內(nèi)測完善之后,微軟終于發(fā)布Win10正式版系統(tǒng)。但是可能對于部分用戶而言,Win7仍然是絕對的經(jīng)典、游戲玩家的不二之選,為何非要升級到Win10系統(tǒng)呢?Windows10性能和功能相比Windows7,有提升嗎?下面IT之家就為大家?guī)鞼in7與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,我們發(fā)現(xiàn)JSon新聞數(shù)據(jù)中要包含這幾個(gè)類別:

  • id:新聞的ID號
  • time:新聞發(fā)布的時(shí)間
  • des: 新聞的內(nèi)容
  • time:新聞的標(biāo)題
  • news_url:新聞的鏈接地址
  • icon_url:新聞圖片的鏈接地址
  • comment: 新聞的評論數(shù)
  • type:新聞是屬于那種類別,0 是頭條新聞, 1為娛樂新聞,2為體育新聞

因此我們的布局界面應(yīng)該如下:


news.png

在開始寫ListView之前我們需要先寫出獲取新聞的bean對象,每個(gè)對象都有g(shù)et和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每個(gè)Item的布局,相信布局這方面的內(nèi)容難不倒大家,所以也縮寫了一部分如下

<?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>

其中有個(gè)<com.example.netnewsdemo.myview.MyImageView>的控件,這個(gè)是自定義的從網(wǎng)絡(luò)中獲取圖片的視圖控件。
然后我們要設(shè)置adapter適配器放置到ListView中去,所以我們先去完成適配器吧!

NewsAdapter

繼承BaseAdapter的NewsAdapter需要有這兩個(gè)成員變量

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

還需要一個(gè)構(gòu)造器獲取傳遞過來的數(shù)據(jù)

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

然后在重寫繼承的4個(gè)方法

    @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;
}

這樣整個(gè)ListView的adapter對象就弄好了,現(xiàn)在只需要在activity_main.xml中加入ListView視圖,然后在MainActivity.java綁定ListView就可以了。

自定義視圖

布局現(xiàn)在還沒寫好,因?yàn)槲覀冞€有一個(gè)獲取網(wǎng)絡(luò)圖片的自定義視圖沒搞好,現(xiàn)在來解決它吧。
自定義視圖繼承自ImageView,需要繼承兩個(gè)構(gòu)造方法:

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

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

我們從json數(shù)據(jù)中獲取的只是網(wǎng)絡(luò)圖片的url地址,因此我們還需要將網(wǎng)絡(luò)流轉(zhuǎn)換為地址,然后通過Message將圖片發(fā)送到主線程中,以避免在子線程更新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();;

}    

詳細(xì)的MyImageView類還是看我的項(xiàng)目代碼吧,哈哈!

好了,現(xiàn)在整個(gè)視圖的布局就完成了,剩下的就是將獲取網(wǎng)絡(luò)數(shù)據(jù)放在適配器中就可以了

獲取網(wǎng)絡(luò)數(shù)據(jù)

在獲取網(wǎng)絡(luò)數(shù)據(jù)之前,我們先編寫一個(gè)工具類,將InputStream流直接轉(zhuǎn)換為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;      
}
}

現(xiàn)在終于可以正式的獲取網(wǎng)絡(luò)中的json數(shù)據(jù)了,這個(gè)類中寫一個(gè)getNetNews方法,傳出Context和URL參數(shù),返回ArrayList<NewsBean>集合類型。
對于JSon數(shù)據(jù),我們研究可以得知,它是一個(gè)JSONObject,里面包含一個(gè)newss的JSONArray數(shù)組,數(shù)組里面在包含著一個(gè)個(gè)新聞對象。
整個(gè)獲取網(wǎng)絡(luò)數(shù)據(jù)并解析json數(shù)據(jù)的方法如下:

    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工具類轉(zhuǎn)換流信息
            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;
}

補(bǔ)全MainActivity

所有的工具都寫完了,我們可以返回補(bǔ)全MainAcitivty了,在MainActivity中有個(gè)Handler,它將網(wǎng)絡(luò)中返回的json數(shù)據(jù)反正NewsAdapter總,然后更新ListView中的數(shù)據(jù)。

    private static String result = "傳入包含Json數(shù)據(jù)的網(wǎng)頁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); // 設(shè)置listView的點(diǎn)擊事件,略過

}

基本的新聞客戶端設(shè)置就到此結(jié)束了,但是這只是一個(gè)非常簡單的客戶端,如果是真實(shí)的新聞客戶端還有很多事情需要做,如設(shè)置新聞緩存,當(dāng)客戶端沒有聯(lián)網(wǎng)的時(shí)候從數(shù)據(jù)庫中獲取新聞等。
現(xiàn)在也順便寫一下如何用數(shù)據(jù)庫緩存新聞吧!

使用SQLite數(shù)據(jù)庫緩存新聞。

使用SQLite需要一個(gè)SQLiteOpenHelper類,然后自己封裝一個(gè)SQLite操作類。
SQLiteOpenHelper類用于建立數(shù)據(jù)庫,并且設(shè)置數(shù)據(jù)庫的列表形式,SQLite操作類這是負(fù)責(zé)數(shù)據(jù)庫的增刪查改等工作。
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);
}   
// 保存新聞到數(shù)據(jù)庫中
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();
}   

// 刪除數(shù)據(jù)庫數(shù)據(jù)
public void deleteNews (){
    SQLiteDatabase db = dbHelper.getReadableDatabase();
    db.delete("news", null, null);
    db.close();
}

// 從數(shù)據(jù)庫中獲取存儲的行為
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;
}
}

返回修改獲取網(wǎng)絡(luò)數(shù)據(jù)類NetUtils和MainActivity類

修改NetUtils在getNetNews的InpuStream流關(guān)閉之前(is.close())添加兩行代碼:

// 如果獲取到網(wǎng)絡(luò)上的數(shù)據(jù),就刪除之前獲取的新聞數(shù)據(jù),保存新的新聞數(shù)據(jù)
            new NewsDBUtils(context).deleteNews();
            new NewsDBUtils(context).saveNews(arraylistNews);

然后給NetUtils添加一個(gè)獲取數(shù)據(jù)庫緩存新聞的方法:

// 返回?cái)?shù)據(jù)庫緩存到的數(shù)據(jù)
public static ArrayList<NewsBean> getDBNews(Context context){
    
    return new NewsDBUtils(context).getNews();
}

OK,最后就是修改MainActivity中的代碼了,我們需要在onCreate()開啟新線程,獲取網(wǎng)絡(luò)數(shù)據(jù)之前,從數(shù)據(jù)庫中獲取到之前緩存的新聞,這樣才不會在網(wǎng)速緩慢的時(shí)候界面空白一片,增加一些代碼:

        // 1.先去數(shù)據(jù)庫中獲取緩存的新聞數(shù)據(jù)展示到listview
    ArrayList<NewsBean> allnews_database = NewsUtils.getDBNews(mContext);

    if (allnews_database != null && allnews_database.size() > 0) {
        // 創(chuàng)建一個(gè)adapter設(shè)置給listview
        NewsAdapter newsAdapter = new NewsAdapter(mContext, allnews_database);
        listview.setAdapter(newsAdapter);
    }

總結(jié)

一個(gè)簡易的網(wǎng)絡(luò)新聞客戶端的制作流程就寫到這里,可能凌亂了一下,但是結(jié)合我在github的代碼還是能夠看明白的。
另外如果需要這個(gè)新聞客戶端獲取本地web服務(wù)方面的代碼,也可以聯(lián)系我。
最后就是如果哪位高手發(fā)現(xiàn)我的代碼還有改進(jìn)的地方,還請不吝賜教~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,168評論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,423評論 4 61
  • 冷卻心閱讀 137評論 0 0
  • 可想而知,讓你每天早上的飲品就是牛奶,一年365天毫無變化,生活該變得多沒意思。可是如果告別牛奶,還真不知道自己能...
    晃悠的老劉忙閱讀 683評論 1 4
  • 當(dāng)我又交到一個(gè)好朋友的時(shí)候,幸福就在眼前。 當(dāng)我會折紙青蛙的時(shí)候,幸福就在眼前。 當(dāng)我去紅梅公園玩的時(shí)候,幸福就在...
    徐寅博閱讀 225評論 2 2

友情鏈接更多精彩內(nèi)容