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;
|
||||
}
|
||||
|
||||
protected void onBoundsChanged() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates boundaries of this DrawCommand.
|
||||
*/
|
||||
|
@ -111,6 +114,8 @@ package com.facebook.react.flat;
|
|||
mTop = top;
|
||||
mRight = right;
|
||||
mBottom = bottom;
|
||||
|
||||
onBoundsChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -107,6 +107,10 @@ import com.facebook.infer.annotation.Assertions;
|
|||
return ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
|
||||
}
|
||||
|
||||
/* package */ boolean isDetached() {
|
||||
return mAttachCounter == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewResult(DataSource<CloseableReference<CloseableImage>> dataSource) {
|
||||
if (!dataSource.isFinished()) {
|
||||
|
|
|
@ -43,4 +43,16 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
|||
* Returns a scale type to draw to the image with.
|
||||
*/
|
||||
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 android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.Shader;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
|
||||
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.
|
||||
* 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 int BORDER_BITMAP_PATH_DIRTY = 1 << 1;
|
||||
|
||||
private final Matrix mTransform = new Matrix();
|
||||
private ScaleType mScaleType = ImageResizeMode.defaultValue();
|
||||
private @Nullable BitmapRequestHelper mBitmapRequestHelper;
|
||||
private @Nullable PorterDuffColorFilter mColorFilter;
|
||||
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
|
||||
private @Nullable Path mPathForRoundedBitmap;
|
||||
private @Nullable BitmapShader mBitmapShader;
|
||||
private boolean mForceClip;
|
||||
|
||||
@Override
|
||||
|
@ -45,6 +51,8 @@ import com.facebook.react.views.image.ImageResizeMode;
|
|||
|
||||
@Override
|
||||
public void setImageRequest(@Nullable ImageRequest imageRequest) {
|
||||
mBitmapShader = null;
|
||||
|
||||
if (imageRequest == null) {
|
||||
mBitmapRequestHelper = null;
|
||||
} else {
|
||||
|
@ -79,14 +87,34 @@ import com.facebook.react.views.image.ImageResizeMode;
|
|||
}
|
||||
|
||||
PAINT.setColorFilter(mColorFilter);
|
||||
|
||||
if (mForceClip) {
|
||||
canvas.save();
|
||||
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
|
||||
|
@ -98,6 +126,17 @@ import com.facebook.react.views.image.ImageResizeMode;
|
|||
@Override
|
||||
public void onDetached() {
|
||||
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) {
|
||||
|
@ -140,5 +179,23 @@ import com.facebook.react.views.image.ImageResizeMode;
|
|||
|
||||
mTransform.setScale(scale, scale);
|
||||
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.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.react.uimanager.ReactProp;
|
||||
import com.facebook.react.uimanager.ViewProps;
|
||||
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() {
|
||||
if (mDrawImage.isFrozen()) {
|
||||
mDrawImage = (T) mDrawImage.mutableCopy();
|
||||
|
|
Loading…
Reference in New Issue