基于Android Java实现Apple Music风格动态流体模糊背景
复刻Apple Music动态流体模糊背景效果
我一直在研究如何复刻Apple Music的背景效果——提取专辑封面颜色,制作带有提取色彩的动态缓慢流动模糊背景。目前已编写了提取颜色的ColorExtractor类,但不清楚如何实现该流体模糊动画效果。
已完成的颜色提取代码
public class ColorExtractor { private int previousColor; // Initial fallback color (black) private long animationDuration; // Duration in milliseconds private Interpolator interpolator; // Interpolator for animation public ColorExtractor(Context context) { this.animationDuration = 300; // Default duration this.interpolator = new DecelerateInterpolator(); // Default interpolator } public interface ColorCallback { void onColorExtracted(int animatedColor); } /** * Set the animation duration for the color transition. * @param duration Animation duration in milliseconds. */ public void setAnimationDuration(long duration) { this.animationDuration = duration; } /** * Set a custom interpolator for the color transition. * @param interpolator Custom interpolator for the animation. */ public void setInterpolator(Interpolator interpolator) { this.interpolator = interpolator; } public void extractDominantColor(byte[] imageData, ColorCallback callback) { Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length); if (bitmap != null) { Palette.from(bitmap).generate(palette -> { if (palette != null) { int newColor = getColorFromPalette(palette); if (newColor != previousColor) { // Only animate if the color changes animateColorChange(previousColor, newColor, callback); previousColor = newColor; // Update previous color to the new one } else { callback.onColorExtracted(previousColor); // No animation if colors are the same } bitmap.recycle(); // Clean up bitmap to free memory } else { callback.onColorExtracted(previousColor); // Fallback to previous color } }); } else { callback.onColorExtracted(previousColor); // Fallback to previous color } } private int getColorFromPalette(Palette palette) { // Create a list of swatch providers List<Palette.Swatch> swatches = new ArrayList<>(); swatches.add(palette.getLightMutedSwatch()); swatches.add(palette.getMutedSwatch()); swatches.add(palette.getLightVibrantSwatch()); swatches.add(palette.getVibrantSwatch()); // Shuffle the list for random selection order Collections.shuffle(swatches); // Iterate over shuffled swatches and return the first available color for (Palette.Swatch swatch : swatches) { if (swatch != null) { return swatch.getRgb(); } } // If no suitable swatch is found, use the last known color instead return previousColor; } private void animateColorChange(int startColor, int endColor, ColorCallback callback) { ValueAnimator colorAnimator = ValueAnimator.ofArgb(startColor, endColor); colorAnimator.setDuration(animationDuration); // Use the dynamic duration colorAnimator.setInterpolator(interpolator); // Use the dynamic interpolator colorAnimator.addUpdateListener(animation -> { int animatedColor = (int) animation.getAnimatedValue(); callback.onColorExtracted(animatedColor); // Provide the smooth interpolated color }); colorAnimator.start(); } }
流体模糊动画实现方案
Apple Music的背景效果核心是多层叠加+色彩融合+缓慢位移动画,以下是具体实现步骤:
1. 布局层叠结构
需要四层布局叠加,保证前景内容清晰:
- 底层:动态纯色背景(接收
ColorExtractor的颜色值) - 中层:专辑封面的模糊镜像,承载流动动画
- 遮罩层:半透明深色遮罩,弱化模糊层,突出前景
- 前景层:播放控件、专辑信息等核心UI
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 动态纯色背景 --> <View android:id="@+id/color_bg_view" android:layout_width="match_parent" android:layout_height="match_parent"/> <!-- 模糊封面层 --> <ImageView android:id="@+id/blur_cover_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop"/> <!-- 半透明遮罩 --> <View android:layout_width="match_parent" android:layout_height="match_parent" android:background="#50000000"/> <!-- 前景内容区 --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="24dp" android:gravity="bottom"> <!-- 这里放置播放控件、专辑名称等UI --> </LinearLayout> </FrameLayout>
2. 生成模糊封面图
使用Android系统的RenderScript实现高效模糊,模糊半径建议设为15-25,平衡效果与性能:
private Bitmap getBlurredBitmap(Bitmap original) { if (original == null) return null; RenderScript rs = RenderScript.create(this); Allocation input = Allocation.createFromBitmap(rs, original); Allocation output = Allocation.createTyped(rs, input.getType()); ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); blurScript.setInput(input); blurScript.setRadius(20f); // 模糊半径,可按需调整 blurScript.forEach(output); Bitmap blurredBitmap = Bitmap.createBitmap(original.getWidth(), original.getHeight(), original.getConfig()); output.copyTo(blurredBitmap); // 释放资源 input.destroy(); output.destroy(); blurScript.destroy(); rs.destroy(); return blurredBitmap; }
3. 实现缓慢流动动画
给模糊层添加低幅度、长周期的平移+缩放动画,模拟自然流动感:
private void startFluidAnimation(ImageView blurView) { // 水平平移动画:左右小幅度循环移动 ObjectAnimator translateX = ObjectAnimator.ofFloat(blurView, "translationX", 0f, 40f, 0f, -40f, 0f); translateX.setDuration(22000); // 22秒一个周期,节奏平缓 translateX.setRepeatCount(ValueAnimator.INFINITE); translateX.setInterpolator(new LinearInterpolator()); // 垂直平移动画:上下小幅度循环移动,与水平周期错开 ObjectAnimator translateY = ObjectAnimator.ofFloat(blurView, "translationY", 0f, 30f, 0f, -30f, 0f); translateY.setDuration(18000); translateY.setRepeatCount(ValueAnimator.INFINITE); translateY.setInterpolator(new LinearInterpolator()); // 轻微缩放动画:增强空间流动感 ObjectAnimator scaleX = ObjectAnimator.ofFloat(blurView, "scaleX", 1f, 1.06f, 1f); scaleX.setDuration(20000); scaleX.setRepeatCount(ValueAnimator.INFINITE); scaleX.setInterpolator(new DecelerateInterpolator()); ObjectAnimator scaleY = ObjectAnimator.ofFloat(blurView, "scaleY", 1f, 1.06f, 1f); scaleY.setDuration(24000); scaleY.setRepeatCount(ValueAnimator.INFINITE); scaleY.setInterpolator(new AccelerateInterpolator()); // 组合动画,同步播放 AnimatorSet animSet = new AnimatorSet(); animSet.playTogether(translateX, translateY, scaleX, scaleY); animSet.start(); }
4. 结合ColorExtractor实现色彩融合
在ColorCallback中同步更新背景色和模糊层的颜色滤镜,让模糊图与提取色自然融合:
// 初始化ColorExtractor,延长过渡时间更贴合Apple Music风格 ColorExtractor extractor = new ColorExtractor(this); extractor.setAnimationDuration(1200); extractor.extractDominantColor(imageData, animatedColor -> { // 更新纯色背景 findViewById(R.id.color_bg_view).setBackgroundColor(animatedColor); // 给模糊层添加颜色滤镜,融合提取的主色调 ImageView blurCover = findViewById(R.id.blur_cover_view); blurCover.setColorFilter(animatedColor, PorterDuff.Mode.SCREEN); }); // 生成模糊封面并启动动画 Bitmap originalCover = BitmapFactory.decodeByteArray(imageData, 0, imageData.length); Bitmap blurredCover = getBlurredBitmap(originalCover); ImageView blurCoverView = findViewById(R.id.blur_cover_view); blurCoverView.setImageBitmap(blurredCover); startFluidAnimation(blurCoverView);
性能优化建议
- 模糊半径不要超过25,避免过度消耗GPU资源;可根据设备性能动态调整
- 动画周期设置为15-30秒,保证流动效果平缓不突兀
- 及时回收Bitmap资源:模糊完成后回收原始封面图,避免内存泄漏
- 低性能设备可关闭缩放动画,只保留平移效果
内容的提问来源于stack exchange,提问作者wayne chirwa
相关产品推荐
相关产品推荐

