NineGridLayout
一个仿微信朋友圈和QQ空间的九宫格图片展示自定义控件。
GitHub:https://github.com/HMY314/NineGridLayout
一、介绍
1、当只有1张图时,可以自己定制图片宽高,也可以使用默认九宫格的宽高;
2、当只有4张图时,以2*2的方式显示;
3、除以上两种情况下,都是按照3列方式显示,但这时有一些细节:
a、如果只有9张图,当然是以3*3的方式显示;
b、如果超过9张图,可以设置是否全部显示。
如果设置不完全显示,则按照3*3的方式显示,但是在第9张图上会有一个带“+”号的数字,
代表还有几张没有显示,这里是模仿了QQ空间图片超出9张的显示方式;
如果设置全部显示,理所当然的将所有图片都显示出来。
4、图片被按下时,会有一个变暗的效果,这也是模仿微信朋友圈的效果。
二、使用方法
1、核心类是NineGridLayout,继承自ViewGroup的抽象类,所以我们实际项目使用需要继承它,并要实现3个方法,如下:
public abstract class NineGridLayout extends ViewGroup { //******************************其他代码省略************************** /** * 显示一张图片 * @param imageView * @param url * @param parentWidth 父控件宽度 * @return true 代表按照九宫格默认大小显示,false 代表按照自定义宽高显示 */ protected abstract boolean displayOneImage(RatioImageView imageView, String url, int parentWidth); protected abstract void displayImage(RatioImageView imageView, String url); /** * 点击图片时执行 */ protected abstract void onClickImage(int position, String url, List<String> urlList); }
2、我这里用NineGridTestLayout继承NineGridLayout实现,displayOneImage()与displayImage()中的参数都是显示图片需要的,我这里用的是ImageLoader显示图片,当然你也可以用其他的。
public class NineGridTestLayout extends NineGridLayout { protected static final int MAX_W_H_RATIO = 3; public NineGridTestLayout(Context context) { super(context); } public NineGridTestLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected boolean displayOneImage(final RatioImageView imageView, String url, final int parentWidth) { //这里是只显示一张图片的情况,显示图片的宽高可以根据实际图片大小*定制,parentWidth 为该layout的宽度 ImageLoader.getInstance().displayImage(imageView, url, ImageLoaderUtil.getPhotoImageOption(), new ImageLoadingListener() { @Override public void onLoadingStarted(String imageUri, View view) { } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { } @Override public void onLoadingComplete(String imageUri, View view, Bitmap bitmap) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); int newW; int newH; if (h > w * MAX_W_H_RATIO) {//h:w = 5:3 newW = parentWidth / 2; newH = newW * 5 / 3; } else if (h < w) {//h:w = 2:3 newW = parentWidth * 2 / 3; newH = newW * 2 / 3; } else {//newH:h = newW :w newW = parentWidth / 2; newH = h * newW / w; } setOneImageLayoutParams(imageView, newW, newH); } @Override public void onLoadingCancelled(String imageUri, View view) { } }); return false;// true 代表按照九宫格默认大小显示(此时不要调用setOneImageLayoutParams);false 代表按照自定义宽高显示。 } @Override protected void displayImage(RatioImageView imageView, String url) { ImageLoaderUtil.getImageLoader(mContext).displayImage(url, imageView, ImageLoaderUtil.getPhotoImageOption()); } @Override protected void onClickImage(int i, String url, List<String> urlList) { Toast.makeText(mContext, "点击了图片" + url, Toast.LENGTH_SHORT).show(); } }
3、在xml中实现
<com.hmy.ninegridlayout.view.NineGridTestLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/layout_nine_grid" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:sapcing="4dp" />
app:sapcing是设置九宫格中图片之间的间隔。
public List<String> urlList = new ArrayList<>();//图片url NineGridTestLayout layout = (NineGridTestLayout) view.findViewById(R.id.layout_nine_grid); layout.setIsShowAll(false); //当传入的图片数超过9张时,是否全部显示 layout.setSpacing(5); //动态设置图片之间的间隔 holder.layout.setUrlList(urlList); //最后再设置图片url
三、核心类
NineGridLayout.java
/** * 描述: * 作者:HMY * 时间:2016/5/10 */ public abstract class NineGridLayout extends ViewGroup { private static final float DEFUALT_SPACING = 3f; private static final int MAX_COUNT = 9; protected Context mContext; private float mSpacing = DEFUALT_SPACING; private int mColumns; private int mRows; private int mTotalWidth; private int mSingleWidth; private boolean mIsShowAll = false; private boolean mIsFirst = true; private List<String> mUrlList = new ArrayList<>(); public NineGridLayout(Context context) { super(context); init(context); } public NineGridLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.NineGridLayout); mSpacing = typedArray.getDimension(R.styleable.NineGridLayout_sapcing, DEFUALT_SPACING); typedArray.recycle(); init(context); } private void init(Context context) { mContext = context; if (getListSize(mUrlList) == 0) { setVisibility(GONE); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mTotalWidth = right - left; mSingleWidth = (int) ((mTotalWidth - mSpacing * (3 - 1)) / 3); if (mIsFirst) { notifyDataSetChanged(); mIsFirst = false; } } /** * 设置间隔 * * @param spacing */ public void setSpacing(float spacing) { mSpacing = spacing; } /** * 设置是否显示所有图片(超过最大数时) * * @param isShowAll */ public void setIsShowAll(boolean isShowAll) { mIsShowAll = isShowAll; } public void setUrlList(List<String> urlList) { if (getListSize(urlList) == 0) { setVisibility(GONE); return; } setVisibility(VISIBLE); mUrlList.clear(); mUrlList.addAll(urlList); if (!mIsFirst) { notifyDataSetChanged(); } } public void notifyDataSetChanged() { removeAllViews(); int size = getListSize(mUrlList); if (size > 0) { setVisibility(VISIBLE); } else { setVisibility(GONE); } if (size == 1) { String url = mUrlList.get(0); RatioImageView imageView = createImageView(0, url); //避免在ListView中一张图未加载成功时,布局高度受其他item影响 LayoutParams params = getLayoutParams(); params.height = mSingleWidth; setLayoutParams(params); imageView.layout(0, 0, mSingleWidth, mSingleWidth); boolean isShowDefualt = displayOneImage(imageView, url, mTotalWidth); if (isShowDefualt) { layoutImageView(imageView, 0, url, false); } else { addView(imageView); } return; } generateChildrenLayout(size); layoutParams(); for (int i = 0; i < size; i++) { String url = mUrlList.get(i); RatioImageView imageView; if (!mIsShowAll) { if (i < MAX_COUNT - 1) { imageView = createImageView(i, url); layoutImageView(imageView, i, url, false); } else { //第9张时 if (size <= MAX_COUNT) {//刚好第9张 imageView = createImageView(i, url); layoutImageView(imageView, i, url, false); } else {//超过9张 imageView = createImageView(i, url); layoutImageView(imageView, i, url, true); break; } } } else { imageView = createImageView(i, url); layoutImageView(imageView, i, url, false); } } } private void layoutParams() { int singleHeight = mSingleWidth; //根据子view数量确定高度 LayoutParams params = getLayoutParams(); params.height = (int) (singleHeight * mRows + mSpacing * (mRows - 1)); setLayoutParams(params); } private RatioImageView createImageView(final int i, final String url) { RatioImageView imageView = new RatioImageView(mContext); imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { onClickImage(i, url, mUrlList); } }); return imageView; } /** * @param imageView * @param url * @param showNumFlag 是否在最大值的图片上显示还有未显示的图片张数 */ private void layoutImageView(RatioImageView imageView, int i, String url, boolean showNumFlag) { final int singleWidth = (int) ((mTotalWidth - mSpacing * (3 - 1)) / 3); int singleHeight = singleWidth; int[] position = findPosition(i); int left = (int) ((singleWidth + mSpacing) * position[1]); int top = (int) ((singleHeight + mSpacing) * position[0]); int right = left + singleWidth; int bottom = top + singleHeight; imageView.layout(left, top, right, bottom); addView(imageView); if (showNumFlag) {//添加超过最大显示数量的文本 int overCount = getListSize(mUrlList) - MAX_COUNT; if (overCount > 0) { float textSize = 30; final TextView textView = new TextView(mContext); textView.setText("+" + String.valueOf(overCount)); textView.setTextColor(Color.WHITE); textView.setPadding(0, singleHeight / 2 - getFontHeight(textSize), 0, 0); textView.setTextSize(textSize); textView.setGravity(Gravity.CENTER); textView.setBackgroundColor(Color.BLACK); textView.getBackground().setAlpha(120); textView.layout(left, top, right, bottom); addView(textView); } } displayImage(imageView, url); } private int[] findPosition(int childNum) { int[] position = new int[2]; for (int i = 0; i < mRows; i++) { for (int j = 0; j < mColumns; j++) { if ((i * mColumns + j) == childNum) { position[0] = i;//行 position[1] = j;//列 break; } } } return position; } /** * 根据图片个数确定行列数量 * * @param length */ private void generateChildrenLayout(int length) { if (length <= 3) { mRows = 1; mColumns = length; } else if (length <= 6) { mRows = 2; mColumns = 3; if (length == 4) { mColumns = 2; } } else { mColumns = 3; if (mIsShowAll) { mRows = length / 3; int b = length % 3; if (b > 0) { mRows++; } } else { mRows = 3; } } } protected void setOneImageLayoutParams(RatioImageView imageView, int width, int height) { imageView.setLayoutParams(new LayoutParams(width, height)); imageView.layout(0, 0, width, height); LayoutParams params = getLayoutParams(); // params.width = width; params.height = height; setLayoutParams(params); } private int getListSize(List<String> list) { if (list == null || list.size() == 0) { return 0; } return list.size(); } private int getFontHeight(float fontSize) { Paint paint = new Paint(); paint.setTextSize(fontSize); Paint.FontMetrics fm = paint.getFontMetrics(); return (int) Math.ceil(fm.descent - fm.ascent); } /** * @param imageView * @param url * @param parentWidth 父控件宽度 * @return true 代表按照九宫格默认大小显示,false 代表按照自定义宽高显示 */ protected abstract boolean displayOneImage(RatioImageView imageView, String url, int parentWidth); protected abstract void displayImage(RatioImageView imageView, String url); protected abstract void onClickImage(int position, String url, List<String> urlList); }
RatioImageView.java
该类有两个功能:
1、是用于ImageView被按下时有变暗效果
2、ImageView的宽高根据设置的比例动态适配高度,如在xml中设置 app:ratio="2" ,ImageView的高度根据其宽度改变,但始终是宽的2倍,该功能在该项目中没有使用。
/** * 根据宽高比例自动计算高度ImageView * Created by HMY on 2016/4/21. */ public class RatioImageView extends ImageView { /** * 宽高比例 */ private float mRatio = 0f; public RatioImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public RatioImageView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RatioImageView); mRatio = typedArray.getFloat(R.styleable.RatioImageView_ratio, 0f); typedArray.recycle(); } public RatioImageView(Context context) { super(context); } /** * 设置ImageView的宽高比 * * @param ratio */ public void setRatio(float ratio) { mRatio = ratio; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); if (mRatio != 0) { float height = width / mRatio; heightMeasureSpec = MeasureSpec.makeMeasureSpec((int) height, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Drawable drawable = getDrawable(); if (drawable != null) { drawable.mutate().setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY); } break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: Drawable drawableUp = getDrawable(); if (drawableUp != null) { drawableUp.mutate().clearColorFilter(); } break; } return super.onTouchEvent(event); } }
代码可在我的GitHub上下载,地址:https://github.com/HMY314/NineGridLayout
效果图