Android視頻壁紙的實(shí)現(xiàn)

視頻壁紙屬于動態(tài)壁紙,所以視頻壁紙就可以用Android系統(tǒng)提供的動態(tài)壁紙服務(wù)來實(shí)現(xiàn)卷仑。首先先介紹一下在實(shí)現(xiàn)過程中會用到的幾個類。

WallpaperManager

Android提供的用于管理壁紙的類茁瘦,里面有幾個方法在實(shí)現(xiàn)過程中需要用到

  1. getInstance(context) 獲取一個實(shí)例
  2. clear() 清空所有的壁紙
  3. getWallpaperInfo() 獲取當(dāng)前壁紙的信息

WallpaperService

Android系統(tǒng)提供的壁紙服務(wù)的抽象類,實(shí)現(xiàn)一個動態(tài)壁紙必須要繼承該類并實(shí)現(xiàn)onCreateEngine()方法坠韩,該類的唯一功能就是返回繪制動態(tài)壁紙所需要的Engine實(shí)例。

WallpaperService.Engine

Engine是WallpaperService一個內(nèi)部類炼列,動態(tài)壁紙的所有顯示過程的繪制都由該類完成只搁,我們需要繼承該類并實(shí)現(xiàn)以下三個方法:

  1. onSurfaceCreated()
  2. onSurfaceDestroyed()
  3. onVisibilityChanged()

當(dāng)然還有其他的如onCreate()和onDestroy()方法等等。

視頻壁紙的實(shí)現(xiàn)過程

  • 首先我們需要自定義一個類VideoWallPaperService來繼承WallpaperService類并實(shí)現(xiàn)onCreateEngine()方法俭尖,在該方法中需要返回一個Engine實(shí)例氢惋。
public class VideoWallPaperService extends WallpaperService {
    @Override
    public Engine onCreateEngine() {
        return new VideoEngine();
    }
}
  • 所以我們需要再VideoWallPaperService內(nèi)部自定義一個VideoEngine類來繼承Engine類并實(shí)現(xiàn)onSurfaceCreated() 、onSurfaceDestroyed()和onVisibilityChanged()方法稽犁。然后在onSurfaceCreated()方法中初始化一個MediaPlayer焰望;在onSurfaceDestroyed()中去釋放和銷毀MediaPlayer;在onVisibilityChanged()方法中去切換MediaPlayer的播放和暫停已亥。
private class VideoEngine extends Engine {
        private MediaPlayer mPlayer;

        @Override
        public void onVisibilityChanged(boolean visible) {
             if(visible) {
                 mPlayer.start();
             } else {
                 mPlayer.pause();
             }
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
            mPlayer = new MediaPlayer();
            //不能使用setDisplay()方法
            mPlayer.setSurface(holder.getSurface());
            mPlayer.setDataSource(videoPath);
            mPlayer.prepare();
            mPlayer.start();
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            if(mPlayer.isPlaying()) {
                mPlayer.stop();
            }
            mPlayer.release();
            mPlayer = null;
        }
    }
  • 以上都完成后我們還需要對VideoWallPaperService進(jìn)行注冊柿估,這里在注冊時寫法和普通的Service相同,其中VideoWallPaperService需要一個"android.permission.BIND_WALLPAPER"的權(quán)限陷猫,需要添加一個action為"android.service.wallpaper.WallpaperService"(固定寫法)秫舌,還需要一個名為"android.service.wallpaper"(固定寫法)的meta-data。
<service
     android:name=".VideoWallPaperService"
     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:resource="@xml/wallpaper" />
     </service>
  • 上面一步的代碼中meta-data中需要一個xml文件源绣檬,所以我們需要再res的xml文件夾(沒有就自己建)下創(chuàng)建一個名為wallpaper的xml文件足陨。xml文件的根標(biāo)簽為wallpaper,有一下三個屬性:
  1. description 動態(tài)壁紙的文字描述
  2. thumbnail 動態(tài)壁紙的圖片描述
  3. settingsActivity 動態(tài)壁紙的設(shè)置界面(會在預(yù)覽界面出現(xiàn))
<?xml version="1.0" encoding="utf-8"?>
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/wallpaper_description"
    android:settingsActivity="com.example.videowallpaper.SettingsActivity"
    android:thumbnail="@mipmap/ic_launcher">
</wallpaper>
  • 接下來就是啟動壁紙服務(wù)了娇未,這里我們不能通過context的startService()方法來啟動壁紙服務(wù)墨缘,我們需要通過啟動系統(tǒng)的預(yù)覽界面來間接啟動服務(wù)。
Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(context, VideoWallPaperService.class));
context.startActivity(intent);
  • 除了啟動還需要關(guān)閉壁紙服務(wù)零抬,我們可以通過WallpaperManager的clear()方法來關(guān)閉镊讼,也可以通過WallpaperService的clearWallpaper()(已經(jīng)被標(biāo)記過時)方法來關(guān)閉壁紙。
try {
      WallpaperManager.getInstance(context).clear();
} catch(IOException e) {
       e.printStackTrace();
}
  • 其他需要注意的地方
  1. 設(shè)置壁紙需要"android.permission.SET_WALLPAPER"權(quán)限
  2. 播放本地視頻需要"android.permission.READ_EXTERNAL_STORAGE"權(quán)限
  3. VideoEngine的MediaPlayer的播放地址要使用持久化保存數(shù)據(jù)(數(shù)據(jù)庫平夜、Preference等)蝶棋,否則設(shè)置好視頻壁紙后將手機(jī)關(guān)機(jī)再開機(jī),就會出bug
  4. WallpaperService的onCreateEngine()方法返回的Engine實(shí)例不能使用單例模式忽妒,必須每次都返回一個新的Engine實(shí)例
  5. 可以通過WallpaperManager的getWallpaperInfo()方法來判斷當(dāng)前自己的服務(wù)是否已經(jīng)在運(yùn)行玩裙,從而可以通過廣播或者其他通知來直接修改壁紙播放的視頻,從而避免每次更換一個視頻都需要走一次系統(tǒng)的預(yù)覽界面段直。

最后貼一下VideoWallPaperService的完整代碼(僅供參考):

package com.example.videowallpaper;

import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.preference.PreferenceManager;
import android.service.wallpaper.WallpaperService;
import android.text.TextUtils;
import android.view.SurfaceHolder;

import java.io.IOException;

public class VideoWallPaperService extends WallpaperService {
    private static final String SERVICE_NAME = "com.example.videowallpaper.VideoWallPaperService";

    @Override
    public Engine onCreateEngine() {
        return new VideoEngine();
    }

    public static void startWallPaper(Context context, String videoPath) {
        WallpaperInfo info = WallpaperManager.getInstance(context).getWallpaperInfo();

        if(info != null && SERVICE_NAME.equals(info.getServiceName())) {
            changeVideo(context, videoPath);
        } else {
            startNewWallpaper(context, videoPath);
        }
    }

    public static void closeWallpaper(Context context) {
        try {
            WallpaperManager.getInstance(context).clear();
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    private static void startNewWallpaper(Context context, String path) {
        saveVideoPath(context, path);
        Intent intent = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER);
        intent.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, new ComponentName(context, VideoWallPaperService.class));
        context.startActivity(intent);
    }

    private static void changeVideo(Context context, String path) {
        saveVideoPath(context, path);
        Intent intent = new Intent();
        intent.setAction(Constant.ACTION);
        intent.putExtra(Constant.BROADCAST_SET_VIDEO_PARAM, Constant.ACTION_SET_VIDEO);
        context.sendBroadcast(intent);
    }

    public static void setVolume(Context context, boolean hasVolume) {
        Intent intent = new Intent();
        intent.setAction(Constant.ACTION);
        if(hasVolume) {
            intent.putExtra(Constant.BROADCAST_SET_VIDEO_PARAM, Constant.ACTION_VOICE_NORMAL);
        } else {
            intent.putExtra(Constant.BROADCAST_SET_VIDEO_PARAM, Constant.ACTION_VOICE_SILENCE);
        }
        context.sendBroadcast(intent);
    }

    private static void saveVideoPath(Context context, String path) {
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
        editor.putString(Constant.VIDEO_PATH, path);
        editor.apply();
    }

    private String getVideoPath() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        return preferences.getString(Constant.VIDEO_PATH, null);
    }

    private class VideoEngine extends Engine implements MediaPlayer.OnPreparedListener,
            MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
        private MediaPlayer mPlayer;
        private boolean mLoop;
        private boolean mVolume;
        private boolean isPapered = false;

        private VideoEngine() {
            SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(VideoWallPaperService.this);
            mLoop = preferences.getBoolean("loop", true);
            mVolume = preferences.getBoolean("volume", false);
        }

        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                int action = intent.getIntExtra(Constant.BROADCAST_SET_VIDEO_PARAM, -1);
                switch(action) {
                    case Constant.ACTION_SET_VIDEO: {
                        setVideo(getVideoPath());
                        break;
                    }
                    case Constant.ACTION_VOICE_NORMAL: {
                        mVolume = true;
                        setVolume();
                        break;
                    }
                    case Constant.ACTION_VOICE_SILENCE: {
                        mVolume = false;
                        setVolume();
                        break;
                    }
                }
            }
        };

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            IntentFilter filter = new IntentFilter();
            filter.addAction(Constant.ACTION);
            registerReceiver(mReceiver, filter);
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            unregisterReceiver(mReceiver);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            if(isPapered) {
                if(visible) {
                    mPlayer.start();
                } else {
                    mPlayer.pause();
                }
            }
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
            mPlayer = new MediaPlayer();
            setVideo(getVideoPath());
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            if(mPlayer.isPlaying()) {
                mPlayer.stop();
            }
            mPlayer.release();
            mPlayer = null;
        }

        @Override
        public void onPrepared(MediaPlayer mp) {
            isPapered = true;
            mp.start();
        }

        @Override
        public void onCompletion(MediaPlayer mp) {
            closeWallpaper(getApplicationContext());
        }

        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            closeWallpaper(getApplicationContext());
            return true;
        }

        private void setVideo(String videoPath) {
            if(TextUtils.isEmpty(videoPath)) {
                closeWallpaper(getApplicationContext());
                throw new IllegalArgumentException("video path is null");
            }
            if(mPlayer != null) {
                mPlayer.reset();
                isPapered = false;
                try {
                    mPlayer.setOnPreparedListener(this);
                    mPlayer.setOnCompletionListener(this);
                    mPlayer.setOnErrorListener(this);
                    mPlayer.setLooping(mLoop);
//                  mPlayer.setDisplay(getSurfaceHolder());
                    mPlayer.setSurface(getSurfaceHolder().getSurface());
                    setVolume();
                    mPlayer.setDataSource(videoPath);
                    mPlayer.prepareAsync();
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void setVolume() {
            if(mPlayer != null) {
                if(mVolume) {
                    mPlayer.setVolume(1.0f, 1.0f);
                } else {
                    mPlayer.setVolume(0f, 0f);
                }
            }
        }
    }
}

還有一個SettingsActivity的代碼也貼出來吧

這里說明一下推薦使用PreferenceFragment來代替PreferenceActivity

package com.example.videowallpaper;

import android.os.Bundle;
import android.preference.PreferenceActivity;

public class SettingsActivity extends PreferenceActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.setting_layout);
    }
}

setting_layout

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:key="video_param"
    android:title="設(shè)置">
    <CheckBoxPreference
        android:defaultValue="true"
        android:key="loop"
        android:title="是否循環(huán)播放" />
    <CheckBoxPreference
        android:checked="false"
        android:key="volume"
        android:title="是否開啟聲音" />
</PreferenceScreen>

Constant.java(自定義的一些常量)

public class Constant {
    public static final String BROADCAST_SET_VIDEO_PARAM = "broadcast_set_video_param";
    public static final String ACTION = "action";
    public static final String VIDEO_PATH = "action_video_path";
    public static final int ACTION_SET_VIDEO = 0x101;
    public static final int ACTION_VOICE_SILENCE = 0x102;
    public static final int ACTION_VOICE_NORMAL = 0x103;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吃溅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸯檬,更是在濱河造成了極大的恐慌决侈,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喧务,死亡現(xiàn)場離奇詭異赖歌,居然都是意外死亡枉圃,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門俏站,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痊土,你說我怎么就攤上這事肄扎。” “怎么了赁酝?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵犯祠,是天一觀的道長。 經(jīng)常有香客問我酌呆,道長衡载,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任隙袁,我火速辦了婚禮痰娱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘菩收。我一直安慰自己梨睁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布娜饵。 她就那樣靜靜地躺著坡贺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪箱舞。 梳的紋絲不亂的頭發(fā)上遍坟,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機(jī)與錄音晴股,去河邊找鬼愿伴。 笑死,一個胖子當(dāng)著我的面吹牛电湘,可吹牛的內(nèi)容都是我干的公般。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胡桨,長吁一口氣:“原來是場噩夢啊……” “哼官帘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昧谊,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刽虹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后呢诬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涌哲,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胖缤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阀圾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哪廓。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖初烘,靈堂內(nèi)的尸體忽然破棺而出涡真,到底是詐尸還是另有隱情,我是刑警寧澤肾筐,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布哆料,位于F島的核電站,受9級特大地震影響吗铐,放射性物質(zhì)發(fā)生泄漏东亦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一唬渗、第九天 我趴在偏房一處隱蔽的房頂上張望典阵。 院中可真熱鬧,春花似錦镊逝、人聲如沸萄喳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽他巨。三九已至,卻和暖如春减江,著一層夾襖步出監(jiān)牢的瞬間染突,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工辈灼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留份企,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓巡莹,卻偏偏與公主長得像司志,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子降宅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容