原文:https://github.com/GcsSloop/rclayout
https://www.gcssloop.com/gebug/rclayout
代码:
RCRelativeLayout.java
package com.example.m_evolution.View; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.Checkable; import android.widget.RelativeLayout; import com.example.m_evolution.Helper.RCAttrs; import com.example.m_evolution.Helper.RCHelper; /** * 作用:圆角相对布局 * 作者:GcsSloop */ public class RCRelativeLayout extends RelativeLayout implements Checkable, RCAttrs { RCHelper mRCHelper; public RCRelativeLayout(Context context) { this(context, null); } public RCRelativeLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RCRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mRCHelper = new RCHelper(); mRCHelper.initAttrs(context, attrs); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mRCHelper.onSizeChanged(this, w, h); } @Override protected void dispatchDraw(Canvas canvas) { canvas.saveLayer(mRCHelper.mLayer, null, Canvas.ALL_SAVE_FLAG); super.dispatchDraw(canvas); mRCHelper.onClipDraw(canvas); canvas.restore(); } @Override public void draw(Canvas canvas) { if (mRCHelper.mClipBackground) { canvas.save(); canvas.clipPath(mRCHelper.mClipPath); super.draw(canvas); canvas.restore(); } else { super.draw(canvas); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); if (action == MotionEvent.ACTION_DOWN && !mRCHelper.mAreaRegion.contains((int) ev.getX(), (int) ev.getY())) { return false; } if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_UP) { refreshDrawableState(); } else if (action == MotionEvent.ACTION_CANCEL) { setPressed(false); refreshDrawableState(); } return super.dispatchTouchEvent(ev); } //--- 公开接口 ---------------------------------------------------------------------------------- public void setClipBackground(boolean clipBackground) { mRCHelper.mClipBackground = clipBackground; invalidate(); } public void setRoundAsCircle(boolean roundAsCircle) { mRCHelper.mRoundAsCircle = roundAsCircle; invalidate(); } public void setRadius(int radius) { for (int i = 0; i < mRCHelper.radii.length; i++) { mRCHelper.radii[i] = radius; } invalidate(); } public void setTopLeftRadius(int topLeftRadius) { mRCHelper.radii[0] = topLeftRadius; mRCHelper.radii[1] = topLeftRadius; invalidate(); } public void setTopRightRadius(int topRightRadius) { mRCHelper.radii[2] = topRightRadius; mRCHelper.radii[3] = topRightRadius; invalidate(); } public void setBottomLeftRadius(int bottomLeftRadius) { mRCHelper.radii[6] = bottomLeftRadius; mRCHelper.radii[7] = bottomLeftRadius; invalidate(); } public void setBottomRightRadius(int bottomRightRadius) { mRCHelper.radii[4] = bottomRightRadius; mRCHelper.radii[5] = bottomRightRadius; invalidate(); } public void setStrokeWidth(int strokeWidth) { mRCHelper.mStrokeWidth = strokeWidth; invalidate(); } public void setStrokeColor(int strokeColor) { mRCHelper.mStrokeColor = strokeColor; invalidate(); } @Override public void invalidate() { if (null != mRCHelper) mRCHelper.refreshRegion(this); super.invalidate(); } public boolean isClipBackground() { return mRCHelper.mClipBackground; } public boolean isRoundAsCircle() { return mRCHelper.mRoundAsCircle; } public float getTopLeftRadius() { return mRCHelper.radii[0]; } public float getTopRightRadius() { return mRCHelper.radii[2]; } public float getBottomLeftRadius() { return mRCHelper.radii[4]; } public float getBottomRightRadius() { return mRCHelper.radii[6]; } public int getStrokeWidth() { return mRCHelper.mStrokeWidth; } public int getStrokeColor() { return mRCHelper.mStrokeColor; } //--- Selector 支持 ---------------------------------------------------------------------------- @Override protected void drawableStateChanged() { super.drawableStateChanged(); mRCHelper.drawableStateChanged(this); } @Override public void setChecked(boolean checked) { if (mRCHelper.mChecked != checked) { mRCHelper.mChecked = checked; refreshDrawableState(); if (mRCHelper.mOnCheckedChangeListener != null) { mRCHelper.mOnCheckedChangeListener.onCheckedChanged(this, mRCHelper.mChecked); } } } @Override public boolean isChecked() { return mRCHelper.mChecked; } @Override public void toggle() { setChecked(!mRCHelper.mChecked); } public void setOnCheckedChangeListener(RCHelper.OnCheckedChangeListener listener) { mRCHelper.mOnCheckedChangeListener = listener; } }
RCHelper.java
package com.example.m_evolution.Helper; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Region; import android.os.Build; import android.util.AttributeSet; import android.view.View; import android.widget.Checkable; import com.example.m_evolution.R; import java.util.ArrayList; /** * 作用:圆角辅助工具 * 作者:GcsSloop */ public class RCHelper { public float[] radii = new float[8]; // top-left, top-right, bottom-right, bottom-left public Path mClipPath; // 剪裁区域路径 public Paint mPaint; // 画笔 public boolean mRoundAsCircle = false; // 圆形 public int mDefaultStrokeColor; // 默认描边颜色 public int mStrokeColor; // 描边颜色 public ColorStateList mStrokeColorStateList;// 描边颜色的状态 public int mStrokeWidth; // 描边半径 public boolean mClipBackground; // 是否剪裁背景 public Region mAreaRegion; // 内容区域 public RectF mLayer; // 画布图层大小 public void initAttrs(Context context, AttributeSet attrs) { TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.RCAttrs); mRoundAsCircle = ta.getBoolean(R.styleable.RCAttrs_round_as_circle, false); mStrokeColorStateList = ta.getColorStateList(R.styleable.RCAttrs_stroke_color); if (null != mStrokeColorStateList) { mStrokeColor = mStrokeColorStateList.getDefaultColor(); mDefaultStrokeColor = mStrokeColorStateList.getDefaultColor(); } else { mStrokeColor = Color.WHITE; mDefaultStrokeColor = Color.WHITE; } mStrokeWidth = ta.getDimensionPixelSize(R.styleable.RCAttrs_stroke_width, 0); mClipBackground = ta.getBoolean(R.styleable.RCAttrs_clip_background, false); int roundCorner = ta.getDimensionPixelSize(R.styleable.RCAttrs_round_corner, 0); int roundCornerTopLeft = ta.getDimensionPixelSize( R.styleable.RCAttrs_round_corner_top_left, roundCorner); int roundCornerTopRight = ta.getDimensionPixelSize( R.styleable.RCAttrs_round_corner_top_right, roundCorner); int roundCornerBottomLeft = ta.getDimensionPixelSize( R.styleable.RCAttrs_round_corner_bottom_left, roundCorner); int roundCornerBottomRight = ta.getDimensionPixelSize( R.styleable.RCAttrs_round_corner_bottom_right, roundCorner); ta.recycle(); radii[0] = roundCornerTopLeft; radii[1] = roundCornerTopLeft; radii[2] = roundCornerTopRight; radii[3] = roundCornerTopRight; radii[4] = roundCornerBottomRight; radii[5] = roundCornerBottomRight; radii[6] = roundCornerBottomLeft; radii[7] = roundCornerBottomLeft; mLayer = new RectF(); mClipPath = new Path(); mAreaRegion = new Region(); mPaint = new Paint(); mPaint.setColor(Color.WHITE); mPaint.setAntiAlias(true); } public void onSizeChanged(View view, int w, int h) { mLayer.set(0, 0, w, h); refreshRegion(view); } public void refreshRegion(View view) { int w = (int) mLayer.width(); int h = (int) mLayer.height(); RectF areas = new RectF(); areas.left = view.getPaddingLeft(); areas.top = view.getPaddingTop(); areas.right = w - view.getPaddingRight(); areas.bottom = h - view.getPaddingBottom(); mClipPath.reset(); if (mRoundAsCircle) { float d = areas.width() >= areas.height() ? areas.height() : areas.width(); float r = d / 2; PointF center = new PointF(w / 2, h / 2); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { mClipPath.addCircle(center.x, center.y, r, Path.Direction.CW); mClipPath.moveTo(0, 0); // 通过空操作让Path区域占满画布 mClipPath.moveTo(w, h); } else { float y = h / 2 - r; mClipPath.moveTo(areas.left, y); mClipPath.addCircle(center.x, y + r, r, Path.Direction.CW); } } else { mClipPath.addRoundRect(areas, radii, Path.Direction.CW); } Region clip = new Region((int) areas.left, (int) areas.top, (int) areas.right, (int) areas.bottom); mAreaRegion.setPath(mClipPath, clip); } public void onClipDraw(Canvas canvas) { if (mStrokeWidth > 0) { // 支持半透明描边,将与描边区域重叠的内容裁剪掉 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); mPaint.setColor(Color.WHITE); mPaint.setStrokeWidth(mStrokeWidth * 2); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mClipPath, mPaint); // 绘制描边 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); mPaint.setColor(mStrokeColor); mPaint.setStyle(Paint.Style.STROKE); canvas.drawPath(mClipPath, mPaint); } mPaint.setColor(Color.WHITE); mPaint.setStyle(Paint.Style.FILL); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawPath(mClipPath, mPaint); } else { mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); final Path path = new Path(); path.addRect(0, 0, (int) mLayer.width(), (int) mLayer.height(), Path.Direction.CW); path.op(mClipPath, Path.Op.DIFFERENCE); canvas.drawPath(path, mPaint); } } //--- Selector 支持 ---------------------------------------------------------------------------- public boolean mChecked; // 是否是 check 状态 public OnCheckedChangeListener mOnCheckedChangeListener; public void drawableStateChanged(View view) { if (view instanceof RCAttrs) { ArrayList<Integer> stateListArray = new ArrayList<>(); if (view instanceof Checkable) { stateListArray.add(android.R.attr.state_checkable); if (((Checkable) view).isChecked()) stateListArray.add(android.R.attr.state_checked); } if (view.isEnabled()) stateListArray.add(android.R.attr.state_enabled); if (view.isFocused()) stateListArray.add(android.R.attr.state_focused); if (view.isPressed()) stateListArray.add(android.R.attr.state_pressed); if (view.isHovered()) stateListArray.add(android.R.attr.state_hovered); if (view.isSelected()) stateListArray.add(android.R.attr.state_selected); if (view.isActivated()) stateListArray.add(android.R.attr.state_activated); if (view.hasWindowFocus()) stateListArray.add(android.R.attr.state_window_focused); if (mStrokeColorStateList != null && mStrokeColorStateList.isStateful()) { int[] stateList = new int[stateListArray.size()]; for (int i = 0; i < stateListArray.size(); i++) { stateList[i] = stateListArray.get(i); } int stateColor = mStrokeColorStateList.getColorForState(stateList, mDefaultStrokeColor); ((RCAttrs) view).setStrokeColor(stateColor); } } } public interface OnCheckedChangeListener { void onCheckedChanged(View view, boolean isChecked); } }
RCAttrs.java
package com.example.m_evolution.Helper; public interface RCAttrs { void setClipBackground(boolean clipBackground); void setRoundAsCircle(boolean roundAsCircle); void setRadius(int radius); void setTopLeftRadius(int topLeftRadius); void setTopRightRadius(int topRightRadius); void setBottomLeftRadius(int bottomLeftRadius); void setBottomRightRadius(int bottomRightRadius); void setStrokeWidth(int strokeWidth); void setStrokeColor(int strokeColor); boolean isClipBackground(); boolean isRoundAsCircle(); float getTopLeftRadius(); float getTopRightRadius(); float getBottomLeftRadius(); float getBottomRightRadius(); int getStrokeWidth(); int getStrokeColor(); }
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- *公共属性* --> <!--圆形--> <attr name="round_as_circle" format="boolean" /> <!--全部圆角半径--> <attr name="round_corner" format="integer|dimension" /> <!--针对各个角的半径--> <attr name="round_corner_top_left" format="integer|dimension" /> <attr name="round_corner_top_right" format="integer|dimension" /> <attr name="round_corner_bottom_left" format="integer|dimension" /> <attr name="round_corner_bottom_right" format="integer|dimension" /> <!--描边颜色/半径--> <attr name="stroke_color" format="color|reference" /> <attr name="stroke_width" format="integer|dimension" /> <!-- 是否剪裁 RCLayout 的背景 --> <attr name="clip_background" format="boolean" /> <!--真正用于解析的属性--> <declare-styleable name="RCAttrs"> <attr name="round_as_circle" /> <attr name="round_corner" /> <attr name="round_corner_top_left" /> <attr name="round_corner_top_right" /> <attr name="round_corner_bottom_left" /> <attr name="round_corner_bottom_right" /> <attr name="stroke_color" /> <attr name="stroke_width" /> <attr name="clip_background" /> </declare-styleable> <!--假体:用于提示--> <declare-styleable name="RCRelativeLayout"> <attr name="round_as_circle" /> <attr name="round_corner" /> <attr name="round_corner_top_left" /> <attr name="round_corner_top_right" /> <attr name="round_corner_bottom_left" /> <attr name="round_corner_bottom_right" /> <attr name="stroke_color" /> <attr name="stroke_width" /> <attr name="clip_background" /> </declare-styleable> <!--假体:用于提示--> <declare-styleable name="RCImageView"> <attr name="round_as_circle" /> <attr name="round_corner" /> <attr name="round_corner_top_left" /> <attr name="round_corner_top_right" /> <attr name="round_corner_bottom_left" /> <attr name="round_corner_bottom_right" /> <attr name="stroke_color" /> <attr name="stroke_width" /> <attr name="clip_background" /> </declare-styleable> </resources>