安卓開發筆記——打造屬於本身的博客園APP(二)

  在上一篇文章《安卓開發筆記——打造屬於本身的博客園APP(一)》中,咱們基本上實現了博客園的主體UI框架(後面可能會有些小變化,等遇到了再說)。今天來說講博客園首頁模塊的大致實現,國際慣例,先來看下效果圖:html

總體UI效果:java

下拉刷新和上拉加載的動畫效果:android

  

  在上篇文章中,咱們定義的Tabs主題文字分別是(首頁,精華,候選,推薦),這邊的命名我是根據博客園網站首頁的欄目來命名的,那時候我還沒仔細看過博客園的開放接口,後來才發現原來博客園沒有對應開放這些欄目的接口,博客園只開放了(文章列表,48小時閱讀排行,10天內推薦排行,推薦博客列表)等接口,因此我對應的在Tabs標籤主題上改動了下文字。因爲是隨性開發,沒有作過多的前期準備,嘿嘿O(∩_∩)O~canvas

  PS:其實不按照接口來也是能夠的,咱們能夠採用數據採集的方式來獲取數據,有興趣的朋友能夠看看我以前寫的一些列關於JAVA採集數據的文章:瀏覽器

  《基於Java數據採集入庫(一)》:http://www.cnblogs.com/lichenwei/p/3904715.html緩存

  《基於Java數據採集入庫(二)》:http://www.cnblogs.com/lichenwei/p/3905370.html網絡

  《基於Java數據採集入庫(三)》:http://www.cnblogs.com/lichenwei/p/3907007.htmlapp

  《基於Java的數據採集(終結篇)》:http://www.cnblogs.com/lichenwei/p/3910492.html框架

 

  如今已經實現的效果:主UI效果的基本搭建,網絡框架的搭建,各博客列表頁面的展現包括更新效果,對圖片作了三級緩存處理(後面會把文章,新聞作成離線閃存,實現無網絡也能照常瀏覽)等,功能還有不少,慢慢去實現,而後對各細節的優化也會慢慢迭代去完成。ide

  好了,進入主題,因爲文章篇幅問題,我這裏只會對第一個頁面進行講解,其餘大同小異了。

 

一、解析XML數據

這裏是博客園對博客內容的開放接口:http://wcf.open.cnblogs.com/blog/help

  很無奈的發現,博客園的接口是用XML編寫的,須要咱們去解析XML,挺麻煩的,若是是Json無論在效率上或是咱們代碼編寫上都會來得方便許多。

下面是對首頁博文列表:http://wcf.open.cnblogs.com/blog/help/operations/GetSitHomeRecentPagedPosts的XML解析代碼

其中第一個參數PAGEINDEX表明頁數(默認1),第二個參數PAGESIZE表明每頁顯示的文章條數(默認20)

  1 package com.lcw.rabbit.myblog.parser;
  2 
  3 import com.lcw.rabbit.myblog.entity.Blog;
  4 
  5 import org.xmlpull.v1.XmlPullParser;
  6 import org.xmlpull.v1.XmlPullParserException;
  7 import org.xmlpull.v1.XmlPullParserFactory;
  8 
  9 import java.io.IOException;
 10 import java.io.InputStream;
 11 import java.util.ArrayList;
 12 import java.util.List;
 13 
 14 /**
 15  * 對博客列表xml數據的解析
 16  * Created by Lichenwei
 17  * Date: 2015-08-17
 18  * Time: 13:32
 19  */
 20 public class BlogsListXmlParser {
 21 
 22 
 23     /**
 24      * 用於解析博客列表的xml,返回Blog的List集合對象
 25      *
 26      * @param inputStream
 27      * @param encode
 28      * @return
 29      * @throws XmlPullParserException
 30      * @throws IOException
 31      */
 32     public static List<Blog> getListBlogs(InputStream inputStream, String encode) throws XmlPullParserException, IOException {
 33 
 34         List<Blog> mBlogs = null;
 35         Blog mBlog = null;
 36 
 37         //獲取XmlPullParser實例
 38         XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
 39         XmlPullParser parser = factory.newPullParser();
 40         parser.setInput(inputStream, encode);
 41         //獲取解析事件
 42         int eventType = parser.getEventType();
 43         //當xml文檔未到尾端時
 44         while (eventType != XmlPullParser.END_DOCUMENT) {
 45             switch (eventType) {
 46                 //解析根標籤的時候,實例化集合
 47                 case XmlPullParser.START_DOCUMENT:
 48                     mBlogs = new ArrayList<Blog>();
 49                     mBlog = new Blog();
 50 
 51                     break;
 52                 case XmlPullParser.START_TAG:
 53                     //當解析到entry標籤的時候,實例化Blog對象
 54                     if ("entry".equals(parser.getName())) {
 55                         mBlog = new Blog();
 56                     }
 57                     if ("id".equals(parser.getName())) {
 58                         parser.next();
 59                         mBlog.setBlogId(parser.getText());
 60                     } else if ("title".equals(parser.getName())) {
 61                         parser.next();
 62                         //特殊處理
 63                         if (!"博客園".equals(parser.getText())) {
 64                             mBlog.setBlogTitle(parser.getText());
 65                         }
 66                     } else if ("summary".equals(parser.getName())) {
 67                         parser.next();
 68                         mBlog.setBlogSummary(parser.getText());
 69                     } else if ("published".equals(parser.getName())) {
 70                         parser.next();
 71                         mBlog.setBlogPublished(parser.getText());
 72                     } else if ("name".equals(parser.getName())) {
 73                         parser.next();
 74                         mBlog.setAuthorName(parser.getText());
 75                     } else if ("uri".equals(parser.getName())) {
 76                         parser.next();
 77                         mBlog.setAuthorUri(parser.getText());
 78                     } else if ("avatar".equals(parser.getName())) {
 79                         parser.next();
 80                         mBlog.setAuthorAvatar(parser.getText());
 81                     } else if ("link".equals(parser.getName())) {
 82                         //特殊處理
 83                         if (parser.getAttributeName(0).equals("rel")) {
 84                             mBlog.setBlogLink(parser.getAttributeValue(1));
 85                         }
 86                     } else if ("diggs".equals(parser.getName())) {
 87                         parser.next();
 88                         mBlog.setBlogDiggs(parser.getText());
 89                     } else if ("views".equals(parser.getName())) {
 90                         parser.next();
 91                         mBlog.setBlogViews(parser.getText());
 92                     } else if ("comments".equals(parser.getName())) {
 93                         parser.next();
 94                         mBlog.setBlogComments(parser.getText());
 95                     }
 96                     break;
 97                 case XmlPullParser.END_TAG:
 98                     //當解析到entry標籤結束的時候添加入Blogs集合,清空Blog對象
 99                     if ("entry".equals(parser.getName())) {
100                         mBlogs.add(mBlog);
101                         mBlog = null;
102                     }
103                     break;
104 
105             }
106             //手動跳轉第一次遍歷
107             eventType = parser.next();
108         }
109 
110 
111         return mBlogs;
112 
113     }
114 
115 }

  在JAVA中解析XML通常有三種方式(SAX,DOM,PULL),上面代碼採用的是最後一種PULL方式的解析,前面兩種SAX,DOM通常用於JAVAEE裏,PULL方式的解析相對前二者來得比較輕量,安卓內部對XML的解析也是採用的PULL,因此不必引入新的JAR包,關於這三種方式的解析,這裏就再也不多說了,不是今天的重點。(PS:以前一直作的是Json解析,XML解析幾乎沒用過,可能你們有更好的更有效率的解析方式,若是有能夠在文章評論裏幫我指點下迷津)。

  好了,上面的代碼已經對XML解析封裝完成,咱們只須要傳入一個輸入流和編碼格式,就能夠把咱們想要的數據裝在到List集合了。

這裏是List集合裏的實體類:

  1 package com.lcw.rabbit.myblog.entity;
  2 
  3 /**
  4  * 博客實體類
  5  * Created by Lichenwei
  6  * Date: 2015-08-17
  7  * Time: 13:34
  8  */
  9 public class Blog {
 10     //文章id
 11     private String blogId;
 12     //文章標題
 13     private String blogTitle;
 14     //文章概要
 15     private String blogSummary;
 16     //更新時間
 17     private String blogPublished;
 18     //博主暱稱
 19     private String authorName;
 20     //博主頭像地址
 21     private String authorAvatar;
 22     //博主博客地址
 23     private String authorUri;
 24     //博文連接
 25     private String blogLink;
 26     //博文評論數
 27     private String blogComments;
 28     //博文瀏覽數
 29     private String blogViews;
 30     //博文推薦數
 31     private String blogDiggs;
 32 
 33     public Blog() {
 34     }
 35 
 36     public Blog(String blogId, String blogTitle, String blogSummary, String blogPublished, String authorName, String authorAvatar, String authorUri, String blogLink, String blogComments, String blogViews, String blogDiggs) {
 37         this.blogId = blogId;
 38         this.blogTitle = blogTitle;
 39         this.blogSummary = blogSummary;
 40         this.blogPublished = blogPublished;
 41         this.authorName = authorName;
 42         this.authorAvatar = authorAvatar;
 43         this.authorUri = authorUri;
 44         this.blogLink = blogLink;
 45         this.blogComments = blogComments;
 46         this.blogViews = blogViews;
 47         this.blogDiggs = blogDiggs;
 48     }
 49 
 50     public String getBlogId() {
 51         return blogId;
 52     }
 53 
 54     public void setBlogId(String blogId) {
 55         this.blogId = blogId;
 56     }
 57 
 58     public String getBlogTitle() {
 59         return blogTitle;
 60     }
 61 
 62     public void setBlogTitle(String blogTitle) {
 63         this.blogTitle = blogTitle;
 64     }
 65 
 66     public String getBlogSummary() {
 67         return blogSummary;
 68     }
 69 
 70     public void setBlogSummary(String blogSummary) {
 71         this.blogSummary = blogSummary;
 72     }
 73 
 74     public String getBlogPublished() {
 75         return blogPublished;
 76     }
 77 
 78     public void setBlogPublished(String blogPublished) {
 79         this.blogPublished = blogPublished;
 80     }
 81 
 82     public String getAuthorName() {
 83         return authorName;
 84     }
 85 
 86     public void setAuthorName(String authorName) {
 87         this.authorName = authorName;
 88     }
 89 
 90     public String getAuthorAvatar() {
 91         return authorAvatar;
 92     }
 93 
 94     public void setAuthorAvatar(String authorAvatar) {
 95         this.authorAvatar = authorAvatar;
 96     }
 97 
 98     public String getAuthorUri() {
 99         return authorUri;
100     }
101 
102     public void setAuthorUri(String authorUri) {
103         this.authorUri = authorUri;
104     }
105 
106     public String getBlogLink() {
107         return blogLink;
108     }
109 
110     public void setBlogLink(String blogLink) {
111         this.blogLink = blogLink;
112     }
113 
114     public String getBlogComments() {
115         return blogComments;
116     }
117 
118     public void setBlogComments(String blogComments) {
119         this.blogComments = blogComments;
120     }
121 
122     public String getBlogViews() {
123         return blogViews;
124     }
125 
126     public void setBlogViews(String blogViews) {
127         this.blogViews = blogViews;
128     }
129 
130     public String getBlogDiggs() {
131         return blogDiggs;
132     }
133 
134     public void setBlogDiggs(String blogDiggs) {
135         this.blogDiggs = blogDiggs;
136     }
137 
138     @Override
139     public String toString() {
140         return "Blog{" +
141                 "blogId='" + blogId + '\'' +
142                 ", blogTitle='" + blogTitle + '\'' +
143                 ", blogSummary='" + blogSummary + '\'' +
144                 ", blogPublished='" + blogPublished + '\'' +
145                 ", authorName='" + authorName + '\'' +
146                 ", authorAvatar='" + authorAvatar + '\'' +
147                 ", authorUri='" + authorUri + '\'' +
148                 ", blogLink='" + blogLink + '\'' +
149                 ", blogComments='" + blogComments + '\'' +
150                 ", blogViews='" + blogViews + '\'' +
151                 ", blogDiggs='" + blogDiggs + '\'' +
152                 '}';
153     }
154 }
Blog

 

二、獲取XML數據

  咱們須要對博客園開放接口的各類數據進行獲取,不論是下拉刷新仍是上拉加載的文字信息仍是對用戶頭像的圖片信息獲取,這邊須要對網絡進行頻繁的操做,這裏我進入了Volley框架,並對圖片作了三級緩存(內存,磁盤,網絡)使得在沒有網絡的狀況下也能夠看到對應的圖片。

對三級緩存封裝不熟悉的朋友,能夠參考我以前寫過的文章《安卓開發筆記——關於圖片的三級緩存策略(內存LruCache+磁盤DiskLruCache+網絡Volley)》,固然你也能夠用你本身的方法,這裏就再也不詳細去說了,一篇文章寫不下哈。

看下LogCat打印的日誌信息:

 

三、頁面佈局

  這裏的佈局我採用了安卓5.0推出的新控件(將來之星RecyclerView和卡片CardView),谷歌也推出了向下兼容,你們能夠在Support-V7包下找到這2個控件。爲了便於代碼的複用,我這裏把每一部分都分開成不一樣的XML,下面是詳細代碼:

 這裏是博文列表欄目的主界面:(關於下拉刷新和上拉加載,下面會提到)

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="match_parent"
 3     android:layout_height="match_parent"
 4     android:orientation="vertical"
 5     android:gravity="center">
 6 
 7     <android.support.v4.widget.SwipeRefreshLayout
 8         android:id="@+id/swipe_refresh"
 9         android:layout_width="match_parent"
10         android:layout_height="match_parent"
11         android:layout_margin="4dp">
12 
13         <android.support.v7.widget.RecyclerView
14             android:id="@+id/rv_view"
15             android:layout_width="match_parent"
16             android:layout_height="match_parent"
17             android:background="@color/md_grey_200"
18             android:scrollbars="vertical"
19            />
20     </android.support.v4.widget.SwipeRefreshLayout>
21 
22     <com.lcw.rabbit.myblog.view.MyProgressBar
23         android:id="@+id/progressbar"
24         android:layout_width="match_parent"
25         android:layout_height="20dp"
26         android:layout_gravity="bottom"
27         android:visibility="gone"
28         />
29 </LinearLayout>
fragment_bloglist

這裏是RecyclerView的item佈局:

 1 <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:app="http://schemas.android.com/apk/res-auto"
 3     android:id="@+id/cv_cardview"
 4     android:layout_width="match_parent"
 5     android:layout_height="wrap_content"
 6     android:layout_margin="8dp"
 7     android:gravity="center"
 8     app:cardCornerRadius="6dp">
 9 
10     <include layout="@layout/recyclerview_item_bloglist_content" />
11 
12 </android.support.v7.widget.CardView>
recyclerview_item_bloglist.xml

這裏是item裏的詳細布局:

  1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2     xmlns:app="http://schemas.android.com/apk/res-auto"
  3     android:layout_width="match_parent"
  4     android:layout_height="wrap_content"
  5     android:orientation="horizontal"
  6     android:padding="3dp">
  7     <!--頭像-->
  8     <com.makeramen.roundedimageview.RoundedImageView
  9         android:id="@+id/iv_userhead"
 10         android:layout_width="60dp"
 11         android:layout_height="60dp"
 12         android:layout_gravity="center_vertical"
 13         android:layout_marginRight="5dp"
 14         android:src="@mipmap/avatar_default"
 15         app:riv_border_color="#ffffff"
 16         app:riv_border_width="2dip"
 17         app:riv_corner_radius="30dip"
 18         app:riv_mutate_background="true"
 19         app:riv_oval="true"
 20         app:riv_tile_mode="repeat" />
 21     <!--信息內容-->
 22     <LinearLayout
 23         android:layout_width="0dp"
 24         android:layout_height="wrap_content"
 25         android:layout_weight="1"
 26         android:layout_marginLeft="3dp"
 27         android:orientation="vertical">
 28 
 29         <TextView
 30             android:id="@+id/tv_title"
 31             android:layout_width="match_parent"
 32             android:layout_height="wrap_content"
 33             android:layout_marginTop="2dp"
 34             android:layout_weight="1"
 35             android:text="測試標題"
 36             android:ellipsize="end"
 37             android:singleLine="true"
 38             android:textColor="@color/md_grey_900"
 39             android:textSize="14sp"
 40             android:textStyle="bold" />
 41 
 42         <TextView
 43             android:id="@+id/tv_description"
 44             android:layout_width="match_parent"
 45             android:layout_height="wrap_content"
 46             android:layout_weight="1"
 47             android:maxLines="3"
 48             android:text="瀏覽器類型判斷方法有兩種:根據瀏覽器特性來判斷根據來檢測具體使用哪一種方法要看具體需求的場景場景一:爲了讓用戶有較流暢完整的體驗,在站點提示用戶使用或者,這種場景對瀏覽器類型的判斷並不是特別嚴格,可使用檢測的方法。(由於不少瀏覽器廠商會篡改標識)。場景二...."
 49             android:textSize="12sp" />
 50 
 51         <LinearLayout
 52             android:layout_width="match_parent"
 53             android:layout_height="match_parent"
 54             android:layout_margin="1dp"
 55             android:layout_weight="1"
 56             android:orientation="horizontal">
 57 
 58             <TextView
 59                 android:layout_width="wrap_content"
 60                 android:layout_height="match_parent"
 61                 android:gravity="center_vertical"
 62                 android:text="發表:"
 63                 android:textColor="@color/md_grey_500"
 64                 android:textSize="11sp" />
 65 
 66             <TextView
 67                 android:id="@+id/tv_time"
 68                 android:layout_width="wrap_content"
 69                 android:layout_height="match_parent"
 70                 android:layout_marginRight="5dp"
 71                 android:gravity="center_vertical"
 72                 android:textColor="@color/md_grey_500"
 73                 android:textSize="11sp" />
 74 
 75             <TextView
 76                 android:layout_width="wrap_content"
 77                 android:layout_height="match_parent"
 78                 android:gravity="center_vertical"
 79                 android:text="推薦:"
 80                 android:textColor="@color/md_grey_500"
 81                 android:textSize="11sp" />
 82 
 83             <TextView
 84                 android:id="@+id/tv_best"
 85                 android:layout_width="wrap_content"
 86                 android:layout_height="match_parent"
 87                 android:layout_marginRight="5dp"
 88                 android:textColor="@color/md_grey_500"
 89                 android:textSize="11sp" />
 90 
 91             <TextView
 92                 android:layout_width="wrap_content"
 93                 android:layout_height="match_parent"
 94                 android:gravity="center_vertical"
 95                 android:text="評論:"
 96                 android:textColor="@color/md_grey_500"
 97                 android:textSize="11sp" />
 98 
 99             <TextView
100                 android:id="@+id/tv_comment"
101                 android:layout_width="wrap_content"
102                 android:layout_height="match_parent"
103                 android:layout_marginRight="5dp"
104                 android:gravity="center_vertical"
105                 android:textColor="@color/md_grey_500"
106                 android:textSize="11sp" />
107 
108             <TextView
109                 android:layout_width="wrap_content"
110                 android:layout_height="match_parent"
111                 android:gravity="center_vertical"
112                 android:text="瀏覽:"
113                 android:textColor="@color/md_grey_500"
114                 android:textSize="11sp" />
115 
116             <TextView
117                 android:id="@+id/tv_browse"
118                 android:layout_width="wrap_content"
119                 android:layout_height="match_parent"
120                 android:gravity="center_vertical"
121                 android:textColor="@color/md_grey_500"
122                 android:textSize="11sp" />
123 
124         </LinearLayout>
125 
126 
127     </LinearLayout>
128 
129     <!--操做按鈕-->
130     <ImageButton
131         android:id="@+id/ib_more"
132         android:layout_width="wrap_content"
133         android:layout_height="match_parent"
134         android:layout_gravity="center"
135         android:background="?android:selectableItemBackground"
136         android:paddingLeft="5dp"
137         android:paddingRight="5dp"
138         android:src="@mipmap/ic_more_dark" />
139 </LinearLayout>
recyclerview_item_bloglist_content.xml

 

四、詳細代碼

  既然用到了RecyclerView,和ListView同樣須要一個適配器,它的適配器和咱們以往使用ListView的,它不是繼承實現BaseAdapter而是去繼承RecyclerView.Adapter<RecyclerViewViewHolder>,這裏谷歌已經強制開發者使用ViewHolder了,RecyclerView嘛,顧名思義,爲了效率而生,看下詳細代碼吧。

  1 package com.lcw.rabbit.myblog.adapter;
  2 
  3 import android.content.Context;
  4 import android.content.res.Resources;
  5 import android.graphics.Bitmap;
  6 import android.graphics.BitmapFactory;
  7 import android.support.v7.widget.RecyclerView;
  8 import android.view.LayoutInflater;
  9 import android.view.View;
 10 import android.view.ViewGroup;
 11 import android.widget.ImageButton;
 12 import android.widget.TextView;
 13 
 14 import com.lcw.rabbit.myblog.R;
 15 import com.lcw.rabbit.myblog.entity.Blog;
 16 import com.lcw.rabbit.myblog.utils.ImageCacheManager;
 17 import com.lcw.rabbit.myblog.utils.TimeUtil;
 18 import com.makeramen.roundedimageview.RoundedImageView;
 19 
 20 import java.util.List;
 21 
 22 /**
 23  * 博文列表適配器
 24  * Created by Lichenwei
 25  * Date: 2015-08-16
 26  * Time: 22:34
 27  */
 28 public class BlogListAdapter extends RecyclerView.Adapter<BlogListAdapter.RecyclerViewViewHolder> {
 29 
 30     private Context mContext;
 31     private List<Blog> mBlogs;
 32 
 33     public BlogListAdapter(Context context, List<Blog> blogs) {
 34         this.mContext = context;
 35         this.mBlogs = blogs;
 36     }
 37 
 38     /**
 39      * 設置新的數據源,提醒adatper更新
 40      *
 41      * @param blogs
 42      */
 43     public void refreshData(List<Blog> blogs) {
 44         this.mBlogs = blogs;
 45         this.notifyDataSetChanged();
 46     }
 47 
 48 
 49     /**
 50      * 建立ViewHolder
 51      *
 52      * @param viewGroup
 53      * @param i
 54      * @return
 55      */
 56     @Override
 57     public RecyclerViewViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
 58         View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item_bloglist, viewGroup, false);
 59         return new RecyclerViewViewHolder(view);
 60     }
 61 
 62     /**
 63      * 根據資源ID返回Bitmap對象
 64      *
 65      * @param resId
 66      * @return
 67      */
 68     public Bitmap getBitmapFromRes(int resId) {
 69         Resources res = mContext.getResources();
 70         return BitmapFactory.decodeResource(res, resId);
 71 
 72     }
 73 
 74     /**
 75      * 綁定數據
 76      *
 77      * @param viewholder
 78      * @param i
 79      */
 80     @Override
 81     public void onBindViewHolder(RecyclerViewViewHolder viewholder, int i) {
 82         //設置頭像
 83         if (mBlogs.get(i).getAuthorAvatar() != null && !"".equals(mBlogs.get(i).getAuthorAvatar())) {
 84             ImageCacheManager.loadImage(mBlogs.get(i).getAuthorAvatar(), viewholder.mUserhead, getBitmapFromRes(R.mipmap.avatar_default), getBitmapFromRes(R.mipmap.avatar_default));
 85         } else {
 86             viewholder.mUserhead.setImageResource(R.mipmap.avatar_default);
 87         }
 88         viewholder.mTitle.setText(mBlogs.get(i).getBlogTitle());
 89         viewholder.mDescription.setText(mBlogs.get(i).getBlogSummary());
 90         viewholder.mBest.setText(mBlogs.get(i).getBlogDiggs());
 91         viewholder.mComment.setText(mBlogs.get(i).getBlogComments());
 92         viewholder.mBrowse.setText(mBlogs.get(i).getBlogViews());
 93         //處理日期特殊格式
 94         String date = TimeUtil.DateToChineseString(TimeUtil.ParseUTCDate(mBlogs.get(i).getBlogPublished()));
 95         viewholder.mTime.setText(date);
 96     }
 97 
 98     @Override
 99     public int getItemCount() {
100         return mBlogs.size();
101     }
102 
103     /**
104      * 自定義ViewHolder
105      */
106     public static class RecyclerViewViewHolder extends RecyclerView.ViewHolder {
107         private RoundedImageView mUserhead;
108         private TextView mTitle;
109         private TextView mDescription;
110         private TextView mTime;
111         private TextView mBest;
112         private TextView mComment;
113         private TextView mBrowse;
114         private ImageButton mMore;
115 
116         public RecyclerViewViewHolder(View view) {
117             super(view);
118             mUserhead = (RoundedImageView) view.findViewById(R.id.iv_userhead);
119             mTitle = (TextView) view.findViewById(R.id.tv_title);
120             mDescription = (TextView) view.findViewById(R.id.tv_description);
121             mTime = (TextView) view.findViewById(R.id.tv_time);
122             mBest = (TextView) view.findViewById(R.id.tv_best);
123             mComment = (TextView) view.findViewById(R.id.tv_comment);
124             mBrowse = (TextView) view.findViewById(R.id.tv_browse);
125             mMore = (ImageButton) view.findViewById(R.id.ib_more);
126 
127         }
128 
129 
130     }
131 }

  而後下拉刷新我採用了谷歌官方推出的SwipeRefreshLayout(Support-V4包下),上拉加載我採用了GitHub上的開源組件mugen(這裏是開源項目地址:http://www.open-open.com/lib/view/open1431414846747.html

這裏是博文列表頁面的主代碼:

  1 package com.lcw.rabbit.myblog.fragment;
  2 
  3 import android.os.Bundle;
  4 import android.support.v4.app.Fragment;
  5 import android.support.v4.widget.SwipeRefreshLayout;
  6 import android.support.v7.widget.LinearLayoutManager;
  7 import android.support.v7.widget.RecyclerView;
  8 import android.view.LayoutInflater;
  9 import android.view.View;
 10 import android.view.ViewGroup;
 11 import android.widget.Toast;
 12 
 13 import com.android.volley.Request;
 14 import com.android.volley.Response;
 15 import com.android.volley.VolleyError;
 16 import com.android.volley.toolbox.StringRequest;
 17 import com.lcw.rabbit.myblog.R;
 18 import com.lcw.rabbit.myblog.adapter.BlogListAdapter;
 19 import com.lcw.rabbit.myblog.entity.Blog;
 20 import com.lcw.rabbit.myblog.parser.BlogsListXmlParser;
 21 import com.lcw.rabbit.myblog.utils.VolleyRequestQueueManager;
 22 import com.lcw.rabbit.myblog.view.MyProgressBar;
 23 import com.mugen.Mugen;
 24 import com.mugen.MugenCallbacks;
 25 import com.mugen.attachers.BaseAttacher;
 26 
 27 import org.xmlpull.v1.XmlPullParserException;
 28 
 29 import java.io.ByteArrayInputStream;
 30 import java.io.IOException;
 31 import java.util.ArrayList;
 32 import java.util.List;
 33 
 34 /**
 35  * Created by Lichenwei
 36  * Date: 2015-08-16
 37  * Time: 13:57
 38  */
 39 public class BlogListFragment extends Fragment {
 40     private View mView;
 41     //下拉刷新
 42     private SwipeRefreshLayout mRefreshLayout;
 43     //無限滾動
 44     private BaseAttacher mBaseAttacher;
 45 
 46     private MyProgressBar myProgressBar;
 47     private RecyclerView mRecyclerView;
 48     private BlogListAdapter mBlogListAdapter;
 49 
 50     //數據源
 51     private List<Blog> mBlogs;
 52 
 53     //是否正在加載
 54     private boolean isLoading = false;
 55     //當前頁數
 56     private int currentPage = 1;
 57 
 58 
 59     @Override
 60     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 61         mView = inflater.inflate(R.layout.fragment_bloglist, null);
 62         initView();
 63         initData();
 64         initAction();
 65         return mView;
 66     }
 67 
 68     /**
 69      * 初始化控件監聽
 70      */
 71     private void initAction() {
 72         mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
 73             @Override
 74             public void onRefresh() {
 75                 getData(1, 20);
 76             }
 77         });
 78         //設置無限滾動,上拉加載
 79         mBaseAttacher = Mugen.with(mRecyclerView, new MugenCallbacks() {
 80             @Override
 81             public void onLoadMore() {
 82                 //加載更多
 83                 isLoading = true;
 84                 myProgressBar.setVisibility(View.VISIBLE);
 85                 getData((currentPage + 1), 20);
 86             }
 87 
 88             @Override
 89             public boolean isLoading() {
 90                 return isLoading;
 91             }
 92 
 93             @Override
 94             public boolean hasLoadedAllItems() {
 95                 return false;
 96             }
 97         }).start();
 98 
 99 
100     }
101 
102     /**
103      * 初始化數據
104      */
105     private void initData() {
106         mBlogs = new ArrayList<Blog>();
107         //設置空數據給RecyclerView
108         mBlogListAdapter = new BlogListAdapter(getActivity(), mBlogs);
109         mRecyclerView.setAdapter(mBlogListAdapter);
110         //顯示下拉刷新樣式
111         mRefreshLayout.setRefreshing(true);
112         //剛開始加載20條數據
113         getData(1, 20);
114 
115     }
116 
117 
118     /**
119      * 初始化控件
120      */
121 
122     private void initView() {
123         mRefreshLayout = (SwipeRefreshLayout) mView.findViewById(R.id.swipe_refresh);
124         mRecyclerView = (RecyclerView) mView.findViewById(R.id.rv_view);
125         myProgressBar = (MyProgressBar) mView.findViewById(R.id.progressbar);
126 
127         //設置拉下刷新滾動條顏色
128         mRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_light, android.R.color.holo_red_light, android.R.color.holo_orange_light, android.R.color.holo_green_light);
129         //設置RecyclerView顯示樣式
130         mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
131 
132     }
133 
134     public void getData(final int page, int num) {
135         //更新當前頁數
136         this.currentPage = page;
137         String url = "http://wcf.open.cnblogs.com/blog/sitehome/paged/" + page + "/" + num;
138         StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
139             @Override
140             public void onResponse(String s) {
141                 try {
142                     isLoading = false;
143                     myProgressBar.setVisibility(View.GONE);
144                     ByteArrayInputStream inputStream = new ByteArrayInputStream(s.getBytes());
145                     //獲取List數據集合
146                     List<Blog> blogs = BlogsListXmlParser.getListBlogs(inputStream, "utf-8");
147                     if (page == 1) {
148                         //清空以前的數據預防重複加載
149                         mBlogs.clear();
150                     }
151                     for (Blog blog : blogs) {
152                         //整理數據源
153                         mBlogs.add(blog);
154                     }
155 
156                     if (mBlogListAdapter == null) {
157                         //若是Adapter不存在
158                         mBlogListAdapter = new BlogListAdapter(getActivity(), mBlogs);
159                         mRecyclerView.setAdapter(mBlogListAdapter);
160                     } else {
161                         //存在通知adatper數據源更新
162                         mBlogListAdapter.refreshData(mBlogs);
163                     }
164 
165 
166                     //關閉下拉刷新樣式
167                     mRefreshLayout.setRefreshing(false);
168 
169                 } catch (XmlPullParserException e) {
170                     e.printStackTrace();
171                 } catch (IOException e) {
172                     e.printStackTrace();
173                 }
174             }
175         }, new Response.ErrorListener() {
176             @Override
177             public void onErrorResponse(VolleyError volleyError) {
178                 Toast.makeText(getActivity(), volleyError.getMessage(), Toast.LENGTH_SHORT).show();
179             }
180         });
181 
182         //加入Volley請求隊列
183         VolleyRequestQueueManager.addRequest(request, "getBlogList");
184 
185 
186     }
187 }

  這裏有個小技巧,對於上拉加載,爲了不重複加載數據,咱們能夠一開始就給RecyclerView設置一個空的Adapter,而後根據加載頁碼的不一樣來作不一樣的操做,若是是第一頁,那麼就是進行下拉刷新,咱們直接清空原來的數據源加載新的便可。若是不是第一頁,那就是上拉加載,咱們把新的數據追加到舊數據源後面便可,這樣就避免了上拉加載滾動條置頂了。

而後這裏的底部上拉加載的無限滾動,我採用了自定義View圓圈滾動,你也能夠用項目自帶的彩色線裝,下面是自定義View的具體代碼:

  1 package com.lcw.rabbit.myblog.view;
  2 
  3 import android.animation.Animator;
  4 import android.animation.AnimatorListenerAdapter;
  5 import android.animation.ObjectAnimator;
  6 import android.animation.TimeInterpolator;
  7 import android.content.Context;
  8 import android.graphics.Canvas;
  9 import android.graphics.Paint;
 10 import android.util.AttributeSet;
 11 import android.util.Log;
 12 import android.view.View;
 13 
 14 /**
 15  * Created by Lichenwei
 16  * Date: 2015-08-19
 17  * Time: 10:59
 18  */
 19 public class MyProgressBar extends View {
 20 
 21 
 22     private Paint paint;
 23     private Paint paint1;
 24     private Paint paint2;
 25     private Paint paint3;
 26     private Paint paint4;
 27 
 28     private float cx0 = -10;
 29     private float cx1 = -10;
 30     private float cx2 = -10;
 31     private float cx3 = -10;
 32     private float cx4 = -10;
 33 
 34     private long delay = 100;
 35     private long duration = 1500;
 36     private float start = -10;
 37     private float end;
 38     private int desiredWidth = 500;
 39     private int desiredHeight = 10;
 40 
 41     private ObjectAnimator animator;
 42     private ObjectAnimator animator1;
 43     private ObjectAnimator animator2;
 44     private ObjectAnimator animator3;
 45     private ObjectAnimator animator4;
 46     private boolean isRunning = false;
 47 
 48     public MyProgressBar(Context context) {
 49         super(context);
 50 //        init();
 51     }
 52 
 53     public MyProgressBar(Context context, AttributeSet attrs) {
 54         super(context, attrs);
 55 //        init();
 56     }
 57 
 58     public MyProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
 59         super(context, attrs, defStyleAttr);
 60 
 61 
 62 //        init();
 63     }
 64 
 65     private void init() {
 66 
 67         paint = new Paint(Paint.ANTI_ALIAS_FLAG);
 68         paint.setColor(getResources().getColor(android.R.color.holo_red_light));
 69 
 70         paint1 = new Paint(Paint.ANTI_ALIAS_FLAG);
 71         paint1.setColor(getResources().getColor(android.R.color.holo_orange_light));
 72 
 73         paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
 74         paint2.setColor(getResources().getColor(android.R.color.holo_green_light));
 75 
 76         paint3 = new Paint(Paint.ANTI_ALIAS_FLAG);
 77         paint3.setColor(getResources().getColor(android.R.color.holo_blue_light));
 78 
 79         paint4 = new Paint(Paint.ANTI_ALIAS_FLAG);
 80         paint4.setColor(getResources().getColor(android.R.color.holo_purple));
 81 
 82         animator = ObjectAnimator.ofFloat(this, "cx0", start, end);
 83         animator.setDuration(duration);
 84         animator.setInterpolator(new DecelerateAccelerateInterpolator());
 85         animator.start();
 86 
 87         animator1 = ObjectAnimator.ofFloat(this, "cx1", start, end);
 88         animator1.setDuration(duration);
 89         animator1.setStartDelay(delay);
 90         animator1.setInterpolator(new DecelerateAccelerateInterpolator());
 91 
 92 
 93         animator2 = ObjectAnimator.ofFloat(this, "cx2", start, end);
 94         animator2.setDuration(duration);
 95         animator2.setStartDelay(delay * 2);
 96         animator2.setInterpolator(new DecelerateAccelerateInterpolator());
 97 
 98         animator3 = ObjectAnimator.ofFloat(this, "cx3", start, end);
 99         animator3.setDuration(duration);
100         animator3.setStartDelay(delay * 3);
101         animator3.setInterpolator(new DecelerateAccelerateInterpolator());
102 
103         animator4 = ObjectAnimator.ofFloat(this, "cx4", start, end);
104         animator4.setDuration(duration);
105         animator4.setStartDelay(delay * 4);
106         animator4.setInterpolator(new DecelerateAccelerateInterpolator());
107         animator4.addListener(new AnimatorListenerAdapter() {
108             @Override
109             public void onAnimationEnd(Animator animation) {
110                 if (isRunning) {
111                     start();
112                 }
113             }
114         });
115     }
116 
117     @Override
118     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
119         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
120 
121         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
122         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
123         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
124         int heightMode = MeasureSpec.getMode(widthMeasureSpec);
125 
126         Log.i("widthSize ", widthSize + "");
127         int measuredWidth, measuredHeight;
128 
129 
130         measuredWidth = widthSize;
131 
132         measuredHeight = heightSize;
133 
134         setMeasuredDimension(measuredWidth, measuredHeight);
135 
136         end = -start + measuredWidth;
137         init();
138     }
139 
140 
141     @Override
142     protected void onDraw(Canvas canvas) {
143         if (!isRunning) {
144             start();
145             isRunning = true;
146         }
147 
148         canvas.drawCircle(cx0, 12, 10, paint);
149         canvas.drawCircle(cx1, 12, 10, paint1);
150         canvas.drawCircle(cx2, 12, 10, paint2);
151         canvas.drawCircle(cx3, 12, 10, paint3);
152         canvas.drawCircle(cx4, 12, 10, paint4);
153     }
154 
155     public void start() {
156 
157         if (animator != null && animator1 != null && animator2 != null && animator3 != null && animator4 != null) {
158             animator.start();
159             animator1.start();
160             animator2.start();
161             animator3.start();
162             animator4.start();
163 
164             isRunning = true;
165         }
166 
167 
168     }
169 
170     public void cancel() {
171         if (animator != null && animator1 != null && animator2 != null && animator3 != null && animator4 != null) {
172             animator.cancel();
173             animator1.cancel();
174             animator2.cancel();
175             animator3.cancel();
176             animator4.cancel();
177             isRunning = false;
178         }
179 
180     }
181 
182 
183     @Override
184     protected void onWindowVisibilityChanged(int visibility) {
185         super.onWindowVisibilityChanged(visibility);
186         if (visibility == View.VISIBLE && !isRunning) {
187             Log.i("ProgressBar_Modern", "可見,運行");
188             start();
189         } else if (visibility == View.GONE && isRunning) {
190             Log.i("ProgressBar_Modern", "不可見,暫停");
191             cancel();
192         }
193     }
194 
195 
196     @Override
197     protected void onVisibilityChanged(View changedView, int visibility) {
198         super.onVisibilityChanged(changedView, visibility);
199         if (visibility == View.VISIBLE && !isRunning) {
200             Log.i("ProgressBar_Modern", "可見,運行");
201             start();
202         } else if (visibility == View.GONE && isRunning) {
203             Log.i("ProgressBar_Modern", "不可見,暫停");
204             cancel();
205         }
206     }
207 
208     public float getCx0() {
209         return cx0;
210     }
211 
212     public void setCx0(float cx0) {
213         this.cx0 = cx0;
214         invalidate();
215     }
216 
217     public float getCx1() {
218         return cx1;
219     }
220 
221     public void setCx1(float cx1) {
222         this.cx1 = cx1;
223         invalidate();
224     }
225 
226     public float getCx2() {
227         return cx2;
228     }
229 
230     public void setCx2(float cx2) {
231         this.cx2 = cx2;
232         invalidate();
233     }
234 
235     public float getCx3() {
236         return cx3;
237     }
238 
239     public void setCx3(float cx3) {
240         this.cx3 = cx3;
241         invalidate();
242     }
243 
244     public float getCx4() {
245         return cx4;
246     }
247 
248     public void setCx4(float cx4) {
249         this.cx4 = cx4;
250         invalidate();
251     }
252 
253 //    @Override
254 //    public boolean onTouchEvent(MotionEvent event) {
255 //        switch (event.getAction()) {
256 //            case MotionEvent.ACTION_DOWN:
257 //                this.start();
258 //        }
259 //        return true;
260 //    }
261 
262     private class DecelerateAccelerateInterpolator implements TimeInterpolator {
263 
264         private DecelerateAccelerateInterpolator() {
265 
266         }
267 
268         @Override
269         public float getInterpolation(float input) {
270 
271 //            if (input < 0.5) {
272 //                return (float) Math.sqrt(0.25 - (input - 0.5) * (input - 0.5));
273 //            } else {
274 //                return (float) (1 - Math.sqrt(0.25 - (input - 0.5) * (input - 0.5)));
275 //            }
276             return (float) (Math.asin(2 * input - 1) / Math.PI + 0.5);
277         }
278     }
279 }
MyProgressBar.java

最後附上一個時間轉換工具類:

  1 package com.lcw.rabbit.myblog.utils;
  2 
  3 import java.text.ParseException;
  4 import java.text.SimpleDateFormat;
  5 import java.util.Date;
  6 import java.util.Locale;
  7 
  8 /**
  9  * 時間轉換工具類
 10  */
 11 public class TimeUtil {
 12     /**
 13      * String轉換爲時間
 14      *
 15      * @param str
 16      * @return
 17      */
 18     public static Date ParseDate(String str) {
 19         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
 20         Date addTime = null;
 21         try {
 22             addTime = dateFormat.parse(str);
 23         } catch (ParseException e) {
 24             e.printStackTrace();
 25         }
 26         return addTime;
 27     }
 28 
 29     /**
 30      * 將日期轉換爲字符串
 31      *
 32      * @param date
 33      * @return
 34      */
 35     public static String ParseDateToString(Date date) {
 36         return ParseDateToString(date, "yyyy-MM-dd HH:mm:ss");
 37     }
 38 
 39     /**
 40      * 將日期轉換爲字符串(重載)
 41      *
 42      * @param date
 43      * @param format:時間格式,必須符合yyyy-MM-dd hh:mm:ss
 44      * @return
 45      */
 46     public static String ParseDateToString(Date date, String format) {
 47         SimpleDateFormat dateFormat = new SimpleDateFormat(format);
 48 
 49         return dateFormat.format(date);
 50     }
 51 
 52     /**
 53      * 將UMT時間轉換爲本地時間
 54      *
 55      * @param str
 56      * @return
 57      * @throws ParseException
 58      */
 59     public static Date ParseUTCDate(String str) {
 60         //格式化2012-03-04T23:42:00+08:00
 61         SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.CHINA);
 62         try {
 63             Date date = formatter.parse(str);
 64 
 65             return date;
 66         } catch (ParseException e) {
 67             //格式化Sat, 17 Mar 2012 11:37:13 +0000
 68             //Sat, 17 Mar 2012 22:13:41 +0800
 69             try {
 70                 SimpleDateFormat formatter2 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.CHINA);
 71                 Date date2 = formatter2.parse(str);
 72 
 73                 return date2;
 74             } catch (ParseException ex) {
 75                 return null;
 76             }
 77         }
 78     }
 79 
 80     /**
 81      * 將時間轉換爲中文
 82      *
 83      * @param datetime
 84      * @return
 85      */
 86     public static String DateToChineseString(Date datetime) {
 87         Date today = new Date();
 88         long seconds = (today.getTime() - datetime.getTime()) / 1000;
 89 
 90         long year = seconds / (24 * 60 * 60 * 30 * 12);// 相差年數
 91         long month = seconds / (24 * 60 * 60 * 30);//相差月數
 92         long date = seconds / (24 * 60 * 60);     //相差的天數
 93         long hour = (seconds - date * 24 * 60 * 60) / (60 * 60);//相差的小時數
 94         long minute = (seconds - date * 24 * 60 * 60 - hour * 60 * 60) / (60);//相差的分鐘數
 95         long second = (seconds - date * 24 * 60 * 60 - hour * 60 * 60 - minute * 60);//相差的秒數
 96 
 97         if (year > 0) {
 98             return year + "年前";
 99         }
100         if (month > 0) {
101             return month + "月前";
102         }
103         if (date > 0) {
104             return date + "天前";
105         }
106         if (hour > 0) {
107             return hour + "小時前";
108         }
109         if (minute > 0) {
110             return minute + "分鐘前";
111         }
112         if (second > 0) {
113             return second + "秒前";
114         }
115         return "未知時間";
116     }
117 }
TimeUtil.java

 

這樣第一個列表界面就完成了,後面頁面的實現基本一致,這裏就不重複再講了,今天先寫到這裏,改天繼續更新,有什麼建議或疑問,能夠在文章評論給我留言。

 

接下一篇《安卓開發筆記——打造屬於本身的博客園APP(三)

做者:李晨瑋
出處:http://www.cnblogs.com/lichenwei/本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。正在看本人博客的這位童鞋,我看你氣度不凡,談吐間隱隱有王者之氣,往後必有一番做爲!旁邊有「推薦」二字,你就順手把它點了吧,相得準,我分文不收;相不許,你也好回來找我!

相關文章
相關標籤/搜索