參考書籍:《第一行代碼》 第二版 郭霖
如有錯漏爬坑,請批評指出!
關(guān)于基本控件以及常用布局的使用怕品,這里不作贅述妇垢,多寫寫布局就能掌握技巧。下面我們直接討論ListView和RecyclerView的使用肉康。
ListView
這里我想通過實現(xiàn)一個功能來了解ListView的使用:訪問鴻洋提供的開放API 玩Android 獲取首頁文章,并通過ListView展示出來灼舍。
-
首先吼和,我們創(chuàng)建一個ListViewActivity,實現(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ù)據(jù)有哪些,打開http://www.wanandroid.com/article/list/0/json
這些就是這個接口返回的JSON數(shù)據(jù)妥畏,我們可以通過一個在線解析工具:JSON在線視圖查看器來查看邦邦。
現(xiàn)在我們來決定我們需要展示什么,簡單起見网棍,我們就展示title黔龟、author、niceDate這些字段滥玷,不過還有一個link字段我們也需要獲取到氏身,也就是這篇文章的鏈接。所以我們需要定義這樣一個Article實體類:
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潮梯。這種實現(xiàn)方式充分利用了ListView的試圖緩存機制,避免每次在調(diào)用getView()方法時都要通過findviewByid()實例化控件惨恭,當convertView為null時秉馏,使用LayoutInflater加載布局,并創(chuàng)建一個ViewHolder對象脱羡,并將控件的實例存放在ViewHolder中萝究,這樣就不需要每次都實例化控件,可以大大提高ListView的運行效率锉罐。
-
最后就是在ListViewActivity中獲取數(shù)據(jù)并將數(shù)據(jù)傳遞給ListView顯示出來帆竹。
首先我們在initView()方法中對控件進行初始化,這里我們調(diào)用了ListView的setemptyView()方法脓规,這個方法是為了設(shè)置一個空數(shù)據(jù)情況下的默認提示栽连,即當我們的數(shù)據(jù)為空時,會顯示這個ImageView抖拦,目的在于優(yōu)化用戶體驗升酣,不至于顯示一片空白。然后初始化Article數(shù)組态罪,并實例化ListAdapter對象噩茄,通過ListView的setAdapter()方法將adapter傳遞進去,這時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ù)帜讲,這樣就實現(xiàn)了我們的ListView衅谷。當然,我們這里僅僅顯示了一部分數(shù)據(jù)似将,接口中提供的數(shù)據(jù)是分頁的获黔,我們通過更改url中的頁碼(list和json中間的數(shù)字即為頁碼)可以獲取到每一頁的數(shù)據(jù)蚀苛,這一部分功能我就不去實現(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è)置一個點擊事件赞辩,用來顯示我們獲取到的link中的文章內(nèi)容雌芽。
-
添加詳情頁
首先我們要創(chuàng)建一個WebActivity,因為我們的文章內(nèi)容其實是一個網(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è)置點擊事件召庞,每點擊一個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); }
其實給ListView添加點擊事件很簡單,就是使用setOnItemClickListener()方法為ListView注冊一個監(jiān)聽器送朱,當用戶點擊ListView的任何一個子項時娘荡,就會回調(diào)onItemClick()方法,在這個方法中驶沼,可以通過position參數(shù)獲取到對應(yīng)子項的數(shù)據(jù)炮沐,然后通過Intent傳遞給WebActivity。接下來就是處理WebActivity中的邏輯了回怜。
我們獲取到intent傳遞過來的數(shù)據(jù)大年,然后創(chuàng)建一個WebView對象,調(diào)用它的setWebViewClient()方法玉雾,并傳入一個WebViewClient實例翔试。它的作用是奶是,當我們從一個網(wǎng)頁跳到另一個網(wǎng)頁時瓣距,我們希望目標網(wǎng)頁仍然在當前WebView中顯示,而不是打開系統(tǒng)瀏覽器窘茁。最后調(diào)用loadUrl()方法驹碍,加載頁面壁涎。這樣凡恍,我們的功能就完成了。當然粹庞,這只是單純的實現(xiàn)功能咳焚,其實還有很多地方需要完善,比如給WebView添加加載動畫等庞溜。感興趣的話可以自己去探索實現(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實現(xiàn)一個和上面相同的功能又官。
- 首先,創(chuàng)建一個RecyclerViewActivity漫试,并實現(xiàn)它的布局六敬。
這里我們需要知道,RecyclerView屬于新增控件驾荣,它被定義在support庫中外构,因此,我們需要在項目的build.gradle中添加相應(yīng)的依賴庫播掷。打開app/build.gradle文件审编,在dependencies閉包中添加下面的代碼:
這個文件修改之后都會自動提示Sync Now,點擊來進行同步歧匈。implementation 'com.android.support:recyclerview-v7:28.0.0'
接下來就可以使用了垒酬,由于RecyclerView不是內(nèi)置在系統(tǒng)SDK中的,所以需要把完整的包路徑寫出來(不過我們只需敲出RecyclerView件炉,Android Studio會自動幫我們補全)勘究。<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>
- 由于前面我們實現(xiàn)ListView時已經(jīng)定義過Article實體類和item_article.xml布局文件了,這里我們直接使用就行了斟冕。
- 和ListView一樣口糕,RecyclerView也需要一個適配器。我們創(chuàng)建一個Java Class宫静,命名為RecyclerAdapter走净。具體實現(xiàn)如下:
我們先為其定義一個靜態(tài)內(nèi)部類ViewHolder,讓它繼承自RecyclerView.ViewHolder孤里,對應(yīng)的伏伯,定義我們所需要的兩個TextView控件,然后重寫它的構(gòu)造方法捌袜,并實例化我們定義的控件说搅。然后讓我們的RecyclerAdapter繼承RecyclerView.Adapter,并將泛型指定為我們剛才定義的RecyclerAdapter.ViewHolder虏等。接著弄唧,我們需要重寫onCreateViewHolder()适肠、onBindViewHolder() 和 getItemCount() 這三個方法。onCreateViewHolder()方法用于創(chuàng)建ViewHolder實例候引,我們使用LayoutInflater的inflate()方法來加載item_articlle布局侯养,然后創(chuàng)建一個ViewHolder對象并返回。onBindViewHolder()方法用于為每個item加載數(shù)據(jù)澄干,在每個item滾動到屏幕內(nèi)時會被調(diào)用逛揩,這里我們通過傳入的position參數(shù)得到當前item對應(yīng)的Article實例,然后通過傳入的ViewHolder對象獲取到當前item的子控件實例麸俘,對其進行操作即可辩稽。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ù)的邏輯是一樣的从媚,就不重復了逞泄。我們主要看ininView()方法,首先實例化RecyclerView對象拜效,然后創(chuàng)建一個LinearLayoutManager對象喷众,調(diào)用RecyclerView的setLayoutManager方法設(shè)置布局管理器,將LinearlayoutManager對象傳遞進去紧憾,使用線性布局的方式管理RecyclerView的item侮腹,然后初始化Article數(shù)組,實例化RecyclerAdapter對象稻励,設(shè)置為RecyclerView的適配器,最后在數(shù)據(jù)更新后調(diào)用notifyDataSetChanged()方法更新數(shù)據(jù)愈涩。這樣RecyclerView的基本功能就實現(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