1、前言:平时在android端用jsoup爬取网页数据时经常用到下拉刷新和上拉加载更多,逛了不少的博客、简书、也看了不少人家的优秀代码,发现了一个非常简单的方式,不敢独享所以发出来。
2、用到的框架主要是这两个
BaseRecyclerViewAdapterHelper和SmartRefreshLayout(以防失效自己复制粘贴)
BRVH的GitHub地址
https://github.com/CymChad/BaseRecyclerViewAdapterHelper(以防失效自己复制粘贴)
SmartRefreshLayout的GitHub地址
https://www.jianshu.com/p/e8137cfb6812(以防失效自己复制粘贴)
GitHub传送门
https://github.com/xiaweizi/jsoupJianshuDemo(以防失效自己复制粘贴)
3、进入主题了,我第一次写废话多了点
我取得的数据来源主要是http://www.mzitu.com/的妹子自拍标签下的数据,注意这个标签下的图片没有防盗链,其他的有防盗链的,抓到无法显示的,还有这只是用于学习,不做商业用途,
首先进到http://www.mzitu.com/的自拍标签下把www换成m后刷新进手机版网页,手机版网页的图片对手机进行过适配可以很好兼容
原来的网页如下图
进入手机版后如下图
要抓数据就抓最合适的,要达到最佳效果手机和电脑两个网页要反复切换的。还有取数据用到Jsoup和OkHttp3如果不会请自行百度学习,很简单的
正事前啰嗦一下Jsoup本身也可以把一个网页请求下来再抓取数据,但是我不推荐,Jsoup抓取静态页面很牛逼,用专业的网络框架请求网页效果会更嗨,说一下抓数据的思路,先用OkHttp3请求网页数据把整张网页请求下来再交给Jsoup抓取网页中的资源,再把数据放到集合里再展示。
private void getDataFromNet(String url) {
mHttpClient = new OkHttpClient();
Request request = new Request
.Builder()
.url(url)
.build();
mHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
// paseHtml(json);
processData(json);
// handler.sendEmptyMessage(10);
}
});
}
取数据和搞适配器
private void processData(String json) {
meiZiBeans=spiderData(json) ;
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.addData(meiZiBeans);
}
});
}
private ArrayList spiderData(String html) {
ArrayList meiZiBeans = new ArrayList<>();
Document doc = Jsoup.parse(html);
Elements lis = doc.select("div.comment-body");
for (Element e : lis) {
MeiZiBean meiZiBean = new MeiZiBean();
String img = e.select("img").attr("src");
Log.e("图片地址", img);
meiZiBean.setMeiziImg(img);
meiZiBeans.add(meiZiBean);
}
Log.e("集合的大小", String.valueOf(meiZiBeans.size()));
return meiZiBeans;
}
拿到数据后搞适配器没做判空,要就自己加,这里展示数据也不能在主线程可以用Handler或者runOnUiThread
适配器代码也很简单
public class MZiAdapter extends BaseQuickAdapter {
private List mList;
private Context context;
public MZiAdapter(Context context) {
super(R.layout.item_card_view);
this.context=context;
}
@Override
protected void convert(BaseViewHolder helper, MeiZiBean item) {
Glide.with(context).load(item.getMeiziImg()).dontAnimate().into((ImageView) helper.getView(R.id.image));
}
}
再说一下BaseRecyclerViewAdapterHelper自带一个上下文mContext,这种Adapter在用的时候传一个上下文,最重要的放数据进去是addData和setNewData();这两个有区别的。这个适配器就是简书的那位同志的代码风格,照套的自己写传个集合的Adapter也可以的和setNewDate的效果一样的。
接下来就是下拉刷新和上拉加载更多了
private void refreshData() {
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(RefreshLayout refreshlayout) {
getDataFromNet(A_baseUrl + Page + B_baseUrl);
refreshlayout.finishRefresh(2000);
}
});
refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() {
@Override
public void onLoadmore(RefreshLayout refreshlayout) {
LoadMore=true;
++Page;
getDataFromNet(A_baseUrl + Page + B_baseUrl);
Log.e("item数", String.valueOf(mAdapter.getItemCount()));
Log.e("集合数", String.valueOf(meiZiBeans.size()));
refreshlayout.finishLoadmore(2000);
}
});
}
注意这里的下拉刷新没有清空原来的集合再填重新添加数据,只是重新传一下第一页的地址给看一下效果而已,重要的是的是上拉加载更多页数加一后重新调用getDataFromNet()方法再搞适配器Adapter,addData();就是这样就实现了加载更多。
下面贴一下全部的主代码
public class MainActivity extends AppCompatActivity {
private RefreshLayout refreshLayout;
private RecyclerView recyclerView;
private MZiAdapter mAdapter;
private ArrayList meiZiBeans;
private OkHttpClient mHttpClient;
int Page = 1;
String A_baseUrl = "http://m.mzitu.com/zipai/comment-page-";
String B_baseUrl = "/#comments";
// private Handler handler = new Handler() {
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// mAdapter.setNewData(meiZiBeans);
//
// }
// };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.rv_mian);
mAdapter = new MZiAdapter(this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mAdapter);
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
mAdapter.openLoadAnimation(BaseQuickAdapter.SLIDEIN_BOTTOM);
//上拉刷新下拉加载更多
refreshLayout = findViewById(R.id.srl_m_refresh);
// meiZiBeans = new ArrayList<>();
getDataFromNet(A_baseUrl + Page + B_baseUrl);
mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "点击了第" + position + "个item", Toast.LENGTH_LONG).show();
}
});
refreshData();
}
private void refreshData() {
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(RefreshLayout refreshlayout) {
getDataFromNet(A_baseUrl + Page + B_baseUrl);
refreshlayout.finishRefresh(2000);
}
});
refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() {
@Override
public void onLoadmore(RefreshLayout refreshlayout) {
++Page;
getDataFromNet(A_baseUrl + Page + B_baseUrl);
Log.e("item数", String.valueOf(mAdapter.getItemCount()));
Log.e("集合数", String.valueOf(meiZiBeans.size()));
refreshlayout.finishLoadmore(4000);
}
});
}
private void getDataFromNet(String url) {
mHttpClient = new OkHttpClient();
Request request = new Request
.Builder()
.url(url)
.build();
mHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
paseHtml(json);
// processData(json);
// handler.sendEmptyMessage(10);
}
});
}
private void paseHtml(String json) {
meiZiBeans = new ArrayList<>();
Document doc = Jsoup.parse(json);
Elements lis = doc.select("div.comment-body");
for (Element e : lis) {
MeiZiBean meiZiBean = new MeiZiBean();
String img = e.select("img").attr("src");
Log.e("图片地址", img);
meiZiBean.setMeiziImg(img);
meiZiBeans.add(meiZiBean);
}
Log.e("集合的大小", String.valueOf(meiZiBeans.size()));
runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.addData(meiZiBeans);
// mAdapter.setNewData(meiZiBeans);
}
});
}
// private void processData(String json) {
// meiZiBeans=spiderData(json) ;
// runOnUiThread(new Runnable() {
// @Override
// public void run() {
// mAdapter.addData(meiZiBeans);
// }
//
//
// });
//
// }
// private ArrayList spiderData(String html) {
// ArrayList meiZiBeans = new ArrayList<>();
// Document doc = Jsoup.parse(html);
// Elements lis = doc.select("div.comment-body");
// for (Element e : lis) {
// MeiZiBean meiZiBean = new MeiZiBean();
// String img = e.select("img").attr("src");
// Log.e("图片地址", img);
// meiZiBean.setMeiziImg(img);
// meiZiBeans.add(meiZiBean);
// }
// Log.e("集合的大小", String.valueOf(meiZiBeans.size()));
// return meiZiBeans;
// }
}
那个SmartRefreshLayout设置样式我选择在application中设置全局的复制官方的改一下颜色就得了,有兴趣的自己改样式
public class MyApp extends Application{
static {
//设置全局的Header构建器
SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() {
@Override
public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) {
layout.setPrimaryColorsId(R.color.colorPrimary, android.R.color.white);//全局设置主题颜色
return new BezierCircleHeader(context);//.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header
}
});
//设置全局的Footer构建器
SmartRefreshLayout.setDefaultRefreshFooterCreater(new DefaultRefreshFooterCreater() {
@Override
public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) {
//指定为经典Footer,默认是 BallPulseFooter
return new ClassicsFooter(context).setDrawableSize(20);
}
});
}
}
别忘了在功能清单文件里配置
android:name=".myapplication.MyApp"
日志集合数据的变化
这种每次集合都是18
gif效果图
注意这里没有做下拉刷新逻辑的要的就自己做,这里下拉刷新就是重新传链接在加载,自己操作试试,慢慢体会addData的效果
这就是一种上拉加载更多的做法,我看过很多上拉加载的例子,发现他们很多都是两种做法,一是每次上拉加载的都是新的,集合重新new数据再装,在把数据加到RecyclerView的尾部,像以上的代码做法一样,不过这个有坑的,就是点击事件的时候就很蛋疼,按照传统的做法根据item的position去取集合的数据时就会发生下标越界,我也已经解决了,你们要自己找办法,二是每次下拉加载更多时,把数据都加到一个集合里,就是集合里的数据是连续的,这样就与RecyclerView 的item一一对应,这两种做法都可以的,注意用BaseRecyclerViewAdapterHelper的setNewData()就是做法二,集合是连续的,再啰嗦一下用addData()可以用BaseRecyclerViewAdapterHelper里的所有动画,可以完美使用,用setNewData()的话必须把所有的动画都禁用,包括布局的包裹属性也要改成固定大小,不然会很难看的,用setNewData()可以基本实现需求这样,但是我觉得他不是严格意义上的上拉加载
以下是第二种做法的上拉加载更多:
public class MainActivity extends AppCompatActivity {
private RefreshLayout refreshLayout;
private RecyclerView recyclerView;
private MZiAdapter mAdapter;
private ArrayList meiZiBeans;
private OkHttpClient mHttpClient;
int Page = 1;
String A_baseUrl = "http://m.mzitu.com/zipai/comment-page-";
String B_baseUrl = "/#comments";
// private Handler handler = new Handler() {
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// mAdapter.setNewData(meiZiBeans);
//
// }
// };
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.rv_mian);
mAdapter = new MZiAdapter(this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mAdapter);
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
mAdapter.openLoadAnimation(BaseQuickAdapter.SLIDEIN_BOTTOM);
//上拉刷新下拉加载更多
meiZiBeans = new ArrayList<>();
refreshLayout = findViewById(R.id.srl_m_refresh);
getDataFromNet(A_baseUrl + Page + B_baseUrl);
mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "点击了第" + position + "个item", Toast.LENGTH_LONG).show();
}
});
refreshData();
}
private void refreshData() {
refreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(RefreshLayout refreshlayout) {
getDataFromNet(A_baseUrl + Page + B_baseUrl);
refreshlayout.finishRefresh(2000);
}
});
refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() {
@Override
public void onLoadmore(RefreshLayout refreshlayout) {
++Page;
getDataFromNet(A_baseUrl + Page + B_baseUrl);
Log.e("item数", String.valueOf(mAdapter.getItemCount()));
Log.e("集合数", String.valueOf(meiZiBeans.size()));
refreshlayout.finishLoadmore(4000);//加载效果时间为4s
}
});
}
private void getDataFromNet(String url) {
mHttpClient = new OkHttpClient();
Request request = new Request
.Builder()
.url(url)
.build();
mHttpClient.newCall(request).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String json = response.body().string();
paseHtml(json);
// processData(json);
// handler.sendEmptyMessage(10);
}
});
}
private void paseHtml(String json) {
// meiZiBeans = new ArrayList<>();
Document doc = Jsoup.parse(json);
Elements lis = doc.select("div.comment-body");
for (Element e : lis) {
MeiZiBean meiZiBean = new MeiZiBean();
String img = e.select("img").attr("src");
Log.e("图片地址", img);
meiZiBean.setMeiziImg(img);
meiZiBeans.add(meiZiBean);
}
Log.e("集合的大小", String.valueOf(meiZiBeans.size()));
runOnUiThread(new Runnable() {
@Override
public void run() {
// mAdapter.addData(meiZiBeans);
mAdapter.setNewData(meiZiBeans);
}
});
}
// private void processData(String json) {
// meiZiBeans=spiderData(json) ;
// runOnUiThread(new Runnable() {
// @Override
// public void run() {
// mAdapter.addData(meiZiBeans);
// }
//
//
// });
//
// }
// private ArrayList spiderData(String html) {
// ArrayList meiZiBeans = new ArrayList<>();
// Document doc = Jsoup.parse(html);
// Elements lis = doc.select("div.comment-body");
// for (Element e : lis) {
// MeiZiBean meiZiBean = new MeiZiBean();
// String img = e.select("img").attr("src");
// Log.e("图片地址", img);
// meiZiBean.setMeiziImg(img);
// meiZiBeans.add(meiZiBean);
// }
// Log.e("集合的大小", String.valueOf(meiZiBeans.size()));
// return meiZiBeans;
// }
}
注意一下
meiZiBeans = new ArrayList<>();
的位置在这里集合已经是全局变量的,看日志数据的变化
上拉一次集合变36,再上拉一次就加18,就变54新的数据就是加载更多的数据,这种做法思路在xRecyclerView的案例里也是差不多,把数据加进一个集合里再notifyDataSetChanged()刷新一下Adapter。
上个gi效果图有点模糊还有删除多余的帧数。
源码没有上传,要的留言一下,我再上传。