1. 程式人生 > >Android圓形頭像的繪製(三)之多人頭像的實現

Android圓形頭像的繪製(三)之多人頭像的實現

上篇文章Android圓形影象的繪製(二)介紹了單人圓形頭像的繪製,這篇文章也是圓形頭像的最後一篇。多人頭像存在的場景有很多,像一些社交軟體,只要涉及到群聊的功能,基本上都會存在多人頭像。下面介紹多人頭像是怎樣實現的,最多支援五人。

在進行多人頭像繪製的過程中,CircleImageView自定義控制元件中應該有個列表儲存多人頭像的相關資訊,每個人的資訊應該封裝為一個bean物件,上篇文章介紹圓形頭像涉及到點陣圖和隨機影象的展示,所以我們bean物件包含的資料有點陣圖、隨機背景、文字,程式碼如下:

public class CircleImageViewBean {
    /**
     * 點陣圖
     */
    private Bitmap bitmap;
    /**
     * 隨機背景
     */
    private int randomBg;
    /**
     * 文字資訊
     */
    private String text;

    public CircleImageViewBean(Bitmap bitmap, int randomBg, String text) {
        this.bitmap = bitmap;
        this.randomBg = randomBg;
        this.text = text;
    }

    public int getRandomBg() {
        return randomBg;
    }

    public void setRandomBg(int randomBg) {
        this.randomBg = randomBg;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Bitmap getBitmap() {
        return bitmap;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }
}
在定義好bean物件後,CircleImageView自定義控制元件應該為外面提供一個公用的方法,方法如下:
    /**
     * 設定多人頭像
     * @param circleImageViewBeanList
     */
    public void setImageBitmaps(List<CircleImageViewBean> circleImageViewBeanList) {
        mCircleImageViewBeanList = circleImageViewBeanList;
        invalidate();
    }
接下來看下onDraw方法的實現,程式碼如下:
    private static final int MAX = 5;
    protected void onDraw(Canvas canvas) {
        //一定要註釋掉,否則圓形影象沒法生效
//        super.onDraw(canvas);
        if (mWidth > 0 && mHeight > 0) {
//            Bitmap bitmap = createCircleBitmapForSRC_IN(canvas);
//            if (bitmap != null) {
//                canvas.drawBitmap(bitmap, 0, 0, new Paint());
//            }
            if (mCircleImageViewBeanList != null && mCircleImageViewBeanList.size() > 0) {
                mCount = Math.min(mCircleImageViewBeanList.size(), MAX);
                //繪製多人頭像
                createCircleBitmap(canvas);
            }
        }
    }
在繪製之前,需要獲取當前多人頭像為幾人頭像,最大值為5,狀態用mCount變數儲存。下面計算每個小圓相對於大圓的縮放比例,縮放比例的獲取用到的是基本的三角函式的相關知識,程式碼如下:
    public float getScale(int count) {
        int angle = getAngle(count);
        if (angle == 360) {
            return 1f;
        }
        double cot = getCot(angle);
        float scale = (float) (1f / (Math.sqrt(1 + Math.pow(cot, 2)) + 1));
        return scale;
    }
    private static final int[] sAngleArray = {360, 90, 60, 45, 36};

    private int getAngle(int count) {
        return count > 0 && count <= sAngleArray.length ? sAngleArray[count - 1] : null;
    }
   private double getCot(int angle) {
        double radian = Math.toRadians(angle);
        double sin = Math.sin(radian);
        double cos = Math.cos(radian);
        return  cos / sin;
    }
下面獲取與大圓最頂部相切的小圓的左上角位置,主要是為了方便計算其他小圓的左上角的位置,因為找到這個小圓的對應的位置是在旋轉角度為零的位置,如果想得到其他小圓的位置,只需要根據旋轉角度,運用三角函式知識就可以得到。最上面小圓左上角位置獲取的程式碼如下:
    public float[] getTopPosition(float radius, float scale) {
        float x = radius * (1 - scale);
        float y = 0;
        return new float[] { x, y };
    }

然後,我們獲取每個小圓左上角的位置,這裡需要知道大圓半徑、旋轉角度、縮放比例、最上面小圓左上角的位置,旋轉角度決定的小頭像的擺放位置,它的值定義了一個數據,程式碼如下:

    private static final float[][] sRotationArray = { new float[] { 360 }, new float[] { 45, -135 },
            new float[] { 120, 0, -120 }, new float[] { 45, 135, -135, -45 },
            new float[] { 144, 72, 0, -72, -144 }};
    public float[] getRotation(int count) {
        return count > 0 && count <= sRotationArray.length ? sRotationArray[count - 1] : null;
    }  

小頭像位置計算的程式碼如下:

  public float[] getTranslatePosition(float radius, float rotation, float scale, float topX, float topY) {
        float smallRadius = radius * (1 - scale);
        double radian = Math.toRadians(rotation);
        double cos = Math.cos(radian);
        double sin = Math.sin(radian);
        float x = (float) (topX - smallRadius * sin);
        float y = (float) (topY + smallRadius * (1 - cos));
        return new float[] { x, y };
    }
得到每個小圓左上角的位置後,需要對目標canvas進行相應的平移,為了能準備畫出每個小圓展示在哪個位置,在進行變換之前,需要對目標canvas進行狀態的儲存,防止這裡的變換操作,對其他小圓產生影響,程式碼如下:
    targetCanvas.save();
    float[] radianPosition = mPresenter.getTranslatePosition(viewSize / 2, rotation[i], scale, topPosition[0], topPosition[1]);
    targetCanvas.translate(radianPosition[0], radianPosition[1]);
    targetCanvas.restore();
當canvas移動到具體的位置之後,我們需要獲取點陣圖,將其在canvas上繪製出來。先處理點陣圖相關的繪製邏輯,因為點陣圖的寬高,跟檢視的寬高肯定是不一致的,此時需要對點陣圖進行相應的縮放操作,這裡涉及到兩次縮放的邏輯,第一次是點陣圖縮放到大圓的邏輯,第二次是大圓縮放到小圓的邏輯,程式碼如下:
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();

    if (width > 0 && height > 0) {
        //對點陣圖進行縮放
        float scaleX = (float) mWidth / width;
        float scaleY = (float) mHeight / height;
        Matrix bitmapMatrix = new Matrix();
        bitmapMatrix.postScale(scaleX, scaleY);
        bitmapMatrix.postConcat(matrix);

        newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width,
                height, bitmapMatrix, true);
    }
隨機頭像的繪製邏輯跟上篇文章的邏輯基本上是一樣的,只是需要注意下,繪製的時候需要在小圓的基礎上繪製,需要對字型的大小進行相應的縮放,程式碼如下:
    int size = (int) (viewSize * scale);

    private Bitmap createRandomMaskBitmap(int size, float scale, String text) {
        Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        final Paint paint = new Paint();
        paint.setAntiAlias(true);// 抗鋸齒
        paint.setFilterBitmap(true);
        paint.setColor(mBackground);

        int center = size / 2;//獲取畫布的中心位置

        //建立canvas物件,繪製隨機背景
        Canvas canvas = new Canvas(output);
        canvas.drawCircle(center, center, center, paint);

        //繪製隨機背景上的文字
        setText(canvas, size, paint, scale, text);

        return output;
    }
    private void setText(Canvas canvas, int size, Paint paint, float scale, String text) {
        Rect targetRect = new Rect(0, 0, size, size);
        //設定繪製文字字型的顏色
        paint.setColor(Color.WHITE);
        //設定繪製文字的大小
        paint.setTextSize(mTextSize * scale);
        //獲取文字展示的居中位置
        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(text, targetRect.centerX(), baseline, paint);
    }

到這裡,多人頭像已經基本是實現了,效果圖如下:


單人頭像的展示,跟上篇文章的效果圖也是一樣的,完整的程式碼如下:

CircleImageView.java

package com.dylan.circleimageview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.List;

/**
 * Description
 * author   Dylan.zhuang
 * Date:    16/6/30-下午2:43
 */
public class CircleImageView extends ImageView {
    private static final String TAG = "CircleImageView";

    private static final int MAX = 5;

    private static final float DEFAULT_SCALE = 0.9f;
    /**
     * 繪製圖片的點陣圖
     */
    private Bitmap mBitmap;
    /**
     * 圓形影象邊框寬度
     */
    private int mStrokeWidth;
    /**
     * 圓形影象邊框顏色
     */
    private int mStrokeColor;
    /**
     * 隨機背景文字大小
     */
    private int mTextSize;
    /**
     * 隨機背景顏色
     */
    private int mBackground;
    /**
     * 隨機背景要展示的文字
     */
    private String mText;
    /**
     * 檢視寬度
     */
    private int mWidth;
    /**
     * 檢視高度
     */
    private int mHeight;
    /**
     * 存放多人頭像的列表
     */
    private List<CircleImageViewBean> mCircleImageViewBeanList;
    /**
     * 邏輯層
     */
    private CircleImageViewPresenter mPresenter;
    /**
     * 記錄頭像的個數
     */
    private int mCount;

    public CircleImageView(Context context) {
        this(context, null);
    }

    public CircleImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //#5AC3B2
        int defaultColor = getResources().getColor(R.color.colorGreen);
        //14sp
        int defaultTextSize = getResources().getDimensionPixelSize(R.dimen.dimen_default_text_size);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyleAttr, 0);
        mStrokeWidth = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_stroke_width, 0);
        mStrokeColor = typedArray.getColor(R.styleable.CircleImageView_stroke_color, defaultColor);
        mTextSize = typedArray.getDimensionPixelSize(R.styleable.CircleImageView_text_size, defaultTextSize);
        mBackground = typedArray.getColor(R.styleable.CircleImageView_random_backgroud, defaultColor);
        mText = typedArray.getString(R.styleable.CircleImageView_text);
        //一定要記得回收
        typedArray.recycle();

        mPresenter = new CircleImageViewPresenter();
    }

    /**
     * 設定邊緣寬度
     *
     * @param width
     */
    public void setStrokeWidth(int width) {
        mStrokeWidth = width;
    }

    /**
     * 設定邊緣顏色
     *
     * @param color
     */
    public void setStrokeColor(int color) {
        mStrokeColor = color;
    }

    /**
     * 設定文字大小
     *
     * @param textSize
     */
    public void setTextSize(int textSize) {
        mTextSize = textSize;
    }

    /**
     * 設定背景顏色
     *
     * @param background
     */
    public void setBackground(int background) {
        mBackground = background;
    }

    /**
     * 設定文字
     *
     * @param text
     */
    public void setText(String text) {
        mText = text;
    }

    /**
     * 繪製隨機背景
     */
    public void drawRandomBackground() {
        readyInvalidate(mBitmap, mBackground, mText);
    }

    /**
     * 設定多人頭像
     * @param circleImageViewBeanList
     */
    public void setImageBitmaps(List<CircleImageViewBean> circleImageViewBeanList) {
        mCircleImageViewBeanList = circleImageViewBeanList;
        invalidate();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    public void setImageResource(int resId) {
        super.setImageResource(resId);
        mBitmap = getBitmapFromDrawable(getDrawable());
        readyInvalidate(mBitmap, mBackground, mText);
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        mBitmap = getBitmapFromDrawable(drawable);
        readyInvalidate(mBitmap, mBackground, mText);
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        mBitmap = bm;
        readyInvalidate(mBitmap, mBackground, mText);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //一定要註釋掉,否則圓形影象沒法生效
//        super.onDraw(canvas);
        if (mWidth > 0 && mHeight > 0) {
//            Bitmap bitmap = createCircleBitmapForSRC_IN(canvas);
//            if (bitmap != null) {
//                canvas.drawBitmap(bitmap, 0, 0, new Paint());
//            }
            if (mCircleImageViewBeanList != null && mCircleImageViewBeanList.size() > 0) {
                mCount = Math.min(mCircleImageViewBeanList.size(), MAX);
                //繪製多人頭像
                createCircleBitmap(canvas);
            }
        }
    }

    /**
     * 為呼叫onDraw之前準備資料
     * @param bitmap
     * @param randomBg
     * @param text
     */
    private void readyInvalidate(Bitmap bitmap, int randomBg, String text) {
        if (mCircleImageViewBeanList == null) {
            mCircleImageViewBeanList = new ArrayList<CircleImageViewBean>();
        }
        mCircleImageViewBeanList.clear();
        CircleImageViewBean bean = new CircleImageViewBean(bitmap, randomBg, text);
        mCircleImageViewBeanList.add(bean);
        invalidate();
    }

    /**
     * 建立圓形點陣圖
     * @param targetCanvas
     */
    private void createCircleBitmap(Canvas targetCanvas) {
        //初始化畫筆
        Paint paint = new Paint();
        paint.setAntiAlias(true);

        //獲取縮放比例
        float scale = mPresenter.getScale(mCount);
        int viewSize = Math.min(mWidth, mHeight);
        //獲取與大圓最上面相切小圓的左上角位置,為了方便計算每個圓具體展示在哪個位置
        float[] topPosition = mPresenter.getTopPosition(viewSize / 2, scale);
        //獲取旋轉角度
        float[] rotation = mPresenter.getRotation(mCount);
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);

        for (int i = 0; i < mCount; i++) {
            CircleImageViewBean bean = mCircleImageViewBeanList.get(i);
             //對canvas進行狀態儲存,防止被變換引起狀態的改變
            targetCanvas.save();
            float[] radianPosition = mPresenter.getTranslatePosition(viewSize / 2, rotation[i], scale, topPosition[0], topPosition[1]);
            targetCanvas.translate(radianPosition[0], radianPosition[1]);

            Bitmap scaleBitmap = getScaleBitmap(bean, matrix, viewSize, scale);
            if (scaleBitmap == null) {
                return;
            }
            int newSize = Math.min(scaleBitmap.getWidth(), scaleBitmap.getHeight());
            int center = newSize / 2;
            //畫邊緣圈
            boolean isDrawBorder = drawCircleBorder(targetCanvas, center, paint);
            Bitmap circleBitmap = drawCircleBitmap(scaleBitmap, newSize, isDrawBorder);
            paint.setStyle(Paint.Style.FILL);
            targetCanvas.drawBitmap(circleBitmap, 0, 0, paint);
            targetCanvas.restore();
        }
    }

    /**
     * 獲取縮放後的點陣圖
     * @param bean
     * @param matrix
     * @param viewSize
     * @param scale
     * @return
     */
    private Bitmap getScaleBitmap(CircleImageViewBean bean, Matrix matrix, int viewSize, float scale) {
        if (bean == null) {
            return null;
        }
        Bitmap newBitmap = null;
        Bitmap bitmap = bean.getBitmap();
        String text = bean.getText();
        if (bitmap != null) {
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();

            if (width > 0 && height > 0) {
                //對點陣圖進行縮放
                float scaleX = (float) mWidth / width;
                float scaleY = (float) mHeight / height;
                Matrix bitmapMatrix = new Matrix();
                bitmapMatrix.postScale(scaleX, scaleY);
                bitmapMatrix.postConcat(matrix);

                newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width,
                        height, bitmapMatrix, true);
            }
        } else {
            int size = (int) (viewSize * scale);
            newBitmap = createRandomMaskBitmap(size, scale, text);
        }
        return newBitmap;
    }

    /**
     *
     * @param newBitmap
     * @param newSize
     * @param isDrawBorder
     * @return
     */
    private Bitmap drawCircleBitmap(Bitmap newBitmap, int newSize, boolean isDrawBorder) {
        Bitmap bitmap = Bitmap.createBitmap(newSize, newSize, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        Paint paint = new Paint();
        paint.setAntiAlias(true);
        int center = newSize / 2;
        if (isDrawBorder) {
            canvas.scale(DEFAULT_SCALE, DEFAULT_SCALE, center, center);
        }

        //在矩陣中心畫圓,與矩陣的四邊相切
        canvas.drawCircle(center, center, center, paint);
        //設定Xfermode為SRC_IN
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //繪製圖片
        canvas.drawBitmap(newBitmap, 0, 0, paint);

        return bitmap;
    }

    /**
     * 建立圓形影象
     *
     * @param targetCanvas
     * @return
     */
    private Bitmap createCircleBitmapForSRC_IN(Canvas targetCanvas) {
        //建立一個和圖片大小差不多的正方形矩陣
        int size = Math.min(mWidth, mHeight);

        Bitmap newBitmap = null;
        if (mBitmap != null) {
            int width = mBitmap.getWidth();
            int height = mBitmap.getHeight();

            // 對bitmap進行縮放,縮放到指定view的大小
            Matrix matrix = new Matrix();
            matrix.postScale((float) mWidth / width, (float) mHeight / height);
            newBitmap = Bitmap.createBitmap(mBitmap, 0, 0, width,
                    height, matrix, true);
        } else {
            newBitmap = createRandomMaskBitmap(size, 1.0f, mText);
        }

        if (newBitmap == null) {
            return null;
        }

        int center = size / 2;
        Paint paint = new Paint();
        boolean isDrawBorder = drawCircleBorder(targetCanvas, center, paint);

        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);

        if (isDrawBorder) {
            paint.setColor(Color.WHITE);
            paint.setStyle(Paint.Style.FILL);
            canvas.scale(DEFAULT_SCALE, DEFAULT_SCALE, center, center);
        }

        //在矩陣中心畫圓,與矩陣的四邊相切
        canvas.drawCircle(center, center, center, paint);
        //設定Xfermode為SRC_IN
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        //繪製圖片
        canvas.drawBitmap(newBitmap, 0, 0, paint);
        return bitmap;
    }

    /**
     * 獲取bitmap
     *
     * @param drawable
     * @return
     */
    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }

        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        try {
            Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (OutOfMemoryError e) {
            return null;
        }
    }

    /**
     * 繪製邊界圓
     *
     * @param canvas
     * @param size
     * @param paint
     * @return
     */
    private boolean drawCircleBorder(Canvas canvas, int size, Paint paint) {
        if (mStrokeWidth > 0) {
            paint.setAntiAlias(true);
            paint.setColor(mStrokeColor);
            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(mStrokeWidth);
            canvas.drawCircle(size, size, size - mStrokeWidth, paint);
            return true;
        }
        return false;
    }

    /**
     * 建立隨機背景
     *
     * @param size
     * @return
     */
    private Bitmap createRandomMaskBitmap(int size, float scale, String text) {
        Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        final Paint paint = new Paint();
        paint.setAntiAlias(true);// 抗鋸齒
        paint.setFilterBitmap(true);
        paint.setColor(mBackground);

        int center = size / 2;//獲取畫布的中心位置

        //建立canvas物件,繪製隨機背景
        Canvas canvas = new Canvas(output);
        canvas.drawCircle(center, center, center, paint);

        //繪製隨機背景上的文字
        setText(canvas, size, paint, scale, text);

        return output;
    }

    /**
     * 繪製文字
     *
     * @param canvas
     * @param size
     * @param paint
     */
    private void setText(Canvas canvas, int size, Paint paint, float scale, String text) {
        Rect targetRect = new Rect(0, 0, size, size);
        //設定繪製文字字型的顏色
        paint.setColor(Color.WHITE);
        //設定繪製文字的大小
        paint.setTextSize(mTextSize * scale);
        //獲取文字展示的居中位置
        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(text, targetRect.centerX(), baseline, paint);
    }
}
CircleImageViewPresenter.java
package com.dylan.circleimageview;

/**
 * Description
 * author   Dylan.zhuang
 * Date:    16/7/7-下午2:51
 */
public class CircleImageViewPresenter {
    /**
     * 每種頭像對應的旋轉角度
     */
    private static final float[][] sRotationArray = { new float[] { 360 }, new float[] { 45, -135 },
            new float[] { 120, 0, -120 }, new float[] { 45, 135, -135, -45 },
            new float[] { 144, 72, 0, -72, -144 }};

    /**
     * 經過小圓圓心的兩條直線ab,和經過大圓圓心的兩條直線cd,a和c垂直,b和d垂直(對兩個圓的條件不成立)
     * 公式為360/(n*2),n代表小圓個數,分割策略如下
     * 基數圓經過每個圓的圓心畫直線,1個圓除外;
     * 偶數圓經過每個圓的圓心畫直線,並在公切線也畫直線;
     * 分割策略提到的直線都經過大圓圓心
     */
    private static final int[] sAngleArray = {360, 90, 60, 45, 36};

    /**
     * 獲取旋轉角度
     * @param count
     * @return
     */
    public float[] getRotation(int count) {
        return count > 0 && count <= sRotationArray.length ? sRotationArray[count - 1] : null;
    }

    /**
     * 獲取縮放比例
     * @param count
     * @return
     */
    public float getScale(int count) {
        int angle = getAngle(count);
        if (angle == 360) {
            return 1f;
        }
        double cot = getCot(angle);
        float scale = (float) (1f / (Math.sqrt(1 + Math.pow(cot, 2)) + 1));
        return scale;
    }

    /**
     * 獲取最上面圓的左上角的值,方便計算每個圓的位置
     * @param radius
     * @param scale
     * @return
     */
    public float[] getTopPosition(float radius, float scale) {
        float x = radius * (1 - scale);
        float y = 0;
        return new float[] { x, y };
    }

    /**
     * 獲取每個小頭像應該平移的距離,找圓心
     * @param radius
     * @param rotation
     * @param scale
     * @param topX
     * @param topY
     * @return
     */
    public float[] getTranslatePosition(float radius, float rotation, float scale, float topX, float topY) {
        float smallRadius = radius * (1 - scale);
        double radian = Math.toRadians(rotation);
        double cos = Math.cos(radian);
        double sin = Math.sin(radian);
        float x = (float) (topX - smallRadius * sin);
        float y = (float) (topY + smallRadius * (1 - cos));
        return new float[] { x, y };
    }

    /**
     * 獲取角度
     * @param count
     * @return
     */
    private int getAngle(int count) {
        return count > 0 && count <= sAngleArray.length ? sAngleArray[count - 1] : null;
    }

    /**
     * 獲取cot值
     * @param angle
     * @return
     */
    private double getCot(int angle) {
        double radian = Math.toRadians(angle);
        double sin = Math.sin(radian);
        double cos = Math.cos(radian);
        return  cos / sin;
    }
}
MainActivity.java
package com.dylan.circleimageview;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CircleImageView circleImageView = (CircleImageView) findViewById(R.id.random_icon);
        circleImageView.drawRandomBackground();

        CircleImageView circleImageView1 = (CircleImageView) findViewById(R.id.random_icon1);
        circleImageView1.drawRandomBackground();

        List<CircleImageViewBean> list2 = new ArrayList<>();
        List<CircleImageViewBean> list3 = new ArrayList<>();
        List<CircleImageViewBean> list4 = new ArrayList<>();
        List<CircleImageViewBean> list5 = new ArrayList<>();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
        CircleImageViewBean bean1 = new CircleImageViewBean(bitmap, -1, "");
        CircleImageViewBean bean2 = new CircleImageViewBean(null, R.color.colorGreen, "A");
        CircleImageViewBean bean3 = new CircleImageViewBean(bitmap, R.color.colorGreen, "A");
        CircleImageViewBean bean4 = new CircleImageViewBean(null, R.color.colorAccent, "B");
        CircleImageViewBean bean5 = new CircleImageViewBean(bitmap, R.color.colorGreen, "A");

        list2.add(bean1);
        list2.add(bean2);
        CircleImageView circleImageView2 = (CircleImageView) findViewById(R.id.icon_two);
        circleImageView2.setImageBitmaps(list2);

        list3.add(bean3);
        list3.addAll(list2);
        CircleImageView circleImageView3 = (CircleImageView) findViewById(R.id.icon_three);
        circleImageView3.setImageBitmaps(list3);

        list4.add(bean4);
        list4.addAll(list3);
        CircleImageView circleImageView4 = (CircleImageView) findViewById(R.id.icon_four);
        circleImageView4.setImageBitmaps(list4);

        list5.add(bean5);
        list5.addAll(list4);
        CircleImageView circleImageView5 = (CircleImageView) findViewById(R.id.icon_five);
        circleImageView5.setImageBitmaps(list5);
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            android:src="@drawable/test"
            circle_icon:stroke_color="#ff0000"
            circle_icon:stroke_width="1dp" />

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:id="@+id/icon_two"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            android:src="@drawable/test"
            circle_icon:stroke_color="#ff0000"
            circle_icon:stroke_width="1dp" />
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:id="@+id/random_icon"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            circle_icon:random_backgroud="@color/colorAccent"
            circle_icon:stroke_color="#000000"
            circle_icon:stroke_width="1dp"
            circle_icon:text="A"
            circle_icon:text_size="25sp" />

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:id="@+id/icon_three"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            circle_icon:random_backgroud="@color/colorAccent"
            circle_icon:stroke_color="#000000"
            circle_icon:stroke_width="1dp"
            circle_icon:text="A"
            circle_icon:text_size="25sp" />
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            android:src="@drawable/test" />

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:id="@+id/icon_four"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            android:src="@drawable/test" />
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:id="@+id/random_icon1"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            circle_icon:random_backgroud="@color/colorPrimary"
            circle_icon:text="B"
            circle_icon:text_size="25sp" />

        <com.dylan.circleimageview.CircleImageView xmlns:circle_icon="http://schemas.android.com/apk/res/com.dylan.circleimageview"
            android:id="@+id/icon_five"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_margin="10dp"
            circle_icon:random_backgroud="@color/colorPrimary"
            circle_icon:text="B"
            circle_icon:text_size="25sp" />
    </LinearLayout>


</LinearLayout>
github地址