/* * 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.content.Context; import android.graphics.Canvas; import android.graphics.CanvasProperty; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RecordingCanvas; import android.graphics.RuntimeShader; import android.os.Bundle; import android.os.Trace; import android.view.RenderNodeAnimator; import android.view.View; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import java.util.ArrayList; public class RippleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); layout.addView(new RippleView(this), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); setContentView(layout); } static class RippleView extends View { static final int DURATION = 1000; static final int MAX_RADIUS = 250; private final int mColor = Color.RED; private boolean mToggle = false; ArrayList mRunningAnimations = new ArrayList(); CanvasProperty mX; CanvasProperty mY; CanvasProperty mRadius; CanvasProperty mProgress; CanvasProperty mNoisePhase; CanvasProperty mPaint; RuntimeShader mRuntimeShader; static final String sSkSL = "" + "uniform float2 in_origin;" + "uniform float in_progress;\n" + "uniform float in_maxRadius;\n" + "uniform shader in_paintColor;\n" + "float dist2(float2 p0, float2 pf) { return sqrt((pf.x - p0.x) * (pf.x - p0.x) + " + "(pf.y - p0.y) * (pf.y - p0.y)); }\n" + "float mod2(float a, float b) { return a - (b * floor(a / b)); }\n" + "float rand(float2 src) { return fract(sin(dot(src.xy, float2(12.9898, 78.233)))" + " * 43758.5453123); }\n" + "float4 main(float2 p)\n" + "{\n" + " float fraction = in_progress;\n" + " float2 fragCoord = p;//sk_FragCoord.xy;\n" + " float maxDist = in_maxRadius;\n" + " float fragDist = dist2(in_origin, fragCoord.xy);\n" + " float circleRadius = maxDist * fraction;\n" + " float colorVal = (fragDist - circleRadius) / maxDist;\n" + " float d = fragDist < circleRadius \n" + " ? 1. - abs(colorVal * 2. * smoothstep(0., 1., fraction)) \n" + " : 1. - abs(colorVal * 3.);\n" + " d = smoothstep(0., 1., d);\n" + " float divider = 2.;\n" + " float x = floor(fragCoord.x / divider);\n" + " float y = floor(fragCoord.y / divider);\n" + " float density = .95;\n" + " d = rand(float2(x, y)) > density ? d : d * .2;\n" + " d = d * rand(float2(fraction, x * y));\n" + " float alpha = 1. - pow(fraction, 3.);\n" + " return float4(in_paintColor.eval(p).rgb, d * alpha);\n" + "}"; RippleView(Context c) { super(c); setClickable(true); mX = CanvasProperty.createFloat(200.0f); mY = CanvasProperty.createFloat(200.0f); mRadius = CanvasProperty.createFloat(150.0f); mProgress = CanvasProperty.createFloat(0.0f); mNoisePhase = CanvasProperty.createFloat(0.0f); Paint p = new Paint(); p.setAntiAlias(true); p.setColor(mColor); mPaint = CanvasProperty.createPaint(p); mRuntimeShader = new RuntimeShader(sSkSL); mRuntimeShader.setFloatUniform("in_maxRadius", MAX_RADIUS); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (canvas.isHardwareAccelerated()) { RecordingCanvas recordingCanvas = (RecordingCanvas) canvas; recordingCanvas.drawRipple(mX, mY, mRadius, mPaint, mProgress, mNoisePhase, mColor, mRuntimeShader); } } @Override public boolean performClick() { for (int i = 0; i < mRunningAnimations.size(); i++) { mRunningAnimations.get(i).cancel(); } mRunningAnimations.clear(); mToggle = !mToggle; mRunningAnimations.add(new RenderNodeAnimator( mX, mToggle ? 400.0f : 200.0f)); mRunningAnimations.add(new RenderNodeAnimator( mY, mToggle ? 600.0f : 200.0f)); mRunningAnimations.add(new RenderNodeAnimator( mRadius, mToggle ? MAX_RADIUS : 150.0f)); mRunningAnimations.add(new RenderNodeAnimator( mProgress, mToggle ? 1.0f : 0.0f)); mRunningAnimations.add(new RenderNodeAnimator( mNoisePhase, DURATION)); mRunningAnimations.add(new RenderNodeAnimator( mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f)); // Will be "chained" to run after the above mRunningAnimations.add(new RenderNodeAnimator( mPaint, RenderNodeAnimator.PAINT_ALPHA, 255.0f)); for (int i = 0; i < mRunningAnimations.size(); i++) { RenderNodeAnimator anim = mRunningAnimations.get(i); anim.setDuration(DURATION); anim.setTarget(this); if (i == (mRunningAnimations.size() - 1)) { // "chain" test anim.setStartValue(64.0f); anim.setStartDelay(anim.getDuration()); } anim.start(); } if (mToggle) { post(new Runnable() { @Override public void run() { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "pretendBusy"); try { Thread.sleep(DURATION); } catch (InterruptedException e) { } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }); } return true; } } }