Android控件CircleLayout

public class CircleLayout extends ViewGroup {

    public static final int LAYOUT_NORMAL = 1;
    public static final int LAYOUT_PIE = 2;

    private int mLayoutMode = LAYOUT_NORMAL;

    private Drawable mInnerCircle;

    private float mAngleOffset;
    private float mAngleRange;

    private float mDividerWidth;
    private int mInnerRadius;

    private Paint mDividerPaint;
    private Paint mCirclePaint;

    private RectF mBounds = new RectF();

    private Bitmap mDst;
    private Bitmap mSrc;
    private Canvas mSrcCanvas;
    private Canvas mDstCanvas;
    private Xfermode mXfer;
    private Paint mXferPaint;

    private View mMotionTarget;

    private Bitmap mDrawingCache;
    private Canvas mCachedCanvas;
    private Set<View> mDirtyViews = new HashSet<View>();
    private boolean mCached = false;

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

    @SuppressLint("NewApi")
    public CircleLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.CircleLayout, 0, 0);

        try {
            int dividerColor = a.getColor(
                    R.styleable.CircleLayout_sliceDivider,
                    getResources().getColor(android.R.color.darker_gray));
            mInnerCircle = a.getDrawable(R.styleable.CircleLayout_innerCircle);

            if (mInnerCircle instanceof ColorDrawable) {
                int innerColor = a.getColor(
                        R.styleable.CircleLayout_innerCircle,
                        getResources().getColor(android.R.color.white));
                mCirclePaint.setColor(innerColor);
            }

            mDividerPaint.setColor(dividerColor);

            mAngleOffset = a
                    .getFloat(R.styleable.CircleLayout_angleOffset, 90f);
            mAngleRange = a.getFloat(R.styleable.CircleLayout_angleRange, 360f);
            mDividerWidth = a.getDimensionPixelSize(
                    R.styleable.CircleLayout_dividerWidth, 1);
            mInnerRadius = a.getDimensionPixelSize(
                    R.styleable.CircleLayout_innerRadius, 80);

            mLayoutMode = a.getColor(R.styleable.CircleLayout_layoutMode,
                    LAYOUT_NORMAL);
        } finally {
            a.recycle();
        }

        mDividerPaint.setStrokeWidth(mDividerWidth);

        mXfer = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mXferPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // Turn off hardware acceleration if possible
        if (Build.VERSION.SDK_INT >= 11) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
    }

    public void setLayoutMode(int mode) {
        mLayoutMode = mode;
        requestLayout();
        invalidate();
    }

    public int getLayoutMode() {
        return mLayoutMode;
    }

    public int getRadius() {
        final int width = getWidth();
        final int height = getHeight();

        final float minDimen = width > height ? height : width;

        float radius = (minDimen - mInnerRadius) / 2f;

        return (int) radius;
    }

    public void getCenter(PointF p) {
        p.set(getWidth() / 2f, getHeight() / 2);
    }

    public void setAngleOffset(float offset) {
        mAngleOffset = offset;
        requestLayout();
        invalidate();
    }

    public float getAngleOffset() {
        return mAngleOffset;
    }

    public void setInnerRadius(int radius) {
        mInnerRadius = radius;
        requestLayout();
        invalidate();
    }

    public int getInnerRadius() {
        return mInnerRadius;
    }

    public void setInnerCircle(Drawable d) {
        mInnerCircle = d;
        requestLayout();
        invalidate();
    }

    public void setInnerCircle(int res) {
        mInnerCircle = getContext().getResources().getDrawable(res);
        requestLayout();
        invalidate();
    }

    public void setInnerCircleColor(int color) {
        mInnerCircle = new ColorDrawable(color);
        requestLayout();
        invalidate();
    }

    public Drawable getInnerCircle() {
        return mInnerCircle;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int count = getChildCount();

        int maxHeight = 0;
        int maxWidth = 0;

        // Find rightmost and bottommost child
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
                maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
            }
        }

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        int width = resolveSize(maxWidth, widthMeasureSpec);
        int height = resolveSize(maxHeight, heightMeasureSpec);

        setMeasuredDimension(width, height);

        if (mSrc != null
                && (mSrc.getWidth() != width || mSrc.getHeight() != height)) {
            mDst.recycle();
            mSrc.recycle();
            mDrawingCache.recycle();

            mDst = null;
            mSrc = null;
            mDrawingCache = null;
        }

        if (mSrc == null) {
            mSrc = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            mDst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            mDrawingCache = Bitmap.createBitmap(width, height,
                    Bitmap.Config.ARGB_8888);

            mSrcCanvas = new Canvas(mSrc);
            mDstCanvas = new Canvas(mDst);
            mCachedCanvas = new Canvas(mDrawingCache);
        }
    }

    private LayoutParams layoutParams(View child) {
        return (LayoutParams) child.getLayoutParams();
    }

    @Override
    @SuppressWarnings("deprecation")
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int childs = getChildCount();

        float totalWeight = 0f;

        for (int i = 0; i < childs; i++) {
            final View child = getChildAt(i);

            LayoutParams lp = layoutParams(child);

            totalWeight += lp.weight;
        }

        final int width = getWidth();
        final int height = getHeight();

        final float minDimen = width > height ? height : width;
        final float radius = (minDimen - mInnerRadius) / 2f;

        mBounds.set(width / 2 - minDimen / 2, height / 2 - minDimen / 2, width
                / 2 + minDimen / 2, height / 2 + minDimen / 2);

        float startAngle = mAngleOffset;

        for (int i = 0; i < childs; i++) {
            final View child = getChildAt(i);

            final LayoutParams lp = layoutParams(child);

            final float angle = mAngleRange / totalWeight * lp.weight;

            final float centerAngle = startAngle + angle / 2f;
            final int x;
            final int y;

            if (childs > 1) {
                x = (int) (radius * Math.cos(Math.toRadians(centerAngle)))
                        + width / 2;
                y = (int) (radius * Math.sin(Math.toRadians(centerAngle)))
                        + height / 2;
            } else {
                x = width / 2;
                y = height / 2;
            }

            final int halfChildWidth = child.getMeasuredWidth() / 2;
            final int halfChildHeight = child.getMeasuredHeight() / 2;

            final int left = lp.width != LayoutParams.FILL_PARENT ? x
                    - halfChildWidth : 0;
            final int top = lp.height != LayoutParams.FILL_PARENT ? y
                    - halfChildHeight : 0;
            final int right = lp.width != LayoutParams.FILL_PARENT ? x
                    + halfChildWidth : width;
            final int bottom = lp.height != LayoutParams.FILL_PARENT ? y
                    + halfChildHeight : height;

            child.layout(left, top, right, bottom);

            if (left != child.getLeft() || top != child.getTop()
                    || right != child.getRight() || bottom != child.getBottom()
                    || lp.startAngle != startAngle
                    || lp.endAngle != startAngle + angle) {
                mCached = false;
            }

            lp.startAngle = startAngle;

            startAngle += angle;

            lp.endAngle = startAngle;
        }

        invalidate();
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        LayoutParams lp = new LayoutParams(p.width, p.height);

        if (p instanceof LinearLayout.LayoutParams) {
            lp.weight = ((LinearLayout.LayoutParams) p).weight;
        }

        return lp;
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mLayoutMode == LAYOUT_NORMAL) {
            return super.dispatchTouchEvent(ev);
        }

        final int action = ev.getAction();
        final float x = ev.getX() - getWidth() / 2f;
        final float y = ev.getY() - getHeight() / 2f;

        if (action == MotionEvent.ACTION_DOWN) {

            if (mMotionTarget != null) {
                MotionEvent cancelEvent = MotionEvent.obtain(ev);
                cancelEvent.setAction(MotionEvent.ACTION_CANCEL);

                cancelEvent.offsetLocation(-mMotionTarget.getLeft(),
                        -mMotionTarget.getTop());

                mMotionTarget.dispatchTouchEvent(cancelEvent);
                cancelEvent.recycle();

                mMotionTarget = null;
            }

            final float radius = (float) Math.sqrt(x * x + y * y);

            if (radius < mInnerRadius || radius > getWidth() / 2f
                    || radius > getHeight() / 2f) {
                return false;
            }

            float angle = (float) Math.toDegrees(Math.atan2(y, x));

            if (angle < 0)
                angle += mAngleRange;

            final int childs = getChildCount();

            for (int i = 0; i < childs; i++) {
                final View child = getChildAt(i);
                final LayoutParams lp = layoutParams(child);

                float startAngle = lp.startAngle % mAngleRange;
                float endAngle = lp.endAngle % mAngleRange;
                float touchAngle = angle;

                if (startAngle > endAngle) {
                    if (touchAngle < startAngle && touchAngle < endAngle) {
                        touchAngle += mAngleRange;
                    }

                    endAngle += mAngleRange;
                }

                if (startAngle <= touchAngle && endAngle >= touchAngle) {
                    ev.offsetLocation(-child.getLeft(), -child.getTop());

                    boolean dispatched = child.dispatchTouchEvent(ev);

                    if (dispatched) {
                        mMotionTarget = child;

                        return true;
                    } else {
                        ev.setLocation(0f, 0f);

                        return onTouchEvent(ev);
                    }
                }
            }
        } else if (mMotionTarget != null) {
            ev.offsetLocation(-mMotionTarget.getLeft(), -mMotionTarget.getTop());

            mMotionTarget.dispatchTouchEvent(ev);

            if (action == MotionEvent.ACTION_UP
                    || action == MotionEvent.ACTION_CANCEL) {
                mMotionTarget = null;
            }
        }

        return onTouchEvent(ev);
    }

    private void drawChild(Canvas canvas, View child, LayoutParams lp) {
        mSrcCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        mDstCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        mSrcCanvas.save();

        int childLeft = child.getLeft();
        int childTop = child.getTop();
        int childRight = child.getRight();
        int childBottom = child.getBottom();

        mSrcCanvas.clipRect(childLeft, childTop, childRight, childBottom,
                Op.REPLACE);
        mSrcCanvas.translate(childLeft, childTop);

        child.draw(mSrcCanvas);

        mSrcCanvas.restore();

        mXferPaint.setXfermode(null);
        mXferPaint.setColor(Color.BLACK);

        float sweepAngle = (lp.endAngle - lp.startAngle) % 361;

        mDstCanvas
                .drawArc(mBounds, lp.startAngle, sweepAngle, true, mXferPaint);
        mXferPaint.setXfermode(mXfer);
        mDstCanvas.drawBitmap(mSrc, 0f, 0f, mXferPaint);

        canvas.drawBitmap(mDst, 0f, 0f, null);
    }

    private void redrawDirty(Canvas canvas) {
        for (View child : mDirtyViews) {
            drawChild(canvas, child, layoutParams(child));
        }

        if (mMotionTarget != null) {
            drawChild(canvas, mMotionTarget, layoutParams(mMotionTarget));
        }
    }

    private void drawDividers(Canvas canvas, float halfWidth, float halfHeight,
                              float radius) {
        final int childs = getChildCount();

        if (childs < 2) {
            return;
        }

        for (int i = 0; i < childs; i++) {
            final View child = getChildAt(i);
            LayoutParams lp = layoutParams(child);

            canvas.drawLine(halfWidth, halfHeight,
                    radius * (float) Math.cos(Math.toRadians(lp.startAngle))
                            + halfWidth,
                    radius * (float) Math.sin(Math.toRadians(lp.startAngle))
                            + halfHeight, mDividerPaint);

            if (i == childs - 1) {
                canvas.drawLine(halfWidth, halfHeight,
                        radius * (float) Math.cos(Math.toRadians(lp.endAngle))
                                + halfWidth,
                        radius * (float) Math.sin(Math.toRadians(lp.endAngle))
                                + halfHeight, mDividerPaint);
            }
        }
    }

    private void drawInnerCircle(Canvas canvas, float halfWidth,
                                 float halfHeight) {
        if (mInnerCircle != null) {
            if (!(mInnerCircle instanceof ColorDrawable)) {
                mInnerCircle
                        .setBounds((int) halfWidth - mInnerRadius,
                                (int) halfHeight - mInnerRadius,
                                (int) halfWidth + mInnerRadius,
                                (int) halfHeight + mInnerRadius);

                mInnerCircle.draw(canvas);
            } else {
                canvas.drawCircle(halfWidth, halfHeight, mInnerRadius,
                        mCirclePaint);
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mLayoutMode == LAYOUT_NORMAL) {
            super.dispatchDraw(canvas);
            return;
        }

        if (mSrc == null || mDst == null || mSrc.isRecycled()
                || mDst.isRecycled()) {
            return;
        }

        final int childs = getChildCount();

        final float halfWidth = getWidth() / 2f;
        final float halfHeight = getHeight() / 2f;

        final float radius = halfWidth > halfHeight ? halfHeight : halfWidth;

        if (mCached && mDrawingCache != null && !mDrawingCache.isRecycled()
                && mDirtyViews.size() < childs / 2) {
            canvas.drawBitmap(mDrawingCache, 0f, 0f, null);

            redrawDirty(canvas);

            drawDividers(canvas, halfWidth, halfHeight, radius);

            drawInnerCircle(canvas, halfWidth, halfHeight);

            return;
        } else {
            mCached = false;
        }

        Canvas sCanvas = null;

        if (mCachedCanvas != null) {
            sCanvas = canvas;
            canvas = mCachedCanvas;
        }

        Drawable bkg = getBackground();
        if (bkg != null) {
            bkg.draw(canvas);
        }

        for (int i = 0; i < childs; i++) {
            final View child = getChildAt(i);
            LayoutParams lp = layoutParams(child);

            drawChild(canvas, child, lp);
        }

        drawDividers(canvas, halfWidth, halfHeight, radius);

        drawInnerCircle(canvas, halfWidth, halfHeight);

        if (mCachedCanvas != null) {
            sCanvas.drawBitmap(mDrawingCache, 0f, 0f, null);
            mDirtyViews.clear();
            mCached = true;
        }
    }

    public static class LayoutParams extends ViewGroup.LayoutParams {

        private float startAngle;
        private float endAngle;

        public float weight = 1f;

        public LayoutParams(int width, int height) {
            super(width, height);
        }

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


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleLayout">
        <attr name="innerRadius" format="dimension" />
        <attr name="sliceDivider" format="reference|color" />
        <attr name="innerCircle" format="reference|color" />
        <attr name="angleOffset" format="float" />
        <attr name="angleRange" format="float" />
        <attr name="layoutMode">
            <enum name="normal" value="1" />
            <enum name="pie" value="2" />
        </attr>
        <attr name="dividerWidth" format="dimension" />
    </declare-styleable>
</resources>




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值