參考書籍:《第一行代碼》 第二版 郭霖
如有錯漏,請批評指出!
關(guān)于基本控件以及常用布局的使用,這里不作贅述,多寫寫布局就能掌握技巧。下面我們直接討論ListView和RecyclerView的使用。
ListView
這里我想通過實(shí)現(xiàn)一個功能來了解ListView的使用:訪問鴻洋提供的開放API 玩Android 獲取首頁文章,并通過ListView展示出來。

-
首先,我們創(chuàng)建一個ListViewActivity,實(shí)現(xiàn)它的布局。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent"/> <ImageView android:id="@+id/empty_view_list" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/img_empty_data" tools:ignore="ContentDescription" /> </FrameLayout>添加ListView控件很簡單,只需要讓它充滿父布局,并為其指定一個id即可,至于下面的ImageView我們暫且不管它,后面再來說它的功能。
-
接下來我們來定義一個實(shí)體類,在此之前,我們先看看這個接口中提供的數(shù)據(jù)有哪些,打開http://www.wanandroid.com/article/list/0/json
這些就是這個接口返回的JSON數(shù)據(jù),我們可以通過一個在線解析工具:JSON在線視圖查看器來查看。
我們只需要將JSON數(shù)據(jù)全部復(fù)制到這個網(wǎng)站的JSON數(shù)據(jù)里面,然后點(diǎn)擊左邊的視圖按鈕,就可以看到這些JSON數(shù)據(jù)的結(jié)構(gòu)了。現(xiàn)在我們來決定我們需要展示什么,簡單起見,我們就展示title、author、niceDate這些字段,不過還有一個link字段我們也需要獲取到,也就是這篇文章的鏈接。所以我們需要定義這樣一個Article實(shí)體類:
public class Article { private String title; private String author; private String date; private String link; public Article(){} public Article(String title, String author, String date, String link){ this.title = title; this.author = author; this.date = date; this.link = link; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } } -
下一步,我們要為ListView的item創(chuàng)建一個自定義布局,也就是我們要如何顯示剛剛我們想要展示的內(nèi)容。在layout目錄下創(chuàng)建一個item_article.xml布局文件。
它的顯示效果就是這樣的:<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="wrap_content" android:padding="12dp" android:paddingTop="24dp" android:paddingBottom="24dp"> <ImageView android:id="@+id/img_article" android:src="@drawable/article" android:layout_width="36dp" android:layout_height="36dp" android:layout_marginRight="12dp" android:layout_marginEnd="12sp" android:layout_centerVertical="true" tools:ignore="ContentDescription" /> <TextView android:id="@+id/text_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="@color/colorBlack" android:layout_toRightOf="@id/img_article" android:layout_toEndOf="@id/img_article" android:layout_marginBottom="10dp" android:singleLine="true" android:ellipsize="end" /> <TextView android:id="@+id/text_inform" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/text_inform" android:textSize="12sp" android:layout_toRightOf="@id/img_article" android:layout_toEndOf="@id/img_article" android:layout_below="@id/text_title" android:layout_marginRight="20dp" android:layout_marginEnd="20dp" android:singleLine="true" android:ellipsize="end"/> </RelativeLayout> -
然后,我們需要創(chuàng)建一個自定義適配器,用來將數(shù)據(jù)與我們的自定義item中的控件綁定起來,這樣就能保證數(shù)據(jù)正確顯示了。下面看代碼:
public class ListAdapter extends BaseAdapter { private List<Article> mArticles; private LayoutInflater mInflater; public ListAdapter(Context context, List<Article> articles){ this.mArticles = articles; this.mInflater = LayoutInflater.from(context); } @Override public int getCount() { return mArticles.size(); } @Override public Object getItem(int position) { return mArticles.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null){ holder = new ViewHolder(); convertView = mInflater.inflate(R.layout.item_article, parent, false); holder.mTitleText = (TextView)convertView.findViewById(R.id.text_title); holder.mInformText = (TextView)convertView.findViewById(R.id.text_inform); convertView.setTag(holder); }else { holder = (ViewHolder)convertView.getTag(); } Article mArticle = mArticles.get(position); holder.mTitleText.setText(mArticle.getTitle()); String format = parent.getResources().getString(R.string.text_inform); holder.mInformText.setText(String.format(format, mArticle.getAuthor(), mArticle.getDate())); return convertView; } public final class ViewHolder{ TextView mTitleText; TextView mInformText; } }BaseAdapter是一個抽象類,讓我們的ListViewAdapter繼承它,重寫它的getCount()、getItem()、getItenid()以及getView()方法。另外,我們在其內(nèi)部定義了一個內(nèi)部類ViewHolder。這種實(shí)現(xiàn)方式充分利用了ListView的試圖緩存機(jī)制,避免每次在調(diào)用getView()方法時都要通過findviewByid()實(shí)例化控件,當(dāng)convertView為null時,使用LayoutInflater加載布局,并創(chuàng)建一個ViewHolder對象,并將控件的實(shí)例存放在ViewHolder中,這樣就不需要每次都實(shí)例化控件,可以大大提高ListView的運(yùn)行效率。
-
最后就是在ListViewActivity中獲取數(shù)據(jù)并將數(shù)據(jù)傳遞給ListView顯示出來。
首先我們在initView()方法中對控件進(jìn)行初始化,這里我們調(diào)用了ListView的setemptyView()方法,這個方法是為了設(shè)置一個空數(shù)據(jù)情況下的默認(rèn)提示,即當(dāng)我們的數(shù)據(jù)為空時,會顯示這個ImageView,目的在于優(yōu)化用戶體驗(yàn),不至于顯示一片空白。然后初始化Article數(shù)組,并實(shí)例化ListAdapter對象,通過ListView的setAdapter()方法將adapter傳遞進(jìn)去,這時Article數(shù)組無數(shù)據(jù)。接下來在initData()方法中調(diào)用我們封裝好的HttpUtil類中的靜態(tài)方法sendOkHttpRequest()發(fā)送HTTP請求,然后調(diào)用parseJSONWithJSONObject()方法解析JSON數(shù)據(jù),將數(shù)據(jù)添加到Article數(shù)組中,最后調(diào)用showArticles()方法,調(diào)用runOnUiThread方法切回主線程,在主線程中調(diào)用ListAdapter的notifyDataSetChanged()方法更新adapter中的數(shù)據(jù),這樣就實(shí)現(xiàn)了我們的ListView。當(dāng)然,我們這里僅僅顯示了一部分?jǐn)?shù)據(jù),接口中提供的數(shù)據(jù)是分頁的,我們通過更改url中的頁碼(list和json中間的數(shù)字即為頁碼)可以獲取到每一頁的數(shù)據(jù),這一部分功能我就不去實(shí)現(xiàn)了,感興趣的話可以自己動動手。中間關(guān)于發(fā)送HTTP請求獲取數(shù)據(jù)以及解析JSON數(shù)據(jù)的部分,我們會在后續(xù)章節(jié)了解到。下面看效果:package com.example.laughter.aboutui.activity; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ListView; import com.example.laughter.aboutui.R; import com.example.laughter.aboutui.adapter.ListAdapter; import com.example.laughter.aboutui.model.Article; import com.example.laughter.aboutui.util.HttpUtil; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.ArrayList; import java.util.List; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; public class ListViewActivity extends AppCompatActivity { private List<Article> mArticles; private ListAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view); initView(); initData(); } private void initView(){ setTitle("ListViewActivity"); ListView listView = (ListView)findViewById(R.id.list_view); listView.setEmptyView(findViewById(R.id.empty_view_list)); mArticles = new ArrayList<>(); adapter = new ListAdapter(this, mArticles); listView.setAdapter(adapter); } private void initData(){ String address = "http://www.wanandroid.com/article/list/0/json"; HttpUtil.sendOkHttpRequest(address, new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { } @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { if (response.body() != null) { parseJSONWithJSONObject(response.body().string()); showArticles(); } } }); } private void parseJSONWithJSONObject(String jsonData){ try { JSONObject json = new JSONObject(jsonData); JSONObject data = json.getJSONObject("data"); JSONArray datas = data.getJSONArray("datas"); for (int i=0;i<datas.length();i++){ JSONObject jsonObject = datas.getJSONObject(i); Article mArticle = new Article(); mArticle.setAuthor(jsonObject.getString("author")); mArticle.setDate(jsonObject.getString("niceDate")); mArticle.setTitle(jsonObject.getString("title")); mArticle.setLink(jsonObject.getString("link")); mArticles.add(mArticle); } } catch (JSONException e) { e.printStackTrace(); } } private void showArticles(){ runOnUiThread(new Runnable() { @Override public void run() { adapter.notifyDataSetChanged(); } }); } }到這里,我們的ListView顯示出了我們獲取到的數(shù)據(jù)。但是還沒有完,我們還需要給每一個item設(shè)置一個點(diǎn)擊事件,用來顯示我們獲取到的link中的文章內(nèi)容。
-
添加詳情頁
首先我們要創(chuàng)建一個WebActivity,因?yàn)槲覀兊奈恼聝?nèi)容其實(shí)是一個網(wǎng)頁,因此我們可以使用WebView來展示,所以在WebActivity的布局中我們需要添加一個WebView。<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>接下來我們要給ListView設(shè)置點(diǎn)擊事件,每點(diǎn)擊一個item,就將該item對應(yīng)文章的link值傳遞給WebActivity,并且啟動WebActivity。我在 Android基礎(chǔ)回顧(二)| 關(guān)于Activity 這篇文章里面介紹過Activity之間傳遞數(shù)據(jù)的方法,這里就不贅述了。這里只需要修改initView()方法,直接看代碼:
private void initView(){ setTitle("ListViewActivity"); ListView listView = (ListView)findViewById(R.id.list_view); listView.setEmptyView(findViewById(R.id.empty_view_list)); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String link = mArticles.get(position).getLink(); String title = mArticles.get(position).getTitle(); Intent intent = new Intent(ListViewActivity.this, WebActivity.class); intent.putExtra("link", link); intent.putExtra("title", title); startActivity(intent); } }); mArticles = new ArrayList<>(); adapter = new ListAdapter(this, mArticles); listView.setAdapter(adapter); }其實(shí)給ListView添加點(diǎn)擊事件很簡單,就是使用setOnItemClickListener()方法為ListView注冊一個監(jiān)聽器,當(dāng)用戶點(diǎn)擊ListView的任何一個子項(xiàng)時,就會回調(diào)onItemClick()方法,在這個方法中,可以通過position參數(shù)獲取到對應(yīng)子項(xiàng)的數(shù)據(jù),然后通過Intent傳遞給WebActivity。接下來就是處理WebActivity中的邏輯了。
我們獲取到intent傳遞過來的數(shù)據(jù),然后創(chuàng)建一個WebView對象,調(diào)用它的setWebViewClient()方法,并傳入一個WebViewClient實(shí)例。它的作用是,當(dāng)我們從一個網(wǎng)頁跳到另一個網(wǎng)頁時,我們希望目標(biāo)網(wǎng)頁仍然在當(dāng)前WebView中顯示,而不是打開系統(tǒng)瀏覽器。最后調(diào)用loadUrl()方法,加載頁面。這樣,我們的功能就完成了。當(dāng)然,這只是單純的實(shí)現(xiàn)功能,其實(shí)還有很多地方需要完善,比如給WebView添加加載動畫等。感興趣的話可以自己去探索實(shí)現(xiàn)。我們再來看一下效果吧!public class WebActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); initData(); } private void initData(){ Intent intent = getIntent(); if (intent != null){ String address = intent.getStringExtra("link"); String title = intent.getStringExtra("title"); setTitle(title); WebView mWebView = (WebView)findViewById(R.id.web_view); mWebView.setWebViewClient(new WebViewClient()); mWebView.loadUrl(address); } } }
RecyclerView
簡單起見,我們使用RecyclerView實(shí)現(xiàn)一個和上面相同的功能。
- 首先,創(chuàng)建一個RecyclerViewActivity,并實(shí)現(xiàn)它的布局。
這里我們需要知道,RecyclerView屬于新增控件,它被定義在support庫中,因此,我們需要在項(xiàng)目的build.gradle中添加相應(yīng)的依賴庫。打開app/build.gradle文件,在dependencies閉包中添加下面的代碼:
這個文件修改之后都會自動提示Sync Now,點(diǎn)擊來進(jìn)行同步。implementation 'com.android.support:recyclerview-v7:28.0.0'
接下來就可以使用了,由于RecyclerView不是內(nèi)置在系統(tǒng)SDK中的,所以需要把完整的包路徑寫出來(不過我們只需敲出RecyclerView,Android Studio會自動幫我們補(bǔ)全)。<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout> - 由于前面我們實(shí)現(xiàn)ListView時已經(jīng)定義過Article實(shí)體類和item_article.xml布局文件了,這里我們直接使用就行了。
- 和ListView一樣,RecyclerView也需要一個適配器。我們創(chuàng)建一個Java Class,命名為RecyclerAdapter。具體實(shí)現(xiàn)如下:
我們先為其定義一個靜態(tài)內(nèi)部類ViewHolder,讓它繼承自RecyclerView.ViewHolder,對應(yīng)的,定義我們所需要的兩個TextView控件,然后重寫它的構(gòu)造方法,并實(shí)例化我們定義的控件。然后讓我們的RecyclerAdapter繼承RecyclerView.Adapter,并將泛型指定為我們剛才定義的RecyclerAdapter.ViewHolder。接著,我們需要重寫onCreateViewHolder()、onBindViewHolder() 和 getItemCount() 這三個方法。onCreateViewHolder()方法用于創(chuàng)建ViewHolder實(shí)例,我們使用LayoutInflater的inflate()方法來加載item_articlle布局,然后創(chuàng)建一個ViewHolder對象并返回。onBindViewHolder()方法用于為每個item加載數(shù)據(jù),在每個item滾動到屏幕內(nèi)時會被調(diào)用,這里我們通過傳入的position參數(shù)得到當(dāng)前item對應(yīng)的Article實(shí)例,然后通過傳入的ViewHolder對象獲取到當(dāng)前item的子控件實(shí)例,對其進(jìn)行操作即可。package com.example.laughter.aboutui.adapter; import android.content.Context; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.example.laughter.aboutui.R; import com.example.laughter.aboutui.model.Article; import java.util.List; public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> { private List<Article> mArticles; private LayoutInflater mInflater; private ViewGroup mViewGroup; public RecyclerAdapter(Context context, List<Article> articles){ mArticles = articles; mInflater = LayoutInflater.from(context); } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) { View view = mInflater.inflate(R.layout.item_article, parent, false); mViewGroup = parent; return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Article mArticle = mArticles.get(position); holder.title.setText(mArticle.getTitle()); String format = mViewGroup.getResources().getString(R.string.text_inform); holder.inform.setText(String.format(format, mArticle.getAuthor(), mArticle.getDate())); } @Override public int getItemCount() { return mArticles.size(); } static class ViewHolder extends RecyclerView.ViewHolder{ TextView title; TextView inform; ViewHolder(View itemView) { super(itemView); title = (TextView)itemView.findViewById(R.id.text_title); inform = (TextView)itemView.findViewById(R.id.text_inform); } } } - 接下來我們需要做的就是獲取數(shù)據(jù)并傳遞給RecyclerView顯示出來。下面看RecyclerViewActivity的代碼:
獲取數(shù)據(jù)的邏輯是一樣的,就不重復(fù)了。我們主要看ininView()方法,首先實(shí)例化RecyclerView對象,然后創(chuàng)建一個LinearLayoutManager對象,調(diào)用RecyclerView的setLayoutManager方法設(shè)置布局管理器,將LinearlayoutManager對象傳遞進(jìn)去,使用線性布局的方式管理RecyclerView的item,然后初始化Article數(shù)組,實(shí)例化RecyclerAdapter對象,設(shè)置為RecyclerView的適配器,最后在數(shù)據(jù)更新后調(diào)用notifyDataSetChanged()方法更新數(shù)據(jù)。這樣RecyclerView的基本功能就實(shí)現(xiàn)了。package com.example.laughter.aboutui.activity; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import com.example.laughter.aboutui.R; import com.example.laughter.aboutui.adapter.RecyclerAdapter; import com.example.laughter.aboutui.model.Article; import com.example.laughter.aboutui.util.HttpUtil; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.util.ArrayList; import java.util.List; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; public class RecyclerViewActivity extends AppCompatActivity { private List<Article> mArticles; private RecyclerView recyclerView; private RecyclerAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycler_view); initView(); initData(); } private void initView(){ recyclerView = (RecyclerView)findViewById(R.id.recycler_view); LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); mArticles = new ArrayList<>(); adapter = new RecyclerAdapter(this, mArticles); recyclerView.setAdapter(adapter); } private void initData(){ String address = "http://www.wanandroid.com/article/list/0/json"; HttpUtil.sendOkHttpRequest(address, new Callback() { @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { } @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { if (response.body() != null) { parseJSONWithJSONObject(response.body().string()); showArticles(); } } }); } private void parseJSONWithJSONObject(String jsonData){ try { JSONObject json = new JSONObject(jsonData); JSONObject data = json.getJSONObject("data"); JSONArray mJsonArray = data.getJSONArray("datas"); for (int i=0;i<mJsonArray.length();i++){ JSONObject mJSONObject = mJsonArray.getJSONObject(i); String title = mJSONObject.getString("title"); String author = mJSONObject.getString("author"); String date = mJSONObject.getString("niceDate"); String link = mJSONObject.getString("link"); Article mArticle = new Article(title, author, date, link); mArticles.add(mArticle); } } catch (JSONException e) { e.printStackTrace(); } } private void showArticles(){ runOnUiThread(new Runnable() { @Override public void run() { adapter.notifyDataSetChanged(); } }); } }
上一篇:Android基礎(chǔ)回顧(一)| 關(guān)于Activity
下一篇:Android基礎(chǔ)回顧(三)| 關(guān)于Fragment





