非平凡背景可绘制的问题
Problems with non-trivial background drawable
我有一些不平凡的卡片视图背景。
我认为,最好的方法是在 Canvas 中绘制这个矩形,但是如何从它相交圆?以及如何将此自定义可绘制对象设置为背景?我还需要给这张卡添加阴影。
我的尝试没有成功)
这是矩形布局
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp"/>
<solid android:color="@color/common.white"/>
<stroke android:color="@color/group_flights.bubble.border"/>
</shape>
这是 drawable 的实现
class CardBackgroudDrawable(val context: Context?, val widht: Float, val height: Float): Drawable() {
val paint = Paint()
override fun draw(canvas: Canvas?) {
val bitmap = BitmapFactory.decodeResource(context?.resources, R.drawable.drawable_flight_card_background)
with(paint) {
strokeWidth = 1f
isAntiAlias = true
isDither = true
}
canvas?.drawBitmap(bitmap, bounds, RectF(0f, 0f, widht, height), paint)
// How to intersect shapes??
}
override fun setAlpha(alpha: Int) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun setColorFilter(colorFilter: ColorFilter?) {}
}
和卡片布局
class FlightCardLayout: ConstraintLayout {
constructor(context: Context?) : super(context) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
private fun init(context: Context?) {
val drawable = CardBackgroudDrawable(context, measuredWidth.toFloat(), measuredHeight.toFloat())
background = drawable
}
}
此外,我想使用 .9.png 格式。这是个好主意吗?
U.P.D
我找到了很好的工具 "PainCode",并尝试将此形状转换为贝塞尔曲线,
public class BezierFlightCard {
// Resizing Behavior
public enum ResizingBehavior {
AspectFit, //!< The content is proportionally resized to fit into the target rectangle.
AspectFill, //!< The content is proportionally resized to completely fill the target rectangle.
Stretch, //!< The content is stretched to match the entire target rectangle.
Center, //!< The content is centered in the target rectangle, but it is NOT resized.
}
// In Trial version of PaintCode, the code generation is limited to 3 canvases.
// Canvas Drawings
// Tab
private static class CacheForCard {
private static Paint paint = new Paint();
private static RectF bezier2Rect = new RectF();
private static Path bezier2Path = new Path();
}
public static void drawCard(Canvas canvas, RectF frame) {
// General Declarations
Paint paint = CacheForCard.paint;
// Local Colors
int strokeColor = Color.argb(255, 215, 215, 215);
int fillColor = Color.argb(255, 255, 255, 255);
// Bezier 2
RectF bezier2Rect = CacheForCard.bezier2Rect;
bezier2Rect.set(frame.left + 19.5f,
frame.top + 16.5f,
frame.left + 370.5f,
frame.top + 334.5f);
Path bezier2Path = CacheForCard.bezier2Path;
bezier2Path.reset();
bezier2Path.moveTo(frame.left + frame.width() * 0.57926f, frame.bottom - 23.5f);
bezier2Path.cubicTo(frame.left + frame.width() * 0.57018f, frame.bottom - 37.3f, frame.left + frame.width() * 0.53813f, frame.bottom - 47.5f, frame.left + frame.width() * 0.5f, frame.bottom - 47.5f);
bezier2Path.cubicTo(frame.left + frame.width() * 0.46187f, frame.bottom - 47.5f, frame.left + frame.width() * 0.42982f, frame.bottom - 37.3f, frame.left + frame.width() * 0.42074f, frame.bottom - 23.5f);
bezier2Path.lineTo(frame.left + 39.5f, frame.bottom - 23.5f);
bezier2Path.cubicTo(frame.left + 28.45f, frame.bottom - 23.5f, frame.left + 19.5f, frame.bottom - 32.45f, frame.left + 19.5f, frame.bottom - 43.5f);
bezier2Path.lineTo(frame.left + 19.5f, frame.top + 36.5f);
bezier2Path.cubicTo(frame.left + 19.5f, frame.top + 25.45f, frame.left + 28.45f, frame.top + 16.5f, frame.left + 39.5f, frame.top + 16.5f);
bezier2Path.lineTo(frame.right - 40.5f, frame.top + 16.5f);
bezier2Path.cubicTo(frame.right - 29.45f, frame.top + 16.5f, frame.right - 20.5f, frame.top + 25.45f, frame.right - 20.5f, frame.top + 36.5f);
bezier2Path.lineTo(frame.right - 20.5f, frame.bottom - 43.5f);
bezier2Path.cubicTo(frame.right - 20.5f, frame.bottom - 32.45f, frame.right - 29.45f, frame.bottom - 23.5f, frame.right - 40.5f, frame.bottom - 23.5f);
bezier2Path.lineTo(frame.left + frame.width() * 0.57926f, frame.bottom - 23.5f);
bezier2Path.close();
paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
bezier2Path.setFillType(Path.FillType.EVEN_ODD);
paint.setStyle(Paint.Style.FILL);
paint.setColor(fillColor);
canvas.drawPath(bezier2Path, paint);
paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(1f);
paint.setStrokeMiter(10f);
canvas.save();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(strokeColor);
canvas.drawPath(bezier2Path, paint);
canvas.restore();
}
// Resizing Behavior
public static void resizingBehaviorApply(ResizingBehavior behavior, RectF rect, RectF target, RectF result) {
if (rect.equals(target) || target == null) {
result.set(rect);
return;
}
if (behavior == ResizingBehavior.Stretch) {
result.set(target);
return;
}
float xRatio = Math.abs(target.width() / rect.width());
float yRatio = Math.abs(target.height() / rect.height());
float scale = 0f;
switch (behavior) {
case AspectFit: {
scale = Math.min(xRatio, yRatio);
break;
}
case AspectFill: {
scale = Math.max(xRatio, yRatio);
break;
}
case Center: {
scale = 1f;
break;
}
}
float newWidth = Math.abs(rect.width() * scale);
float newHeight = Math.abs(rect.height() * scale);
result.set(target.centerX() - newWidth / 2,
target.centerY() - newHeight / 2,
target.centerX() + newWidth / 2,
target.centerY() + newHeight / 2);
}
}
但是..它仍然不起作用,因为它没有针对测量调整大小进行优化。也许最好的方法是 "spell" 就可以了?)
您可以控制Canvas
的当前剪辑,即Canvas
绘制的区域,通过使用clipXXX
和(来自API 26) clipOutXXX
方法。您可以在 here.
中找到这些方法
要解决您的问题,您可以准备一个描述底部圆圈的 Path
并将其从 Canvas
中剪出。
首先,为了避免在自定义 Drawable
的绘制方法中分配对象,您可能有一个字段:
private Path bottomCircle = new Path();
现在,只要您的自定义可绘制对象的边界发生变化,您就会重新计算圆的位置:
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
bottomCircle.reset();
bottomCircle.addCircle(bounds.centerX(), bounds.bottom, bottomCircleRadius, CW);
}
最后画背景:
@Override
public void draw(@NonNull Canvas canvas) {
canvas.save(); // Save the canvas so others won't get the clipped version
if (Build.VERSION.SDK_INT >= 26) {
canvas.clipOutPath(bottomCircle);
} else {
canvas.clipPath(bottomCircle, DIFFERENCE);
}
// draw your background here
canvas.restore(); // Restore the non-clipped version of the canvas
}
我有一些不平凡的卡片视图背景。
我认为,最好的方法是在 Canvas 中绘制这个矩形,但是如何从它相交圆?以及如何将此自定义可绘制对象设置为背景?我还需要给这张卡添加阴影。
我的尝试没有成功)
这是矩形布局
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp"/>
<solid android:color="@color/common.white"/>
<stroke android:color="@color/group_flights.bubble.border"/>
</shape>
这是 drawable 的实现
class CardBackgroudDrawable(val context: Context?, val widht: Float, val height: Float): Drawable() {
val paint = Paint()
override fun draw(canvas: Canvas?) {
val bitmap = BitmapFactory.decodeResource(context?.resources, R.drawable.drawable_flight_card_background)
with(paint) {
strokeWidth = 1f
isAntiAlias = true
isDither = true
}
canvas?.drawBitmap(bitmap, bounds, RectF(0f, 0f, widht, height), paint)
// How to intersect shapes??
}
override fun setAlpha(alpha: Int) {}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
override fun setColorFilter(colorFilter: ColorFilter?) {}
}
和卡片布局
class FlightCardLayout: ConstraintLayout {
constructor(context: Context?) : super(context) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
private fun init(context: Context?) {
val drawable = CardBackgroudDrawable(context, measuredWidth.toFloat(), measuredHeight.toFloat())
background = drawable
}
}
此外,我想使用 .9.png 格式。这是个好主意吗?
U.P.D
我找到了很好的工具 "PainCode",并尝试将此形状转换为贝塞尔曲线,
public class BezierFlightCard {
// Resizing Behavior
public enum ResizingBehavior {
AspectFit, //!< The content is proportionally resized to fit into the target rectangle.
AspectFill, //!< The content is proportionally resized to completely fill the target rectangle.
Stretch, //!< The content is stretched to match the entire target rectangle.
Center, //!< The content is centered in the target rectangle, but it is NOT resized.
}
// In Trial version of PaintCode, the code generation is limited to 3 canvases.
// Canvas Drawings
// Tab
private static class CacheForCard {
private static Paint paint = new Paint();
private static RectF bezier2Rect = new RectF();
private static Path bezier2Path = new Path();
}
public static void drawCard(Canvas canvas, RectF frame) {
// General Declarations
Paint paint = CacheForCard.paint;
// Local Colors
int strokeColor = Color.argb(255, 215, 215, 215);
int fillColor = Color.argb(255, 255, 255, 255);
// Bezier 2
RectF bezier2Rect = CacheForCard.bezier2Rect;
bezier2Rect.set(frame.left + 19.5f,
frame.top + 16.5f,
frame.left + 370.5f,
frame.top + 334.5f);
Path bezier2Path = CacheForCard.bezier2Path;
bezier2Path.reset();
bezier2Path.moveTo(frame.left + frame.width() * 0.57926f, frame.bottom - 23.5f);
bezier2Path.cubicTo(frame.left + frame.width() * 0.57018f, frame.bottom - 37.3f, frame.left + frame.width() * 0.53813f, frame.bottom - 47.5f, frame.left + frame.width() * 0.5f, frame.bottom - 47.5f);
bezier2Path.cubicTo(frame.left + frame.width() * 0.46187f, frame.bottom - 47.5f, frame.left + frame.width() * 0.42982f, frame.bottom - 37.3f, frame.left + frame.width() * 0.42074f, frame.bottom - 23.5f);
bezier2Path.lineTo(frame.left + 39.5f, frame.bottom - 23.5f);
bezier2Path.cubicTo(frame.left + 28.45f, frame.bottom - 23.5f, frame.left + 19.5f, frame.bottom - 32.45f, frame.left + 19.5f, frame.bottom - 43.5f);
bezier2Path.lineTo(frame.left + 19.5f, frame.top + 36.5f);
bezier2Path.cubicTo(frame.left + 19.5f, frame.top + 25.45f, frame.left + 28.45f, frame.top + 16.5f, frame.left + 39.5f, frame.top + 16.5f);
bezier2Path.lineTo(frame.right - 40.5f, frame.top + 16.5f);
bezier2Path.cubicTo(frame.right - 29.45f, frame.top + 16.5f, frame.right - 20.5f, frame.top + 25.45f, frame.right - 20.5f, frame.top + 36.5f);
bezier2Path.lineTo(frame.right - 20.5f, frame.bottom - 43.5f);
bezier2Path.cubicTo(frame.right - 20.5f, frame.bottom - 32.45f, frame.right - 29.45f, frame.bottom - 23.5f, frame.right - 40.5f, frame.bottom - 23.5f);
bezier2Path.lineTo(frame.left + frame.width() * 0.57926f, frame.bottom - 23.5f);
bezier2Path.close();
paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
bezier2Path.setFillType(Path.FillType.EVEN_ODD);
paint.setStyle(Paint.Style.FILL);
paint.setColor(fillColor);
canvas.drawPath(bezier2Path, paint);
paint.reset();
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(1f);
paint.setStrokeMiter(10f);
canvas.save();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(strokeColor);
canvas.drawPath(bezier2Path, paint);
canvas.restore();
}
// Resizing Behavior
public static void resizingBehaviorApply(ResizingBehavior behavior, RectF rect, RectF target, RectF result) {
if (rect.equals(target) || target == null) {
result.set(rect);
return;
}
if (behavior == ResizingBehavior.Stretch) {
result.set(target);
return;
}
float xRatio = Math.abs(target.width() / rect.width());
float yRatio = Math.abs(target.height() / rect.height());
float scale = 0f;
switch (behavior) {
case AspectFit: {
scale = Math.min(xRatio, yRatio);
break;
}
case AspectFill: {
scale = Math.max(xRatio, yRatio);
break;
}
case Center: {
scale = 1f;
break;
}
}
float newWidth = Math.abs(rect.width() * scale);
float newHeight = Math.abs(rect.height() * scale);
result.set(target.centerX() - newWidth / 2,
target.centerY() - newHeight / 2,
target.centerX() + newWidth / 2,
target.centerY() + newHeight / 2);
}
}
但是..它仍然不起作用,因为它没有针对测量调整大小进行优化。也许最好的方法是 "spell" 就可以了?)
您可以控制Canvas
的当前剪辑,即Canvas
绘制的区域,通过使用clipXXX
和(来自API 26) clipOutXXX
方法。您可以在 here.
要解决您的问题,您可以准备一个描述底部圆圈的 Path
并将其从 Canvas
中剪出。
首先,为了避免在自定义 Drawable
的绘制方法中分配对象,您可能有一个字段:
private Path bottomCircle = new Path();
现在,只要您的自定义可绘制对象的边界发生变化,您就会重新计算圆的位置:
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
bottomCircle.reset();
bottomCircle.addCircle(bounds.centerX(), bounds.bottom, bottomCircleRadius, CW);
}
最后画背景:
@Override
public void draw(@NonNull Canvas canvas) {
canvas.save(); // Save the canvas so others won't get the clipped version
if (Build.VERSION.SDK_INT >= 26) {
canvas.clipOutPath(bottomCircle);
} else {
canvas.clipPath(bottomCircle, DIFFERENCE);
}
// draw your background here
canvas.restore(); // Restore the non-clipped version of the canvas
}