增加悬浮窗播放

悬浮窗播放,可处于其他应用之上
pull/107/head
xufulong 5 years ago
parent 36656ea085
commit 083c306d2b
  1. 9
      app/src/main/AndroidManifest.xml
  2. 32
      app/src/main/java/com/frank/ffmpeg/activity/MainActivity.java
  3. 10
      app/src/main/java/com/frank/ffmpeg/activity/VideoHandleActivity.java
  4. 81
      app/src/main/java/com/frank/ffmpeg/floating/FloatActivity.java
  5. 114
      app/src/main/java/com/frank/ffmpeg/floating/FloatLifecycle.java
  6. 111
      app/src/main/java/com/frank/ffmpeg/floating/FloatPhone.java
  7. 64
      app/src/main/java/com/frank/ffmpeg/floating/FloatPlayerView.java
  8. 96
      app/src/main/java/com/frank/ffmpeg/floating/FloatToast.java
  9. 38
      app/src/main/java/com/frank/ffmpeg/floating/FloatView.java
  10. 171
      app/src/main/java/com/frank/ffmpeg/floating/FloatWindow.java
  11. 30
      app/src/main/java/com/frank/ffmpeg/floating/IFloatWindow.java
  12. 248
      app/src/main/java/com/frank/ffmpeg/floating/IFloatWindowImpl.java
  13. 15
      app/src/main/java/com/frank/ffmpeg/floating/LifecycleListener.java
  14. 24
      app/src/main/java/com/frank/ffmpeg/floating/MoveType.java
  15. 11
      app/src/main/java/com/frank/ffmpeg/floating/PermissionListener.java
  16. 21
      app/src/main/java/com/frank/ffmpeg/floating/Screen.java
  17. 50
      app/src/main/java/com/frank/ffmpeg/floating/Util.java
  18. 9
      app/src/main/res/layout/activity_main.xml
  19. 1
      app/src/main/res/values/strings.xml

@ -2,12 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.frank.ffmpeg"> package="com.frank.ffmpeg">
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.camera.autofocus" />
@ -23,7 +23,7 @@
<activity android:name=".activity.MainActivity"> <activity android:name=".activity.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -61,6 +61,11 @@
<activity android:name=".activity.VideoReverseActivity" <activity android:name=".activity.VideoReverseActivity"
android:screenOrientation="landscape" android:screenOrientation="landscape"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" /> android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" />
<!--申请悬浮窗权限-->
<activity android:name=".floating.FloatActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"/>
</application> </application>
</manifest> </manifest>

@ -7,7 +7,13 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.view.animation.BounceInterpolator;
import com.frank.ffmpeg.R; import com.frank.ffmpeg.R;
import com.frank.ffmpeg.floating.FloatPlayerView;
import com.frank.ffmpeg.floating.FloatWindow;
import com.frank.ffmpeg.floating.MoveType;
import com.frank.ffmpeg.floating.Screen;
/** /**
* 使用ffmpeg进行音视频处理入口 * 使用ffmpeg进行音视频处理入口
@ -39,6 +45,7 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
findViewById(R.id.btn_live).setOnClickListener(this); findViewById(R.id.btn_live).setOnClickListener(this);
findViewById(R.id.btn_filter).setOnClickListener(this); findViewById(R.id.btn_filter).setOnClickListener(this);
findViewById(R.id.btn_reverse).setOnClickListener(this); findViewById(R.id.btn_reverse).setOnClickListener(this);
findViewById(R.id.btn_floating).setOnClickListener(this);
} }
@Override @Override
@ -69,6 +76,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
case R.id.btn_reverse://视频倒播 case R.id.btn_reverse://视频倒播
intent.setClass(MainActivity.this, VideoReverseActivity.class); intent.setClass(MainActivity.this, VideoReverseActivity.class);
break; break;
case R.id.btn_floating://悬浮窗播放
floatingToPlay();
return;
default: default:
break; break;
} }
@ -84,4 +94,26 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
} }
} }
/**
* 悬浮窗播放
*/
private void floatingToPlay(){
if (FloatWindow.get() != null) {
return;
}
FloatPlayerView floatPlayerView = new FloatPlayerView(getApplicationContext());
FloatWindow
.with(getApplicationContext())
.setView(floatPlayerView)
.setWidth(Screen.width, 0.4f)
.setHeight(Screen.width, 0.4f)
.setX(Screen.width, 0.8f)
.setY(Screen.height, 0.3f)
.setMoveType(MoveType.slide)
.setFilter(false)
.setMoveStyle(500, new BounceInterpolator())
.build();
FloatWindow.get().show();
}
} }

@ -10,9 +10,14 @@ import android.support.v7.app.AppCompatActivity;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.animation.BounceInterpolator;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.frank.ffmpeg.FFmpegCmd; import com.frank.ffmpeg.FFmpegCmd;
import com.frank.ffmpeg.R; import com.frank.ffmpeg.R;
import com.frank.ffmpeg.floating.FloatPlayerView;
import com.frank.ffmpeg.floating.FloatWindow;
import com.frank.ffmpeg.floating.MoveType;
import com.frank.ffmpeg.floating.Screen;
import com.frank.ffmpeg.format.VideoLayout; import com.frank.ffmpeg.format.VideoLayout;
import com.frank.ffmpeg.util.FFmpegUtil; import com.frank.ffmpeg.util.FFmpegUtil;
import com.frank.ffmpeg.util.FileUtil; import com.frank.ffmpeg.util.FileUtil;
@ -290,7 +295,10 @@ public class VideoHandleActivity extends AppCompatActivity implements View.OnCli
String imagePath = PATH + File.separator + "Video2Image/";//图片保存路径 String imagePath = PATH + File.separator + "Video2Image/";//图片保存路径
File imageFile = new File(imagePath); File imageFile = new File(imagePath);
if (!imageFile.exists()){ if (!imageFile.exists()){
imageFile.mkdir(); boolean result = imageFile.mkdir();
if (!result){
return;
}
} }
int mStartTime = 10;//开始时间 int mStartTime = 10;//开始时间
int mDuration = 20;//持续时间(注意开始时间+持续时间之和不能大于视频总时长) int mDuration = 20;//持续时间(注意开始时间+持续时间之和不能大于视频总时长)

@ -0,0 +1,81 @@
package com.frank.ffmpeg.floating;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.List;
/**
* 用于在内部自动申请权限
* https://github.com/yhaolpz
*/
public class FloatActivity extends Activity {
private static List<PermissionListener> mPermissionListenerList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 23){
requestAlertWindowPermission();
}
}
@RequiresApi(api = 23)
private void requestAlertWindowPermission() {
Intent intent = new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION");
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, 123);
}
@RequiresApi(api = 23)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= 23){
//todo 用23以上编译即可出现canDrawOverlays
if (Util.hasPermission(this)) {
mPermissionListener.onSuccess();
} else {
mPermissionListener.onFail();
}
}
finish();
}
static synchronized void request(Context context, PermissionListener permissionListener) {
if (mPermissionListenerList == null) {
mPermissionListenerList = new ArrayList<>();
mPermissionListener = new PermissionListener() {
@Override
public void onSuccess() {
for (PermissionListener listener : mPermissionListenerList) {
listener.onSuccess();
}
}
@Override
public void onFail() {
for (PermissionListener listener : mPermissionListenerList) {
listener.onFail();
}
}
};
Intent intent = new Intent(context, FloatActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
mPermissionListenerList.add(permissionListener);
}
private static PermissionListener mPermissionListener;
}

@ -0,0 +1,114 @@
package com.frank.ffmpeg.floating;
import android.app.Activity;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
/**
* Created by yhao on 17-12-1.
* 用于控制悬浮窗显示周期
* 使用了三种方法针对返回桌面时隐藏悬浮按钮
* 1.startCount计数针对back到桌面可以及时隐藏
* 2.监听home键从而及时隐藏
* 3.resumeCount计时针对一些只执行onPause不执行onStop的奇葩情况
*/
class FloatLifecycle extends BroadcastReceiver implements Application.ActivityLifecycleCallbacks {
private static final long delay = 300;
private Handler mHandler;
private Class[] activities;
private boolean showFlag;
private int resumeCount;
private boolean appBackground;
private LifecycleListener mLifecycleListener;
FloatLifecycle(Context applicationContext, boolean showFlag, Class[] activities, LifecycleListener lifecycleListener) {
this.showFlag = showFlag;
this.activities = activities;
mLifecycleListener = lifecycleListener;
mHandler = new Handler();
((Application) applicationContext).registerActivityLifecycleCallbacks(this);
applicationContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
private boolean needShow(Activity activity) {
if (activities == null) {
return true;
}
for (Class a : activities) {
if (a.isInstance(activity)) {
return showFlag;
}
}
return !showFlag;
}
@Override
public void onActivityResumed(Activity activity) {
resumeCount++;
if (needShow(activity)) {
mLifecycleListener.onShow();
}
if (appBackground) {
appBackground = false;
}
}
@Override
public void onActivityPaused(Activity activity) {
resumeCount--;
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (resumeCount == 0) {
appBackground = true;
//mLifecycleListener.onPostHide();
}
}
}, delay);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onReceive(Context context, Intent intent) {
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}

@ -0,0 +1,111 @@
package com.frank.ffmpeg.floating;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
/**
* Created by yhao on 17-11-14.
* 7.1及以上需申请权限
*/
class FloatPhone extends FloatView {
private final Context mContext;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mLayoutParams;
private View mView;
private int mX, mY;
FloatPhone(Context applicationContext) {
mContext = applicationContext;
mWindowManager = (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams();
}
@Override
public void setSize(int width, int height) {
mLayoutParams.width = width;
mLayoutParams.height = height;
}
@Override
public void setView(View view) {
int layout_type;
layout_type = WindowManager.LayoutParams.TYPE_PHONE;
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLayoutParams.type = layout_type;
mLayoutParams.windowAnimations = 0;
mView = view;
}
@Override
public void setGravity(int gravity, int xOffset, int yOffset) {
mLayoutParams.gravity = gravity;
mLayoutParams.x = mX = xOffset;
mLayoutParams.y = mY = yOffset;
}
@Override
public void init() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Util.hasPermission(mContext)) {
mLayoutParams.format = PixelFormat.RGBA_8888;
mWindowManager.addView(mView, mLayoutParams);
} else {
FloatActivity.request(mContext, new PermissionListener() {
@Override
public void onSuccess() {
mLayoutParams.format = PixelFormat.RGBA_8888;
mWindowManager.addView(mView, mLayoutParams);
}
@Override
public void onFail() {
}
});
}
}
}
@Override
public void dismiss() {
mWindowManager.removeView(mView);
}
@Override
public void updateXY(int x, int y) {
mLayoutParams.x = mX = x;
mLayoutParams.y = mY = y;
mWindowManager.updateViewLayout(mView, mLayoutParams);
}
@Override
void updateX(int x) {
mLayoutParams.x = mX = x;
mWindowManager.updateViewLayout(mView, mLayoutParams);
}
@Override
void updateY(int y) {
mLayoutParams.y = mY = y;
mWindowManager.updateViewLayout(mView, mLayoutParams);
}
@Override
int getX() {
return mX;
}
@Override
int getY() {
return mY;
}
}

@ -0,0 +1,64 @@
package com.frank.ffmpeg.floating;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.VideoView;
public class FloatPlayerView extends FrameLayout {
private final static String VIDEO_PATH = "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov";
// private final static String VIDEO_PATH = "storage/emulated/0/beyond.mp4";
private VideoView mPlayer;
public FloatPlayerView(Context context) {
super(context);
init(context);
}
public FloatPlayerView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public FloatPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mPlayer = new VideoView(context);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
layoutParams.gravity = Gravity.CENTER;
addView(mPlayer, layoutParams);
mPlayer.setVideoPath(VIDEO_PATH);
mPlayer.requestFocus();
mPlayer.start();
}
public void onPause() {
if (mPlayer != null){
mPlayer.pause();
}
}
public void onResume() {
if (mPlayer != null){
mPlayer.resume();
}
}
public void onDestroy(){
if (mPlayer != null){
mPlayer.stopPlayback();
mPlayer = null;
}
}
}

@ -0,0 +1,96 @@
package com.frank.ffmpeg.floating;
import android.content.Context;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 自定义 toast 方式无需申请权限
*/
class FloatToast extends FloatView {
private Toast toast;
private Object mTN;
private Method show;
private Method hide;
private int mWidth;
private int mHeight;
FloatToast(Context applicationContext) {
toast = new Toast(applicationContext);
}
@Override
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void setView(View view) {
toast.setView(view);
initTN();
}
@Override
public void setGravity(int gravity, int xOffset, int yOffset) {
toast.setGravity(gravity, xOffset, yOffset);
}
@Override
public void init() {
try {
show.invoke(mTN);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void dismiss() {
try {
hide.invoke(mTN);
} catch (Exception e) {
e.printStackTrace();
}
}
private void initTN() {
try {
//todo android P test
Field tnField = toast.getClass().getDeclaredField("mTN");
tnField.setAccessible(true);
mTN = tnField.get(toast);
show = mTN.getClass().getMethod("show");
hide = mTN.getClass().getMethod("hide");
Field tnParamsField = mTN.getClass().getDeclaredField("mParams");
tnParamsField.setAccessible(true);
WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.width = mWidth;
params.height = mHeight;
params.windowAnimations = 0;
Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");
tnNextViewField.setAccessible(true);
tnNextViewField.set(mTN, toast.getView());
} catch (Exception e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,38 @@
package com.frank.ffmpeg.floating;
import android.view.View;
/**
* Created by yhao on 17-11-14.
* https://github.com/yhaolpz
*/
abstract class FloatView {
abstract void setSize(int width, int height);
abstract void setView(View view);
abstract void setGravity(int gravity, int xOffset, int yOffset);
abstract void init();
abstract void dismiss();
void updateXY(int x, int y) {
}
void updateX(int x) {
}
void updateY(int y) {
}
int getX() {
return 0;
}
int getY() {
return 0;
}
}

@ -0,0 +1,171 @@
package com.frank.ffmpeg.floating;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import java.util.HashMap;
import java.util.Map;
/**
* Created by yhao on 2017/12/22.
* https://github.com/yhaolpz
*/
public class FloatWindow {
private FloatWindow() {
}
private static final String mDefaultTag = "default_float_window_tag";
private static Map<String, IFloatWindow> mFloatWindowMap;
public static IFloatWindow get() {
return get(mDefaultTag);
}
public static IFloatWindow get(@NonNull String tag) {
return mFloatWindowMap == null ? null : mFloatWindowMap.get(tag);
}
@MainThread
public static WindowBuilder with(@NonNull Context applicationContext) {
return new WindowBuilder(applicationContext);
}
public static class WindowBuilder {
Context mApplicationContext;
View mView;
private int mLayoutId;
int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
int gravity = Gravity.TOP | Gravity.START;
int xOffset;
int yOffset;
boolean mShow = true;
Class[] mActivities;
int mMoveType = MoveType.fixed;
long mDuration = 300;
TimeInterpolator mInterpolator;
private String mTag = mDefaultTag;
WindowBuilder(Context applicationContext) {
mApplicationContext = applicationContext;
}
public WindowBuilder setView(@NonNull View view) {
mView = view;
return this;
}
public WindowBuilder setView(@LayoutRes int layoutId) {
mLayoutId = layoutId;
return this;
}
public WindowBuilder setWidth(int width) {
mWidth = width;
return this;
}
public WindowBuilder setHeight(int height) {
mHeight = height;
return this;
}
public WindowBuilder setWidth(@Screen.screenType int screenType, float ratio) {
mWidth = (int) ((screenType == Screen.width ?
Util.getScreenWidth(mApplicationContext) :
Util.getScreenHeight(mApplicationContext)) * ratio);
return this;
}
public WindowBuilder setHeight(@Screen.screenType int screenType, float ratio) {
mHeight = (int) ((screenType == Screen.width ?
Util.getScreenWidth(mApplicationContext) :
Util.getScreenHeight(mApplicationContext)) * ratio);
return this;
}
public WindowBuilder setX(int x) {
xOffset = x;
return this;
}
public WindowBuilder setY(int y) {
yOffset = y;
return this;
}
public WindowBuilder setX(@Screen.screenType int screenType, float ratio) {
xOffset = (int) ((screenType == Screen.width ?
Util.getScreenWidth(mApplicationContext) :
Util.getScreenHeight(mApplicationContext)) * ratio);
return this;
}
public WindowBuilder setY(@Screen.screenType int screenType, float ratio) {
yOffset = (int) ((screenType == Screen.width ?
Util.getScreenWidth(mApplicationContext) :
Util.getScreenHeight(mApplicationContext)) * ratio);
return this;
}
/**
* 设置 Activity 过滤器用于指定在哪些界面显示悬浮窗默认全部界面都显示
*
* @param show  过滤类型,子类类型也会生效
* @param activities  过滤界面
*/
public WindowBuilder setFilter(boolean show, @NonNull Class... activities) {
mShow = show;
mActivities = activities;
return this;
}
public WindowBuilder setMoveType(@MoveType.MOVE_TYPE int moveType) {
mMoveType = moveType;
return this;
}
public WindowBuilder setMoveStyle(long duration, @Nullable TimeInterpolator interpolator) {
mDuration = duration;
mInterpolator = interpolator;
return this;
}
public WindowBuilder setTag(@NonNull String tag) {
mTag = tag;
return this;
}
public void build() {
if (mFloatWindowMap == null) {
mFloatWindowMap = new HashMap<>();
}
if (mFloatWindowMap.containsKey(mTag)) {
throw new IllegalArgumentException("FloatWindow of this tag has been added, Please set a new tag for the new FloatWindow");
}
if (mView == null && mLayoutId == 0) {
throw new IllegalArgumentException("View has not been set!");
}
if (mView == null) {
mView = Util.inflate(mApplicationContext, mLayoutId);
}
IFloatWindow floatWindowImpl = new IFloatWindowImpl(this);
mFloatWindowMap.put(mTag, floatWindowImpl);
}
}
}

@ -0,0 +1,30 @@
package com.frank.ffmpeg.floating;
import android.view.View;
/**
* Created by yhao on 2017/12/22.
* https://github.com/yhaolpz
*/
public abstract class IFloatWindow {
public abstract void show();
public abstract void hide();
public abstract int getX();
public abstract int getY();
public abstract void updateX(int x);
public abstract void updateX(@Screen.screenType int screenType,float ratio);
public abstract void updateY(int y);
public abstract void updateY(@Screen.screenType int screenType,float ratio);
public abstract View getView();
abstract void dismiss();
}

@ -0,0 +1,248 @@
package com.frank.ffmpeg.floating;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.os.Build;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
/**
* Created by yhao on 2017/12/22.
* https://github.com/yhaolpz
*/
public class IFloatWindowImpl extends IFloatWindow {
private FloatWindow.WindowBuilder mB;
private FloatView mFloatView;
private boolean isShow;
private boolean once = true;
private ValueAnimator mAnimator;
private TimeInterpolator mDecelerateInterpolator;
IFloatWindowImpl(FloatWindow.WindowBuilder b) {
mB = b;
if (mB.mMoveType == MoveType.fixed) {
if (Build.VERSION.SDK_INT >=25) {
mFloatView = new FloatPhone(b.mApplicationContext);
} else {
mFloatView = new FloatToast(b.mApplicationContext);
}
} else {
mFloatView = new FloatPhone(b.mApplicationContext);
initTouchEvent();
}
mFloatView.setSize(mB.mWidth, mB.mHeight);
mFloatView.setGravity(mB.gravity, mB.xOffset, mB.yOffset);
mFloatView.setView(mB.mView);
new FloatLifecycle(mB.mApplicationContext, mB.mShow, mB.mActivities, new LifecycleListener() {
@Override
public void onShow() {
show();
}
@Override
public void onHide() {
hide();
}
@Override
public void onPostHide() {
postHide();
}
});
}
@Override
public void show() {
if (once) {
mFloatView.init();
once = false;
isShow = true;
} else {
if (isShow) return;
getView().setVisibility(View.VISIBLE);
isShow = true;
}
}
@Override
public void hide() {
if (once || !isShow) return;
getView().setVisibility(View.INVISIBLE);
isShow = false;
}
@Override
void dismiss() {
mFloatView.dismiss();
isShow = false;
}
@Override
public void updateX(int x) {
checkMoveType();
mB.xOffset = x;
mFloatView.updateX(x);
}
@Override
public void updateY(int y) {
checkMoveType();
mB.yOffset = y;
mFloatView.updateY(y);
}
@Override
public void updateX(int screenType, float ratio) {
checkMoveType();
mB.xOffset = (int) ((screenType == Screen.width ?
Util.getScreenWidth(mB.mApplicationContext) :
Util.getScreenHeight(mB.mApplicationContext)) * ratio);
mFloatView.updateX(mB.xOffset);
}
@Override
public void updateY(int screenType, float ratio) {
checkMoveType();
mB.yOffset = (int) ((screenType == Screen.width ?
Util.getScreenWidth(mB.mApplicationContext) :
Util.getScreenHeight(mB.mApplicationContext)) * ratio);
mFloatView.updateY(mB.yOffset);
}
@Override
public int getX() {
return mFloatView.getX();
}
@Override
public int getY() {
return mFloatView.getY();
}
@Override
public View getView() {
return mB.mView;
}
void postHide() {
if (once || !isShow) return;
getView().post(new Runnable() {
@Override
public void run() {
getView().setVisibility(View.INVISIBLE);
}
});
isShow = false;
}
private void checkMoveType() {
if (mB.mMoveType == MoveType.fixed) {
throw new IllegalArgumentException("FloatWindow of this tag is not allowed to move!");
}
}
private void initTouchEvent() {
switch (mB.mMoveType) {
case MoveType.free:
break;
default:
getView().setOnTouchListener(new View.OnTouchListener() {
float lastX, lastY, changeX, changeY;
int newX, newY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getRawX();
lastY = event.getRawY();
cancelAnimator();
break;
case MotionEvent.ACTION_MOVE:
changeX = event.getRawX() - lastX;
changeY = event.getRawY() - lastY;
newX = (int) (mFloatView.getX() + changeX);
newY = (int) (mFloatView.getY() + changeY);
mFloatView.updateXY(newX, newY);
lastX = event.getRawX();
lastY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
switch (mB.mMoveType) {
case MoveType.slide:
int startX = mFloatView.getX();
int endX = (startX * 2 + v.getWidth() >
Util.getScreenWidth(mB.mApplicationContext)) ?
Util.getScreenWidth(mB.mApplicationContext) - v.getWidth() : 0;
mAnimator = ObjectAnimator.ofInt(startX, endX);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int x = (int) animation.getAnimatedValue();
mFloatView.updateX(x);
}
});
startAnimator();
break;
case MoveType.back:
PropertyValuesHolder pvhX = PropertyValuesHolder.ofInt("x", mFloatView.getX(), mB.xOffset);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofInt("y", mFloatView.getY(), mB.yOffset);
mAnimator = ObjectAnimator.ofPropertyValuesHolder(pvhX, pvhY);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int x = (int) animation.getAnimatedValue("x");
int y = (int) animation.getAnimatedValue("y");
mFloatView.updateXY(x, y);
}
});
startAnimator();
break;
}
break;
}
return false;
}
});
}
}
private void startAnimator() {
if (mB.mInterpolator == null) {
if (mDecelerateInterpolator == null) {
mDecelerateInterpolator = new DecelerateInterpolator();
}
mB.mInterpolator = mDecelerateInterpolator;
}
mAnimator.setInterpolator(mB.mInterpolator);
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mAnimator.removeAllUpdateListeners();
mAnimator.removeAllListeners();
mAnimator = null;
}
});
mAnimator.setDuration(mB.mDuration).start();
}
private void cancelAnimator() {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
}
}
}

@ -0,0 +1,15 @@
package com.frank.ffmpeg.floating;
/**
* Created by yhao on 2017/12/22.
* https://github.com/yhaolpz
*/
interface LifecycleListener {
void onShow();
void onHide();
void onPostHide();
}

@ -0,0 +1,24 @@
package com.frank.ffmpeg.floating;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by yhao on 2017/12/22.
* https://github.com/yhaolpz
*/
public class MoveType {
static final int fixed = 0;
public static final int free = 1;
public static final int active = 2;
public static final int slide = 3;
public static final int back = 4;
@IntDef({fixed, free, active, slide, back})
@Retention(RetentionPolicy.SOURCE)
@interface MOVE_TYPE {
}
}

@ -0,0 +1,11 @@
package com.frank.ffmpeg.floating;
/**
* Created by yhao on 2017/11/14.
* https://github.com/yhaolpz
*/
interface PermissionListener {
void onSuccess();
void onFail();
}

@ -0,0 +1,21 @@
package com.frank.ffmpeg.floating;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by yhao on 2017/12/23.
* https://github.com/yhaolpz
*/
public class Screen {
public static final int width = 0;
public static final int height = 1;
@IntDef({width, height})
@Retention(RetentionPolicy.SOURCE)
@interface screenType {
}
}

@ -0,0 +1,50 @@
package com.frank.ffmpeg.floating;
import android.content.Context;
import android.graphics.Point;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.RequiresApi;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
/**
* Created by yhao on 2017/12/22.
* https://github.com/yhaolpz
*/
public class Util {
static View inflate(Context applicationContext, int layoutId) {
LayoutInflater inflate = (LayoutInflater) applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflate.inflate(layoutId, null);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean hasPermission(Context context) {
return Settings.canDrawOverlays(context);
}
private static Point sPoint;
static int getScreenWidth(Context context) {
if (sPoint == null) {
sPoint = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(sPoint);
}
return sPoint.x;
}
static int getScreenHeight(Context context) {
if (sPoint == null) {
sPoint = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(sPoint);
}
return sPoint.y;
}
}

@ -74,4 +74,13 @@
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_filter"/> android:layout_below="@+id/btn_filter"/>
<Button
android:id="@+id/btn_floating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video_floating"
android:layout_marginTop="10dp"
android:layout_centerHorizontal="true"
android:layout_below="@+id/btn_reverse"/>
</RelativeLayout> </RelativeLayout>

@ -39,6 +39,7 @@
<string name="video_denoise">视频降噪</string> <string name="video_denoise">视频降噪</string>
<string name="video_image">视频转图片</string> <string name="video_image">视频转图片</string>
<string name="video_pip">视频画中画</string> <string name="video_pip">视频画中画</string>
<string name="video_floating">悬浮窗播放</string>
<string name="swap">切换</string> <string name="swap">切换</string>
<string name="start">开始</string> <string name="start">开始</string>

Loading…
Cancel
Save