
Product
Reachability for Ruby Now in Beta
Reachability analysis for Ruby is now in beta, helping teams identify which vulnerabilities are truly exploitable in their applications.
io.github.flyjingfish:openimage-full
Advanced tools
Your own low-intrusive large image viewer, high imitation WeChat perfect transition animation, supports custom video players, and can also customize the kernel for loading pictures, such as Glide, Picasso or others
推荐一个库 AndroidAOP 一个注解就可请求权限,禁止多点,切换线程等等
| RecyclerView场景 | 聊天页面 | 打开视频 |
|---|---|---|
![]() | ![]() | ![]() |
| 朋友圈 | 快手 | WebView |
|---|---|---|
![]() | ![]() | ![]() |
1、支持自定义图片加载引擎
2、支持多种图片缓存模式
3、支持聊天界面的查看大图功能
4、支持任意定制属于你的大图查看页面UI,多种定制方式总有一种适合你(点此查看更多使用说明)
5、支持打开后的大图页面数据的增、删、改、查(点此查看更多使用说明)
6、支持全部 ImageView.ScaleType 显示模式的图片打开大图效果,并且新增startCrop、endCrop、autoStartCenterCrop、autoEndCenterCrop四种显示模式
7、支持图片和视频混合数据
8、支持传入包含图片的 RecyclerView、ViewPager、ViewPager2、ListView、GridView 和 多个ImageView 的调用方式,甚至 WebView,傻瓜式调用,无需关心图片切换后该返回到哪个位置
9、支持Gif图
10、支持长图和长图阅读模式
11、支持显示超大图及放大后的清晰细节图
12、支持视频缩放拖动功能、图片缩放拖动功能
13、支持自定义大图切换效果(PageTransformer)
14、支持 Compose 示例
1、建议使用Glide效果更好,另外建议开启原图缓存(有些版本是自动缓存原图的)Glide通过设置diskCacheStrategy 为DiskCacheStrategy.ALL或DiskCacheStrategy.DATA
2、当然如果您加载的是本地图片可直接忽略第1点
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
你可以选择下面三种的其中一种,在module下的build.gradle添加。
请注意如果使用以下导入方式,如果你的项目组存在GSYVideoPlayer请升级至 10.0.0(从2.3.0开始升级至 10.0.0,之前是8.3.0,并且需要升级AGP到8.1.1以上) 或者更高的版本,否则会冲突
//OpenImageFullLib 默认已经包含了OpenImageGlideLib
implementation 'io.github.flyjingfish:openimage-full:2.4.8'
//OpenImageFullLib 因为已经包含了 OpenImageGlideLib,所以需要排除掉 OpenImageGlideLib,否则会同时存在 Glide 和 Coil
implementation ('io.github.flyjingfish:openimage-full:2.4.8'){
exclude module: 'OpenImageGlideLib'
}
//OpenImageCoilLib 引入Coil(2.4.0)图片引擎
implementation 'io.github.flyjingfish:openimage-coil:2.4.8'
在 Glide 和 Coil 中选一个作为图片加载器
//OpenImageGlideLib 引入Glide(4.12.0)图片引擎,没有引入视频播放器;如需定制视频播放功能,详细看Wiki文档,如果不想定制可直接使用上边的库
implementation 'io.github.flyjingfish:openimage-glide:2.4.8'
//OpenImageCoilLib 引入Coil(2.4.0)图片引擎,没有引入视频播放器;如需定制视频播放功能,详细看Wiki文档,如果不想定制可直接使用上边的库
implementation 'io.github.flyjingfish:openimage-coil:2.4.8'
自己定义加载大图时请注意内存溢出问题,详情可看Wiki文档(点此查看常见问题)
//OpenImageLib 是基础库,没有引入图片引擎和视频播放器
//至少需要实现BigImageHelper来定制您的图片引擎,如需定制视频播放功能,详细看Wiki文档
implementation 'io.github.flyjingfish:openimage-base:2.4.8'
先确保你有没有添加以下权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Android 13版本适配,细化存储权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
</manifest>
你可以选择下面两种图片数据的其中一种
List<String> dataList = new ArrayList<>();
for (ImageEntity data : datas) {
dataList.add(data.getImageUrl());
}
//在点击时调用(以下以RecyclerView为例介绍)
OpenImage.with(activity)
//点击ImageView所在的RecyclerView(也支持设置setClickViewPager2,setClickViewPager,setClickGridView,setClickListView,setClickImageView,setClickWebView,setNoneClickView)
.setClickRecyclerView(recyclerView,new SourceImageViewIdGet() {
@Override
public int getImageViewId(OpenImageUrl data, int position) {
return R.id.iv_image;//点击的ImageView的Id或者切换图片后对应的ImageView的Id
}
})
//点击的ImageView的ScaleType类型(如果设置不对,打开的动画效果将是错误的)
.setSrcImageViewScaleType(ImageView.ScaleType.CENTER_CROP,true)
//RecyclerView的数据
.setImageUrlList(dataList, MediaType.IMAGE)
//点击的ImageView所在数据的位置
.setClickPosition(position)
//开始展示大图
.show();
在此着重再介绍下 SourceImageViewIdGet 在这是动态获取 ImageView 的Id的,如果你的 RecyclerView(或ViewPager2、ListView、GridView) 展示的图片 Id 出现多个则需根据 getImageViewId 中的 position 或 data 来返回对应的 Id,例如:
new SourceImageViewIdGet() {
@Override
public int getImageViewId(OpenImageUrl data, int position) {
//data 可强转为您传入的数据类型
MessageBean msgBean = (MessageBean) data;
//在此视频数据和图片数据的 ImageView 的 Id 不一样(根据您具体情况返回对应的Id即可,以下仅为示例~)
if (msgBean.type == MessageBean.IMAGE){
return R.id.iv_image;
}else {
return R.id.iv_video;
}
}
}
PS:列表中展示的图片链接和展示大图时所用链接是不同时(即存在缩略图和原始大图两种链接的情况),这种方式可以有更好的过渡效果
public class ImageEntity implements OpenImageUrl {
public String photoUrl;//图片大图
public String smallPhotoUrl;//图片小图
public String coverUrl;//视频封面大图
public String smallCoverUrl;//视频封面小图
public String videoUrl;//视频链接
public int resouceType; //0图片1视频
@Override
public String getImageUrl() {
return resouceType == 1 ? coverUrl : photoUrl;//大图链接(或视频的封面大图链接)
}
@Override
public String getVideoUrl() {
return videoUrl;//视频链接
}
@Override
public String getCoverImageUrl() {//这个代表前边列表展示的图片(即缩略图)
return resouceType == 1 ? smallCoverUrl : smallPhotoUrl;//封面小图链接(或视频的封面小图链接)
}
@Override
public MediaType getType() {
return resouceType == 1 ? MediaType.VIDEO : MediaType.IMAGE;//数据是图片还是视频
}
}
然后调用显示
//在点击时调用(以下以RecyclerView为例介绍)
OpenImage.with(activity)
//点击ImageView所在的RecyclerView(也支持设置setClickViewPager2,setClickViewPager,setClickGridView,setClickListView,setClickImageView,setClickWebView,setNoneClickView)
.setClickRecyclerView(recyclerView,new SourceImageViewIdGet() {
@Override
public int getImageViewId(OpenImageUrl data, int position) {
return R.id.iv_image;//点击的ImageView的Id或者切换图片后对应的ImageView的Id
}
})
//点击的ImageView的ScaleType类型(如果设置不对,打开的动画效果将是错误的)
.setSrcImageViewScaleType(ImageView.ScaleType.CENTER_CROP,true)
//RecyclerView的数据
.setImageUrlList(datas)
//点击的ImageView所在数据的位置
.setClickPosition(position)
//开始展示大图
.show();
如果没有可以传的View(即不使用动画打开大图页面)
//在点击时调用(以下以RecyclerView为例介绍)
OpenImage.with(activity)
//打开大图页面时没有点击的ImageView则用这个
.setNoneClickView()
//图片的数据
.setImageUrlList(datas)
//默认展示数据的位置
.setClickPosition(position)
//开始展示大图
.show();
PS.完整调用示例 (点此查看更多使用说明)
//在点击时调用,按需使用即可(以下以RecyclerView为例介绍)
OpenImage.with(activity)
//点击ImageView所在的RecyclerView(也支持设置setClickViewPager2,setClickViewPager,setClickGridView,setClickListView,setClickImageView,setClickWebView)
.setClickRecyclerView(recyclerView,new SourceImageViewIdGet() {
@Override
public int getImageViewId(OpenImageUrl data, int position) {
return R.id.iv_image;//点击的ImageView的Id或者切换图片后对应的ImageView的Id
}
})
//点击的ImageView的ScaleType类型(如果设置不对,打开的动画效果将是错误的)
.setSrcImageViewScaleType(ImageView.ScaleType.CENTER_CROP,true)
//RecyclerView的数据
.setImageUrlList(datas)
//点击的ImageView所在数据的位置
.setClickPosition(position)
//clickDataPosition 点击的数据所在位置 clickViewPosition 点击的视图所在位置(和上边方法二选一,详细使用方法可看wiki文档)
//这个方法主要是针对的是像聊天页面那种图文混合的数据,可以看 "聊天页面" Demo
.setClickPosition(clickDataPosition, clickViewPosition)
//可不设置(定制页面样式,详细可看Wiki文档)
.setOpenImageStyle(R.style.DefaultPhotosTheme)
//设置显示在页面上层的fragment(可不设置)
.setUpperLayerFragmentCreate(new FriendLayerFragmentCreateImpl(),bundle,false,false)
//设置自定义的视频播放fragment页面(可不设置)
.setVideoFragmentCreate(new VideoFragmentCreateImpl())
//设置加载失败时显示的图片(可不设置)
.setErrorResId(R.mipmap.ic_launcher)
//设置退出页面时,如果页面无对应的ImageView则回到点击位置(类似微信聊天页面的效果)(可不设置)
.setWechatExitFillInEffect(true)
//设置true后关闭时,将看不到前一页面正在查看的图片(可不设置)
.setShowSrcImageView(true)
//设置自定义的大图外壳页面(可不设置)
.setOpenImageActivityCls(MyBigImageActivity.class)
//设置切换图片时前一页面跟随滚动(可不设置)
.setAutoScrollScanPosition(true)
//设置显示下载按钮(可不设置,默认不显示,打开后默认图片和视频都会显示此按钮)
.setShowDownload()
//设置显示关闭按钮(可不设置,默认不显示,打开后默认只在显示图片时显示,因为视频页面默认有返回按钮,如需都要显示可在此基础上传入更多参数)
.setShowClose()
//设置画廊效果,参数为左右两侧漏出的宽度(可不设置)
.setGalleryEffect(10)
//设置切换图片监听(可不设置)
.setOnSelectMediaListener(new OnSelectMediaListener() {
@Override
public void onSelect(OpenImageUrl openImageUrl, int position) {
}
})
//设置点击监听(可不设置)
.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(BaseInnerFragment fragment, OpenImageUrl openImageUrl, int position) {
}
})
//设置长按图片监听(可不设置)
.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public void onItemLongClick(BaseInnerFragment fragment, OpenImageUrl openImageUrl, int position) {
}
})
//设置退出大图页面时的监听(可不设置)
.setOnExitListener(new OnExitListener() {
@Override
public void onExit() {
}
})
//设置切换大图时的效果(可不设置,本库中目前只有这一个,如需其他效果可参照ScaleInTransformer自行定义效果)
.addPageTransformer(new ScaleInTransformer())
//开始展示大图
.show();
(如果您使用的是 OpenImageFullLib 或 OpenImageGlideLib 或 OpenImageCoilLib 则不需要这一步)
自己定义加载大图时请注意内存溢出问题,详情可看Wiki文档(点此查看常见问题)
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//初始化大图加载器
OpenImageConfig.getInstance().setBigImageHelper(new BigImageHelperImpl());
}
}
public class BigImageHelperImpl implements BigImageHelper {
@Override
public void loadImage(Context context, String imageUrl, OnLoadBigImageListener onLoadBigImageListener) {
//这个地方只是示例,如果你的项目存在超大图,请注意需要自行处理(否则可能内存溢出或崩溃)
//不想自己搞的,可直接用 OpenImageGlideLib 或 OpenImageFullLib
RequestOptions requestOptions = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
//这句是为了加载原图,如果你的原图可能是超大图,请注意内存溢出问题,不想自己搞的,可直接用 OpenImageGlideLib 或 OpenImageFullLib
.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) {
}
});
}
}
(如果您使用的是 OpenImageFullLib 则不需要这一步)
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
//初始化视频创建类
OpenImageConfig.getInstance().setVideoFragmentCreate(new VideoFragmentCreateImpl());
}
}
public class VideoFragmentCreateImpl implements VideoFragmentCreate {
@Override
public BaseFragment createVideoFragment() {
return new VideoPlayerFragment();
}
}
public class VideoPlayerFragment extends BaseImageFragment<ENDownloadView> {
private FragmentVideoBinding binding;
private String playerKey;
private boolean isLoadImageFinish;
protected boolean isPlayed;
@Override
protected ImageView getSmallCoverImageView() {//返回小封面图
return binding.videoPlayer.getSmallCoverImageView();
}
@Override
protected ImageView getPhotoView() {//返回大封面图,必须在小封面图下边
return binding.videoPlayer.getCoverImageView();
}
@Override
protected ENDownloadView getLoadingView() {//返回loadingView
return (ENDownloadView) binding.videoPlayer.getLoadingView();
}
@Override
protected void hideLoading(ENDownloadView pbLoading) {//隐藏loading需要特殊处理的重写这个
super.hideLoading(pbLoading);
pbLoading.release();
binding.videoPlayer.getStartButton().setVisibility(View.VISIBLE);
}
@Override
protected void showLoading(ENDownloadView pbLoading) {//显示loading需要特殊处理的重写这个
super.showLoading(pbLoading);
pbLoading.start();
binding.videoPlayer.getStartButton().setVisibility(View.GONE);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.videoPlayer.findViewById(R.id.back).setOnClickListener(v -> close());
playerKey = binding.videoPlayer.getVideoKey();
binding.videoPlayer.goneAllWidget();
isPlayed = false;
}
@Override
protected void onTouchClose(float scale) {//下拉关闭回调
super.onTouchClose(scale);
binding.videoPlayer.findViewById(R.id.surface_container).setVisibility(View.GONE);
binding.videoPlayer.goneAllWidget();
}
@Override
protected void onTouchScale(float scale) {//下拉时回调
super.onTouchScale(scale);
binding.videoPlayer.goneAllWidget();
if (scale == 1){
binding.videoPlayer.showAllWidget();
}
}
@Override
protected void loadImageFinish(boolean isLoadImageSuccess) {
isLoadImageFinish = true;
play();
}
private void play(){
if (isTransitionEnd && isLoadImageFinish && !isPlayed){//这里可以不等封面图加载完就播放,这个是为了更好的效果
if (getLifecycle().getCurrentState() == Lifecycle.State.RESUMED){
toPlay4Resume();
}else {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_RESUME){
toPlay4Resume();
source.getLifecycle().removeObserver(this);
}
}
});
}
isPlayed = true;
}
}
protected void toPlay4Resume(){
binding.videoPlayer.playUrl(openImageBean.getVideoUrl());
binding.videoPlayer.startPlayLogic();
}
@Override
protected void onTransitionEnd() {
super.onTransitionEnd();
play();
}
@Override
public void onResume() {
super.onResume();
if (playerKey != null) {
GSYVideoController.resumeByKey(playerKey);
}
}
@Override
public void onPause() {
super.onPause();
if (playerKey != null) {
GSYVideoController.pauseByKey(playerKey);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (playerKey != null) {
GSYVideoController.cancelByKeyAndDeleteKey(playerKey);
}
}
@Override
public View getExitImageView() {//退出页面时需要保证封面图可见
binding.videoPlayer.getThumbImageViewLayout().setVisibility(View.VISIBLE);
return super.getExitImageView();
}
}
GSYVideoPlayer 的混淆规则:
-keep class com.shuyu.gsyvideoplayer.video.** { *; }
-dontwarn com.shuyu.gsyvideoplayer.video.**
-keep class com.shuyu.gsyvideoplayer.video.base.** { *; }
-dontwarn com.shuyu.gsyvideoplayer.video.base.**
-keep class com.shuyu.gsyvideoplayer.utils.** { *; }
-dontwarn com.shuyu.gsyvideoplayer.utils.**
-keep class tv.danmaku.ijk.** { *; }
-dontwarn tv.danmaku.ijk.**
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, java.lang.Boolean);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
Glide 的混淆规则:
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep class * extends com.bumptech.glide.module.AppGlideModule {
<init>(...);
}
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder {
*** rewind();
}
Coil 混淆规则,遵循 Coroutines,OkHttp
# When editing this file, update the following files as well:
# - META-INF/com.android.tools/proguard/coroutines.pro
# - META-INF/com.android.tools/r8/coroutines.pro
# ServiceLoader support
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Most of volatile fields are updated with AFU and should not be mangled
-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
}
# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater
-keepclassmembers class kotlin.coroutines.SafeContinuation {
volatile <fields>;
}
# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
-dontwarn java.lang.instrument.ClassFileTransformer
-dontwarn sun.misc.SignalHandler
-dontwarn java.lang.instrument.Instrumentation
-dontwarn sun.misc.Signal
# Only used in `kotlinx.coroutines.internal.ExceptionsConstructor`.
# The case when it is not available is hidden in a `try`-`catch`, as well as a check for Android.
-dontwarn java.lang.ClassValue
# An annotation used for build tooling, won't be directly accessed.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Okhttp3
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
最低SDK版本:minSdkVersion >= 22
都看到这里了,如果您喜欢 OpenImage,或感觉 OpenImage 帮助到了您,可以点右上角“Star”支持一下,您的支持就是我的动力,谢谢~ 😃
如果感觉 OpenImage 为您节约了大量开发时间、为您的项目增光添彩,您也可以扫描下面的二维码,请作者喝杯咖啡 ☕

FAQs
Your own low-intrusive large image viewer, high imitation WeChat perfect transition animation, supports custom video players, and can also customize the kernel for loading pictures, such as Glide, Picasso or others
We found that io.github.flyjingfish:openimage-full demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Product
Reachability analysis for Ruby is now in beta, helping teams identify which vulnerabilities are truly exploitable in their applications.

Research
/Security News
Malicious npm packages use Adspect cloaking and fake CAPTCHAs to fingerprint visitors and redirect victims to crypto-themed scam sites.

Security News
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.