Android之旅

动手做视频动态壁纸~

2018-12-23  本文已影响167人  h2coder

某天,女朋友在刷某音,突然看到有视频作为锁屏,十分崇拜,撒娇的眼生看着我...看来这个需求躲不过了,身为安卓仔,这有什么的,开干!

动态壁纸-设置页面.png 视频设置成功-锁屏.png 视频设置成功-桌面.png

搜了下资料,实现大概分为以下几步:

思路

下面是码代码时间~

public class VideoLiveWallpaper extends WallpaperService {
    //返回一个Engine对象,这里
    @Override
    public Engine onCreateEngine() {
        return new VideoEngine();
    }
    
    class VideoEngine extends Engine {
        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
                //Engine对象被创建时回调,这里可以做广播注册等操作
        }
        
        @Override
        public void onDestroy() {
              //Engine对象被销毁时回调,这里可以注销广播注册等操作
            super.onDestroy();
        }
        
        @Override
        public void onVisibilityChanged(boolean visible) {
              //显示、隐藏时切换,在桌面时为显示,跳转到别的App页面时为隐藏
              //这里做视频的暂停和恢复播放~
        }
        
        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
             //SurfaceView被创建时回调,我们的视频MediaPlayer对象播放的视频输出在这个surface上。
        }
        
        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            //Surface销毁时回调,这里我们应该销毁MediaPlayer,回收MediaPlayer。
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
           android:thumbnail="@mipmap/fa_live_wallpaper_icon"/>
<!-- 配置实时壁纸Service -->
        <service
                android:name="me.wally.arch.livewallpaper.VideoLiveWallpaper"
                android:label="@string/fs_video_live_wallpaper"
                android:permission="android.permission.BIND_WALLPAPER"
                android:process=":wallpaper">
            <!-- 为实时壁纸配置intent-filter -->
            <intent-filter>
                <action android:name="android.service.wallpaper.WallpaperService" />
            </intent-filter>
            <!-- 为实时壁纸配置meta-data -->
            <meta-data
                    android:name="android.service.wallpaper"
                    android:resource="@xml/fs_video_wallpaper" />
        </service>

上面分别分析了WallpaperService服务的几个抽象方法,下面我们一步步来将MediaPlayer的视频输出到桌面的SufaceView中~

@Override
    public Engine onCreateEngine() {
        return new VideoEngine();
    }
@Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setSurface(holder.getSurface());
            try {
                mMediaPlayer.setDataSource(getApplicationContext(), Uri.parse(videoFilePath));
                mMediaPlayer.setLooping(true);
                mMediaPlayer.setVolume(0, 0);
                mMediaPlayer.prepare();
                mMediaPlayer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
@Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            mMediaPlayer.release();
            mMediaPlayer = null;
        }
@Override
        public void onVisibilityChanged(boolean visible) {
            if (visible) {
                mMediaPlayer.start();
            } else {
                mMediaPlayer.pause();
            }
        }

到这里其实我们已经将视频功能做完啦是不是挺简单的

  1. 但是,,,我们要的效果是桌面和锁屏都是视频,原生ROM测试是可以滴,国产ROM都改为他们自家的主题设置了😂(我冇眼睇!)
  2. 接下来就是写Activity,点按钮设置我们的动态壁纸~
  1. 首先是我们调用系统图库选择文件,这里我做了抽取,将选择工作交给了一个叫FileChooseCompat的类,将选择资源的工作交给它。然后写了一个ChooseFileCallback回调接口,回调选择结果到Activity。
public class FileChooseCompat {
    /**
     * 资源类型分隔符
     */
    private static final String RESOURCES_TYPE_SEPARATOR = ";";

    private FragmentActivity mActivity;

    /**
     * 资源类型
     */
    public enum ResourceType {
        /**
         * 无类型限制
         */
        GENERAL("0", "*/*", "无类型限制"),
        /**
         * 选择图片
         */
        IMAGE("1", "image/*", "选择图片"),
        /**
         * 选择音频
         */
        AUDIO("2", "audio/*", "选择音频"),
        /**
         * 选择视频,(mp4 3gp 是android支持的视频格式)
         */
        VIDEO("3", "video/*", "选择视频");

        private String mType;
        private String mIntentType;
        private String mDesc;

        ResourceType(String type, String intentType, String desc) {
            mType = type;
            mIntentType = intentType;
            mDesc = desc;
        }

        public String getType() {
            return mType;
        }

        public String getIntentType() {
            return mIntentType;
        }

        public String getDesc() {
            return mDesc;
        }

        @Override
        public String toString() {
            return this.mType;
        }
    }

    public interface ChooseFileCallback {
        void onChoose(Uri uri, String filePath);

        void onChooseCancel();
    }

    public static class SimpleChooseFileCallback implements ChooseFileCallback {

        @Override
        public void onChoose(Uri uri, String filePath) {
        }

        @Override
        public void onChooseCancel() {
        }
    }

    private FileChooseCompat() {
    }

    private FileChooseCompat(FragmentActivity activity) {
        this.mActivity = activity;
    }

    public static FileChooseCompat create(FragmentActivity activity) {
        return new FileChooseCompat(activity);
    }

    /**
     * 选择图片
     */
    public void startChooseImageFile(ChooseFileCallback chooseFileCallback) {
        startChooseFile(chooseFileCallback, ResourceType.IMAGE);
    }

    /**
     * 选择音频
     */
    public void startChooseAudioFile(ChooseFileCallback chooseFileCallback) {
        startChooseFile(chooseFileCallback, ResourceType.AUDIO);
    }

    /**
     * 选择视频
     */
    public void startChooseVideoFile(ChooseFileCallback chooseFileCallback) {
        startChooseFile(chooseFileCallback, ResourceType.VIDEO);
    }

    /**
     * 选择图片和视频
     */
    public void startChooseImageAndVideo(ChooseFileCallback chooseFileCallback) {
        startChooseFile(chooseFileCallback, ResourceType.IMAGE, ResourceType.VIDEO);
    }

    private void startChooseFile(ChooseFileCallback chooseFileCallback, final ResourceType... type) {
        FileChooseDelegateFragment delegateFragment = DelegateFragmentFinder
                .getInstance()
                .find(mActivity, FileChooseDelegateFragment.class);
        //拼接多个资源类型
        StringBuilder builder = new StringBuilder();
        for (FileChooseCompat.ResourceType resourcesType : type) {
            builder.append(resourcesType.getIntentType());
            builder.append(RESOURCES_TYPE_SEPARATOR);
        }
        String typeResult = builder.toString();
        typeResult = typeResult.substring(0, typeResult.length() - 1);
        delegateFragment.startChooseFile(chooseFileCallback, typeResult);
    }
}
  1. 调用系统图库Api,需要复写onActivityResult()拿到选择文件的地址,我这里将onActivityResult()的回调给代理Fragment。
public class FileChooseDelegateFragment extends BaseDelegateFragment {
    private static final int REQUEST_CHOOSE_FILE = 100;
    /**
     * 选择回调
     */
    private FileChooseCompat.ChooseFileCallback mChooseFileCallback;
    private UriToPathFactory mUriToPathFactory = new UriToPathFactory();

    /**
     * 选择文件
     *
     * @param chooseFileCallback 选择的回调
     * @param type               资源类型
     */
    public void startChooseFile(FileChooseCompat.ChooseFileCallback chooseFileCallback, final String type) {
        this.mChooseFileCallback = chooseFileCallback;
        runTaskOnStart(new LifecycleTask() {
            @Override
            public void execute(BaseDelegateFragment delegateFragment) {

                //选择文件,调用系统的文件管理
                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                intent.setType(type);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                startActivityForResult(intent, REQUEST_CHOOSE_FILE);
            }
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {//选择文件返回
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_CANCELED) {
            switch (requestCode) {
                case REQUEST_CHOOSE_FILE:
                    if (mChooseFileCallback != null) {
                        mChooseFileCallback.onChooseCancel();
                    }
                    break;
                default:
                    break;
            }
        } else if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case REQUEST_CHOOSE_FILE:
                    Uri uri = data.getData();
                    String chooseFilePath = mUriToPathFactory.startUriToPath(getContext(), uri);
                    if (mChooseFileCallback != null) {
                        mChooseFileCallback.onChoose(uri, chooseFilePath);
                    }
                    break;
                default:
                    break;
            }
        }
    }
}
  1. Activity按钮点击事件开始调用~
Button chooseVideoBtn = findViewById(R.id.choose_video_btn);
        chooseVideoBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                FileChooseCompat.create(ChooseVideoWallpaperActivity.this).startChooseVideoFile(new FileChooseCompat.SimpleChooseFileCallback() {
                    @Override
                    public void onChoose(Uri uri, String filePath) {
                        //这里将文件的filePath交给Service处理。
                        VideoLiveWallpaper.setWallPaper(ChooseVideoWallpaperActivity.this, filePath);
                    }
                });
            }
        });
  1. 上面的VideoLiveWallpaper.setWallPaper(),将视频地址交给Service处理。
    这里有个小问题,估计谷歌在设计这个动态壁纸时,没有提供是否设置成功的回调,就是说跳转到系统的设置界面后,我们就没法得知用户是设置了我们的壁纸,还是点了返回键返回...所以这里,我的处理是,设置前将文件地址保存到sp,再跳转到系统的设置界面,再广播更新视频地址。为什么在这里就要更新视频地址?因为系统的设置页面,在选择你的动态地址后,会跳转到一个预览界面,播放你的动态壁纸,而上面的动态壁纸也是使用我们前面返回Engine类,并且每次进入该界面,Engine类不一定会重建,有时候会使用之前的实例,而且Engine类创建时,也没有提供一个Intent设置一个Bundle参数带这个地址过去😂完全不受自己控制...所以这里就需要进行广播更新了(😂总感觉这块没设计好...)。

public static final String VIDEO_PARAMS_CONTROL_ACTION = "me.wally.arch.livewallpaper.VideoLiveWallpaper";

    public static final String KEY_ACTION = "action";
    public static final int ACTION_UPDATE_VIDEO_FILE_PATH = 112;

/**
     * 跳转到动态壁纸设置页面
     */
    public static void setWallPaper(Context context, String videoFilePath) {
        //因为没有提供是否设置成功的回调,只能当选择了就是设置成功
        saveVideoPath(videoFilePath);
        startNewWallpaper(context);
        updateWallpaperVideo(context);
    }
    
    private static void saveVideoPath(String videoFilePath) {
        //将视频地址保存,后面广播更新时再读取
        PropertyHelper.setProperty(KEY_WALLPAPER_VIDEO_DATA_SOURCE_FILE_PATH, videoFilePath);
    }
    
    /**
     * 跳转到系统的动态壁纸设置界面
     */
    private static void startNewWallpaper(Context context) {
        Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
        intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(context, VideoLiveWallpaper.class));
        context.startActivity(intent);
    }
    
    private static void updateWallpaperVideo(Context context) {
        //发送广播更新
        Intent intent = new Intent(VideoLiveWallpaper.VIDEO_PARAMS_CONTROL_ACTION);
        intent.putExtra(VideoLiveWallpaper.KEY_ACTION, VideoLiveWallpaper.ACTION_UPDATE_VIDEO_FILE_PATH);
        context.sendBroadcast(intent);
    }
  1. 在VideoEngine的onCreate()方法中,注册广播,在onDestroy()方法中,注销广播。这样我们就完成了选择视频->视频播放~
@Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            IntentFilter intentFilter = new IntentFilter(VIDEO_PARAMS_CONTROL_ACTION);
            registerReceiver(mVideoParamsControlReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    int action = intent.getIntExtra(KEY_ACTION, -1);
                    switch (action) {
                        case ACTION_UPDATE_VIDEO_FILE_PATH:
                            //更新视频播放源
                            String videoFilePath = getVideoPath();
                            try {
                                mMediaPlayer.setDataSource(getApplicationContext(), Uri.parse(videoFilePath));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            break;
                        default:
                            break;
                    }
                }
            }, intentFilter);
        }

        @Override
        public void onDestroy() {
            unregisterReceiver(mVideoParamsControlReceiver);
            super.onDestroy();
        }
视频设置成功-桌面.png 视频设置成功-锁屏.png

你以为这样就完了吗?不,还有点小缺陷,写完以上代码,你会发现,选择视频后,咋么视频的声音都出来了...回个桌面还有声音,吓人呢😂。其实这是正常滴,视频播放就肯定会播声音啦,那我们我还需要做一步静音操作~

//设置不静音
mMediaPlayer.setVolume(1.0f, 1.0f);
//设置静音
mMediaPlayer.setVolume(0, 0);
    public static final int ACTION_VOICE_SILENCE = 110;
    public static final int ACTION_VOICE_NORMAL = 111;

    /**
     * 设置是否静音
     *
     * @param isSilence 是否静音
     */
    public static void setVolume(Context context, boolean isSilence) {
        Intent intent = new Intent(VideoLiveWallpaper.VIDEO_PARAMS_CONTROL_ACTION);
        if (isSilence) {
            intent.putExtra(VideoLiveWallpaper.KEY_ACTION, VideoLiveWallpaper.ACTION_VOICE_SILENCE);
        } else {
            intent.putExtra(VideoLiveWallpaper.KEY_ACTION, VideoLiveWallpaper.ACTION_VOICE_NORMAL);
        }
        context.sendBroadcast(intent);
    }
@Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            IntentFilter intentFilter = new IntentFilter(VIDEO_PARAMS_CONTROL_ACTION);
            registerReceiver(mVideoParamsControlReceiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    int action = intent.getIntExtra(KEY_ACTION, -1);
                    switch (action) {
                        case ACTION_VOICE_NORMAL:
                            mMediaPlayer.setVolume(1.0f, 1.0f);
                            break;
                        case ACTION_VOICE_SILENCE:
                            mMediaPlayer.setVolume(0, 0);
                            break;
                        case ACTION_UPDATE_VIDEO_FILE_PATH:
                            ...
                            break;
                        default:
                            break;
                    }
                }
            }, intentFilter);
        }
CheckBox voiceCheckBox = findViewById(R.id.voice_check_box);
        boolean isVolume = getVideoLiveWallpaperSilence();
        voiceCheckBox.setChecked(isVolume);
        voiceCheckBox.setOnCheckedChangeListener(
                new CompoundButton.OnCheckedChangeListener() {
                    @Override
                    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                        if (isChecked) {
                            //静音
                            setVideoLiveWallpaperSilence(true);
                        } else {
                            setVideoLiveWallpaperSilence(false);
                        }
                    }
                });
@Override
        public void onCreate(SurfaceHolder surfaceHolder) {
         super.onCreate(surfaceHolder);
        boolean isVolume = PropertyHelper.getProperty(Const.Key.KEY_WALLPAPER_VOLUME_IS_SILENCE, true);
        VideoLiveWallpaper.setVolume(this.getApplicationContext(), isVolume);
    }

总结

上一篇 下一篇

猜你喜欢

热点阅读