简介
现在众多App中都会有发表图文的功能,在编辑图文的时候会有多图的展示效果。下面就给大家分享一下最近项目中用到的一款自定义出来的一个库。
思路讲解
该库是在fragment中,使用RecyclerView进行编写。利用RecyclerView的一些特性较好的实现了项目中需求。在此分享给大家,不足之处多多指出。
1.首先介绍一下所用到的几款开源库,非常实用向大家推荐一下:
图片选择库:FinalGallery是一款非常强大的图片选择库,支持多种自定义。
图片加载库:glide支持各种类型,各种路径下图片的查看。
屏幕适配库:AndroidAutoLayout鸿洋大神的一款神作,适配非常给力。
2.该库使用RecyclerView来加载显示图片,适配器代码如下:
public class AddPhotoAdapter extends RecyclerView.Adapter<AddPhotoAdapter.MyViewHolder> {
private List<PhotoInfo> data = new ArrayList<>();
private LayoutInflater mInflater;
public final static int DEFAULT_PIC_ID = 0x2B67;
private int maxNum;
private Context mContext;
private onRecyclerViewItemClickListener mRecyclerViewItemClickListener;
public void setRecyclerViewItemClickListener(onRecyclerViewItemClickListener recyclerViewItemClickListener) {
this.mRecyclerViewItemClickListener = recyclerViewItemClickListener;
}
public AddPhotoAdapter(Context context, int max) {
this.maxNum = max;
mInflater = LayoutInflater.from(context);
mContext = context;
PhotoInfo photo = addDefaultItem();
data.add(photo);
}
@NonNull
public PhotoInfo addDefaultItem() {
PhotoInfo photo = new PhotoInfo();
photo.setPhotoPath(R.drawable.icon_add_photo_new + "");
photo.setPhotoId(DEFAULT_PIC_ID);
return photo;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.fragment_add_photo_item, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
if (isAddIcon(position)) {
holder.im_add_photo_item.setScaleType(ImageView.ScaleType.FIT_XY);
holder.im_add_photo_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int pos = holder.getLayoutPosition();
mRecyclerViewItemClickListener.onImagePickClick(view, pos);
}
});
holder.im_close_photo_item.setVisibility(View.GONE);
} else {
holder.im_add_photo_item.setScaleType(ImageView.ScaleType.CENTER_CROP);
holder.im_add_photo_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
mRecyclerViewItemClickListener.onImagePreviewClick(holder.itemView, pos);
}
});
holder.im_add_photo_item.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
int pos = holder.getLayoutPosition();
mRecyclerViewItemClickListener.onImageLongClick(view, holder);
return true;
}
});
holder.im_close_photo_item.setVisibility(View.VISIBLE);
}
if (getInfo(position).getPhotoPath().equals(R.drawable.icon_add_photo_new + ""))
Glide.with(mContext).load(R.drawable.icon_add_photo_new).into(holder.im_add_photo_item);
else
Glide.with(mContext).load(getInfo(position).getPhotoPath()).into(holder.im_add_photo_item);
holder.im_close_photo_item.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int pos = holder.getLayoutPosition();
if (pos < data.size())
mRecyclerViewItemClickListener.onCloseImageClick(view, pos);
}
});
}
@Override
public int getItemCount() {
return data.size();
}
private PhotoInfo getInfo(int position) {
return data.get(position);
}
private boolean isAddIcon(int position) {
return data.get(position).getPhotoId() == DEFAULT_PIC_ID;
}
public void addData(List<PhotoInfo> list) {
for (PhotoInfo photo : list) {
data.add(data.size() - 1, photo);
notifyItemInserted(data.size() - 2);
}
if (data.size() == maxNum + 1) {
data.remove(data.size() - 1);
notifyItemRemoved(data.size());
}
}
public List<PhotoInfo> getData() {
return data;
}
public List<String> getAllPath() {
List<String> list = new ArrayList<>();
for (PhotoInfo info : data) {
if (info.getPhotoId() != DEFAULT_PIC_ID)
list.add(info.getPhotoPath().trim());
}
return list;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
private ImageView im_add_photo_item;
private ImageView im_close_photo_item;
public MyViewHolder(View itemView) {
super(itemView);
AutoUtils.autoSize(itemView);
im_add_photo_item = (ImageView) itemView.findViewById(R.id.im_add_photo_item);
im_close_photo_item = (ImageView) itemView.findViewById(R.id.im_close_photo_item);
}
}
public void setMaxNum(int maxNum) {
this.maxNum = maxNum;
}
}
RecyclerView中在图片未达所设置最大选择数量时,会一直有一张默认图片供用户点击加载图片。所以需要在构造方法中调用addDefaultItem()方法添加一张默认图片,加入集合data中。
在onBindViewHolder(final MyViewHolder holder, final int position)方法中:通过isAddIcon(int position)方法判断用户当前点击的图片是默认图片或者已选择的图片;
定义接口onRecyclerViewItemClickListener来监控用户对RecyclerView的图片的各种操作:
public interface onRecyclerViewItemClickListener {
void onImagePickClick(View view, int position);
void onImagePreviewClick(View view, int position);
void onImageLongClick(View view, AddPhotoAdapter.MyViewHolder holder);
void onCloseImageClick(View view, int position);
}
需要注意的是在图库中选择好图片往RecyclerView中添加时需要做好默认图片的处理,addData(List list)需要在默认图片前面插入新选择的图片,并且判断所选择图片是否已经达到最大可选数量。注意刷新RecyclerView中相应位置的item。
3.fragment的代码如下:
public class AddPhotoFragment extends Fragment implements onRecyclerViewItemClickListener {
private View view;
private RecyclerView fragment_add_photo;
private AddPhotoAdapter mAddPhotoAdapter;
private int maxNum = 9;
private int REQUEST_CODE_GALLERY = 0x12313;
private ItemTouchHelper itemTouchHelper;
private onPhotoNumChangedListener mNumCountListener;
public void setNumCountListener(onPhotoNumChangedListener numCountListener) {
mNumCountListener = numCountListener;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_add_photo, null);
initView(view);
initEvent();
return view;
}
private void initView(View view) {
fragment_add_photo = (RecyclerView) view.findViewById(R.id.fragment_add_photo);
mAddPhotoAdapter = new AddPhotoAdapter(getActivity(), maxNum);
}
private void initEvent() {
GridLayoutManager layoutManager = new GridLayoutManager(getActivity(), 3);
layoutManager.setAutoMeasureEnabled(true);
fragment_add_photo.setLayoutManager(layoutManager);
fragment_add_photo.setAdapter(mAddPhotoAdapter);
fragment_add_photo.setItemAnimator(new DefaultItemAnimator());
itemTouchHelper = new ItemTouchHelper(new MyItemTouchHelper(mAddPhotoAdapter,maxNum));
itemTouchHelper.attachToRecyclerView(fragment_add_photo);
mAddPhotoAdapter.setRecyclerViewItemClickListener(this);
}
@Override
public void onImagePickClick(View view, int position) {
FunctionConfig config = new FunctionConfig.Builder().setMutiSelectMaxSize(maxNum - mAddPhotoAdapter.getData().size() + 1).build();
GalleryFinal.openGalleryMuti(REQUEST_CODE_GALLERY, config, new GalleryFinal.OnHanlderResultCallback() {
@Override
public void onHanlderSuccess(int reqeustCode, List<PhotoInfo> resultList) {
Log.e("roy", resultList.toString());
mAddPhotoAdapter.addData(resultList);
if (mNumCountListener != null)
mNumCountListener.getSelectedPhotoNum(mAddPhotoAdapter.getAllPath().size());
}
@Override
public void onHanlderFailure(int requestCode, String errorMsg) {
}
});
}
@Override
public void onImagePreviewClick(View view, final int position) {
FunctionConfig config = new FunctionConfig.Builder().setMutiSelectMaxSize(1).build();
GalleryFinal.openGallerySingle(REQUEST_CODE_GALLERY, config, new GalleryFinal.OnHanlderResultCallback() {
@Override
public void onHanlderSuccess(int reqeustCode, List<PhotoInfo> resultList) {
mAddPhotoAdapter.getData().remove(position);
mAddPhotoAdapter.getData().add(position, resultList.get(0));
mAddPhotoAdapter.notifyItemChanged(position);
}
@Override
public void onHanlderFailure(int requestCode, String errorMsg) {
}
});
}
@Override
public void onImageLongClick(View view, AddPhotoAdapter.MyViewHolder holder) {
boolean isContain = false;
if (mAddPhotoAdapter.getData().size() == maxNum) {
for (PhotoInfo info : mAddPhotoAdapter.getData()) {
if (info.getPhotoId() == DEFAULT_PIC_ID) {
isContain = true;
}
}
}
if (mAddPhotoAdapter.getData().size() < maxNum || isContain) {
mAddPhotoAdapter.getData().remove(mAddPhotoAdapter.getData().size() - 1);
mAddPhotoAdapter.notifyItemRemoved(mAddPhotoAdapter.getData().size());
}
itemTouchHelper.startDrag(holder);
}
@Override
public void onCloseImageClick(View view, int position) {
/**
* 该判断条件防止删除过快出现的crash
*/
if (position <= mAddPhotoAdapter.getData().size() - 1) {
mAddPhotoAdapter.getData().remove(position);
mAddPhotoAdapter.notifyItemRemoved(position);
boolean isContain = false;
for (PhotoInfo photo : mAddPhotoAdapter.getData()) {
if (photo.getPhotoId() == DEFAULT_PIC_ID) {
isContain = true;
}
}
if (!isContain) {
PhotoInfo photo = mAddPhotoAdapter.addDefaultItem();
mAddPhotoAdapter.getData().add(photo);
mAddPhotoAdapter.notifyItemInserted(mAddPhotoAdapter.getData().size() - 1);
}
}
if (mNumCountListener != null)
mNumCountListener.getSelectedPhotoNum(mAddPhotoAdapter.getAllPath().size());
}
/**
* 设置可选择的最大照片数量
*
* @param maxNum
*/
public void setMaxNum(int maxNum) {
this.maxNum = maxNum;
mAddPhotoAdapter.setMaxNum(maxNum);
mAddPhotoAdapter.notifyDataSetChanged();
}
/**
* 获取选择照片的本地路径
*
* @return
*/
public List<String> getAllSelectedPath() {
return mAddPhotoAdapter.getAllPath();
}
}
这里需要注意的是layoutManager.setAutoMeasureEnabled(true)属性,该属性是在RecyclerView24以上版本才有的。其作用是为了让RecyclerView自适应高度。
该类中实现了适配器中的回调,在onImagePreviewClick(View view, final int position)和onImagePickClick(View view, int position)回调方法中结合FinalGallery库实现对图库的图片进行选择,拿到返回的路径。
在onCloseImageClick(View view, int position)回调方法实现图片的删除操作时需要加上if (position <= mAddPhotoAdapter.getData().size() - 1) 的判断,否则操作过快适配器刷新未完成会造成崩溃。
通过设置setMaxNum(int maxNum)和调用getAllSelectedPath()方法,我们可以设置选择图片的最大数量和获取到所选择图片的本地路径。
这里定义了接口onPhotoNumChangedListener来监听所选择的图片数量,可以自行选择实现与否。
public interface onPhotoNumChangedListener {
void getSelectedPhotoNum(int count);
}
4.在这里利用RecyclerView的特性实现了选择图片的位置拖拽变换功能,需要继承ItemTouchHelper.Callback类,实现相应的方法:
public class MyItemTouchHelper extends ItemTouchHelper.Callback {
private AddPhotoAdapter mAddPhotoAdapter;
private int maxNum;
public MyItemTouchHelper(AddPhotoAdapter adapter, int count) {
this.mAddPhotoAdapter = adapter;
this.maxNum = count;
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
} else {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
final int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(mAddPhotoAdapter.getData(), i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(mAddPhotoAdapter.getData(), i, i - 1);
}
}
mAddPhotoAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (mAddPhotoAdapter.getData().size() < maxNum) {
PhotoInfo photo = mAddPhotoAdapter.addDefaultItem();
mAddPhotoAdapter.getData().add(photo);
mAddPhotoAdapter.notifyItemInserted(mAddPhotoAdapter.getData().size() - 1);
}
}
@Override
public boolean isLongPressDragEnabled() {
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
}
上一步中实现的适配器中的onImageLongClick(View view, AddPhotoAdapter.MyViewHolder holder)回调方法,我们需要在长按加载到RechclerView的图片时,如果默认图片存在,需要使其隐藏掉。并且调用itemTouchHelper.startDrag(holder);
getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)中:我们需要判断当前的RecyclerView是实现的ListView的功能还是GridView的功能,设置拖动的方向。
在onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)中:我们需要判断图片移动的位置,按照拖拽后的位置重新给集合排序,并且刷新RecyclerView。
isLongPressDragEnabled()中:需设置为return false。
clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)中:在拖拽结束后需要判断当前选择的图片数量是否已经达到设置的上限,若无,需要把默认图片显示出来。
简单的几行代码就能实现这么炫酷的功能,是不是很酷。