您的位置:首页 > 移动开发 > 微信开发

自定义ViewGroup打造微信朋友圈之九宫图效果

2016-05-10 17:36 489 查看
相信很多朋友用过微博和微信,比如在微博的首页里面有个九宫图的功能,请看下方我截的图,正如你看到的那样可以放九张图片,而且这九张图片拼在一起刚好是一个正方形。需要注意的是当四张图片的时候需要上面下面各两张显示出来。关于九宫图的效果还是挺棒的,我想你们应该都用过。



今天我来不是介绍九宫图的,正如本次博客标题所说的那样

我今天想从Android开发的角度来剖析九宫图是怎么实现的。

想要做成这种效果,我想到有两种方法

1.官方提供的GridView控件

2.自定义九宫图控件

先说说第1种: ListView嵌套GridView ,然后在ListView的Adapter中的getView方法里设置

GridView的Adapter,其实就是要更新GridView的内容,这种做法ListView在滑动的时候会出现明显的卡顿。

我之用的是这种方法,发现不满意。原生的轮子满足不了你,咱们自己来造一个满足自己。

再说说第2种是自定义控件,接着想是继承View呢?还是继承ViewGroup呢?继承View你可以把九宫图

想象成一个控件,然后把图片Draw出来。继承ViewGroup的话你可以把九宫图想象成一个容器,里面再包含九个

ImageView控件就行了。说到这里,聪明的你是不是快有思路了。

OK下面开始谈谈怎样通过继承ViewGroup来打造属于自己的九宫图效果。

[java] view
plain copy

print?

/**

* 朋友圈九宫图控件

* @author manymore13

* @blog http://blog.csdn.net/manymore13/
*/

public class MultyPicView extends ViewGroup {

/**

* 单行最多图片数

*/

public final static int LINE_MAX_COUNT = 3;

/**

* 这里是九宫图

*/

public final static int MAX_IMG_COUNT = 9;

/**

* 每行最大图片数

*/

public int mLineMaxCount = LINE_MAX_COUNT;

/**

* 图片地址

*/

private String[] mImgUrls;

/**

* 图片的数量

*/

private int mImgCount;

/**

* 图片之间的间距

*/

private int mPicSpace = 5;

/**

* 子view边长度

*/

private int mChildEdgeSize;

/**

* 子view可见个数

*/

private int mChildVisibleCount;

/**

* 这里是九宫格 所以设置为数值9

*/

private int mMaxChildCount = MAX_IMG_COUNT;

/**

* 是否截断点击事件

*/

private boolean mIntercept = false;

/**

* 服务器上缩略图最大尺寸

*/

private static final int maxPicSize = 250;

/**

* 单张图片宽度

*/

private int mSingleSrcWidth;

/**

* 单张图片高度

*/

private int mSingleSrcHeight;

/**

* 单张图片时控件期望的边的最大的大小

*/

private int mSingleExpectMaxViewSize;

private float mSingleExpectMaxViewRatio = 0.8f;

/**

* 单张图片时图片缩放比例

*/

private float mSingleScaleRatio;

/**

* 各个图片点击事件回调

*/

private ClickCallback mClickCallback;

private DisplayImageOptions mOptions = new DisplayImageOptions.Builder()

.cacheInMemory(true)

.cacheOnDisk(true)

.bitmapConfig(Bitmap.Config.ARGB_8888)

.imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();

private ImageLoader mLoader = ImageLoader.getInstance();

private OnClickListener mClickListener = new OnClickListener() {

@Override

public void onClick(View v) {

if (v instanceof ImageView) {

Integer index = (Integer) v.getTag();

if (mClickCallback != null) {

mClickCallback.callback(index, mImgUrls);

}

}

}

};

public MultyPicView(Context context) {

super(context);

}

public MultyPicView(Context context, AttributeSet attrs) {

super(context, attrs);

TypedArray a = context.getTheme().obtainStyledAttributes(attrs,

R.styleable.multy_pic_view, 0, 0);

int len = a.getIndexCount();

// 获取自定义属性

for (int i = 0; i < len; i++) {

int attr = a.getIndex(i);

if (attr == R.styleable.multy_pic_view_pic_space) {

float mul = a.getFloat(attr, 1.0f);

mPicSpace = DeviceUtils.dp2px(mul, getContext());

}

}

mSingleExpectMaxViewSize = Math.min(DeviceUtils.getScreenHeight((Activity) getContext()),

DeviceUtils.getScreenWidth((Activity) getContext()));

mSingleExpectMaxViewSize = (int) (mSingleExpectMaxViewSize

* mSingleExpectMaxViewRatio);

a.recycle();

}

/**

* 初始化该控件

*

* @param len

*/

public void setMaxChildCount(int len) {

removeAllViews();

mMaxChildCount = len;

for (int i = 0; i < len; i++) {

ImageViewChangeBg iv = new ImageViewChangeBg(getContext());

LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,

LayoutParams.MATCH_PARENT);

iv.setScaleType(ImageView.ScaleType.CENTER_CROP);

iv.setOnClickListener(mClickListener);

iv.setTag(i);

this.addView(iv, params);

}

}

public String[] getImgUrl() {

return mImgUrls;

}

/**

* 单张图时调用 ,注意 这里宽高需要服务器提供

* 因为在你下载图片时是不知道图片大小的

* 所以需要服务器告诉你

*

* @param imgUrl 图片地址

* @param srcWidth 图片宽度

* @param srcHeight 图片高度

*/

public void setSingleImg(String imgUrl, int srcWidth, int srcHeight) {

mLineMaxCount = 1;

int maxSize = Math.max(srcWidth, srcHeight);

mSingleScaleRatio = 1f;

if (maxSize > mSingleExpectMaxViewSize) {

mSingleScaleRatio = maxSize * 1.0f / mSingleExpectMaxViewSize;

}

mSingleSrcWidth = (int) (srcWidth / mSingleScaleRatio);

mSingleSrcHeight = (int) (srcHeight / mSingleScaleRatio);

boolean request = false;

mImgUrls = new String[]{imgUrl};

if (mImgUrls != null && mImgUrls.length == 1) {

request = true;

}

dealWithImgs(mImgUrls);

if (request) {

this.requestLayout();

}

}

/**

* 显示多张图片(两张以上)图片 传图片数组进去

* 你在外部使用时需要把图片传进去

* @param imgs 图片url数组

*/

public void setImgs(String[] imgs) {

mLineMaxCount = LINE_MAX_COUNT;

mSingleSrcHeight = mSingleSrcWidth = 0;

mSingleScaleRatio = 1;

dealWithImgs(imgs);

}

/**

* 设置是否拦截事件

*

* @param intercept true 事件拦截

*/

public void setIntercept(boolean intercept) {

mIntercept = intercept;

}

/**

* 设置图片点击回调

*

* @param callback 事件回调

*/

public void setClickCallback(ClickCallback callback) {

mClickCallback = callback;

}

/**

* 点击图片回调

*/

public interface ClickCallback {

/**

* 回调方法

* @param index 点击的索引

2496b
* @param str 图片地址数组

*/

void callback(int index, String[] str);

}

/**

* 获取图片可能显示多少行数

* @param imgSize 图片个数

* @return 行数

*/

public int getMultyImgLines(int imgSize) {

if (imgSize == 0) {

return 1;

}

return (imgSize + mLineMaxCount - 1) / mLineMaxCount;

}

/**

* 获取当前图片数量

*

* @return 数量

*/

public int getImgCount() {

return mImgCount;

}

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

return mIntercept;

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

int lpadding = getPaddingLeft();

int tpadding = getPaddingTop();

int left = lpadding, top = tpadding;

int childCount = getChildCount();

int visibleChildCount = mChildVisibleCount;

int breakLineC = 0; // 断行

if (visibleChildCount == 4) {

// 当四张图片时 两张时换行

breakLineC = 2;

} else {

// 每行三张图片换行

breakLineC = mLineMaxCount;

}

for (int i = 0; i < childCount; i++) {

View child = getChildAt(i);

if (child.getVisibility() == View.GONE) {

continue;

}

if (visibleChildCount == 1) {

// 单张做特殊显示

if (mLineMaxCount == 1) {

// left = (getMeasuredWidth() - mSingleSrcWidth) / 2;// 居中

left = lpadding; // 居左

}

if (mSingleSrcWidth == 0 || mSingleSrcHeight == 0) {

child.layout(left, top, left + mChildEdgeSize,

top + mChildEdgeSize);

} else {

child.layout(left, top, left + mSingleSrcWidth,

top + mSingleSrcHeight);

}

} else {

child.layout(left, top, left + mChildEdgeSize,

top + mChildEdgeSize);

left += (mPicSpace + mChildEdgeSize);

if ((i + 1) % breakLineC == 0) {

top += mChildEdgeSize + mPicSpace;

left = lpadding;

}

}

}

}

/**

* 确定九宫图控件自身的大小以及内部ImageView的大小

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

if (getChildCount() == 0) {

setMaxChildCount(mMaxChildCount);

}

measureImgWidth(widthMeasureSpec);

mChildVisibleCount = getVisibleChildCount();

int lines = getMultyImgLines(mChildVisibleCount);

int viewHeight = ((lines - 1) * mPicSpace + lines * mChildEdgeSize)

+ getPaddingTop() + getPaddingBottom();

if (mChildVisibleCount == 1) {

viewHeight = mSingleSrcHeight == 0 ? viewHeight : mSingleSrcHeight;

}

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

setMeasuredDimension(widthSize, viewHeight);

int heightSize = mChildEdgeSize;

widthSize = heightSize;

if (mChildVisibleCount == 1 && mSingleSrcWidth != 0) {

widthSize = mSingleSrcWidth;

heightSize = mSingleSrcHeight;

}

measureChildren(widthSize, heightSize);

}

/**

* 计算图片的大小

* @param widthMeasureSpec

*/

protected void measureImgWidth(int widthMeasureSpec) {

if (mChildEdgeSize == 0) {

int measureSize = MeasureSpec.getSize(widthMeasureSpec);

mChildEdgeSize = (measureSize - (LINE_MAX_COUNT - 1) * mPicSpace

- getPaddingLeft() - getPaddingRight()) / LINE_MAX_COUNT;

}

}

/**

* 获取可见图片数量

*/

private int getVisibleChildCount() {

int childCount = getChildCount();

int count = 0;

for (int i = 0; i < childCount; i++) {

if (getChildAt(i).getVisibility() != View.GONE) {

count++;

}

}

return count;

}

/**

* 处理图片

*

* @param imgs 图片地址列表

*/

private void dealWithImgs(String[] imgs) {

if (imgs == null || imgs.length == 0) {

return;

}

mImgUrls = imgs;

mImgCount = imgs.length;

int imgLen = mImgCount;

int maxChildCount = mMaxChildCount;

for (int i = 0; i < maxChildCount; i++) {

final ImageView chileIv = (ImageView) getChildAt(i);

if (i < imgLen) {

chileIv.setVisibility(View.VISIBLE);

String url = imgs[i];

ImageSize imageSize = null;

if (i == 0 && mImgCount == 1 && mSingleSrcWidth != 0

&& mSingleSrcWidth != 0) {

imageSize = new ImageSize(mSingleSrcWidth, mSingleSrcHeight);

} else {

imageSize = new ImageSize(maxPicSize, maxPicSize);

}

loadImg(url, imageSize, chileIv);

} else {

chileIv.setVisibility(View.GONE);

}

}

}

private void loadImg(String url, ImageSize imageSize, final ImageView chileIv) {

mLoader.displayImage(url,chileIv);

}

}

在MultyPicView类中,大家可以看到里面用到ImageLoader框架来管理图片的三缓存,当然你也可以用其他的框架来管理。
构造函数里获取自定义属性值 pic_space,这是图片之间的间距值。浮点型

紧接着调用setMaxChildCount进行九宫图的初始化工作 存放ImageView 早期存储起来,后面用的时候就不需要再创建新的控件对象。

setImgs方法是你在Adapter的getView方法中需要把相应图片的url数组传进去即可 如果你是一张图片的话请调用 setSingleImg方法。

另外onLayout和onMeasure是本次博客的一个关键点。

会自定义控件的同学都知道这两个方法

onLayout是ViewGroup中的方法,它的用处是告诉系统确定View本身的位置,以及确定容器内部View的位置,

而onMeasure是View中的方法测量View本身的大小 如果你的控件是容器的话 那还要测量其内部View的大小。

说了他们的用处后,下面我讲解下该怎么样确认每张图片View的位置以及大小。



我画了一张九宫图控件的简易图,最外层蓝色边框所包含的区域表示的是九宫图控件的区域。

通过这张图 可以很好的算出图片大小以及它的位置

先确定九宫图控件大小 它的宽度由父容器决定,它的父容器给他多宽我就设置它多宽;

宽度知道了后就可以确定其内部图片控件的宽高 由于图片控件是正方形的所以它的边长是 (MultyViewWidth - paddingleft - paddingRight - 2*picSpace) / 3 详情请看measureImgWidth方法 边长知道了求它的位置坐标 这完全是初中数学,我这里就不作详细讲解,你可以参考onlayout方法中的解法。



上面是模拟器截取的动态图(图片做了压缩处理) 看起来有点卡,其实在真机上运行的很流畅。图中黑边是ListView的分割线

好了今天就写这么多,这篇博客你可能几分钟就看完了,台上一分钟,台下十年功,写一篇博客也是一样要花不少时间,整理啊构思啊 写Demo啊。所以如果你读完觉得有那么点用处,那请你帮我点个赞。

转载的请注明出处来自manymore13
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  微信