“马赛克”(拆分)图片 - Gmail的字母样式

时间:2013-06-26 08:48:18

标签: android split gmail imageview

在新版本的gmail上,有一个很酷的imageView,可以在其中显示多个联系人图片(例如链接here)。

例如,如果有人给我发了一封电子邮件,我只会看到他的图片:

#######
#     #
#  A  #
#     #
#######

如果我回复了他,我可以在旁边看到我的图像,但是我的图像和他的图像都减半并且共享imageView的相同空间(我认为两者都有scaleType作为中心裁剪):< / p>

#######
#  #  #
# A# B#
#  #  #
#######

如果其他人加入了对话,则可能如下所示:

#######
#  # B#
# A####
#  # C#
#######

如果另一个加入,它可能看起来像这样:

#######
# A# C#
#######
# B# D#
#######

我不确定项目的顺序(和规则,所以这里的所有内容都是我的猜测),以及当更多人加入时会发生什么。

重要的是我想知道如何实现这一点。

有没有人知道这方面的解决方案?他们是怎么做到的?使用了哪种观点?

这肯定是一种自定义视图,但最好的方法是什么?这种方式可能效率最高,并且不会占用大量内存......

我甚至可能想要将最终图像舍入,因此处理位图而不是图像视图可能会更好......

我甚至不确定如何称呼这样的观点。我想到了“CollageView”或“MosaicView”。

为了说清楚,我认为应该使用下一个API处理这样的问题:

public static Bitmap createMosaicOfBitmaps(int targetWidth,int targetHeight,ArrayList<Bitmap> imagesToShow)

或者,如果位图可能占用太多内存,我们可以使用类似:

public static Bitmap createMosaicOfBitmaps(int targetWidth,int targetHeight,ArrayList<LazyBitmap> imagesToShow)

/**interface for lazy loading of a bitmap, while downscaling the bitmap to the needed size*/
public interface LazyBitmap{
   public getBitmap(int width,int height);
}

我提出了两个解决方案,每个都有自己的优点和缺点,但我仍然需要对最终结果执行特殊效果(特别是圆角,但也许其他的东西),这是我的东西不知道该怎么做。

任何人都可以帮忙吗?你认为谷歌在他们的应用上使用了什么?


编辑:我想出了一些可能的解决方案,因为每个人都写了这个帖子的答案。我不确定哪个是最好的所以我发布了它们。我想每个人都有自己的优点和缺点。

我目前的解决方案都没有像我提供的那样处理位图,但它们非常直观......

我仍然希望就你应该如何做到这一点提出一些建议。

4 个答案:

答案 0 :(得分:4)

这是我称之为的解决方案:

XML解决方案

它使用XML来设置mosaicView的外观。仍然没有按照我的计划,但它可能会帮助一些需要这样的东西并能够按照他们想要的方式改变它的人。

我添加的是添加自定义分隔符的功能(为此使用来自actionBarSherlock的IcsLinearLayout)。当然,你可以添加你想要的任何东西......

这是代码:

public class MosaicView extends FrameLayout {

    public static final int SHOW_DIVIDER_NONE = 0;
    public static final int SHOW_DIVIDER_OUTER = 0x01;
    public static final int SHOW_DIVIDER_INNER = 0x02;

    private ImageView mTopLeftImageView, mTopRightImageView, mBottomRightImageView, mBottomLeftImageView;
    private IcsLinearLayout mLeftContainer, mRightContainer, mMainContainer;
    private int mShowDivider;
    private Drawable mHorizontalDividerDrawable;
    private Drawable mVerticalDividerDrawable;

    public MosaicView(final Context context) {
        super(context);
        init(context, null, 0);
    }

    public MosaicView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public MosaicView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs, defStyle);
    }

    private void init(final Context context, final AttributeSet attrs, final int defStyle) {
        removeAllViews();
        final LayoutInflater inflater = LayoutInflater.from(context);
        inflater.inflate(R.layout.mosaic_view, this, true);
        mTopLeftImageView = (ImageView) findViewById(R.id.mosaicView__topLeftImageView);
        mTopRightImageView = (ImageView) findViewById(R.id.mosaicView__topRightImageView);
        mBottomLeftImageView = (ImageView) findViewById(R.id.mosaicView__bottomLeftImageView);
        mBottomRightImageView = (ImageView) findViewById(R.id.mosaicView__bottomRightImageView);
        mLeftContainer = (IcsLinearLayout) findViewById(R.id.mosaicView__leftContainer);
        mRightContainer = (IcsLinearLayout) findViewById(R.id.mosaicView__rightContainer);
        mMainContainer = (IcsLinearLayout) findViewById(R.id.mosaicView__mainContainer);
        //
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MosaicView, defStyle, 0);
        final int attributeCount = a.getIndexCount();
        for (int i = 0; i < attributeCount; i++) {
            final int curAttr = a.getIndex(i);
            switch (curAttr) {
            case R.styleable.MosaicView_mosaicVerticalDividerDrawable:
                setVerticalDividerDrawable(a.getDrawable(curAttr));
                break;
            case R.styleable.MosaicView_mosaicHorizontalDividerDrawable:
                setHorizontalDividerDrawable(a.getDrawable(curAttr));
                break;
            case R.styleable.MosaicView_mosaicShowDividers:
                setShowDivider(a.getInt(curAttr, SHOW_DIVIDER_NONE));
                break;
            }
        }
        a.recycle();
        //
        if (!isInEditMode())
            resetAllImageViews();
        else {
            final ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
            for (int i = 0; i < 4; ++i)
                bitmaps.add(BitmapFactory.decodeResource(getResources(), android.R.drawable.sym_def_app_icon));
            setImages(bitmaps);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void setVerticalDividerDrawable(final Drawable drawable) {
        mVerticalDividerDrawable = drawable;
        mMainContainer.setDividerDrawable(drawable);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void setHorizontalDividerDrawable(final Drawable drawable) {
        mHorizontalDividerDrawable = drawable;
        mLeftContainer.setDividerDrawable(drawable);
        mRightContainer.setDividerDrawable(drawable);
    }

    public Drawable getVerticalDividerDrawable() {
        return this.mVerticalDividerDrawable;
    }

    public Drawable getHorizontalDividerDrawable() {
        return this.mHorizontalDividerDrawable;
    }

    public int getShowDivider() {
        return this.mShowDivider;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void setShowDivider(final int dividers) {
        mShowDivider = dividers;
        int containersDividers = IcsLinearLayout.SHOW_DIVIDER_NONE;
        if ((dividers & SHOW_DIVIDER_INNER) != 0)
            containersDividers |= IcsLinearLayout.SHOW_DIVIDER_MIDDLE;
        if ((dividers & SHOW_DIVIDER_OUTER) != 0)
            containersDividers |= IcsLinearLayout.SHOW_DIVIDER_END | IcsLinearLayout.SHOW_DIVIDER_BEGINNING;
        mLeftContainer.setShowDividers(containersDividers);
        mRightContainer.setShowDividers(containersDividers);
        mMainContainer.setShowDividers(containersDividers);
    }

    private void resetAllImageViews() {
        mTopLeftImageView.setImageResource(0);
        mTopRightImageView.setImageResource(0);
        mBottomLeftImageView.setImageResource(0);
        mBottomRightImageView.setImageResource(0);
        mTopLeftImageView.setVisibility(View.GONE);
        mTopRightImageView.setVisibility(View.GONE);
        mBottomLeftImageView.setVisibility(View.GONE);
        mBottomRightImageView.setVisibility(View.GONE);
        mLeftContainer.setVisibility(View.GONE);
        mRightContainer.setVisibility(View.GONE);
    }

    public void setImages(final ArrayList<Bitmap> images) {
        resetAllImageViews();
        if (images == null || images.size() == 0)
            return;
        switch (images.size()) {
        case 1:
            mTopLeftImageView.setImageBitmap(images.get(0));
            mTopLeftImageView.setVisibility(View.VISIBLE);
            mLeftContainer.setVisibility(View.VISIBLE);
            break;
        case 2:
            mTopLeftImageView.setImageBitmap(images.get(0));
            mTopRightImageView.setImageBitmap(images.get(1));
            mTopLeftImageView.setVisibility(View.VISIBLE);
            mTopRightImageView.setVisibility(View.VISIBLE);
            mLeftContainer.setVisibility(View.VISIBLE);
            mRightContainer.setVisibility(View.VISIBLE);
            break;
        case 3:
            mTopLeftImageView.setImageBitmap(images.get(0));
            mTopRightImageView.setImageBitmap(images.get(1));
            mBottomRightImageView.setImageBitmap(images.get(2));
            mBottomRightImageView.setVisibility(View.VISIBLE);
            mTopLeftImageView.setVisibility(View.VISIBLE);
            mTopRightImageView.setVisibility(View.VISIBLE);
            mLeftContainer.setVisibility(View.VISIBLE);
            mRightContainer.setVisibility(View.VISIBLE);
            break;
        default:
            // TODO handle case of more than 4 images
        case 4:
            mTopLeftImageView.setImageBitmap(images.get(0));
            mTopRightImageView.setImageBitmap(images.get(1));
            mBottomRightImageView.setImageBitmap(images.get(2));
            mBottomLeftImageView.setImageBitmap(images.get(3));
            mBottomLeftImageView.setVisibility(View.VISIBLE);
            mBottomRightImageView.setVisibility(View.VISIBLE);
            mTopLeftImageView.setVisibility(View.VISIBLE);
            mTopRightImageView.setVisibility(View.VISIBLE);
            mLeftContainer.setVisibility(View.VISIBLE);
            mRightContainer.setVisibility(View.VISIBLE);
            break;
        }
    }

}

mosaic_view.xml:

<com.actionbarsherlock.internal.widget.IcsLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mosaicView__mainContainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    tools:context=".MainActivity" >

    <com.actionbarsherlock.internal.widget.IcsLinearLayout
        android:id="@+id/mosaicView__leftContainer"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/mosaicView__topLeftImageView"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:scaleType="centerCrop"
            android:src="@android:drawable/sym_def_app_icon" />

        <ImageView
            android:id="@+id/mosaicView__bottomLeftImageView"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:scaleType="centerCrop"
            android:src="@android:drawable/sym_def_app_icon" />
    </com.actionbarsherlock.internal.widget.IcsLinearLayout>

    <com.actionbarsherlock.internal.widget.IcsLinearLayout
        android:id="@+id/mosaicView__rightContainer"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/mosaicView__topRightImageView"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:scaleType="centerCrop"
            android:src="@android:drawable/sym_def_app_icon" />

        <ImageView
            android:id="@+id/mosaicView__bottomRightImageView"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:scaleType="centerCrop"
            android:src="@android:drawable/sym_def_app_icon" />
    </com.actionbarsherlock.internal.widget.IcsLinearLayout>

</com.actionbarsherlock.internal.widget.IcsLinearLayout>

attr.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">

    <declare-styleable name="MosaicView">
        <attr name="mosaicVerticalDividerDrawable" format="reference" />
        <attr name="mosaicHorizontalDividerDrawable" format="reference" />
        <attr name="mosaicShowDividers">
            <flag name="none" value="0x00" />
            <flag name="outer" value="0x01" />
            <flag name="inner" value="0x02" />
        </attr>
    </declare-styleable>

</resources>

答案 1 :(得分:3)

这是我想要致电的解决方案

viewGroup解决方案

遗憾的是,它使用了多个imageView,并且没有最终的位图。

请,如果有人知道显示图像的好方法,请发布。

这是代码:

public class MosaicView extends ViewGroup {

    private ArrayList<Bitmap> mImages;
    private ImageView[] mImageViews;

    public MosaicView(final Context context) {
        super(context);
    }

    public MosaicView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    public MosaicView(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
    }

    public void setImages(final ArrayList<Bitmap> images) {
        this.mImages = images;
        removeAllViews();
        mImageViews = new ImageView[Math.min(4, mImages.size())];
        for (int i = 0; i < mImageViews.length; ++i) {
            ImageView imageView;
            imageView = mImageViews[i] = new ImageView(getContext());
            imageView.setImageBitmap(mImages.get(i));
            imageView.setScaleType(ScaleType.CENTER_CROP);
            addView(mImageViews[i]);
        }
        invalidate();
    }

    @Override
    protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b) {
        if (!changed)
            return;
        final int width = r - l;
        final int height = b - t;
        if (mImageViews != null)
            switch (mImageViews.length) {
            case 0:
                break;
            case 1:
                // all area
                mImageViews[0].layout(0, 0, width, height);
                break;
            case 2:
                // left
                mImageViews[0].layout(0, 0, width / 2, height);
                // right
                mImageViews[1].layout(width / 2, 0, width, height);
                break;
            case 3:
                // left
                mImageViews[0].layout(0, 0, width / 2, height);
                // right top
                mImageViews[1].layout(width / 2, 0, width, height / 2);
                // right bottom
                mImageViews[2].layout(width / 2, height / 2, width, height);
                break;
            default:
                // TODO think what should be done when more than 4 items should be shown
            case 4:
                // left top
                mImageViews[0].layout(0, 0, width / 2, height / 2);
                // right top
                mImageViews[1].layout(width / 2, 0, width, height / 2);
                // right bottom
                mImageViews[2].layout(width / 2, height / 2, width, height);
                // left bottom
                mImageViews[3].layout(0, height / 2, width / 2, height);
                break;
            }
    }

}

答案 2 :(得分:2)

我建议你延长ViewGroup,让你的孩子像你想要的那样安排在街区。通过这样做我实现了类似的东西。您可以指定参数,这些参数将根据每个块中的图像数量确定布局。您的父母将指定您孩子的大小和位置。因此,例如,如果您要在父级中显示2个项目,则父级将看到该项并测量一个子项的块宽度的一半而另一个子项的另一半用于另一个子项,然后父项将定位子项以便它们显示正确。

对于您的孩子,您可以扩展ImageView并使用抽样位图填充它。这将减少内存使用量,您将可以在父级中使用多个图像块。如果您的图片已下载,我建议您创建AsyncTask,为您完成所有工作,然后在完成抽样后更新ImageView Bitmap。在ImageView中使用回收时,您还可以使用此任务将图片加载到ListView。当在父母中执行onMeasure时,您孩子的大小显然将由父母确定。

然后,您可以使用您创建的自定义视图并在ListView中实现它以获得所需的效果

您可以查看thisthisthis以便开始使用

-----编辑-----

Here是我实施的控件的屏幕截图。这不完全相同,但它具有相同的方法和原则。在这个控件中,我的父(全屏)是包含图像的小块,我的孩子是(彩色块)是你的图像。现在,在您的孩子中,您可以做任何事情来达到预期的效果。您可以对每个孩子实施onTouch个事件,为每个孩子添加动画等。如果正确实现父子结构,可能性是无穷无尽的。

这就是我在上面的示例屏幕截图中ViewGroup父母中安排孩子的方式

@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {

    int childCount = getChildCount();
    final int childWidth = _viewWidth;
    final int childHeight = _viewHeight;
    final int hPadding = (int) _paddingW; //set horizontal padding
    final int vPadding = (int) _paddingH; //set vertical padding

    if (childCount > 0) {

        int rowTop = 0;
        int rowBottom = 1;
        int columnCount = 1;
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);

            int childLeft   = (columnCount != 1) ? (hPadding * columnCount) + (childWidth * (columnCount-1)) : hPadding;
            int childRight  = (columnCount != 1) ? (hPadding * columnCount) + childWidth * columnCount : hPadding + childWidth;
            int childTop    = (rowTop == 0) ? vPadding : vPadding + ((childHeight + vPadding) * rowTop);
            int childBottom = (rowBottom == 1) ? vPadding + childHeight : (childHeight + vPadding) * rowBottom;

            child.layout(childLeft, childTop, childRight, childBottom);

            if (columnCount < BLOCK_COUNT) {
                columnCount++;
            } else {
                rowTop++;
                rowBottom++;
                columnCount = 1;
            }
        }
    }

}

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int desiredWidth = 100;
    int desiredHeight = 100;

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;
    int maxHeight = 0;

    if (widthMode == MeasureSpec.EXACTLY) {
        width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
        width = Math.min(desiredWidth, widthSize);
    } else {
        width = desiredWidth;
    }

    if (heightMode == MeasureSpec.EXACTLY) {
        height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
        height = Math.min(desiredHeight, heightSize);
    } else {
        height = desiredHeight;
    }

    setMeasuredItemDimentions(width, height);

    final int childWidth = _viewWidth;
    final int childHeight = _viewHeight;
    final int vPadding = (int) _paddingH; //set vertical padding
    final int count = getChildCount();
    int columnCount = 1;
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() == GONE) {
            continue;
        }

        child.measure(childWidth, childHeight);

        if (columnCount < BLOCK_COUNT) {
            columnCount++;
        } else {
            maxHeight += childHeight + vPadding;
            columnCount = 1;
        }
    }

    if (count % BLOCK_COUNT != 0) maxHeight += childHeight + vPadding;

    maxHeight += vPadding;

    setMeasuredDimension(width, maxHeight);

}

此布局仅显示2列但行数无限,因此它不会像您希望的那样百分之百地工作,但您可以使用类似的方法。

这是我孩子的一个例子

public class Block extends ViewGroup {

    private static final String TAG = Block.class.getSimpleName();

    private String _text;
    private State _state;
    private Context _context;

    private int _viewWidth;
    private int _viewHeight;
    private int _textSize;

    public enum State {
        GOOD, NEAR, PASSED;
    }

    public Block(Context context) {
        super(context);

        _context = context;
        _textSize = 15;
        TextView tx = new TextView(context);
        tx.setTextColor(context.getResources().getColor(R.color.terminal_text_color));
        tx.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        tx.setGravity(Gravity.CENTER);
        tx.setTypeface(null, Typeface.BOLD);
        addView(tx);

        TextView stateText = new TextView(context);
        stateText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        stateText.setTextSize(18);
        stateText.setGravity(Gravity.CENTER);
        stateText.setTextColor(context.getResources().getColor(R.color.terminal_text_color));
        stateText.setGravity(Gravity.CENTER);

        addView(stateText);
    }

    @Override
    public void onLayout(boolean changed, int left, int top, int right, int bottom) {

        int childCount = getChildCount();
        final int childWidth = _viewWidth;
        final int childHeight = _viewHeight;

        if (childCount > 0) {

            TextView child = (TextView) getChildAt(0);
            int padding = (int) (childWidth * 0.05);

            int childLeft   = padding;
            int childRight  = childWidth - padding;
            int childTop    = padding;
            int childBottom = (int) (childHeight * 0.5);

            if (child != null) {
                child.layout(childLeft, childTop, childRight, childBottom);
                child.setText(_text);
                child.setTextSize(_textSize);
            }

            TextView stateText = (TextView) getChildAt(1);

            if (stateText != null) {
                stateText.layout(padding, ((int) (childHeight * 0.75)), childWidth - padding, ((int) (childHeight * 0.95)));

                if (stateText != null)
                    switch (_state) {
                        case GOOD:
                            stateText.setBackgroundColor(_context.getResources().getColor(R.color.google_green));
                            stateText.setText(_context.getResources().getString(R.string.bottom_bar_legend_good));
                            break;
                        case NEAR:
                            stateText.setBackgroundColor(_context.getResources().getColor(R.color.google_yellow));
                            stateText.setText(_context.getResources().getString(R.string.bottom_bar_legend_mild));
                            break;
                        case PASSED:
                            stateText.setBackgroundColor(_context.getResources().getColor(R.color.google_red));
                            stateText.setText(_context.getResources().getString(R.string.bottom_bar_legend_passed));
                            break;
                    }
            }

        }

    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        _viewWidth = widthMeasureSpec;
        _viewHeight = heightMeasureSpec;

        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);

        int padding = (int) (widthMeasureSpec * 0.05);
        TextView child = (TextView) getChildAt(0);
        if (child != null) child.measure(widthMeasureSpec - ((int)(widthMeasureSpec * 0.1)), heightMeasureSpec - ((int)(widthMeasureSpec * 0.5)) - padding);

        TextView childLayout = (TextView) getChildAt(1);
        if (childLayout != null) childLayout.measure(widthMeasureSpec - ((int)(widthMeasureSpec * 0.1)), heightMeasureSpec);

    }
}

我为我的孩子使用ViewGroup,因为我的要求与您的要求不同,但您可以使用简单的ImageView,因为您只想显示操作的位图。您可以使用this方法(正如您在评论中提到的那样)在子项中为您的位图提供圆角。

希望这有帮助

答案 3 :(得分:2)

这是我称之为的解决方案:

imageView解决方案

它从ImageView扩展,并覆盖其onDraw方法。它工作正常,但它有一些缺点,如果有人可以提高我会很高兴:

  1. 它不对位图进行操作。
  2. 我不知道如何对我扩展的imageView执行特殊操作,例如反射,圆角等......
  3. 它不遵循我编写的建议API,以节省内存使用量。
  4. 代码在这里:

    public class MosaicView extends ImageView {
    
        private ArrayList<Bitmap> mImages;
        private ArrayList<Rect> mImagesRects;
        private final Paint mPaint = new Paint();
        private Rect mTopLeftRect, mLeftRect, mWholeRect, mRightRect, mTopRightRect, mBottomLeftRect, mBottomRightRect;
        private boolean mIsDirty = false;
        private final Rect mCenterCropRect = new Rect();
    
        public MosaicView(final Context context) {
            super(context);
        }
    
        public MosaicView(final Context context, final AttributeSet attrs) {
            super(context, attrs);
        }
    
        public MosaicView(final Context context, final AttributeSet attrs, final int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public void setImages(final ArrayList<Bitmap> images) {
            this.mImages = images;
            if (mImages == null)
                mImagesRects = null;
            else {
                mImagesRects = new ArrayList<Rect>(images.size());
                for (final Bitmap bitmap : images)
                    mImagesRects.add(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
            }
            mIsDirty = true;
            invalidate();
        }
    
        @Override
        protected void onDraw(final Canvas canvas) {
            super.onDraw(canvas);
            final int width = getWidth();
            final int height = getHeight();
            if (mIsDirty) {
                mIsDirty = false;
                mTopLeftRect = new Rect(0, 0, width / 2, height / 2);
                mLeftRect = new Rect(0, 0, width / 2, height);
                mWholeRect = new Rect(0, 0, width, height);
                mRightRect = new Rect(width / 2, 0, width, height);
                mTopRightRect = new Rect(width / 2, 0, width, height / 2);
                mBottomLeftRect = new Rect(0, height / 2, width / 2, height);
                mBottomRightRect = new Rect(width / 2, height / 2, width, height);
            }
            if (mImages == null)
                return;
            Bitmap b;
            switch (mImages.size()) {
            case 0:
                break;
            case 1:
                b = mImages.get(0);
                getCenterCropRect(mImagesRects.get(0), mWholeRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mWholeRect, mPaint);
                break;
            case 2:
                b = mImages.get(0);
                getCenterCropRect(mImagesRects.get(0), mLeftRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mLeftRect, mPaint);
                b = mImages.get(1);
                getCenterCropRect(mImagesRects.get(1), mRightRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mRightRect, mPaint);
                break;
            case 3:
                b = mImages.get(0);
                getCenterCropRect(mImagesRects.get(0), mLeftRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mLeftRect, mPaint);
                b = mImages.get(1);
                getCenterCropRect(mImagesRects.get(1), mTopRightRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mTopRightRect, mPaint);
                b = mImages.get(2);
                getCenterCropRect(mImagesRects.get(2), mBottomRightRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mBottomRightRect, mPaint);
                break;
            default:
            case 4:
                b = mImages.get(0);
                getCenterCropRect(mImagesRects.get(0), mTopLeftRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mTopLeftRect, mPaint);
                b = mImages.get(1);
                getCenterCropRect(mImagesRects.get(1), mTopRightRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mTopRightRect, mPaint);
                b = mImages.get(2);
                getCenterCropRect(mImagesRects.get(2), mBottomRightRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mBottomRightRect, mPaint);
                b = mImages.get(3);
                getCenterCropRect(mImagesRects.get(3), mBottomLeftRect, mCenterCropRect);
                canvas.drawBitmap(b, mCenterCropRect, mBottomLeftRect, mPaint);
                break;
    
            }
        }
    
        private void getCenterCropRect(final Rect srcRect, final Rect limitRect, final Rect dstRect) {
            final float scaleX = (float) srcRect.width() / limitRect.width();
            final float scaleY = (float) srcRect.height() / limitRect.height();
            if (scaleX >= scaleY) {
                // image will fit in height, and truncate from the width
                dstRect.top = srcRect.top;
                dstRect.bottom = srcRect.bottom;
                final float newWidth = limitRect.width() * scaleY;
                dstRect.left = (int) (srcRect.width() / 2 - newWidth / 2);
                dstRect.right = (int) (srcRect.width() / 2 + newWidth / 2);
            } else {
                // image will fit in width, and truncate from the height
                dstRect.left = srcRect.left;
                dstRect.right = srcRect.right;
                final float newHeight = limitRect.height() * scaleX;
                dstRect.top = (int) (srcRect.height() / 2 - newHeight / 2);
                dstRect.bottom = (int) (srcRect.height() / 2 + newHeight / 2);
    
            }
        }
    }