大图查看器是许多app的常用功能,主要使用场景是用户点击图片,然后启动一个新界面来展示完整尺寸的图片,并能通过手势移动图片以及放大缩小。当然,上面说的是最基本的功能,实际使用中还要包括:如果是本地图片应该可以移除,如果是网络图片,应提供一个保存到本地的功能等。
本文为什么叫封装一个大图查看器,而不是叫做编写一个大图查看器呢?因为大图查看器的最核心功能,展示图片以及手势操控我们使用了一个开源库来完成,这个开源库叫做subsampling-scale-image-view,sciv非常靠谱,使用也非常简单,我记得知乎的Android app也是使用了它。这个库的Github地址是:subsampling-scale-image-view。
我先把我做出来的效果图展示出来:
首先第一张是:
然后来看第二张:
两张界面截图展示的是同一张图片,但区别在于,第一张图片右下角有一个下载的图标,对应的是查看网络图片这种方式(大家都需要保存网络图片到本地这个功能);而第二张图片右下角没有了图标,而右上角有一个垃圾桶的图标,用来进行移除操作,想象一下,本来查看的就是本地图片,那用户也就不用再保存它一次了,而移除操作在很多实际使用场景中有效,比如说在微信中,你想发朋友圈,你选择了几张本地照片以后进行大图查看,而这时候你发现其中几张不太好,想移除它,于是为了用户方便,应该给用户提供一个在大图查看器中进行移除的方法。
这就是全屏Dialog的实现方式。接下来,定义Dialog的布局:
public class ScaleImageView {
private static final byte URLS = 0;//网络查看状态
private static final byte FILES = 1;//本地查看状态
private byte status;//用来表示当前大图查看器的状态
private Activity activity;
private List urls;//网络查看状态中传入的要查看的图片的Url的List
private List files;//本地查看状态中传入的要查看的图片对应的file对象的List
private List downloadFiles;//网络查看状态中从Url下载下来的图片对应的Url的List
private int selectedPosition;//表示当前被选中的ViewPager的item的位置
private Dialog dialog;//用于承载整个大图查看器的Dialog
private ImageView delete;//删除图片的按钮
private ImageView download;//保存图片到本地的按钮
private TextView imageCount;//用于显示当前正在查看第几张图片的TextView
private ViewPager viewPager;
private List views;//ViewPager适配器的数据源
private MyPagerAdapter adapter;
private OnDeleteItemListener listener;
private int startPosition;//打开大图查看器时,想要查看的ViewPager的item的位置
public ScaleImageView(Activity activity) {
this.activity = activity;
init();
}
public void setOnDeleteItemListener(OnDeleteItemListener listener) {
this.listener = listener;
}
private void init() {
RelativeLayout relativeLayout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.dialog_scale_image, null);
ImageView close = (ImageView) relativeLayout.findViewById(R.id.scale_image_close);
delete = (ImageView) relativeLayout.findViewById(R.id.scale_image_delete);
download = (ImageView) relativeLayout.findViewById(R.id.scale_image_save);
imageCount = (TextView) relativeLayout.findViewById(R.id.scale_image_count);
viewPager = (ViewPager) relativeLayout.findViewById(R.id.scale_image_view_pager);
dialog = new Dialog(activity, R.style.Dialog_Fullscreen);
dialog.setContentView(relativeLayout);
close.setOnClickListener(v -> dialog.dismiss());
delete.setOnClickListener(v -> {
int size = views.size();
files.remove(selectedPosition);
if (listener != null) {
listener.onDelete(selectedPosition);
}
viewPager.removeView(views.remove(selectedPosition));
if (selectedPosition != size) {
int position = selectedPosition + 1;
String text = position + "/" + views.size();
imageCount.setText(text);
}
adapter.notifyDataSetChanged();
});
download.setOnClickListener(v -> {
try {
MediaStore.Images.Media.insertImage(activity.getContentResolver(),
downloadFiles.get(selectedPosition).getAbsolutePath(),
downloadFiles.get(selectedPosition).getName(), null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Snackbar.make(viewPager, "图片保存成功", Snackbar.LENGTH_SHORT).show();
});
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
selectedPosition = position;
String text = ++position + "/" + views.size();
imageCount.setText(text);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
}
首先,我们关注一下最上面三行定义的几个变量,URLS和FILES两个常量分别表示网络查看模式和本地查看模式,而status则用来标示现在查看器处于哪个模式下。
public interface OnDeleteItemListener {
void onDelete(int position);
}
public void setUrls(List urls, int startPosition) {
if (this.urls == null) {
this.urls = new ArrayList<>();
} else {
this.urls.clear();
}
this.urls.addAll(urls);
status = URLS;
delete.setVisibility(View.GONE);
if (downloadFiles == null) {
downloadFiles = new ArrayList<>();
} else {
downloadFiles.clear();
}
this.startPosition = startPosition++;
String text = startPosition + "/" + urls.size();
imageCount.setText(text);
}
public void setFiles(List files, int startPosition) {
if (this.files == null) {
this.files = new LinkedList<>();
} else {
this.files.clear();
}
this.files.addAll(files);
status = FILES;
download.setVisibility(View.GONE);
this.startPosition = startPosition++;
String text = startPosition + "/" + files.size();
imageCount.setText(text);
}
public void create() {
dialog.show();
views = new ArrayList<>();
adapter = new MyPagerAdapter(views, dialog);
if (status == URLS) {
for (String url : urls) {
FrameLayout frameLayout = (FrameLayout) activity.getLayoutInflater().inflate(R.layout.view_scale_image, null);
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView) frameLayout.findViewById(R.id.scale_image_view);
views.add(frameLayout);
IOThread.getSingleThread().execute(() -> {
File downLoadFile;
try {
downLoadFile = Glide.with(activity).load(url).downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get();
downloadFiles.add(downLoadFile);
activity.runOnUiThread(() -> imageView.setImage(ImageSource.uri(Uri.fromFile(downLoadFile))));
} catch (Exception e) {
e.printStackTrace();
}
});
}
viewPager.setAdapter(adapter);
} else if (status == FILES) {
for (File file : files) {
FrameLayout frameLayout = (FrameLayout) activity.getLayoutInflater().inflate(R.layout.view_scale_image, null);
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView) frameLayout.findViewById(R.id.scale_image_view);
views.add(frameLayout);
imageView.setImage(ImageSource.uri(Uri.fromFile(file)));
}
viewPager.setAdapter(adapter);
}
viewPager.setCurrentItem(startPosition);
}
private static class MyPagerAdapter extends PagerAdapter {
private List views;
private Dialog dialog;
MyPagerAdapter(List views, Dialog dialog) {
this.views = views;
this.dialog = dialog;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(views.get(position));
return views.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (position == 0 && views.size() == 0) {
dialog.dismiss();
return;
}
if (position == views.size()) {
container.removeView(views.get(--position));
} else {
container.removeView(views.get(position));
}
}
@Override
public int getCount() {
return views.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
ScaleImageView scaleImageView = new ScaleImageView(activity);
scaleImageView.setUrls(urls, position);
scaleImageView.create();
ScaleImageView scaleImageView = new ScaleImageView(activity);
scaleImageView.setFiles(files, position);
scaleImageView.setOnDeleteItemListener(deletePosition -> adapter.removeItem(deletePosition));
scaleImageView.create();
public class ScaleImageView {
private static final byte URLS = 0;
private static final byte FILES = 1;
private byte status;
private Activity activity;
private List urls;
private List files;
private List downloadFiles;
private int selectedPosition;
private Dialog dialog;
private ImageView delete;
private ImageView download;
private TextView imageCount;
private ViewPager viewPager;
private List views;
private MyPagerAdapter adapter;
private OnDeleteItemListener listener;
private int startPosition;
public ScaleImageView(Activity activity) {
this.activity = activity;
init();
}
public void setUrls(List urls, int startPosition) {
if (this.urls == null) {
this.urls = new ArrayList<>();
} else {
this.urls.clear();
}
this.urls.addAll(urls);
status = URLS;
delete.setVisibility(View.GONE);
if (downloadFiles == null) {
downloadFiles = new ArrayList<>();
} else {
downloadFiles.clear();
}
this.startPosition = startPosition++;
String text = startPosition + "/" + urls.size();
imageCount.setText(text);
}
public void setFiles(List files, int startPosition) {
if (this.files == null) {
this.files = new LinkedList<>();
} else {
this.files.clear();
}
this.files.addAll(files);
status = FILES;
download.setVisibility(View.GONE);
this.startPosition = startPosition++;
String text = startPosition + "/" + files.size();
imageCount.setText(text);
}
public void setOnDeleteItemListener(OnDeleteItemListener listener) {
this.listener = listener;
}
private void init() {
RelativeLayout relativeLayout = (RelativeLayout) activity.getLayoutInflater().inflate(R.layout.dialog_scale_image, null);
ImageView close = (ImageView) relativeLayout.findViewById(R.id.scale_image_close);
delete = (ImageView) relativeLayout.findViewById(R.id.scale_image_delete);
download = (ImageView) relativeLayout.findViewById(R.id.scale_image_save);
imageCount = (TextView) relativeLayout.findViewById(R.id.scale_image_count);
viewPager = (ViewPager) relativeLayout.findViewById(R.id.scale_image_view_pager);
dialog = new Dialog(activity, R.style.Dialog_Fullscreen);
dialog.setContentView(relativeLayout);
close.setOnClickListener(v -> dialog.dismiss());
delete.setOnClickListener(v -> {
int size = views.size();
files.remove(selectedPosition);
if (listener != null) {
listener.onDelete(selectedPosition);
}
viewPager.removeView(views.remove(selectedPosition));
if (selectedPosition != size) {
int position = selectedPosition + 1;
String text = position + "/" + views.size();
imageCount.setText(text);
}
adapter.notifyDataSetChanged();
});
download.setOnClickListener(v -> {
try {
MediaStore.Images.Media.insertImage(activity.getContentResolver(),
downloadFiles.get(selectedPosition).getAbsolutePath(),
downloadFiles.get(selectedPosition).getName(), null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Snackbar.make(viewPager, "图片保存成功", Snackbar.LENGTH_SHORT).show();
});
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
selectedPosition = position;
String text = ++position + "/" + views.size();
imageCount.setText(text);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
public void create() {
dialog.show();
views = new ArrayList<>();
adapter = new MyPagerAdapter(views, dialog);
if (status == URLS) {
for (String url : urls) {
FrameLayout frameLayout = (FrameLayout) activity.getLayoutInflater().inflate(R.layout.view_scale_image, null);
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView) frameLayout.findViewById(R.id.scale_image_view);
views.add(frameLayout);
IOThread.getSingleThread().execute(() -> {
File downLoadFile;
try {
downLoadFile = Glide.with(activity).load(url).downloadOnly(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get();
downloadFiles.add(downLoadFile);
activity.runOnUiThread(() -> imageView.setImage(ImageSource.uri(Uri.fromFile(downLoadFile))));
} catch (Exception e) {
e.printStackTrace();
}
});
}
viewPager.setAdapter(adapter);
} else if (status == FILES) {
for (File file : files) {
FrameLayout frameLayout = (FrameLayout) activity.getLayoutInflater().inflate(R.layout.view_scale_image, null);
SubsamplingScaleImageView imageView = (SubsamplingScaleImageView) frameLayout.findViewById(R.id.scale_image_view);
views.add(frameLayout);
imageView.setImage(ImageSource.uri(Uri.fromFile(file)));
}
viewPager.setAdapter(adapter);
}
viewPager.setCurrentItem(startPosition);
}
private static class MyPagerAdapter extends PagerAdapter {
private List views;
private Dialog dialog;
MyPagerAdapter(List views, Dialog dialog) {
this.views = views;
this.dialog = dialog;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
container.addView(views.get(position));
return views.get(position);
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (position == 0 && views.size() == 0) {
dialog.dismiss();
return;
}
if (position == views.size()) {
container.removeView(views.get(--position));
} else {
container.removeView(views.get(position));
}
}
@Override
public int getCount() {
return views.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
public interface OnDeleteItemListener {
void onDelete(int position);
}
}