Merge "Wire SKSL based stretch shader to HWUI" into sc-dev

This commit is contained in:
Nader Jawad
2021-03-05 03:14:55 +00:00
committed by Android (Google) Code Review
14 changed files with 1150 additions and 139 deletions

View File

@@ -22188,9 +22188,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
// Clear the overscroll effect:
// TODO: Use internal API instead of overriding the existing RenderEffect
setRenderEffect(null);
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
/* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList.

View File

@@ -29,7 +29,6 @@ import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderEffect;
import android.graphics.RenderNode;
import android.os.Build;
import android.util.AttributeSet;
@@ -83,6 +82,8 @@ public class EdgeEffect {
public @interface EdgeEffectType {
}
private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f;
@SuppressWarnings("UnusedDeclaration")
private static final String TAG = "EdgeEffect";
@@ -128,6 +129,8 @@ public class EdgeEffect {
private long mStartTime;
private float mDuration;
private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY;
private float mStretchDistance = -1f;
private final Interpolator mInterpolator;
@@ -146,6 +149,8 @@ public class EdgeEffect {
private float mPullDistance;
private final Rect mBounds = new Rect();
private float mWidth;
private float mHeight;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123769450)
private final Paint mPaint = new Paint();
private float mRadius;
@@ -202,6 +207,19 @@ public class EdgeEffect {
mBaseGlowScale = h > 0 ? Math.min(oh / h, 1.f) : 1.f;
mBounds.set(mBounds.left, mBounds.top, width, (int) Math.min(height, h));
mWidth = width;
mHeight = height;
}
/**
* Configure the distance in pixels to stretch the content. This is only consumed as part
* if {@link #setType(int)} is set to {@link #TYPE_STRETCH}
* @param stretchDistance Stretch distance in pixels when the target View is overscrolled
* @hide
*/
public void setStretchDistance(float stretchDistance) {
mStretchDistance = stretchDistance;
}
/**
@@ -436,6 +454,13 @@ public class EdgeEffect {
mEdgeEffectType = type;
}
/**
* @hide
*/
public void setMaxStretchIntensity(float stretchIntensity) {
mStretchIntensity = stretchIntensity;
}
/**
* Set or clear the blend mode. A blend mode defines how source pixels
* (generated by a drawing command) are composited with the destination pixels
@@ -520,23 +545,55 @@ public class EdgeEffect {
RecordingCanvas recordingCanvas = (RecordingCanvas) canvas;
if (mTmpMatrix == null) {
mTmpMatrix = new Matrix();
mTmpPoints = new float[4];
mTmpPoints = new float[12];
}
//noinspection deprecation
recordingCanvas.getMatrix(mTmpMatrix);
mTmpPoints[0] = mBounds.width() * mDisplacement;
mTmpPoints[1] = mDistance * mBounds.height();
mTmpPoints[2] = mTmpPoints[0];
mTmpPoints[3] = 0;
mTmpPoints[0] = 0;
mTmpPoints[1] = 0; // top-left
mTmpPoints[2] = mWidth;
mTmpPoints[3] = 0; // top-right
mTmpPoints[4] = mWidth;
mTmpPoints[5] = mHeight; // bottom-right
mTmpPoints[6] = 0;
mTmpPoints[7] = mHeight; // bottom-left
mTmpPoints[8] = mWidth * mDisplacement;
mTmpPoints[9] = 0; // drag start point
mTmpPoints[10] = mWidth * mDisplacement;
mTmpPoints[11] = mHeight * mDistance; // drag point
mTmpMatrix.mapPoints(mTmpPoints);
float x = mTmpPoints[0] - mTmpPoints[2];
float y = mTmpPoints[1] - mTmpPoints[3];
RenderNode renderNode = recordingCanvas.mNode;
// TODO: use stretchy RenderEffect and use internal API when it is ready
// TODO: wrap existing RenderEffect
renderNode.setRenderEffect(RenderEffect.createOffsetEffect(x, y));
float left = renderNode.getLeft()
+ min(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]);
float top = renderNode.getTop()
+ min(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]);
float right = renderNode.getLeft()
+ max(mTmpPoints[0], mTmpPoints[2], mTmpPoints[4], mTmpPoints[6]);
float bottom = renderNode.getTop()
+ max(mTmpPoints[1], mTmpPoints[3], mTmpPoints[5], mTmpPoints[7]);
// assume rotations of increments of 90 degrees
float x = mTmpPoints[10] - mTmpPoints[8];
float width = right - left;
float vecX = Math.max(-1f, Math.min(1f, x / width));
float y = mTmpPoints[11] - mTmpPoints[9];
float height = bottom - top;
float vecY = Math.max(-1f, Math.min(1f, y / height));
renderNode.stretch(
left,
top,
right,
bottom,
vecX * mStretchIntensity,
vecY * mStretchIntensity,
// TODO (njawad/mount) figure out proper stretch distance from UX
// for now leverage placeholder logic if no stretch distance is provided to
// consume the displacement ratio times the minimum of the width or height
mStretchDistance > 0 ? mStretchDistance :
(mDisplacement * Math.min(mWidth, mHeight))
);
}
boolean oneLastFrame = false;
@@ -548,6 +605,18 @@ public class EdgeEffect {
return mState != STATE_IDLE || oneLastFrame;
}
private float min(float f1, float f2, float f3, float f4) {
float min = Math.min(f1, f2);
min = Math.min(min, f3);
return Math.min(min, f4);
}
private float max(float f1, float f2, float f3, float f4) {
float max = Math.max(f1, f2);
max = Math.max(max, f3);
return Math.max(max, f4);
}
/**
* Return the maximum height that the edge effect will be drawn at given the original
* {@link #setSize(int, int) input size}.

View File

@@ -248,6 +248,26 @@ public class HorizontalScrollView extends FrameLayout {
setRightEdgeEffectColor(color);
}
/**
* API used for prototyping stretch effect parameters in framework sample apps
* @hide
*/
public void setEdgeEffectIntensity(float intensity) {
mEdgeGlowLeft.setMaxStretchIntensity(intensity);
mEdgeGlowRight.setMaxStretchIntensity(intensity);
invalidate();
}
/**
* API used for prototyping stretch effect parameters in the framework sample apps
* @hide
*/
public void setStretchDistance(float distance) {
mEdgeGlowLeft.setStretchDistance(distance);
mEdgeGlowRight.setStretchDistance(distance);
invalidate();
}
/**
* Sets the right edge effect color.
*

View File

@@ -280,6 +280,26 @@ public class ScrollView extends FrameLayout {
setBottomEdgeEffectColor(color);
}
/**
* API used for prototyping stretch effect parameters in framework sample apps
* @hide
*/
public void setEdgeEffectIntensity(float intensity) {
mEdgeGlowTop.setMaxStretchIntensity(intensity);
mEdgeGlowBottom.setMaxStretchIntensity(intensity);
invalidate();
}
/**
* API used for prototyping stretch effect parameters in the framework sample apps
* @hide
*/
public void setStretchDistance(float distance) {
mEdgeGlowTop.setStretchDistance(distance);
mEdgeGlowBottom.setStretchDistance(distance);
invalidate();
}
/**
* Sets the bottom edge effect color.
*

View File

@@ -719,11 +719,11 @@ public final class RenderNode {
/** @hide */
public boolean stretch(float left, float top, float right, float bottom,
float vecX, float vecY, float maxStretchAmount) {
if (1.0 < vecX || vecX < -1.0) {
throw new IllegalArgumentException("vecX must be in the range [-1, 1], was " + vecX);
if (Float.isInfinite(vecX) || Float.isNaN(vecX)) {
throw new IllegalArgumentException("vecX must be a finite, non-NaN value " + vecX);
}
if (1.0 < vecY || vecY < -1.0) {
throw new IllegalArgumentException("vecY must be in the range [-1, 1], was " + vecY);
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(
@@ -734,7 +734,16 @@ public final class RenderNode {
throw new IllegalArgumentException(
"The max stretch amount must be >0, got " + maxStretchAmount);
}
return nStretch(mNativeRenderNode, left, top, right, bottom, vecX, vecY, maxStretchAmount);
return nStretch(
mNativeRenderNode,
left,
top,
right,
bottom,
vecX,
vecY,
maxStretchAmount
);
}
/**

View File

@@ -552,8 +552,8 @@ public:
bool promotedToLayer() const {
return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
(mComputedFields.mNeedLayerForFunctors ||
mLayerProperties.mImageFilter != nullptr ||
(mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr ||
!mLayerProperties.getStretchEffect().isEmpty() ||
(!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
mPrimitiveFields.mHasOverlappingRendering));
}

View File

@@ -15,13 +15,195 @@
*/
#include "StretchEffect.h"
#include <SkImageFilter.h>
#include <SkRefCnt.h>
#include <SkRuntimeEffect.h>
#include <SkString.h>
#include <SkSurface.h>
#include <include/effects/SkImageFilters.h>
#include <memory>
namespace android::uirenderer {
sk_sp<SkImageFilter> StretchEffect::getImageFilter() const {
// TODO: Implement & Cache
// Probably need to use mutable to achieve caching
return nullptr;
static const SkString stretchShader = SkString(R"(
uniform shader uContentTexture;
// multiplier to apply to scale effect
uniform float uMaxStretchIntensity;
// Maximum percentage to stretch beyond bounds of target
uniform float uStretchAffectedDist;
// Distance stretched as a function of the normalized overscroll times
// scale intensity
uniform float uDistanceStretchedX;
uniform float uDistanceStretchedY;
uniform float uDistDiffX;
// Difference between the peak stretch amount and overscroll amount normalized
uniform float uDistDiffY;
// Horizontal offset represented as a ratio of pixels divided by the target width
uniform float uScrollX;
// Vertical offset represented as a ratio of pixels divided by the target height
uniform float uScrollY;
// Normalized overscroll amount in the horizontal direction
uniform float uOverscrollX;
// Normalized overscroll amount in the vertical direction
uniform float uOverscrollY;
uniform float viewportWidth; // target height in pixels
uniform float viewportHeight; // target width in pixels
void computeOverscrollStart(
out float outPos,
float inPos,
float overscroll,
float uStretchAffectedDist,
float distanceStretched
) {
float offsetPos = uStretchAffectedDist - inPos;
float posBasedVariation = smoothstep(0., uStretchAffectedDist, offsetPos);
float stretchIntensity = overscroll * posBasedVariation;
outPos = distanceStretched - (offsetPos / (1. + stretchIntensity));
}
void computeOverscrollEnd(
out float outPos,
float inPos,
float overscroll,
float reverseStretchDist,
float uStretchAffectedDist,
float distanceStretched
) {
float offsetPos = inPos - reverseStretchDist;
float posBasedVariation = (smoothstep(0., uStretchAffectedDist, offsetPos));
float stretchIntensity = (-overscroll) * posBasedVariation;
outPos = 1 - (distanceStretched - (offsetPos / (1. + stretchIntensity)));
}
void computeOverscroll(
out float outPos,
float inPos,
float overscroll,
float uStretchAffectedDist,
float distanceStretched,
float distanceDiff
) {
if (overscroll > 0) {
if (inPos <= uStretchAffectedDist) {
computeOverscrollStart(
outPos,
inPos,
overscroll,
uStretchAffectedDist,
distanceStretched
);
} else if (inPos >= distanceStretched) {
outPos = distanceDiff + inPos;
}
}
if (overscroll < 0) {
float stretchAffectedDist = 1. - uStretchAffectedDist;
if (inPos >= stretchAffectedDist) {
computeOverscrollEnd(
outPos,
inPos,
overscroll,
stretchAffectedDist,
uStretchAffectedDist,
distanceStretched
);
} else if (inPos < stretchAffectedDist) {
outPos = -distanceDiff + inPos;
}
}
}
vec4 main(vec2 coord) {
// Normalize SKSL pixel coordinate into a unit vector
float inU = coord.x / viewportWidth;
float inV = coord.y / viewportHeight;
float outU;
float outV;
float stretchIntensity;
// Add the normalized scroll position within scrolling list
inU += uScrollX;
inV += uScrollY;
outU = inU;
outV = inV;
computeOverscroll(
outU,
inU,
uOverscrollX,
uStretchAffectedDist,
uDistanceStretchedX,
uDistDiffX
);
computeOverscroll(
outV,
inV,
uOverscrollY,
uStretchAffectedDist,
uDistanceStretchedY,
uDistDiffY
);
coord.x = outU * viewportWidth;
coord.y = outV * viewportHeight;
return sample(uContentTexture, coord);
})");
static const float ZERO = 0.f;
sk_sp<SkImageFilter> StretchEffect::getImageFilter(const sk_sp<SkImage>& snapshotImage) const {
if (isEmpty()) {
return nullptr;
}
if (mStretchFilter != nullptr) {
return mStretchFilter;
}
float distanceNotStretchedX = maxStretchAmount / stretchArea.width();
float distanceNotStretchedY = maxStretchAmount / stretchArea.height();
float normOverScrollDistX = mStretchDirection.x();
float normOverScrollDistY = mStretchDirection.y();
float distanceStretchedX = maxStretchAmount / (1 + abs(normOverScrollDistX));
float distanceStretchedY = maxStretchAmount / (1 + abs(normOverScrollDistY));
float diffX = distanceStretchedX - distanceNotStretchedX;
float diffY = distanceStretchedY - distanceNotStretchedY;
float viewportWidth = stretchArea.width();
float viewportHeight = stretchArea.height();
if (mBuilder == nullptr) {
mBuilder = std::make_unique<SkRuntimeShaderBuilder>(getStretchEffect());
}
mBuilder->child("uContentTexture") = snapshotImage->makeShader(
SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions(SkFilterMode::kLinear));
mBuilder->uniform("uStretchAffectedDist").set(&maxStretchAmount, 1);
mBuilder->uniform("uDistanceStretchedX").set(&distanceStretchedX, 1);
mBuilder->uniform("uDistanceStretchedY").set(&distanceStretchedY, 1);
mBuilder->uniform("uDistDiffX").set(&diffX, 1);
mBuilder->uniform("uDistDiffY").set(&diffY, 1);
mBuilder->uniform("uOverscrollX").set(&normOverScrollDistX, 1);
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);
mStretchFilter = SkImageFilters::Shader(mBuilder->makeShader(nullptr, false),
SkRect{0, 0, viewportWidth, viewportHeight});
return mStretchFilter;
}
sk_sp<SkRuntimeEffect> StretchEffect::getStretchEffect() {
const static SkRuntimeEffect::Result instance = SkRuntimeEffect::Make(stretchShader);
return instance.effect;
}
} // namespace android::uirenderer

View File

@@ -18,9 +18,11 @@
#include "utils/MathUtils.h"
#include <SkImage.h>
#include <SkImageFilter.h>
#include <SkPoint.h>
#include <SkRect.h>
#include <SkImageFilter.h>
#include <SkRuntimeEffect.h>
namespace android::uirenderer {
@@ -31,15 +33,27 @@ public:
SmoothStep,
};
StretchEffect(const SkRect& area, const SkVector& direction, float maxStretchAmount)
: stretchArea(area), maxStretchAmount(maxStretchAmount), mStretchDirection(direction) {}
StretchEffect() {}
bool isEmpty() const {
return MathUtils::isZero(stretchDirection.x())
&& MathUtils::isZero(stretchDirection.y());
return MathUtils::isZero(mStretchDirection.x()) && MathUtils::isZero(mStretchDirection.y());
}
void setEmpty() {
*this = StretchEffect{};
}
StretchEffect& operator=(const StretchEffect& other) {
this->stretchArea = other.stretchArea;
this->mStretchDirection = other.mStretchDirection;
this->mStretchFilter = nullptr;
this->maxStretchAmount = other.maxStretchAmount;
return *this;
}
void mergeWith(const StretchEffect& other) {
if (other.isEmpty()) {
return;
@@ -48,7 +62,7 @@ public:
*this = other;
return;
}
stretchDirection += other.stretchDirection;
setStretchDirection(mStretchDirection + other.mStretchDirection);
if (isEmpty()) {
return setEmpty();
}
@@ -56,11 +70,23 @@ public:
maxStretchAmount = std::max(maxStretchAmount, other.maxStretchAmount);
}
sk_sp<SkImageFilter> getImageFilter() const;
sk_sp<SkImageFilter> getImageFilter(const sk_sp<SkImage>& snapshotImage) const;
SkRect stretchArea {0, 0, 0, 0};
SkVector stretchDirection {0, 0};
float maxStretchAmount = 0;
void setStretchDirection(const SkVector& direction) {
mStretchFilter = 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<SkImageFilter> mStretchFilter;
};
} // namespace android::uirenderer

View File

@@ -180,14 +180,13 @@ 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 max) {
jfloat left, jfloat top, jfloat right,
jfloat bottom, jfloat vX, jfloat vY, jfloat max) {
StretchEffect effect =
StretchEffect(SkRect::MakeLTRB(left, top, right, bottom), {.fX = vX, .fY = vY}, max);
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
renderNode->mutateStagingProperties().mutateLayerProperties().mutableStretchEffect().mergeWith(
StretchEffect{
.stretchArea = SkRect::MakeLTRB(left, top, right, bottom),
.stretchDirection = {.fX = vX, .fY = vY},
.maxStretchAmount = max
});
effect);
renderNode->setPropertyFieldsDirty(RenderNode::GENERIC);
return true;
}
@@ -659,10 +658,11 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
return;
}
#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, effect->stretchDirection.fX,
effect->stretchDirection.fY, effect->maxStretchAmount);
area.right, area.bottom, stretchDirection.fX, stretchDirection.fY,
effect->maxStretchAmount);
#endif
env->DeleteLocalRef(localref);
}
@@ -702,106 +702,110 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
const char* const kClassPathName = "android/graphics/RenderNode";
static const JNINativeMethod gMethods[] = {
// ----------------------------------------------------------------------------
// Regular JNI
// ----------------------------------------------------------------------------
{ "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
{ "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer },
{ "nOutput", "(J)V", (void*) android_view_RenderNode_output },
{ "nGetUsageSize", "(J)I", (void*) android_view_RenderNode_getUsageSize },
{ "nGetAllocatedSize", "(J)I", (void*) android_view_RenderNode_getAllocatedSize },
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
{ "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
{ "nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates },
// ----------------------------------------------------------------------------
// Regular JNI
// ----------------------------------------------------------------------------
{"nCreate", "(Ljava/lang/String;)J", (void*)android_view_RenderNode_create},
{"nGetNativeFinalizer", "()J", (void*)android_view_RenderNode_getNativeFinalizer},
{"nOutput", "(J)V", (void*)android_view_RenderNode_output},
{"nGetUsageSize", "(J)I", (void*)android_view_RenderNode_getUsageSize},
{"nGetAllocatedSize", "(J)I", (void*)android_view_RenderNode_getAllocatedSize},
{"nAddAnimator", "(JJ)V", (void*)android_view_RenderNode_addAnimator},
{"nEndAllAnimators", "(J)V", (void*)android_view_RenderNode_endAllAnimators},
{"nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V",
(void*)android_view_RenderNode_requestPositionUpdates},
// ----------------------------------------------------------------------------
// Critical JNI via @CriticalNative annotation in RenderNode.java
// ----------------------------------------------------------------------------
{ "nDiscardDisplayList", "(J)V", (void*) android_view_RenderNode_discardDisplayList },
{ "nIsValid", "(J)Z", (void*) android_view_RenderNode_isValid },
{ "nSetLayerType", "(JI)Z", (void*) android_view_RenderNode_setLayerType },
{ "nGetLayerType", "(J)I", (void*) android_view_RenderNode_getLayerType },
{ "nSetLayerPaint", "(JJ)Z", (void*) android_view_RenderNode_setLayerPaint },
{ "nSetStaticMatrix", "(JJ)Z", (void*) android_view_RenderNode_setStaticMatrix },
{ "nSetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_setAnimationMatrix },
{ "nGetAnimationMatrix", "(JJ)Z", (void*) android_view_RenderNode_getAnimationMatrix },
{ "nSetClipToBounds", "(JZ)Z", (void*) android_view_RenderNode_setClipToBounds },
{ "nGetClipToBounds", "(J)Z", (void*) android_view_RenderNode_getClipToBounds },
{ "nSetClipBounds", "(JIIII)Z", (void*) android_view_RenderNode_setClipBounds },
{ "nSetClipBoundsEmpty", "(J)Z", (void*) android_view_RenderNode_setClipBoundsEmpty },
{ "nSetProjectBackwards", "(JZ)Z", (void*) android_view_RenderNode_setProjectBackwards },
{ "nSetProjectionReceiver","(JZ)Z", (void*) android_view_RenderNode_setProjectionReceiver },
// ----------------------------------------------------------------------------
// Critical JNI via @CriticalNative annotation in RenderNode.java
// ----------------------------------------------------------------------------
{"nDiscardDisplayList", "(J)V", (void*)android_view_RenderNode_discardDisplayList},
{"nIsValid", "(J)Z", (void*)android_view_RenderNode_isValid},
{"nSetLayerType", "(JI)Z", (void*)android_view_RenderNode_setLayerType},
{"nGetLayerType", "(J)I", (void*)android_view_RenderNode_getLayerType},
{"nSetLayerPaint", "(JJ)Z", (void*)android_view_RenderNode_setLayerPaint},
{"nSetStaticMatrix", "(JJ)Z", (void*)android_view_RenderNode_setStaticMatrix},
{"nSetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_setAnimationMatrix},
{"nGetAnimationMatrix", "(JJ)Z", (void*)android_view_RenderNode_getAnimationMatrix},
{"nSetClipToBounds", "(JZ)Z", (void*)android_view_RenderNode_setClipToBounds},
{"nGetClipToBounds", "(J)Z", (void*)android_view_RenderNode_getClipToBounds},
{"nSetClipBounds", "(JIIII)Z", (void*)android_view_RenderNode_setClipBounds},
{"nSetClipBoundsEmpty", "(J)Z", (void*)android_view_RenderNode_setClipBoundsEmpty},
{"nSetProjectBackwards", "(JZ)Z", (void*)android_view_RenderNode_setProjectBackwards},
{"nSetProjectionReceiver", "(JZ)Z", (void*)android_view_RenderNode_setProjectionReceiver},
{ "nSetOutlineRoundRect", "(JIIIIFF)Z", (void*) android_view_RenderNode_setOutlineRoundRect },
{ "nSetOutlinePath", "(JJF)Z", (void*) android_view_RenderNode_setOutlinePath },
{ "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", "(JFFFFFFF)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 },
{ "nSetAmbientShadowColor","(JI)Z", (void*) android_view_RenderNode_setAmbientShadowColor },
{ "nGetAmbientShadowColor","(J)I", (void*) android_view_RenderNode_getAmbientShadowColor },
{ "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline },
{ "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip },
{"nSetOutlineRoundRect", "(JIIIIFF)Z", (void*)android_view_RenderNode_setOutlineRoundRect},
{"nSetOutlinePath", "(JJF)Z", (void*)android_view_RenderNode_setOutlinePath},
{"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", "(JFFFFFFF)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},
{"nSetAmbientShadowColor", "(JI)Z", (void*)android_view_RenderNode_setAmbientShadowColor},
{"nGetAmbientShadowColor", "(J)I", (void*)android_view_RenderNode_getAmbientShadowColor},
{"nSetClipToOutline", "(JZ)Z", (void*)android_view_RenderNode_setClipToOutline},
{"nSetRevealClip", "(JZFFF)Z", (void*)android_view_RenderNode_setRevealClip},
{ "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha },
{ "nSetRenderEffect", "(JJ)Z", (void*) android_view_RenderNode_setRenderEffect },
{ "nSetHasOverlappingRendering", "(JZ)Z",
(void*) android_view_RenderNode_setHasOverlappingRendering },
{ "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint },
{ "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation },
{ "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX },
{ "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY },
{ "nSetTranslationZ", "(JF)Z", (void*) android_view_RenderNode_setTranslationZ },
{ "nSetRotation", "(JF)Z", (void*) android_view_RenderNode_setRotation },
{ "nSetRotationX", "(JF)Z", (void*) android_view_RenderNode_setRotationX },
{ "nSetRotationY", "(JF)Z", (void*) android_view_RenderNode_setRotationY },
{ "nSetScaleX", "(JF)Z", (void*) android_view_RenderNode_setScaleX },
{ "nSetScaleY", "(JF)Z", (void*) android_view_RenderNode_setScaleY },
{ "nSetPivotX", "(JF)Z", (void*) android_view_RenderNode_setPivotX },
{ "nSetPivotY", "(JF)Z", (void*) android_view_RenderNode_setPivotY },
{ "nResetPivot", "(J)Z", (void*) android_view_RenderNode_resetPivot },
{ "nSetCameraDistance", "(JF)Z", (void*) android_view_RenderNode_setCameraDistance },
{ "nSetLeft", "(JI)Z", (void*) android_view_RenderNode_setLeft },
{ "nSetTop", "(JI)Z", (void*) android_view_RenderNode_setTop },
{ "nSetRight", "(JI)Z", (void*) android_view_RenderNode_setRight },
{ "nSetBottom", "(JI)Z", (void*) android_view_RenderNode_setBottom },
{ "nGetLeft", "(J)I", (void*) android_view_RenderNode_getLeft },
{ "nGetTop", "(J)I", (void*) android_view_RenderNode_getTop },
{ "nGetRight", "(J)I", (void*) android_view_RenderNode_getRight },
{ "nGetBottom", "(J)I", (void*) android_view_RenderNode_getBottom },
{ "nSetLeftTopRightBottom","(JIIII)Z", (void*) android_view_RenderNode_setLeftTopRightBottom },
{ "nOffsetLeftAndRight", "(JI)Z", (void*) android_view_RenderNode_offsetLeftAndRight },
{ "nOffsetTopAndBottom", "(JI)Z", (void*) android_view_RenderNode_offsetTopAndBottom },
{"nSetAlpha", "(JF)Z", (void*)android_view_RenderNode_setAlpha},
{"nSetRenderEffect", "(JJ)Z", (void*)android_view_RenderNode_setRenderEffect},
{"nSetHasOverlappingRendering", "(JZ)Z",
(void*)android_view_RenderNode_setHasOverlappingRendering},
{"nSetUsageHint", "(JI)V", (void*)android_view_RenderNode_setUsageHint},
{"nSetElevation", "(JF)Z", (void*)android_view_RenderNode_setElevation},
{"nSetTranslationX", "(JF)Z", (void*)android_view_RenderNode_setTranslationX},
{"nSetTranslationY", "(JF)Z", (void*)android_view_RenderNode_setTranslationY},
{"nSetTranslationZ", "(JF)Z", (void*)android_view_RenderNode_setTranslationZ},
{"nSetRotation", "(JF)Z", (void*)android_view_RenderNode_setRotation},
{"nSetRotationX", "(JF)Z", (void*)android_view_RenderNode_setRotationX},
{"nSetRotationY", "(JF)Z", (void*)android_view_RenderNode_setRotationY},
{"nSetScaleX", "(JF)Z", (void*)android_view_RenderNode_setScaleX},
{"nSetScaleY", "(JF)Z", (void*)android_view_RenderNode_setScaleY},
{"nSetPivotX", "(JF)Z", (void*)android_view_RenderNode_setPivotX},
{"nSetPivotY", "(JF)Z", (void*)android_view_RenderNode_setPivotY},
{"nResetPivot", "(J)Z", (void*)android_view_RenderNode_resetPivot},
{"nSetCameraDistance", "(JF)Z", (void*)android_view_RenderNode_setCameraDistance},
{"nSetLeft", "(JI)Z", (void*)android_view_RenderNode_setLeft},
{"nSetTop", "(JI)Z", (void*)android_view_RenderNode_setTop},
{"nSetRight", "(JI)Z", (void*)android_view_RenderNode_setRight},
{"nSetBottom", "(JI)Z", (void*)android_view_RenderNode_setBottom},
{"nGetLeft", "(J)I", (void*)android_view_RenderNode_getLeft},
{"nGetTop", "(J)I", (void*)android_view_RenderNode_getTop},
{"nGetRight", "(J)I", (void*)android_view_RenderNode_getRight},
{"nGetBottom", "(J)I", (void*)android_view_RenderNode_getBottom},
{"nSetLeftTopRightBottom", "(JIIII)Z",
(void*)android_view_RenderNode_setLeftTopRightBottom},
{"nOffsetLeftAndRight", "(JI)Z", (void*)android_view_RenderNode_offsetLeftAndRight},
{"nOffsetTopAndBottom", "(JI)Z", (void*)android_view_RenderNode_offsetTopAndBottom},
{ "nHasOverlappingRendering", "(J)Z", (void*) android_view_RenderNode_hasOverlappingRendering },
{ "nGetClipToOutline", "(J)Z", (void*) android_view_RenderNode_getClipToOutline },
{ "nGetAlpha", "(J)F", (void*) android_view_RenderNode_getAlpha },
{ "nGetCameraDistance", "(J)F", (void*) android_view_RenderNode_getCameraDistance },
{ "nGetScaleX", "(J)F", (void*) android_view_RenderNode_getScaleX },
{ "nGetScaleY", "(J)F", (void*) android_view_RenderNode_getScaleY },
{ "nGetElevation", "(J)F", (void*) android_view_RenderNode_getElevation },
{ "nGetTranslationX", "(J)F", (void*) android_view_RenderNode_getTranslationX },
{ "nGetTranslationY", "(J)F", (void*) android_view_RenderNode_getTranslationY },
{ "nGetTranslationZ", "(J)F", (void*) android_view_RenderNode_getTranslationZ },
{ "nGetRotation", "(J)F", (void*) android_view_RenderNode_getRotation },
{ "nGetRotationX", "(J)F", (void*) android_view_RenderNode_getRotationX },
{ "nGetRotationY", "(J)F", (void*) android_view_RenderNode_getRotationY },
{ "nIsPivotExplicitlySet", "(J)Z", (void*) android_view_RenderNode_isPivotExplicitlySet },
{ "nHasIdentityMatrix", "(J)Z", (void*) android_view_RenderNode_hasIdentityMatrix },
{"nHasOverlappingRendering", "(J)Z",
(void*)android_view_RenderNode_hasOverlappingRendering},
{"nGetClipToOutline", "(J)Z", (void*)android_view_RenderNode_getClipToOutline},
{"nGetAlpha", "(J)F", (void*)android_view_RenderNode_getAlpha},
{"nGetCameraDistance", "(J)F", (void*)android_view_RenderNode_getCameraDistance},
{"nGetScaleX", "(J)F", (void*)android_view_RenderNode_getScaleX},
{"nGetScaleY", "(J)F", (void*)android_view_RenderNode_getScaleY},
{"nGetElevation", "(J)F", (void*)android_view_RenderNode_getElevation},
{"nGetTranslationX", "(J)F", (void*)android_view_RenderNode_getTranslationX},
{"nGetTranslationY", "(J)F", (void*)android_view_RenderNode_getTranslationY},
{"nGetTranslationZ", "(J)F", (void*)android_view_RenderNode_getTranslationZ},
{"nGetRotation", "(J)F", (void*)android_view_RenderNode_getRotation},
{"nGetRotationX", "(J)F", (void*)android_view_RenderNode_getRotationX},
{"nGetRotationY", "(J)F", (void*)android_view_RenderNode_getRotationY},
{"nIsPivotExplicitlySet", "(J)Z", (void*)android_view_RenderNode_isPivotExplicitlySet},
{"nHasIdentityMatrix", "(J)Z", (void*)android_view_RenderNode_hasIdentityMatrix},
{ "nGetTransformMatrix", "(JJ)V", (void*) android_view_RenderNode_getTransformMatrix },
{ "nGetInverseTransformMatrix","(JJ)V", (void*) android_view_RenderNode_getInverseTransformMatrix },
{"nGetTransformMatrix", "(JJ)V", (void*)android_view_RenderNode_getTransformMatrix},
{"nGetInverseTransformMatrix", "(JJ)V",
(void*)android_view_RenderNode_getInverseTransformMatrix},
{ "nGetPivotX", "(J)F", (void*) android_view_RenderNode_getPivotX },
{ "nGetPivotY", "(J)F", (void*) android_view_RenderNode_getPivotY },
{ "nGetWidth", "(J)I", (void*) android_view_RenderNode_getWidth },
{ "nGetHeight", "(J)I", (void*) android_view_RenderNode_getHeight },
{ "nSetAllowForceDark", "(JZ)Z", (void*) android_view_RenderNode_setAllowForceDark },
{ "nGetAllowForceDark", "(J)Z", (void*) android_view_RenderNode_getAllowForceDark },
{ "nGetUniqueId", "(J)J", (void*) android_view_RenderNode_getUniqueId },
{"nGetPivotX", "(J)F", (void*)android_view_RenderNode_getPivotX},
{"nGetPivotY", "(J)F", (void*)android_view_RenderNode_getPivotY},
{"nGetWidth", "(J)I", (void*)android_view_RenderNode_getWidth},
{"nGetHeight", "(J)I", (void*)android_view_RenderNode_getHeight},
{"nSetAllowForceDark", "(JZ)Z", (void*)android_view_RenderNode_setAllowForceDark},
{"nGetAllowForceDark", "(J)Z", (void*)android_view_RenderNode_getAllowForceDark},
{"nGetUniqueId", "(J)J", (void*)android_view_RenderNode_getUniqueId},
};
int register_android_view_RenderNode(JNIEnv* env) {

View File

@@ -169,8 +169,8 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
displayList->mProjectedOutline = nullptr;
}
static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultiplier,
SkPaint* paint) {
static bool layerNeedsPaint(const sk_sp<SkImage>& snapshotImage, const LayerProperties& properties,
float alphaMultiplier, SkPaint* paint) {
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr ||
properties.getImageFilter() != nullptr || !properties.getStretchEffect().isEmpty()) {
@@ -179,7 +179,8 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip
paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
sk_sp<SkImageFilter> imageFilter = sk_ref_sp(properties.getImageFilter());
sk_sp<SkImageFilter> stretchFilter = properties.getStretchEffect().getImageFilter();
sk_sp<SkImageFilter> stretchFilter =
properties.getStretchEffect().getImageFilter(snapshotImage);
sk_sp<SkImageFilter> filter;
if (imageFilter && stretchFilter) {
filter = SkImageFilters::Compose(
@@ -240,7 +241,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
if (renderNode->getLayerSurface() && mComposeLayer) {
SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
SkPaint paint;
layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
sk_sp<SkImage> snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
layerNeedsPaint(snapshotImage, layerProperties, alphaMultiplier, &paint);
SkSamplingOptions sampling(SkFilterMode::kLinear);
// surfaces for layers are created on LAYER_SIZE boundaries (which are >= layer size) so
@@ -254,8 +256,8 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
canvas->drawAnnotation(bounds, String8::format(
"SurfaceID|%" PRId64, renderNode->uniqueId()).c_str(), nullptr);
}
canvas->drawImageRect(renderNode->getLayerSurface()->makeImageSnapshot(), bounds,
bounds, sampling, &paint, SkCanvas::kStrict_SrcRectConstraint);
canvas->drawImageRect(snapshotImage, bounds, bounds, sampling, &paint,
SkCanvas::kStrict_SrcRectConstraint);
if (!renderNode->getSkiaLayer()->hasRenderedSinceRepaint) {
renderNode->getSkiaLayer()->hasRenderedSinceRepaint = true;

View File

@@ -771,6 +771,24 @@
</intent-filter>
</activity>
<activity android:name="StretchShaderActivity"
android:label="RenderEffect/Stretch"
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="EdgeEffectStretchActivity"
android:label="RenderEffect/EdgeEffect stretch"
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="TextActivity"
android:label="Text/Simple Text"
android:theme="@android:style/Theme.NoTitleBar"

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 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:id="@+id/scroll_view"
android:edgeEffectType="stretch"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<HorizontalScrollView
android:id="@+id/horizontal_scroll_view"
android:edgeEffectType="stretch"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
</LinearLayout>
</HorizontalScrollView>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="200dp"
android:src="@drawable/sunset1"/>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,36 @@
/*
* 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.os.Bundle;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
public class EdgeEffectStretchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.stretch_layout);
HorizontalScrollView hsv = findViewById(R.id.horizontal_scroll_view);
hsv.setStretchDistance(50f);
ScrollView sv = findViewById(R.id.scroll_view);
sv.setStretchDistance(50f);
}
}

View File

@@ -0,0 +1,540 @@
/*
* 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.annotation.Nullable;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderEffect;
import android.graphics.RuntimeShader;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
public class StretchShaderActivity extends Activity {
private static final float MAX_STRETCH_INTENSITY = 1.5f;
private static final float STRETCH_AFFECTED_DISTANCE = 1.0f;
private float mScrollX = 0f;
private float mScrollY = 0f;
private float mMaxStretchIntensity = MAX_STRETCH_INTENSITY;
private float mStretchAffectedDistance = STRETCH_AFFECTED_DISTANCE;
private float mOverscrollX = 25f;
private float mOverscrollY = 25f;
private RuntimeShader mRuntimeShader;
private ImageView mImageView;
private ImageView mTestImageView;
private Bitmap mBitmap;
private StretchDrawable mStretchDrawable = new StretchDrawable();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
mBitmap = ((BitmapDrawable) getDrawable(R.drawable.sunset1)).getBitmap();
mRuntimeShader = new RuntimeShader(SKSL, false);
BitmapShader bitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
mRuntimeShader.setInputShader("uContentTexture", bitmapShader);
mImageView = new ImageView(this);
mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader));
mImageView.setImageDrawable(new ColorDrawable(Color.CYAN));
TextView overscrollXText = new TextView(this);
overscrollXText.setText("Overscroll X");
SeekBar overscrollXBar = new SeekBar(this);
overscrollXBar.setProgress(0);
overscrollXBar.setMin(-50);
overscrollXBar.setMax(50);
overscrollXBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mOverscrollX = progress;
overscrollXText.setText("Overscroll X: " + mOverscrollX);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView overscrollYText = new TextView(this);
overscrollYText.setText("Overscroll Y");
SeekBar overscrollYBar = new SeekBar(this);
overscrollYBar.setProgress(0);
overscrollYBar.setMin(-50);
overscrollYBar.setMax(50);
overscrollYBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mOverscrollY = progress;
overscrollYText.setText("Overscroll Y: " + mOverscrollY);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView scrollXText = new TextView(this);
scrollXText.setText("Scroll X");
SeekBar scrollXSeekBar = new SeekBar(this);
scrollXSeekBar.setMin(0);
scrollXSeekBar.setMax(100);
scrollXSeekBar.setProgress(0);
scrollXSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mScrollX = (progress / 100f);
scrollXText.setText("Scroll X: " + mScrollY);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView scrollYText = new TextView(this);
scrollYText.setText("Scroll Y");
SeekBar scrollYSeekBar = new SeekBar(this);
scrollYSeekBar.setMin(0);
scrollYSeekBar.setMax(100);
scrollYSeekBar.setProgress(0);
scrollYSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mScrollY = (progress / 100f);
scrollYText.setText("Scroll Y: " + mScrollY);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView stretchIntensityText = new TextView(this);
int stretchProgress = (int) (mMaxStretchIntensity * 100);
stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity);
SeekBar stretchIntensitySeekbar = new SeekBar(this);
stretchIntensitySeekbar.setProgress(stretchProgress);
stretchIntensitySeekbar.setMin(1);
stretchIntensitySeekbar.setMax((int) (MAX_STRETCH_INTENSITY * 100));
stretchIntensitySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mMaxStretchIntensity = progress / 100f;
stretchIntensityText.setText("StretchIntensity: " + mMaxStretchIntensity);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
TextView stretchDistanceText = new TextView(this);
stretchDistanceText.setText("StretchDistance");
SeekBar stretchDistanceSeekbar = new SeekBar(this);
stretchDistanceSeekbar.setMin(0);
stretchDistanceSeekbar.setProgress((int) (mStretchAffectedDistance * 100));
stretchDistanceSeekbar.setMax(100);
stretchDistanceSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mStretchAffectedDistance = progress / 100f;
stretchDistanceText.setText("StretchDistance: " + mStretchAffectedDistance);
updateShader();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
linearLayout.addView(mImageView,
new LinearLayout.LayoutParams(
mBitmap.getWidth(),
mBitmap.getHeight())
);
linearLayout.addView(overscrollXText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(overscrollXBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(overscrollYText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(overscrollYBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(scrollXText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(scrollXSeekBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(scrollYText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(scrollYSeekBar,
new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(stretchIntensityText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(stretchIntensitySeekbar,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
);
linearLayout.addView(stretchDistanceText,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
linearLayout.addView(stretchDistanceSeekbar,
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
ImageView test = new ImageView(this);
mStretchDrawable.setBitmap(mBitmap);
test.setImageDrawable(mStretchDrawable);
mTestImageView = test;
linearLayout.addView(test,
new LinearLayout.LayoutParams(mBitmap.getWidth(), mBitmap.getHeight()));
setContentView(linearLayout);
}
@Override
protected void onResume() {
super.onResume();
mImageView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
updateShader();
mImageView.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
private void updateShader() {
final float width = mImageView.getWidth();
final float height = mImageView.getHeight();
final float distanceNotStretched = mStretchAffectedDistance;
final float normOverScrollDistX = mOverscrollX / width;
final float normOverScrollDistY = mOverscrollY / height;
final float distanceStretchedX =
mStretchAffectedDistance
/ (1 + Math.abs(normOverScrollDistX) * mMaxStretchIntensity);
final float distanceStretchedY =
mStretchAffectedDistance
/ (1 + Math.abs(normOverScrollDistY) * mMaxStretchIntensity);
final float diffX = distanceStretchedX - distanceNotStretched;
final float diffY = distanceStretchedY - distanceNotStretched;
float uScrollX = mScrollX;
float uScrollY = mScrollY;
mRuntimeShader.setUniform("uMaxStretchIntensity", mMaxStretchIntensity);
mRuntimeShader.setUniform("uStretchAffectedDist", mStretchAffectedDistance);
mRuntimeShader.setUniform("uDistanceStretchedX", distanceStretchedX);
mRuntimeShader.setUniform("uDistanceStretchedY", distanceStretchedY);
mRuntimeShader.setUniform("uDistDiffX", diffX);
mRuntimeShader.setUniform("uDistDiffY", diffY);
mRuntimeShader.setUniform("uOverscrollX", normOverScrollDistX);
mRuntimeShader.setUniform("uOverscrollY", normOverScrollDistY);
mRuntimeShader.setUniform("uScrollX", uScrollX);
mRuntimeShader.setUniform("uScrollY", uScrollY);
mRuntimeShader.setUniform("viewportWidth", width);
mRuntimeShader.setUniform("viewportHeight", height);
mImageView.setRenderEffect(RenderEffect.createShaderEffect(mRuntimeShader));
mStretchDrawable.setStretchDistance(mStretchAffectedDistance);
mStretchDrawable.setOverscrollX(normOverScrollDistX);
mStretchDrawable.setOverscrollY(normOverScrollDistY);
}
private static class StretchDrawable extends Drawable {
private float mStretchDistance = 0;
private float mOverScrollX = 0f;
private float mOverScrollY = 0f;
private Bitmap mBitmap = null;
public void setStretchDistance(float stretchDistance) {
mStretchDistance = stretchDistance;
invalidateSelf();
}
public void setOverscrollX(float overscrollX) {
mOverScrollX = overscrollX;
invalidateSelf();
}
public void setOverscrollY(float overscrollY) {
mOverScrollY = overscrollY;
invalidateSelf();
}
public void setBitmap(Bitmap bitmap) {
mBitmap = bitmap;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
if (mStretchDistance > 0 && canvas instanceof RecordingCanvas) {
Rect bounds = getBounds();
((RecordingCanvas) canvas).mNode.stretch(
0,
0,
bounds.width(),
bounds.height(),
mOverScrollX,
mOverScrollY,
mStretchDistance
);
}
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, 0f, 0f, null);
}
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return 0;
}
}
private static final String SKSL = "in shader uContentTexture;\n"
+ "uniform float uMaxStretchIntensity; // multiplier to apply to scale effect\n"
+ "uniform float uStretchAffectedDist; // Maximum percentage to stretch beyond bounds"
+ " of target\n"
+ "\n"
+ "// Distance stretched as a function of the normalized overscroll times scale "
+ "intensity\n"
+ "uniform float uDistanceStretchedX;\n"
+ "uniform float uDistanceStretchedY;\n"
+ "uniform float uDistDiffX;\n"
+ "uniform float uDistDiffY; // Difference between the peak stretch amount and "
+ "overscroll amount normalized\n"
+ "uniform float uScrollX; // Horizontal offset represented as a ratio of pixels "
+ "divided by the target width\n"
+ "uniform float uScrollY; // Vertical offset represented as a ratio of pixels "
+ "divided by the target height\n"
+ "uniform float uOverscrollX; // Normalized overscroll amount in the horizontal "
+ "direction\n"
+ "uniform float uOverscrollY; // Normalized overscroll amount in the vertical "
+ "direction\n"
+ "\n"
+ "uniform float viewportWidth; // target height in pixels\n"
+ "uniform float viewportHeight; // target width in pixels\n"
+ "\n"
+ "vec4 main(vec2 coord) {\n"
+ "\n"
+ " // Normalize SKSL pixel coordinate into a unit vector\n"
+ " vec2 uv = vec2(coord.x / viewportWidth, coord.y / viewportHeight);\n"
+ " float inU = uv.x;\n"
+ " float inV = uv.y;\n"
+ " float outU;\n"
+ " float outV;\n"
+ " float stretchIntensity;\n"
+ "\n"
+ " // Add the normalized scroll position within scrolling list\n"
+ " inU += uScrollX;\n"
+ " inV += uScrollY;\n"
+ "\n"
+ " outU = inU;\n"
+ " outV = inV;\n"
+ " if (uOverscrollX > 0) {\n"
+ " if (inU <= uStretchAffectedDist) {\n"
+ " inU = uStretchAffectedDist - inU;\n"
+ " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inU);\n"
+ " stretchIntensity = uMaxStretchIntensity * uOverscrollX * "
+ "posBasedVariation;\n"
+ " outU = uDistanceStretchedX - (inU / (1. + stretchIntensity));\n"
+ " } else {\n"
+ " outU = uDistDiffX + inU;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (uOverscrollX < 0) {\n"
+ " float stretchAffectedDist = 1. - uStretchAffectedDist;\n"
+ " if (inU >= stretchAffectedDist) {\n"
+ " inU = inU - stretchAffectedDist;\n"
+ " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, "
+ "inU));\n"
+ " stretchIntensity = uMaxStretchIntensity * (-uOverscrollX) * "
+ "posBasedVariation;\n"
+ " outU = 1 - (uDistanceStretchedX - (inU / (1. + stretchIntensity)))"
+ ";\n"
+ " } else if (inU < stretchAffectedDist) {\n"
+ " outU = -uDistDiffX + inU;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (uOverscrollY > 0) {\n"
+ " if (inV <= uStretchAffectedDist) {\n"
+ " inV = uStretchAffectedDist - inV;\n"
+ " float posBasedVariation = smoothstep(0., uStretchAffectedDist, inV);\n"
+ " stretchIntensity = uMaxStretchIntensity * uOverscrollY * "
+ "posBasedVariation;\n"
+ " outV = uDistanceStretchedY - (inV / (1. + stretchIntensity));\n"
+ " } else if (inV >= uStretchAffectedDist) {\n"
+ " outV = uDistDiffY + inV;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " if (uOverscrollY < 0) {\n"
+ " float stretchAffectedDist = 1. - uStretchAffectedDist;\n"
+ " if (inV >= stretchAffectedDist) {\n"
+ " inV = inV - stretchAffectedDist;\n"
+ " float posBasedVariation = (smoothstep(0., uStretchAffectedDist, inV));\n"
+ " stretchIntensity = uMaxStretchIntensity * (-uOverscrollY) * "
+ "posBasedVariation;\n"
+ " outV = 1 - (uDistanceStretchedY - (inV / (1. + stretchIntensity)));\n"
+ " } else if (inV < stretchAffectedDist) {\n"
+ " outV = -uDistDiffY + inV;\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " uv.x = outU;\n"
+ " uv.y = outV;\n"
+ " coord.x = uv.x * viewportWidth;\n"
+ " coord.y = uv.y * viewportHeight;\n"
+ " return sample(uContentTexture, coord);\n"
+ "}";
}