Support borders in RCTImageView
Summary: @public Initial RCTImageView implementation only supported 'src', 'tintColor' and 'resizeMode'. This diff adds support for the rest of the properties: 'borderColor', 'borderWidth' and 'borderRadius'. `AbstractDrawBorders` class is reused in a follow up diff to draw borders for 'RCTView'. Reviewed By: sriramramani Differential Revision: D2693560
This commit is contained in:
parent
760422525e
commit
71ca522c68
|
@ -0,0 +1,132 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.react.flat;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.PathEffect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for border drawing operations (used by DrawImage and DrawBorder). Draws rectangular or
|
||||||
|
* rounded border along the rectangle, defined by the bounding box of the AbstractDrawCommand.
|
||||||
|
*/
|
||||||
|
/* package */ abstract class AbstractDrawBorder extends AbstractDrawCommand {
|
||||||
|
|
||||||
|
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
private static final RectF TMP_RECT = new RectF();
|
||||||
|
private static final int BORDER_PATH_DIRTY = 1 << 0;
|
||||||
|
|
||||||
|
static {
|
||||||
|
PAINT.setStyle(Paint.Style.STROKE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mSetPropertiesFlag;
|
||||||
|
private int mBorderColor = Color.BLACK;
|
||||||
|
private float mBorderWidth;
|
||||||
|
private float mBorderRadius;
|
||||||
|
private @Nullable Path mPathForBorderRadius;
|
||||||
|
|
||||||
|
public final void setBorderWidth(float borderWidth) {
|
||||||
|
mBorderWidth = borderWidth;
|
||||||
|
setFlag(BORDER_PATH_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final float getBorderWidth() {
|
||||||
|
return mBorderWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBorderRadius(float borderRadius) {
|
||||||
|
mBorderRadius = borderRadius;
|
||||||
|
setFlag(BORDER_PATH_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final float getBorderRadius() {
|
||||||
|
return mBorderRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setBorderColor(int borderColor) {
|
||||||
|
mBorderColor = borderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getBorderColor() {
|
||||||
|
return mBorderColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBoundsChanged() {
|
||||||
|
setFlag(BORDER_PATH_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void drawBorders(Canvas canvas) {
|
||||||
|
if (mBorderWidth < 0.5f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mBorderColor == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PAINT.setColor(mBorderColor);
|
||||||
|
PAINT.setStrokeWidth(mBorderWidth);
|
||||||
|
PAINT.setPathEffect(getPathEffectForBorderStyle());
|
||||||
|
canvas.drawPath(getPathForBorderRadius(), PAINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void updatePath(Path path, float correction) {
|
||||||
|
path.reset();
|
||||||
|
|
||||||
|
TMP_RECT.set(
|
||||||
|
getLeft() + correction,
|
||||||
|
getTop() + correction,
|
||||||
|
getRight() - correction,
|
||||||
|
getBottom() - correction);
|
||||||
|
|
||||||
|
path.addRoundRect(
|
||||||
|
TMP_RECT,
|
||||||
|
mBorderRadius,
|
||||||
|
mBorderRadius,
|
||||||
|
Path.Direction.CW);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected @Nullable PathEffect getPathEffectForBorderStyle() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean isFlagSet(int mask) {
|
||||||
|
return (mSetPropertiesFlag & mask) == mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void setFlag(int mask) {
|
||||||
|
mSetPropertiesFlag |= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void resetFlag(int mask) {
|
||||||
|
mSetPropertiesFlag &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Path getPathForBorderRadius() {
|
||||||
|
if (isFlagSet(BORDER_PATH_DIRTY)) {
|
||||||
|
if (mPathForBorderRadius == null) {
|
||||||
|
mPathForBorderRadius = new Path();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePath(mPathForBorderRadius, mBorderWidth * 0.5f);
|
||||||
|
|
||||||
|
resetFlag(BORDER_PATH_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPathForBorderRadius;
|
||||||
|
}
|
||||||
|
}
|
|
@ -103,6 +103,9 @@ package com.facebook.react.flat;
|
||||||
return mBottom;
|
return mBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void onBoundsChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates boundaries of this DrawCommand.
|
* Updates boundaries of this DrawCommand.
|
||||||
*/
|
*/
|
||||||
|
@ -111,6 +114,8 @@ package com.facebook.react.flat;
|
||||||
mTop = top;
|
mTop = top;
|
||||||
mRight = right;
|
mRight = right;
|
||||||
mBottom = bottom;
|
mBottom = bottom;
|
||||||
|
|
||||||
|
onBoundsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -107,6 +107,10 @@ import com.facebook.infer.annotation.Assertions;
|
||||||
return ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
|
return ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* package */ boolean isDetached() {
|
||||||
|
return mAttachCounter == 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewResult(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
public void onNewResult(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||||
if (!dataSource.isFinished()) {
|
if (!dataSource.isFinished()) {
|
||||||
|
|
|
@ -43,4 +43,16 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
* Returns a scale type to draw to the image with.
|
* Returns a scale type to draw to the image with.
|
||||||
*/
|
*/
|
||||||
public ScaleType getScaleType();
|
public ScaleType getScaleType();
|
||||||
|
|
||||||
|
public void setBorderWidth(float borderWidth);
|
||||||
|
|
||||||
|
public float getBorderWidth();
|
||||||
|
|
||||||
|
public void setBorderRadius(float borderRadius);
|
||||||
|
|
||||||
|
public float getBorderRadius();
|
||||||
|
|
||||||
|
public void setBorderColor(int borderColor);
|
||||||
|
|
||||||
|
public int getBorderColor();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,14 @@ package com.facebook.react.flat;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapShader;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
|
import android.graphics.Shader;
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
@ -27,15 +30,18 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
* DrawImageWithPipeline is DrawCommand that can draw a local or remote image.
|
* DrawImageWithPipeline is DrawCommand that can draw a local or remote image.
|
||||||
* It uses BitmapRequestHelper internally to fetch and cache the images.
|
* It uses BitmapRequestHelper internally to fetch and cache the images.
|
||||||
*/
|
*/
|
||||||
/* package */ final class DrawImageWithPipeline extends AbstractDrawCommand implements DrawImage {
|
/* package */ final class DrawImageWithPipeline extends AbstractDrawBorder implements DrawImage {
|
||||||
|
|
||||||
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
private static final int BORDER_BITMAP_PATH_DIRTY = 1 << 1;
|
||||||
|
|
||||||
private final Matrix mTransform = new Matrix();
|
private final Matrix mTransform = new Matrix();
|
||||||
private ScaleType mScaleType = ImageResizeMode.defaultValue();
|
private ScaleType mScaleType = ImageResizeMode.defaultValue();
|
||||||
private @Nullable BitmapRequestHelper mBitmapRequestHelper;
|
private @Nullable BitmapRequestHelper mBitmapRequestHelper;
|
||||||
private @Nullable PorterDuffColorFilter mColorFilter;
|
private @Nullable PorterDuffColorFilter mColorFilter;
|
||||||
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
||||||
|
private @Nullable Path mPathForRoundedBitmap;
|
||||||
|
private @Nullable BitmapShader mBitmapShader;
|
||||||
private boolean mForceClip;
|
private boolean mForceClip;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -45,6 +51,8 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setImageRequest(@Nullable ImageRequest imageRequest) {
|
public void setImageRequest(@Nullable ImageRequest imageRequest) {
|
||||||
|
mBitmapShader = null;
|
||||||
|
|
||||||
if (imageRequest == null) {
|
if (imageRequest == null) {
|
||||||
mBitmapRequestHelper = null;
|
mBitmapRequestHelper = null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,14 +87,34 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
PAINT.setColorFilter(mColorFilter);
|
PAINT.setColorFilter(mColorFilter);
|
||||||
|
|
||||||
if (mForceClip) {
|
if (mForceClip) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.clipRect(getLeft(), getTop(), getRight(), getBottom());
|
canvas.clipRect(getLeft(), getTop(), getRight(), getBottom());
|
||||||
canvas.drawBitmap(bitmap, mTransform, PAINT);
|
|
||||||
canvas.restore();
|
|
||||||
} else {
|
|
||||||
canvas.drawBitmap(bitmap, mTransform, PAINT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getBorderRadius() < 0.5f) {
|
||||||
|
canvas.drawBitmap(bitmap, mTransform, PAINT);
|
||||||
|
} else {
|
||||||
|
if (mBitmapShader == null) {
|
||||||
|
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||||
|
mBitmapShader.setLocalMatrix(mTransform);
|
||||||
|
}
|
||||||
|
PAINT.setShader(mBitmapShader);
|
||||||
|
canvas.drawPath(getPathForRoundedBitmap(), PAINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBorders(canvas);
|
||||||
|
|
||||||
|
if (mForceClip) {
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBorderRadius(float borderRadius) {
|
||||||
|
super.setBorderRadius(borderRadius);
|
||||||
|
setFlag(BORDER_BITMAP_PATH_DIRTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,6 +126,17 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
@Override
|
@Override
|
||||||
public void onDetached() {
|
public void onDetached() {
|
||||||
Assertions.assumeNotNull(mBitmapRequestHelper).detach();
|
Assertions.assumeNotNull(mBitmapRequestHelper).detach();
|
||||||
|
|
||||||
|
if (mBitmapRequestHelper.isDetached()) {
|
||||||
|
// Make sure we don't hold on to the Bitmap.
|
||||||
|
mBitmapShader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBoundsChanged() {
|
||||||
|
super.onBoundsChanged();
|
||||||
|
setFlag(BORDER_BITMAP_PATH_DIRTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void updateBounds(Bitmap bitmap) {
|
/* package */ void updateBounds(Bitmap bitmap) {
|
||||||
|
@ -140,5 +179,23 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
|
|
||||||
mTransform.setScale(scale, scale);
|
mTransform.setScale(scale, scale);
|
||||||
mTransform.postTranslate(left + paddingLeft, top + paddingTop);
|
mTransform.postTranslate(left + paddingLeft, top + paddingTop);
|
||||||
|
|
||||||
|
if (mBitmapShader != null) {
|
||||||
|
mBitmapShader.setLocalMatrix(mTransform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path getPathForRoundedBitmap() {
|
||||||
|
if (isFlagSet(BORDER_BITMAP_PATH_DIRTY)) {
|
||||||
|
if (mPathForRoundedBitmap == null) {
|
||||||
|
mPathForRoundedBitmap = new Path();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePath(mPathForRoundedBitmap, 1.0f);
|
||||||
|
|
||||||
|
resetFlag(BORDER_BITMAP_PATH_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mPathForRoundedBitmap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import android.net.Uri;
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
import com.facebook.react.uimanager.PixelUtil;
|
||||||
import com.facebook.react.uimanager.ReactProp;
|
import com.facebook.react.uimanager.ReactProp;
|
||||||
import com.facebook.react.uimanager.ViewProps;
|
import com.facebook.react.uimanager.ViewProps;
|
||||||
import com.facebook.react.views.image.ImageResizeMode;
|
import com.facebook.react.views.image.ImageResizeMode;
|
||||||
|
@ -102,6 +103,29 @@ import com.facebook.react.views.image.ImageResizeMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "borderColor", customType = "Color")
|
||||||
|
public void setBorderColor(int borderColor) {
|
||||||
|
if (mDrawImage.getBorderColor() != borderColor) {
|
||||||
|
getMutableDrawImage().setBorderColor(borderColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "borderWidth")
|
||||||
|
public void setBorderWidth(float borderWidth) {
|
||||||
|
borderWidth = PixelUtil.toPixelFromDIP(borderWidth);
|
||||||
|
|
||||||
|
if (mDrawImage.getBorderWidth() != borderWidth) {
|
||||||
|
getMutableDrawImage().setBorderWidth(borderWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = "borderRadius")
|
||||||
|
public void setBorderRadius(float borderRadius) {
|
||||||
|
if (mDrawImage.getBorderRadius() != borderRadius) {
|
||||||
|
getMutableDrawImage().setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private T getMutableDrawImage() {
|
private T getMutableDrawImage() {
|
||||||
if (mDrawImage.isFrozen()) {
|
if (mDrawImage.isFrozen()) {
|
||||||
mDrawImage = (T) mDrawImage.mutableCopy();
|
mDrawImage = (T) mDrawImage.mutableCopy();
|
||||||
|
|
Loading…
Reference in New Issue