Android显示网络图片并点击会放大图片
开发过程中遇到的问题:CLEARTEXT communication to xxx.xxx.xxx not permitted by network security policy
1、W/System.err: java.net.UnknownServiceException:
报错
W/System.err: java.net.UnknownServiceException:
CLEARTEXT communication to xxx.xxx.xxx not permitted by network security policy
问题分析:
为保证用户数据和设备的安全,Google针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。
在Android P系统的设备上,如果应用使用的是非加密的明文流量的http网络请求,则会导致该应用无法进行网络请求,https则不会受影响,同样地,如果应用嵌套了webview,webview也只能使用https请求。
解决方法:往AndroidManifest
的application
中设置下列属性即可。
android:usesCleartextTraffic="true"
一、效果演示
这次主要是实现的功能有以下几点:
- 通过网络图片链接显示网络图片。
- 点击该图片后会放大图片。(主要是实现这个)
如下gif
所示:通过之前的博客,我们已经实现了上传图片到服务器,我们可以得到上传图片的链接,接着就可以通过图片链接来访问到图片,并在页面展示出来。并点击之后实现放大功能。
:
二、 导入相关依赖
(1)导入Glide框架依赖
显示网络图片我们可以导入Glide
图片框架。且Glide
框架版本不能小于4.12.0
。我们在build.gradle
里面添加下面代码即可。
//glide图片框架
implementation "com.github.bumptech.glide:glide:4.12.0"
implementation 'com.github.bumptech.glide:annotations:4.12.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation 'jp.wasabeef:glide-transformations:4.3.0'
(2)导入点击图片打开大图依赖
实现图片放大的话我们可以导入下面这个库。我们在build.gradle
里面添加下面代码即可。
implementation 'com.github.FlyJingFish.OpenImage:OpenImageGlideLib:v1.2.92'
该作者的Github
网址为:https://github.com/FlyJingFish/OpenImage,想深入了解的可以进去看看。
三、 实现显示图片以及点击放大图片的工具类:
(1)MyApplication
、MyAppGlideModule
、BigImageHelperImpl
以及MyImageLoader
MyApplication类:
工具类,不用看,主要是加载大图。复制到项目里面即可,报错也先别管,先把后面的类也用上。
import android.app.Application;
import com.flyjingfish.openimagelib.OpenImageConfig;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyApplication extends Application {
public static MyApplication mInstance;
public static ExecutorService cThreadPool = Executors.newFixedThreadPool(5);
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
//初始化大图加载器
OpenImageConfig.getInstance().setBigImageHelper(new BigImageHelperImpl());
}
}
MyAppGlideModule类:
工具类,不用看。复制到项目里面即可,报错也先别管,先把后面的类也用上。
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
super.applyOptions(context, builder);
builder.setLogLevel(Log.DEBUG);
}
}
MyImageLoader类:
我们主要是调用这个类来显示图片,也不用看,后面会有怎么使用。
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
//import com.example.picture10.GlideApp;
//import com.example.picture10.MyApplication;
import com.flyjingfish.openimagelib.enums.ImageDiskMode;
import com.flyjingfish.openimagelib.utils.ActivityCompatHelper;
import com.flyjingfish.openimagelib.utils.ScreenUtils;
import jp.wasabeef.glide.transformations.BlurTransformation;
public class MyImageLoader {
public static int load_img_type = 0;
public static final int GLIDE = 1;
public static int loader_os_type = GLIDE;
public static ImageDiskMode imageDiskMode = ImageDiskMode.CONTAIN_ORIGINAL;
private MyImageLoader() {
}
private static MyImageLoader mInstance;
public static MyImageLoader getInstance() {
if (null == mInstance) {
synchronized (MyImageLoader.class) {
if (null == mInstance) {
mInstance = new MyImageLoader();
}
}
}
return mInstance;
}
public void load(ImageView iv, String url, @DrawableRes int p, @DrawableRes int err) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext())){
return;
}
into(url, iv, 0, 0, p, err, false, -1, false);
}
public void load(ImageView iv, String url, boolean isCircle, @DrawableRes int p, @DrawableRes int err) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext())){
return;
}
into(url, iv, 0, 0, p, err, isCircle, -1, false);
}
public void load(ImageView iv, String url, int w, int h, @DrawableRes int p, @DrawableRes int err) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext()))
return;
into(url, iv, w, h, p, err, false, -1, false,null);
}
public void load(ImageView iv, String url, int w, int h, @DrawableRes int p, @DrawableRes int err,OnImageLoadListener requestListener) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext()))
return;
into(url, iv, w, h, p, err, false, -1, false,requestListener);
}
public void load(ImageView iv, String url, int w, int h, boolean isCircle, @DrawableRes int p, @DrawableRes int err,OnImageLoadListener requestListener) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext()))
return;
into(url, iv, w, h, p, err, isCircle, -1, false,requestListener);
}
public void loadRoundCorner(ImageView iv, String url, float radiusDp, @DrawableRes int p, @DrawableRes int err) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext()))
return;
into(url, iv, 0, 0, p, err, false, radiusDp, false);
}
public void loadRoundCorner(ImageView iv, String url, float radiusDp, int w, int h, @DrawableRes int p, @DrawableRes int err,OnImageLoadListener requestListener) {
if (!ActivityCompatHelper.assertValidRequest(iv.getContext()))
return;
into(url, iv, w, h, p, err, false, radiusDp, false,requestListener);
}
private void into(String url, ImageView iv, int w, int h, @DrawableRes int p, @DrawableRes int err, boolean isCircle, float radiusDp, boolean isBlur) {
into(url, iv, w, h, p, err, isCircle, radiusDp, isBlur,null);
}
private void into(String url,ImageView iv, int w, int h, @DrawableRes int p, @DrawableRes int err, boolean isCircle, float radiusDp, boolean isBlur, OnImageLoadListener requestListener) {
if (loader_os_type == GLIDE){
RequestBuilder<Drawable> requestBuilder = GlideApp.with(iv).load(url);
if (isBlur || isCircle || radiusDp != -1) {
Transformation[] transformations = new Transformation[0];
if (isBlur && !isCircle && radiusDp == -1) {
transformations = new Transformation[]{new CenterCrop(), new BlurTransformation()};
} else if (isBlur && isCircle && radiusDp == -1) {
transformations = new Transformation[]{new CenterCrop(), new BlurTransformation(), new CircleCrop()};
} else if (isBlur && !isCircle && radiusDp != -1) {
transformations = new Transformation[]{new CenterCrop(), new BlurTransformation(), new RoundedCorners(dp2px(radiusDp))};
} else if (!isBlur && isCircle && radiusDp == -1) {
transformations = new Transformation[]{new CenterCrop(), new CircleCrop()};
} else if (!isBlur && !isCircle && radiusDp != -1) {
transformations = new Transformation[]{new CenterCrop(), new RoundedCorners(dp2px(radiusDp))};
}
RequestOptions mRequestOptions = new RequestOptions().transform(transformations);
requestBuilder.apply(mRequestOptions);
if (w > 0 && h > 0)
mRequestOptions.override(w, h);
} else if (w > 0 && h > 0) {
requestBuilder.override(w, h);
} else if (w == Target.SIZE_ORIGINAL && h == Target.SIZE_ORIGINAL) {
requestBuilder.override(w, h);
}
if (imageDiskMode == ImageDiskMode.RESULT){
requestBuilder.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
}if (imageDiskMode == ImageDiskMode.CONTAIN_ORIGINAL){
requestBuilder.diskCacheStrategy(DiskCacheStrategy.ALL);
}else if (imageDiskMode == ImageDiskMode.NONE){
requestBuilder.diskCacheStrategy(DiskCacheStrategy.NONE);
}
if (p != -1)
requestBuilder.placeholder(p);
if (err != -1)
requestBuilder.error(err);
if (requestListener != null){
requestBuilder.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
requestListener.onFailed();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
requestListener.onSuccess();
return false;
}
});
}
requestBuilder.into(iv);
}
}
public interface OnImageLoadListener{
void onSuccess();
void onFailed();
}
public static int dp2px(float value) {
return (int) ScreenUtils.dp2px(MyApplication.mInstance,value);
}
}
BigImageHelperImpl类:
工具类,是加载大图的关键。
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import com.flyjingfish.openimagelib.listener.BigImageHelper;
import com.flyjingfish.openimagelib.listener.OnLoadBigImageListener;
public class BigImageHelperImpl implements BigImageHelper {
@Override
public void loadImage(Context context, String imageUrl, OnLoadBigImageListener onLoadBigImageListener) {
RequestOptions requestOptions = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.format(DecodeFormat.PREFER_RGB_565);
Glide.with(context)
.load(imageUrl).apply(requestOptions).addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
onLoadBigImageListener.onLoadImageFailed();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
onLoadBigImageListener.onLoadImageSuccess(resource);
return false;
}
}).into(new CustomTarget<Drawable>() {
@Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
}
@Override
public void loadImage(Context context, String imageUrl, ImageView imageView) {
RequestOptions requestOptions = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.format(DecodeFormat.PREFER_RGB_565);
Glide.with(context)
.load(imageUrl).apply(requestOptions).into(imageView);
}
}
(2)代码编写实现。
为了看懂,我写在一个Activity里。我将其命名为·TTTActivity·
MainActivity
:
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.example.pictureapp.R;
import com.example.pictureapp.dao.UserDao;
import com.example.pictureapp.imageloader.MyImageLoader;
import com.flyjingfish.openimagelib.OpenImage;
import com.flyjingfish.openimagelib.beans.OpenImageUrl;
import com.flyjingfish.openimagelib.listener.ItemLoadHelper;
import com.flyjingfish.openimagelib.listener.OnLoadCoverImageListener;
import com.flyjingfish.openimagelib.listener.SourceImageViewIdGet;
import com.flyjingfish.openimagelib.transformers.ScaleInTransformer;
import java.util.ArrayList;
import java.util.List;
public class TTTActivity extends AppCompatActivity {
private List<ImageDTO> onePieceList2 = new ArrayList<>();
RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tttactivity);
initView();
}
private void initView(){
TextView tvName = findViewById(R.id.tvName);
tvName.setText(UserDao.user.getNickname());
ImageDTO imageEntity1 = new ImageDTO("http://xqzyyds.top/images/img_2.png");
ImageDTO imageEntity2 = new ImageDTO("http://xqzyyds.top/images/img_3.png");
ImageDTO imageEntity3 = new ImageDTO("http://xqzyyds.top/images/img_4.png");
ImageDTO imageEntity4 = new ImageDTO("http://xqzyyds.top/images/img_5.png");
onePieceList2.add(imageEntity1);
onePieceList2.add(imageEntity2);
onePieceList2.add(imageEntity3);
onePieceList2.add(imageEntity4);
onePieceList2.add(imageEntity1);
onePieceList2.add(imageEntity2);
onePieceList2.add(imageEntity3);
onePieceList2.add(imageEntity4);
recyclerView = findViewById(R.id.rv);
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
TTTActivity.RvAdapter myAdapter = new RvAdapter(onePieceList2);
recyclerView.setAdapter(myAdapter);
}
/**
* 自定义图片瀑布流适配器
*/
private class RvAdapter extends RecyclerView.Adapter<TTTActivity.RvAdapter.MyHolder> {
List<ImageDTO> datas;
public RvAdapter(List<ImageDTO> datas) {
this.datas = datas;
}
@NonNull
@Override
public TTTActivity.RvAdapter.MyHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
//设置布局
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_person, parent, false);
return new TTTActivity.RvAdapter.MyHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TTTActivity.RvAdapter.MyHolder holder, int position) {
//图片
MyImageLoader.getInstance().load(holder.ivImage, datas.get(position).getCoverImageUrl(), R.mipmap.img_load_placeholder, R.mipmap.img_load_placeholder);
//设置点击图片后打开大图的监听器
holder.ivImage.setOnClickListener(v -> {
OpenImage.with(TTTActivity.this).setClickRecyclerView(recyclerView, new SourceImageViewIdGet() {
@Override
public int getImageViewId(OpenImageUrl data, int position) {
return R.id.iv_image;
}
})
.setAutoScrollScanPosition(true)
.setSrcImageViewScaleType(ImageView.ScaleType.CENTER_CROP, true)
.setImageUrlList(datas).setImageDiskMode(MyImageLoader.imageDiskMode)
.setItemLoadHelper(new ItemLoadHelper() {
@Override
public void loadImage(Context context, OpenImageUrl openImageUrl, String imageUrl, ImageView imageView, int overrideWidth, int overrideHeight, OnLoadCoverImageListener onLoadCoverImageListener) {
MyImageLoader.getInstance().load(imageView, imageUrl, overrideWidth, overrideHeight, R.mipmap.img_load_placeholder, R.mipmap.img_load_placeholder, new MyImageLoader.OnImageLoadListener() {
@Override
public void onSuccess() {
onLoadCoverImageListener.onLoadImageSuccess();
}
@Override
public void onFailed() {
onLoadCoverImageListener.onLoadImageFailed();
}
});
}
}).addPageTransformer(new ScaleInTransformer())
.setOpenImageStyle(R.style.DefaultPhotosTheme)
.setClickPosition(position).show();
});
}
@Override
public int getItemCount() {
return datas.size();
}
class MyHolder extends RecyclerView.ViewHolder {
ImageView ivImage;
public MyHolder(@NonNull View itemView) {
super(itemView);
ivImage = (ImageView) itemView.findViewById(R.id.iv_image);
}
}
}
上面的布局:activity_tttactivity.xml
因为有些资源是图片,所以换上自己的图片就行。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".activity.test.TTTActivity">
<ScrollView
android:scrollbars="none"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="80dp">
<Button
android:id="@+id/set"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="30dp"
android:background="@mipmap/icon_setting" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="130dp"
android:gravity="center_vertical|bottom"
android:paddingBottom="30dp">
<androidx.cardview.widget.CardView
android:id="@+id/ivHead"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginStart="30dp"
android:layout_marginEnd="20dp"
app:cardElevation="10dp"
app:cardCornerRadius="12dp"
app:cardPreventCornerOverlap="true">
<ImageView
android:layout_width="60dp"
android:layout_height="60dp"
android:src="@drawable/icon_moren_usericon" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/tvName"
android:layout_width="200dp"
android:layout_height="30dp"
android:layout_toEndOf="@+id/ivHead"
android:ellipsize="end"
android:letterSpacing="0.1"
android:maxLines="1"
android:text="用户名"
android:textColor="#515151"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_word"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_below="@+id/tvName"
android:layout_marginTop="10dp"
android:layout_toEndOf="@+id/ivHead"
android:text="留言:" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_below="@id/tv_word"
android:layout_marginStart="30dp"
android:layout_toEndOf="@+id/ivHead"
android:maxLines="2"
android:text="这个人很懒,什么都没留下……" />
</RelativeLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#e8e8e8"
android:layout_below="@id/tvMessage"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#03808080"
android:paddingTop="5dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:paddingBottom="60dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>
单个图片布局:
item_person.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:id="@+id/iv_image"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:layout_margin="2dp"
android:scaleType="fitXY">
</ImageView>
每个图片封装起来:
ImageDTO:
import com.flyjingfish.openimagelib.beans.OpenImageUrl;
import com.flyjingfish.openimagelib.enums.MediaType;
public class ImageDTO implements OpenImageUrl {
public String url;
public ImageDTO(String url) {
this.url = url;
}
public ImageDTO() {
}
@Override
public String getImageUrl() {
return url;//大图链接(或视频的封面大图链接)
}
@Override
public String getVideoUrl() {
return null;//视频链接
}
@Override
public String getCoverImageUrl() {
return url;//封面小图链接(或视频的封面小图链接)
}
@Override
public MediaType getType() {
return MediaType.IMAGE;//数据是图片还是视频
}
}
四、完善代码
全部写完之后这里会有一个爆红:
但是没关系,我们需要编译一下,然后重新点击这个红色的地方就可以import了,编译不行的话,就点击执行。
这里是图片加载不出来时显示的图片,可以换上自己要显示的图片:
比如:
最后大功告成,谢谢大家的观看,如有问题欢迎反馈。