WallpaperManager(壁纸管理器)

使用 WallpaperManager (壁纸管理器)

WallpaperManager 是 Android 提供的一个系统服务类,用于管理和设置设备的壁纸(包括静态壁纸和动态壁纸)。它允许开发者获取当前壁纸、设置新的壁纸、清除壁纸或调整壁纸显示参数。结合 Android 动画合集的背景,WallpaperManager 可用于在动画场景中动态切换壁纸或与动画效果(如淡入淡出)结合,增强视觉体验。本文将详细介绍 WallpaperManager 的功能、使用方法、权限要求及注意事项,重点展示如何设置壁纸并结合动画实现交互效果。

WallpaperManager 的作用与原理

  • 作用WallpaperManager 提供对设备壁纸的访问和修改功能,支持设置静态图片、获取壁纸信息或控制动态壁纸。它适用于个性化应用、主题切换或动态 UI 场景。
  • 原理WallpaperManager 通过 Android 的壁纸服务与系统壁纸引擎交互,管理主屏幕和锁屏壁纸。它支持位图(Bitmap)或资源文件作为壁纸,并可与动态壁纸服务(WallpaperService)配合。
  • 应用场景
  • 设置自定义壁纸(如用户选择图片后触发动画)。
  • 获取当前壁纸并在应用中显示(结合缩放或平移动画)。
  • 清除壁纸,恢复默认壁纸。
  • 结合 WindowManager 或动画(如淡入效果)实现壁纸切换动画。
  • 响应用户交互(如按钮点击)动态调整壁纸。

权限要求

使用 WallpaperManager 的某些功能需要以下权限,在 AndroidManifest.xml 中声明:

<!-- 设置壁纸 -->
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<!-- 读取壁纸(可选,API 28+ 可能需要) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 动态壁纸服务(可选) -->
<service android:name=".MyWallpaperService"
         android:permission="android.permission.BIND_WALLPAPER">
    <intent-filter>
        <action android:name="android.service.wallpaper.WallpaperService" />
    </intent-filter>
</service>
  • 动态权限(API 23+):
  • READ_EXTERNAL_STORAGE:读取外部存储的图片作为壁纸。
  • 使用 ActivityCompat.requestPermissions 请求。
  • 普通权限
  • SET_WALLPAPER:设置壁纸,无需动态请求。
  • 注意:API 28+(Android 9)对读取壁纸可能需要存储权限;动态壁纸需声明 BIND_WALLPAPER

获取 WallpaperManager

通过 Context 获取 WallpaperManager 实例:

import android.app.WallpaperManager;
import android.content.Context;

WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);

常用功能与方法

以下是 WallpaperManager 的核心方法:

方法/功能描述权限要求API 级别用法示例
getInstance(Context context)获取 WallpaperManager 实例。5+WallpaperManager wm = WallpaperManager.getInstance(context);
setBitmap(Bitmap bitmap)设置位图为壁纸(主屏幕)。SET_WALLPAPER5+wallpaperManager.setBitmap(bitmap);
setResource(int resid)设置资源图片为壁纸。SET_WALLPAPER5+wallpaperManager.setResource(R.drawable.wallpaper);
setStream(InputStream data)从输入流设置壁纸。SET_WALLPAPER5+wallpaperManager.setStream(inputStream);
getWallpaperInfo()获取当前动态壁纸信息(若为动态壁纸)。7+WallpaperInfo info = wallpaperManager.getWallpaperInfo();
getDrawable()获取当前壁纸为 Drawable(主屏幕)。5+Drawable wallpaper = wallpaperManager.getDrawable();
clear()清除壁纸,恢复默认壁纸。SET_WALLPAPER5+wallpaperManager.clear();
setBitmap(Bitmap bitmap, Rect visibleCropHint, boolean allowBackup, int which)设置壁纸(可指定主屏幕/锁屏,API 24+)。SET_WALLPAPER24+wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM);
  • 壁纸类型(API 24+,which 参数):
  • FLAG_SYSTEM:主屏幕壁纸。
  • FLAG_LOCK:锁屏壁纸。
  • FLAG_SYSTEM | FLAG_LOCK:同时设置。

完整示例(设置壁纸 + 动画)

以下是一个使用 WallpaperManager 设置壁纸并结合 ImageView 动画的示例,用户点击按钮选择图片并设置为壁纸,同时触发动画。

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import android.animation.ObjectAnimator;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.app.WallpaperManager;

public class WallpaperActivity extends AppCompatActivity {
    private static final int REQUEST_STORAGE_PERMISSION = 100;
    private ImageView wallpaperPreview;
    private WallpaperManager wallpaperManager;
    private ActivityResultLauncher<Intent> imagePickerLauncher;
    private ObjectAnimator fadeAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_wallpaper);

        wallpaperPreview = findViewById(R.id.wallpaper_preview);
        Button selectImageButton = findViewById(R.id.select_image_button);
        Button setWallpaperButton = findViewById(R.id.set_wallpaper_button);
        wallpaperManager = WallpaperManager.getInstance(this);

        // 初始化动画
        fadeAnimator = ObjectAnimator.ofFloat(wallpaperPreview, "alpha", 0f, 1f);
        fadeAnimator.setDuration(500);

        // 初始化图片选择器
        imagePickerLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == RESULT_OK && result.getData() != null) {
                Uri imageUri = result.getData().getData();
                try {
                    Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
                    wallpaperPreview.setImageBitmap(bitmap);
                    fadeAnimator.start();
                } catch (Exception e) {
                    Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
                }
            }
        });

        // 选择图片
        selectImageButton.setOnClickListener(v -> {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION);
            } else {
                Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                imagePickerLauncher.launch(intent);
            }
        });

        // 设置壁纸
        setWallpaperButton.setOnClickListener(v -> {
            Bitmap bitmap = ((android.graphics.drawable.BitmapDrawable) wallpaperPreview.getDrawable()).getBitmap();
            if (bitmap != null) {
                try {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM);
                    } else {
                        wallpaperManager.setBitmap(bitmap);
                    }
                    Toast.makeText(this, "Wallpaper set successfully", Toast.LENGTH_SHORT).show();
                    ObjectAnimator.ofFloat(wallpaperPreview, "scaleX", 1f, 1.2f, 1f).setDuration(300).start();
                } catch (Exception e) {
                    Toast.makeText(this, "Failed to set wallpaper", Toast.LENGTH_SHORT).show();
                }
            }
        });

        // 显示当前壁纸
        Drawable currentWallpaper = wallpaperManager.getDrawable();
        if (currentWallpaper != null) {
            wallpaperPreview.setImageDrawable(currentWallpaper);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_STORAGE_PERMISSION && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            imagePickerLauncher.launch(intent);
        } else {
            Toast.makeText(this, "Storage permission denied", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        fadeAnimator.cancel();
    }
}
  • 布局 XML(res/layout/activity_wallpaper.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">
    <ImageView
        android:id="@+id/wallpaper_preview"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:scaleType="centerCrop" />
    <Button
        android:id="@+id/select_image_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Select Image" />
    <Button
        android:id="@+id/set_wallpaper_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Set Wallpaper" />
</LinearLayout>
  • AndroidManifest.xml(添加权限):
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  • 说明
  • 权限:动态请求 READ_EXTERNAL_STORAGE(API 23+),SET_WALLPAPER 为普通权限。
  • 选择图片:通过 ActivityResultLauncher 从图库选择图片。
  • 设置壁纸:使用 setBitmap 设置主屏幕壁纸(API 24+ 支持锁屏)。
  • 动画:加载图片时触发淡入动画(alpha),设置壁纸时触发缩放动画(scaleX)。
  • 生命周期onDestroy 取消动画。

设置动态壁纸

创建自定义动态壁纸需实现 WallpaperService

import android.service.wallpaper.WallpaperService;
import android.view.SurfaceHolder;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;

public class MyWallpaperService extends WallpaperService {
    @Override
    public Engine onCreateEngine() {
        return new MyWallpaperEngine();
    }

    private class MyWallpaperEngine extends Engine {
        private final Paint paint = new Paint();
        private float offset = 0f;
        private boolean visible = true;

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            paint.setColor(Color.BLUE);
            paint.setStyle(Paint.Style.FILL);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            this.visible = visible;
            drawFrame();
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep, float yOffsetStep, int xPixelOffset, int yPixelOffset) {
            offset = xOffset;
            drawFrame();
        }

        private void drawFrame() {
            final SurfaceHolder holder = getSurfaceHolder();
            Canvas canvas = null;
            try {
                canvas = holder.lockCanvas();
                if (canvas != null) {
                    canvas.drawColor(Color.BLACK);
                    canvas.drawCircle(offset * canvas.getWidth(), canvas.getHeight() / 2, 50, paint);
                }
            } finally {
                if (canvas != null) {
                    holder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }
}
  • AndroidManifest.xml(注册动态壁纸服务):
<service
    android:name=".MyWallpaperService"
    android:label="My Wallpaper"
    android:permission="android.permission.BIND_WALLPAPER">
    <intent-filter>
        <action android:name="android.service.wallpaper.WallpaperService" />
    </intent-filter>
    <meta-data
        android:name="android.service.wallpaper"
        android:resourceName="wallpaper" />
</service>
  • 说明:动态壁纸通过 WallpaperService 绘制动画(如移动的圆形),需用户手动在系统设置中选择。

结合动画

加载壁纸时添加淡入动画:

Drawable wallpaper = wallpaperManager.getDrawable();
wallpaperPreview.setImageDrawable(wallpaper);
ObjectAnimator.ofFloat(wallpaperPreview, "alpha", 0f, 1f).setDuration(500).start();

优缺点

  • 优点
  • 提供灵活的壁纸管理(静态/动态)。
  • 支持主屏幕和锁屏壁纸(API 24+)。
  • 可与动画结合,增强视觉效果。
  • 缺点
  • 设置壁纸需权限,API 28+ 可能需要存储权限。
  • 动态壁纸开发复杂,耗电量较高。
  • 壁纸大小和格式需适配设备分辨率。
  • 替代方案
  • LiveData/ViewModel:动态更新 UI 中的壁纸预览。
  • Glide/Picasso:加载和显示壁纸图片。
  • Jetpack Compose:声明式 UI 替代 XML 布局。
  • Custom View:绘制类似壁纸的效果。

注意事项

  • 权限:API 23+ 需动态请求 READ_EXTERNAL_STORAGE
  • 分辨率适配:确保壁纸图片尺寸适合设备屏幕。
  • 性能:避免加载超大图片,优化内存使用。
  • 生命周期:在 onDestroy 取消动画,释放资源。
  • 动态壁纸:测试耗电量,确保流畅性。
  • 调试:通过 Log 检查壁纸设置是否成功,验证图片格式。

扩展:Jetpack Compose 中的实现

在 Jetpack Compose 中,WallpaperManager 可通过 AndroidView 或直接加载壁纸:

import android.Manifest
import android.app.WallpaperManager
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.provider.MediaStore
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween

@Composable
fun WallpaperScreen() {
    val context = LocalContext.current
    val wallpaperManager = remember { WallpaperManager.getInstance(context) }
    var bitmap by remember { mutableStateOf<Bitmap?>(null) }
    val alpha by animateFloatAsState(
        targetValue = if (bitmap != null) 1f else 0f,
        animationSpec = tween(500)
    )

    val imagePickerLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == android.app.Activity.RESULT_OK && result.data != null) {
            val uri = result.data?.data
            try {
                bitmap = MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
            } catch (e: Exception) {
                // Handle error
            }
        }
    }

    val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) {
            val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            imagePickerLauncher.launch(intent)
        }
    }

    Column(
        modifier = Modifier.fillMaxSize().padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        bitmap?.let {
            Image(
                bitmap = it.asImageBitmap(),
                contentDescription = "Wallpaper Preview",
                modifier = Modifier
                    .fillMaxWidth()
                    .height(200.dp)
                    .alpha(alpha)
            )
        }
        Button(
            onClick = {
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M &&
                    !androidx.core.content.ContextCompat.checkSelfPermission(
                        context,
                        Manifest.permission.READ_EXTERNAL_STORAGE
                    ).equals(android.content.pm.PackageManager.PERMISSION_GRANTED)
                ) {
                    permissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
                } else {
                    val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
                    imagePickerLauncher.launch(intent)
                }
            }
        ) {
            Text("Select Image")
        }
        Button(
            onClick = {
                bitmap?.let {
                    try {
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
                            wallpaperManager.setBitmap(it, null, true, WallpaperManager.FLAG_SYSTEM)
                        } else {
                            wallpaperManager.setBitmap(it)
                        }
                    } catch (e: Exception) {
                        // Handle error
                    }
                }
            },
            enabled = bitmap != null
        ) {
            Text("Set Wallpaper")
        }
    }
}
  • 说明
  • 使用 rememberLauncherForActivityResult 处理图片选择和权限请求。
  • animateFloatAsState 实现图片淡入动画。
  • Image 显示选择的壁纸预览。
  • WallpaperManager 设置壁纸,兼容 API 24+。

示例:结合振动

结合 Vibrator(前文介绍)为设置壁纸添加振动反馈:

setWallpaperButton.setOnClickListener(v -> {
    Bitmap bitmap = ((android.graphics.drawable.BitmapDrawable) wallpaperPreview.getDrawable()).getBitmap();
    if (bitmap != null) {
        try {
            wallpaperManager.setBitmap(bitmap);
            Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
            } else {
                vibrator.vibrate(100);
            }
            ObjectAnimator.ofFloat(wallpaperPreview, "scaleX", 1f, 1.2f, 1f).setDuration(300).start();
        } catch (Exception e) {
            Toast.makeText(this, "Failed to set wallpaper", Toast.LENGTH_SHORT).show();
        }
    }
});

如果需要更复杂的功能(如动态壁纸动画、与 WindowManager 结合创建壁纸预览悬浮窗、或 Canvas 绘制壁纸效果),或想继续探讨 Android 动画合集(如过渡动画、Lottie),请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注