自定義仿微信九張圖片選擇展示庫
簡介
現在眾多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)中:在拖拽結束後需要判斷當前選擇的圖片數量是否已經達到設定的上限,若無,需要把預設圖片顯示出來。
簡單的幾行程式碼就能實現這麼炫酷的功能,是不是很酷。