嗯,确实很炫,那么我们一步步去分析是如何实现的:
一、实现下雪的 View
首先,最上面一层的全屏雪花极有可能是一个顶层的View,而这个View是通过动态加载去控制显示的(不更新淘宝也能看到这个效果)。那么我们先得实现雪花效果的 View,人生苦短,拿来就用。打开 gank.io,搜索"雪花":
看样子第7个库就是我们想要的了,点进源码,直接 download 不解释,记得 star 一个支持作者。那么现在我们的项目中就有一个完整的下雪效果 View 了。
二、实现雪人播放器 View
这个一张雪人图片+一个按钮即可实现,就不多解释了。接下来需要一段圣诞节音频,直接进行在线音频播放无疑是节省空间的好方案。『我的滑板鞋』烘托出的寂寞而甜蜜的氛围无疑是最适合圣诞节的,因此我们得到了『神曲』URL 一枚:
http://cdn.ifancc.com/TomaToDo/bgms/my_hbx.mp3
接下来要找一个小雪人的图片当作播放器的背景,那么阿姆斯特朗...不对,是这个:
嗯,相当可爱喜庆。那么播放器核心代码如下:
package com.kot32.christmasview.player;import android.content.Context;import android.media.AudioManager;import android.media.MediaPlayer;import android.util.AttributeSet;import android.view.View;import android.widget.Toast;import com.kot32.christmasview.R;import java.io.IOException;/** * Created by kot32 on 16/12/8. */public class MyPlayer extends View { public MediaPlayer mediaPlayer; public MyPlayer(Context context) {super(context);init(); } public MyPlayer(Context context, AttributeSet attrs) {super(context, attrs);init(); } private void init() {setBackgroundResource(R.drawable.pig);mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);playUrl("http://172.20.248.106/IXC5b415fcacfc3c439e25a3e74533d2239/TomaToDo/bgms/my_hbx.mp3");Toast.makeText(getContext(), "开始播放", Toast.LENGTH_SHORT).show();setOnClickListener(new OnClickListener() { @Override public void onClick(View v) {if (!mediaPlayer.isPlaying()) { mediaPlayer.start(); Toast.makeText(getContext(), "继续播放", Toast.LENGTH_SHORT).show();} else { mediaPlayer.pause(); Toast.makeText(getContext(), "暂停播放", Toast.LENGTH_SHORT).show();} }}); } public void playUrl(String videoUrl) {try { mediaPlayer.reset(); mediaPlayer.setDataSource(videoUrl); mediaPlayer.prepare();//prepare之后自动播放 mediaPlayer.start();} catch (IllegalArgumentException e) { e.printStackTrace();} catch (IllegalStateException e) { e.printStackTrace();} catch (IOException e) { e.printStackTrace();} } @Override protected void onDetachedFromWindow() {super.onDetachedFromWindow();try { mediaPlayer.stop(); mediaPlayer.release();}catch (Exception e){ e.printStackTrace();} }}三、动态加载思路
private void initResource() {Resources resources = getContext().getResources();try { AssetManager newManager = AssetManager.class.newInstance(); Method addAssetPath = newManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(newManager, DynamicViewManager.getInstance().getUpdateFileFullPath()); Resources newResources = new Resources(newManager, resources.getDisplayMetrics(), resources.getConfiguration()); Reflect.onObject(getContext()).set("mResources", newResources);} catch (Exception e) { e.printStackTrace();} }上面代码的作用是:把添加了外部更新包路径的资源管理器赋值给了App原来的资源管理器,也就是说现在可以在宿主中访问插件资源了。
DexClassLoader classLoader = new DexClassLoader(apkFile.getAbsolutePath() , "dex_out_put_dir" , null , getClass().getClassLoader());Class newViewClazz = classLoader.loadClass("view"s package name");Constructor con = newViewClazz.getConstructor(Context.class);//first use Activity"s Resource lie to Viewif (dynamicView == null) { dynamicView = (View) con.newInstance(getContext());}//Replace the View"s mResources and recovery the Activity"s avoid disorder of ResourcesReflect.onObject(getContext()).set("mResources", null);getContext().getResources();RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(DisplayUtil.dip2px(getContext(), viewInfo.layoutParams.width), DisplayUtil.dip2px(getContext(), viewInfo.layoutParams.height));layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);addView(dynamicView, layoutParams);中间对 mResources 的操作的作用是:将宿主的Activity 的mResources 重置,避免在Activity 中使用资源时和插件冲突。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/tb_bg" > <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroupandroid:layout_width="match_parent"android:layout_height="match_parent"app:uuid="activity_frame"><TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="原始页面" /> </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup> <com.kot32.dynamicloadviewlibrary.core.DynamicViewGroupandroid:layout_width="60dp"android:layout_height="60dp"android:layout_alignParentRight="true"android:layout_centerVertical="true"app:uuid="activity_player"> </com.kot32.dynamicloadviewlibrary.core.DynamicViewGroup></RelativeLayout>以上声明了主界面的布局,当然,在动态加载之前除了原有的"原始页面"TextView,是不会有任何其他东西的,也就是圣诞节来临之前的程序。注意:uuid 会和在线包相匹配。
{ "version": 54, "downLoadPath": "http://obfgb7oet.bkt.clouddn.com/patch106.apk", "fileName": "patch106.apk", "viewInfo": [ {"packageName": "com.kot32.testdynamicviewproject.snow.widgets.SnowingView","uuid": "activity_frame","layoutParams": {"width": -1,"height": -1} }, {"packageName": "com.kot32.testdynamicviewproject.player.MyPlayer","uuid": "activity_player","layoutParams": {"width": -1,"height": -1} } ]}我们声明了这次在线包的版本,每个View 的包名和布局参数, 以及最重要的 和宿主程序中声明对齐的uuid。