/** * Copyright 2016 bingoogolapple * <p/> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.bingoogolapple.photopicker.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import android.support.v4.view.ViewCompat; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import java.util.ArrayList; import cn.bingoogolapple.androidcommon.adapter.BGAOnItemChildClickListener; import cn.bingoogolapple.androidcommon.adapter.BGAOnRVItemClickListener; import cn.bingoogolapple.androidcommon.adapter.BGARecyclerViewAdapter; import cn.bingoogolapple.androidcommon.adapter.BGARecyclerViewHolder; import cn.bingoogolapple.androidcommon.adapter.BGAViewHolderHelper; import cn.bingoogolapple.photopicker.R; import cn.bingoogolapple.photopicker.imageloader.BGAImage; import cn.bingoogolapple.photopicker.util.BGAPhotoPickerUtil; import cn.bingoogolapple.photopicker.util.BGASpaceItemDecoration; import static android.support.v7.widget.helper.ItemTouchHelper.ACTION_STATE_IDLE; /** * 作者:王浩 邮件:bingoogolapple@gmail.com * 创建时间:16/7/8 下午4:51 * 描述:拖拽排序九宫格图片控件 */ public class BGASortableNinePhotoLayout extends RecyclerView implements BGAOnItemChildClickListener, BGAOnRVItemClickListener { private PhotoAdapter mPhotoAdapter; private ItemTouchHelper mItemTouchHelper; private Delegate mDelegate; private GridLayoutManager mGridLayoutManager; private boolean mPlusEnable; private boolean mSortable; private int mDeleteDrawableResId; private boolean mDeleteDrawableOverlapQuarter; private int mPhotoTopRightMargin; private int mMaxItemCount; private int mItemSpanCount; private int mPlusDrawableResId; private int mItemCornerRadius; private int mItemWhiteSpacing; private int mOtherWhiteSpacing; private int mPlaceholderDrawableResId; private boolean mEditable; private int mItemWidth; public BGASortableNinePhotoLayout(Context context) { this(context, null); } public BGASortableNinePhotoLayout(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BGASortableNinePhotoLayout(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initDefaultAttrs(); initCustomAttrs(context, attrs); afterInitDefaultAndCustomAttrs(); } private void initDefaultAttrs() { mPlusEnable = true; mSortable = true; mEditable = true; mDeleteDrawableResId = R.mipmap.bga_pp_ic_delete; mDeleteDrawableOverlapQuarter = false; mMaxItemCount = 9; mItemSpanCount = 3; mItemWidth = 0; mItemCornerRadius = 0; mPlusDrawableResId = R.mipmap.bga_pp_ic_plus; mItemWhiteSpacing = BGAPhotoPickerUtil.dp2px(4); mPlaceholderDrawableResId = R.mipmap.bga_pp_ic_holder_light; mOtherWhiteSpacing = BGAPhotoPickerUtil.dp2px(100); } private void initCustomAttrs(Context context, AttributeSet attrs) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BGASortableNinePhotoLayout); final int N = typedArray.getIndexCount(); for (int i = 0; i < N; i++) { initCustomAttr(typedArray.getIndex(i), typedArray); } typedArray.recycle(); } private void initCustomAttr(int attr, TypedArray typedArray) { if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_plusEnable) { mPlusEnable = typedArray.getBoolean(attr, mPlusEnable); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_sortable) { mSortable = typedArray.getBoolean(attr, mSortable); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_deleteDrawable) { mDeleteDrawableResId = typedArray.getResourceId(attr, mDeleteDrawableResId); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_deleteDrawableOverlapQuarter) { mDeleteDrawableOverlapQuarter = typedArray.getBoolean(attr, mDeleteDrawableOverlapQuarter); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_maxItemCount) { mMaxItemCount = typedArray.getInteger(attr, mMaxItemCount); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_itemSpanCount) { mItemSpanCount = typedArray.getInteger(attr, mItemSpanCount); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_plusDrawable) { mPlusDrawableResId = typedArray.getResourceId(attr, mPlusDrawableResId); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_itemCornerRadius) { mItemCornerRadius = typedArray.getDimensionPixelSize(attr, 0); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_itemWhiteSpacing) { mItemWhiteSpacing = typedArray.getDimensionPixelSize(attr, mItemWhiteSpacing); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_otherWhiteSpacing) { mOtherWhiteSpacing = typedArray.getDimensionPixelOffset(attr, mOtherWhiteSpacing); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_placeholderDrawable) { mPlaceholderDrawableResId = typedArray.getResourceId(attr, mPlaceholderDrawableResId); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_editable) { mEditable = typedArray.getBoolean(attr, mEditable); } else if (attr == R.styleable.BGASortableNinePhotoLayout_bga_snpl_itemWidth) { mItemWidth = typedArray.getDimensionPixelSize(attr, mItemWidth); } } private void afterInitDefaultAndCustomAttrs() { if (mItemWidth == 0) { mItemWidth = (BGAPhotoPickerUtil.getScreenWidth() - mOtherWhiteSpacing) / mItemSpanCount; } else { mItemWidth += mItemWhiteSpacing; } setOverScrollMode(OVER_SCROLL_NEVER); mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelperCallback()); mItemTouchHelper.attachToRecyclerView(this); mGridLayoutManager = new GridLayoutManager(getContext(), mItemSpanCount); setLayoutManager(mGridLayoutManager); addItemDecoration(new BGASpaceItemDecoration(mItemWhiteSpacing / 2)); calculatePhotoTopRightMargin(); mPhotoAdapter = new PhotoAdapter(this); mPhotoAdapter.setOnItemChildClickListener(this); mPhotoAdapter.setOnRVItemClickListener(this); setAdapter(mPhotoAdapter); } /** * 设置是否可拖拽排序,默认值为 true * * @param sortable */ public void setSortable(boolean sortable) { mSortable = sortable; } /** * 获取是否可拖拽排序 * * @return */ public boolean isSortable() { return mSortable; } /** * 设置是否可编辑,默认值为 true * * @param editable */ public void setEditable(boolean editable) { mEditable = editable; mPhotoAdapter.notifyDataSetChanged(); } /** * 获取是否可编辑 * * @return */ public boolean isEditable() { return mEditable; } /** * 设置删除按钮图片资源id,默认值为 * * @param deleteDrawableResId */ public void setDeleteDrawableResId(@DrawableRes int deleteDrawableResId) { mDeleteDrawableResId = deleteDrawableResId; calculatePhotoTopRightMargin(); } /** * 设置删除按钮是否重叠四分之一,默认值为 false * * @param deleteDrawableOverlapQuarter */ public void setDeleteDrawableOverlapQuarter(boolean deleteDrawableOverlapQuarter) { mDeleteDrawableOverlapQuarter = deleteDrawableOverlapQuarter; calculatePhotoTopRightMargin(); } /** * 计算图片右上角 margin */ private void calculatePhotoTopRightMargin() { if (mDeleteDrawableOverlapQuarter) { int deleteDrawableWidth = BitmapFactory.decodeResource(getResources(), mDeleteDrawableResId).getWidth(); int deleteDrawablePadding = getResources().getDimensionPixelOffset(R.dimen.bga_pp_size_delete_padding); mPhotoTopRightMargin = deleteDrawablePadding + deleteDrawableWidth / 2; } else { mPhotoTopRightMargin = 0; } } /** * 设置可选择图片的总张数,默认值为 9 * * @param maxItemCount */ public void setMaxItemCount(int maxItemCount) { mMaxItemCount = maxItemCount; } /** * 获取选择的图片的最大张数 * * @return */ public int getMaxItemCount() { return mMaxItemCount; } /** * 设置列数,默认值为 3 * * @param itemSpanCount */ public void setItemSpanCount(int itemSpanCount) { mItemSpanCount = itemSpanCount; mGridLayoutManager.setSpanCount(mItemSpanCount); } /** * 设置添加按钮图片,默认值为 R.mipmap.bga_pp_ic_plus * * @param plusDrawableResId */ public void setPlusDrawableResId(@DrawableRes int plusDrawableResId) { mPlusDrawableResId = plusDrawableResId; } /** * 设置 Item 条目圆角尺寸,默认值为 0dp * * @param itemCornerRadius */ public void setItemCornerRadius(int itemCornerRadius) { mItemCornerRadius = itemCornerRadius; } /** * 设置图片路径数据集合 * * @param photos */ public void setData(ArrayList<String> photos) { mPhotoAdapter.setData(photos); } /** * 在集合尾部添加更多数据集合 * * @param photos */ public void addMoreData(ArrayList<String> photos) { if (photos != null) { mPhotoAdapter.getData().addAll(photos); mPhotoAdapter.notifyDataSetChanged(); } } /** * 在集合的尾部添加一条数据 * * @param photo */ public void addLastItem(String photo) { if (!TextUtils.isEmpty(photo)) { mPhotoAdapter.getData().add(photo); mPhotoAdapter.notifyDataSetChanged(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int spanCount = mItemSpanCount; int itemCount = mPhotoAdapter.getItemCount(); if (itemCount > 0 && itemCount < mItemSpanCount) { spanCount = itemCount; } mGridLayoutManager.setSpanCount(spanCount); int expectWidth = mItemWidth * spanCount; int expectHeight = 0; if (itemCount > 0) { int rowCount = (itemCount - 1) / spanCount + 1; expectHeight = mItemWidth * rowCount; } int width = resolveSize(expectWidth, widthMeasureSpec); int height = resolveSize(expectHeight, heightMeasureSpec); width = Math.min(width, expectWidth); height = Math.min(height, expectHeight); setMeasuredDimension(width, height); } /** * 获取图片路径数据集合 * * @return */ public ArrayList<String> getData() { return (ArrayList<String>) mPhotoAdapter.getData(); } /** * 删除指定索引位置的图片 * * @param position */ public void removeItem(int position) { mPhotoAdapter.removeItem(position); } /** * 获取图片总数 * * @return */ public int getItemCount() { return mPhotoAdapter.getData().size(); } @Override public void onItemChildClick(ViewGroup parent, View childView, int position) { if (mDelegate != null) { mDelegate.onClickDeleteNinePhotoItem(this, childView, position, mPhotoAdapter.getItem(position), (ArrayList<String>) mPhotoAdapter.getData()); } } @Override public void onRVItemClick(ViewGroup parent, View itemView, int position) { if (mPhotoAdapter.isPlusItem(position)) { if (mDelegate != null) { mDelegate.onClickAddNinePhotoItem(this, itemView, position, (ArrayList<String>) mPhotoAdapter.getData()); } } else { if (mDelegate != null && ViewCompat.getScaleX(itemView) <= 1.0f) { mDelegate.onClickNinePhotoItem(this, itemView, position, mPhotoAdapter.getItem(position), (ArrayList<String>) mPhotoAdapter.getData()); } } } /** * 设置是否显示加号 * * @param plusEnable */ public void setPlusEnable(boolean plusEnable) { mPlusEnable = plusEnable; mPhotoAdapter.notifyDataSetChanged(); } /** * 是否显示加号按钮 * * @return */ public boolean isPlusEnable() { return mPlusEnable; } public void setDelegate(Delegate delegate) { mDelegate = delegate; } private class PhotoAdapter extends BGARecyclerViewAdapter<String> { private int mImageSize; public PhotoAdapter(RecyclerView recyclerView) { super(recyclerView, R.layout.bga_pp_item_nine_photo); mImageSize = BGAPhotoPickerUtil.getScreenWidth() / (mItemSpanCount > 3 ? 8 : 6); } @Override protected void setItemChildListener(BGAViewHolderHelper helper, int viewType) { helper.setItemChildClickListener(R.id.iv_item_nine_photo_flag); } @Override public int getItemCount() { if (mEditable && mPlusEnable && super.getItemCount() < mMaxItemCount) { return super.getItemCount() + 1; } return super.getItemCount(); } @Override public String getItem(int position) { if (isPlusItem(position)) { return null; } return super.getItem(position); } public boolean isPlusItem(int position) { return mEditable && mPlusEnable && super.getItemCount() < mMaxItemCount && position == getItemCount() - 1; } @Override protected void fillData(BGAViewHolderHelper helper, int position, String model) { MarginLayoutParams params = (MarginLayoutParams) helper.getView(R.id.iv_item_nine_photo_photo).getLayoutParams(); params.setMargins(0, mPhotoTopRightMargin, mPhotoTopRightMargin, 0); if (mItemCornerRadius > 0) { BGAImageView imageView = helper.getView(R.id.iv_item_nine_photo_photo); imageView.setCornerRadius(mItemCornerRadius); } if (isPlusItem(position)) { helper.setVisibility(R.id.iv_item_nine_photo_flag, View.GONE); helper.setImageResource(R.id.iv_item_nine_photo_photo, mPlusDrawableResId); } else { if (mEditable) { helper.setVisibility(R.id.iv_item_nine_photo_flag, View.VISIBLE); helper.setImageResource(R.id.iv_item_nine_photo_flag, mDeleteDrawableResId); } else { helper.setVisibility(R.id.iv_item_nine_photo_flag, View.GONE); } BGAImage.display(helper.getImageView(R.id.iv_item_nine_photo_photo), mPlaceholderDrawableResId, model, mImageSize); } } } private class ItemTouchHelperCallback extends ItemTouchHelper.Callback { @Override public boolean isLongPressDragEnabled() { return mEditable && mSortable && mPhotoAdapter.getData().size() > 1; } @Override public boolean isItemViewSwipeEnabled() { return false; } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; if (mPhotoAdapter.isPlusItem(viewHolder.getAdapterPosition())) { dragFlags = ItemTouchHelper.ACTION_STATE_IDLE; } int swipeFlags = ACTION_STATE_IDLE; return makeMovementFlags(dragFlags, swipeFlags); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { if (source.getItemViewType() != target.getItemViewType() || mPhotoAdapter.isPlusItem(target.getAdapterPosition())) { return false; } mPhotoAdapter.moveItem(source.getAdapterPosition(), target.getAdapterPosition()); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { } @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { if (actionState != ACTION_STATE_IDLE) { ViewCompat.setScaleX(viewHolder.itemView, 1.2f); ViewCompat.setScaleY(viewHolder.itemView, 1.2f); ((BGARecyclerViewHolder) viewHolder).getViewHolderHelper().getImageView(R.id.iv_item_nine_photo_photo).setColorFilter(getResources().getColor(R.color.bga_pp_photo_selected_mask)); } super.onSelectedChanged(viewHolder, actionState); } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { ViewCompat.setScaleX(viewHolder.itemView, 1.0f); ViewCompat.setScaleY(viewHolder.itemView, 1.0f); ((BGARecyclerViewHolder) viewHolder).getViewHolderHelper().getImageView(R.id.iv_item_nine_photo_photo).setColorFilter(null); super.clearView(recyclerView, viewHolder); } } public interface Delegate { void onClickAddNinePhotoItem(BGASortableNinePhotoLayout sortableNinePhotoLayout, View view, int position, ArrayList<String> models); void onClickDeleteNinePhotoItem(BGASortableNinePhotoLayout sortableNinePhotoLayout, View view, int position, String model, ArrayList<String> models); void onClickNinePhotoItem(BGASortableNinePhotoLayout sortableNinePhotoLayout, View view, int position, String model, ArrayList<String> models); } }