WanAndroid實戰——首頁文章
前情回顧:
1.WanAndroid實戰——首頁Banner前面完成了首頁Banner的實現,本次繼續來完成首頁文章的獲取和顯示。
按照慣例,先上實現過的效果圖。

首頁文章效果
1.相關佈局檔案
首頁文章的展示使用的是recycleView,每一項使用的是cardview來進行,因此,先在gradle中引入需要的依賴。
//cardview implementation 'com.android.support:cardview-v7:28.0.0' //recycleview implementation 'com.android.support:recyclerview-v7:28.0.0'
在 activity_main.xml
檔案中新增recycleView的相關內容,修改後的檔案如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" tools:context=".view.MainActivity" android:orientation="vertical"> <com.youth.banner.Banner android:id="@+id/main_banner" android:layout_width="match_parent" android:layout_height="@dimen/banner_height"/> <android.support.v7.widget.RecyclerView android:id="@+id/article_content" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
article_item.xml
檔案作為每一項的佈局,這裡使用cardView作為跟佈局,檔案內容和佈局效果如下:

佈局效果
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView 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="wrap_content" android:layout_margin="@dimen/card_view_margin" app:cardBackgroundColor="@color/card_view_bg_color" app:cardCornerRadius="@dimen/card_view_corner_radius" app:cardElevation="@dimen/card_view_elevation" > <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/article_item_constrain_margin"> <android.support.constraint.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.79"/> <TextView android:id="@+id/article_title" style="@style/article_card_style" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="title" /> <TextView android:id="@+id/article_author" style="@style/article_card_style" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/article_item_text_margin_top" app:layout_constraintBottom_toTopOf="@id/article_classify" app:layout_constraintEnd_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/article_title" tools:text="tom" /> <TextView android:id="@+id/article_classify" style="@style/article_card_style" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/article_item_text_margin_top" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/article_author" tools:text="分類" /> <TextView android:id="@+id/article_time" style="@style/article_card_style" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="end" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" tools:text="2010-00-00" /> <ImageView android:id="@+id/article_collection" android:layout_width="@dimen/article_item_collection" android:layout_height="@dimen/article_item_collection" android:src="@drawable/article_collect_selector" app:layout_constraintBottom_toTopOf="@id/article_time" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="contentDescription" /> </android.support.constraint.ConstraintLayout> </android.support.v7.widget.CardView>
- 使用tools名稱空間,用來展示佈局效果,並且編譯的時候不會編譯進去,包括去掉一些警告,如ImageView的contentDescription屬性。
- 使用Guideline,用來保證textView不會因為內容過長而遮擋其它控制元件的顯示。
2.準備adapter
根據玩Android的開放API介面,我們知道首頁資料的格式
可以直接複製示例內容後通過GsonFormat自動生成,具體的可以檢視我的上一篇文章。生成的類名為 MainArticleBean.java
,記得加上toString方法,方便自己除錯。
ArticleAdapter.java
package com.tom.wanandroid.adapter; import android.content.Context; import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.text.Html; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.tom.wanandroid.R; import com.tom.wanandroid.bean.MainArticleBean; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; /** * <p>Title: ArticleAdapter</p> * <p>Description: 文章介面卡</p> * * @author tom * @date 2019/3/9 10:15 **/ public class ArticleAdapter extends RecyclerView.Adapter<ArticleAdapter.ArticleHolder> { private Context mContext; private List<MainArticleBean.DataBean.DatasBean> mBeans; public void setBeans(MainArticleBean beans) { mBeans = beans.getData().getDatas(); } public ArticleAdapter(RecyclerView recyclerView) { mContext = recyclerView.getContext(); } @NonNull @Override public ArticleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.article_item, parent, false); return new ArticleHolder(view); } @Override public void onBindViewHolder(@NonNull ArticleHolder articleHolder, int i) { if (mBeans != null) { MainArticleBean.DataBean.DatasBean bean = mBeans.get(i); articleHolder.mArticleTitle.setText(Html.fromHtml(bean.getTitle(), Html.FROM_HTML_MODE_COMPACT)); articleHolder.mArticleAuthor.setText(String.format(mContext.getResources().getString(R.string.article_author),bean.getAuthor())); articleHolder.mArticleTime.setText(bean.getNiceDate()); String category = String.format(mContext.getResources().getString(R.string.article_category), bean.getSuperChapterName(), bean.getChapterName()); articleHolder.mArticleClassify.setText(Html.fromHtml(category, Html.FROM_HTML_MODE_COMPACT)); } } @Override public int getItemCount() { return mBeans != null ? mBeans.size() : 0; } class ArticleHolder extends RecyclerView.ViewHolder { @BindView(R.id.article_title) TextView mArticleTitle; @BindView(R.id.article_author) TextView mArticleAuthor; @BindView(R.id.article_classify) TextView mArticleClassify; @BindView(R.id.article_collection) ImageView mArticleCollection; @BindView(R.id.article_time) TextView mArticleTime; ArticleHolder(@NonNull View itemView) { super(itemView); ButterKnife.bind(this, itemView); } } }
這裡有一點需要說明,因為獲取的資料為網路資料,且部分資料會帶有html中的標籤並且使用unicode編碼,如果不處理的話會直接顯示出unicode碼,不好看。這裡使用的是 Html.fromHtml
方法,裡面的引數官網上面有說明。
3.在介面中增加相關的內容
分別增加MVP層的介面,比較簡單,直接給出程式碼 Contract.java
。
package com.tom.wanandroid.contract; import com.tom.wanandroid.bean.BannerBean; import com.tom.wanandroid.bean.MainArticleBean; import io.reactivex.Observable; /** * <p>Title: Contract</p> * <p>Description: </p> * * @author tom * @date 2019/3/7 10:13 **/ public class Contract { public interface IMainModel { /** * <p>獲取banner資料</p> * @return banner資料 */ Observable<BannerBean> loadBanner(); /** * <p>獲取首頁文章</p> * @param number 頁碼 * @return 首頁文章 */ Observable<MainArticleBean> loadArticle(int number); } public interface IMainView { /** * <p>View 獲取到資料後進行顯示</p> * @param bean banner的資料 */ void loadBanner(BannerBean bean); /** * <p>展示首頁文章資訊</p> * @param bean 首頁文章資料 */ void loadArticle(MainArticleBean bean); } public interface IMainPresenter{ /** * <p>首頁banner</p> */ void loadBanner(); /** * <p>載入首頁文章</p> */ void loadArticle(); } }
因為使用retorfit來獲取網路資料,首先還是要增加介面。
IRetrofitData.java
這裡需要傳入頁碼作為引數。
/** * <p>獲取首頁文章資料</p> * @param number 頁碼 * @return 返回首頁文章 */ @GET("article/list/{number}/json") Observable<MainArticleBean> loadMainArticle(@Path("number") int number);
4.實現介面
介面加完以後,就來處理報錯吧,哪裡報錯改哪裡,so easy!
MainModel.java
和之前獲取首頁資料一樣的套路。
@Override public Observable<MainArticleBean> loadArticle(int number) { Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); IRetrofitData retrofitData = retrofit.create(IRetrofitData.class); return retrofitData.loadMainArticle(number); }
MainPresenter.java
增加首頁文章的相關內容,這裡固定獲取第一頁的資料(傳入引數為0)。
@Override public void loadArticle() { mModel.loadArticle(0) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<MainArticleBean>() { @Override public void onSubscribe(Disposable d) { mCompositeDisposable.add(d); } @Override public void onNext(MainArticleBean bean) { if (isViewAttached()) { getView().loadArticle(bean); } } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onComplete() { } }); }
MainActivity.java
,這裡有一點小修改,將之前初始化資料的方法由init中放到了onresume中。
@Override protected void onResume() { super.onResume(); mPresenter.loadBanner(); mPresenter.loadArticle(); } @Override public void loadArticle(MainArticleBean bean) { mArticleContent.setLayoutManager(new LinearLayoutManager(this)); if (bean.getErrorCode() == 0) { ArticleAdapter adapter = new ArticleAdapter(mArticleContent); adapter.setBeans(bean); mArticleContent.setAdapter(adapter); } }
到這裡為止,關鍵內容基本都OK了,程式跑起來就可以看到前面的效果圖了。
這裡給出用到的資原始檔
colors.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#008577</color> <color name="colorPrimaryDark">#00574B</color> <color name="colorAccent">#D81B60</color> <color name="card_view_bg_color">#F0F0F0</color> </resources>
dimens.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="banner_height">200dp</dimen> <dimen name="card_view_corner_radius">12dp</dimen> <dimen name="card_view_margin">15dp</dimen> <dimen name="card_view_elevation">10dp</dimen> <dimen name="article_item_constrain_margin">10dp</dimen> <dimen name="article_item_text_margin_top">15dp</dimen> <dimen name="article_item_collection">30dp</dimen> </resources>
strings.xml
中文和英文是兩個夾子的,這裡寫在一起了。
<!-- 英文--> <resources> <string name="app_name">WanAndroid</string> <string name="article_author">author:%1$s</string> <string name="article_category">category:%1$s/%2$s</string> </resources> <!-- 中文--> <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">玩Android</string> <string name="article_author">作者:%1$s</string> <string name="article_category">分類:%1$s/%2$s</string> </resources>
用到的圖示:

collect_disable.png

collect_enable.png