相信很多朋友用過微博和微信,比如在微博的首頁里面有個九宮圖的功能,請看下方我截的圖,正如你看到的那樣可以放九張圖片,而且這九張圖片拼在一起剛好是一個正方形。需要注意的是當四張圖片的時候需要上面下面各兩張顯示出來。關於九宮圖的效果還是挺棒的,我想你們應該都用過。
今天我來不是介紹九宮圖的,正如本次博客標題所說的那樣
我今天想從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?
-
-
-
-
-
- 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;
-
-
-
-
- private int mChildEdgeSize;
-
-
-
-
- private int mChildVisibleCount;
-
-
-
-
- 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();
- }
-
-
-
-
-
-
- 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;
- }
-
-
-
-
-
-
-
-
-
-
- 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();
- }
- }
-
-
-
-
-
-
- public void setImgs(String[] imgs) {
- mLineMaxCount = LINE_MAX_COUNT;
- mSingleSrcHeight = mSingleSrcWidth = 0;
- mSingleScaleRatio = 1;
- dealWithImgs(imgs);
- }
-
-
-
-
-
-
- public void setIntercept(boolean intercept) {
- mIntercept = intercept;
- }
-
-
-
-
-
-
- public void setClickCallback(ClickCallback callback) {
- mClickCallback = callback;
- }
-
-
-
-
- public interface ClickCallback {
-
-
-
-
-
- void callback(int index, String[] str);
- }
-
-
-
-
-
-
- public int getMultyImgLines(int imgSize) {
- if (imgSize == 0) {
- return 1;
- }
- return (imgSize + mLineMaxCount - 1) / mLineMaxCount;
- }
-
-
-
-
-
-
- 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 = 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;
- }
- }
- }
- }
-
-
-
-
- @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);
- }
-
-
-
-
-
- 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;
- }
-
-
-
-
-
-
- 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
Demo下載 Android studio環境
GIT https://github.com/manymore13/MultyPicture
Ps:Demo里面類MultyPicView有點問題 博客上的代碼是最新的,已處理這個問題
這里要感謝下 longsh_在博客下反映的問題
MultyImgBean multyImgBean = mMultyImgBeanList.get(position);
if (multyImgBean.getUrl().length == 1) {
vh.multyPicView.setSingleImg(multyImgBean.getUrl()[0], 600, 600);
} else {
vh.multyPicView.setImgs(multyImgBean.getUrl());
}
單張圖的需要傳入圖片的寬和高。
listview在滑動的時候MultyPicView需要立即知道圖片的寬和高, 這樣好立即固定控件的大小,所以需要你傳進去