仿9GAG制作过程(四)
有话要说:
这次主要讲述主页面下拉刷新和上拉加载功能的实现。
主要是使用了SwipeRefreshLayout的布局方式,并在此基础上通过RecyclerView的特性增加了上拉加载的功能。
成果:
实现方式:
页面布局:
1 <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:id="@+id/swipeRefreshView" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent"> 5 <android.support.v7.widget.RecyclerView 6 android:id="@+id/recyclerView" 7 android:background="#f0f0f0" 8 android:layout_width="match_parent" 9 android:layout_height="match_parent"/> 10 </android.support.v4.widget.SwipeRefreshLayout>
通过SwipeRefreshLayout来实现下拉刷新功能。
下拉刷新:
1 SwipeRefreshLayout swipeRefreshLayout; 2 // 下拉刷新控件 3 swipeRefreshLayout = getView().findViewById(R.id.swipeRefreshView); 4 // 设置下拉控件背景色 5 swipeRefreshLayout.setProgressBackgroundColorSchemeColor(Color.WHITE); 6 // 设置下来控件主色 7 swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent); 8 // 设置下拉刷新事件 9 swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 10 @Override 11 public void onRefresh() { 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 try { 16 Thread.sleep(1000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 String url = baseUrl + (++currentPage); 21 OkHttpClient client = new OkHttpClient(); 22 Request request = new Request.Builder() 23 .url(url) 24 .build(); 25 try { 26 Response response = client.newCall(request).execute(); 27 String json = response.body().string(); 28 if (json != null) { 29 Gson gson = new Gson(); 30 List<NewsBean> newDatas = gson.fromJson(json, new TypeToken<List<NewsBean>>(){}.getType()); 31 if (newsBeans != null && newsBeans.size() > 0) { 32 newsBeans.addAll(0, newDatas); 33 } 34 } 35 Message message = new Message(); 36 message.what = UPDATE_NEWS; 37 handler.sendMessage(message); 38 } catch (IOException e) { 39 e.printStackTrace(); 40 } 41 } 42 }).start(); 43 } 44 });
通过setProgressBackgroundColorSchemeColor来设置下拉控件的背景色,也就是圈圈的主体颜色。
通过setColorSchemeResources来设置下拉控件中间的线条颜色。
通过setOnRefreshListener来定义下拉刷新事件。
然后通过currentPage来实现下拉刷新之后获取的数据是下一页的数据,再添加到集合开头。
1 private Handler handler = new Handler() { 2 @Override 3 public void handleMessage(Message msg) { 4 switch (msg.what) { 5 case QUERY_NEWS: 6 recyclerView.getAdapter().notifyDataSetChanged(); 7 break; 8 case UPDATE_NEWS: 9 recyclerView.getAdapter().notifyDataSetChanged(); 10 swipeRefreshLayout.setRefreshing(false); 11 break; 12 case LOAD_MORE: 13 ((NewsAdapter)recyclerView.getAdapter()).changeStatus(NewsAdapter.UNLOADING); 14 currentState = NewsAdapter.UNLOADING; 15 break; 16 } 17 } 18 };
刷新完成之后,通过notifyDataSetChanged告诉RecyclerView数据改变了,进而更改页面显示。通过setRefreshing来控制下拉刷新控件的显示。
由此,完成了下拉刷新的实现。
上拉加载:
由于SwipeRefreshLayout并不提供上拉加载的功能,于是准备利用RecyclerView灵活的特性来实现上拉加载功能。
1 package com.example.lanxingren.imitating9gag.adapter; 2 3 import android.content.Context; 4 import android.support.annotation.NonNull; 5 import android.support.v7.widget.CardView; 6 import android.support.v7.widget.RecyclerView; 7 import android.view.LayoutInflater; 8 import android.view.View; 9 import android.view.ViewGroup; 10 import android.widget.ImageView; 11 import android.widget.TextView; 12 13 import com.example.lanxingren.imitating9gag.R; 14 import com.example.lanxingren.imitating9gag.bean.NewsBean; 15 import com.example.lanxingren.imitating9gag.util.GlideApp; 16 17 import java.util.List; 18 19 public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { 20 21 private List<NewsBean> myNewsList; 22 private Context myContext; 23 24 // 是否加载 25 public static final int LOADING = 1; 26 public static final int UNLOADING = 2; 27 28 private int mStatus = UNLOADING;// 当前加载状态 29 30 // item的viewType 31 private final int ITEM = 1; 32 private final int FOOTER = 2; 33 34 static class NewsHolder extends RecyclerView.ViewHolder { 35 CardView cardView; 36 TextView titleView; 37 ImageView imageView; 38 TextView pointView; 39 ImageView likeImageView; 40 ImageView unlikeImageView; 41 42 43 private NewsHolder (View view) { 44 super(view); 45 cardView = (CardView) view; 46 titleView = view.findViewById(R.id.item_title); 47 imageView = view.findViewById(R.id.item_image); 48 pointView = view.findViewById(R.id.item_point); 49 likeImageView = view.findViewById(R.id.item_like); 50 unlikeImageView = view.findViewById(R.id.item_unlike); 51 } 52 } 53 54 static class FooterHolder extends RecyclerView.ViewHolder { 55 CardView cardView; 56 private FooterHolder (View view) { 57 super(view); 58 cardView = view.findViewById(R.id.cardView_footer); 59 } 60 } 61 62 public NewsAdapter (List<NewsBean> newsList) { 63 this.myNewsList = newsList; 64 } 65 66 @Override 67 public int getItemCount() { 68 int count = 0; 69 if (myNewsList != null) { 70 count = myNewsList.size() + 1; 71 } 72 return count; 73 } 74 75 @NonNull 76 @Override 77 public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 78 if (myContext == null) { 79 myContext = parent.getContext(); 80 } 81 82 if (viewType == ITEM) { 83 View view = LayoutInflater.from(myContext).inflate(R.layout.item_news, parent, false); 84 return new NewsHolder(view); 85 } else { 86 View view = LayoutInflater.from(myContext).inflate(R.layout.item_footer, parent, false); 87 return new FooterHolder(view); 88 } 89 } 90 91 @Override 92 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { 93 if (holder instanceof NewsHolder) { 94 final NewsHolder newsHolder = (NewsHolder)holder; 95 final NewsBean newsBean = myNewsList.get(position); 96 97 // 设置标题 98 String title = "9GAG#" + newsBean.getId(); 99 if (newsBean.getTitle() != null && newsBean.getTitle().length() > 0) { 100 title = newsBean.getTitle(); 101 } 102 newsHolder.titleView.setText(title); 103 104 // 屏幕宽度 105 int screenWidth = myContext.getResources() 106 .getDisplayMetrics() 107 .widthPixels; 108 // 屏幕高度 109 int screenHeight = myContext.getResources() 110 .getDisplayMetrics() 111 .heightPixels; 112 113 // 设置图片,不知道为什么override这样设置就可以让图片正正好显示,有时间研究一下? 114 if (newsBean.getUrls() != null && newsBean.getUrls().size() > 0) { 115 GlideApp.with(myContext) 116 .load(newsBean.getUrls().get(0)) 117 .override(screenWidth, screenHeight) 118 .into(newsHolder.imageView); 119 } 120 121 // 设置点赞数 122 String point = Integer.toString(newsBean.getLike() - newsBean.getUnlike()); 123 newsHolder.pointView.setText(point); 124 125 // 设置点赞图标显示 126 int accentColor = myContext.getResources().getColor(R.color.colorAccent); 127 int defaultColor = myContext.getResources().getColor(R.color.defaultColor); 128 switch (newsBean.getIsLiked()) { 129 case -1: 130 newsHolder.likeImageView.setColorFilter(defaultColor); 131 newsHolder.unlikeImageView.setColorFilter(accentColor); 132 break; 133 case 0: 134 newsHolder.likeImageView.setColorFilter(defaultColor); 135 newsHolder.unlikeImageView.setColorFilter(defaultColor); 136 break; 137 case 1: 138 newsHolder.likeImageView.setColorFilter(accentColor); 139 newsHolder.unlikeImageView.setColorFilter(defaultColor); 140 break; 141 } 142 143 // 初始化监听事件 144 newsHolder.likeImageView.setOnClickListener(new View.OnClickListener() { 145 @Override 146 public void onClick(View v) { 147 newsBean.setIsLiked(1); 148 notifyDataSetChanged(); 149 } 150 }); 151 newsHolder.unlikeImageView.setOnClickListener(new View.OnClickListener() { 152 @Override 153 public void onClick(View v) { 154 newsBean.setIsLiked(-1); 155 notifyDataSetChanged(); 156 } 157 }); 158 159 } else if (holder instanceof FooterHolder) { 160 switch (mStatus) { 161 case UNLOADING: 162 ((FooterHolder) holder).cardView.setVisibility(View.GONE); 163 break; 164 case LOADING: 165 ((FooterHolder) holder).cardView.setVisibility(View.VISIBLE); 166 break; 167 } 168 } 169 } 170 171 @Override 172 public int getItemViewType(int position) { 173 if (position + 1 == getItemCount()) { 174 return FOOTER; 175 } else { 176 return ITEM; 177 } 178 } 179 180 public void changeStatus(int status) { 181 this.mStatus = status; 182 notifyDataSetChanged(); 183 } 184 }
在适配器中定义了两个布局,一个是普通布局,一个是尾布局(footer)。
下面是footer的具体布局:
1 <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="wrap_content" 4 xmlns:app="http://schemas.android.com/apk/res-auto" 5 android:layout_marginTop="10dp" 6 android:layout_marginBottom="0dp" 7 app:cardCornerRadius="0dp" 8 android:elevation="0dp" 9 android:id="@+id/cardView_footer"> 10 <ProgressBar 11 android:layout_width="wrap_content" 12 android:layout_height="100dp" 13 android:layout_gravity="center_horizontal" 14 /> 15 </android.support.v7.widget.CardView>
注意点:
- 通过mStatus来判断尾布局是否展示
- ITEM代表的是普通布局,即段子的布局;FOOTER代表的是尾布局
- 由于增加了一个item,故getItemCount得在原先的基础上加上一
- 在onCreateViewHolder中通过viewType来创建不同的viewHolder
- 实现getItemViewType方法,根据position的值来确定viewType
- 在onBindViewHolder中先判断viewHolder的类型,如果是尾布局(footer)的话,再根据mStatus来判断是否展示
- changeStatus主要是给外面用的,通过该方法可以控制footer的显示
使用上拉加载:
1 private void initLoadMoreListener() { 2 recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { 3 @Override 4 public void onScrollStateChanged(final RecyclerView recyclerView, int newState) { 5 super.onScrollStateChanged(recyclerView, newState); 6 7 // 获取当前可见的item位置 8 int lastVisiblePosition = 0; 9 RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 10 if (layoutManager instanceof LinearLayoutManager) { 11 lastVisiblePosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); 12 } 13 14 // 当前加载状态是UNLOADING && 当前可见的item位置是最后一条时 15 if (currentState == NewsAdapter.UNLOADING 16 && lastVisiblePosition + 1 == recyclerView.getAdapter().getItemCount()) { 17 // 改变footer的可见性 18 ((NewsAdapter)recyclerView.getAdapter()).changeStatus(NewsAdapter.LOADING); 19 currentState = NewsAdapter.LOADING; 20 21 new Thread(new Runnable() { 22 @Override 23 public void run() { 24 try { 25 Thread.sleep(1000); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } 29 30 String url = baseUrl + (++currentPage); 31 OkHttpClient client = new OkHttpClient(); 32 Request request = new Request.Builder() 33 .url(url) 34 .build(); 35 try { 36 Response response = client.newCall(request).execute(); 37 String json = response.body().string(); 38 if (json != null) { 39 Gson gson = new Gson(); 40 List<NewsBean> newDatas = gson.fromJson(json, new TypeToken<List<NewsBean>>(){}.getType()); 41 if (newsBeans != null && newsBeans.size() > 0) { 42 newsBeans.addAll(newsBeans.size(), newDatas); 43 } 44 } 45 Message message = new Message(); 46 message.what = LOAD_MORE; 47 handler.sendMessage(message); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 } 52 }).start(); 53 } 54 } 55 }); 56 }
通过给recyclerView加上滚动事件来实现下拉加载功能,具体逻辑如下:
获取页面可见最下面的item位置
→判断当前尾布局的加载状态
→如果尾布局现在的状态是UNLOADING && item位置为最后一个,则开线程加载数据
→将尾布局展示,即开始转圈动画
→请求网络,获取数据,放入数据集
→根据handler的处理,告诉RecyclerView数据改变,然后将footer隐藏
通过以上的过程就可以实现上拉加载的效果。
参考:
SwipeRefreshLayout详解和自定义上拉加载更多,SwipeRefreshLayout + RecyclerView 实现 上拉刷新 和 下拉刷新
结束语:
本次学习了Android的下拉刷新以及上拉加载的实现,对RecyclerView有了进一步的了解。
接下来准备实现点赞功能以及GIF的暂停功能。
大家如果有什么疑问或者建议可以通过评论或者邮件的方式联系我,欢迎大家的评论~