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:
Denis Koroskin 2015-12-12 14:48:34 -08:00 committed by Ahmed El-Helw
parent 760422525e
commit 71ca522c68
6 changed files with 239 additions and 5 deletions

View File

@ -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;
}
}

View File

@ -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();
}
/**

View File

@ -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()) {

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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();