我想创建一个通用的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>
答案 0 :(得分:48)
创建剪切其子项的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);
}
您实际上可以创建任何复杂路径:)
请记住,您可以使用&#34; Op&#34;多次调用clipPath。请你按照自己喜欢的方式操作多个剪辑。
注意:我在onSizeChanged
中创建了路径,因为在onDraw
中这样做会对性能造成影响。
注意2:剪切路径是在没有消除锯齿的情况下完成的:/所以如果你想要光滑的边框,你需要以其他方式完成它。我现在还没有意识到任何削减使用抗锯齿的方法。
更新(大纲)
由于Android Lollipop(API 21)可以将高程和阴影应用于视图。引入了一个名为Outline的新概念。这是一条告诉框架用于compute the shadow和其他事物(如涟漪效应)的视图形状的路径。
视图的默认Outline
是视图大小的矩形,但可以很容易地制成椭圆/圆形或圆角矩形。要定义自定义Outline
,您必须在视图上使用setOutlineProvider()
方法,如果它是自定义视图,您可能希望在构造函数中使用自定义ViewOutlineProvider
定义它作为自定义视图的内部类。您可以使用您选择的Outline
来定义自己的Path
提供商,只要它是convex path(数学概念意味着没有凹陷且没有洞的封闭路径,例如星形和齿轮形状都不凸出。
您还可以使用方法setClipToOutline(true)
使大纲也剪辑(我认为这也适用于抗锯齿,有人可以在评论中确认/反驳吗? ),但仅支持非Path
大纲。
答案 1 :(得分:20)
您可以覆盖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();
}
}
答案 2 :(得分:2)
ViewGroup
(及其子类)设置一个标志,指示默认情况下它不会执行任何绘制。在源代码中看起来有点像这样:
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
所以你的onDraw(...)
可能现在没有被击中。如果您想进行任何手动绘图,请致电setWillNotDraw(false)
。
答案 3 :(得分:2)
不要忘记要求使用setWillNotDraw(false)调用onDraw;并将值设置为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);
}
答案 4 :(得分:1)
您应该覆盖dispatchDraw;
答案 5 :(得分:-2)
你需要覆盖drawChild()方法来剪辑childViews。
@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);
}
答案 6 :(得分:-3)
为什么不将ShapeDrawable定义为布局的背景,只需在运行时更改drawable的圆角半径。