Android 网络图片查看器HappyLook开发
一、前言
有时候浏览网站图片感觉不是很方便,不是广告太多就是要一个个翻页。我们都知道用Python做数据的爬取挺方便的,所以我想Java有没有类似的方法可以获取网页图片数据然后在Android端上显示。
答案肯定是有的,所以我就搞出来了,老规矩先看看效果:
图片来源:摄图网照片
应用主界面 >
查看图片详情 >
二、框架介绍
1、Jsoup简介
Jsoup 是Java的一个HTML解析工具,可直接解析某个URL地址、HTML文本内容,功能如下:
(1)从一个URL,文件或字符串中解析HTML
(2)使用DOM或CSS选择器来查找、取出数据使用DOM或CSS选择器来查找、取出数据
(3)可操作HTML元素、属性、文本可操作HTML元素、属性、文本
主要类:Jsoup类提供了连接,清理和解析HTML文档的方法;Document 获取HTML文档;Element 获取、操作HTML节点。详情见官网
2、EventBus简介
EventBus是由greenrobot组织开发的一个Android事件发布/订阅轻量级框架,通过解耦发布者和订阅者简化Android事件传递。EventBus可以代替Android传统的Intent、Handler、Broadcast或接口函数,在Fragment、Activity、Service线程之间传递数据以及执行方法。详情见源码
3、RecyclerView及Glide
RecyclerView就不用多说了,主要用于数据的列表展示。
Glide 是一款强大的Android图片加载和缓存库,可高效地加载网络图片、本地图片、资源ID图片等格式的图片文件,代码简洁好用。详情见郭霖大神博客
三、具体实现
1、需求确认
以摄图网照片网址为例:https://699pic.com/photo/
可以看到其首页有各个分类的照片,点击某一项可看到其详情图片页
图片详情页包含了图片主题、图片数量等内容
eeeee… 这小龙虾把我都看饿了 T-T
总额言之!我们只需要实现首页的分类预览以及详情展示即可,其他信息可根据个人需求获取。。
2、引入依赖
确定好开发所需的框架后,在项目中的app / build.gradle下导入依赖:
导入Jsoup、EventBus、Glide以及SpinKit一个动画加载库,用于网络请求时显示动画效果
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'org.greenrobot:eventbus:3.2.0'
implementation 'com.github.bumptech.glide:glide:4.12.0'
implementation 'com.github.ybq:Android-SpinKit:1.4.0'
3、UI布局
项目的需求比较简单,首页分类图片的显示,在主布局文件中使用RecyclerView做首页图片预览即可。
activity_main.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<!--自定义标题栏-->
<RelativeLayout
android:id="@+id/tool_bar"
android:background="@color/blue"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:id="@+id/tag_name"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@color/white"
android:text="HappyLook"/>
</RelativeLayout>
<!--加载动画-->
<com.github.ybq.android.spinkit.SpinKitView
android:id="@+id/load_view"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
style="@style/SpinKitView.CubeGrid"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tool_bar"
android:padding="8dp" />
</RelativeLayout>
然后是一个点击查看图片详情的界面,包含了返回按钮、图片名称(数量)
sec_layout.xml文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--自定义标题栏-->
<RelativeLayout
android:id="@+id/pic_bar"
android:background="@color/blue"
android:layout_width="match_parent"
android:layout_height="50dp">
<!--返回按钮-->
<ImageView
android:id="@+id/btn_back"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_marginStart="10dp"
android:src="@mipmap/ic_back"/>
<!--图片分类-->
<TextView
android:id="@+id/pic_name"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@color/white"
android:text="火锅烧烤"/>
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_centerHorizontal="true"
android:layout_below="@id/pic_bar"
android:id="@+id/pic_recycler_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"/>
<com.github.ybq.android.spinkit.SpinKitView
android:id="@+id/pic_load_view"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerInParent="true"
style="@style/SpinKitView.Circle"
android:visibility="gone"/>
</RelativeLayout>
4、网页解析
解析网页获取元素,就是通过网页特有的标签属性对所需元素进行定位。这是关键的一步,使用浏览器开发者模式定位获取所需的元素,我用的是谷歌浏览器按F12就能进入,其他浏览器应该也差不多。图片分类首页进入开发者模式后就点击左上角Element一栏,开始元素定位:
1、div class=“img-show” 整个网页显示分类图片的预览的地方
2、div class=“pl-list” 每一项的图片分类
3、href=" " 该类图片的详情链接地址
4、data-original=" " 预览封面的图片地址
5、alt=" " 图片分类主题
然后点击进入到图片详情页获取元素
1、li class=“list” 每张图片项
2、 img src=" " data-original=" " 图片的地址(两个获取哪个都可以)
最后获取该类图片的页数
"div pager-linkPage" 图片页数列表
"a href=" " 页数链接
以上就是我们需要获取的所有元素。
5、功能实现
这里我介绍几个主要的类和方法,其他比较常用的东西就不多说了。项目Demo地址我会在文章末尾给大家放出来,如果有感兴趣的朋友可以去看一看。
根据网页解析所得到的元素,我们先建立一个bean文件夹并在里面新建一个Pictures类,包括图片详情链接、预览图链接以及分类名称三个变量。Pictures.java如下:
/**
* Created by qzh.
* Date: 2021/4/19
*/
public class Pictures {
private String picUrl; //图片详情链接
private String preViewImg; //预览图链接
private String picName; //分类名称
public String getPicUrl() {
return picUrl;
}
public void setPicUrl(String picUrl) {
this.picUrl = picUrl;
}
public String getPreViewImg() {
return preViewImg;
}
public void setPreViewImg(String preViewImg) {
this.preViewImg = preViewImg;
}
public String getPicName() {
return picName;
}
public void setPicName(String picName) {
this.picName = picName;
}
}
然后新建一个Tools.java工具类,用于解析网页获取数据、发送事件等操作。
sendMeaasge() 封装消息发送方法,使用EventBus来发送传递数据给主线程,一个带数据,一个不带数据。
/**
* EventBus发送消息
* @param what 事件标签
*/
public static void sendMessage(int what){
Message message = new Message();
message.what = what;
EventBus.getDefault().post(message);
}
/**
* EventBus发送消息
* @param what 事件标签
* @param obj 数据对象
*/
public static void sendMessage(int what , Object obj){
Message message = new Message();
message.what = what;
message.obj = obj;
EventBus.getDefault().post(message);
}
InitData() 函数主要通过Jsoup来对网址链接进行解析 在子线程中进行,connect连接网址、timeout设置请求超时限制,使用get方法获取Docment实例,大家可以打印里面的数据看看,出来的是body的文本类型。
然后根据解析类型来执行不同的方法:getMainData() 获取首页数据,getPicData() 获取照片详情数据。
/**
* 初始化网页数据
* @param mUrl 解析链接
* @param type 解析类型:0是首页,1是图片详情页
*/
public static void initData( String mUrl , int type){
new Thread(() -> {
try {
Document document = Jsoup.connect(mUrl).timeout(5000).get();
if (0 == type){
if (list != null){
list.clear();
}
getMainData(document);
}else if (1 == type){
if (mList != null){
mList.clear();
}
getPicData(document);
}
}catch (Exception e){
e.printStackTrace();
sendMessage(1008613);
}
}).start();
}
getMainData() 获取首页数据:
根据我们上面所定位的标签获取相应元素,每一个elements对象对应这一个类型的照片,根据其数量生成对应的Pictures实例,并添加到列表中,最后使用自定义的sendMessage() 向主线程发送事件和数据。
/**
* 定位首页元素,获取分类预览封面、标题、链接
* 生成 Pictures 实例
* @param document Jsoup文档类解析网页body
*/
private static void getMainData(Document document ){
Elements elements = document.select("div.pl-list");
for (Element element : elements){
String preImg = element.select("img").attr("data-original");
String name = element.select("img").attr("alt");
String url = element.select("a").attr("href");
Pictures pictures = new Pictures();
pictures.setPreViewImg(preImg);
pictures.setPicName(name);
pictures.setPicUrl(url);
list.add(pictures);
}
sendMessage(10086, list);
}
getPicData() 获取图片详情数据:
使用getPicSrc() 方法获取每张图片的链接,getPageLink() 方法获取翻页并返回翻页链接数量,之后循环获取每页中的具体照片链接,最后使用sendMessage() 向主线程发送消息和数据
/**
* 获取所有照片数据
* 若当前类图片存在翻页,则循环获取其他页数照片数据
* @param document Jsoup文档类解析网页body
*/
private static void getPicData(Document document) {
mList = getPicSrc(document);
List<String> linkList = getPageLink(document);
if (linkList.size() != 0 ){
for (int i = 0 ; i < linkList.size() ; i++){
try {
Document doc = Jsoup.connect(linkList.get(i)).timeout(5000).get();
mList.addAll(getPicSrc(doc));
}catch (Exception e){
e.printStackTrace();
sendMessage(1008613);
}
}
}
Log.d(TAG, "照片数量: "+ mList.size());
sendMessage(1008611 , mList);
}
接下来就是实现MainActivity里的方法了:
在onCreate() 方法里调用initView() 声明控件,并调用Tools.initData() 获取网页数据,要注意的是在使用EventBus之前需要对订阅者(当前上下文)进行注册,在活动销毁的时候取消注册。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
Tools.initData(Tools.photoUrl,0);
}
@Override
protected void onStart() {
super.onStart();
if (!EventBus.getDefault().isRegistered(this)){
EventBus.getDefault().register(this); //注册当前上下文
}
}
@SuppressLint("WrongViewCast")
public void initView(){
recyclerView = findViewById(R.id.main_recycler);
tagName = findViewById(R.id.tag_name);
loadView = findViewById(R.id.load_view);
GridLayoutManager layoutManager = new GridLayoutManager(this,2);
recyclerView.setLayoutManager(layoutManager);
adapter = new MainAdapter(this ,this);
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this); //活动销毁注销
}
在应用加载完首页分类的图片后,需要点击进入到图片详情页面中,这时候需要在适配器中写一个监听接口,然后再MainActivity中实现:(这里适配器贴个接口代码,具体实现可参考Demo)
MainAdapter.java
/**
* Created by qzh.
* Date: 2021/4/17
*/
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.ViewHolder> {
private final ItemClickListener mListener;
public interface ItemClickListener{
void clickListener(View v ,String categoryName/*图片分类名称*/, String moreUrl/*该类图片链接*/);
}
}
MainActivity.java 中实现接口,获取类名以及链接,再次调用Tools.initData() 获取图片详情页,最后结果通过sendMessage() 返回
@Override
public void clickListener(View v, String categoryName, String moreUrl) {
loadView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.INVISIBLE);
picName = categoryName;
Tools.initData(moreUrl , 1);
}
然后是消息接收处理,使用EventBus发送过来的消息在MainActivty中的处理如下:
将该处理函数声明为主线程接收模式 @Subscribe(threadMode = ThreadMode.MAIN) 这样就会接收来自Tools里的信息,然后根据msg.what执行相应的操作:
10086: 获取首页图片数据,这里需要把msg.obj对象转化一下变成 Pictures 的列表对象 然后更新到适配器中显示出来。
1008611: 获取图片详情页数据,将图片类型名称以及数据列表传到 SecActivity中显示出来。
后面两个方法就是对UI进行简单的控制
@Subscribe(threadMode = ThreadMode.MAIN)
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 10086:
loadView.setVisibility(View.INVISIBLE);
adapter.setList((List<Pictures>)msg.obj);
recyclerView.setAdapter(adapter);
break;
case 1008611:
loadView.setVisibility(View.INVISIBLE);
ArrayList<String> mList = (ArrayList<String>)msg.obj;
Intent intent = new Intent(MainActivity.this,SecActivity.class);
intent.putExtra("picName",picName);
intent.putStringArrayListExtra("picUrl", mList);
startActivity(intent);
break;
case 1008612:
recyclerView.setVisibility(View.VISIBLE);
break;
case 1008613:
Toast.makeText(this, "出现错误!!", Toast.LENGTH_SHORT).show();
break;
}
}
点击某个分类图片执行1008611下的方法,启动SecActivity 显示图片详情。
SecActivity.java 如下:
获取图片类型名称、图片数据以及图片数量,最后显示出来
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sec_layout);
picName = getIntent().getStringExtra("picName");
mList = getIntent().getStringArrayListExtra("picUrl");
initView();
}
private void initView() {
picRecyclerView = findViewById(R.id.pic_recycler_view);
btn_Back = findViewById(R.id.btn_back);
pic_Name = findViewById(R.id.pic_name);
picLoadView = findViewById(R.id.pic_load_view);
btn_Back.setOnClickListener(view -> {
Tools.sendMessage(1008612);
finish();
});
String title = picName +" ("+mList.size()+"p) ";
pic_Name.setText(title);
adapter = new SecAdapter(SecActivity.this , mList);
manager = new LinearLayoutManager(this);
picRecyclerView.setLayoutManager(manager);
picRecyclerView.setAdapter(adapter);
}
@Override
public void onBackPressed() {
Tools.sendMessage(1008612);
super.onBackPressed();
}
6、总结
总的来说项目实现起来不难,功能比较简单,主要是使用Jsoup作为网页解析获取数据,EventBus作为通信工具在不同线程中传递信息和数据,再用RecyclerView 和Glide显示出来。后面有时间的话就继续添加完善功能,比如 网址选择,图片下载等,感兴趣的朋友可以尝试搞一下。
最后附上 Demo下载地址《《《
ok,打完收工~