package com.lwkandroid.widget.ninegridview; import android.content.Context; import android.content.res.TypedArray; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import java.util.ArrayList; import java.util.List; /** * 九宫格控件 * * @author LWK */ public class NineGridView extends ViewGroup { /** * 只有单张图片时,ImageView的宽度 */ private int mSingleImageWidth = 0; /** * 只有单张图片时,ImageView的宽高比 */ private float mSingleImageRatio = 1.0f; /** * 间距大小 */ private int mSpaceSize = 3; /** * 多张图片时,每个ImageView的宽高 */ private int mImageWidth, mImageHeight; /** * 列数 */ private int mColumnCount = 3; /** * 行数 */ private int mRawCount; /** * 图片加载器接口 */ private INineGridImageLoader mImageLoader; /** * 图片数据 */ private List<NineGridBean> mDataList = new ArrayList<>(); /** * “+”号图片 */ private NineGridImageView mImgAddData; /** * 子控件点击监听 */ private onItemClickListener mListener; /** * 当前是否为编辑模式 */ private boolean mIsEditMode; /** * 最大显示图片数量 */ private int mMaxNum = 9; /** * “+”号图片资源id */ private int mIcAddMoreResId = R.drawable.ic_ngv_add_pic; /** * 删除图片的资源id */ private int mIcDelete = R.drawable.ic_ngv_delete; /** * 删除图片的尺寸比例 */ private float mRatioOfDelete = 0.25f; public NineGridView(Context context) { super(context); initParams(context, null); } public NineGridView(Context context, AttributeSet attrs) { super(context, attrs); initParams(context, attrs); } private void initParams(Context context, AttributeSet attrs) { DisplayMetrics dm = context.getResources().getDisplayMetrics(); mSingleImageWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mSingleImageWidth, dm); mSpaceSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mSpaceSize, dm); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NineGridView); if (ta != null) { int count = ta.getIndexCount(); for (int i = 0; i < count; i++) { int index = ta.getIndex(i); if (index == R.styleable.NineGridView_sapce_size) { mSpaceSize = ta.getDimensionPixelSize(index, mSpaceSize); } else if (index == R.styleable.NineGridView_single_image_ratio) { mSingleImageRatio = ta.getFloat(index, mSingleImageRatio); } else if (index == R.styleable.NineGridView_single_image_size) { mSingleImageWidth = ta.getDimensionPixelSize(index, mSingleImageWidth); } else if (index == R.styleable.NineGridView_column_count) { mColumnCount = ta.getInteger(index, mColumnCount); } else if (index == R.styleable.NineGridView_is_edit_mode) { mIsEditMode = ta.getBoolean(index, mIsEditMode); } else if (index == R.styleable.NineGridView_max_num) { mMaxNum = ta.getInteger(index, mMaxNum); } else if (index == R.styleable.NineGridView_icon_addmore) { mIcAddMoreResId = ta.getResourceId(index, mIcAddMoreResId); } else if (index == R.styleable.NineGridView_icon_delete) { mIcDelete = ta.getResourceId(index, mIcDelete); } else if (index == R.styleable.NineGridView_delete_ratio) { mRatioOfDelete = ta.getFloat(index, mRatioOfDelete); } } ta.recycle(); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); //最终整个控件的所需宽高 int requiredWidth, requiredHeight; //获取当前可用最大宽度 int measureWidth = MeasureSpec.getSize(widthMeasureSpec); int totalWidth = measureWidth - getPaddingLeft() - getPaddingRight(); //多张图片时,每个子控件的建议尺寸 int childCount = getChildCount(); int suggestWidth = (totalWidth - (mColumnCount - 1) * mSpaceSize) / mColumnCount; if (canShowAddMore()) { //编辑模式下,每个子控件的尺寸=建议尺寸 mImageWidth = mImageHeight = suggestWidth; if (childCount < mColumnCount) { requiredWidth = mImageWidth * childCount + (childCount - 1) * mSpaceSize + getPaddingLeft() + getPaddingRight(); } else { requiredWidth = mImageWidth * mColumnCount + (mColumnCount - 1) * mSpaceSize + getPaddingLeft() + getPaddingRight(); } requiredHeight = mImageHeight * mRawCount + (mRawCount - 1) * mSpaceSize + getPaddingTop() + getPaddingBottom(); } else { //非编辑模式下,每个控件的尺寸需要根据实际情况决定 if (getDataList().isEmpty()) { //没有数据的时候,每个子控件尺寸=0 mImageWidth = mImageHeight = 0; requiredWidth = getPaddingLeft() + getPaddingRight(); requiredHeight = getPaddingTop() + getPaddingBottom(); } else if (getDataList().size() == 1) { //只有一张图片时 //没有额外设置单张图片尺寸时使用建议尺寸,否则需要取最小值 if (mSingleImageWidth <= 0) { mImageWidth = mImageHeight = suggestWidth; } else { mImageWidth = Math.min(mSingleImageWidth, totalWidth); mImageHeight = (int) (mImageWidth / mSingleImageRatio); } requiredWidth = mImageWidth + getPaddingLeft() + getPaddingRight(); requiredHeight = mImageHeight + getPaddingTop() + getPaddingBottom(); } else { //多张图片时,每个子控件的尺寸=建议尺寸 mImageWidth = mImageHeight = suggestWidth; if (childCount < mColumnCount) { requiredWidth = mImageWidth * childCount + (childCount - 1) * mSpaceSize + getPaddingLeft() + getPaddingRight(); } else { requiredWidth = mImageWidth * mColumnCount + (mColumnCount - 1) * mSpaceSize + getPaddingLeft() + getPaddingRight(); } requiredHeight = mImageHeight * mRawCount + (mRawCount - 1) * mSpaceSize + getPaddingTop() + getPaddingBottom(); } } //设置最终该控件宽高 setMeasuredDimension(requiredWidth, requiredHeight); //设置每个子控件宽高 int childrenCount = getChildCount(); for (int index = 0; index < childrenCount; index++) { View childView = getChildAt(index); int childWidth = mImageWidth; int childHeight = mImageHeight; int childMode = MeasureSpec.EXACTLY; int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, childMode); int childHeightSpec = MeasureSpec.makeMeasureSpec(childHeight, childMode); childView.measure(childWidthSpec, childHeightSpec); } } @Override protected void onLayout(boolean b, int i, int i1, int i2, int i3) { int childCount = getChildCount(); for (int index = 0; index < childCount; index++) { View childrenView = getChildAt(index); int rowNum = index / mColumnCount; int columnNum = index % mColumnCount; int left = (mImageWidth + mSpaceSize) * columnNum + getPaddingLeft(); int top = (mImageHeight + mSpaceSize) * rowNum + getPaddingTop(); int right = left + mImageWidth; int bottom = top + mImageHeight; childrenView.layout(left, top, right, bottom); } } @Override protected void onFinishInflate() { super.onFinishInflate(); setDataList(null); } /** * 设置数据集合 */ public void setDataList(List<NineGridBean> dataList) { mDataList.clear(); //不允许超过最大值 if (dataList != null && !dataList.isEmpty()) { if (dataList.size() <= mMaxNum) { mDataList.addAll(dataList); } else { mDataList.addAll(dataList.subList(0, mMaxNum - 1)); } } clearAllViews(); addChildViews(mDataList); } /** * 添加数据集合 */ public void addDataList(List<NineGridBean> dataList) { if (mDataList.size() >= mMaxNum || dataList == null || dataList.isEmpty()) { return; } int cha = mMaxNum - mDataList.size(); List<NineGridBean> availableList; if (cha >= dataList.size()) { availableList = dataList; } else { availableList = dataList.subList(0, cha - 1); } mDataList.addAll(availableList); addChildViews(availableList); } /** * 设置是否为编辑模式 */ public void setIsEditMode(boolean b) { mIsEditMode = b; int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView instanceof NineGirdImageContainer) { ((NineGirdImageContainer) childView).setIsDeleteMode(b); } } if (canShowAddMore()) { if (mImgAddData == null) { addInAddMoreView(); } } else { removeAddMoreView(); } calRawAndColumn(); requestLayout(); } /** * 设置图片加载器实现类 */ public void setImageLoader(INineGridImageLoader loader) { this.mImageLoader = loader; } /** * 设置列数 */ public void setColumnCount(int columnCount) { this.mColumnCount = columnCount; } /** * 设置删除图片尺寸在父容器内的比例 */ public void setRatioOfDeleteIcon(float ratio) { this.mRatioOfDelete = ratio; } /** * 获取当前数据集合 */ public List<NineGridBean> getDataList() { return mDataList; } /** * 设置“+”号图片的资源id */ public void setIcAddMoreResId(int resId) { this.mIcAddMoreResId = resId; if (mImgAddData != null) { mImgAddData.setImageResource(resId); } } /** * 设置删除图片的资源id */ public void setIcDeleteResId(int resId) { this.mIcDelete = resId; } /** * 获取最大允许显示数量与当前显示数量的差值 */ public int getDiffValue() { return mMaxNum - mDataList.size(); } /** * 设置子控件点击监听 */ public void setOnItemClickListener(onItemClickListener l) { this.mListener = l; } /** * 最社最大显示数量 */ public void setMaxNum(int maxNum) { this.mMaxNum = maxNum; } /** * 设置间距尺寸,单位dp */ public void setSpaceSize(int dpValue) { this.mSpaceSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue , getContext().getResources().getDisplayMetrics()); } /** * 【已废弃】 * 设置只有单张图片时的显示尺寸,单位dp * use {@link #setSingleImageWidth(int dpValue)} */ @Deprecated public void setSingleImageSize(int dpValue) { setSingleImageWidth(dpValue); } /** * 设置只有单张图片时的显示宽度,单位dp */ public void setSingleImageWidth(int dpValue) { this.mSingleImageWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue , getContext().getResources().getDisplayMetrics()); } /** * 设置只有单张图片时的显示宽高比,单位dp */ public void setSingleImageRatio(float ratio) { this.mSingleImageRatio = ratio; } /** * 子控件点击监听 */ public interface onItemClickListener { /** * Callback when click plus button be clicked * * @param dValue the diff value between current data number displayed and maximum number */ void onNineGirdAddMoreClick(int dValue); /** * Callback when image be clicked * * @param position position,started with 0 * @param gridBean data of image be clicked * @param imageContainer image container of image be clicked */ void onNineGirdItemClick(int position, NineGridBean gridBean, NineGirdImageContainer imageContainer); /** * Callback when one image be deleted * * @param position position,started with 0 * @param gridBean data of image be clicked * @param imageContainer image container of image be clicked */ void onNineGirdItemDeleted(int position, NineGridBean gridBean, NineGirdImageContainer imageContainer); } /** * 移除“+”号 */ private void removeAddMoreView() { if (mImgAddData != null) { removeView(mImgAddData); } mImgAddData = null; } /** * 添加“+”号 */ private void addInAddMoreView() { mImgAddData = new NineGridImageView(getContext()); mImgAddData.setImageResource(mIcAddMoreResId); int paddingSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10 , getContext().getResources().getDisplayMetrics()); mImgAddData.setPadding(paddingSize, paddingSize, paddingSize, paddingSize); mImgAddData.setScaleType(ImageView.ScaleType.FIT_XY); mImgAddData.setOnClickListener(view -> { if (mListener != null) { mListener.onNineGirdAddMoreClick(getDiffValue()); } }); addView(mImgAddData); } /** * 计算行数和列数 */ private void calRawAndColumn() { int childCount = getChildCount(); //calculate the raw count if (childCount == 0) { mRawCount = 0; } else if (childCount <= mColumnCount) { mRawCount = 1; } else { mRawCount = childCount % mColumnCount == 0 ? childCount / mColumnCount : childCount / mColumnCount + 1; } } /** * 根据数据集合添加子控件 * * @param dataList 待显示的数据集合 */ private void addChildViews(List<NineGridBean> dataList) { if (canShowAddMore()) { removeAddMoreView(); } if (dataList != null) { for (int i = 0, dataSize = dataList.size(); i < dataSize; i++) { final NineGridBean gridBean = dataList.get(i); final NineGirdImageContainer imageContainer = new NineGirdImageContainer(getContext()); imageContainer.setIsDeleteMode(mIsEditMode); imageContainer.setRatioOfDeleteIcon(mRatioOfDelete); imageContainer.setDeleteIcon(mIcDelete); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { String transitionName = TextUtils.isEmpty(gridBean.getTransitionName()) ? gridBean.getOriginUrl() : gridBean.getTransitionName(); imageContainer.getImageView().setTransitionName(transitionName); } imageContainer.setOnClickDeleteListener(() -> { int position = mDataList.indexOf(gridBean); mDataList.remove(position); removeViewAt(position); setIsEditMode(mIsEditMode); if (mListener != null) { mListener.onNineGirdItemDeleted(position, gridBean, imageContainer); } }); imageContainer.getImageView().setOnClickListener(view -> { if (mListener != null) { mListener.onNineGirdItemClick(mDataList.indexOf(gridBean), gridBean, imageContainer); } }); addView(imageContainer); imageContainer.post(() -> { if (mImageLoader != null) { String url = TextUtils.isEmpty(gridBean.getThumbUrl()) ? gridBean.getOriginUrl() : gridBean.getThumbUrl(); if (imageContainer.getImageWidth() != 0 && imageContainer.getImageWidth() != 0) { mImageLoader.displayNineGridImage(getContext(), url, imageContainer.getImageView() , imageContainer.getImageWidth(), imageContainer.getImageHeight()); } else { mImageLoader.displayNineGridImage(getContext(), url, imageContainer.getImageView()); } } else { Log.w("NineGridView", "Can not display the image of NineGridView, you'd better set a imageloader!!!!"); } }); } } setIsEditMode(mIsEditMode); } /** * 判断当前是否能显示“+”号 */ private boolean canShowAddMore() { return mIsEditMode && mDataList.size() < mMaxNum; } /** * 清除所有子控件 */ private void clearAllViews() { removeAllViews(); mImgAddData = null; } /***************************************************** * State cache ****************************************************************/ @Override protected Parcelable onSaveInstanceState() { SavedViewState ss = new SavedViewState(super.onSaveInstanceState()); ss.singleImageSize = mSingleImageWidth; ss.singleImageRatio = mSingleImageRatio; ss.spaceSize = mSpaceSize; ss.columnCount = mColumnCount; ss.rawCount = mRawCount; ss.maxNum = mMaxNum; ss.isEditMode = mIsEditMode; ss.icAddMoreResId = mIcAddMoreResId; ss.icDeleteResId = mIcDelete; ss.ratioDelete = mRatioOfDelete; ss.dataList = mDataList; return ss; } @Override protected void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedViewState)) { super.onRestoreInstanceState(state); return; } SavedViewState ss = (SavedViewState) state; super.onRestoreInstanceState(ss); mSingleImageWidth = ss.singleImageSize; mSingleImageRatio = ss.singleImageRatio; mSpaceSize = ss.spaceSize; mColumnCount = ss.columnCount; mRawCount = ss.rawCount; mMaxNum = ss.maxNum; mIsEditMode = ss.isEditMode; mIcAddMoreResId = ss.icAddMoreResId; mIcDelete = ss.icDeleteResId; mRatioOfDelete = ss.ratioDelete; setDataList(ss.dataList); } static class SavedViewState extends BaseSavedState { int singleImageSize; float singleImageRatio; int spaceSize; int columnCount; int rawCount; int maxNum; boolean isEditMode; int icAddMoreResId; List<NineGridBean> dataList; int icDeleteResId; float ratioDelete; SavedViewState(Parcelable superState) { super(superState); } private SavedViewState(Parcel source) { super(source); singleImageSize = source.readInt(); singleImageRatio = source.readFloat(); spaceSize = source.readInt(); columnCount = source.readInt(); rawCount = source.readInt(); maxNum = source.readInt(); isEditMode = source.readByte() == (byte) 1; icAddMoreResId = source.readInt(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { dataList = source.readParcelableList(dataList, NineGridView.class.getClassLoader()); } else { dataList = source.readArrayList(NineGridBean.class.getClassLoader()); } icDeleteResId = source.readInt(); ratioDelete = source.readFloat(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(singleImageSize); out.writeFloat(singleImageRatio); out.writeInt(spaceSize); out.writeInt(columnCount); out.writeInt(rawCount); out.writeInt(maxNum); out.writeByte(isEditMode ? (byte) 1 : (byte) 0); out.writeInt(icAddMoreResId); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { out.writeParcelableList(dataList, 0); } else { out.writeList(dataList); } out.writeInt(icDeleteResId); out.writeFloat(ratioDelete); } public static final Parcelable.Creator<SavedViewState> CREATOR = new Creator<SavedViewState>() { @Override public SavedViewState createFromParcel(Parcel source) { return new SavedViewState(source); } @Override public SavedViewState[] newArray(int size) { return new SavedViewState[size]; } }; } }