/* * 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.animation.ValueAnimator import android.content.Context import android.graphics.BitmapShader import android.graphics.Canvas import android.graphics.ImageDecoder import android.graphics.Matrix import android.graphics.Paint import android.graphics.RuntimeShader import android.graphics.Shader import android.util.AttributeSet import android.view.View class BitmapTransitionView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private val mPaint = Paint() private val mImageA = ImageDecoder.decodeBitmap( ImageDecoder.createSource(context.resources, R.drawable.large_photo)) private val mImageB = ImageDecoder.decodeBitmap( ImageDecoder.createSource(context.resources, R.drawable.very_large_photo)) private val mShaderA = BitmapShader(mImageA, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) private val mShaderB = BitmapShader(mImageB, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) private val mShader = RuntimeShader(AGSL) private var mCurrentProgress = -1f private var mForwardProgress = true private var mCurrentAnimator = ValueAnimator.ofFloat(-1f, 1f) init { isClickable = true mCurrentAnimator.duration = 1500 mCurrentAnimator.addUpdateListener { animation -> mCurrentProgress = animation.animatedValue as Float postInvalidate() } } override fun performClick(): Boolean { if (super.performClick()) return true if (mCurrentAnimator.isRunning) { mCurrentAnimator.reverse() return true } if (mForwardProgress) { mCurrentAnimator.setFloatValues(-1f, 1f) mForwardProgress = false } else { mCurrentAnimator.setFloatValues(1f, -1f) mForwardProgress = true } mCurrentAnimator.start() postInvalidate() return true } override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) { val matrixA = Matrix() val matrixB = Matrix() matrixA.postScale(width.toFloat() / mImageA.width, height.toFloat() / mImageA.height) matrixB.postScale(width.toFloat() / mImageB.width, height.toFloat() / mImageB.height) mShaderA.setLocalMatrix(matrixA) mShaderB.setLocalMatrix(matrixB) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) mShader.setInputShader("imageA", mShaderA) mShader.setInputShader("imageB", mShaderB) mShader.setIntUniform("imageDimensions", width, height) mShader.setFloatUniform("progress", mCurrentProgress) mPaint.shader = mShader canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), mPaint) } private companion object { const val AGSL = """ uniform shader imageA; uniform shader imageB; uniform ivec2 imageDimensions; uniform float progress; const vec2 iSize = vec2(48.0, 48.0); const float iDir = 0.5; const float iRand = 0.81; float hash12(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } float ramp(float2 p) { return mix(hash12(p), dot(p/vec2(imageDimensions), float2(iDir, 1 - iDir)), iRand); } half4 main(float2 p) { float2 lowRes = p / iSize; float2 cellCenter = (floor(lowRes) + 0.5) * iSize; float2 posInCell = fract(lowRes) * 2 - 1; float v = ramp(cellCenter) + progress; float distToCenter = max(abs(posInCell.x), abs(posInCell.y)); return distToCenter > v ? imageA.eval(p).rgb1 : imageB.eval(p).rgb1; } """ } }