Update hole punch logic in HWUI
--Updated HWUI holepunch logic for SurfaceView to also apply the stretch to the hole punch --Updated RenderNode callbacks to also include an offset from the ancestor RenderNode that also has a stretch configured on it --Added new test activity to verify hole punch logic Bug: 179047472 Test: manual Change-Id: Ibbaf8248a31839ba9dc352ecb9fef54e1276918e
This commit is contained in:
@@ -146,8 +146,9 @@ public final class SurfaceControl implements Parcelable {
|
||||
private static native void nativeSetBlurRegions(long transactionObj, long nativeObj,
|
||||
float[][] regions, int length);
|
||||
private static native void nativeSetStretchEffect(long transactionObj, long nativeObj,
|
||||
float left, float top, float right, float bottom, float vecX, float vecY,
|
||||
float maxStretchAmount);
|
||||
float width, float height, float vecX, float vecY,
|
||||
float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
|
||||
float childRelativeTop, float childRelativeRight, float childRelativeBottom);
|
||||
|
||||
private static native boolean nativeClearContentFrameStats(long nativeObject);
|
||||
private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
|
||||
@@ -3038,11 +3039,14 @@ public final class SurfaceControl implements Parcelable {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public Transaction setStretchEffect(SurfaceControl sc, float left, float top, float right,
|
||||
float bottom, float vecX, float vecY, float maxStretchAmount) {
|
||||
public Transaction setStretchEffect(SurfaceControl sc, float width, float height,
|
||||
float vecX, float vecY, float maxStretchAmountX,
|
||||
float maxStretchAmountY, float childRelativeLeft, float childRelativeTop, float childRelativeRight,
|
||||
float childRelativeBottom) {
|
||||
checkPreconditions(sc);
|
||||
nativeSetStretchEffect(mNativeObject, sc.mNativeObject, left, top, right, bottom,
|
||||
vecX, vecY, maxStretchAmount);
|
||||
nativeSetStretchEffect(mNativeObject, sc.mNativeObject, width, height,
|
||||
vecX, vecY, maxStretchAmountX, maxStretchAmountY, childRelativeLeft, childRelativeTop,
|
||||
childRelativeRight, childRelativeBottom);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1471,10 +1471,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStretch(long frameNumber, float left, float top, float right,
|
||||
float bottom, float vecX, float vecY, float maxStretch) {
|
||||
mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY,
|
||||
maxStretch);
|
||||
public void applyStretch(long frameNumber, float width, float height,
|
||||
float vecX, float vecY, float maxStretchX, float maxStretchY,
|
||||
float childRelativeLeft, float childRelativeTop, float childRelativeRight,
|
||||
float childRelativeBottom) {
|
||||
mRtTransaction.setStretchEffect(mSurfaceControl, width, height, vecX, vecY,
|
||||
maxStretchX, maxStretchY, childRelativeLeft, childRelativeTop,
|
||||
childRelativeRight, childRelativeBottom);
|
||||
applyOrMergeTransaction(mRtTransaction, frameNumber);
|
||||
}
|
||||
|
||||
|
||||
@@ -641,14 +641,10 @@ public class EdgeEffect {
|
||||
boolean hasValidVectors = Float.isFinite(vecX) && Float.isFinite(vecY);
|
||||
if (right > left && bottom > top && mWidth > 0 && mHeight > 0 && hasValidVectors) {
|
||||
renderNode.stretch(
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
vecX,
|
||||
vecY,
|
||||
mWidth,
|
||||
mHeight
|
||||
vecX, // horizontal stretch intensity
|
||||
vecY, // vertical stretch intensity
|
||||
mWidth, // max horizontal stretch in pixels
|
||||
mHeight // max vertical stretch in pixels
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -626,12 +626,24 @@ static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj
|
||||
}
|
||||
|
||||
static void nativeSetStretchEffect(JNIEnv* env, jclass clazz, jlong transactionObj,
|
||||
jlong nativeObject, jfloat left, jfloat top, jfloat right,
|
||||
jfloat bottom, jfloat vecX, jfloat vecY,
|
||||
jfloat maxStretchAmount) {
|
||||
jlong nativeObject, jfloat width, jfloat height,
|
||||
jfloat vecX, jfloat vecY,
|
||||
jfloat maxStretchAmountX, jfloat maxStretchAmountY,
|
||||
jfloat childRelativeLeft, jfloat childRelativeTop,
|
||||
jfloat childRelativeRight, jfloat childRelativeBottom) {
|
||||
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
|
||||
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
|
||||
transaction->setStretchEffect(ctrl, left, top, right, bottom, vecX, vecY, maxStretchAmount);
|
||||
auto* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
|
||||
auto stretch = StretchEffect{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.vectorX = vecX,
|
||||
.vectorY = vecY,
|
||||
.maxAmountX = maxStretchAmountX,
|
||||
.maxAmountY = maxStretchAmountY,
|
||||
.mappedChildBounds = FloatRect(
|
||||
childRelativeLeft, childRelativeTop, childRelativeRight, childRelativeBottom)
|
||||
};
|
||||
transaction->setStretchEffect(ctrl, stretch);
|
||||
}
|
||||
|
||||
static void nativeSetSize(JNIEnv* env, jclass clazz, jlong transactionObj,
|
||||
@@ -1829,7 +1841,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
|
||||
(void*)nativeSetLayerStack },
|
||||
{"nativeSetBlurRegions", "(JJ[[FI)V",
|
||||
(void*)nativeSetBlurRegions },
|
||||
{"nativeSetStretchEffect", "(JJFFFFFFF)V",
|
||||
{"nativeSetStretchEffect", "(JJFFFFFFFFFF)V",
|
||||
(void*) nativeSetStretchEffect },
|
||||
{"nativeSetShadowRadius", "(JJF)V",
|
||||
(void*)nativeSetShadowRadius },
|
||||
|
||||
@@ -280,8 +280,10 @@ public final class RenderNode {
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
default void applyStretch(long frameNumber, float left, float top, float right,
|
||||
float bottom, float vecX, float vecY, float maxStretch) { }
|
||||
default void applyStretch(long frameNumber, float width, float height,
|
||||
float vecX, float vecY,
|
||||
float maxStretchX, float maxStretchY, float childRelativeLeft,
|
||||
float childRelativeTop, float childRelativeRight, float childRelativeBottom) { }
|
||||
|
||||
/**
|
||||
* Called by native on RenderThread to notify that the view is no longer in the
|
||||
@@ -326,10 +328,13 @@ public final class RenderNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
|
||||
float vecX, float vecY, float maxStretch) {
|
||||
public void applyStretch(long frameNumber, float width, float height,
|
||||
float vecX, float vecY, float maxStretchX, float maxStretchY, float childRelativeLeft,
|
||||
float childRelativeTop, float childRelativeRight, float childRelativeBottom) {
|
||||
for (PositionUpdateListener pul : mListeners) {
|
||||
pul.applyStretch(frameNumber, left, top, right, bottom, vecX, vecY, maxStretch);
|
||||
pul.applyStretch(frameNumber, width, height, vecX, vecY, maxStretchX,
|
||||
maxStretchY, childRelativeLeft, childRelativeTop, childRelativeRight,
|
||||
childRelativeBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -719,19 +724,15 @@ public final class RenderNode {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean stretch(float left, float top, float right, float bottom,
|
||||
float vecX, float vecY, float maxStretchAmountX, float maxStretchAmountY) {
|
||||
public boolean stretch(float vecX, float vecY,
|
||||
float maxStretchAmountX, float maxStretchAmountY) {
|
||||
if (Float.isInfinite(vecX) || Float.isNaN(vecX)) {
|
||||
throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX);
|
||||
}
|
||||
if (Float.isInfinite(vecY) || Float.isNaN(vecY)) {
|
||||
throw new IllegalArgumentException("vecY must be a finite, non-NaN value " + vecY);
|
||||
}
|
||||
if (top >= bottom || left >= right) {
|
||||
throw new IllegalArgumentException(
|
||||
"Stretch region must not be empty, got "
|
||||
+ new RectF(left, top, right, bottom).toString());
|
||||
}
|
||||
|
||||
if (maxStretchAmountX <= 0.0f) {
|
||||
throw new IllegalArgumentException(
|
||||
"The max horizontal stretch amount must be >0, got " + maxStretchAmountX);
|
||||
@@ -742,10 +743,6 @@ public final class RenderNode {
|
||||
}
|
||||
return nStretch(
|
||||
mNativeRenderNode,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
vecX,
|
||||
vecY,
|
||||
maxStretchAmountX,
|
||||
@@ -1701,8 +1698,8 @@ public final class RenderNode {
|
||||
private static native boolean nClearStretch(long renderNode);
|
||||
|
||||
@CriticalNative
|
||||
private static native boolean nStretch(long renderNode, float left, float top, float right,
|
||||
float bottom, float vecX, float vecY, float maxStretchX, float maxStretchY);
|
||||
private static native boolean nStretch(long renderNode, float vecX, float vecY,
|
||||
float maxStretchX, float maxStretchY);
|
||||
|
||||
@CriticalNative
|
||||
private static native boolean nHasShadow(long renderNode);
|
||||
|
||||
@@ -467,6 +467,7 @@ cc_defaults {
|
||||
"pipeline/skia/HolePunch.cpp",
|
||||
"pipeline/skia/SkiaDisplayList.cpp",
|
||||
"pipeline/skia/SkiaRecordingCanvas.cpp",
|
||||
"pipeline/skia/StretchMask.cpp",
|
||||
"pipeline/skia/RenderNodeDrawable.cpp",
|
||||
"pipeline/skia/ReorderBarrierDrawables.cpp",
|
||||
"pipeline/skia/TransformCanvas.cpp",
|
||||
|
||||
@@ -197,6 +197,27 @@ static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
|
||||
}
|
||||
}
|
||||
|
||||
static void computeTransformImpl(const DirtyStack* frame, const DirtyStack* end,
|
||||
Matrix4* outMatrix) {
|
||||
while (frame != end) {
|
||||
switch (frame->type) {
|
||||
case TransformRenderNode:
|
||||
frame->renderNode->applyViewPropertyTransforms(*outMatrix);
|
||||
break;
|
||||
case TransformMatrix4:
|
||||
outMatrix->multiply(*frame->matrix4);
|
||||
break;
|
||||
case TransformNone:
|
||||
// nothing to be done
|
||||
break;
|
||||
default:
|
||||
LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d",
|
||||
frame->type);
|
||||
}
|
||||
frame = frame->prev;
|
||||
}
|
||||
}
|
||||
|
||||
void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
|
||||
if (frame->pendingDirty.isEmpty()) {
|
||||
return;
|
||||
@@ -249,19 +270,38 @@ void DamageAccumulator::finish(SkRect* totalDirty) {
|
||||
mHead->pendingDirty.setEmpty();
|
||||
}
|
||||
|
||||
const StretchEffect* DamageAccumulator::findNearestStretchEffect() const {
|
||||
DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const {
|
||||
DirtyStack* frame = mHead;
|
||||
while (frame->prev != frame) {
|
||||
frame = frame->prev;
|
||||
if (frame->type == TransformRenderNode) {
|
||||
const auto& renderNode = frame->renderNode;
|
||||
const auto& frameRenderNodeProperties = renderNode->properties();
|
||||
const auto& effect =
|
||||
frame->renderNode->properties().layerProperties().getStretchEffect();
|
||||
frameRenderNodeProperties.layerProperties().getStretchEffect();
|
||||
const float width = (float) renderNode->getWidth();
|
||||
const float height = (float) renderNode->getHeight();
|
||||
if (!effect.isEmpty()) {
|
||||
return &effect;
|
||||
Matrix4 stretchMatrix;
|
||||
computeTransformImpl(mHead, frame, &stretchMatrix);
|
||||
Rect stretchRect = Rect(0.f, 0.f, width, height);
|
||||
stretchMatrix.mapRect(stretchRect);
|
||||
|
||||
return StretchResult{
|
||||
.stretchEffect = &effect,
|
||||
.childRelativeBounds = SkRect::MakeLTRB(
|
||||
stretchRect.left,
|
||||
stretchRect.top,
|
||||
stretchRect.right,
|
||||
stretchRect.bottom
|
||||
),
|
||||
.width = width,
|
||||
.height = height
|
||||
};
|
||||
}
|
||||
}
|
||||
frame = frame->prev;
|
||||
}
|
||||
return nullptr;
|
||||
return StretchResult{};
|
||||
}
|
||||
|
||||
} /* namespace uirenderer */
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <SkMatrix.h>
|
||||
#include <SkRect.h>
|
||||
#include <effects/StretchEffect.h>
|
||||
|
||||
#include "utils/Macros.h"
|
||||
|
||||
@@ -35,7 +36,6 @@ namespace uirenderer {
|
||||
struct DirtyStack;
|
||||
class RenderNode;
|
||||
class Matrix4;
|
||||
class StretchEffect;
|
||||
|
||||
class DamageAccumulator {
|
||||
PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
|
||||
@@ -63,7 +63,29 @@ public:
|
||||
|
||||
void finish(SkRect* totalDirty);
|
||||
|
||||
const StretchEffect* findNearestStretchEffect() const;
|
||||
struct StretchResult {
|
||||
/**
|
||||
* Stretch parameters configured on the stretch container
|
||||
*/
|
||||
const StretchEffect* stretchEffect;
|
||||
|
||||
/**
|
||||
* Bounds of the child relative to the stretch container
|
||||
*/
|
||||
const SkRect childRelativeBounds;
|
||||
|
||||
/**
|
||||
* Width of the stretch container
|
||||
*/
|
||||
const float width;
|
||||
|
||||
/**
|
||||
* Height of the stretch container
|
||||
*/
|
||||
const float height;
|
||||
};
|
||||
|
||||
[[nodiscard]] StretchResult findNearestStretchEffect() const;
|
||||
|
||||
private:
|
||||
void pushCommon();
|
||||
|
||||
@@ -194,6 +194,9 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
|
||||
SkRect dirty;
|
||||
info.damageAccumulator->peekAtDirty(&dirty);
|
||||
info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
|
||||
if (!dirty.isEmpty()) {
|
||||
mStretchMask.markDirty();
|
||||
}
|
||||
|
||||
// There might be prefetched layers that need to be accounted for.
|
||||
// That might be us, so tell CanvasContext that this layer is in the
|
||||
@@ -302,6 +305,12 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
|
||||
damageSelf(info);
|
||||
info.damageAccumulator->popTransform();
|
||||
syncProperties();
|
||||
|
||||
const StretchEffect& stagingStretch =
|
||||
mProperties.layerProperties().getStretchEffect();
|
||||
if (stagingStretch.isEmpty()) {
|
||||
mStretchMask.clear();
|
||||
}
|
||||
// We could try to be clever and only re-damage if the matrix changed.
|
||||
// However, we don't need to worry about that. The cost of over-damaging
|
||||
// here is only going to be a single additional map rect of this node
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "pipeline/skia/SkiaLayer.h"
|
||||
|
||||
#include <vector>
|
||||
#include <pipeline/skia/StretchMask.h>
|
||||
|
||||
class SkBitmap;
|
||||
class SkPaint;
|
||||
@@ -127,6 +128,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
StretchMask& getStretchMask() { return mStretchMask; }
|
||||
|
||||
VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
|
||||
|
||||
void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
|
||||
@@ -286,6 +289,7 @@ private:
|
||||
UsageHint mUsageHint = UsageHint::Unknown;
|
||||
|
||||
bool mHasHolePunches;
|
||||
StretchMask mStretchMask;
|
||||
|
||||
// METHODS & FIELDS ONLY USED BY THE SKIA RENDERER
|
||||
public:
|
||||
|
||||
@@ -189,17 +189,12 @@ static const float ZERO = 0.f;
|
||||
static const float CONTENT_DISTANCE_STRETCHED = 1.f;
|
||||
static const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
|
||||
|
||||
sk_sp<SkShader> StretchEffect::getShader(const sk_sp<SkImage>& snapshotImage) const {
|
||||
sk_sp<SkShader> StretchEffect::getShader(float width, float height,
|
||||
const sk_sp<SkImage>& snapshotImage) const {
|
||||
if (isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mStretchShader != nullptr) {
|
||||
return mStretchShader;
|
||||
}
|
||||
|
||||
float viewportWidth = stretchArea.width();
|
||||
float viewportHeight = stretchArea.height();
|
||||
float normOverScrollDistX = mStretchDirection.x();
|
||||
float normOverScrollDistY = mStretchDirection.y();
|
||||
float distanceStretchedX = CONTENT_DISTANCE_STRETCHED / (1 + abs(normOverScrollDistX));
|
||||
@@ -228,12 +223,10 @@ sk_sp<SkShader> StretchEffect::getShader(const sk_sp<SkImage>& snapshotImage) co
|
||||
mBuilder->uniform("uOverscrollY").set(&normOverScrollDistY, 1);
|
||||
mBuilder->uniform("uScrollX").set(&ZERO, 1);
|
||||
mBuilder->uniform("uScrollY").set(&ZERO, 1);
|
||||
mBuilder->uniform("viewportWidth").set(&viewportWidth, 1);
|
||||
mBuilder->uniform("viewportHeight").set(&viewportHeight, 1);
|
||||
mBuilder->uniform("viewportWidth").set(&width, 1);
|
||||
mBuilder->uniform("viewportHeight").set(&height, 1);
|
||||
|
||||
mStretchShader = mBuilder->makeShader(nullptr, false);
|
||||
|
||||
return mStretchShader;
|
||||
return mBuilder->makeShader(nullptr, false);
|
||||
}
|
||||
|
||||
sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
|
||||
|
||||
@@ -26,19 +26,15 @@
|
||||
|
||||
namespace android::uirenderer {
|
||||
|
||||
// TODO: Inherit from base RenderEffect type?
|
||||
class StretchEffect {
|
||||
public:
|
||||
enum class StretchInterpolator {
|
||||
SmoothStep,
|
||||
};
|
||||
|
||||
StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmountX,
|
||||
StretchEffect(const SkVector& direction,
|
||||
float maxStretchAmountX,
|
||||
float maxStretchAmountY)
|
||||
: stretchArea(area)
|
||||
, maxStretchAmountX(maxStretchAmountX)
|
||||
: maxStretchAmountX(maxStretchAmountX)
|
||||
, maxStretchAmountY(maxStretchAmountY)
|
||||
, mStretchDirection(direction) {}
|
||||
, mStretchDirection(direction) { }
|
||||
|
||||
StretchEffect() {}
|
||||
|
||||
@@ -51,14 +47,18 @@ public:
|
||||
}
|
||||
|
||||
StretchEffect& operator=(const StretchEffect& other) {
|
||||
this->stretchArea = other.stretchArea;
|
||||
this->mStretchDirection = other.mStretchDirection;
|
||||
this->mStretchShader = other.mStretchShader;
|
||||
this->maxStretchAmountX = other.maxStretchAmountX;
|
||||
this->maxStretchAmountY = other.maxStretchAmountY;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(const StretchEffect& other) const {
|
||||
return mStretchDirection == other.mStretchDirection &&
|
||||
maxStretchAmountX == other.maxStretchAmountX &&
|
||||
maxStretchAmountY == other.maxStretchAmountY;
|
||||
}
|
||||
|
||||
void mergeWith(const StretchEffect& other) {
|
||||
if (other.isEmpty()) {
|
||||
return;
|
||||
@@ -67,33 +67,26 @@ public:
|
||||
*this = other;
|
||||
return;
|
||||
}
|
||||
setStretchDirection(mStretchDirection + other.mStretchDirection);
|
||||
mStretchDirection += other.mStretchDirection;
|
||||
if (isEmpty()) {
|
||||
return setEmpty();
|
||||
}
|
||||
stretchArea.join(other.stretchArea);
|
||||
maxStretchAmountX = std::max(maxStretchAmountX, other.maxStretchAmountX);
|
||||
maxStretchAmountY = std::max(maxStretchAmountY, other.maxStretchAmountY);
|
||||
}
|
||||
|
||||
sk_sp<SkShader> getShader(const sk_sp<SkImage>& snapshotImage) const;
|
||||
sk_sp<SkShader> getShader(float width, float height,
|
||||
const sk_sp<SkImage>& snapshotImage) const;
|
||||
|
||||
SkRect stretchArea {0, 0, 0, 0};
|
||||
float maxStretchAmountX = 0;
|
||||
float maxStretchAmountY = 0;
|
||||
|
||||
void setStretchDirection(const SkVector& direction) {
|
||||
mStretchShader = nullptr;
|
||||
mStretchDirection = direction;
|
||||
}
|
||||
|
||||
const SkVector getStretchDirection() const { return mStretchDirection; }
|
||||
|
||||
private:
|
||||
static sk_sp<SkRuntimeEffect> getStretchEffect();
|
||||
mutable SkVector mStretchDirection{0, 0};
|
||||
mutable std::unique_ptr<SkRuntimeShaderBuilder> mBuilder;
|
||||
mutable sk_sp<SkShader> mStretchShader;
|
||||
};
|
||||
|
||||
} // namespace android::uirenderer
|
||||
|
||||
@@ -180,12 +180,10 @@ static jboolean android_view_RenderNode_clearStretch(CRITICAL_JNI_PARAMS_COMMA j
|
||||
}
|
||||
|
||||
static jboolean android_view_RenderNode_stretch(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
|
||||
jfloat left, jfloat top, jfloat right,
|
||||
jfloat bottom, jfloat vX, jfloat vY, jfloat maxX,
|
||||
jfloat vX, jfloat vY, jfloat maxX,
|
||||
jfloat maxY) {
|
||||
StretchEffect effect = StretchEffect(SkRect::MakeLTRB(left, top, right, bottom),
|
||||
{.fX = vX, .fY = vY}, maxX, maxY);
|
||||
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
|
||||
auto* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
|
||||
StretchEffect effect = StretchEffect({.fX = vX, .fY = vY}, maxX, maxY);
|
||||
renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
|
||||
effect);
|
||||
renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
|
||||
@@ -643,13 +641,15 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
|
||||
|
||||
void handleStretchEffect(const TreeInfo& info, const Matrix4& transform) {
|
||||
// Search up to find the nearest stretcheffect parent
|
||||
const StretchEffect* effect = info.damageAccumulator->findNearestStretchEffect();
|
||||
const DamageAccumulator::StretchResult result =
|
||||
info.damageAccumulator->findNearestStretchEffect();
|
||||
const StretchEffect* effect = result.stretchEffect;
|
||||
if (!effect) {
|
||||
return;
|
||||
}
|
||||
|
||||
uirenderer::Rect area = effect->stretchArea;
|
||||
transform.mapRect(area);
|
||||
const auto& childRelativeBounds = result.childRelativeBounds;
|
||||
|
||||
JNIEnv* env = jnienv();
|
||||
|
||||
jobject localref = env->NewLocalRef(mWeakRef);
|
||||
@@ -661,9 +661,17 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
|
||||
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
|
||||
SkVector stretchDirection = effect->getStretchDirection();
|
||||
env->CallVoidMethod(localref, gPositionListener_ApplyStretchMethod,
|
||||
info.canvasContext.getFrameNumber(), area.left, area.top,
|
||||
area.right, area.bottom, stretchDirection.fX, stretchDirection.fY,
|
||||
effect->maxStretchAmountX, effect->maxStretchAmountY);
|
||||
info.canvasContext.getFrameNumber(),
|
||||
result.width,
|
||||
result.height,
|
||||
stretchDirection.fX,
|
||||
stretchDirection.fY,
|
||||
effect->maxStretchAmountX,
|
||||
effect->maxStretchAmountY,
|
||||
childRelativeBounds.left(),
|
||||
childRelativeBounds.top(),
|
||||
childRelativeBounds.right(),
|
||||
childRelativeBounds.bottom());
|
||||
#endif
|
||||
env->DeleteLocalRef(localref);
|
||||
}
|
||||
@@ -739,7 +747,7 @@ static const JNINativeMethod gMethods[] = {
|
||||
{"nSetOutlineEmpty", "(J)Z", (void*)android_view_RenderNode_setOutlineEmpty},
|
||||
{"nSetOutlineNone", "(J)Z", (void*)android_view_RenderNode_setOutlineNone},
|
||||
{"nClearStretch", "(J)Z", (void*)android_view_RenderNode_clearStretch},
|
||||
{"nStretch", "(JFFFFFFFF)Z", (void*)android_view_RenderNode_stretch},
|
||||
{"nStretch", "(JFFFF)Z", (void*)android_view_RenderNode_stretch},
|
||||
{"nHasShadow", "(J)Z", (void*)android_view_RenderNode_hasShadow},
|
||||
{"nSetSpotShadowColor", "(JI)Z", (void*)android_view_RenderNode_setSpotShadowColor},
|
||||
{"nGetSpotShadowColor", "(J)I", (void*)android_view_RenderNode_getSpotShadowColor},
|
||||
@@ -814,7 +822,7 @@ int register_android_view_RenderNode(JNIEnv* env) {
|
||||
gPositionListener_PositionChangedMethod = GetMethodIDOrDie(env, clazz,
|
||||
"positionChanged", "(JIIII)V");
|
||||
gPositionListener_ApplyStretchMethod =
|
||||
GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFF)V");
|
||||
GetMethodIDOrDie(env, clazz, "applyStretch", "(JFFFFFFFFFF)V");
|
||||
gPositionListener_PositionLostMethod = GetMethodIDOrDie(env, clazz,
|
||||
"positionLost", "(J)V");
|
||||
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "RenderNodeDrawable.h"
|
||||
#include <SkPaintFilterCanvas.h>
|
||||
#include "StretchMask.h"
|
||||
#include "RenderNode.h"
|
||||
#include "SkiaDisplayList.h"
|
||||
#include "TransformCanvas.h"
|
||||
@@ -245,17 +246,37 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
|
||||
"SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
|
||||
}
|
||||
|
||||
if (renderNode->hasHolePunches()) {
|
||||
TransformCanvas transformCanvas(canvas);
|
||||
displayList->draw(&transformCanvas);
|
||||
}
|
||||
|
||||
const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
|
||||
if (stretch.isEmpty()) {
|
||||
// If we don't have any stretch effects, issue the filtered
|
||||
// canvas draw calls to make sure we still punch a hole
|
||||
// with the same canvas transformation + clip into the target
|
||||
// canvas then draw the layer on top
|
||||
if (renderNode->hasHolePunches()) {
|
||||
TransformCanvas transformCanvas(canvas, SkBlendMode::kClear);
|
||||
displayList->draw(&transformCanvas);
|
||||
}
|
||||
canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint,
|
||||
SkCanvas::kStrict_SrcRectConstraint);
|
||||
} else {
|
||||
sk_sp<SkShader> stretchShader = stretch.getShader(snapshotImage);
|
||||
// If we do have stretch effects and have hole punches,
|
||||
// then create a mask and issue the filtered draw calls to
|
||||
// get the corresponding hole punches.
|
||||
// Then apply the stretch to the mask and draw the mask to
|
||||
// the destination
|
||||
if (renderNode->hasHolePunches()) {
|
||||
GrRecordingContext* context = canvas->recordingContext();
|
||||
StretchMask& stretchMask = renderNode->getStretchMask();
|
||||
stretchMask.draw(context,
|
||||
stretch,
|
||||
bounds,
|
||||
displayList,
|
||||
canvas);
|
||||
}
|
||||
|
||||
sk_sp<SkShader> stretchShader = stretch.getShader(bounds.width(),
|
||||
bounds.height(),
|
||||
snapshotImage);
|
||||
paint.setShader(stretchShader);
|
||||
canvas->drawRect(bounds, paint);
|
||||
}
|
||||
|
||||
64
libs/hwui/pipeline/skia/StretchMask.cpp
Normal file
64
libs/hwui/pipeline/skia/StretchMask.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "StretchMask.h"
|
||||
#include "SkSurface.h"
|
||||
#include "SkCanvas.h"
|
||||
#include "TransformCanvas.h"
|
||||
#include "SkiaDisplayList.h"
|
||||
|
||||
using android::uirenderer::StretchMask;
|
||||
|
||||
void StretchMask::draw(GrRecordingContext* context,
|
||||
const StretchEffect& stretch,
|
||||
const SkRect& bounds,
|
||||
skiapipeline::SkiaDisplayList* displayList,
|
||||
SkCanvas* canvas) {
|
||||
float width = bounds.width();
|
||||
float height = bounds.height();
|
||||
if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
|
||||
mMaskSurface->height() != height) {
|
||||
// Create a new surface if we don't have one or our existing size does
|
||||
// not match.
|
||||
mMaskSurface = SkSurface::MakeRenderTarget(
|
||||
context,
|
||||
SkBudgeted::kYes,
|
||||
SkImageInfo::Make(
|
||||
width,
|
||||
height,
|
||||
SkColorType::kAlpha_8_SkColorType,
|
||||
SkAlphaType::kPremul_SkAlphaType)
|
||||
);
|
||||
mIsDirty = true;
|
||||
}
|
||||
|
||||
if (mIsDirty) {
|
||||
SkCanvas* maskCanvas = mMaskSurface->getCanvas();
|
||||
maskCanvas->drawColor(0, SkBlendMode::kClear);
|
||||
TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
|
||||
displayList->draw(&transformCanvas);
|
||||
}
|
||||
|
||||
sk_sp<SkImage> maskImage = mMaskSurface->makeImageSnapshot();
|
||||
sk_sp<SkShader> maskStretchShader = stretch.getShader(
|
||||
width, height, maskImage);
|
||||
|
||||
SkPaint maskPaint;
|
||||
maskPaint.setShader(maskStretchShader);
|
||||
maskPaint.setBlendMode(SkBlendMode::kDstOut);
|
||||
canvas->drawRect(bounds, maskPaint);
|
||||
|
||||
mIsDirty = false;
|
||||
}
|
||||
62
libs/hwui/pipeline/skia/StretchMask.h
Normal file
62
libs/hwui/pipeline/skia/StretchMask.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GrRecordingContext.h"
|
||||
#include <effects/StretchEffect.h>
|
||||
#include <SkSurface.h>
|
||||
#include "SkiaDisplayList.h"
|
||||
|
||||
namespace android::uirenderer {
|
||||
|
||||
/**
|
||||
* Helper class used to create/cache an SkSurface instance
|
||||
* to create a mask that is used to draw a stretched hole punch
|
||||
*/
|
||||
class StretchMask {
|
||||
public:
|
||||
/**
|
||||
* Release the current surface used for the stretch mask
|
||||
*/
|
||||
void clear() {
|
||||
mMaskSurface = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the dirty flag to re-create the stretch mask on the next draw
|
||||
* pass
|
||||
*/
|
||||
void markDirty() {
|
||||
mIsDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the stretch mask into the given target canvas
|
||||
* @param context GrRecordingContext used to create the surface if necessary
|
||||
* @param stretch StretchEffect to apply to the mask
|
||||
* @param bounds Target bounds to draw into the given canvas
|
||||
* @param displayList List of drawing commands to render into the stretch mask
|
||||
* @param canvas Target canvas to draw the mask into
|
||||
*/
|
||||
void draw(GrRecordingContext* context,
|
||||
const StretchEffect& stretch, const SkRect& bounds,
|
||||
skiapipeline::SkiaDisplayList* displayList, SkCanvas* canvas);
|
||||
private:
|
||||
sk_sp<SkSurface> mMaskSurface;
|
||||
bool mIsDirty = true;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -28,8 +28,8 @@ void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkDa
|
||||
SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY);
|
||||
|
||||
SkPaint paint;
|
||||
paint.setColor(0);
|
||||
paint.setBlendMode(SkBlendMode::kClear);
|
||||
paint.setColor(SkColors::kBlack);
|
||||
paint.setBlendMode(mHolePunchBlendMode);
|
||||
mWrappedCanvas->drawRRect(roundRect, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
#include <include/core/SkCanvas.h>
|
||||
#include "SkPaintFilterCanvas.h"
|
||||
#include <effects/StretchEffect.h>
|
||||
|
||||
class TransformCanvas : public SkPaintFilterCanvas {
|
||||
public:
|
||||
TransformCanvas(SkCanvas* target) : SkPaintFilterCanvas(target), mWrappedCanvas(target) {}
|
||||
TransformCanvas(SkCanvas* target, SkBlendMode blendmode) :
|
||||
SkPaintFilterCanvas(target), mWrappedCanvas(target), mHolePunchBlendMode(blendmode) {}
|
||||
|
||||
protected:
|
||||
bool onFilter(SkPaint& paint) const override;
|
||||
@@ -32,4 +34,5 @@ protected:
|
||||
private:
|
||||
// We don't own the canvas so just maintain a raw pointer to it
|
||||
SkCanvas* mWrappedCanvas;
|
||||
const SkBlendMode mHolePunchBlendMode;
|
||||
};
|
||||
|
||||
@@ -409,6 +409,15 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="ScrollingStretchSurfaceViewActivity"
|
||||
android:label="SurfaceView/Scrolling Stretched SurfaceView"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="com.android.test.hwui.TEST"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="GetBitmapSurfaceViewActivity"
|
||||
android:label="SurfaceView/GetBitmap with Camera source"
|
||||
android:exported="true">
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2021 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:overScrollMode="always"
|
||||
android:fillViewport="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginTop="100dp"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/vertical_imageview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"/>
|
||||
<FrameLayout
|
||||
android:id="@+id/vertical_surfaceview_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:overScrollMode="always"
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="#FF0000"
|
||||
android:layout_weight="1"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="400dp"
|
||||
android:layout_height="400dp"
|
||||
android:layout_marginLeft="100dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/horizontal_imageview"
|
||||
android:layout_width="100dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="0dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/horizontal_surfaceview_container"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -74,12 +74,10 @@ public class PositionListenerActivity extends Activity {
|
||||
float maxStretchAmount = 100f;
|
||||
// Although we could do this in a single call, the real one won't be - so mimic that
|
||||
if (dir.x != 0f) {
|
||||
node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
|
||||
dir.x, 0f, maxStretchAmount, maxStretchAmount);
|
||||
node.stretch(dir.x, 0f, maxStretchAmount, maxStretchAmount);
|
||||
}
|
||||
if (dir.y != 0f) {
|
||||
node.stretch(0f, 0f, (float) getWidth(), (float) getHeight(),
|
||||
0f, dir.y, maxStretchAmount, maxStretchAmount);
|
||||
node.stretch(0f, dir.y, maxStretchAmount, maxStretchAmount);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -94,10 +92,13 @@ public class PositionListenerActivity extends Activity {
|
||||
int mCurrentCount = 0;
|
||||
int mTranslateY = 0;
|
||||
Rect mPosition = new Rect();
|
||||
RectF mStretchArea = new RectF();
|
||||
float mWidth = 0f;
|
||||
float mHeight = 0f;
|
||||
RectF mMappedBounds = new RectF();
|
||||
float mStretchX = 0.0f;
|
||||
float mStretchY = 0.0f;
|
||||
float mStretchMax = 0.0f;
|
||||
float mStretchMaxX = 0.0f;
|
||||
float mStretchMaxY = 0.0f;
|
||||
|
||||
MyPositionReporter(Context c) {
|
||||
super(c);
|
||||
@@ -128,9 +129,12 @@ public class PositionListenerActivity extends Activity {
|
||||
}
|
||||
|
||||
void updateText() {
|
||||
setText(String.format("%d: Position %s, stretch area %s, vec %f,%f, amount %f",
|
||||
mCurrentCount, mPosition.toShortString(), mStretchArea.toShortString(),
|
||||
mStretchX, mStretchY, mStretchMax));
|
||||
String posText =
|
||||
"%d: Position %s, stretch width %f, height %f, vec %f,%f, amountX %f amountY %f mappedBounds %s";
|
||||
setText(String.format(posText,
|
||||
mCurrentCount, mPosition.toShortString(), mWidth, mHeight,
|
||||
mStretchX, mStretchY, mStretchMaxX, mStretchMaxY,
|
||||
mMappedBounds.toShortString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,13 +147,19 @@ public class PositionListenerActivity extends Activity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStretch(long frameNumber, float left, float top, float right, float bottom,
|
||||
float vecX, float vecY, float maxStretch) {
|
||||
public void applyStretch(long frameNumber, float width, float height,
|
||||
float vecX, float vecY,
|
||||
float maxStretchX, float maxStretchY, float childRelativeLeft,
|
||||
float childRelativeTop, float childRelativeRight, float childRelativeBottom) {
|
||||
getHandler().postAtFrontOfQueue(() -> {
|
||||
mStretchArea.set(left, top, right, bottom);
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mStretchX = vecX;
|
||||
mStretchY = vecY;
|
||||
mStretchMax = maxStretch;
|
||||
mStretchMaxX = maxStretchX;
|
||||
mStretchMaxY = maxStretchY;
|
||||
mMappedBounds.set(childRelativeLeft, childRelativeTop, childRelativeRight,
|
||||
childRelativeBottom);
|
||||
updateText();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.test.hwui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceHolder.Callback;
|
||||
import android.view.SurfaceView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
public class ScrollingStretchSurfaceViewActivity extends Activity implements Callback {
|
||||
|
||||
SurfaceView mVerticalSurfaceView;
|
||||
SurfaceView mHorizontalSurfaceView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.scrolling_stretch_surfaceview);
|
||||
|
||||
mVerticalSurfaceView = new SurfaceView(this);
|
||||
mVerticalSurfaceView.getHolder().addCallback(this);
|
||||
|
||||
mHorizontalSurfaceView = new SurfaceView(this);
|
||||
mHorizontalSurfaceView.getHolder().addCallback(this);
|
||||
|
||||
FrameLayout verticalContainer = findViewById(R.id.vertical_surfaceview_container);
|
||||
verticalContainer.addView(mVerticalSurfaceView,
|
||||
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
|
||||
FrameLayout horizontalContainer = findViewById(R.id.horizontal_surfaceview_container);
|
||||
horizontalContainer.addView(mHorizontalSurfaceView,
|
||||
new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
|
||||
ImageView verticalImageView = findViewById(R.id.vertical_imageview);
|
||||
verticalImageView.setImageDrawable(new LineDrawable());
|
||||
|
||||
ImageView horizontalImageView = findViewById(R.id.horizontal_imageview);
|
||||
horizontalImageView.setImageDrawable(new LineDrawable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
Canvas canvas = holder.lockCanvas();
|
||||
|
||||
drawLine(canvas, width, height);
|
||||
holder.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
|
||||
private static void drawLine(Canvas canvas, int width, int height) {
|
||||
canvas.drawColor(Color.GRAY);
|
||||
|
||||
Paint paint = new Paint();
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(Color.GREEN);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
paint.setStrokeWidth(10f);
|
||||
canvas.drawLine(0, 0, width, height, paint);
|
||||
}
|
||||
|
||||
private static class LineDrawable extends Drawable {
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
drawLine(canvas, getBounds().width(), getBounds().height());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
}
|
||||
}
|
||||
@@ -409,10 +409,6 @@ public class StretchShaderActivity extends Activity {
|
||||
if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) {
|
||||
Rect bounds = getBounds();
|
||||
((RecordingCanvas) canvas).mNode.stretch(
|
||||
0,
|
||||
0,
|
||||
bounds.width(),
|
||||
bounds.height(),
|
||||
mOverScrollX,
|
||||
mOverScrollY,
|
||||
mStretchDistance,
|
||||
|
||||
@@ -99,7 +99,7 @@ public class StretchySurfaceViewActivity extends Activity implements Callback {
|
||||
super.onDraw(canvas);
|
||||
|
||||
RenderNode node = ((RecordingCanvas) canvas).mNode;
|
||||
node.stretch(0f, 0f, getWidth(), getHeight() / 2f, 0f,
|
||||
node.stretch(0f,
|
||||
1f, 400f, 400f);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user