Пользовательский макет, который округляет углы его содержимого

Я хотел бы создать общую ViewGroup, которая затем может быть повторно использована в XML-макетах для округления углов всего, что помещается в нее.

По какой-то причине canvas.clipPath() , похоже, не имеет эффекта. Что я делаю не так?

Вот код Java:

 package rounded; import static android.graphics.Path.Direction.CCW; public class RoundedView extends FrameLayout { private float radius; private Path path = new Path(); private RectF rect = new RectF(); public RoundedView(Context context, AttributeSet attrs) { super(context, attrs); this.radius = attrs.getAttributeFloatValue(null, "corner_radius", 0f); } @Override protected void onDraw(Canvas canvas) { int savedState = canvas.save(); float w = getWidth(); float h = getHeight(); path.reset(); rect.set(0, 0, w, h); path.addRoundRect(rect, radius, radius, CCW); path.close(); boolean debug = canvas.clipPath(path); super.onDraw(canvas); canvas.restoreToCount(savedState); } } 

Использование в XML:

 <?xml version="1.0" encoding="utf-8"?> <rounded.RoundedView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" corner_radius="40.0" > <RelativeLayout android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" > ... </RelativeLayout> </rounded.RoundedView> 

Solutions Collecting From Web of "Пользовательский макет, который округляет углы его содержимого"

Правильный способ создания ViewGroup, который закрепил его дочерние элементы, должен сделать это в методе dispatchDraw (Canvas).

Это пример того, как вы можете обрезать любые дочерние элементы ViewGroup с кругом:

 private Path path = new Path(); @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // compute the path float halfWidth = w / 2f; float halfHeight = h / 2f; float centerX = halfWidth; float centerY = halfHeight; path.reset(); path.addCircle(centerX, centerY, Math.min(halfWidth, halfHeight), Path.Direction.CW); path.close(); } @Override protected void dispatchDraw(Canvas canvas) { int save = canvas.save(); canvas.clipPath(circlePath); super.dispatchDraw(canvas); canvas.restoreToCount(save); } 

Метод dispatchDraw – это тот, который вызван для клипов. Не нужно setWillNotDraw (false), если ваш макет просто закрепил его детей.

Это изображение получается с помощью кода выше, я просто расширил Facebook ProfilePictureView (который является FrameLayout, включая квадрат ImageView с изображением профиля facebook):

Обрезка круга

Поэтому для достижения круглой границы вы делаете что-то вроде этого:

 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // compute the path path.reset(); rect.set(0, 0, w, h); path.addRoundRect(rect, radius, radius, Path.Direction.CW); path.close(); } @Override protected void dispatchDraw(Canvas canvas) { int save = canvas.save(); canvas.clipPath(path); super.dispatchDraw(canvas); canvas.restoreToCount(save); } 

Круглая обрезка границ

Вы можете создать любой сложный путь 🙂

Помните, что вы можете многократно вызывать clipPath с помощью операции «Op», чтобы пересечь несколько клипов так, как вам нравится.

ПРИМЕЧАНИЕ. Я создал Путь в onSizeChanged, потому что это делает onDraw плохой для производительности.

ПРИМЕЧАНИЕ2: отсечение Пути выполняется без сглаживания: /, поэтому, если вы хотите гладкие границы, вам нужно будет сделать это каким-то другим способом. Я не знаю, как сделать обрезание с помощью сглаживания прямо сейчас.

Удачи

Вы можете переопределить метод draw(Canvas canvas) :

 public class RoundedLinearLayout extends LinearLayout { Path mPath; float mCornerRadius; public RoundedLinearLayout(Context context) { super(context); } public RoundedLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public RoundedLinearLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void draw(Canvas canvas) { canvas.save(); canvas.clipPath(mPath); super.draw(canvas); canvas.restore(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); RectF r = new RectF(0, 0, w, h); mPath = new Path(); mPath.addRoundRect(r, mCornerRadius, mCornerRadius, Direction.CW); mPath.close(); } public void setCornerRadius(int radius) { mCornerRadius = radius; invalidate(); } } 

ViewGroup (и, следовательно, его подклассы) устанавливает флаг, указывающий, что по умолчанию он не делает никакого чертежа. В источнике это выглядит примерно так:

 // ViewGroup doesn't draw by default if (!debugDraw()) { setFlags(WILL_NOT_DRAW, DRAW_MASK); } 

Таким образом, ваш onDraw(...) вероятно, не попадает прямо сейчас. Если вы хотите сделать любой ручной рисунок, вызовите setWillNotDraw(false) .

Не забудьте запросить onDraw для вызова с помощью setWillNotDraw (false); И установите значение mRadius, тогда просто сделайте что-то вроде этого:

 @Override protected void onDraw(Canvas canvas) { mPath.reset(); mRect.set(0, 0, canvas.getWidth(), canvas.getHeight()); mPath.addRoundRect(mRect, mRadius, mRadius, Direction.CCW); mPath.close(); canvas.clipPath(mPath); } 

OnDraw of FrameLayout не вызывается, если не установлен фон Layout Layout ;

Вы должны переопределить dispatchDraw;

U необходимо переопределить метод drawChild (), чтобы обрезать дочерние элементы.

 @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { int flag = canvas.save(); canvas.clipPath(pathToClip); boolean result=super.drawChild(canvas, child, drawingTime); canvas.restoreToCount(flag); return result; } 

Если вы хотите скопировать фон ViewGroup, переопределите draw () вместо этого.

 @Override public void draw(Canvas canvas) { int flag = canvas.save(); canvas.clipPath(pathToClip); super.draw(canvas); canvas.restoreToCount(flag); } 

Почему бы просто не определить ShapeDrawable в качестве фона вашего макета и просто изменить радиус угла вытягиваемого во время выполнения.