前言:
利用RecyclerView展示朋友圈UI布局,包含展示、预览、删除等功能
1、在项目app\build.gradle添加依赖
//图片加载
implementation 'com.github.bumptech.glide:glide:4.8.0'
//初始化控件找ID
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
//图片选择器
implementation 'com.github.LuckSiege.PictureSelector:picture_library:v2.5.8'
//权限请求
implementation 'com.hjq:xxpermissions:6.5'
build.gradle主项目中添加:maven { url ‘https://jitpack.io’ }
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
具体使用:
①、MainActivity:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@BindView(R.id.edit)
EditText edit;
@BindView(R.id.recycler)
RecyclerView recycler;
private SelectPlotAdapter adapter;
private ArrayList allSelectList;//所有的图片集合
private ArrayList categoryLists;//查看图片集合
private List selectList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
if (null == allSelectList) {
allSelectList = new ArrayList();
}
if (null == categoryLists) {
categoryLists = new ArrayList();
}
Tools.requestPermissions(MainActivity.this);
initAdapter();
}
private void initAdapter() {
//最多9张图片
adapter = new SelectPlotAdapter(this, 9);
recycler.setLayoutManager(new GridLayoutManager(this, 3));
adapter.setImageList(allSelectList);
recycler.setAdapter(adapter);
adapter.setListener(new SelectPlotAdapter.CallbackListener() {
@Override
public void add() {
//可添加的最大张数=9-当前已选的张数
int size = 9 - allSelectList.size();
Tools.galleryPictures(MainActivity.this, size);
}
@Override
public void delete(int position) {
allSelectList.remove(position);
categoryLists.remove(position);
adapter.setImageList(allSelectList);//再set所有集合
}
@Override
public void item(int position, List mList) {
selectList.clear();
for (int i = 0; i < allSelectList.size(); i++) {
LocalMedia localMedia = new LocalMedia();
localMedia.setPath(allSelectList.get(i));
selectList.add(localMedia);
}
//①、图片选择器自带预览
PictureSelector.create(MainActivity.this)
.themeStyle(R.style.picture_default_style)
.isNotPreviewDownload(true)//是否显示保存弹框
.imageEngine(GlideEngine.createGlideEngine()) // 选择器展示不出图片则添加
.openExternalPreview(position, selectList);
//②:自定义布局预览
//Tools.startPhotoViewActivity(MainActivity.this, categoryLists, position);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
// 结果回调
List selectList = PictureSelector.obtainMultipleResult(data);
showSelectPic(selectList);
}
}
private void showSelectPic(List result) {
for (int i = 0; i < result.size(); i++) {
String path;
//判断是否10.0以上
if (Build.VERSION.SDK_INT >= 29) {
path = result.get(i).getAndroidQToPath();
} else {
path = result.get(i).getPath();
}
allSelectList.add(path);
categoryLists.add(path);
Log.e(TAG, "图片链接: " + path);
}
adapter.setImageList(allSelectList);
}
@OnClick({R.id.upload})
public void onClick(View view) {
switch (view.getId()) {
case R.id.upload:
String content = edit.getText().toString();
if (TextUtils.isEmpty(content)) {
Toast.makeText(this, "请输入上传内容", Toast.LENGTH_LONG).show();
return;
}
if (allSelectList.size() == 0) {
Toast.makeText(this, "请选择图片进行上传", Toast.LENGTH_LONG).show();
return;
}
Log.e(TAG, "内容: " + content);
Log.e(TAG, "图片: " + allSelectList.toString());
break;
}
}
}
②、SelectPlotAdapter 展示适配器
public class SelectPlotAdapter extends RecyclerView.Adapter {
private Context mContext;
private List mediaDtoList;
//最大图片张数
private int picMax;
public void setImageList(List mList) {
this.mediaDtoList = mList;
notifyDataSetChanged();
}
public SelectPlotAdapter(Context mContext, int max) {
this.mContext = mContext;
this.picMax = max;
}
@Override
public SelectHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new SelectHolder(LayoutInflater.from(mContext).
inflate(R.layout.item_gallery, parent, false));
}
@Override
public void onBindViewHolder(SelectHolder holder, final int position) {
//当前位置大于图片数量并且小于最大减1
if (position >= mediaDtoList.size() && position <= (picMax - 1)) {
//显示添加图片按钮、并隐藏删除按钮
Tools.showGlide(mContext,holder.gallery,"");
holder.delete.setVisibility(View.GONE);
} else {
//显示本地或网络图片,并显示删除按钮
Tools.showGlide(mContext,holder.gallery,mediaDtoList.get(position));
holder.delete.setVisibility(View.VISIBLE);
}
//按钮删除事件
holder.delete.setOnClickListener(v -> {
//传入position删除第几张
listener.delete(position);
});
holder.gallery.setOnClickListener(v -> {
//添加新图片点击事件(回调activity)
if (position >= mediaDtoList.size() && position <= (picMax - 1)) {
listener.add();
} else {
//点击查看图片事件,并将最新list传入actiuvity
listener.item(position, mediaDtoList);
}
});
}
@Override
public int getItemCount() {
if (mediaDtoList == null || mediaDtoList.size() == 0) {
return 1;
} else {
return this.mediaDtoList.size() >= picMax ? picMax : this.mediaDtoList.size() + 1;
}
}
public class SelectHolder extends RecyclerView.ViewHolder {
private ImageView gallery;
private ImageView delete;
public SelectHolder(View itemView) {
super(itemView);
gallery = itemView.findViewById(R.id.im_show_gallery);
delete = itemView.findViewById(R.id.iv_del);
}
}
private CallbackListener listener;
public void setListener(CallbackListener listener) {
this.listener = listener;
}
public interface CallbackListener {
//图片添加事件
void add();
//删除第几张图片
void delete(int position);
//图片点击
void item(int position, List mList);
}
}
由于Butterknife需使用Java 8故需在依赖处添加
// Butterknife requires Java 8.
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
③、自定义图片预览MyImageAdapter(需要则添加)
public class MyImageAdapter extends PagerAdapter {
private List imageUrls;
private Activity mContext;
public MyImageAdapter(Activity context, List imageUrls) {
this.imageUrls = imageUrls;
this.mContext = context;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
String url = imageUrls.get(position);
PhotoView photoView = new PhotoView(mContext);
Tools.showGlide(mContext, photoView, url);
container.addView(photoView);
photoView.setOnClickListener(v -> mContext.finish());
return photoView;
}
@Override
public int getCount() {
return imageUrls != null ? imageUrls.size() : 0;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
④、工具类Tools:
public class Tools {
/**
* 请求权限
*/
public static void requestPermissions(final AppCompatActivity activity) {
XXPermissions.with(activity)
// 可设置被拒绝后继续申请,直到用户授权或者永久拒绝
//.constantRequest()
// 支持请求6.0悬浮窗权限8.0请求安装权限
//.permission(Permission.SYSTEM_ALERT_WINDOW, Permission.REQUEST_INSTALL_PACKAGES)
// 不指定权限则自动获取清单中的危险权限
.permission(Permission.Group.STORAGE)
.permission(Permission.CAMERA)
.request(new OnPermission() {
@Override
public void hasPermission(List granted, boolean all) {
}
@Override
public void noPermission(List denied, boolean quick) {
}
});
}
/**
* 打开图库
*/
public static void openGallery(AppCompatActivity activity, int maxSize) {
PictureSelector.create(activity)
.openGallery(PictureMimeType.ofImage())//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio()
//.theme()//主题样式(不设置为默认样式) 也可参考demo values/styles下 例如:R.style.picture.white.style
.maxSelectNum(maxSize)// 最大图片选择数量 int
.minSelectNum(1)// 最小选择数量 int
.imageSpanCount(3)// 每行显示个数 int
.imageEngine(GlideEngine.createGlideEngine())
//.selectionMode()// 多选 or 单选 PictureConfig.MULTIPLE or PictureConfig.SINGLE
//.isPreviewImage(true)// 是否可预览图片 true or false
//.isPreviewVideo()// 是否可预览视频 true or false
//.freeStyleCropEnabled() // 是否可播放音频 true or false
.isCamera(false)// 是否显示拍照按钮 true or false
//.imageFormat(PictureMimeType.PNG)// 拍照保存图片格式后缀,默认jpeg
.isZoomAnim(true)// 图片列表点击 缩放效果 默认true
//.setOutputCameraPath("/CustomPath")// 自定义拍照保存路径,可不填
.isEnableCrop(true)// 是否裁剪 true or false
//.isCompress(true)// 是否压缩 true or false
//.withAspectRatio()// int 裁剪比例 如16:9 3:2 3:4 1:1 可自定义
//.hideBottomControls()// 是否显示uCrop工具栏,默认不显示 true or false
//.isGif(false)// 是否显示gif图片 true or false
//.compressSavePath(getPath())//压缩图片保存地址
.freeStyleCropEnabled(true)// 裁剪框是否可拖拽 true or false
//.circleDimmedLayer()// 是否圆形裁剪 true or false
//.showCropFrame()// 是否显示裁剪矩形边框 圆形裁剪时建议设为false true or false
//.showCropGrid()// 是否显示裁剪矩形网格 圆形裁剪时建议设为false true or false
//.isOpenClickSound(false)// 是否开启点击声音 true or false
//.selectionData()// 是否传入已选图片 List list
//.isPreviewEggs()// 预览图片时 是否增强左右滑动图片体验(图片滑动一半即可看到上一张是否选中) true or false
//.cutOutQuality(90)// 裁剪压缩质量 默认90 int
.minimumCompressSize(100)// 小于100kb的图片不压缩
//.synOrAsy(true)//同步true或异步false 压缩 默认同步
//.cropImageWideHigh()// 裁剪宽高比,设置如果大于图片本身宽高则无效 int
//.rotateEnabled(true) // 裁剪是否可旋转图片 true or false
.scaleEnabled(true)// 裁剪是否可放大缩小图片 true or false
//.videoQuality()// 视频录制质量 0 or 1 int
//.videoMaxSecond(15)// 显示多少秒以内的视频or音频也可适用 int
//.videoMinSecond(10)// 显示多少秒以内的视频or音频也可适用 int
//.recordVideoSecond()//视频秒数录制 默认60s int
.forResult(PictureConfig.CHOOSE_REQUEST);//结果回调onActivityResult code
}
/**
* 打开拍照
*/
public static void takingPictures(AppCompatActivity activity) {
PictureSelector.create(activity)
.openCamera(PictureMimeType.ofImage())
.forResult(PictureConfig.REQUEST_CAMERA);
}
/**
* 打开图库+拍照按钮
*/
public static void galleryPictures(AppCompatActivity activity, int maxSize) {
PictureSelector.create(activity)
.openGallery(PictureMimeType.ofImage())//全部.PictureMimeType.ofAll()、图片.ofImage()、视频.ofVideo()、音频.ofAudio()
//.theme()//主题样式(不设置为默认样式) 也可参考demo values/styles下 例如:R.style.picture.white.style
.maxSelectNum(maxSize)// 最大图片选择数量 int
.minSelectNum(1)// 最小选择数量 int
.imageEngine(GlideEngine.createGlideEngine())
.imageSpanCount(3)// 每行显示个数 int
.isCamera(true)// 是否显示拍照按钮 true or false
.isZoomAnim(true)// 图片列表点击 缩放效果 默认true
//.isEnableCrop(true)// 是否裁剪 true or false
.isCompress(true)// 是否压缩 true or false
.minimumCompressSize(100)// 小于100kb的图片不压缩
.forResult(PictureConfig.CHOOSE_REQUEST);//结果回调onActivityResult code
}
/**
* 加载圆角图片
*
* @param context 上下文
* @param view 图片控件
* @param url 网络图片地址
*/
public static void showGlide(Context context, ImageView view, String url) {
RequestOptions options = new RequestOptions()
.error(R.mipmap.ic_add)
.transform(new GlideRoundTransform(context,5));
Glide.with(context)
.load(url)
.apply(options)
.into(view);
}
/**
* 跳转到查看图片界面
* 上下文、list、点击的位置
*/
public static void startPhotoViewActivity(Activity context, ArrayList mList, int position) {
Intent intent = new Intent(context, PhotoViewActivity.class);
Bundle bundle = new Bundle();
bundle.putStringArrayList("list", mList);
intent.putExtras(bundle);
intent.putExtra("position", position);
context.startActivity(intent);
}
}
⑤、自定义PhotoViewActivity(需要则添加):
public class PhotoViewActivity extends AppCompatActivity {
@BindView(R.id.viewpager)
ViewPager viewpager;
@BindView(R.id.mTvImageCount)
TextView mTvImageCount;
//点击的下标
private int currentPosition;
private List urlLists;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_photo_view);
ButterKnife.bind(this);
initData();
}
private void initData() {
if (null == urlLists) {
urlLists = new ArrayList<>();
}
//获得点击的位置
currentPosition = getIntent().getIntExtra("position", 0);
//图片集合
urlLists = getIntent().getStringArrayListExtra("list");
MyImageAdapter adapter = new MyImageAdapter(this, urlLists);
viewpager.setAdapter(adapter);
viewpager.setCurrentItem(currentPosition);
mTvImageCount.setText(currentPosition + 1 + "/" + urlLists.size());
viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mTvImageCount.setText(position + 1 + "/" + urlLists.size());
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
}
⑥:布局相关文件:
1、activity_main.xml
2、item_gallery.xml
picture_icon_delete_photo为自带删除按钮,如需更换自行修改
3、activity_photo_view.xml
⑦、app\src\main\AndroidManifest.xml添加相应权限
扩展相关
1、图片圆角GlideRoundTransform:
public class GlideRoundTransform extends BitmapTransformation {
private static float radius = 0f;
public GlideRoundTransform(Context context) {
this(context, 4);
}
/**
* 圆角大小
* @param context
* @param dp
*/
public GlideRoundTransform(Context context, int dp) {
//super(context);
this.radius = Resources.getSystem().getDisplayMetrics().density * dp;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap bitmap = TransformationUtils.centerCrop(pool, toTransform, outWidth, outHeight);
return roundCrop(pool, bitmap);
}
private static Bitmap roundCrop(BitmapPool pool, Bitmap source) {
if (source == null) return null;
Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint();
paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
paint.setAntiAlias(true);
RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight());
canvas.drawRoundRect(rectF, radius, radius, paint);
return result;
}
public String getId() {
return getClass().getName() + Math.round(radius);
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
}
}
2、图片选择器加载图片失败GlideEngine:
public class GlideEngine implements ImageEngine {
/**
* 加载图片
*
* @param context
* @param url
* @param imageView
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.into(imageView);
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @param callback 网络图片加载回调监听 {link after version 2.5.1 Please use the #OnImageCompleteCallback#}
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView, OnImageCompleteCallback callback) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget(imageView) {
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
if (callback != null) {
callback.onShowLoading();
}
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
if (callback != null) {
callback.onHideLoading();
}
}
@Override
protected void setResource(@Nullable Bitmap resource) {
if (callback != null) {
callback.onHideLoading();
}
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setPanEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.bitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载网络图片适配长图方案
* # 注意:此方法只有加载网络图片才会回调
*
* @param context
* @param url
* @param imageView
* @param longImageView
* @ 已废弃
*/
@Override
public void loadImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView,
SubsamplingScaleImageView longImageView) {
Glide.with(context)
.asBitmap()
.load(url)
.into(new ImageViewTarget(imageView) {
@Override
protected void setResource(@Nullable Bitmap resource) {
if (resource != null) {
boolean eqLongImage = MediaUtils.isLongImg(resource.getWidth(),
resource.getHeight());
longImageView.setVisibility(eqLongImage ? View.VISIBLE : View.GONE);
imageView.setVisibility(eqLongImage ? View.GONE : View.VISIBLE);
if (eqLongImage) {
// 加载长图
longImageView.setQuickScaleEnabled(true);
longImageView.setZoomEnabled(true);
longImageView.setPanEnabled(true);
longImageView.setDoubleTapZoomDuration(100);
longImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_CROP);
longImageView.setDoubleTapZoomDpi(SubsamplingScaleImageView.ZOOM_FOCUS_CENTER);
longImageView.setImage(ImageSource.bitmap(resource),
new ImageViewState(0, new PointF(0, 0), 0));
} else {
// 普通图片
imageView.setImageBitmap(resource);
}
}
}
});
}
/**
* 加载相册目录
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadFolderImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.asBitmap()
.load(url)
.apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder))
.into(new BitmapImageViewTarget(imageView) {
@Override
protected void setResource(Bitmap resource) {
RoundedBitmapDrawable circularBitmapDrawable =
RoundedBitmapDrawableFactory.
create(context.getResources(), resource);
circularBitmapDrawable.setCornerRadius(8);
imageView.setImageDrawable(circularBitmapDrawable);
}
});
}
/**
* 加载gif
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadAsGifImage(@NonNull Context context, @NonNull String url,
@NonNull ImageView imageView) {
Glide.with(context)
.asGif()
.load(url)
.into(imageView);
}
/**
* 加载图片列表图片
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
@Override
public void loadGridImage(@NonNull Context context, @NonNull String url, @NonNull ImageView imageView) {
Glide.with(context)
.load(url)
.apply(new RequestOptions().placeholder(R.drawable.picture_image_placeholder))
.into(imageView);
}
private GlideEngine() {
}
private static GlideEngine instance;
public static GlideEngine createGlideEngine() {
if (null == instance) {
synchronized (GlideEngine.class) {
if (null == instance) {
instance = new GlideEngine();
}
}
}
return instance;
}
}