顯示廣告
隱藏 ✕
看板 Knuckles_note
作者 Knuckles (站長 那克斯)
標題 [AndroidStudio] 自訂Adapter用JSON資料建立ListView
時間 2015-11-30 Mon. 17:36:16


從網路上抓了熱門文章的JSON資料後
接下來要用 ListView 顯示出來

先複製一個預設的圖示放到 /res/drawable
下載圖示 http://i.disp.cc/disp/displogo300.png
複製後在 /res/drawable 按右鍵選貼上
(之前放過的話不用再放一次)

有用到第三方函式庫 Picasso
如果之前沒有加過的話,要在 build.gradle (Module:app)
裡的 dependencies { 裡加上
    compile 'com.squareup.picasso:picasso:2.5.2'


修改 activity_main.xml

將 <RelativeLayout> 的android:padding上下左右改為"0dp"
加上 android:background="#000" 將背景改為黑色
刪除 <TextView …/> 改為 <ListView .../>
修改結果像這樣
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="0dp"
    android:paddingLeft="0dp"
    android:paddingRight="0dp"
    android:paddingTop="0dp"
    android:background="#000"
    tools:context="com.xxx.dispbbs.MainActivity">

    <ListView
        android:id="@+id/main_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
        android:divider="#555"
        android:dividerHeight="1px" />

</RelativeLayout>


因為要自訂 ListView 裡每個 Row 的版位
要再開一個 xml 檔來設定

在 res/layout 點右鍵選 New > Layout resource file
[圖]


名稱輸入「row_main」,root element輸入「RelativeLayout」
[圖]


將 <RelativeLayout 的屬性 android:layout_height 由 "match_parent" 改為 "75dp"
然後在 <RelativeLayout …> 與 </RelativeLayout> 中間插入
一個<ImageView/>和兩個<TextView/>,像這樣
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="75dp">

    <ImageView
        android:id="@+id/img_thumb"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="0dp"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="5dp"
        android:layout_toRightOf="@+id/img_thumb"
        android:layout_alignTop="@+id/img_thumb"
        android:textColor="#9CF"
        android:textSize="14dp"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/text_desc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/text_title"
        android:layout_alignLeft="@+id/text_title"
        android:textColor="#FFF"
        android:textSize="12dp"/>

</RelativeLayout>

顯示的結果會像這樣
[圖]



要將資料加進 ListView,必需先將資料轉為 Adapter
再傳給 ListView,因為 ListView 不接受其他種類的資料

這邊我們要在 ListView 裡用JSON資料產生自訂格式的 Row
所以要自己寫一個繼承 BaseAdapter 的類別

在 java/com.xxx.dispbbs (或是建立project時輸入的package name) 點右鍵
點選 New > Java Class
[圖]


名稱輸入 MainAdapter 代表這是給主頁面的ListView使用的 Adapter
[圖]


將自動產生的內容
class MainAdapter {
加上繼承 BaseAdapter
class MainAdapter extends BaseAdapter {

依紅線提示按alt+Enter自動加上缺少的成員函式
[圖]


點OK,自動加上這四個成員函式
[圖]


然後在 class MainAdapter extends BaseAdapter { 的下一行輸入以下程式
    private LayoutInflater mInflater;
    private JSONArray mJsonArray;

    // 用來儲存row裡每個view的id,以免每次都要取一次
    private static class ViewHolder {
        ImageView thumbImageView;
        TextView titleTextView;
        TextView descTextView;
    }

    // 類別的建構子
    MainAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
        mJsonArray = new JSONArray();
    }

    // 輸入JSON資料
    void updateData(JSONArray jsonArray) {
        mJsonArray = jsonArray;
        notifyDataSetChanged();
    }

前面建立了三個成員變數
Context mContext; 和 LayoutInflater mInflater; 是在物件建立時要初始化的東西
mContext 就是建立這個 Adapter 的 Activity
mInflater 可用 LayoutInflater.from(mContext); 取得,在抓取Layout ID時會用到
第三個 JSONArray mJsonArray; 就是用來存下載的 JSON 資料

接著建立了一個類別 ViewHolder 用來存列表每個 Row 裡的三個View
ViewHolder是用來在避免ListView裡產生太多Row,要回收沒有用到的Row時用的

接著的 public MainAdapter(Context context, LayoutInflater inflater) {
是類別的建構子,在產生Adaptiter物件時要提供這兩個參數
並初始化 mJsonArray

最後的 public void updateData(JSONArray jsonArray){
就是在程式下載了JSON資料後,用這個成員函式將JSON資料傳入Adapter裡
再利用 notifyDataSetChanged(); 通知系統有資料更新了

再來還要修改之前自動產生的四個成員函式
改成這樣
    @Override
    public int getCount() {
        return mJsonArray.length();
    }

    @Override
    public Object getItem(int position) {
        return mJsonArray.optJSONObject(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        // 檢查view是否已存在,如果已存在就不用再取一次id
        if (convertView == null) {
            // Inflate the custom row layout from your XML.
            convertView = mInflater.inflate(R.layout.row_main, parent, false);
            // create a new "Holder" with subviews
            holder = new ViewHolder();
            holder.thumbImageView = (ImageView) convertView.findViewById(R.id.img_thumb);
            holder.titleTextView = (TextView) convertView.findViewById(R.id.text_title);
            holder.descTextView = (TextView) convertView.findViewById(R.id.text_desc);
            // hang onto this holder for future recyclage
            convertView.setTag(holder);
        } else {
            // skip all the expensive inflation/findViewById
            // and just get the holder you already made
            holder = (ViewHolder) convertView.getTag();
        }

        // 取得目前這個Row的JSON資料
        JSONObject jsonObject = (JSONObject) getItem(position);

        String imageUrl = "";
        if (jsonObject.has("img_list")) {
            JSONArray img_list = jsonObject.optJSONArray("img_list");
            if(img_list.length()!=0) {
                imageUrl = img_list.optString(0);
            }
        }
        if (!imageUrl.equals("")) {
            // 使用 Picasso 來載入網路上的圖片
            // 圖片載入前先用placeholder顯示預設圖片
            Picasso.with(mContext).load(imageUrl).placeholder(R.drawable.displogo300).into(holder.thumbImageView);
        } else { // 沒有縮圖的話放 disp logo
            holder.thumbImageView.setImageResource(R.drawable.displogo300);
        }

        // 從JSON資料取得標題和摘要
        String title = "";
        String desc = "";
        if (jsonObject.has("title")) {
            title = jsonObject.optString("title");
        }
        if (jsonObject.has("desc")) {
            desc = jsonObject.optString("desc");
        }

        // 將標題和摘要顯示在TextView上
        holder.titleTextView.setText(title);
        holder.descTextView.setText(desc);

        return convertView;
    }

public int getCount() {
用來取得資料的筆數,回傳 mJsonArray.length(); 即可

public Object getItem(int position) {
用來取得第 position 筆的資料,回傳 mJsonArray.optJSONObject(position);

public long getItemId(int position) {
用來取得第 position 筆資料的 ID,直接用 position 回傳即可

public View getView(int position, View convertView, ViewGroup parent) {
最重要的一個成員函式,用來設定這筆資料要怎麼顯示

輸入的參數 convertView 是用來做Row的回收機制
若是 null 的話,代表沒有可回收的Row,
所以用 mInflater 取得版位的id建立新的 convertView
將Row裡的三個View用 convertView.setTag() 存起來

若 convertView 不是 null 的話,代表有可回收的Row能用
用 convertView.getTag() 取出View即可

接著就是取出JSON資料裡,第position筆資料
將縮圖、標題、摘要,設定在Row裡的三個View來顯示

其中縮圖用到了 Picasso 來將圖片網址顯示出來
要先判定JSON資料裡有沒有縮圖網址
沒有的話就改成用APP的圖示 displogo300 來顯示



整個 MainAdapter.java 最後就是修改成這樣
package com.xxx.dispbbs;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.json.JSONArray;
import org.json.JSONObject;

class MainAdapter extends BaseAdapter {
    private LayoutInflater mInflater;
    private JSONArray mJsonArray;

    // 用來儲存row裡每個view的id,以免每次都要取一次
    private static class ViewHolder {
        ImageView thumbImageView;
        TextView titleTextView;
        TextView descTextView;
    }

    // 類別的建構子
    MainAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
        mJsonArray = new JSONArray();
    }

    // 輸入JSON資料
    void updateData(JSONArray jsonArray) {
        mJsonArray = jsonArray;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return mJsonArray.length();
    }

    @Override
    public Object getItem(int position) {
        return mJsonArray.optJSONObject(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        // 檢查view是否已存在,如果已存在就不用再取一次id
        if (convertView == null) {
            // Inflate the custom row layout from your XML.
            convertView = mInflater.inflate(R.layout.row_main, parent, false);
            // create a new "Holder" with subviews
            holder = new ViewHolder();
            holder.thumbImageView = (ImageView) convertView.findViewById(R.id.img_thumb);
            holder.titleTextView = (TextView) convertView.findViewById(R.id.text_title);
            holder.descTextView = (TextView) convertView.findViewById(R.id.text_desc);
            // hang onto this holder for future recyclage
            convertView.setTag(holder);
        } else {
            // skip all the expensive inflation/findViewById
            // and just get the holder you already made
            holder = (ViewHolder) convertView.getTag();
        }

        // 取得目前這個Row的JSON資料
        JSONObject jsonObject = (JSONObject) getItem(position);

        String imageUrl = "";
        if (jsonObject.has("img_list")) {
            JSONArray img_list = jsonObject.optJSONArray("img_list");
            if(img_list.length()!=0) {
                imageUrl = img_list.optString(0);
            }
        }
        if (!imageUrl.equals("")) {
            // 使用 Picasso 來載入網路上的圖片
            // 圖片載入前先用placeholder顯示預設圖片
            Picasso.with(mContext).load(imageUrl).placeholder(R.drawable.displogo300).into(holder.thumbImageView);
        } else { // 沒有縮圖的話放 disp logo
            holder.thumbImageView.setImageResource(R.drawable.displogo300);
        }

        // 從JSON資料取得標題和摘要
        String title = "";
        String desc = "";
        if (jsonObject.has("title")) {
            title = jsonObject.optString("title");
        }
        if (jsonObject.has("desc")) {
            desc = jsonObject.optString("desc");
        }

        // 將標題和摘要顯示在TextView上
        holder.titleTextView.setText(title);
        holder.descTextView.setText(desc);

        return convertView;
    }
}


修改 MainActivity.java

在 public class MainActivity … { 這行下面加上兩個成員變數
    ListView mListView;
    MainAdapter mAdapter;

在成員函式 onCreate(){} 裡的 loadData(); 這行前面加上
        mListView = (ListView) findViewById(R.id.main_listview);
        mAdapter = new MainAdapter(this);
        mListView.setAdapter(mAdapter);

在成員函式 loadData(){} 裡,下載JSON檔後要執行的 onSuccess(){} 裡的最後加上
                if( response.has("err") && response.optInt("err")!=0 ){
                    Toast.makeText(getApplicationContext(),"Data error", Toast.LENGTH_LONG).show();
                }
                JSONArray list = response.optJSONArray("list");
                if(list==null){
                    Toast.makeText(getApplicationContext(),"Data error", Toast.LENGTH_LONG).show();
                }
                mAdapter.updateData(list);

執行結果
[圖]




參考:
http://www.raywenderlich.com/78578/android-tutorial-for-beginners-part-3
http://www.cnblogs.com/devinzhang/archive/2012/01/20/2328334.html
http://www.vogella.com/tutorials/AndroidListView/article.html

--
※ 作者: Knuckles 時間: 2015-11-30 17:36:16
※ 編輯: Knuckles 時間: 2017-01-06 12:26:19
※ 看板: KnucklesNote 文章推薦值: 0 目前人氣: 0 累積人氣: 3825 
分享網址: 複製 已複製
1樓 時間: 2016-01-09 21:01:51 (台灣)
  01-09 21:01 TW
···
感謝教學, 已經順利做出如範例圖片的文章列表了, 繼續下一步...
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇