[置頂] 自定義ViewGroup打造微信朋友圈之九宮圖效果


    相信很多朋友用過微博和微信,比如在微博的首頁里面有個九宮圖的功能,請看下方我截的圖,正如你看到的那樣可以放九張圖片,而且這九張圖片拼在一起剛好是一個正方形。需要注意的是當四張圖片的時候需要上面下面各兩張顯示出來。關於九宮圖的效果還是挺棒的,我想你們應該都用過。

                     


今天我來不是介紹九宮圖的,正如本次博客標題所說的那樣

我今天想從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?
  1. /** 
  2.  * 朋友圈九宮圖控件 
  3.  * @author manymore13 
  4.  * @blog http://blog.csdn.net/manymore13/ 
  5.  */  
  6. public class MultyPicView extends ViewGroup {  
  7.   
  8.     /** 
  9.      * 單行最多圖片數 
  10.      */  
  11.     public final static int LINE_MAX_COUNT = 3;  
  12.   
  13.     /** 
  14.      * 這里是九宮圖 
  15.      */  
  16.     public final static int MAX_IMG_COUNT = 9;  
  17.   
  18.     /** 
  19.      * 每行最大圖片數 
  20.      */  
  21.     public int mLineMaxCount = LINE_MAX_COUNT;  
  22.   
  23.     /** 
  24.      * 圖片地址 
  25.      */  
  26.     private String[] mImgUrls;  
  27.   
  28.     /** 
  29.      * 圖片的數量 
  30.      */  
  31.     private int mImgCount;  
  32.   
  33.     /** 
  34.      * 圖片之間的間距 
  35.      */  
  36.     private int mPicSpace = 5;  
  37.   
  38.     /** 
  39.      * 子view邊長度 
  40.      */  
  41.     private int mChildEdgeSize;  
  42.   
  43.     /** 
  44.      * 子view可見個數 
  45.      */  
  46.     private int mChildVisibleCount;  
  47.   
  48.     /** 
  49.      * 這里是九宮格 所以設置為數值9 
  50.      */  
  51.     private int mMaxChildCount = MAX_IMG_COUNT;  
  52.   
  53.     /** 
  54.      * 是否截斷點擊事件 
  55.      */  
  56.     private boolean mIntercept = false;  
  57.   
  58.     /** 
  59.      * 服務器上縮略圖最大尺寸 
  60.      */  
  61.     private static final int maxPicSize = 250;  
  62.   
  63.     /** 
  64.      * 單張圖片寬度 
  65.      */  
  66.     private int mSingleSrcWidth;  
  67.   
  68.     /** 
  69.      * 單張圖片高度 
  70.      */  
  71.     private int mSingleSrcHeight;  
  72.   
  73.     /** 
  74.      * 單張圖片時控件期望的邊的最大的大小 
  75.      */  
  76.     private int mSingleExpectMaxViewSize;  
  77.   
  78.     private float mSingleExpectMaxViewRatio = 0.8f;  
  79.   
  80.     /** 
  81.      * 單張圖片時圖片縮放比例 
  82.      */  
  83.     private float mSingleScaleRatio;  
  84.   
  85.     /** 
  86.      * 各個圖片點擊事件回調 
  87.      */  
  88.     private ClickCallback mClickCallback;  
  89.   
  90.     private DisplayImageOptions mOptions = new DisplayImageOptions.Builder()  
  91.             .cacheInMemory(true)  
  92.             .cacheOnDisk(true)  
  93.             .bitmapConfig(Bitmap.Config.ARGB_8888)  
  94.             .imageScaleType(ImageScaleType.IN_SAMPLE_INT).build();  
  95.   
  96.     private ImageLoader mLoader = ImageLoader.getInstance();  
  97.   
  98.     private OnClickListener mClickListener = new OnClickListener() {  
  99.   
  100.         @Override  
  101.         public void onClick(View v) {  
  102.             if (v instanceof ImageView) {  
  103.                 Integer index = (Integer) v.getTag();  
  104.                 if (mClickCallback != null) {  
  105.                     mClickCallback.callback(index, mImgUrls);  
  106.                 }  
  107.             }  
  108.         }  
  109.     };  
  110.   
  111.     public MultyPicView(Context context) {  
  112.         super(context);  
  113.     }  
  114.   
  115.     public MultyPicView(Context context, AttributeSet attrs) {  
  116.         super(context, attrs);  
  117.         TypedArray a = context.getTheme().obtainStyledAttributes(attrs,  
  118.                 R.styleable.multy_pic_view, 00);  
  119.   
  120.         int len = a.getIndexCount();  
  121.         // 獲取自定義屬性  
  122.         for (int i = 0; i < len; i++) {  
  123.             int attr = a.getIndex(i);  
  124.   
  125.             if (attr == R.styleable.multy_pic_view_pic_space) {  
  126.                 float mul = a.getFloat(attr, 1.0f);  
  127.                 mPicSpace = DeviceUtils.dp2px(mul, getContext());  
  128.             }  
  129.         }  
  130.         mSingleExpectMaxViewSize = Math.min(DeviceUtils.getScreenHeight((Activity) getContext()),  
  131.                 DeviceUtils.getScreenWidth((Activity) getContext()));  
  132.         mSingleExpectMaxViewSize = (int) (mSingleExpectMaxViewSize  
  133.                 * mSingleExpectMaxViewRatio);  
  134.         a.recycle();  
  135.     }  
  136.   
  137.     /** 
  138.      * 初始化該控件 
  139.      * 
  140.      * @param len 
  141.      */  
  142.     public void setMaxChildCount(int len) {  
  143.         removeAllViews();  
  144.         mMaxChildCount = len;  
  145.   
  146.         for (int i = 0; i < len; i++) {  
  147.             ImageViewChangeBg iv = new ImageViewChangeBg(getContext());  
  148.             LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,  
  149.                     LayoutParams.MATCH_PARENT);  
  150.             iv.setScaleType(ImageView.ScaleType.CENTER_CROP);  
  151.             iv.setOnClickListener(mClickListener);  
  152.             iv.setTag(i);  
  153.             this.addView(iv, params);  
  154.         }  
  155.     }  
  156.   
  157.     public String[] getImgUrl() {  
  158.         return mImgUrls;  
  159.     }  
  160.   
  161.     /** 
  162.      * 單張圖時調用 ,注意 這里寬高需要服務器提供 
  163.      * 因為在你下載圖片時是不知道圖片大小的 
  164.      * 所以需要服務器告訴你 
  165.      * 
  166.      * @param imgUrl    圖片地址 
  167.      * @param srcWidth  圖片寬度 
  168.      * @param srcHeight 圖片高度 
  169.      */  
  170.     public void setSingleImg(String imgUrl, int srcWidth, int srcHeight) {  
  171.         mLineMaxCount = 1;  
  172.         int maxSize = Math.max(srcWidth, srcHeight);  
  173.         mSingleScaleRatio = 1f;  
  174.         if (maxSize > mSingleExpectMaxViewSize) {  
  175.             mSingleScaleRatio = maxSize * 1.0f / mSingleExpectMaxViewSize;  
  176.         }  
  177.   
  178.         mSingleSrcWidth = (int) (srcWidth / mSingleScaleRatio);  
  179.         mSingleSrcHeight = (int) (srcHeight / mSingleScaleRatio);  
  180.         boolean request = false;  
  181.         mImgUrls = new String[]{imgUrl};  
  182.         if (mImgUrls != null && mImgUrls.length == 1) {  
  183.             request = true;  
  184.         }  
  185.         dealWithImgs(mImgUrls);  
  186.         if (request) {  
  187.             this.requestLayout();  
  188.         }  
  189.     }  
  190.   
  191.     /** 
  192.      * 顯示多張圖片(兩張以上)圖片 傳圖片數組進去 
  193.      * 你在外部使用時需要把圖片傳進去 
  194.      * @param imgs 圖片url數組 
  195.      */  
  196.     public void setImgs(String[] imgs) {  
  197.         mLineMaxCount = LINE_MAX_COUNT;  
  198.         mSingleSrcHeight = mSingleSrcWidth = 0;  
  199.         mSingleScaleRatio = 1;  
  200.         dealWithImgs(imgs);  
  201.     }  
  202.   
  203.     /** 
  204.      * 設置是否攔截事件 
  205.      * 
  206.      * @param intercept true 事件攔截 
  207.      */  
  208.     public void setIntercept(boolean intercept) {  
  209.         mIntercept = intercept;  
  210.     }  
  211.   
  212.     /** 
  213.      * 設置圖片點擊回調 
  214.      * 
  215.      * @param callback 事件回調 
  216.      */  
  217.     public void setClickCallback(ClickCallback callback) {  
  218.         mClickCallback = callback;  
  219.     }  
  220.   
  221.     /** 
  222.      * 點擊圖片回調 
  223.      */  
  224.     public interface ClickCallback {  
  225.         /** 
  226.          * 回調方法 
  227.          * @param index 點擊的索引 
  228.          * @param str   圖片地址數組 
  229.          */  
  230.         void callback(int index, String[] str);  
  231.     }  
  232.   
  233.     /** 
  234.      * 獲取圖片可能顯示多少行數 
  235.      * @param imgSize 圖片個數 
  236.      * @return 行數 
  237.      */  
  238.     public int getMultyImgLines(int imgSize) {  
  239.         if (imgSize == 0) {  
  240.             return 1;  
  241.         }  
  242.         return (imgSize + mLineMaxCount - 1) / mLineMaxCount;  
  243.     }  
  244.   
  245.     /** 
  246.      * 獲取當前圖片數量 
  247.      * 
  248.      * @return 數量 
  249.      */  
  250.     public int getImgCount() {  
  251.         return mImgCount;  
  252.     }  
  253.   
  254.     @Override  
  255.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  256.         return mIntercept;  
  257.     }  
  258.   
  259.     @Override  
  260.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  261.   
  262.         int lpadding = getPaddingLeft();  
  263.         int tpadding = getPaddingTop();  
  264.         int left = lpadding, top = tpadding;  
  265.         int childCount = getChildCount();  
  266.         int visibleChildCount = mChildVisibleCount;  
  267.         int breakLineC = 0// 斷行  
  268.         if (visibleChildCount == 4) {  
  269.             // 當四張圖片時 兩張時換行  
  270.             breakLineC = 2;  
  271.         } else {  
  272.             // 每行三張圖片換行  
  273.             breakLineC = mLineMaxCount;  
  274.         }  
  275.         for (int i = 0; i < childCount; i++) {  
  276.             View child = getChildAt(i);  
  277.             if (child.getVisibility() == View.GONE) {  
  278.                 continue;  
  279.             }  
  280.             if (visibleChildCount == 1) {  
  281.                 // 單張做特殊顯示  
  282.                 if (mLineMaxCount == 1) {  
  283. //                    left = (getMeasuredWidth() - mSingleSrcWidth) / 2;// 居中  
  284.                     left = lpadding; // 居左  
  285.                 }  
  286.                 if (mSingleSrcWidth == 0 || mSingleSrcHeight == 0) {  
  287.                     child.layout(left, top, left + mChildEdgeSize,  
  288.                             top + mChildEdgeSize);  
  289.                 } else {  
  290.                     child.layout(left, top, left + mSingleSrcWidth,  
  291.                             top + mSingleSrcHeight);  
  292.                 }  
  293.             } else {  
  294.                 child.layout(left, top, left + mChildEdgeSize,  
  295.                         top + mChildEdgeSize);  
  296.                 left += (mPicSpace + mChildEdgeSize);  
  297.                 if ((i + 1) % breakLineC == 0) {  
  298.                     top += mChildEdgeSize + mPicSpace;  
  299.                     left = lpadding;  
  300.                 }  
  301.             }  
  302.         }  
  303.     }  
  304.   
  305.     /** 
  306.      * 確定九宮圖控件自身的大小以及內部ImageView的大小 
  307.      */  
  308.     @Override  
  309.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  310.   
  311.         if (getChildCount() == 0) {  
  312.             setMaxChildCount(mMaxChildCount);  
  313.         }  
  314.         measureImgWidth(widthMeasureSpec);  
  315.         mChildVisibleCount = getVisibleChildCount();  
  316.         int lines = getMultyImgLines(mChildVisibleCount);  
  317.         int viewHeight = ((lines - 1) * mPicSpace + lines * mChildEdgeSize)  
  318.                 + getPaddingTop() + getPaddingBottom();  
  319.         if (mChildVisibleCount == 1) {  
  320.             viewHeight = mSingleSrcHeight == 0 ? viewHeight : mSingleSrcHeight;  
  321.         }  
  322.         int widthSize = MeasureSpec.getSize(widthMeasureSpec);  
  323.         setMeasuredDimension(widthSize, viewHeight);  
  324.   
  325.         int heightSize = mChildEdgeSize;  
  326.         widthSize = heightSize;  
  327.         if (mChildVisibleCount == 1 && mSingleSrcWidth != 0) {  
  328.             widthSize = mSingleSrcWidth;  
  329.             heightSize = mSingleSrcHeight;  
  330.         }  
  331.         measureChildren(widthSize, heightSize);  
  332.     }  
  333.   
  334.     /** 
  335.      * 計算圖片的大小 
  336.      * @param widthMeasureSpec 
  337.      */  
  338.     protected void measureImgWidth(int widthMeasureSpec) {  
  339.         if (mChildEdgeSize == 0) {  
  340.             int measureSize = MeasureSpec.getSize(widthMeasureSpec);  
  341.             mChildEdgeSize = (measureSize - (LINE_MAX_COUNT - 1) * mPicSpace  
  342.                     - getPaddingLeft() - getPaddingRight()) / LINE_MAX_COUNT;  
  343.         }  
  344.     }  
  345.   
  346.     /** 
  347.      * 獲取可見圖片數量 
  348.      */  
  349.     private int getVisibleChildCount() {  
  350.         int childCount = getChildCount();  
  351.         int count = 0;  
  352.         for (int i = 0; i < childCount; i++) {  
  353.             if (getChildAt(i).getVisibility() != View.GONE) {  
  354.                 count++;  
  355.             }  
  356.         }  
  357.         return count;  
  358.     }  
  359.   
  360.     /** 
  361.      * 處理圖片 
  362.      * 
  363.      * @param imgs 圖片地址列表 
  364.      */  
  365.     private void dealWithImgs(String[] imgs) {  
  366.         if (imgs == null || imgs.length == 0) {  
  367.             return;  
  368.         }  
  369.         mImgUrls = imgs;  
  370.         mImgCount = imgs.length;  
  371.         int imgLen = mImgCount;  
  372.         int maxChildCount = mMaxChildCount;  
  373.         for (int i = 0; i < maxChildCount; i++) {  
  374.             final ImageView chileIv = (ImageView) getChildAt(i);  
  375.             if (i < imgLen) {  
  376.                 chileIv.setVisibility(View.VISIBLE);  
  377.                 String url = imgs[i];  
  378.                 ImageSize imageSize = null;  
  379.                 if (i == 0 && mImgCount == 1 && mSingleSrcWidth != 0  
  380.                         && mSingleSrcWidth != 0) {  
  381.                     imageSize = new ImageSize(mSingleSrcWidth, mSingleSrcHeight);  
  382.                 } else {  
  383.                     imageSize = new ImageSize(maxPicSize, maxPicSize);  
  384.                 }  
  385.                 loadImg(url, imageSize, chileIv);  
  386.             } else {  
  387.                 chileIv.setVisibility(View.GONE);  
  388.             }  
  389.         }  
  390.     }  
  391.   
  392.     private void loadImg(String url, ImageSize imageSize, final ImageView chileIv) {  
  393.         mLoader.displayImage(url,chileIv);  
  394.     }  
  395. }  



在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需要立即知道圖片的寬和高, 這樣好立即固定控件的大小,所以需要你傳進去



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com