1. 程式人生 > >安卓專案實戰之仿微信朋友圈的九宮格自定義控制元件

安卓專案實戰之仿微信朋友圈的九宮格自定義控制元件

效果圖

圖片展示形式

1、當只有1張圖時,可以自己定製圖片寬高,也可以使用預設九宮格的寬高;
2、當只有4張圖時,以2*2的方式顯示;
3、除以上兩種情況下,都是按照3列方式顯示,但這時有一些細節:
    a、如果只有9張圖,當然是以3*3的方式顯示;
    b、如果超過9張圖,可以設定是否全部顯示。
        如果設定不完全顯示,則按照3*3的方式顯示,但是在第9張圖上會有一個帶“+”號的數字,
        代表還有幾張沒有顯示,這裡是模仿了QQ空間圖片超出9張的顯示方式;
        如果設定全部顯示,理所當然的將所有圖片都顯示出來。
4、圖片被按下時,會有一個變暗的效果,這也是模仿微信朋友圈的效果。

專案中使用

111111,自定義控制元件NineGridLayout的建立

該控制元件為自定義控制元件,因此不需要新增什麼依賴或者作為Library引入,只需要按照如下方式定義一個NineGridLayout的抽象類,然後按照專案需求建立該抽象類的子類即可。
1,建立抽象類NineGridLayout,繼承自ViewGroup,程式碼如下:

public abstract class NineGridLayout extends ViewGroup {

    // 預設圖片之間的間隙為3f
    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_spacing, 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() { post(new TimerTask() { @Override public void run() { refresh(); } }); } private void refresh() { 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); }

2,根據寬高自動計算比例的ImageView:

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);
    }
}

3,新增該自定義控制元件用到的兩個自定義屬性,在res/values/attrs.xml中新增如下程式碼:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="RatioImageView">
        <attr name="ratio" format="float"/>
    </declare-styleable>

    <declare-styleable name="NineGridLayout">
        <attr name="spacing" format="dimension"/>
    </declare-styleable>
</resources>

222222,開始使用上面的自定義九宮格控制元件NineGridLayout

1,繼承我們的自定義控制元件抽象類定義對應的子類,實現裡面的三個方法,程式碼如下所示,GitHub官網上給出的例子使用的圖片載入框架是ImageLoader,這裡我採用使用Glide的方式,關於Glide框架監聽在實際專案中的使用,不理解的同