新接入FaceUnity美颜SDK
This commit is contained in:
parent
c6770c1d51
commit
333e4fc1e6
1
FaceUnity/.gitignore
vendored
Normal file
1
FaceUnity/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
70
FaceUnity/build.gradle
Normal file
70
FaceUnity/build.gradle
Normal file
@ -0,0 +1,70 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'img-optimizer'
|
||||
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
packagingOptions {
|
||||
pickFirst "lib/armeabi/libyuvutils.so"
|
||||
pickFirst "lib/arm64-v8a/libyuvutils.so"
|
||||
pickFirst "lib/armeabi-v7a/libyuvutils.so"
|
||||
pickFirst "lib/armeabi/libyuvtools.so"
|
||||
pickFirst "lib/arm64-v8a/libyuvtools.so"
|
||||
pickFirst "lib/armeabi-v7a/libyuvtools.so"
|
||||
exclude "lib/arm64-v8a/libmmcv_api_handgesture.so"
|
||||
exclude "lib/arm64-v8a/libmmcv_api_express.so"
|
||||
exclude "lib/arm64-v8a/libMediaEncoder.so"
|
||||
exclude "lib/arm64-v8a/libarcore_sdk_c.so"
|
||||
exclude "lib/arm64-v8a/libmediadecoder.so"
|
||||
exclude "lib/arm64-v8a/libMediaMuxer.so"
|
||||
exclude "lib/arm64-v8a/libarcore_sdk_jni.so"
|
||||
exclude "lib/arm64-v8a/libMediaUtils.so"
|
||||
exclude "lib/arm64-v8a/libcosmosffmpeg.so"
|
||||
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode rootProject.ext.android.versionCode
|
||||
versionName rootProject.ext.android.versionName
|
||||
manifestPlaceholders = rootProject.ext.manifestPlaceholders
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a","arm64-v8a"
|
||||
}
|
||||
}
|
||||
aaptOptions {
|
||||
cruncherEnabled = false
|
||||
useNewCruncher = false
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'libs','../libs'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation rootProject.ext.dependencies["appcompat-androidx"]
|
||||
implementation rootProject.ext.dependencies["recyclerview-androidx"]
|
||||
//common
|
||||
implementation project(path: ':common')
|
||||
|
||||
implementation 'com.faceunity:core:8.3.1'
|
||||
implementation 'com.faceunity:model:8.3.1'
|
||||
//implementation 'com.faceunity:nama:8.3.1' //底层库-标准版
|
||||
|
||||
|
||||
}
|
0
FaceUnity/consumer-rules.pro
Normal file
0
FaceUnity/consumer-rules.pro
Normal file
21
FaceUnity/proguard-rules.pro
vendored
Normal file
21
FaceUnity/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
||||
package com.yunbao.faceunity;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
assertEquals("com.yunbao.faceunity.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
17
FaceUnity/src/main/AndroidManifest.xml
Normal file
17
FaceUnity/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.yunbao.faceunity"
|
||||
>
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true">
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
FaceUnity/src/main/assets/makeup/chaoa.bundle
Normal file
BIN
FaceUnity/src/main/assets/makeup/chaoa.bundle
Normal file
Binary file not shown.
BIN
FaceUnity/src/main/assets/makeup/dousha.bundle
Normal file
BIN
FaceUnity/src/main/assets/makeup/dousha.bundle
Normal file
Binary file not shown.
BIN
FaceUnity/src/main/assets/makeup/naicha.bundle
Normal file
BIN
FaceUnity/src/main/assets/makeup/naicha.bundle
Normal file
Binary file not shown.
BIN
FaceUnity/src/main/assets/sticker/fashi.bundle
Normal file
BIN
FaceUnity/src/main/assets/sticker/fashi.bundle
Normal file
Binary file not shown.
BIN
FaceUnity/src/main/assets/sticker/sdlu.bundle
Normal file
BIN
FaceUnity/src/main/assets/sticker/sdlu.bundle
Normal file
Binary file not shown.
217
FaceUnity/src/main/java/com/yunbao/faceunity/FaceManager.java
Normal file
217
FaceUnity/src/main/java/com/yunbao/faceunity/FaceManager.java
Normal file
@ -0,0 +1,217 @@
|
||||
package com.yunbao.faceunity;
|
||||
|
||||
import static android.content.Context.SENSOR_SERVICE;
|
||||
|
||||
import static com.lzy.okgo.utils.HttpUtils.runOnUiThread;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.faceunity.core.callback.OperateCallback;
|
||||
import com.faceunity.core.entity.FURenderFrameData;
|
||||
import com.faceunity.core.entity.FURenderInputData;
|
||||
import com.faceunity.core.entity.FURenderOutputData;
|
||||
import com.faceunity.core.enumeration.CameraFacingEnum;
|
||||
import com.faceunity.core.enumeration.FUAIProcessorEnum;
|
||||
import com.faceunity.core.enumeration.FUInputTextureEnum;
|
||||
import com.faceunity.core.enumeration.FUTransformMatrixEnum;
|
||||
import com.faceunity.core.faceunity.FURenderManager;
|
||||
import com.faceunity.core.listener.OnGlRendererListener;
|
||||
import com.faceunity.core.renderer.CameraRenderer;
|
||||
import com.faceunity.core.utils.CameraUtils;
|
||||
import com.faceunity.core.utils.FULogger;
|
||||
import com.yunbao.faceunity.data.FaceUnityDataFactory;
|
||||
import com.yunbao.faceunity.listener.FURendererListener;
|
||||
import com.yunbao.faceunity.ui.FaceUnityView;
|
||||
import com.yunbao.faceunity.utils.Authpack;
|
||||
import com.yunbao.faceunity.utils.CSVUtils;
|
||||
import com.yunbao.faceunity.utils.FURenderer;
|
||||
import com.yunbao.faceunity.utils.FaceCameraConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import cn.rongcloud.rtc.api.RCRTCEngine;
|
||||
import cn.rongcloud.rtc.api.callback.IRCRTCVideoOutputFrameListener;
|
||||
import cn.rongcloud.rtc.api.stream.RCRTCVideoView;
|
||||
import cn.rongcloud.rtc.base.RCRTCVideoFrame;
|
||||
import cn.rongcloud.rtc.core.EglRenderer;
|
||||
import io.rong.callkit.SingleCallActivity;
|
||||
|
||||
/**
|
||||
* 美颜模块管理类,接入测试中
|
||||
*/
|
||||
public class FaceManager implements SensorEventListener {
|
||||
private static boolean isInit = false;
|
||||
private static final String TAG = FaceManager.class.getSimpleName();
|
||||
private FaceUnityDataFactory mFaceUnityDataFactory;
|
||||
|
||||
/**
|
||||
* 初始化美颜模块,在AppContext中调用
|
||||
*/
|
||||
public static void initFaceUnity(Context context) {
|
||||
if (isInit) {
|
||||
return;
|
||||
}
|
||||
FURenderManager.setCoreDebug(FULogger.LogLevel.DEBUG);
|
||||
FURenderManager.setKitDebug(FULogger.LogLevel.DEBUG);
|
||||
FURenderManager.registerFURender(context, Authpack.A(), new OperateCallback() {
|
||||
@Override
|
||||
public void onSuccess(int i, @NonNull String s) {
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(int i, @NonNull String s) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private FURenderer mFURenderer;
|
||||
|
||||
/**
|
||||
* 配置美颜SDK
|
||||
* @param beautyControlView 控制view
|
||||
*/
|
||||
public void initFURender(Context context, FaceUnityView beautyControlView) {
|
||||
FURenderer.getInstance().setup(context);
|
||||
mFURenderer = FURenderer.getInstance();
|
||||
mFURenderer.setInputTextureType(FUInputTextureEnum.FU_ADM_FLAG_COMMON_TEXTURE);
|
||||
mFURenderer.setCameraFacing(CameraFacingEnum.CAMERA_FRONT);
|
||||
mFURenderer.setInputBufferMatrix(FUTransformMatrixEnum.CCROT90_FLIPHORIZONTAL);
|
||||
mFURenderer.setInputTextureMatrix(FUTransformMatrixEnum.CCROT90_FLIPHORIZONTAL);
|
||||
mFURenderer.setOutputMatrix(FUTransformMatrixEnum.CCROT270);
|
||||
mFURenderer.setInputOrientation(CameraUtils.INSTANCE.getCameraOrientation(Camera.CameraInfo.CAMERA_FACING_FRONT));
|
||||
mFURenderer.setMarkFPSEnable(true);
|
||||
mFaceUnityDataFactory = FaceUnityDataFactory.getInstance();
|
||||
beautyControlView.bindDataFactory(mFaceUnityDataFactory);
|
||||
SensorManager mSensorManager = (SensorManager) context.getSystemService(SENSOR_SERVICE);
|
||||
Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
|
||||
|
||||
}
|
||||
|
||||
private boolean mIsFirstFrame = true;
|
||||
private CSVUtils mCSVUtils;
|
||||
private int mSkippedFrames = CAMERA_SWITCH_SKIP_FRAME;
|
||||
// 相机切换跳过 5 帧,如果还有问题,可以增加帧数
|
||||
private static final int CAMERA_SWITCH_SKIP_FRAME = 0;
|
||||
private volatile boolean mSkip;
|
||||
|
||||
/**
|
||||
* 渲染融云视频帧
|
||||
*/
|
||||
public void drawRongFrame(Context context) {
|
||||
RCRTCEngine.getInstance().getDefaultVideoStream().setVideoFrameListener(new IRCRTCVideoOutputFrameListener() {
|
||||
@Override
|
||||
public RCRTCVideoFrame processVideoFrame(RCRTCVideoFrame callVideoFrame) {
|
||||
//Log.i(TAG, "processVideoFrame: egl context " + EGL14.eglGetCurrentContext());
|
||||
int width = callVideoFrame.getWidth();
|
||||
int height = callVideoFrame.getHeight();
|
||||
|
||||
if (mIsFirstFrame) {
|
||||
mIsFirstFrame = false;
|
||||
//initCsvUtil(context);
|
||||
mFURenderer.prepareRenderer(mFURendererListener);
|
||||
}
|
||||
//long start = System.nanoTime();
|
||||
mFURenderer.setInputOrientation(callVideoFrame.getRotation());
|
||||
FURenderOutputData data = mFURenderer.onDrawFrameInputWithReturn(callVideoFrame.getData(), width, height);
|
||||
/* long time = System.nanoTime() - start;
|
||||
if (mCSVUtils != null) {
|
||||
mCSVUtils.writeCsv(null, time);
|
||||
}*/
|
||||
if (mSkippedFrames > 0 || mSkip) {
|
||||
--mSkippedFrames;
|
||||
return callVideoFrame;
|
||||
}
|
||||
if (data != null && data.getImage() != null && data.getImage().getBuffer() != null) {
|
||||
callVideoFrame.setData(data.getImage().getBuffer());
|
||||
}
|
||||
return callVideoFrame;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录渲染工具,调试用,在processVideoFrame里使用
|
||||
*/
|
||||
private void initCsvUtil(Context context) {
|
||||
mCSVUtils = new CSVUtils(context);
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault());
|
||||
String dateStrDir = format.format(new Date(System.currentTimeMillis()));
|
||||
dateStrDir = dateStrDir.replaceAll("-", "").replaceAll("_", "");
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS", Locale.getDefault());
|
||||
String dateStrFile = df.format(new Date());
|
||||
String filePath = Environment.getExternalStoragePublicDirectory("") + dateStrDir + File.separator + "excel-" + dateStrFile + ".csv";
|
||||
Log.d("CSV", "initLog: CSV file path:" + filePath);
|
||||
StringBuilder headerInfo = new StringBuilder();
|
||||
headerInfo.append("version:").append(FURenderer.getInstance().getVersion()).append(CSVUtils.COMMA)
|
||||
.append("机型:").append(android.os.Build.MANUFACTURER).append(android.os.Build.MODEL)
|
||||
.append("处理方式:Texture").append(CSVUtils.COMMA);
|
||||
mCSVUtils.initHeader(filePath, headerInfo);
|
||||
}
|
||||
|
||||
|
||||
private FURendererListener mFURendererListener = new FURendererListener() {
|
||||
|
||||
@Override
|
||||
public void onPrepare() {
|
||||
mFaceUnityDataFactory.bindCurrentRenderer();
|
||||
Log.e(TAG, "mFURendererListener: onPrepare: ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrackStatusChanged(FUAIProcessorEnum type, int status) {
|
||||
Log.e(TAG, "onTrackStatusChanged: 人脸数: " + status);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFpsChanged(double fps, double callTime) {
|
||||
final String FPS = String.format(Locale.getDefault(), "%.2f", fps);
|
||||
Log.d(TAG, "onFpsChanged FPS: " + FPS + ", callTime: " + String.format("%.2f", callTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRelease() {
|
||||
// RongCallClient.getInstance().unregisterVideoFrameObserver();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent sensorEvent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int i) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开渲染界面时注销融云监听器,不然会绿屏
|
||||
*/
|
||||
public void onClose() {
|
||||
RCRTCEngine.getInstance().getDefaultVideoStream().setVideoFrameListener(null);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.yunbao.faceunity.base;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* DESC:RecycleView 通用业务调用
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class BaseDelegate<T> {
|
||||
|
||||
/**
|
||||
* 根据页面以及数据内容返回Item的布局index,默认返回第一个布局
|
||||
*
|
||||
* @param data
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public int getItemViewType(T data, int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为ViewHolder绑定数据item
|
||||
*
|
||||
* @param viewType
|
||||
* @param helper
|
||||
* @param data
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public abstract void convert(int viewType, BaseViewHolder helper, T data, int position);
|
||||
|
||||
/**
|
||||
* 绑定单击事件
|
||||
*
|
||||
* @param view
|
||||
* @param data
|
||||
* @param position
|
||||
*/
|
||||
public void onItemClickListener(View view, T data, int position) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绑定长按事件
|
||||
*
|
||||
* @param view
|
||||
* @param data
|
||||
* @param position
|
||||
*/
|
||||
public boolean onItemLongClickListener(View view, T data, int position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.yunbao.faceunity.base;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.yunbao.faceunity.listener.OnMultiClickListener;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* DESC:RecycleView 通用适配器
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class BaseListAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
|
||||
private ArrayList<T> data;
|
||||
private BaseDelegate<T> viewHolderDelegate;
|
||||
private int[] mLayouts;
|
||||
private HashMap<Integer, BaseViewHolder> mViewHolder = new HashMap<>();
|
||||
|
||||
public BaseListAdapter(ArrayList<T> data, BaseDelegate<T> viewHolderDelegate, int... resLayouts) {
|
||||
mLayouts = resLayouts;
|
||||
this.data = data;
|
||||
this.viewHolderDelegate = viewHolderDelegate;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
|
||||
View view = LayoutInflater.from(viewGroup.getContext()).inflate(getLayoutId(viewType), viewGroup, false);
|
||||
return new BaseViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
|
||||
mViewHolder.put(position, holder);
|
||||
viewHolderDelegate.convert(getItemViewType(position), holder, data.get(position), position);
|
||||
bindViewClickListener(holder, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
|
||||
public void setData(ArrayList<T> items) {
|
||||
data.clear();
|
||||
data.addAll(items);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public T getData(int position) {
|
||||
return data.get(position);
|
||||
}
|
||||
|
||||
|
||||
public BaseViewHolder getViewHolderByPosition(int position) {
|
||||
if (!mViewHolder.containsKey(position)) {
|
||||
return null;
|
||||
}
|
||||
return mViewHolder.get(position);
|
||||
}
|
||||
|
||||
public View getViewByPosition(int position) {
|
||||
if (mViewHolder.get(position) == null) {
|
||||
return null;
|
||||
}
|
||||
return mViewHolder.get(position).itemView;
|
||||
}
|
||||
|
||||
|
||||
private void bindViewClickListener(BaseViewHolder holder, int position) {
|
||||
View view = holder.itemView;
|
||||
view.setOnClickListener(new OnMultiClickListener() {
|
||||
@Override
|
||||
protected void onMultiClick(@Nullable View v) {
|
||||
viewHolderDelegate.onItemClickListener(view, data.get(position), position);
|
||||
}
|
||||
});
|
||||
view.setOnLongClickListener(v -> viewHolderDelegate.onItemLongClickListener(view, data.get(position), position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return viewHolderDelegate.getItemViewType(data.get(position), position);
|
||||
}
|
||||
|
||||
private int getLayoutId(int viewType) {
|
||||
return mLayouts[viewType];
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
package com.yunbao.faceunity.base;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.util.Linkify;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RatingBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:DESC:控件布局绑定
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class BaseViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private SparseArray views;
|
||||
/**
|
||||
* use itemView instead
|
||||
*/
|
||||
private View convertView;
|
||||
|
||||
public BaseViewHolder(final View itemView) {
|
||||
super(itemView);
|
||||
this.views = new SparseArray<>();
|
||||
convertView = itemView;
|
||||
}
|
||||
|
||||
/**
|
||||
* use itemView instead
|
||||
*
|
||||
* @return the ViewHolder root view
|
||||
*/
|
||||
@Deprecated
|
||||
public View getConvertView() {
|
||||
return convertView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will set the text of a TextView.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param value The text to put in the text view.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
|
||||
TextView view = getView(viewId);
|
||||
view.setText(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BaseViewHolder setText(@IdRes int viewId, @StringRes int strId) {
|
||||
TextView view = getView(viewId);
|
||||
view.setText(strId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will set the image of an ImageView from a resource id.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param imageResId The image resource id.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setImageResource(@IdRes int viewId, @DrawableRes int imageResId) {
|
||||
ImageView view = getView(viewId);
|
||||
view.setImageResource(imageResId);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will set background color of a view.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param color A color, not a resource id.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setBackgroundColor(@IdRes int viewId, @ColorInt int color) {
|
||||
View view = getView(viewId);
|
||||
view.setBackgroundColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will set background of a view.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param backgroundRes A resource to use as a background.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setBackgroundRes(@IdRes int viewId, @DrawableRes int backgroundRes) {
|
||||
View view = getView(viewId);
|
||||
view.setBackgroundResource(backgroundRes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will set text color of a TextView.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param textColor The text color (not a resource id).
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setTextColor(@IdRes int viewId, @ColorInt int textColor) {
|
||||
TextView view = getView(viewId);
|
||||
view.setTextColor(textColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Will set the image of an ImageView from a drawable.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param drawable The image drawable.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setImageDrawable(@IdRes int viewId, Drawable drawable) {
|
||||
ImageView view = getView(viewId);
|
||||
view.setImageDrawable(drawable);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action to set the image of an image view. Can be called multiple times.
|
||||
*/
|
||||
public BaseViewHolder setImageBitmap(@IdRes int viewId, Bitmap bitmap) {
|
||||
ImageView view = getView(viewId);
|
||||
view.setImageBitmap(bitmap);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action to set the alpha of a view. Can be called multiple times.
|
||||
* Alpha between 0-1.
|
||||
*/
|
||||
public BaseViewHolder setAlpha(@IdRes int viewId, float value) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
getView(viewId).setAlpha(value);
|
||||
} else {
|
||||
// Pre-honeycomb hack to set Alpha value
|
||||
AlphaAnimation alpha = new AlphaAnimation(value, value);
|
||||
alpha.setDuration(0);
|
||||
alpha.setFillAfter(true);
|
||||
getView(viewId).startAnimation(alpha);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a view visibility to VISIBLE (true) or GONE (false).
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param visible True for VISIBLE, false for GONE.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setGone(@IdRes int viewId, boolean visible) {
|
||||
View view = getView(viewId);
|
||||
view.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a view visibility to VISIBLE (true) or INVISIBLE (false).
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param visible True for VISIBLE, false for INVISIBLE.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setVisible(@IdRes int viewId, boolean visible) {
|
||||
View view = getView(viewId);
|
||||
view.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add links into a TextView.
|
||||
*
|
||||
* @param viewId The id of the TextView to linkify.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder linkify(@IdRes int viewId) {
|
||||
TextView view = getView(viewId);
|
||||
Linkify.addLinks(view, Linkify.ALL);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the typeface to the given viewId, and enable subpixel rendering.
|
||||
*/
|
||||
public BaseViewHolder setTypeface(@IdRes int viewId, Typeface typeface) {
|
||||
TextView view = getView(viewId);
|
||||
view.setTypeface(typeface);
|
||||
view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the typeface to all the given viewIds, and enable subpixel rendering.
|
||||
*/
|
||||
public BaseViewHolder setTypeface(Typeface typeface, int... viewIds) {
|
||||
for (int viewId : viewIds) {
|
||||
TextView view = getView(viewId);
|
||||
view.setTypeface(typeface);
|
||||
view.setPaintFlags(view.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress of a ProgressBar.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param progress The progress.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setProgress(@IdRes int viewId, int progress) {
|
||||
ProgressBar view = getView(viewId);
|
||||
view.setProgress(progress);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the progress and max of a ProgressBar.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param progress The progress.
|
||||
* @param max The max value of a ProgressBar.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setProgress(@IdRes int viewId, int progress, int max) {
|
||||
ProgressBar view = getView(viewId);
|
||||
view.setMax(max);
|
||||
view.setProgress(progress);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the range of a ProgressBar to 0...max.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param max The max value of a ProgressBar.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setMax(@IdRes int viewId, int max) {
|
||||
ProgressBar view = getView(viewId);
|
||||
view.setMax(max);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rating (the number of stars filled) of a RatingBar.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param rating The rating.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setRating(@IdRes int viewId, float rating) {
|
||||
RatingBar view = getView(viewId);
|
||||
view.setRating(rating);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rating (the number of stars filled) and max of a RatingBar.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param rating The rating.
|
||||
* @param max The range of the RatingBar to 0...max.
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setRating(@IdRes int viewId, float rating, int max) {
|
||||
RatingBar view = getView(viewId);
|
||||
view.setMax(max);
|
||||
view.setRating(rating);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tag of the view.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param tag The tag;
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setTag(@IdRes int viewId, Object tag) {
|
||||
View view = getView(viewId);
|
||||
view.setTag(tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tag of the view.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param key The key of tag;
|
||||
* @param tag The tag;
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setTag(@IdRes int viewId, int key, Object tag) {
|
||||
View view = getView(viewId);
|
||||
view.setTag(key, tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checked status of a checkable.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param checked The checked status;
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setChecked(@IdRes int viewId, boolean checked) {
|
||||
View view = getView(viewId);
|
||||
// View unable cast to Checkable
|
||||
if (view instanceof Checkable) {
|
||||
((Checkable) view).setChecked(checked);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the enabled state of this view.
|
||||
*
|
||||
* @param viewId The view id.
|
||||
* @param enable The checked status;
|
||||
* @return The BaseViewHolder for chaining.
|
||||
*/
|
||||
public BaseViewHolder setEnabled(@IdRes int viewId, boolean enable) {
|
||||
View view = getView(viewId);
|
||||
view.setEnabled(enable);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public <T extends View> T getView(int viewId) {
|
||||
View view = (View) views.get(viewId);
|
||||
if (view == null) {
|
||||
view = itemView.findViewById(viewId);
|
||||
views.put(viewId, view);
|
||||
}
|
||||
return (T) view;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.yunbao.faceunity.checkbox;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatCheckBox;
|
||||
|
||||
|
||||
/**
|
||||
* 解决 RadioButton 在 Android4.4 调用setButtonDrawable(null) 和 XML 设置 android:button="@null"无效的问题
|
||||
*
|
||||
* @author Richie on 2020.05.18
|
||||
*/
|
||||
public class CheckBoxCompat extends AppCompatCheckBox {
|
||||
|
||||
public CheckBoxCompat(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CheckBoxCompat(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CheckBoxCompat(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
setButtonDrawable(new StateListDrawable());
|
||||
}
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
package com.yunbao.faceunity.checkbox;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
|
||||
/**
|
||||
* Created by tujh on 2018/4/17.
|
||||
*/
|
||||
public class CheckGroup extends LinearLayout {
|
||||
private static final String LOG_TAG = CheckGroup.class.getSimpleName();
|
||||
|
||||
// holds the checked id; the selection is empty by default
|
||||
private int mCheckedId = View.NO_ID;
|
||||
// tracks children radio buttons checked state
|
||||
private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
|
||||
// when true, mOnCheckedChangeListener discards events
|
||||
private boolean mProtectFromCheckedChange = false;
|
||||
private OnCheckedChangeListener mOnCheckedChangeListener;
|
||||
private PassThroughHierarchyChangeListener mPassThroughListener;
|
||||
private OnDispatchActionUpListener mOnDispatchActionUpListener;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public CheckGroup(Context context) {
|
||||
super(context);
|
||||
setOrientation(VERTICAL);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public CheckGroup(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mChildOnCheckedChangeListener = new CheckedStateTracker();
|
||||
mPassThroughListener = new PassThroughHierarchyChangeListener();
|
||||
super.setOnHierarchyChangeListener(mPassThroughListener);
|
||||
}
|
||||
|
||||
public void setOnDispatchActionUpListener(OnDispatchActionUpListener onDispatchActionUpListener) {
|
||||
mOnDispatchActionUpListener = onDispatchActionUpListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
|
||||
// the user listener is delegated to our pass-through listener
|
||||
mPassThroughListener.mOnHierarchyChangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
if (ev.getAction() == MotionEvent.ACTION_UP) {
|
||||
if (mOnDispatchActionUpListener != null) {
|
||||
mOnDispatchActionUpListener.onDispatchActionUp((int) ev.getX());
|
||||
}
|
||||
}
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
// checks the appropriate radio button as requested in the XML file
|
||||
if (mCheckedId != View.NO_ID) {
|
||||
mProtectFromCheckedChange = true;
|
||||
setCheckedStateForView(mCheckedId, true);
|
||||
mProtectFromCheckedChange = false;
|
||||
setCheckedId(mCheckedId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(View child, int index, ViewGroup.LayoutParams params) {
|
||||
if (child instanceof CheckBox) {
|
||||
final CheckBox button = (CheckBox) child;
|
||||
if (button.isChecked()) {
|
||||
mProtectFromCheckedChange = true;
|
||||
if (mCheckedId != View.NO_ID) {
|
||||
setCheckedStateForView(mCheckedId, false);
|
||||
}
|
||||
mProtectFromCheckedChange = false;
|
||||
setCheckedId(button.getId());
|
||||
}
|
||||
}
|
||||
|
||||
super.addView(child, index, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the selection to the radio button whose identifier is passed in
|
||||
* parameter. Using View.NO_ID as the selection identifier clears the selection;
|
||||
* such an operation is equivalent to invoking {@link #clearCheck()}.</p>
|
||||
*
|
||||
* @param id the unique id of the radio button to select in this group
|
||||
* @see #getCheckedCheckBoxId()
|
||||
* @see #clearCheck()
|
||||
*/
|
||||
public void check(@IdRes int id) {
|
||||
// don't even bother
|
||||
if (id != View.NO_ID && (id == mCheckedId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCheckedId != View.NO_ID) {
|
||||
setCheckedStateForView(mCheckedId, false);
|
||||
}
|
||||
|
||||
if (id != View.NO_ID) {
|
||||
setCheckedStateForView(id, true);
|
||||
}
|
||||
|
||||
setCheckedId(id);
|
||||
}
|
||||
|
||||
private void setCheckedId(@IdRes int id) {
|
||||
mCheckedId = id;
|
||||
if (mOnCheckedChangeListener != null) {
|
||||
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
|
||||
}
|
||||
}
|
||||
|
||||
private void setCheckedStateForView(int viewId, boolean checked) {
|
||||
View checkedView = findViewById(viewId);
|
||||
if (checkedView != null && checkedView instanceof CheckBox) {
|
||||
((CheckBox) checkedView).setChecked(checked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns the identifier of the selected radio button in this group.
|
||||
* Upon empty selection, the returned value is View.NO_ID.</p>
|
||||
*
|
||||
* @return the unique id of the selected radio button in this group
|
||||
* @attr ref android.R.styleable#CheckGroup_checkedButton
|
||||
* @see #check(int)
|
||||
* @see #clearCheck()
|
||||
*/
|
||||
@IdRes
|
||||
public int getCheckedCheckBoxId() {
|
||||
return mCheckedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears the selection. When the selection is cleared, no radio button
|
||||
* in this group is selected and {@link #getCheckedCheckBoxId()} returns
|
||||
* null.</p>
|
||||
*
|
||||
* @see #check(int)
|
||||
* @see #getCheckedCheckBoxId()
|
||||
*/
|
||||
public void clearCheck() {
|
||||
check(View.NO_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Register a callback to be invoked when the checked radio button
|
||||
* changes in this group.</p>
|
||||
*
|
||||
* @param listener the callback to call on checked state change
|
||||
*/
|
||||
public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
|
||||
mOnCheckedChangeListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getAccessibilityClassName() {
|
||||
return CheckGroup.class.getName();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Interface definition for a callback to be invoked when the checked
|
||||
* radio button changed in this group.</p>
|
||||
*/
|
||||
public interface OnCheckedChangeListener {
|
||||
/**
|
||||
* <p>Called when the checked radio button has changed. When the
|
||||
* selection is cleared, checkedId is View.NO_ID.</p>
|
||||
*
|
||||
* @param group the group in which the checked radio button has changed
|
||||
* @param checkedId the unique identifier of the newly checked radio button
|
||||
*/
|
||||
public void onCheckedChanged(CheckGroup group, @IdRes int checkedId);
|
||||
}
|
||||
|
||||
private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
// prevents from infinite recursion
|
||||
if (mProtectFromCheckedChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
int id = buttonView.getId();
|
||||
mProtectFromCheckedChange = true;
|
||||
if (mCheckedId != View.NO_ID && mCheckedId != id) {
|
||||
setCheckedStateForView(mCheckedId, false);
|
||||
}
|
||||
mProtectFromCheckedChange = false;
|
||||
|
||||
setCheckedId(isChecked ? id : View.NO_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A pass-through listener acts upon the events and dispatches them
|
||||
* to another listener. This allows the table layout to set its own internal
|
||||
* hierarchy change listener without preventing the user to setup his.</p>
|
||||
*/
|
||||
private class PassThroughHierarchyChangeListener implements
|
||||
OnHierarchyChangeListener {
|
||||
private OnHierarchyChangeListener mOnHierarchyChangeListener;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onChildViewAdded(View parent, View child) {
|
||||
if (parent == CheckGroup.this && child instanceof CheckBox) {
|
||||
int id = child.getId();
|
||||
// generates an id if it's missing
|
||||
if (id == View.NO_ID) {
|
||||
id = View.generateViewId();
|
||||
child.setId(id);
|
||||
}
|
||||
((CheckBox) child).setOnCheckedChangeListener(
|
||||
mChildOnCheckedChangeListener);
|
||||
}
|
||||
|
||||
if (mOnHierarchyChangeListener != null) {
|
||||
mOnHierarchyChangeListener.onChildViewAdded(parent, child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void onChildViewRemoved(View parent, View child) {
|
||||
if (parent == CheckGroup.this && child instanceof CheckBox) {
|
||||
((CheckBox) child).setOnCheckedChangeListener(null);
|
||||
}
|
||||
|
||||
if (mOnHierarchyChangeListener != null) {
|
||||
mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnDispatchActionUpListener {
|
||||
/**
|
||||
* 分发 action up 事件时回调
|
||||
*
|
||||
* @param x
|
||||
*/
|
||||
void onDispatchActionUp(int x);
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.yunbao.faceunity.control;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import com.yunbao.faceunity.base.BaseListAdapter;
|
||||
import com.yunbao.faceunity.dialog.BaseDialogFragment;
|
||||
import com.yunbao.faceunity.dialog.ConfirmDialogFragment;
|
||||
import com.yunbao.faceunity.listener.OnBottomAnimatorChangeListener;
|
||||
|
||||
/**
|
||||
* DESC:自定义菜单Base类
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class BaseControlView extends FrameLayout {
|
||||
|
||||
protected Context mContext;
|
||||
|
||||
public BaseControlView(@NonNull Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public BaseControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public BaseControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* adapter单项点击,选中状态变更
|
||||
*
|
||||
* @param adapter BaseListAdapter<T>
|
||||
* @param old nt
|
||||
* @param position Int
|
||||
*/
|
||||
protected <T> void changeAdapterSelected(BaseListAdapter<T> adapter, int old, int position) {
|
||||
if (old >= 0 && adapter.getViewByPosition(old) != null) {
|
||||
adapter.getViewByPosition(old).setSelected(false);
|
||||
}
|
||||
if (position >= 0 && adapter.getViewByPosition(position) != null) {
|
||||
adapter.getViewByPosition(position).setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 显示弹框
|
||||
*
|
||||
* @param tip
|
||||
* @param runnable
|
||||
*/
|
||||
protected void showDialog(String tip, Runnable runnable) {
|
||||
ConfirmDialogFragment confirmDialogFragment = ConfirmDialogFragment.newInstance(tip, new BaseDialogFragment.OnClickListener() {
|
||||
@Override
|
||||
public void onConfirm() {
|
||||
runnable.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
|
||||
}
|
||||
});
|
||||
confirmDialogFragment.show(((FragmentActivity) mContext).getSupportFragmentManager(), "ConfirmDialogFragmentReset");
|
||||
}
|
||||
|
||||
|
||||
protected void initHorizontalRecycleView(RecyclerView recyclerView) {
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
|
||||
((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false);
|
||||
}
|
||||
|
||||
|
||||
/****************************************菜单动画*****************************************************/
|
||||
|
||||
|
||||
protected boolean isBottomShow;
|
||||
protected ValueAnimator bottomLayoutAnimator = null;
|
||||
protected OnBottomAnimatorChangeListener onBottomAnimatorChangeListener = null;
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,242 @@
|
||||
package com.yunbao.faceunity.control;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.faceunity.core.utils.DecimalUtils;
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.base.BaseDelegate;
|
||||
import com.yunbao.faceunity.base.BaseListAdapter;
|
||||
import com.yunbao.faceunity.base.BaseViewHolder;
|
||||
import com.yunbao.faceunity.entity.BodyBeautyBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
import com.yunbao.faceunity.infe.AbstractBodyBeautyDataFactory;
|
||||
import com.yunbao.faceunity.seekbar.DiscreteSeekBar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class BodyBeautyControlView extends BaseControlView {
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private DiscreteSeekBar discreteSeekBar;
|
||||
private LinearLayout recoverLayout;
|
||||
private ImageView recoverImageView;
|
||||
private TextView recoverTextView;
|
||||
private SwitchCompat switchCompat;
|
||||
|
||||
private AbstractBodyBeautyDataFactory mDataFactory;
|
||||
private HashMap<String, ModelAttributeData> mModelAttributeRange;
|
||||
private ArrayList<BodyBeautyBean> mBodyBeautyBeans;
|
||||
private BaseListAdapter<BodyBeautyBean> mBodyAdapter;
|
||||
private int mBodyIndex = 0;
|
||||
|
||||
|
||||
public BodyBeautyControlView(@NonNull Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BodyBeautyControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public BodyBeautyControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.layout_body_beauty_control, this);
|
||||
initView();
|
||||
initAdapter();
|
||||
bindListener();
|
||||
}
|
||||
|
||||
|
||||
public void bindDataFactory(AbstractBodyBeautyDataFactory dataFactory) {
|
||||
mDataFactory = dataFactory;
|
||||
mBodyBeautyBeans = mDataFactory.getBodyBeautyParam();
|
||||
mBodyAdapter.setData(mBodyBeautyBeans);
|
||||
mModelAttributeRange = mDataFactory.getModelAttributeRange();
|
||||
BodyBeautyBean data = mBodyBeautyBeans.get(mBodyIndex);
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(data.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
setRecoverEnable(checkParamsChanged());
|
||||
}
|
||||
|
||||
|
||||
private void initView() {
|
||||
recyclerView = findViewById(R.id.recycler_view);
|
||||
discreteSeekBar = findViewById(R.id.seek_bar);
|
||||
recoverLayout = findViewById(R.id.lyt_beauty_recover);
|
||||
recoverImageView = findViewById(R.id.iv_beauty_recover);
|
||||
recoverTextView = findViewById(R.id.tv_beauty_recover);
|
||||
switchCompat = findViewById(R.id.switch_compat);
|
||||
initHorizontalRecycleView(recyclerView);
|
||||
}
|
||||
|
||||
private void initAdapter() {
|
||||
mBodyAdapter = new BaseListAdapter<>(new ArrayList<>(), new BaseDelegate<BodyBeautyBean>() {
|
||||
@Override
|
||||
public void convert(int viewType, BaseViewHolder helper, BodyBeautyBean data, int position) {
|
||||
helper.setText(R.id.tv_control, data.getDesRes());
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
if (DecimalUtils.doubleEquals(value, stand)) {
|
||||
helper.setImageResource(R.id.iv_control, data.getCloseRes());
|
||||
} else {
|
||||
helper.setImageResource(R.id.iv_control, data.getOpenRes());
|
||||
}
|
||||
helper.itemView.setSelected(position == mBodyIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClickListener(View view, BodyBeautyBean data, int position) {
|
||||
if (mBodyIndex != position) {
|
||||
changeAdapterSelected(mBodyAdapter, mBodyIndex, position);
|
||||
mBodyIndex = position;
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(data.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
}
|
||||
}
|
||||
|
||||
}, R.layout.list_item_control_title_image_circle);
|
||||
recyclerView.setAdapter(mBodyAdapter);
|
||||
}
|
||||
|
||||
private void bindListener() {
|
||||
findViewById(R.id.cyt_main).setOnTouchListener((v, event) -> true);
|
||||
discreteSeekBar.setOnProgressChangeListener(new DiscreteSeekBar.OnSimpleProgressChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
|
||||
if (!fromUser) {
|
||||
return;
|
||||
}
|
||||
double valueF = 1.0 * (value - seekBar.getMin()) / 100;
|
||||
BodyBeautyBean data = mBodyBeautyBeans.get(mBodyIndex);
|
||||
double intensity = mDataFactory.getParamIntensity(data.getKey());
|
||||
double range = mModelAttributeRange.get(data.getKey()).getMaxRange();
|
||||
double res = valueF * range;
|
||||
if (!DecimalUtils.doubleEquals(res, intensity)) {
|
||||
mDataFactory.updateParamIntensity(data.getKey(), res);
|
||||
setRecoverEnable(checkParamsChanged());
|
||||
updateBeautyItemUI(mBodyAdapter.getViewHolderByPosition(mBodyIndex), data);
|
||||
}
|
||||
}
|
||||
});
|
||||
findViewById(R.id.lyt_beauty_recover).setOnClickListener((view) -> showDialog(mContext.getString(R.string.dialog_reset_avatar_model), () -> recoverData()));
|
||||
switchCompat.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
mDataFactory.enableBodyBeauty(isChecked);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置滚动条数值
|
||||
*/
|
||||
private void seekToSeekBar(double value, double stand, double range) {
|
||||
if (stand == 0.5) {
|
||||
discreteSeekBar.setMin(-50);
|
||||
discreteSeekBar.setMax(50);
|
||||
discreteSeekBar.setProgress((int) (value * 100 / range - 50));
|
||||
} else {
|
||||
discreteSeekBar.setMin(0);
|
||||
discreteSeekBar.setMax(100);
|
||||
discreteSeekBar.setProgress((int) (value * 100 / range));
|
||||
}
|
||||
discreteSeekBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单项是否为基准值显示
|
||||
*/
|
||||
private void updateBeautyItemUI(BaseViewHolder viewHolder, BodyBeautyBean bean) {
|
||||
double value = mDataFactory.getParamIntensity(bean.getKey());
|
||||
double stand = mModelAttributeRange.get(bean.getKey()).getStandV();
|
||||
if (viewHolder == null) {
|
||||
return;
|
||||
}
|
||||
if (DecimalUtils.doubleEquals(value, stand)) {
|
||||
viewHolder.setImageResource(R.id.iv_control, bean.getCloseRes());
|
||||
} else {
|
||||
viewHolder.setImageResource(R.id.iv_control, bean.getOpenRes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置数据
|
||||
*/
|
||||
private void recoverData() {
|
||||
for (BodyBeautyBean bean : mBodyBeautyBeans) {
|
||||
double intensity = mModelAttributeRange.get(bean.getKey()).getDefaultV();
|
||||
mDataFactory.updateParamIntensity(bean.getKey(), intensity);
|
||||
}
|
||||
BodyBeautyBean data = mBodyBeautyBeans.get(mBodyIndex);
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(data.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
mBodyAdapter.notifyDataSetChanged();
|
||||
setRecoverEnable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历数据确认还原按钮是否可以点击
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private boolean checkParamsChanged() {
|
||||
BodyBeautyBean bean = mBodyBeautyBeans.get(mBodyIndex);
|
||||
double value = mDataFactory.getParamIntensity(bean.getKey());
|
||||
double defaultV = mModelAttributeRange.get(bean.getKey()).getDefaultV();
|
||||
if (!DecimalUtils.doubleEquals(value, defaultV)) {
|
||||
return true;
|
||||
}
|
||||
for (BodyBeautyBean beautyBean : mBodyBeautyBeans) {
|
||||
value = mDataFactory.getParamIntensity(beautyBean.getKey());
|
||||
defaultV = mModelAttributeRange.get(beautyBean.getKey()).getDefaultV();
|
||||
if (!DecimalUtils.doubleEquals(value, defaultV)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置还原按钮状态
|
||||
*
|
||||
* @param enable Boolean
|
||||
*/
|
||||
private void setRecoverEnable(Boolean enable) {
|
||||
if (enable) {
|
||||
recoverImageView.setAlpha(1f);
|
||||
recoverTextView.setAlpha(1f);
|
||||
} else {
|
||||
recoverImageView.setAlpha(0.6f);
|
||||
recoverTextView.setAlpha(0.6f);
|
||||
}
|
||||
recoverLayout.setEnabled(enable);
|
||||
}
|
||||
}
|
@ -0,0 +1,463 @@
|
||||
package com.yunbao.faceunity.control;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.faceunity.core.utils.DecimalUtils;
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.base.BaseDelegate;
|
||||
import com.yunbao.faceunity.base.BaseListAdapter;
|
||||
import com.yunbao.faceunity.base.BaseViewHolder;
|
||||
import com.yunbao.faceunity.checkbox.CheckGroup;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyBean;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyFilterBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
import com.yunbao.faceunity.infe.AbstractFaceBeautyDataFactory;
|
||||
import com.yunbao.faceunity.seekbar.DiscreteSeekBar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class FaceBeautyControlView extends BaseControlView {
|
||||
|
||||
private AbstractFaceBeautyDataFactory mDataFactory;
|
||||
|
||||
/* 美颜、美型 */
|
||||
private HashMap<String, ModelAttributeData> mModelAttributeRange;
|
||||
private ArrayList<FaceBeautyBean> mSkinBeauty;
|
||||
private ArrayList<FaceBeautyBean> mShapeBeauty;
|
||||
private int mSkinIndex = 0;
|
||||
private int mShapeIndex = 1;
|
||||
private BaseListAdapter<FaceBeautyBean> mBeautyAdapter;
|
||||
|
||||
/* 滤镜 */
|
||||
private ArrayList<FaceBeautyFilterBean> mFilters;
|
||||
private BaseListAdapter<FaceBeautyFilterBean> mFiltersAdapter;
|
||||
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private DiscreteSeekBar discreteSeekBar;
|
||||
private CheckGroup checkGroup;
|
||||
private LinearLayout recoverLayout;
|
||||
private ImageView recoverImageView;
|
||||
private TextView recoverTextView;
|
||||
private View lineView;
|
||||
private LinearLayout bottomLayout;
|
||||
private SwitchCompat switchCompat;
|
||||
|
||||
public FaceBeautyControlView(@NonNull Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public FaceBeautyControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public FaceBeautyControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 给控制绑定FaceBeautyController,数据工厂
|
||||
*
|
||||
* @param dataFactory IFaceBeautyDataFactory
|
||||
*/
|
||||
public void bindDataFactory(AbstractFaceBeautyDataFactory dataFactory) {
|
||||
mDataFactory = dataFactory;
|
||||
mModelAttributeRange = dataFactory.getModelAttributeRange();
|
||||
mSkinBeauty = dataFactory.getSkinBeauty();
|
||||
mShapeBeauty = dataFactory.getShapeBeauty();
|
||||
mFilters = dataFactory.getBeautyFilters();
|
||||
mFiltersAdapter.setData(mFilters);
|
||||
checkGroup.check(View.NO_ID);
|
||||
}
|
||||
|
||||
|
||||
private void init() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.layout_face_beauty_control, this);
|
||||
initView();
|
||||
initAdapter();
|
||||
bindListener();
|
||||
|
||||
}
|
||||
// region init
|
||||
|
||||
private void initView() {
|
||||
recyclerView = findViewById(R.id.recycler_view);
|
||||
discreteSeekBar = findViewById(R.id.seek_bar);
|
||||
checkGroup = findViewById(R.id.beauty_radio_group);
|
||||
recoverLayout = findViewById(R.id.lyt_beauty_recover);
|
||||
recoverImageView = findViewById(R.id.iv_beauty_recover);
|
||||
recoverTextView = findViewById(R.id.tv_beauty_recover);
|
||||
lineView = findViewById(R.id.iv_line);
|
||||
bottomLayout = findViewById(R.id.fyt_bottom_view);
|
||||
switchCompat = findViewById(R.id.switch_compat);
|
||||
initHorizontalRecycleView(recyclerView);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造Adapter
|
||||
*/
|
||||
private void initAdapter() {
|
||||
mFiltersAdapter = new BaseListAdapter<>(
|
||||
new ArrayList<>(), new BaseDelegate<FaceBeautyFilterBean>() {
|
||||
@Override
|
||||
public void convert(int viewType, BaseViewHolder helper, FaceBeautyFilterBean data, int position) {
|
||||
helper.setText(R.id.tv_control, data.getDesRes());
|
||||
helper.setImageResource(R.id.iv_control, data.getImageRes());
|
||||
helper.itemView.setSelected(mDataFactory.getCurrentFilterIndex() == position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClickListener(View view, FaceBeautyFilterBean data, int position) {
|
||||
if (mDataFactory.getCurrentFilterIndex() != position) {
|
||||
changeAdapterSelected(mFiltersAdapter, mDataFactory.getCurrentFilterIndex(), position);
|
||||
mDataFactory.setCurrentFilterIndex(position);
|
||||
mDataFactory.onFilterSelected(data.getKey(), data.getIntensity(), data.getDesRes());
|
||||
if (position == 0) {
|
||||
discreteSeekBar.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
seekToSeekBar(data.getIntensity(), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}, R.layout.list_item_control_title_image_square);
|
||||
|
||||
|
||||
mBeautyAdapter = new BaseListAdapter<>(new ArrayList<>(), new BaseDelegate<FaceBeautyBean>() {
|
||||
@Override
|
||||
public void convert(int viewType, BaseViewHolder helper, FaceBeautyBean data, int position) {
|
||||
helper.setText(R.id.tv_control, data.getDesRes());
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
if (DecimalUtils.doubleEquals(value, stand)) {
|
||||
helper.setImageResource(R.id.iv_control, data.getCloseRes());
|
||||
} else {
|
||||
helper.setImageResource(R.id.iv_control, data.getOpenRes());
|
||||
}
|
||||
boolean isShinSelected = checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_skin_beauty;
|
||||
helper.itemView.setSelected(isShinSelected ? mSkinIndex == position : mShapeIndex == position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClickListener(View view, FaceBeautyBean data, int position) {
|
||||
boolean isShinSelected = checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_skin_beauty;
|
||||
if ((isShinSelected && position == mSkinIndex) || (!isShinSelected && position == mShapeIndex)) {
|
||||
return;
|
||||
}
|
||||
if (isShinSelected) {
|
||||
changeAdapterSelected(mBeautyAdapter, mSkinIndex, position);
|
||||
mSkinIndex = position;
|
||||
} else {
|
||||
changeAdapterSelected(mBeautyAdapter, mShapeIndex, position);
|
||||
mShapeIndex = position;
|
||||
}
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(data.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
}
|
||||
}, R.layout.list_item_control_title_image_circle);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绑定监听事件
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void bindListener() {
|
||||
/*拦截触碰事件*/
|
||||
findViewById(R.id.fyt_bottom_view).setOnTouchListener((v, event) -> true);
|
||||
/*菜单控制*/
|
||||
bindBottomRadioListener();
|
||||
/*滑动条控制*/
|
||||
bindSeekBarListener();
|
||||
/*还原数据*/
|
||||
recoverLayout.setOnClickListener((view) -> showDialog(mContext.getString(R.string.dialog_reset_avatar_model), () -> recoverData()));
|
||||
/*渲染开关*/
|
||||
switchCompat.setOnCheckedChangeListener((buttonView, isChecked) -> mDataFactory.enableFaceBeauty(isChecked));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 滚动条绑定事件
|
||||
*/
|
||||
private void bindSeekBarListener() {
|
||||
discreteSeekBar.setOnProgressChangeListener(new DiscreteSeekBar.OnSimpleProgressChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
|
||||
if (!fromUser) {
|
||||
return;
|
||||
}
|
||||
double valueF = 1.0 * (value - seekBar.getMin()) / 100;
|
||||
if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_skin_beauty) {
|
||||
FaceBeautyBean bean = mSkinBeauty.get(mSkinIndex);
|
||||
double range = mModelAttributeRange.get(bean.getKey()).getMaxRange();
|
||||
double res = valueF * range;
|
||||
double intensity = mDataFactory.getParamIntensity(bean.getKey());
|
||||
if (!DecimalUtils.doubleEquals(res, intensity)) {
|
||||
mDataFactory.updateParamIntensity(bean.getKey(), res);
|
||||
setRecoverEnable(checkFaceSkinChanged());
|
||||
updateBeautyItemUI(mBeautyAdapter.getViewHolderByPosition(mSkinIndex), bean);
|
||||
}
|
||||
} else if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_face_shape) {
|
||||
FaceBeautyBean bean = mShapeBeauty.get(mShapeIndex);
|
||||
double range = mModelAttributeRange.get(bean.getKey()).getMaxRange();
|
||||
double res = valueF * range;
|
||||
double intensity = mDataFactory.getParamIntensity(bean.getKey());
|
||||
if (!DecimalUtils.doubleEquals(res, intensity)) {
|
||||
mDataFactory.updateParamIntensity(bean.getKey(), res);
|
||||
setRecoverEnable(checkFaceShapeChanged());
|
||||
updateBeautyItemUI(mBeautyAdapter.getViewHolderByPosition(mShapeIndex), bean);
|
||||
}
|
||||
} else if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_filter) {
|
||||
FaceBeautyFilterBean bean = mFilters.get(mDataFactory.getCurrentFilterIndex());
|
||||
if (!DecimalUtils.doubleEquals(bean.getIntensity(), valueF)) {
|
||||
bean.setIntensity(valueF);
|
||||
mDataFactory.updateFilterIntensity(valueF);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 底部导航栏绑定监听事件,处理RecycleView等相关布局变更
|
||||
*/
|
||||
private void bindBottomRadioListener() {
|
||||
checkGroup.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (checkedId == R.id.beauty_radio_skin_beauty || checkedId == R.id.beauty_radio_face_shape) {
|
||||
discreteSeekBar.setVisibility(View.VISIBLE);
|
||||
recoverLayout.setVisibility(View.VISIBLE);
|
||||
lineView.setVisibility(View.VISIBLE);
|
||||
switchCompat.setVisibility(View.VISIBLE);
|
||||
} else if (checkedId == R.id.beauty_radio_filter) {
|
||||
discreteSeekBar.setVisibility((mDataFactory.getCurrentFilterIndex() == 0) ? View.INVISIBLE : View.VISIBLE);
|
||||
recoverLayout.setVisibility(View.GONE);
|
||||
lineView.setVisibility(View.GONE);
|
||||
switchCompat.setVisibility(View.VISIBLE);
|
||||
} else if (checkedId == View.NO_ID) {
|
||||
mDataFactory.enableFaceBeauty(true);
|
||||
switchCompat.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
if (checkedId == R.id.beauty_radio_skin_beauty) {
|
||||
mBeautyAdapter.setData(mSkinBeauty);
|
||||
recyclerView.setAdapter(mBeautyAdapter);
|
||||
FaceBeautyBean item = mSkinBeauty.get(mSkinIndex);
|
||||
double value = mDataFactory.getParamIntensity(item.getKey());
|
||||
double stand = mModelAttributeRange.get(item.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(item.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
setRecoverEnable(checkFaceSkinChanged());
|
||||
changeBottomLayoutAnimator(true);
|
||||
} else if (checkedId == R.id.beauty_radio_face_shape) {
|
||||
mBeautyAdapter.setData(mShapeBeauty);
|
||||
recyclerView.setAdapter(mBeautyAdapter);
|
||||
FaceBeautyBean item = mShapeBeauty.get(mShapeIndex);
|
||||
double value = mDataFactory.getParamIntensity(item.getKey());
|
||||
double stand = mModelAttributeRange.get(item.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(item.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
setRecoverEnable(checkFaceShapeChanged());
|
||||
changeBottomLayoutAnimator(true);
|
||||
} else if (checkedId == R.id.beauty_radio_filter) {
|
||||
recyclerView.setAdapter(mFiltersAdapter);
|
||||
recyclerView.scrollToPosition(mDataFactory.getCurrentFilterIndex());
|
||||
if (mDataFactory.getCurrentFilterIndex() == 0) {
|
||||
discreteSeekBar.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
seekToSeekBar(mFilters.get(mDataFactory.getCurrentFilterIndex()).getIntensity(), 0.0, 1.0);
|
||||
}
|
||||
changeBottomLayoutAnimator(true);
|
||||
} else if (checkedId == View.NO_ID) {
|
||||
changeBottomLayoutAnimator(false);
|
||||
mDataFactory.enableFaceBeauty(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
// endregion
|
||||
// region 业务处理
|
||||
|
||||
/**
|
||||
* 设置滚动条数值
|
||||
*
|
||||
* @param value Double 结果值
|
||||
* @param stand Double 标准值
|
||||
* @param range Double 范围区间
|
||||
*/
|
||||
private void seekToSeekBar(double value, double stand, double range) {
|
||||
if (stand == 0.5) {
|
||||
discreteSeekBar.setMin(-50);
|
||||
discreteSeekBar.setMax(50);
|
||||
discreteSeekBar.setProgress((int) (value * 100 / range - 50));
|
||||
} else {
|
||||
discreteSeekBar.setMin(0);
|
||||
discreteSeekBar.setMax(100);
|
||||
discreteSeekBar.setProgress((int) (value * 100 / range));
|
||||
}
|
||||
discreteSeekBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单项是否为基准值显示
|
||||
*/
|
||||
private void updateBeautyItemUI(BaseViewHolder viewHolder, FaceBeautyBean bean) {
|
||||
double value = mDataFactory.getParamIntensity(bean.getKey());
|
||||
double stand = mModelAttributeRange.get(bean.getKey()).getStandV();
|
||||
if (viewHolder == null) {
|
||||
return;
|
||||
}
|
||||
if (DecimalUtils.doubleEquals(value, stand)) {
|
||||
viewHolder.setImageResource(R.id.iv_control, bean.getCloseRes());
|
||||
} else {
|
||||
viewHolder.setImageResource(R.id.iv_control, bean.getOpenRes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置还原按钮状态
|
||||
*
|
||||
* @param enable Boolean
|
||||
*/
|
||||
private void setRecoverEnable(Boolean enable) {
|
||||
if (enable) {
|
||||
recoverImageView.setAlpha(1f);
|
||||
recoverTextView.setAlpha(1f);
|
||||
} else {
|
||||
recoverImageView.setAlpha(0.6f);
|
||||
recoverTextView.setAlpha(0.6f);
|
||||
}
|
||||
recoverLayout.setEnabled(enable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 遍历美肤数据确认还原按钮是否可以点击
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private boolean checkFaceSkinChanged() {
|
||||
FaceBeautyBean bean = mSkinBeauty.get(mSkinIndex);
|
||||
double value = mDataFactory.getParamIntensity(bean.getKey());
|
||||
double defaultV = mModelAttributeRange.get(bean.getKey()).getDefaultV();
|
||||
if (!DecimalUtils.doubleEquals(value, defaultV)) {
|
||||
return true;
|
||||
}
|
||||
for (FaceBeautyBean beautyBean : mSkinBeauty) {
|
||||
value = mDataFactory.getParamIntensity(beautyBean.getKey());
|
||||
defaultV = mModelAttributeRange.get(beautyBean.getKey()).getDefaultV();
|
||||
if (!DecimalUtils.doubleEquals(value, defaultV)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历美型数据确认还原按钮是否可以点击
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private boolean checkFaceShapeChanged() {
|
||||
FaceBeautyBean bean = mShapeBeauty.get(mShapeIndex);
|
||||
double value = mDataFactory.getParamIntensity(bean.getKey());
|
||||
double defaultV = mModelAttributeRange.get(bean.getKey()).getDefaultV();
|
||||
if (!DecimalUtils.doubleEquals(value, defaultV)) {
|
||||
return true;
|
||||
}
|
||||
for (FaceBeautyBean beautyBean : mShapeBeauty) {
|
||||
value = mDataFactory.getParamIntensity(beautyBean.getKey());
|
||||
defaultV = mModelAttributeRange.get(beautyBean.getKey()).getDefaultV();
|
||||
if (!DecimalUtils.doubleEquals(value, defaultV)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 还原 美型、美肤数据
|
||||
*/
|
||||
private void recoverData() {
|
||||
if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_skin_beauty) {
|
||||
recoverData(mSkinBeauty, mSkinIndex);
|
||||
} else if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_face_shape) {
|
||||
recoverData(mShapeBeauty, mShapeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置数据
|
||||
*
|
||||
* @param beautyBeans
|
||||
* @param currentIndex
|
||||
*/
|
||||
private void recoverData(ArrayList<FaceBeautyBean> beautyBeans, int currentIndex) {
|
||||
for (FaceBeautyBean bean : beautyBeans) {
|
||||
double intensity = mModelAttributeRange.get(bean.getKey()).getDefaultV();
|
||||
mDataFactory.updateParamIntensity(bean.getKey(), intensity);
|
||||
}
|
||||
FaceBeautyBean data = beautyBeans.get(currentIndex);
|
||||
double value = mDataFactory.getParamIntensity(data.getKey());
|
||||
double stand = mModelAttributeRange.get(data.getKey()).getStandV();
|
||||
double maxRange = mModelAttributeRange.get(data.getKey()).getMaxRange();
|
||||
seekToSeekBar(value, stand, maxRange);
|
||||
mBeautyAdapter.notifyDataSetChanged();
|
||||
setRecoverEnable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 底部动画处理
|
||||
*
|
||||
* @param isOpen Boolean
|
||||
*/
|
||||
private void changeBottomLayoutAnimator(boolean isOpen) {
|
||||
if (isBottomShow == isOpen) {
|
||||
return;
|
||||
}
|
||||
int start = isOpen ? getResources().getDimensionPixelSize(R.dimen.x1) : getResources().getDimensionPixelSize(R.dimen.x268);
|
||||
int end = isOpen ? getResources().getDimensionPixelSize(R.dimen.x268) : getResources().getDimensionPixelSize(R.dimen.x1);
|
||||
if (bottomLayoutAnimator != null && bottomLayoutAnimator.isRunning()) {
|
||||
bottomLayoutAnimator.end();
|
||||
}
|
||||
bottomLayoutAnimator = ValueAnimator.ofInt(start, end).setDuration(150);
|
||||
bottomLayoutAnimator.addUpdateListener(animation -> {
|
||||
int height = (int) animation.getAnimatedValue();
|
||||
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) bottomLayout.getLayoutParams();
|
||||
params.height = height;
|
||||
bottomLayout.setLayoutParams(params);
|
||||
if (onBottomAnimatorChangeListener != null) {
|
||||
float showRate = 1.0f * (height - start) / (end - start);
|
||||
onBottomAnimatorChangeListener.onBottomAnimatorChangeListener(isOpen ? showRate : 1 - showRate);
|
||||
}
|
||||
if (DecimalUtils.floatEquals(animation.getAnimatedFraction(), 1.0f) && isOpen) {
|
||||
switchCompat.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
bottomLayoutAnimator.start();
|
||||
isBottomShow = isOpen;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package com.yunbao.faceunity.control;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.faceunity.core.utils.DecimalUtils;
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.base.BaseDelegate;
|
||||
import com.yunbao.faceunity.base.BaseListAdapter;
|
||||
import com.yunbao.faceunity.base.BaseViewHolder;
|
||||
import com.yunbao.faceunity.entity.MakeupCombinationBean;
|
||||
import com.yunbao.faceunity.infe.AbstractMakeupDataFactory;
|
||||
import com.yunbao.faceunity.seekbar.DiscreteSeekBar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class MakeupControlView extends BaseControlView {
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private DiscreteSeekBar discreteSeekBar;
|
||||
|
||||
private AbstractMakeupDataFactory mDataFactory;
|
||||
|
||||
/* 组合妆容 */
|
||||
private BaseListAdapter<MakeupCombinationBean> mCombinationAdapter;
|
||||
|
||||
public MakeupControlView(@NonNull Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public MakeupControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public MakeupControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.layout_make_up_control, this);
|
||||
initView();
|
||||
initAdapter();
|
||||
bindListener();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
recyclerView = findViewById(R.id.recycler_view);
|
||||
discreteSeekBar = findViewById(R.id.seek_bar);
|
||||
initHorizontalRecycleView(recyclerView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给控制绑定FaceBeautyController,MakeupController 数据工厂
|
||||
*
|
||||
* @param dataFactory IFaceBeautyDataFactory
|
||||
*/
|
||||
public void bindDataFactory(AbstractMakeupDataFactory dataFactory) {
|
||||
mDataFactory = dataFactory;
|
||||
mCombinationAdapter.setData(dataFactory.getMakeupCombinations());
|
||||
showCombinationSeekBar(mCombinationAdapter.getData(dataFactory.getCurrentCombinationIndex()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 组合妆容Adapter
|
||||
*/
|
||||
private void initAdapter() {
|
||||
mCombinationAdapter = new BaseListAdapter<>(new ArrayList<>(), new BaseDelegate<MakeupCombinationBean>() {
|
||||
@Override
|
||||
public void convert(int viewType, BaseViewHolder helper, MakeupCombinationBean data, int position) {
|
||||
helper.setText(R.id.tv_control, data.getDesRes());
|
||||
helper.setImageResource(R.id.iv_control, data.getImageRes());
|
||||
helper.itemView.setSelected(position == mDataFactory.getCurrentCombinationIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClickListener(View view, MakeupCombinationBean data, int position) {
|
||||
if (position != mDataFactory.getCurrentCombinationIndex()) {
|
||||
changeAdapterSelected(mCombinationAdapter, mDataFactory.getCurrentCombinationIndex(), position);
|
||||
mDataFactory.setCurrentCombinationIndex(position);
|
||||
mDataFactory.onMakeupCombinationSelected(data);
|
||||
showCombinationSeekBar(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}, R.layout.list_item_control_title_image_square);
|
||||
recyclerView.setAdapter(mCombinationAdapter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 绑定监听事件
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void bindListener() {
|
||||
/*组合妆容强度变更回调*/
|
||||
discreteSeekBar.setOnProgressChangeListener(new DiscreteSeekBar.OnSimpleProgressChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(DiscreteSeekBar seekBar, int value, boolean fromUser) {
|
||||
if (!fromUser) {
|
||||
return;
|
||||
}
|
||||
double valueF = 1.0f * (value - seekBar.getMin()) / 100;
|
||||
MakeupCombinationBean combination = mCombinationAdapter.getData(mDataFactory.getCurrentCombinationIndex());
|
||||
if (!DecimalUtils.doubleEquals(valueF, combination.getIntensity())) {
|
||||
combination.setIntensity(valueF);
|
||||
mDataFactory.updateCombinationIntensity(valueF);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//endregion init
|
||||
|
||||
|
||||
// region Adapter
|
||||
|
||||
|
||||
// region 视图控制
|
||||
|
||||
/**
|
||||
* 选中组合妆容,控制强度调节器以及自定义按钮状态变更
|
||||
*/
|
||||
private void showCombinationSeekBar(MakeupCombinationBean data) {
|
||||
if (data.getBundlePath() == null) {
|
||||
discreteSeekBar.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
discreteSeekBar.setVisibility(View.VISIBLE);
|
||||
discreteSeekBar.setProgress((int) (data.getIntensity() * 100));
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.yunbao.faceunity.control;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.base.BaseDelegate;
|
||||
import com.yunbao.faceunity.base.BaseListAdapter;
|
||||
import com.yunbao.faceunity.base.BaseViewHolder;
|
||||
import com.yunbao.faceunity.entity.PropBean;
|
||||
import com.yunbao.faceunity.infe.AbstractPropDataFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class PropControlView extends BaseControlView {
|
||||
|
||||
private AbstractPropDataFactory mDataFactory;
|
||||
private BaseListAdapter<PropBean> mPropAdapter;
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
|
||||
public PropControlView(@NonNull Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public PropControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public PropControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
// region init
|
||||
|
||||
private void init() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.layout_effect_control, this);
|
||||
initView();
|
||||
initAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 给控制绑定 EffectController,数据工厂
|
||||
*
|
||||
* @param dataFactory IFaceBeautyDataFactory
|
||||
*/
|
||||
public void bindDataFactory(AbstractPropDataFactory dataFactory) {
|
||||
mDataFactory = dataFactory;
|
||||
mPropAdapter.setData(dataFactory.getPropBeans());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* View初始化
|
||||
*/
|
||||
private void initView() {
|
||||
recyclerView = findViewById(R.id.recycler_view);
|
||||
initHorizontalRecycleView(recyclerView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter初始化
|
||||
*/
|
||||
private void initAdapter() {
|
||||
mPropAdapter = new BaseListAdapter<>(new ArrayList<>(), new BaseDelegate<PropBean>() {
|
||||
@Override
|
||||
public void convert(int viewType, BaseViewHolder helper, PropBean data, int position) {
|
||||
helper.setImageResource(R.id.iv_control, data.getIconId());
|
||||
helper.itemView.setSelected(position == mDataFactory.getCurrentPropIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClickListener(View view, PropBean data, int position) {
|
||||
if (mDataFactory.getCurrentPropIndex() != position) {
|
||||
changeAdapterSelected(mPropAdapter, mDataFactory.getCurrentPropIndex(), position);
|
||||
mDataFactory.setCurrentPropIndex(position);
|
||||
mDataFactory.onItemSelected(data);
|
||||
}
|
||||
|
||||
}
|
||||
}, R.layout.list_item_control_image_circle);
|
||||
recyclerView.setAdapter(mPropAdapter);
|
||||
}
|
||||
|
||||
|
||||
// endregion
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
package com.yunbao.faceunity.data;
|
||||
|
||||
import com.faceunity.core.controller.bodyBeauty.BodyBeautyParam;
|
||||
import com.faceunity.core.entity.FUBundleData;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.faceunity.core.model.bodyBeauty.BodyBeauty;
|
||||
|
||||
import com.yunbao.faceunity.entity.BodyBeautyBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
import com.yunbao.faceunity.infe.AbstractBodyBeautyDataFactory;
|
||||
import com.yunbao.faceunity.repo.BodyBeautySource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:美体业务工厂
|
||||
* Created on 2021/3/2
|
||||
*/
|
||||
public class BodyBeautyDataFactory extends AbstractBodyBeautyDataFactory {
|
||||
|
||||
|
||||
interface BodyBeautySetParamInterface {
|
||||
void setValue(double value);
|
||||
}
|
||||
|
||||
interface BodyBeautyGetParamInterface {
|
||||
double getValue();
|
||||
}
|
||||
|
||||
|
||||
/*渲染控制器*/
|
||||
private final FURenderKit mFURenderKit = FURenderKit.getInstance();
|
||||
|
||||
/*美体数据模型*/
|
||||
public final BodyBeauty bodyBeauty;
|
||||
|
||||
public BodyBeautyDataFactory() {
|
||||
bodyBeauty = new BodyBeauty(new FUBundleData(BodyBeautySource.BUNDLE_BODY_BEAUTY));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取美体属性列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public ArrayList<BodyBeautyBean> getBodyBeautyParam() {
|
||||
return BodyBeautySource.buildBodyBeauty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取美体扩展参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public HashMap<String, ModelAttributeData> getModelAttributeRange() {
|
||||
return BodyBeautySource.buildModelAttributeRange();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取模型参数
|
||||
*
|
||||
* @param key 名称标识
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public double getParamIntensity(String key) {
|
||||
if (bodyBeautyGetMapping.containsKey(key)) {
|
||||
return bodyBeautyGetMapping.get(key).getValue();
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性参数
|
||||
*
|
||||
* @param key 名称标识
|
||||
* @param value 结果值
|
||||
*/
|
||||
@Override
|
||||
public void updateParamIntensity(String key, double value) {
|
||||
if (bodyBeautySetMapping.containsKey(key)) {
|
||||
bodyBeautySetMapping.get(key).setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前模型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private BodyBeauty getCurrentBodyBeautyModel() {
|
||||
return bodyBeauty;
|
||||
}
|
||||
|
||||
/**
|
||||
* 美体开关设置
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
@Override
|
||||
public void enableBodyBeauty(boolean enable) {
|
||||
if (mFURenderKit.getBodyBeauty() != null) {
|
||||
mFURenderKit.getBodyBeauty().setEnable(enable);
|
||||
}
|
||||
}
|
||||
|
||||
/*模型映射设置模型值*/
|
||||
private final HashMap<String, BodyBeautySetParamInterface> bodyBeautySetMapping = new HashMap<String, BodyBeautySetParamInterface>() {
|
||||
{
|
||||
put(BodyBeautyParam.BODY_SLIM_INTENSITY, value -> getCurrentBodyBeautyModel().setBodySlimIntensity(value));
|
||||
put(BodyBeautyParam.LEG_STRETCH_INTENSITY, value -> getCurrentBodyBeautyModel().setLegStretchIntensity(value));
|
||||
put(BodyBeautyParam.WAIST_SLIM_INTENSITY, value -> getCurrentBodyBeautyModel().setWaistSlimIntensity(value));
|
||||
put(BodyBeautyParam.SHOULDER_SLIM_INTENSITY, value -> getCurrentBodyBeautyModel().setShoulderSlimIntensity(value));
|
||||
put(BodyBeautyParam.HIP_SLIM_INTENSITY, value -> getCurrentBodyBeautyModel().setHipSlimIntensity(value));
|
||||
put(BodyBeautyParam.HEAD_SLIM_INTENSITY, value -> getCurrentBodyBeautyModel().setHeadSlimIntensity(value));
|
||||
put(BodyBeautyParam.LEG_SLIM_INTENSITY, value -> getCurrentBodyBeautyModel().setLegSlimIntensity(value));
|
||||
}
|
||||
};
|
||||
|
||||
/*模型映射获取模型值*/
|
||||
HashMap<String, BodyBeautyGetParamInterface> bodyBeautyGetMapping = new HashMap<String, BodyBeautyGetParamInterface>() {
|
||||
{
|
||||
put(BodyBeautyParam.BODY_SLIM_INTENSITY, () -> getCurrentBodyBeautyModel().getBodySlimIntensity());
|
||||
put(BodyBeautyParam.LEG_STRETCH_INTENSITY, () -> getCurrentBodyBeautyModel().getLegStretchIntensity());
|
||||
put(BodyBeautyParam.WAIST_SLIM_INTENSITY, () -> getCurrentBodyBeautyModel().getWaistSlimIntensity());
|
||||
put(BodyBeautyParam.SHOULDER_SLIM_INTENSITY, () -> getCurrentBodyBeautyModel().getShoulderSlimIntensity());
|
||||
put(BodyBeautyParam.HIP_SLIM_INTENSITY, () -> getCurrentBodyBeautyModel().getHipSlimIntensity());
|
||||
put(BodyBeautyParam.HEAD_SLIM_INTENSITY, () -> getCurrentBodyBeautyModel().getHeadSlimIntensity());
|
||||
put(BodyBeautyParam.LEG_SLIM_INTENSITY, () -> getCurrentBodyBeautyModel().getLegSlimIntensity());
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FURenderKit加载当前特效
|
||||
*/
|
||||
public void bindCurrentRenderer() {
|
||||
mFURenderKit.setBodyBeauty(bodyBeauty);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
package com.yunbao.faceunity.data;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.faceunity.core.controller.facebeauty.FaceBeautyParam;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.faceunity.core.model.facebeauty.FaceBeauty;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyBean;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyFilterBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
import com.yunbao.faceunity.infe.AbstractFaceBeautyDataFactory;
|
||||
import com.yunbao.faceunity.repo.FaceBeautySource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:美颜业务工厂
|
||||
* Created on 2021/3/1
|
||||
*/
|
||||
public class FaceBeautyDataFactory extends AbstractFaceBeautyDataFactory {
|
||||
|
||||
|
||||
interface FaceBeautySetParamInterface {
|
||||
/**
|
||||
* 设置属性值
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
void setValue(double value);
|
||||
}
|
||||
|
||||
interface FaceBeautyGetParamInterface {
|
||||
/**
|
||||
* 获取属性值
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
double getValue();
|
||||
}
|
||||
|
||||
/*渲染控制器*/
|
||||
private FURenderKit mFURenderKit = FURenderKit.getInstance();
|
||||
|
||||
/*当前生效美颜数据模型*/
|
||||
public FaceBeauty defaultFaceBeauty = FaceBeautySource.getDefaultFaceBeauty();
|
||||
|
||||
/*默认滤镜选中下标*/
|
||||
private int currentFilterIndex = 0;
|
||||
|
||||
/**
|
||||
* 获取美肤参数列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public ArrayList<FaceBeautyBean> getSkinBeauty() {
|
||||
return FaceBeautySource.buildSkinParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取美型参数列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public ArrayList<FaceBeautyBean> getShapeBeauty() {
|
||||
return FaceBeautySource.buildShapeParams();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取美肤、美型扩展参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public HashMap<String, ModelAttributeData> getModelAttributeRange() {
|
||||
return FaceBeautySource.buildModelAttributeRange();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取滤镜参数列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public ArrayList<FaceBeautyFilterBean> getBeautyFilters() {
|
||||
ArrayList<FaceBeautyFilterBean> filterBeans = FaceBeautySource.buildFilters();
|
||||
for (int i = 0; i < filterBeans.size(); i++) {
|
||||
if (filterBeans.get(i).getKey().equals(defaultFaceBeauty.getFilterName())) {
|
||||
filterBeans.get(i).setIntensity(defaultFaceBeauty.getFilterIntensity());
|
||||
currentFilterIndex = i;
|
||||
}
|
||||
|
||||
}
|
||||
return filterBeans;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前滤镜下标
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int getCurrentFilterIndex() {
|
||||
return currentFilterIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前滤镜下标
|
||||
*
|
||||
* @param currentFilterIndex
|
||||
*/
|
||||
@Override
|
||||
public void setCurrentFilterIndex(int currentFilterIndex) {
|
||||
this.currentFilterIndex = currentFilterIndex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 美颜开关设置
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
@Override
|
||||
public void enableFaceBeauty(boolean enable) {
|
||||
if (mFURenderKit.getFaceBeauty() != null) {
|
||||
mFURenderKit.getFaceBeauty().setEnable(enable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型参数
|
||||
*
|
||||
* @param key 名称标识
|
||||
* @return 属性值
|
||||
*/
|
||||
@Override
|
||||
public double getParamIntensity(@NonNull String key) {
|
||||
if (faceBeautyGetMapping.containsKey(key)) {
|
||||
return faceBeautyGetMapping.get(key).getValue();
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型参数
|
||||
*
|
||||
* @param key 名称标识
|
||||
* @param value 属性值
|
||||
*/
|
||||
@Override
|
||||
public void updateParamIntensity(@NonNull String key, double value) {
|
||||
if (faceBeautySetMapping.containsKey(key)) {
|
||||
faceBeautySetMapping.get(key).setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换滤镜
|
||||
*
|
||||
* @param name 滤镜名称标识
|
||||
* @param intensity 滤镜强度
|
||||
* @param resID 滤镜名称
|
||||
*/
|
||||
@Override
|
||||
public void onFilterSelected(@NonNull String name, double intensity, int resID) {
|
||||
defaultFaceBeauty.setFilterName(name);
|
||||
defaultFaceBeauty.setFilterIntensity(intensity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更换滤镜强度
|
||||
*
|
||||
* @param intensity 滤镜强度
|
||||
*/
|
||||
@Override
|
||||
public void updateFilterIntensity(double intensity) {
|
||||
defaultFaceBeauty.setFilterIntensity(intensity);
|
||||
}
|
||||
|
||||
|
||||
/*模型映射设置模型值*/
|
||||
private final HashMap<String, FaceBeautySetParamInterface> faceBeautySetMapping = new HashMap<String, FaceBeautySetParamInterface>() {{
|
||||
put(FaceBeautyParam.COLOR_INTENSITY, defaultFaceBeauty::setColorIntensity);
|
||||
put(FaceBeautyParam.BLUR_INTENSITY, defaultFaceBeauty::setBlurIntensity);
|
||||
put(FaceBeautyParam.RED_INTENSITY, defaultFaceBeauty::setRedIntensity);
|
||||
put(FaceBeautyParam.SHARPEN_INTENSITY, defaultFaceBeauty::setSharpenIntensity);
|
||||
put(FaceBeautyParam.EYE_BRIGHT_INTENSITY, defaultFaceBeauty::setEyeBrightIntensity);
|
||||
put(FaceBeautyParam.TOOTH_WHITEN_INTENSITY, defaultFaceBeauty::setToothIntensity);
|
||||
put(FaceBeautyParam.REMOVE_POUCH_INTENSITY, defaultFaceBeauty::setRemovePouchIntensity);
|
||||
put(FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY, defaultFaceBeauty::setRemoveLawPatternIntensity);
|
||||
/*美型*/
|
||||
put(FaceBeautyParam.FACE_SHAPE_INTENSITY, defaultFaceBeauty::setSharpenIntensity);
|
||||
put(FaceBeautyParam.CHEEK_THINNING_INTENSITY, defaultFaceBeauty::setCheekThinningIntensity);
|
||||
put(FaceBeautyParam.CHEEK_V_INTENSITY, defaultFaceBeauty::setCheekVIntensity);
|
||||
put(FaceBeautyParam.CHEEK_NARROW_INTENSITY_V2, defaultFaceBeauty::setCheekNarrowIntensityV2);
|
||||
put(FaceBeautyParam.CHEEK_SHORT_INTENSITY, defaultFaceBeauty::setCheekShortIntensity);
|
||||
put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_V2, defaultFaceBeauty::setCheekSmallIntensityV2);
|
||||
put(FaceBeautyParam.INTENSITY_CHEEKBONES_INTENSITY, defaultFaceBeauty::setCheekBonesIntensity);
|
||||
put(FaceBeautyParam.INTENSITY_LOW_JAW_INTENSITY, defaultFaceBeauty::setLowerJawIntensity);
|
||||
put(FaceBeautyParam.EYE_ENLARGING_INTENSITY_V2, defaultFaceBeauty::setEyeEnlargingIntensityV2);
|
||||
put(FaceBeautyParam.EYE_CIRCLE_INTENSITY, defaultFaceBeauty::setEyeCircleIntensity);
|
||||
put(FaceBeautyParam.CHIN_INTENSITY, defaultFaceBeauty::setChinIntensity);
|
||||
put(FaceBeautyParam.FOREHEAD_INTENSITY_V2, defaultFaceBeauty::setForHeadIntensityV2);
|
||||
put(FaceBeautyParam.NOSE_INTENSITY_V2, defaultFaceBeauty::setNoseIntensityV2);
|
||||
put(FaceBeautyParam.MOUTH_INTENSITY_V2, defaultFaceBeauty::setMouthIntensityV2);
|
||||
put(FaceBeautyParam.CANTHUS_INTENSITY, defaultFaceBeauty::setCanthusIntensity);
|
||||
put(FaceBeautyParam.EYE_SPACE_INTENSITY, defaultFaceBeauty::setEyeSpaceIntensity);
|
||||
put(FaceBeautyParam.EYE_ROTATE_INTENSITY, defaultFaceBeauty::setEyeRotateIntensity);
|
||||
put(FaceBeautyParam.LONG_NOSE_INTENSITY, defaultFaceBeauty::setLongNoseIntensity);
|
||||
put(FaceBeautyParam.PHILTRUM_INTENSITY, defaultFaceBeauty::setPhiltrumIntensity);
|
||||
put(FaceBeautyParam.SMILE_INTENSITY, defaultFaceBeauty::setSmileIntensity);
|
||||
}};
|
||||
|
||||
/*模型映射获取模型值*/
|
||||
HashMap<String, FaceBeautyGetParamInterface> faceBeautyGetMapping = new HashMap<String, FaceBeautyGetParamInterface>() {
|
||||
{
|
||||
put(FaceBeautyParam.COLOR_INTENSITY, defaultFaceBeauty::getColorIntensity);
|
||||
put(FaceBeautyParam.BLUR_INTENSITY, defaultFaceBeauty::getBlurIntensity);
|
||||
put(FaceBeautyParam.RED_INTENSITY, defaultFaceBeauty::getRedIntensity);
|
||||
put(FaceBeautyParam.SHARPEN_INTENSITY, defaultFaceBeauty::getSharpenIntensity);
|
||||
put(FaceBeautyParam.EYE_BRIGHT_INTENSITY, defaultFaceBeauty::getEyeBrightIntensity);
|
||||
put(FaceBeautyParam.TOOTH_WHITEN_INTENSITY, defaultFaceBeauty::getToothIntensity);
|
||||
put(FaceBeautyParam.REMOVE_POUCH_INTENSITY, defaultFaceBeauty::getRemovePouchIntensity);
|
||||
put(FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY, defaultFaceBeauty::getRemoveLawPatternIntensity);
|
||||
/*美型*/
|
||||
put(FaceBeautyParam.FACE_SHAPE_INTENSITY, defaultFaceBeauty::getSharpenIntensity);
|
||||
put(FaceBeautyParam.CHEEK_THINNING_INTENSITY, defaultFaceBeauty::getCheekThinningIntensity);
|
||||
put(FaceBeautyParam.CHEEK_V_INTENSITY, defaultFaceBeauty::getCheekVIntensity);
|
||||
put(FaceBeautyParam.CHEEK_NARROW_INTENSITY_V2, defaultFaceBeauty::getCheekNarrowIntensityV2);
|
||||
put(FaceBeautyParam.CHEEK_SHORT_INTENSITY, defaultFaceBeauty::getCheekShortIntensity);
|
||||
put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_V2, defaultFaceBeauty::getCheekSmallIntensityV2);
|
||||
put(FaceBeautyParam.INTENSITY_CHEEKBONES_INTENSITY, defaultFaceBeauty::getCheekBonesIntensity);
|
||||
put(FaceBeautyParam.INTENSITY_LOW_JAW_INTENSITY, defaultFaceBeauty::getLowerJawIntensity);
|
||||
put(FaceBeautyParam.EYE_ENLARGING_INTENSITY_V2, defaultFaceBeauty::getEyeEnlargingIntensityV2);
|
||||
put(FaceBeautyParam.EYE_CIRCLE_INTENSITY, defaultFaceBeauty::getEyeCircleIntensity);
|
||||
put(FaceBeautyParam.CHIN_INTENSITY, defaultFaceBeauty::getChinIntensity);
|
||||
put(FaceBeautyParam.FOREHEAD_INTENSITY_V2, defaultFaceBeauty::getForHeadIntensityV2);
|
||||
put(FaceBeautyParam.NOSE_INTENSITY_V2, defaultFaceBeauty::getNoseIntensityV2);
|
||||
put(FaceBeautyParam.MOUTH_INTENSITY_V2, defaultFaceBeauty::getMouthIntensityV2);
|
||||
put(FaceBeautyParam.CANTHUS_INTENSITY, defaultFaceBeauty::getCanthusIntensity);
|
||||
put(FaceBeautyParam.EYE_SPACE_INTENSITY, defaultFaceBeauty::getEyeSpaceIntensity);
|
||||
put(FaceBeautyParam.EYE_ROTATE_INTENSITY, defaultFaceBeauty::getEyeRotateIntensity);
|
||||
put(FaceBeautyParam.LONG_NOSE_INTENSITY, defaultFaceBeauty::getLongNoseIntensity);
|
||||
put(FaceBeautyParam.PHILTRUM_INTENSITY, defaultFaceBeauty::getPhiltrumIntensity);
|
||||
put(FaceBeautyParam.SMILE_INTENSITY, defaultFaceBeauty::getSmileIntensity);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FURenderKit加载当前特效
|
||||
*/
|
||||
public void bindCurrentRenderer() {
|
||||
mFURenderKit.setFaceBeauty(defaultFaceBeauty);
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package com.yunbao.faceunity.data;
|
||||
|
||||
import com.faceunity.core.enumeration.FUAIProcessorEnum;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.yunbao.faceunity.utils.FURenderer;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/25
|
||||
*/
|
||||
public class FaceUnityDataFactory {
|
||||
|
||||
/**
|
||||
* 道具数据工厂
|
||||
*/
|
||||
public FaceBeautyDataFactory mFaceBeautyDataFactory;
|
||||
public BodyBeautyDataFactory mBodyBeautyDataFactory;
|
||||
public MakeupDataFactory mMakeupDataFactory;
|
||||
public PropDataFactory mPropDataFactory;
|
||||
|
||||
|
||||
private FURenderKit mFURenderKit = FURenderKit.getInstance();
|
||||
private FURenderer mFURenderer = FURenderer.getInstance();
|
||||
|
||||
private static FaceUnityDataFactory sInstance;
|
||||
public static FaceUnityDataFactory getInstance() {
|
||||
if (sInstance == null) {
|
||||
synchronized (FaceUnityDataFactory.class) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new FaceUnityDataFactory(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public static void release() {
|
||||
sInstance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 道具加载标识
|
||||
*/
|
||||
public int currentFunctionIndex;
|
||||
private boolean hasFaceBeautyLoaded = false;
|
||||
private boolean hasBodyBeautyLoaded = false;
|
||||
private boolean hasMakeupLoaded = false;
|
||||
private boolean hasPropLoaded = false;
|
||||
|
||||
|
||||
public FaceUnityDataFactory(int index) {
|
||||
currentFunctionIndex = index;
|
||||
mFaceBeautyDataFactory = new FaceBeautyDataFactory();
|
||||
mBodyBeautyDataFactory = new BodyBeautyDataFactory();
|
||||
mMakeupDataFactory = new MakeupDataFactory(0);
|
||||
mPropDataFactory = new PropDataFactory(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* FURenderKit加载当前特效
|
||||
*/
|
||||
public void bindCurrentRenderer() {
|
||||
switch (currentFunctionIndex) {
|
||||
case 0:
|
||||
mFaceBeautyDataFactory.bindCurrentRenderer();
|
||||
hasFaceBeautyLoaded = true;
|
||||
break;
|
||||
case 1:
|
||||
mPropDataFactory.bindCurrentRenderer();
|
||||
hasPropLoaded = true;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
mMakeupDataFactory.bindCurrentRenderer();
|
||||
hasMakeupLoaded = true;
|
||||
break;
|
||||
case 3:
|
||||
mBodyBeautyDataFactory.bindCurrentRenderer();
|
||||
hasBodyBeautyLoaded = true;
|
||||
break;
|
||||
}
|
||||
if (hasFaceBeautyLoaded && currentFunctionIndex != 0) {
|
||||
mFaceBeautyDataFactory.bindCurrentRenderer();
|
||||
}
|
||||
if (hasPropLoaded && currentFunctionIndex != 1) {
|
||||
mPropDataFactory.bindCurrentRenderer();
|
||||
}
|
||||
if (hasMakeupLoaded && currentFunctionIndex != 2) {
|
||||
mMakeupDataFactory.bindCurrentRenderer();
|
||||
}
|
||||
if (hasBodyBeautyLoaded && currentFunctionIndex != 3) {
|
||||
mBodyBeautyDataFactory.bindCurrentRenderer();
|
||||
}
|
||||
if (currentFunctionIndex == 3) {
|
||||
mFURenderKit.getFUAIController().setMaxFaces(1);
|
||||
mFURenderer.setAIProcessTrackType(FUAIProcessorEnum.HUMAN_PROCESSOR);
|
||||
} else {
|
||||
mFURenderKit.getFUAIController().setMaxFaces(4);
|
||||
mFURenderer.setAIProcessTrackType(FUAIProcessorEnum.FACE_PROCESSOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 道具功能切换
|
||||
*/
|
||||
public void onFunctionSelected(int index) {
|
||||
currentFunctionIndex = index;
|
||||
switch (index) {
|
||||
case 0:
|
||||
if (!hasFaceBeautyLoaded) {
|
||||
mFaceBeautyDataFactory.bindCurrentRenderer();
|
||||
hasFaceBeautyLoaded = true;
|
||||
}
|
||||
mFURenderKit.getFUAIController().setMaxFaces(4);
|
||||
mFURenderer.setAIProcessTrackType(FUAIProcessorEnum.FACE_PROCESSOR);
|
||||
break;
|
||||
case 1:
|
||||
if (!hasPropLoaded) {
|
||||
mPropDataFactory.bindCurrentRenderer();
|
||||
hasPropLoaded = true;
|
||||
}
|
||||
mFURenderKit.getFUAIController().setMaxFaces(4);
|
||||
mFURenderer.setAIProcessTrackType(FUAIProcessorEnum.FACE_PROCESSOR);
|
||||
break;
|
||||
case 2:
|
||||
if (!hasMakeupLoaded) {
|
||||
mMakeupDataFactory.bindCurrentRenderer();
|
||||
hasMakeupLoaded = true;
|
||||
}
|
||||
mFURenderKit.getFUAIController().setMaxFaces(4);
|
||||
mFURenderer.setAIProcessTrackType(FUAIProcessorEnum.FACE_PROCESSOR);
|
||||
break;
|
||||
case 3:
|
||||
if (!hasBodyBeautyLoaded) {
|
||||
mBodyBeautyDataFactory.bindCurrentRenderer();
|
||||
hasBodyBeautyLoaded = true;
|
||||
}
|
||||
mFURenderKit.getFUAIController().setMaxFaces(1);
|
||||
mFURenderer.setAIProcessTrackType(FUAIProcessorEnum.HUMAN_PROCESSOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package com.yunbao.faceunity.data;
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.faceunity.core.entity.FUBundleData;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.faceunity.core.model.makeup.SimpleMakeup;
|
||||
|
||||
import com.yunbao.faceunity.entity.MakeupCombinationBean;
|
||||
import com.yunbao.faceunity.infe.AbstractMakeupDataFactory;
|
||||
import com.yunbao.faceunity.repo.MakeupSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* DESC:美妆业务工厂
|
||||
* Created on 2021/3/1
|
||||
*/
|
||||
public class MakeupDataFactory extends AbstractMakeupDataFactory {
|
||||
|
||||
|
||||
/*渲染控制器*/
|
||||
private FURenderKit mFURenderKit = FURenderKit.getInstance();
|
||||
|
||||
/*组合妆容列表*/
|
||||
private ArrayList<MakeupCombinationBean> makeupCombinations;
|
||||
/*组合妆容当前下标*/
|
||||
private int currentCombinationIndex;//-1:自定义
|
||||
/*美妆数据模型*/
|
||||
private SimpleMakeup currentMakeup;
|
||||
/*美妆数据模型缓存*/
|
||||
private HashMap<String, SimpleMakeup> makeupMap = new HashMap<>();
|
||||
|
||||
|
||||
public MakeupDataFactory(int index) {
|
||||
makeupCombinations = MakeupSource.buildCombinations();
|
||||
currentCombinationIndex = index;
|
||||
currentMakeup = getMakeupModel(makeupCombinations.get(currentCombinationIndex)); // 当前生效模型
|
||||
}
|
||||
|
||||
//region 组合妆
|
||||
|
||||
/**
|
||||
* 获取当前组合妆容列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public ArrayList<MakeupCombinationBean> getMakeupCombinations() {
|
||||
return makeupCombinations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前组合妆容下标
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int getCurrentCombinationIndex() {
|
||||
return currentCombinationIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置组合妆容下标
|
||||
*
|
||||
* @param currentCombinationIndex
|
||||
*/
|
||||
@Override
|
||||
public void setCurrentCombinationIndex(int currentCombinationIndex) {
|
||||
this.currentCombinationIndex = currentCombinationIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换组合妆容
|
||||
*
|
||||
* @param bean
|
||||
*/
|
||||
@Override
|
||||
public void onMakeupCombinationSelected(MakeupCombinationBean bean) {
|
||||
currentMakeup = getMakeupModel(bean);
|
||||
mFURenderKit.setMakeup(currentMakeup);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换美妆模型整体强度
|
||||
*
|
||||
* @param intensity
|
||||
*/
|
||||
@Override
|
||||
public void updateCombinationIntensity(double intensity) {
|
||||
currentMakeup.setMakeupIntensity(intensity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造美妆模型
|
||||
*
|
||||
* @param bean
|
||||
* @return
|
||||
*/
|
||||
private SimpleMakeup getMakeupModel(MakeupCombinationBean bean) {
|
||||
if (bean.getBundlePath() == null) {
|
||||
return null;
|
||||
} else {
|
||||
if (makeupMap.containsKey(bean.getKey())) {
|
||||
return makeupMap.get(bean.getKey());
|
||||
}
|
||||
SimpleMakeup makeup = new SimpleMakeup(new FUBundleData(MakeupSource.BUNDLE_FACE_MAKEUP));
|
||||
makeup.setCombinedConfig(new FUBundleData(bean.getBundlePath()));
|
||||
makeup.setMakeupIntensity(bean.getIntensity());
|
||||
makeupMap.put(bean.getKey(), makeup);
|
||||
return makeup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//endregion 组合妆
|
||||
|
||||
/**
|
||||
* FURenderKit加载当前特效
|
||||
*/
|
||||
public void bindCurrentRenderer() {
|
||||
mFURenderKit.setMakeup(currentMakeup);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.yunbao.faceunity.data;
|
||||
|
||||
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.faceunity.core.entity.FUBundleData;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.faceunity.core.model.prop.Prop;
|
||||
import com.faceunity.core.model.prop.sticker.Sticker;
|
||||
|
||||
import com.yunbao.faceunity.entity.PropBean;
|
||||
import com.yunbao.faceunity.infe.AbstractPropDataFactory;
|
||||
import com.yunbao.faceunity.repo.PropSource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* DESC:道具业务工厂
|
||||
* Created on 2021/3/2
|
||||
*/
|
||||
public class PropDataFactory extends AbstractPropDataFactory {
|
||||
|
||||
/*渲染控制器*/
|
||||
private final FURenderKit mFURenderKit = FURenderKit.getInstance();
|
||||
/*道具列表*/
|
||||
private final ArrayList<PropBean> propBeans;
|
||||
/*默认选中下标*/
|
||||
private int currentPropIndex;
|
||||
/*当前道具*/
|
||||
public Prop currentProp;
|
||||
|
||||
public PropDataFactory(int index) {
|
||||
currentPropIndex = index;
|
||||
propBeans = PropSource.buildPropBeans();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前选中下标
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int getCurrentPropIndex() {
|
||||
return currentPropIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前选中下标
|
||||
*
|
||||
* @param currentPropIndex
|
||||
*/
|
||||
@Override
|
||||
public void setCurrentPropIndex(int currentPropIndex) {
|
||||
this.currentPropIndex = currentPropIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取道具队列
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public ArrayList<PropBean> getPropBeans() {
|
||||
return propBeans;
|
||||
}
|
||||
|
||||
/**
|
||||
* 道具选中
|
||||
*
|
||||
* @param bean
|
||||
*/
|
||||
@Override
|
||||
public void onItemSelected(PropBean bean) {
|
||||
String path = bean.getPath();
|
||||
if (path == null || path.trim().length() == 0) {
|
||||
mFURenderKit.getPropContainer().removeAllProp();
|
||||
currentProp = null;
|
||||
return;
|
||||
}
|
||||
Prop prop = new Sticker(new FUBundleData(path));
|
||||
mFURenderKit.getPropContainer().replaceProp(currentProp, prop);
|
||||
currentProp = prop;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* FURenderKit加载当前特效
|
||||
*/
|
||||
public void bindCurrentRenderer() {
|
||||
PropBean propBean = propBeans.get(currentPropIndex);
|
||||
onItemSelected(propBean);
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.yunbao.faceunity.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
/**
|
||||
* @author Richie on 2018.09.19
|
||||
*/
|
||||
public abstract class BaseDialogFragment extends DialogFragment {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
|
||||
View dialogView = createDialogView(inflater, container);
|
||||
getDialog().setCanceledOnTouchOutside(false);//点击屏幕不消失
|
||||
getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {//点击返回键不消失
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
initWindowParams();
|
||||
return dialogView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 dialog view
|
||||
*
|
||||
* @param inflater
|
||||
* @param container
|
||||
* @return
|
||||
*/
|
||||
protected abstract View createDialogView(LayoutInflater inflater, @Nullable ViewGroup container);
|
||||
|
||||
protected int getDialogWidth() {
|
||||
return WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
|
||||
protected int getDialogHeight() {
|
||||
return WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
|
||||
private void initWindowParams() {
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog != null) {
|
||||
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.getDecorView().setPadding(0, 0, 0, 0);
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent);
|
||||
WindowManager.LayoutParams windowAttributes = window.getAttributes();
|
||||
windowAttributes.gravity = Gravity.CENTER;
|
||||
windowAttributes.width = getDialogWidth();
|
||||
windowAttributes.height = getDialogHeight();
|
||||
window.setAttributes(windowAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
/**
|
||||
* 确认
|
||||
*/
|
||||
void onConfirm();
|
||||
|
||||
/**
|
||||
* 取消
|
||||
*/
|
||||
void onCancel();
|
||||
}
|
||||
|
||||
public interface OnDismissListener {
|
||||
/**
|
||||
* 消失
|
||||
*/
|
||||
void onDismiss();
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package com.yunbao.faceunity.dialog;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
|
||||
|
||||
/**
|
||||
* 确认对话框
|
||||
*
|
||||
* @author Richie on 2018.08.28
|
||||
*/
|
||||
public class ConfirmDialogFragment extends BaseDialogFragment {
|
||||
private static final String TITLE = "content";
|
||||
private static final String CONFIRM = "confirm";
|
||||
private static final String CANCEL = "cancel";
|
||||
private OnClickListener mOnClickListener;
|
||||
|
||||
/**
|
||||
* 创建对话框
|
||||
*
|
||||
* @param title
|
||||
* @param onClickListener
|
||||
* @return
|
||||
*/
|
||||
public static ConfirmDialogFragment newInstance(@NonNull String title, @NonNull OnClickListener onClickListener) {
|
||||
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
|
||||
fragment.mOnClickListener = onClickListener;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(TITLE, title);
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static ConfirmDialogFragment newInstance(@NonNull String title, @NonNull String confirmText,
|
||||
@NonNull String cancelText, @NonNull OnClickListener onClickListener) {
|
||||
ConfirmDialogFragment fragment = new ConfirmDialogFragment();
|
||||
fragment.mOnClickListener = onClickListener;
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(TITLE, title);
|
||||
bundle.putString(CONFIRM, confirmText);
|
||||
bundle.putString(CANCEL, cancelText);
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View createDialogView(LayoutInflater inflater, @Nullable ViewGroup container) {
|
||||
View view = inflater.inflate(R.layout.dialog_confirm, container, false);
|
||||
View.OnClickListener onClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dismiss();
|
||||
int id = v.getId();
|
||||
if (id == R.id.tv_confirm) {
|
||||
if (mOnClickListener != null) {
|
||||
mOnClickListener.onConfirm();
|
||||
}
|
||||
} else if (id == R.id.tv_cancel) {
|
||||
if (mOnClickListener != null) {
|
||||
mOnClickListener.onCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
String confirmTxt = getArguments().getString(CONFIRM);
|
||||
TextView tvConfirm = view.findViewById(R.id.tv_confirm);
|
||||
if (!TextUtils.isEmpty(confirmTxt)) {
|
||||
tvConfirm.setText(confirmTxt);
|
||||
}
|
||||
String cancelTxt = getArguments().getString(CANCEL);
|
||||
TextView tvCancel = view.findViewById(R.id.tv_cancel);
|
||||
if (!TextUtils.isEmpty(cancelTxt)) {
|
||||
tvCancel.setText(cancelTxt);
|
||||
}
|
||||
tvConfirm.setOnClickListener(onClickListener);
|
||||
tvCancel.setOnClickListener(onClickListener);
|
||||
String title = getArguments().getString(TITLE);
|
||||
((TextView) view.findViewById(R.id.tv_content)).setText(title);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDialogWidth() {
|
||||
return getResources().getDimensionPixelSize(R.dimen.x562);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDialogHeight() {
|
||||
return getResources().getDimensionPixelSize(R.dimen.x294);
|
||||
}
|
||||
|
||||
public void setOnClickListener(OnClickListener onClickListener) {
|
||||
mOnClickListener = onClickListener;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
package com.yunbao.faceunity.dialog;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
|
||||
/**
|
||||
* Created by tujh on 2018/6/28.
|
||||
*/
|
||||
public final class ToastHelper {
|
||||
private static Toast sNormalToast;
|
||||
private static Toast sWhiteTextToast;
|
||||
private static WeakReference<Context> mWeakContext;
|
||||
|
||||
public static void showToast(Context context, String str) {
|
||||
Toast toast = Toast.makeText(context, str, Toast.LENGTH_SHORT);
|
||||
toast.setGravity(Gravity.CENTER, 0, 0);
|
||||
toast.show();
|
||||
}
|
||||
|
||||
public static void showToast(Context context, @StringRes int strId) {
|
||||
showToast(context, context.getString(strId));
|
||||
}
|
||||
|
||||
public static void showWhiteTextToast(Context context, @StringRes int strId) {
|
||||
showWhiteTextToast(context, context.getString(strId));
|
||||
}
|
||||
|
||||
public static void showNormalToast(Context context, @StringRes int strId) {
|
||||
showNormalToast(context, context.getString(strId));
|
||||
}
|
||||
|
||||
public static void dismissToast() {
|
||||
dismissWhiteTextToast();
|
||||
dismissNormalToast();
|
||||
}
|
||||
|
||||
|
||||
public static void dismissWhiteTextToast() {
|
||||
if (sWhiteTextToast != null) {
|
||||
sWhiteTextToast.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void dismissNormalToast() {
|
||||
if (sNormalToast != null) {
|
||||
sNormalToast.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void showWhiteTextToast(Context context, String text) {
|
||||
if (mWeakContext != null && mWeakContext.get() == context && sWhiteTextToast != null) {
|
||||
TextView view = (TextView) sWhiteTextToast.getView();
|
||||
view.setText(text);
|
||||
if (!view.isShown()) {
|
||||
sWhiteTextToast.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
mWeakContext = new WeakReference<>(context);
|
||||
Resources resources = context.getResources();
|
||||
TextView textView = new TextView(context);
|
||||
textView.setTextColor(resources.getColor(R.color.colorWhite));
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.x64));
|
||||
textView.setText(text);
|
||||
sWhiteTextToast = new Toast(context);
|
||||
sWhiteTextToast.setView(textView);
|
||||
sWhiteTextToast.setDuration(Toast.LENGTH_SHORT);
|
||||
int yOffset = context.getResources().getDimensionPixelSize(R.dimen.x560);
|
||||
sWhiteTextToast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, yOffset);
|
||||
sWhiteTextToast.show();
|
||||
}
|
||||
|
||||
|
||||
public static void showNormalToast(Context context, String text) {
|
||||
if (mWeakContext != null && mWeakContext.get() == context && sNormalToast != null) {
|
||||
TextView view = (TextView) sNormalToast.getView();
|
||||
view.setText(text);
|
||||
if (!view.isShown()) {
|
||||
sNormalToast.show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
mWeakContext = new WeakReference<>(context);
|
||||
Resources resources = context.getResources();
|
||||
TextView textView = new TextView(context);
|
||||
textView.setTextColor(resources.getColor(R.color.colorWhite));
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.x26));
|
||||
textView.setBackgroundResource(R.drawable.bg_toast_more);
|
||||
int hPadding = resources.getDimensionPixelSize(R.dimen.x28);
|
||||
int vPadding = resources.getDimensionPixelSize(R.dimen.x16);
|
||||
textView.setPadding(hPadding, vPadding, hPadding, vPadding);
|
||||
textView.setText(text);
|
||||
sNormalToast = new Toast(context);
|
||||
sNormalToast.setView(textView);
|
||||
sNormalToast.setDuration(Toast.LENGTH_SHORT);
|
||||
int yOffset = context.getResources().getDimensionPixelSize(R.dimen.x182);
|
||||
sNormalToast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, yOffset);
|
||||
sNormalToast.show();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.yunbao.faceunity.entity;
|
||||
|
||||
/**
|
||||
* DESC:美体
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class BodyBeautyBean {
|
||||
private String key;//名称标识
|
||||
private int desRes;//描述
|
||||
private int closeRes;//图片
|
||||
private int openRes;//图片
|
||||
|
||||
public BodyBeautyBean(String key, int desRes, int closeRes, int openRes) {
|
||||
this.key = key;
|
||||
this.desRes = desRes;
|
||||
this.closeRes = closeRes;
|
||||
this.openRes = openRes;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public int getDesRes() {
|
||||
return desRes;
|
||||
}
|
||||
|
||||
public void setDesRes(int desRes) {
|
||||
this.desRes = desRes;
|
||||
}
|
||||
|
||||
public int getCloseRes() {
|
||||
return closeRes;
|
||||
}
|
||||
|
||||
public void setCloseRes(int closeRes) {
|
||||
this.closeRes = closeRes;
|
||||
}
|
||||
|
||||
public int getOpenRes() {
|
||||
return openRes;
|
||||
}
|
||||
|
||||
public void setOpenRes(int openRes) {
|
||||
this.openRes = openRes;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.yunbao.faceunity.entity;
|
||||
|
||||
/**
|
||||
* DESC:美颜
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class FaceBeautyBean {
|
||||
|
||||
private String key;//名称标识
|
||||
private int desRes;//描述
|
||||
private int closeRes;//图片
|
||||
private int openRes;//图片
|
||||
|
||||
public FaceBeautyBean(String key, int desRes, int closeRes, int openRes) {
|
||||
this.key = key;
|
||||
this.desRes = desRes;
|
||||
this.closeRes = closeRes;
|
||||
this.openRes = openRes;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public int getDesRes() {
|
||||
return desRes;
|
||||
}
|
||||
|
||||
public void setDesRes(int desRes) {
|
||||
this.desRes = desRes;
|
||||
}
|
||||
|
||||
public int getCloseRes() {
|
||||
return closeRes;
|
||||
}
|
||||
|
||||
public void setCloseRes(int closeRes) {
|
||||
this.closeRes = closeRes;
|
||||
}
|
||||
|
||||
public int getOpenRes() {
|
||||
return openRes;
|
||||
}
|
||||
|
||||
public void setOpenRes(int openRes) {
|
||||
this.openRes = openRes;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.yunbao.faceunity.entity;
|
||||
|
||||
/**
|
||||
* DESC:美颜滤镜
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class FaceBeautyFilterBean {
|
||||
|
||||
private String key;//名称标识
|
||||
private int imageRes;//图片
|
||||
private int desRes;//描述
|
||||
private double intensity = 0.4;//强度
|
||||
|
||||
public FaceBeautyFilterBean(String key, int imageRes, int desRes) {
|
||||
this.key = key;
|
||||
this.imageRes = imageRes;
|
||||
this.desRes = desRes;
|
||||
}
|
||||
|
||||
public FaceBeautyFilterBean(String key, int imageRes, int desRes, double intensity) {
|
||||
this.key = key;
|
||||
this.imageRes = imageRes;
|
||||
this.desRes = desRes;
|
||||
this.intensity = intensity;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public int getImageRes() {
|
||||
return imageRes;
|
||||
}
|
||||
|
||||
public void setImageRes(int imageRes) {
|
||||
this.imageRes = imageRes;
|
||||
}
|
||||
|
||||
public int getDesRes() {
|
||||
return desRes;
|
||||
}
|
||||
|
||||
public void setDesRes(int desRes) {
|
||||
this.desRes = desRes;
|
||||
}
|
||||
|
||||
public double getIntensity() {
|
||||
return intensity;
|
||||
}
|
||||
|
||||
public void setIntensity(double intensity) {
|
||||
this.intensity = intensity;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package com.yunbao.faceunity.entity;
|
||||
|
||||
/**
|
||||
* DESC:美妆-组合妆容
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class MakeupCombinationBean {
|
||||
|
||||
private String key;//名称标识
|
||||
private int imageRes;//图片
|
||||
private int desRes;//描述
|
||||
private String bundlePath;//资源句柄
|
||||
private double intensity = 1.0;//强度
|
||||
|
||||
public MakeupCombinationBean(String key, int imageRes, int desRes, String bundlePath) {
|
||||
this.key = key;
|
||||
this.imageRes = imageRes;
|
||||
this.desRes = desRes;
|
||||
this.bundlePath = bundlePath;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public int getImageRes() {
|
||||
return imageRes;
|
||||
}
|
||||
|
||||
public void setImageRes(int imageRes) {
|
||||
this.imageRes = imageRes;
|
||||
}
|
||||
|
||||
public int getDesRes() {
|
||||
return desRes;
|
||||
}
|
||||
|
||||
public void setDesRes(int desRes) {
|
||||
this.desRes = desRes;
|
||||
}
|
||||
|
||||
public String getBundlePath() {
|
||||
return bundlePath;
|
||||
}
|
||||
|
||||
public void setBundlePath(String bundlePath) {
|
||||
this.bundlePath = bundlePath;
|
||||
}
|
||||
|
||||
public double getIntensity() {
|
||||
return intensity;
|
||||
}
|
||||
|
||||
public void setIntensity(double intensity) {
|
||||
this.intensity = intensity;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.yunbao.faceunity.entity;
|
||||
|
||||
/**
|
||||
* 模型单项补充模型
|
||||
*/
|
||||
public class ModelAttributeData {
|
||||
private double defaultV = 0.0;//默认值
|
||||
private double standV = 0.0;//无变化时候的基准值
|
||||
private double minRange = 0.0;//范围最小值
|
||||
private double maxRange = 1.0;//范围最大值
|
||||
|
||||
public ModelAttributeData(double defaultV, double standV, double minRange, double maxRange) {
|
||||
this.defaultV = defaultV;
|
||||
this.standV = standV;
|
||||
this.minRange = minRange;
|
||||
this.maxRange = maxRange;
|
||||
}
|
||||
|
||||
|
||||
public double getDefaultV() {
|
||||
return defaultV;
|
||||
}
|
||||
|
||||
public void setDefaultV(double defaultV) {
|
||||
this.defaultV = defaultV;
|
||||
}
|
||||
|
||||
public double getStandV() {
|
||||
return standV;
|
||||
}
|
||||
|
||||
public void setStandV(double standV) {
|
||||
this.standV = standV;
|
||||
}
|
||||
|
||||
public double getMinRange() {
|
||||
return minRange;
|
||||
}
|
||||
|
||||
public void setMinRange(double minRange) {
|
||||
this.minRange = minRange;
|
||||
}
|
||||
|
||||
public double getMaxRange() {
|
||||
return maxRange;
|
||||
}
|
||||
|
||||
public void setMaxRange(double maxRange) {
|
||||
this.maxRange = maxRange;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.yunbao.faceunity.entity;
|
||||
|
||||
/**
|
||||
* DESC:道具
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class PropBean {
|
||||
private int iconId;
|
||||
private String path;
|
||||
|
||||
|
||||
public PropBean(int iconId, String path) {
|
||||
this.iconId = iconId;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public int getIconId() {
|
||||
return iconId;
|
||||
}
|
||||
|
||||
public void setIconId(int iconId) {
|
||||
this.iconId = iconId;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.yunbao.faceunity.infe;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.entity.BodyBeautyBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:数据构造工厂抽象类
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class AbstractBodyBeautyDataFactory {
|
||||
|
||||
|
||||
/**
|
||||
* 获取美体参数集合
|
||||
* @return
|
||||
*/
|
||||
public abstract ArrayList<BodyBeautyBean> getBodyBeautyParam();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取美体项目数据扩展模型
|
||||
* @return
|
||||
*/
|
||||
public abstract HashMap<String, ModelAttributeData> getModelAttributeRange();
|
||||
|
||||
|
||||
/**
|
||||
* 根据名称标识获取对应的值
|
||||
*
|
||||
* @param key String 标识
|
||||
* @return Double 值
|
||||
*/
|
||||
public abstract double getParamIntensity(String key);
|
||||
|
||||
/**
|
||||
* 根据名称标识更新对应的值
|
||||
*
|
||||
* @param key String 标识
|
||||
* @return Double 值
|
||||
*/
|
||||
public abstract void updateParamIntensity(String key, double value);
|
||||
|
||||
/**
|
||||
* 美体开关
|
||||
*
|
||||
* @param enable Boolean
|
||||
*/
|
||||
public abstract void enableBodyBeauty(boolean enable);
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package com.yunbao.faceunity.infe;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.entity.FaceBeautyBean;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyFilterBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* DESC:数据构造工厂抽象类
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class AbstractFaceBeautyDataFactory {
|
||||
|
||||
|
||||
/**
|
||||
* 美肤底部菜单数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract ArrayList<FaceBeautyBean> getSkinBeauty();
|
||||
|
||||
|
||||
/**
|
||||
* 美型底部菜单数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract ArrayList<FaceBeautyBean> getShapeBeauty();
|
||||
|
||||
|
||||
/**
|
||||
* 滤镜底部菜单数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract ArrayList<FaceBeautyFilterBean> getBeautyFilters();
|
||||
|
||||
/**
|
||||
* 获取当前滤镜下标
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract int getCurrentFilterIndex();
|
||||
|
||||
/**
|
||||
* 设置当前滤镜下标
|
||||
*
|
||||
* @param currentFilterIndex
|
||||
*/
|
||||
public abstract void setCurrentFilterIndex(int currentFilterIndex);
|
||||
|
||||
|
||||
/**
|
||||
* 美颜项目数据扩展模型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract HashMap<String, ModelAttributeData> getModelAttributeRange();
|
||||
|
||||
/**
|
||||
* 切换滤镜
|
||||
*
|
||||
* @param name String
|
||||
* @param intensity Double
|
||||
*/
|
||||
public abstract void onFilterSelected(String name, double intensity, int resDes);
|
||||
|
||||
|
||||
/**
|
||||
* 更改滤镜强度
|
||||
*
|
||||
* @param intensity Double
|
||||
*/
|
||||
public abstract void updateFilterIntensity(double intensity);
|
||||
|
||||
/**
|
||||
* 美颜开关
|
||||
*
|
||||
* @param enable Boolean
|
||||
*/
|
||||
public abstract void enableFaceBeauty(boolean enable);
|
||||
|
||||
/**
|
||||
* 获取单项强度
|
||||
*
|
||||
* @param key String
|
||||
* @return Double
|
||||
*/
|
||||
public abstract double getParamIntensity(String key);
|
||||
|
||||
/**
|
||||
* 设置单项强度
|
||||
*
|
||||
* @param key String
|
||||
* @param value Double
|
||||
*/
|
||||
public abstract void updateParamIntensity(String key, double value);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.yunbao.faceunity.infe;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.entity.MakeupCombinationBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* DESC:数据构造工厂抽象类
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class AbstractMakeupDataFactory {
|
||||
|
||||
/**
|
||||
* 获取当前组合妆容列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract ArrayList<MakeupCombinationBean> getMakeupCombinations();
|
||||
|
||||
/**
|
||||
* 获取当前组合妆容下标
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract int getCurrentCombinationIndex();
|
||||
|
||||
/**
|
||||
* 设置组合妆容下标
|
||||
*
|
||||
* @param currentCombinationIndex
|
||||
*/
|
||||
public abstract void setCurrentCombinationIndex(int currentCombinationIndex);
|
||||
|
||||
|
||||
/**
|
||||
* 组合妆容选中
|
||||
*
|
||||
* @param bean MakeupCombinationBean
|
||||
*/
|
||||
public abstract void onMakeupCombinationSelected(MakeupCombinationBean bean);
|
||||
|
||||
/**
|
||||
* 设置美妆整体强度
|
||||
*
|
||||
* @param intensity Double
|
||||
*/
|
||||
public abstract void updateCombinationIntensity(double intensity);
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.yunbao.faceunity.infe;
|
||||
|
||||
import com.yunbao.faceunity.entity.PropBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* DESC:数据构造工厂抽象类
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class AbstractPropDataFactory {
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前选中下标
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract int getCurrentPropIndex();
|
||||
|
||||
/**
|
||||
* 设置当前选中下标
|
||||
*
|
||||
* @param currentPropIndex
|
||||
*/
|
||||
public abstract void setCurrentPropIndex(int currentPropIndex);
|
||||
|
||||
/**
|
||||
* 获取道具队列
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract ArrayList<PropBean> getPropBeans();
|
||||
|
||||
/**
|
||||
* 道具选中
|
||||
*
|
||||
* @param bean StickerBean
|
||||
*/
|
||||
public abstract void onItemSelected(PropBean bean);
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.yunbao.faceunity.listener;
|
||||
|
||||
import com.faceunity.core.enumeration.FUAIProcessorEnum;
|
||||
|
||||
/**
|
||||
* DESC:FURenderer状态回调监听
|
||||
* Created on 2021/4/29
|
||||
*/
|
||||
public interface FURendererListener {
|
||||
|
||||
/**
|
||||
* prepare完成回调
|
||||
*/
|
||||
void onPrepare();
|
||||
|
||||
/**
|
||||
* 识别到的人脸或人体数量发生变化
|
||||
*
|
||||
* @param type 类型
|
||||
* @param status 数量
|
||||
*/
|
||||
void onTrackStatusChanged(FUAIProcessorEnum type, int status);
|
||||
|
||||
|
||||
/**
|
||||
* 统计每 10 帧的平均数据,FPS 和渲染函数调用时间
|
||||
*
|
||||
* @param fps FPS
|
||||
* @param callTime 渲染函数调用时间
|
||||
*/
|
||||
void onFpsChanged(double fps, double callTime);
|
||||
|
||||
|
||||
/**
|
||||
* release完成回调
|
||||
*/
|
||||
void onRelease();
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.yunbao.faceunity.listener;
|
||||
|
||||
import com.faceunity.core.enumeration.FUAITypeEnum;
|
||||
import com.faceunity.core.faceunity.FUAIKit;
|
||||
import com.yunbao.faceunity.utils.FaceUnityConfig;
|
||||
|
||||
/**
|
||||
* 模块配置
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class IFaceModel<T> {
|
||||
public abstract T Builder();
|
||||
|
||||
public void apply() {
|
||||
FUAIKit.getInstance().loadAIProcessor(FaceUnityConfig.BUNDLE_AI_FACE, FUAITypeEnum.FUAITYPE_FACEPROCESSOR);
|
||||
FUAIKit.getInstance().faceProcessorSetFaceLandmarkQuality(FaceUnityConfig.DEVICE_LEVEL);
|
||||
}
|
||||
public abstract void setEnable(boolean enable);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.yunbao.faceunity.listener;
|
||||
|
||||
/**
|
||||
* DESC:底部菜单动画回调
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public interface OnBottomAnimatorChangeListener {
|
||||
void onBottomAnimatorChangeListener(float showRate);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.yunbao.faceunity.listener;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public abstract class OnMultiClickListener implements OnClickListener {
|
||||
private long mLastClickTime = 0L;
|
||||
private int mViewId = View.NO_ID;
|
||||
private static long MIN_CLICK_DELAY_TIME = 500L;
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
long curClickTime = System.currentTimeMillis();
|
||||
int viewId = v.getId();
|
||||
if (mViewId == viewId) {
|
||||
if (curClickTime - mLastClickTime >= MIN_CLICK_DELAY_TIME) {
|
||||
mLastClickTime = curClickTime;
|
||||
onMultiClick(v);
|
||||
}
|
||||
} else {
|
||||
mViewId = viewId;
|
||||
mLastClickTime = curClickTime;
|
||||
onMultiClick(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理后的点击事件
|
||||
*
|
||||
* @param v
|
||||
*/
|
||||
protected abstract void onMultiClick(View v);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.yunbao.faceunity.model;
|
||||
|
||||
|
||||
import com.faceunity.core.entity.FUBundleData;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.faceunity.core.model.facebeauty.FaceBeauty;
|
||||
import com.yunbao.faceunity.listener.IFaceModel;
|
||||
import com.yunbao.faceunity.utils.FaceUnityConfig;
|
||||
|
||||
/**
|
||||
* 美颜模块
|
||||
*/
|
||||
public class FaceBeautyModel extends IFaceModel<FaceBeauty> {
|
||||
|
||||
private FaceBeauty beauty;
|
||||
|
||||
/**
|
||||
* 构造美颜模块
|
||||
* 参考配置:https://gitee.com/hangzhou_xiangxin_1/FULiveDemoDroid/blob/master/doc/4.%E7%BE%8E%E9%A2%9C.md
|
||||
*/
|
||||
@Override
|
||||
public FaceBeauty Builder() {
|
||||
beauty = new FaceBeauty(new FUBundleData(FaceUnityConfig.BUNDLE_FACE_BEAUTIFICATION));
|
||||
return beauty;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用美颜
|
||||
*/
|
||||
@Override
|
||||
public void apply() {
|
||||
super.apply();
|
||||
FURenderKit kit = FURenderKit.getInstance();
|
||||
kit.setFaceBeauty(beauty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnable(boolean enable) {
|
||||
beauty.setEnable(enable);
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.yunbao.faceunity.repo;
|
||||
|
||||
import com.faceunity.core.controller.bodyBeauty.BodyBeautyParam;
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.entity.BodyBeautyBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:美体数据构造
|
||||
* Created on 2021/3/26
|
||||
*/
|
||||
public class BodyBeautySource {
|
||||
|
||||
public static final String BUNDLE_BODY_BEAUTY = "graphics" + File.separator + "body_slim.bundle";
|
||||
|
||||
|
||||
/**
|
||||
* 构造美体
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static ArrayList<BodyBeautyBean> buildBodyBeauty() {
|
||||
ArrayList<BodyBeautyBean> params = new ArrayList<>();
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.BODY_SLIM_INTENSITY, R.string.slimming, R.drawable.icon_body_slimming_close_selector, R.drawable.icon_body_slimming_open_selector));
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.LEG_STRETCH_INTENSITY, R.string.long_legs, R.drawable.icon_body_stovepipe_close_selector, R.drawable.icon_body_stovepipe_open_selector));
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.WAIST_SLIM_INTENSITY, R.string.thin_waist, R.drawable.icon_body_waist_close_selector, R.drawable.icon_body_waist_open_selector));
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.SHOULDER_SLIM_INTENSITY, R.string.beautify_shoulder, R.drawable.icon_body_shoulder_close_selector, R.drawable.icon_body_shoulder_open_selector));
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.HIP_SLIM_INTENSITY, R.string.beautify_hip_slim, R.drawable.icon_body_hip_close_selector, R.drawable.icon_body_hip_open_selector));
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.HEAD_SLIM_INTENSITY, R.string.beautify_head_slim, R.drawable.icon_body_little_head_close_selector, R.drawable.icon_body_little_head_open_selector));
|
||||
params.add(new BodyBeautyBean(BodyBeautyParam.LEG_SLIM_INTENSITY, R.string.beautify_leg_thin_slim, R.drawable.icon_body_thin_leg_close_selector, R.drawable.icon_body_thin_leg_open_selector));
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型属性扩展数据
|
||||
*
|
||||
* @return HashMap<String, ModelAttributeData>
|
||||
*/
|
||||
public static HashMap<String, ModelAttributeData> buildModelAttributeRange() {
|
||||
HashMap<String, ModelAttributeData> params = new HashMap<>();
|
||||
params.put(BodyBeautyParam.BODY_SLIM_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(BodyBeautyParam.LEG_STRETCH_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(BodyBeautyParam.WAIST_SLIM_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(BodyBeautyParam.SHOULDER_SLIM_INTENSITY, new ModelAttributeData(0.5, 0.5, 0.0, 1.0));
|
||||
params.put(BodyBeautyParam.HIP_SLIM_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(BodyBeautyParam.HEAD_SLIM_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(BodyBeautyParam.LEG_SLIM_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
return params;
|
||||
}
|
||||
}
|
@ -0,0 +1,294 @@
|
||||
package com.yunbao.faceunity.repo;
|
||||
|
||||
import com.faceunity.core.controller.facebeauty.FaceBeautyParam;
|
||||
import com.faceunity.core.entity.FUBundleData;
|
||||
import com.faceunity.core.model.facebeauty.FaceBeauty;
|
||||
import com.faceunity.core.model.facebeauty.FaceBeautyBlurTypeEnum;
|
||||
import com.faceunity.core.model.facebeauty.FaceBeautyFilterEnum;
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyBean;
|
||||
import com.yunbao.faceunity.entity.FaceBeautyFilterBean;
|
||||
import com.yunbao.faceunity.entity.ModelAttributeData;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* DESC:美颜数据构造
|
||||
* Created on 2021/3/27
|
||||
*/
|
||||
public class FaceBeautySource {
|
||||
|
||||
public static String BUNDLE_FACE_BEAUTIFICATION = "graphics" + File.separator + "face_beautification.bundle";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取默认推荐美颜模型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static FaceBeauty getDefaultFaceBeauty() {
|
||||
FaceBeauty recommendFaceBeauty = new FaceBeauty(new FUBundleData(BUNDLE_FACE_BEAUTIFICATION));
|
||||
recommendFaceBeauty.setFilterName(FaceBeautyFilterEnum.ZIRAN_1);
|
||||
recommendFaceBeauty.setFilterIntensity(0.4);
|
||||
/*美肤*/
|
||||
recommendFaceBeauty.setBlurType(FaceBeautyBlurTypeEnum.FineSkin);
|
||||
recommendFaceBeauty.setSharpenIntensity(0.2);
|
||||
recommendFaceBeauty.setColorIntensity(0.3);
|
||||
recommendFaceBeauty.setRedIntensity(0.3);
|
||||
recommendFaceBeauty.setBlurIntensity(4.2);
|
||||
/*美型*/
|
||||
recommendFaceBeauty.setFaceShapeIntensity(1.0);
|
||||
recommendFaceBeauty.setEyeEnlargingIntensityV2(0.4);
|
||||
recommendFaceBeauty.setCheekVIntensity(0.5);
|
||||
recommendFaceBeauty.setNoseIntensityV2(0.5);
|
||||
recommendFaceBeauty.setForHeadIntensityV2(0.3);
|
||||
recommendFaceBeauty.setMouthIntensityV2(0.4);
|
||||
recommendFaceBeauty.setChinIntensity(0.3);
|
||||
return recommendFaceBeauty;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化美肤参数
|
||||
*
|
||||
* @return ArrayList<FaceBeautyBean>
|
||||
*/
|
||||
public static ArrayList<FaceBeautyBean> buildSkinParams() {
|
||||
ArrayList<FaceBeautyBean> params = new ArrayList<>();
|
||||
params.add(new FaceBeautyBean(
|
||||
FaceBeautyParam.BLUR_INTENSITY, R.string.beauty_box_heavy_blur_fine,
|
||||
R.drawable.icon_beauty_skin_buffing_close_selector, R.drawable.icon_beauty_skin_buffing_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.COLOR_INTENSITY, R.string.beauty_box_color_level,
|
||||
R.drawable.icon_beauty_skin_color_close_selector, R.drawable.icon_beauty_skin_color_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.RED_INTENSITY, R.string.beauty_box_red_level,
|
||||
R.drawable.icon_beauty_skin_red_close_selector, R.drawable.icon_beauty_skin_red_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.SHARPEN_INTENSITY, R.string.beauty_box_sharpen,
|
||||
R.drawable.icon_beauty_skin_sharpen_close_selector, R.drawable.icon_beauty_skin_sharpen_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.EYE_BRIGHT_INTENSITY, R.string.beauty_box_eye_bright,
|
||||
R.drawable.icon_beauty_skin_eyes_bright_close_selector, R.drawable.icon_beauty_skin_eyes_bright_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.TOOTH_WHITEN_INTENSITY, R.string.beauty_box_tooth_whiten,
|
||||
R.drawable.icon_beauty_skin_teeth_close_selector, R.drawable.icon_beauty_skin_teeth_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.REMOVE_POUCH_INTENSITY, R.string.beauty_micro_pouch,
|
||||
R.drawable.icon_beauty_skin_dark_circles_close_selector, R.drawable.icon_beauty_skin_dark_circles_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY, R.string.beauty_micro_nasolabial,
|
||||
R.drawable.icon_beauty_skin_wrinkle_close_selector, R.drawable.icon_beauty_skin_wrinkle_open_selector
|
||||
)
|
||||
);
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化美型参数
|
||||
*
|
||||
* @return ArrayList<FaceBeautyBean>
|
||||
*/
|
||||
public static ArrayList<FaceBeautyBean> buildShapeParams() {
|
||||
ArrayList<FaceBeautyBean> params = new ArrayList<>();
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CHEEK_THINNING_INTENSITY, R.string.beauty_box_cheek_thinning,
|
||||
R.drawable.icon_beauty_shape_face_cheekthin_close_selector, R.drawable.icon_beauty_shape_face_cheekthin_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CHEEK_V_INTENSITY, R.string.beauty_box_cheek_v,
|
||||
R.drawable.icon_beauty_shape_face_v_close_selector, R.drawable.icon_beauty_shape_face_v_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CHEEK_NARROW_INTENSITY_V2, R.string.beauty_box_cheek_narrow,
|
||||
R.drawable.icon_beauty_shape_face_narrow_close_selector, R.drawable.icon_beauty_shape_face_narrow_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CHEEK_SHORT_INTENSITY, R.string.beauty_box_cheek_short,
|
||||
R.drawable.icon_beauty_shape_face_short_close_selector, R.drawable.icon_beauty_shape_face_short_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CHEEK_SMALL_INTENSITY_V2, R.string.beauty_box_cheek_small,
|
||||
R.drawable.icon_beauty_shape_face_little_close_selector, R.drawable.icon_beauty_shape_face_little_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.INTENSITY_CHEEKBONES_INTENSITY, R.string.beauty_box_cheekbones,
|
||||
R.drawable.icon_beauty_shape_cheek_bones_close_selector, R.drawable.icon_beauty_shape_cheek_bones_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.INTENSITY_LOW_JAW_INTENSITY, R.string.beauty_box_lower_jaw,
|
||||
R.drawable.icon_beauty_shape_lower_jaw_close_selector, R.drawable.icon_beauty_shape_lower_jaw_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.EYE_ENLARGING_INTENSITY_V2, R.string.beauty_box_eye_enlarge,
|
||||
R.drawable.icon_beauty_shape_enlarge_eye_close_selector, R.drawable.icon_beauty_shape_enlarge_eye_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.EYE_CIRCLE_INTENSITY, R.string.beauty_box_eye_circle,
|
||||
R.drawable.icon_beauty_shape_round_eye_close_selector, R.drawable.icon_beauty_shape_round_eye_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CHIN_INTENSITY, R.string.beauty_box_intensity_chin,
|
||||
R.drawable.icon_beauty_shape_chin_close_selector, R.drawable.icon_beauty_shape_chin_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.FOREHEAD_INTENSITY_V2, R.string.beauty_box_intensity_forehead,
|
||||
R.drawable.icon_beauty_shape_forehead_close_selector, R.drawable.icon_beauty_shape_forehead_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.NOSE_INTENSITY_V2, R.string.beauty_box_intensity_nose,
|
||||
R.drawable.icon_beauty_shape_thin_nose_close_selector, R.drawable.icon_beauty_shape_thin_nose_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.MOUTH_INTENSITY_V2, R.string.beauty_box_intensity_mouth,
|
||||
R.drawable.icon_beauty_shape_mouth_close_selector, R.drawable.icon_beauty_shape_mouth_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.CANTHUS_INTENSITY, R.string.beauty_micro_canthus,
|
||||
R.drawable.icon_beauty_shape_open_eyes_close_selector, R.drawable.icon_beauty_shape_open_eyes_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.EYE_SPACE_INTENSITY, R.string.beauty_micro_eye_space,
|
||||
R.drawable.icon_beauty_shape_distance_close_selector, R.drawable.icon_beauty_shape_distance_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.EYE_ROTATE_INTENSITY, R.string.beauty_micro_eye_rotate,
|
||||
R.drawable.icon_beauty_shape_angle_close_selector, R.drawable.icon_beauty_shape_angle_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.LONG_NOSE_INTENSITY, R.string.beauty_micro_long_nose,
|
||||
R.drawable.icon_beauty_shape_proboscis_close_selector, R.drawable.icon_beauty_shape_proboscis_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.PHILTRUM_INTENSITY, R.string.beauty_micro_philtrum,
|
||||
R.drawable.icon_beauty_shape_shrinking_close_selector, R.drawable.icon_beauty_shape_shrinking_open_selector
|
||||
)
|
||||
);
|
||||
params.add(
|
||||
new FaceBeautyBean(
|
||||
FaceBeautyParam.SMILE_INTENSITY, R.string.beauty_micro_smile,
|
||||
R.drawable.icon_beauty_shape_smile_close_selector, R.drawable.icon_beauty_shape_smile_open_selector
|
||||
)
|
||||
);
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化参数扩展列表
|
||||
*
|
||||
* @return HashMap<String, ModelAttributeData>
|
||||
*/
|
||||
public static HashMap<String, ModelAttributeData> buildModelAttributeRange() {
|
||||
HashMap<String, ModelAttributeData> params = new HashMap<>();
|
||||
/*美肤*/
|
||||
params.put(FaceBeautyParam.COLOR_INTENSITY, new ModelAttributeData(0.3, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.BLUR_INTENSITY, new ModelAttributeData(4.2, 0.0, 0.0, 6.0));
|
||||
params.put(FaceBeautyParam.RED_INTENSITY, new ModelAttributeData(0.3, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.SHARPEN_INTENSITY, new ModelAttributeData(0.2, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.EYE_BRIGHT_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.TOOTH_WHITEN_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.REMOVE_POUCH_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
/*美型*/
|
||||
params.put(FaceBeautyParam.FACE_SHAPE_INTENSITY, new ModelAttributeData(1.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_THINNING_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_LONG_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_CIRCLE_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_V_INTENSITY, new ModelAttributeData(0.5, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_NARROW_INTENSITY_V2, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_SHORT_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_V2, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.INTENSITY_CHEEKBONES_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.INTENSITY_LOW_JAW_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.EYE_ENLARGING_INTENSITY_V2, new ModelAttributeData(0.4, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.EYE_CIRCLE_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CHIN_INTENSITY, new ModelAttributeData(0.3, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.FOREHEAD_INTENSITY_V2, new ModelAttributeData(0.3, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.NOSE_INTENSITY_V2, new ModelAttributeData(0.5, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.MOUTH_INTENSITY_V2, new ModelAttributeData(0.4, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.CANTHUS_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.EYE_SPACE_INTENSITY, new ModelAttributeData(0.5, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.EYE_ROTATE_INTENSITY, new ModelAttributeData(0.5, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.LONG_NOSE_INTENSITY, new ModelAttributeData(0.5, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.PHILTRUM_INTENSITY, new ModelAttributeData(0.5, 0.5, 0.0, 1.0));
|
||||
params.put(FaceBeautyParam.SMILE_INTENSITY, new ModelAttributeData(0.0, 0.0, 0.0, 1.0));
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化滤镜参数
|
||||
*
|
||||
* @return ArrayList<FaceBeautyFilterBean>
|
||||
*/
|
||||
public static ArrayList<FaceBeautyFilterBean> buildFilters() {
|
||||
ArrayList<FaceBeautyFilterBean> filters = new ArrayList<>();
|
||||
filters.add(new FaceBeautyFilterBean(FaceBeautyFilterEnum.ORIGIN, R.mipmap.icon_beauty_filter_cancel, R.string.origin, 0.0));
|
||||
filters.add(new FaceBeautyFilterBean(FaceBeautyFilterEnum.ZIRAN_1, R.mipmap.icon_beauty_filter_natural_1, R.string.ziran_1));
|
||||
filters.add(new FaceBeautyFilterBean(FaceBeautyFilterEnum.ZHIGANHUI_1, R.mipmap.icon_beauty_filter_texture_gray_1, R.string.zhiganhui_1));
|
||||
filters.add(new FaceBeautyFilterBean(FaceBeautyFilterEnum.BAILIANG_1, R.mipmap.icon_beauty_filter_bailiang_1, R.string.bailiang_1));
|
||||
filters.add(new FaceBeautyFilterBean(FaceBeautyFilterEnum.FENNEN_1, R.mipmap.icon_beauty_filter_fennen_1, R.string.fennen_1));
|
||||
filters.add(new FaceBeautyFilterBean(FaceBeautyFilterEnum.LENGSEDIAO_1, R.mipmap.icon_beauty_filter_lengsediao_1, R.string.lengsediao_1));
|
||||
return filters;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.yunbao.faceunity.repo;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.entity.MakeupCombinationBean;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* DESC:美妆数据构造
|
||||
* Created on 2021/3/28
|
||||
*/
|
||||
public class MakeupSource {
|
||||
public static String BUNDLE_FACE_MAKEUP = "graphics" + File.separator + "face_makeup.bundle";
|
||||
|
||||
//region 组合妆容
|
||||
|
||||
/**
|
||||
* 构造美妆组合妆容配置
|
||||
*
|
||||
* @return ArrayList<MakeupCombinationBean>
|
||||
*/
|
||||
public static ArrayList<MakeupCombinationBean> buildCombinations() {
|
||||
ArrayList<MakeupCombinationBean> combinations = new ArrayList<MakeupCombinationBean>();
|
||||
combinations.add(new MakeupCombinationBean("origin", R.mipmap.icon_control_none, R.string.makeup_radio_remove, null));
|
||||
combinations.add(new MakeupCombinationBean("naicha", R.mipmap.icon_makeup_combination_tea_with_milk, R.string.makeup_combination_naicha, "makeup/naicha.bundle"));
|
||||
combinations.add(new MakeupCombinationBean("dousha", R.mipmap.icon_makeup_combination_red_bean_paste, R.string.makeup_combination_dousha, "makeup/dousha.bundle"));
|
||||
combinations.add(new MakeupCombinationBean("chaoa", R.mipmap.icon_makeup_combination_super_a, R.string.makeup_combination_chaoa, "makeup/chaoa.bundle"));
|
||||
return combinations;
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.yunbao.faceunity.repo;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.entity.PropBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:道具数据构造
|
||||
* Created on 2021/3/28
|
||||
*/
|
||||
public class PropSource {
|
||||
|
||||
/**
|
||||
* 构造贴纸列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static ArrayList<PropBean> buildPropBeans() {
|
||||
ArrayList<PropBean> propBeans = new ArrayList<>();
|
||||
propBeans.add(new PropBean(R.mipmap.icon_control_delete_all, null));
|
||||
propBeans.add(new PropBean(R.mipmap.icon_sticker_sdlu, "sticker/sdlu.bundle"));
|
||||
propBeans.add(new PropBean(R.mipmap.icon_sticker_fashi, "sticker/fashi.bundle"));
|
||||
return propBeans;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,196 @@
|
||||
package com.yunbao.faceunity.seekbar.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.seekbar.internal.compat.SeekBarCompat;
|
||||
import com.yunbao.faceunity.seekbar.internal.drawable.MarkerDrawable;
|
||||
|
||||
|
||||
/**
|
||||
* {@link ViewGroup} to be used as the real indicator.
|
||||
* <p>
|
||||
* I've used this to be able to accommodate the TextView
|
||||
* and the {@link MarkerDrawable}
|
||||
* with the required positions and offsets
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class Marker extends ViewGroup implements MarkerDrawable.MarkerAnimationListener {
|
||||
private static final int PADDING_DP = 1;
|
||||
private static final int ELEVATION_DP = 8;
|
||||
//The TextView to show the info
|
||||
private TextView mNumber;
|
||||
//The max width of this View
|
||||
private int mWidth;
|
||||
//some distance between the thumb and our bubble marker.
|
||||
//This will be added to our measured height
|
||||
private int mSeparation;
|
||||
MarkerDrawable mMarkerDrawable;
|
||||
|
||||
public Marker(Context context, AttributeSet attrs, int defStyleAttr, String maxValue, int thumbSize, int separation) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
//as we're reading the parent DiscreteSeekBar attributes, it may wrongly set this view's visibility.
|
||||
setVisibility(View.VISIBLE);
|
||||
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DiscreteSeekBar,
|
||||
R.attr.discreteSeekBarStyle, R.style.Widget_DiscreteSeekBar);
|
||||
|
||||
int padding = (int) (PADDING_DP * displayMetrics.density) * 2;
|
||||
int textAppearanceId = a.getResourceId(R.styleable.DiscreteSeekBar_dsb_indicatorTextAppearance,
|
||||
R.style.Widget_DiscreteIndicatorTextAppearance);
|
||||
mNumber = new TextView(context);
|
||||
//Add some padding to this textView so the bubble has some space to breath
|
||||
mNumber.setPadding(padding, 0, padding, 0);
|
||||
mNumber.setTextAppearance(context, textAppearanceId);
|
||||
mNumber.setGravity(Gravity.CENTER);
|
||||
mNumber.setText(maxValue);
|
||||
mNumber.setMaxLines(1);
|
||||
mNumber.setSingleLine(true);
|
||||
SeekBarCompat.setTextDirection(mNumber, TEXT_DIRECTION_LOCALE);
|
||||
mNumber.setVisibility(View.INVISIBLE);
|
||||
|
||||
//add some padding for the elevation shadow not to be clipped
|
||||
//I'm sure there are better ways of doing this...
|
||||
setPadding(padding, padding, padding, padding);
|
||||
|
||||
resetSizes(maxValue);
|
||||
|
||||
mSeparation = separation;
|
||||
ColorStateList color = a.getColorStateList(R.styleable.DiscreteSeekBar_dsb_indicatorColor);
|
||||
mMarkerDrawable = new MarkerDrawable(color, thumbSize);
|
||||
mMarkerDrawable.setCallback(this);
|
||||
mMarkerDrawable.setMarkerListener(this);
|
||||
mMarkerDrawable.setExternalOffset(padding);
|
||||
|
||||
//Elevation for anroid 5+
|
||||
float elevation = a.getDimension(R.styleable.DiscreteSeekBar_dsb_indicatorElevation, ELEVATION_DP * displayMetrics.density);
|
||||
ViewCompat.setElevation(this, elevation);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
SeekBarCompat.setOutlineProvider(this, mMarkerDrawable);
|
||||
}
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
public void resetSizes(String maxValue) {
|
||||
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
|
||||
//Account for negative numbers... is there any proper way of getting the biggest string between our range????
|
||||
mNumber.setText("-" + maxValue);
|
||||
//Do a first forced measure call for the TextView (with the biggest text content),
|
||||
//to calculate the max width and use always the same.
|
||||
//this avoids the TextView from shrinking and growing when the text content changes
|
||||
int wSpec = MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, MeasureSpec.AT_MOST);
|
||||
int hSpec = MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, MeasureSpec.AT_MOST);
|
||||
mNumber.measure(wSpec, hSpec);
|
||||
mWidth = Math.max(mNumber.getMeasuredWidth(), mNumber.getMeasuredHeight());
|
||||
removeView(mNumber);
|
||||
addView(mNumber, new FrameLayout.LayoutParams(mWidth, mWidth, Gravity.LEFT | Gravity.TOP));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
mMarkerDrawable.draw(canvas);
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
measureChildren(widthMeasureSpec, heightMeasureSpec);
|
||||
int widthSize = mWidth + getPaddingLeft() + getPaddingRight();
|
||||
int heightSize = mWidth + getPaddingTop() + getPaddingBottom();
|
||||
//This diff is the basic calculation of the difference between
|
||||
//a square side size and its diagonal
|
||||
//this helps us account for the visual offset created by MarkerDrawable
|
||||
//when leaving one of the corners un-rounded
|
||||
int diff = (int) ((1.41f * mWidth) - mWidth) / 2;
|
||||
setMeasuredDimension(widthSize, heightSize + diff + mSeparation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int left = getPaddingLeft();
|
||||
int top = getPaddingTop();
|
||||
int right = getWidth() - getPaddingRight();
|
||||
int bottom = getHeight() - getPaddingBottom();
|
||||
//the TetView is always layout at the top
|
||||
mNumber.layout(left, top, left + mWidth, top + mWidth);
|
||||
//the MarkerDrawable uses the whole view, it will adapt itself...
|
||||
// or it seems so...
|
||||
mMarkerDrawable.setBounds(left, top, right, bottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean verifyDrawable(Drawable who) {
|
||||
return who == mMarkerDrawable || super.verifyDrawable(who);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
//HACK: Sometimes, the animateOpen() call is made before the View is attached
|
||||
//so the drawable cannot schedule itself to run the animation
|
||||
//I think we can call it here safely.
|
||||
//I've seen it happen in android 2.3.7
|
||||
animateOpen();
|
||||
}
|
||||
|
||||
public void setValue(CharSequence value) {
|
||||
mNumber.setText(value);
|
||||
}
|
||||
|
||||
public CharSequence getValue() {
|
||||
return mNumber.getText();
|
||||
}
|
||||
|
||||
public void animateOpen() {
|
||||
mMarkerDrawable.stop();
|
||||
mMarkerDrawable.animateToPressed();
|
||||
}
|
||||
|
||||
public void animateClose() {
|
||||
mMarkerDrawable.stop();
|
||||
mNumber.setVisibility(View.INVISIBLE);
|
||||
mMarkerDrawable.animateToNormal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpeningComplete() {
|
||||
mNumber.setVisibility(View.VISIBLE);
|
||||
if (getParent() instanceof MarkerDrawable.MarkerAnimationListener) {
|
||||
((MarkerDrawable.MarkerAnimationListener) getParent()).onOpeningComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosingComplete() {
|
||||
if (getParent() instanceof MarkerDrawable.MarkerAnimationListener) {
|
||||
((MarkerDrawable.MarkerAnimationListener) getParent()).onClosingComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mMarkerDrawable.stop();
|
||||
}
|
||||
|
||||
public void setColors(int startColor, int endColor) {
|
||||
mMarkerDrawable.setColors(startColor, endColor);
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
package com.yunbao.faceunity.seekbar.internal;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.core.view.GravityCompat;
|
||||
|
||||
import com.yunbao.faceunity.seekbar.internal.compat.SeekBarCompat;
|
||||
import com.yunbao.faceunity.seekbar.internal.drawable.MarkerDrawable;
|
||||
|
||||
|
||||
/**
|
||||
* Class to manage the floating bubble thing, similar (but quite worse tested than {@link android.widget.PopupWindow}
|
||||
* <p/>
|
||||
* <p>
|
||||
* This will attach a View to the Window (full-width, measured-height, positioned just under the thumb)
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
* @see #showIndicator(View, Rect)
|
||||
* @see #dismiss()
|
||||
* @see #dismissComplete()
|
||||
* @see Floater
|
||||
*/
|
||||
public class PopupIndicator {
|
||||
|
||||
private final WindowManager mWindowManager;
|
||||
private boolean mShowing;
|
||||
private Floater mPopupView;
|
||||
//Outside listener for the DiscreteSeekBar to get MarkerDrawable animation events.
|
||||
//The whole chain of events goes this way:
|
||||
//MarkerDrawable->Marker->Floater->mListener->DiscreteSeekBar....
|
||||
//... phew!
|
||||
private MarkerDrawable.MarkerAnimationListener mListener;
|
||||
private int[] mDrawingLocation = new int[2];
|
||||
Point screenSize = new Point();
|
||||
|
||||
public PopupIndicator(Context context, AttributeSet attrs, int defStyleAttr, String maxValue, int thumbSize, int separation) {
|
||||
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
mPopupView = new Floater(context, attrs, defStyleAttr, maxValue, thumbSize, separation);
|
||||
}
|
||||
|
||||
public void updateSizes(String maxValue) {
|
||||
dismissComplete();
|
||||
if (mPopupView != null) {
|
||||
mPopupView.mMarker.resetSizes(maxValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(MarkerDrawable.MarkerAnimationListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want the Floater to be full-width because the contents will be moved from side to side.
|
||||
* We may/should change this in the future to use just the PARENT View width and/or pass it in the constructor
|
||||
*/
|
||||
private void measureFloater() {
|
||||
int specWidth = View.MeasureSpec.makeMeasureSpec(screenSize.x, View.MeasureSpec.EXACTLY);
|
||||
int specHeight = View.MeasureSpec.makeMeasureSpec(screenSize.y, View.MeasureSpec.AT_MOST);
|
||||
mPopupView.measure(specWidth, specHeight);
|
||||
}
|
||||
|
||||
public void setValue(CharSequence value) {
|
||||
mPopupView.mMarker.setValue(value);
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return mShowing;
|
||||
}
|
||||
|
||||
public void showIndicator(View parent, Rect touchBounds) {
|
||||
if (isShowing()) {
|
||||
mPopupView.mMarker.animateOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
IBinder windowToken = parent.getWindowToken();
|
||||
if (windowToken != null) {
|
||||
WindowManager.LayoutParams p = createPopupLayout(windowToken);
|
||||
|
||||
p.gravity = Gravity.TOP | GravityCompat.START;
|
||||
updateLayoutParamsForPosiion(parent, p, touchBounds.bottom);
|
||||
mShowing = true;
|
||||
|
||||
translateViewIntoPosition(touchBounds.centerX());
|
||||
invokePopup(p);
|
||||
}
|
||||
}
|
||||
|
||||
public void move(int x) {
|
||||
if (!isShowing()) {
|
||||
return;
|
||||
}
|
||||
translateViewIntoPosition(x);
|
||||
}
|
||||
|
||||
public void setColors(int startColor, int endColor) {
|
||||
mPopupView.setColors(startColor, endColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will start the closing animation of the Marker and call onClosingComplete when finished
|
||||
*/
|
||||
public void dismiss() {
|
||||
mPopupView.mMarker.animateClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* FORCE the popup window to be removed.
|
||||
* You typically calls this when the parent view is being removed from the window to avoid a Window Leak
|
||||
*/
|
||||
public void dismissComplete() {
|
||||
if (isShowing()) {
|
||||
mShowing = false;
|
||||
try {
|
||||
mWindowManager.removeViewImmediate(mPopupView);
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateLayoutParamsForPosiion(View anchor, WindowManager.LayoutParams p, int yOffset) {
|
||||
DisplayMetrics displayMetrics = anchor.getResources().getDisplayMetrics();
|
||||
screenSize.set(displayMetrics.widthPixels, displayMetrics.heightPixels);
|
||||
|
||||
measureFloater();
|
||||
int measuredHeight = mPopupView.getMeasuredHeight();
|
||||
int paddingBottom = mPopupView.mMarker.getPaddingBottom();
|
||||
anchor.getLocationInWindow(mDrawingLocation);
|
||||
p.x = 0;
|
||||
p.y = mDrawingLocation[1] - measuredHeight + yOffset + paddingBottom;
|
||||
p.width = screenSize.x;
|
||||
p.height = measuredHeight;
|
||||
}
|
||||
|
||||
private void translateViewIntoPosition(final int x) {
|
||||
mPopupView.setFloatOffset(x + mDrawingLocation[0]);
|
||||
}
|
||||
|
||||
private void invokePopup(WindowManager.LayoutParams p) {
|
||||
mWindowManager.addView(mPopupView, p);
|
||||
mPopupView.mMarker.animateOpen();
|
||||
}
|
||||
|
||||
private WindowManager.LayoutParams createPopupLayout(IBinder token) {
|
||||
WindowManager.LayoutParams p = new WindowManager.LayoutParams();
|
||||
p.gravity = Gravity.START | Gravity.TOP;
|
||||
p.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
p.height = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
p.format = PixelFormat.TRANSLUCENT;
|
||||
p.flags = computeFlags(p.flags);
|
||||
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
|
||||
p.token = token;
|
||||
p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
|
||||
p.setTitle("DiscreteSeekBar Indicator:" + Integer.toHexString(hashCode()));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* I'm NOT completely sure how all this bitwise things work...
|
||||
*
|
||||
* @param curFlags
|
||||
* @return
|
||||
*/
|
||||
private int computeFlags(int curFlags) {
|
||||
curFlags &= ~(
|
||||
WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES |
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
|
||||
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
|
||||
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
|
||||
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
|
||||
curFlags |= WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
|
||||
curFlags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
curFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
||||
curFlags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
||||
return curFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Small FrameLayout class to hold and move the bubble around when requested
|
||||
* I wanted to use the {@link Marker} directly
|
||||
* but doing so would make some things harder to implement
|
||||
* (like moving the marker around, having the Marker's outline to work, etc)
|
||||
*/
|
||||
private class Floater extends FrameLayout implements MarkerDrawable.MarkerAnimationListener {
|
||||
private Marker mMarker;
|
||||
private int mOffset;
|
||||
|
||||
public Floater(Context context, AttributeSet attrs, int defStyleAttr, String maxValue, int thumbSize, int separation) {
|
||||
super(context);
|
||||
mMarker = new Marker(context, attrs, defStyleAttr, maxValue, thumbSize, separation);
|
||||
addView(mMarker, new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
measureChildren(widthMeasureSpec, heightMeasureSpec);
|
||||
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int heightSie = mMarker.getMeasuredHeight();
|
||||
setMeasuredDimension(widthSize, heightSie);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
int centerDiffX = mMarker.getMeasuredWidth() / 2;
|
||||
int offset = (mOffset - centerDiffX);
|
||||
mMarker.layout(offset, 0, offset + mMarker.getMeasuredWidth(), mMarker.getMeasuredHeight());
|
||||
}
|
||||
|
||||
public void setFloatOffset(int x) {
|
||||
mOffset = x;
|
||||
int centerDiffX = mMarker.getMeasuredWidth() / 2;
|
||||
int offset = (x - centerDiffX);
|
||||
mMarker.offsetLeftAndRight(offset - mMarker.getLeft());
|
||||
//Without hardware acceleration (or API levels<11), offsetting a view seems to NOT invalidate the proper area.
|
||||
//We should calc the proper invalidate Rect but this will be for now...
|
||||
if (!SeekBarCompat.isHardwareAccelerated(this)) {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosingComplete() {
|
||||
if (mListener != null) {
|
||||
mListener.onClosingComplete();
|
||||
}
|
||||
dismissComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpeningComplete() {
|
||||
if (mListener != null) {
|
||||
mListener.onOpeningComplete();
|
||||
}
|
||||
}
|
||||
|
||||
public void setColors(int startColor, int endColor) {
|
||||
mMarker.setColors(startColor, endColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.compat;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.seekbar.DiscreteSeekBar;
|
||||
|
||||
/**
|
||||
* Currently, there's no {@link android.animation.ValueAnimator} compatibility version
|
||||
* and as we didn't want to throw in external dependencies, we made this small class.
|
||||
* <p/>
|
||||
* <p>
|
||||
* This will work like {@link android.support.v4.view.ViewPropertyAnimatorCompat}, that is,
|
||||
* not doing anything on API<11 and using the default {@link android.animation.ValueAnimator}
|
||||
* on API>=11
|
||||
* </p>
|
||||
* <p>
|
||||
* This class is used to provide animation to the {@link DiscreteSeekBar}
|
||||
* when navigating with the Keypad
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class AnimatorCompat {
|
||||
public interface AnimationFrameUpdateListener {
|
||||
public void onAnimationFrame(float currentValue);
|
||||
}
|
||||
|
||||
AnimatorCompat() {
|
||||
|
||||
}
|
||||
|
||||
public abstract void cancel();
|
||||
|
||||
public abstract boolean isRunning();
|
||||
|
||||
public abstract void setDuration(int progressAnimationDuration);
|
||||
|
||||
public abstract void start();
|
||||
|
||||
public static final AnimatorCompat create(float start, float end, AnimationFrameUpdateListener listener) {
|
||||
return new AnimatorCompatBase(start, end, listener);
|
||||
}
|
||||
|
||||
private static class AnimatorCompatBase extends AnimatorCompat {
|
||||
|
||||
private final AnimationFrameUpdateListener mListener;
|
||||
private final float mEndValue;
|
||||
|
||||
public AnimatorCompatBase(float start, float end, AnimationFrameUpdateListener listener) {
|
||||
mListener = listener;
|
||||
mEndValue = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDuration(int progressAnimationDuration) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mListener.onAnimationFrame(mEndValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.compat;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
import com.yunbao.faceunity.seekbar.internal.drawable.AlmostRippleDrawable;
|
||||
import com.yunbao.faceunity.seekbar.internal.drawable.MarkerDrawable;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper compatibility class to call some API-Specific methods
|
||||
* And offer alternate procedures when possible
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SeekBarCompat {
|
||||
|
||||
/**
|
||||
* Sets the custom Outline provider on API>=21.
|
||||
* Does nothing on API<21
|
||||
*
|
||||
* @param view
|
||||
* @param markerDrawable
|
||||
*/
|
||||
public static void setOutlineProvider(View view, final MarkerDrawable markerDrawable) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
SeekBarCompatDontCrash.setOutlineProvider(view, markerDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Our DiscreteSeekBar implementation uses a circular drawable on API < 21
|
||||
* because we don't set it as Background, but draw it ourselves
|
||||
*
|
||||
* @param colorStateList
|
||||
* @return
|
||||
*/
|
||||
public static Drawable getRipple(ColorStateList colorStateList) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
return SeekBarCompatDontCrash.getRipple(colorStateList);
|
||||
} else {
|
||||
return new AlmostRippleDrawable(colorStateList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of the seekbar ripple
|
||||
*
|
||||
* @param drawable
|
||||
* @param colorStateList The ColorStateList the track ripple will be changed to
|
||||
*/
|
||||
public static void setRippleColor(@NonNull Drawable drawable, ColorStateList colorStateList) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
((RippleDrawable) drawable).setColor(colorStateList);
|
||||
} else {
|
||||
((AlmostRippleDrawable) drawable).setColor(colorStateList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As our DiscreteSeekBar implementation uses a circular drawable on API < 21
|
||||
* we want to use the same method to set its bounds as the Ripple's hotspot bounds.
|
||||
*
|
||||
* @param drawable
|
||||
* @param left
|
||||
* @param top
|
||||
* @param right
|
||||
* @param bottom
|
||||
*/
|
||||
public static void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
//We don't want the full size rect, Lollipop ripple would be too big
|
||||
int size = (right - left) / 8;
|
||||
DrawableCompat.setHotspotBounds(drawable, left + size, top + size, right - size, bottom - size);
|
||||
} else {
|
||||
drawable.setBounds(left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* android.support.v4.view.ViewCompat SHOULD include this once and for all!!
|
||||
* But it doesn't...
|
||||
*
|
||||
* @param view
|
||||
* @param background
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void setBackground(View view, Drawable background) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
SeekBarCompatDontCrash.setBackground(view, background);
|
||||
} else {
|
||||
view.setBackgroundDrawable(background);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TextView text direction attribute when possible
|
||||
*
|
||||
* @param textView
|
||||
* @param textDirection
|
||||
* @see TextView#setTextDirection(int)
|
||||
*/
|
||||
public static void setTextDirection(TextView textView, int textDirection) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
SeekBarCompatDontCrash.setTextDirection(textView, textDirection);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInScrollingContainer(ViewParent p) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
return SeekBarCompatDontCrash.isInScrollingContainer(p);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isHardwareAccelerated(View view) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
return SeekBarCompatDontCrash.isHardwareAccelerated(view);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.compat;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewParent;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.yunbao.faceunity.seekbar.internal.drawable.MarkerDrawable;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper compatibility class to call some API-Specific methods
|
||||
* And offer alternate procedures when possible
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TargetApi(21)
|
||||
class SeekBarCompatDontCrash {
|
||||
public static void setOutlineProvider(View marker, final MarkerDrawable markerDrawable) {
|
||||
marker.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
outline.setConvexPath(markerDrawable.getPath());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Drawable getRipple(ColorStateList colorStateList) {
|
||||
return new RippleDrawable(colorStateList, null, null);
|
||||
}
|
||||
|
||||
public static void setBackground(View view, Drawable background) {
|
||||
view.setBackground(background);
|
||||
}
|
||||
|
||||
public static void setTextDirection(TextView number, int textDirection) {
|
||||
number.setTextDirection(textDirection);
|
||||
}
|
||||
|
||||
public static boolean isInScrollingContainer(ViewParent p) {
|
||||
while (p != null && p instanceof ViewGroup) {
|
||||
if (((ViewGroup) p).shouldDelayChildPressedState()) {
|
||||
return true;
|
||||
}
|
||||
p = p.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isHardwareAccelerated(View view) {
|
||||
return view.isHardwareAccelerated();
|
||||
}
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.os.SystemClock;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
public class AlmostRippleDrawable extends StateDrawable implements Animatable {
|
||||
private static final long FRAME_DURATION = 1000 / 60;
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
private static final float INACTIVE_SCALE = 0f;
|
||||
private static final float ACTIVE_SCALE = 1f;
|
||||
private float mCurrentScale = INACTIVE_SCALE;
|
||||
private Interpolator mInterpolator;
|
||||
private long mStartTime;
|
||||
private boolean mReverse = false;
|
||||
private boolean mRunning = false;
|
||||
private int mDuration = ANIMATION_DURATION;
|
||||
private float mAnimationInitialValue;
|
||||
//We don't use colors just with our drawable state because of animations
|
||||
private int mPressedColor;
|
||||
private int mFocusedColor;
|
||||
private int mDisabledColor;
|
||||
private int mRippleColor;
|
||||
private int mRippleBgColor;
|
||||
|
||||
public AlmostRippleDrawable(@NonNull ColorStateList tintStateList) {
|
||||
super(tintStateList);
|
||||
mInterpolator = new AccelerateDecelerateInterpolator();
|
||||
setColor(tintStateList);
|
||||
}
|
||||
|
||||
public void setColor(@NonNull ColorStateList tintStateList) {
|
||||
int defaultColor = tintStateList.getDefaultColor();
|
||||
mFocusedColor = tintStateList.getColorForState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, defaultColor);
|
||||
mPressedColor = tintStateList.getColorForState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, defaultColor);
|
||||
mDisabledColor = tintStateList.getColorForState(new int[]{-android.R.attr.state_enabled}, defaultColor);
|
||||
|
||||
//The ripple should be partially transparent
|
||||
mFocusedColor = getModulatedAlphaColor(130, mFocusedColor);
|
||||
mPressedColor = getModulatedAlphaColor(130, mPressedColor);
|
||||
mDisabledColor = getModulatedAlphaColor(130, mDisabledColor);
|
||||
}
|
||||
|
||||
private static int getModulatedAlphaColor(int alphaValue, int originalColor) {
|
||||
int alpha = Color.alpha(originalColor);
|
||||
int scale = alphaValue + (alphaValue >> 7);
|
||||
alpha = alpha * scale >> 8;
|
||||
return Color.argb(alpha, Color.red(originalColor), Color.green(originalColor), Color.blue(originalColor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDraw(Canvas canvas, Paint paint) {
|
||||
Rect bounds = getBounds();
|
||||
int size = Math.min(bounds.width(), bounds.height());
|
||||
float scale = mCurrentScale;
|
||||
int rippleColor = mRippleColor;
|
||||
int bgColor = mRippleBgColor;
|
||||
float radius = (size / 2);
|
||||
float radiusAnimated = radius * scale;
|
||||
if (scale > INACTIVE_SCALE) {
|
||||
if (bgColor != 0) {
|
||||
paint.setColor(bgColor);
|
||||
paint.setAlpha(decreasedAlpha(Color.alpha(bgColor)));
|
||||
canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, paint);
|
||||
}
|
||||
if (rippleColor != 0) {
|
||||
paint.setColor(rippleColor);
|
||||
paint.setAlpha(modulateAlpha(Color.alpha(rippleColor)));
|
||||
canvas.drawCircle(bounds.centerX(), bounds.centerY(), radiusAnimated, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int decreasedAlpha(int alpha) {
|
||||
int scale = 100 + (100 >> 7);
|
||||
return alpha * scale >> 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setState(int[] stateSet) {
|
||||
int[] oldState = getState();
|
||||
boolean oldPressed = false;
|
||||
for (int i : oldState) {
|
||||
if (i == android.R.attr.state_pressed) {
|
||||
oldPressed = true;
|
||||
}
|
||||
}
|
||||
super.setState(stateSet);
|
||||
boolean focused = false;
|
||||
boolean pressed = false;
|
||||
boolean disabled = true;
|
||||
for (int i : stateSet) {
|
||||
if (i == android.R.attr.state_focused) {
|
||||
focused = true;
|
||||
} else if (i == android.R.attr.state_pressed) {
|
||||
pressed = true;
|
||||
} else if (i == android.R.attr.state_enabled) {
|
||||
disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
unscheduleSelf(mUpdater);
|
||||
mRippleColor = mDisabledColor;
|
||||
mRippleBgColor = 0;
|
||||
mCurrentScale = ACTIVE_SCALE / 2;
|
||||
invalidateSelf();
|
||||
} else {
|
||||
if (pressed) {
|
||||
animateToPressed();
|
||||
mRippleColor = mRippleBgColor = mPressedColor;
|
||||
} else if (oldPressed) {
|
||||
mRippleColor = mRippleBgColor = mPressedColor;
|
||||
animateToNormal();
|
||||
} else if (focused) {
|
||||
mRippleColor = mFocusedColor;
|
||||
mRippleBgColor = 0;
|
||||
mCurrentScale = ACTIVE_SCALE;
|
||||
invalidateSelf();
|
||||
} else {
|
||||
mRippleColor = 0;
|
||||
mRippleBgColor = 0;
|
||||
mCurrentScale = INACTIVE_SCALE;
|
||||
invalidateSelf();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void animateToPressed() {
|
||||
unscheduleSelf(mUpdater);
|
||||
if (mCurrentScale < ACTIVE_SCALE) {
|
||||
mReverse = false;
|
||||
mRunning = true;
|
||||
mAnimationInitialValue = mCurrentScale;
|
||||
float durationFactor = 1f - ((mAnimationInitialValue - INACTIVE_SCALE) / (ACTIVE_SCALE - INACTIVE_SCALE));
|
||||
mDuration = (int) (ANIMATION_DURATION * durationFactor);
|
||||
mStartTime = SystemClock.uptimeMillis();
|
||||
scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
public void animateToNormal() {
|
||||
unscheduleSelf(mUpdater);
|
||||
if (mCurrentScale > INACTIVE_SCALE) {
|
||||
mReverse = true;
|
||||
mRunning = true;
|
||||
mAnimationInitialValue = mCurrentScale;
|
||||
float durationFactor = 1f - ((mAnimationInitialValue - ACTIVE_SCALE) / (INACTIVE_SCALE - ACTIVE_SCALE));
|
||||
mDuration = (int) (ANIMATION_DURATION * durationFactor);
|
||||
mStartTime = SystemClock.uptimeMillis();
|
||||
scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAnimation(float factor) {
|
||||
float initial = mAnimationInitialValue;
|
||||
float destination = mReverse ? INACTIVE_SCALE : ACTIVE_SCALE;
|
||||
mCurrentScale = initial + (destination - initial) * factor;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private final Runnable mUpdater = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
long currentTime = SystemClock.uptimeMillis();
|
||||
long diff = currentTime - mStartTime;
|
||||
if (diff < mDuration) {
|
||||
float interpolation = mInterpolator.getInterpolation((float) diff / (float) mDuration);
|
||||
scheduleSelf(mUpdater, currentTime + FRAME_DURATION);
|
||||
updateAnimation(interpolation);
|
||||
} else {
|
||||
unscheduleSelf(mUpdater);
|
||||
mRunning = false;
|
||||
updateAnimation(1f);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
//No-Op. We control our own animation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
//No-Op. We control our own animation
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.os.SystemClock;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link StateDrawable} to draw a morphing marker symbol.
|
||||
* <p>
|
||||
* It's basically an implementation of an {@link Animatable} Drawable with the following details:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Animates from a circle shape to a "marker" shape just using a RoundRect</li>
|
||||
* <li>Animates color change from the normal state color to the pressed state color</li>
|
||||
* <li>Uses a {@link Path} to also serve as Outline for API>=21</li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class MarkerDrawable extends StateDrawable implements Animatable {
|
||||
private static final long FRAME_DURATION = 1000 / 60;
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
private float mCurrentScale = 0f;
|
||||
private Interpolator mInterpolator;
|
||||
private long mStartTime;
|
||||
private boolean mReverse = false;
|
||||
private boolean mRunning = false;
|
||||
private int mDuration = ANIMATION_DURATION;
|
||||
//size of the actual thumb drawable to use as circle state size
|
||||
private float mClosedStateSize;
|
||||
//value to store que current scale when starting an animation and interpolate from it
|
||||
private float mAnimationInitialValue;
|
||||
//extra offset directed from the View to account
|
||||
//for its internal padding between circle state and marker state
|
||||
private int mExternalOffset;
|
||||
//colors for interpolation
|
||||
private int mStartColor;//Color when the Marker is OPEN
|
||||
private int mEndColor;//Color when the arker is CLOSED
|
||||
|
||||
Path mPath = new Path();
|
||||
RectF mRect = new RectF();
|
||||
Matrix mMatrix = new Matrix();
|
||||
private MarkerAnimationListener mMarkerListener;
|
||||
|
||||
public MarkerDrawable(@NonNull ColorStateList tintList, int closedSize) {
|
||||
super(tintList);
|
||||
mInterpolator = new AccelerateDecelerateInterpolator();
|
||||
mClosedStateSize = closedSize;
|
||||
mStartColor = tintList.getColorForState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, tintList.getDefaultColor());
|
||||
mEndColor = tintList.getDefaultColor();
|
||||
|
||||
}
|
||||
|
||||
public void setExternalOffset(int offset) {
|
||||
mExternalOffset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* The two colors that will be used for the seek thumb.
|
||||
*
|
||||
* @param startColor Color used for the seek thumb
|
||||
* @param endColor Color used for popup indicator
|
||||
*/
|
||||
public void setColors(int startColor, int endColor) {
|
||||
mStartColor = startColor;
|
||||
mEndColor = endColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
void doDraw(Canvas canvas, Paint paint) {
|
||||
if (!mPath.isEmpty()) {
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
int color = blendColors(mStartColor, mEndColor, mCurrentScale);
|
||||
paint.setColor(color);
|
||||
canvas.drawPath(mPath, paint);
|
||||
}
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return mPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
computePath(bounds);
|
||||
}
|
||||
|
||||
private void computePath(Rect bounds) {
|
||||
final float currentScale = mCurrentScale;
|
||||
final Path path = mPath;
|
||||
final RectF rect = mRect;
|
||||
final Matrix matrix = mMatrix;
|
||||
|
||||
path.reset();
|
||||
int totalSize = Math.min(bounds.width(), bounds.height());
|
||||
|
||||
float initial = mClosedStateSize;
|
||||
float destination = totalSize;
|
||||
float currentSize = initial + (destination - initial) * currentScale;
|
||||
|
||||
float halfSize = currentSize / 2f;
|
||||
float inverseScale = 1f - currentScale;
|
||||
float cornerSize = halfSize * inverseScale;
|
||||
float[] corners = new float[]{halfSize, halfSize, halfSize, halfSize, halfSize, halfSize, cornerSize, cornerSize};
|
||||
rect.set(bounds.left, bounds.top, bounds.left + currentSize, bounds.top + currentSize);
|
||||
path.addRoundRect(rect, corners, Path.Direction.CCW);
|
||||
matrix.reset();
|
||||
matrix.postRotate(-45, bounds.left + halfSize, bounds.top + halfSize);
|
||||
matrix.postTranslate((bounds.width() - currentSize) / 2, 0);
|
||||
float hDiff = (bounds.bottom - currentSize - mExternalOffset) * inverseScale;
|
||||
matrix.postTranslate(0, hDiff);
|
||||
path.transform(matrix);
|
||||
}
|
||||
|
||||
private void updateAnimation(float factor) {
|
||||
float initial = mAnimationInitialValue;
|
||||
float destination = mReverse ? 0f : 1f;
|
||||
mCurrentScale = initial + (destination - initial) * factor;
|
||||
computePath(getBounds());
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
public void animateToPressed() {
|
||||
unscheduleSelf(mUpdater);
|
||||
mReverse = false;
|
||||
if (mCurrentScale < 1) {
|
||||
mRunning = true;
|
||||
mAnimationInitialValue = mCurrentScale;
|
||||
float durationFactor = 1f - mCurrentScale;
|
||||
mDuration = (int) (ANIMATION_DURATION * durationFactor);
|
||||
mStartTime = SystemClock.uptimeMillis();
|
||||
scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
|
||||
} else {
|
||||
notifyFinishedToListener();
|
||||
}
|
||||
}
|
||||
|
||||
public void animateToNormal() {
|
||||
mReverse = true;
|
||||
unscheduleSelf(mUpdater);
|
||||
if (mCurrentScale > 0) {
|
||||
mRunning = true;
|
||||
mAnimationInitialValue = mCurrentScale;
|
||||
float durationFactor = 1f - mCurrentScale;
|
||||
mDuration = ANIMATION_DURATION - (int) (ANIMATION_DURATION * durationFactor);
|
||||
mStartTime = SystemClock.uptimeMillis();
|
||||
scheduleSelf(mUpdater, mStartTime + FRAME_DURATION);
|
||||
} else {
|
||||
notifyFinishedToListener();
|
||||
}
|
||||
}
|
||||
|
||||
private final Runnable mUpdater = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
long currentTime = SystemClock.uptimeMillis();
|
||||
long diff = currentTime - mStartTime;
|
||||
if (diff < mDuration) {
|
||||
float interpolation = mInterpolator.getInterpolation((float) diff / (float) mDuration);
|
||||
scheduleSelf(mUpdater, currentTime + FRAME_DURATION);
|
||||
updateAnimation(interpolation);
|
||||
} else {
|
||||
unscheduleSelf(mUpdater);
|
||||
mRunning = false;
|
||||
updateAnimation(1f);
|
||||
notifyFinishedToListener();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public void setMarkerListener(MarkerAnimationListener listener) {
|
||||
mMarkerListener = listener;
|
||||
}
|
||||
|
||||
private void notifyFinishedToListener() {
|
||||
if (mMarkerListener != null) {
|
||||
if (mReverse) {
|
||||
mMarkerListener.onClosingComplete();
|
||||
} else {
|
||||
mMarkerListener.onOpeningComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
//No-Op. We control our own animation
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
unscheduleSelf(mUpdater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
|
||||
private static int blendColors(int color1, int color2, float factor) {
|
||||
final float inverseFactor = 1f - factor;
|
||||
float a = (Color.alpha(color1) * factor) + (Color.alpha(color2) * inverseFactor);
|
||||
float r = (Color.red(color1) * factor) + (Color.red(color2) * inverseFactor);
|
||||
float g = (Color.green(color1) * factor) + (Color.green(color2) * inverseFactor);
|
||||
float b = (Color.blue(color1) * factor) + (Color.blue(color2) * inverseFactor);
|
||||
return Color.argb((int) a, (int) r, (int) g, (int) b);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A listener interface to porpagate animation events
|
||||
* This is the "poor's man" AnimatorListener for this Drawable
|
||||
*/
|
||||
public interface MarkerAnimationListener {
|
||||
public void onClosingComplete();
|
||||
|
||||
public void onOpeningComplete();
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
/**
|
||||
* A drawable that changes it's Paint color depending on the Drawable State
|
||||
* <p>
|
||||
* Subclasses should implement {@link #doDraw(Canvas, Paint)}
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class StateDrawable extends Drawable {
|
||||
private ColorStateList mTintStateList;
|
||||
private final Paint mPaint;
|
||||
private int mCurrentColor;
|
||||
private int mAlpha = 255;
|
||||
|
||||
public StateDrawable(@NonNull ColorStateList tintStateList) {
|
||||
super();
|
||||
setColorStateList(tintStateList);
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateful() {
|
||||
return (mTintStateList.isStateful()) || super.isStateful();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setState(int[] stateSet) {
|
||||
boolean handled = super.setState(stateSet);
|
||||
handled = updateTint(stateSet) || handled;
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
|
||||
private boolean updateTint(int[] state) {
|
||||
final int color = mTintStateList.getColorForState(state, mCurrentColor);
|
||||
if (color != mCurrentColor) {
|
||||
mCurrentColor = color;
|
||||
//We've changed states
|
||||
invalidateSelf();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
mPaint.setColor(mCurrentColor);
|
||||
int alpha = modulateAlpha(Color.alpha(mCurrentColor));
|
||||
mPaint.setAlpha(alpha);
|
||||
doDraw(canvas, mPaint);
|
||||
}
|
||||
|
||||
public void setColorStateList(@NonNull ColorStateList tintStateList) {
|
||||
mTintStateList = tintStateList;
|
||||
mCurrentColor = tintStateList.getDefaultColor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should implement this method to do the actual drawing
|
||||
*
|
||||
* @param canvas The current {@link Canvas} to draw into
|
||||
* @param paint The {@link Paint} preconfigurred with the current
|
||||
* {@link ColorStateList} color
|
||||
*/
|
||||
abstract void doDraw(Canvas canvas, Paint paint);
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mAlpha = alpha;
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
int modulateAlpha(int alpha) {
|
||||
int scale = mAlpha + (mAlpha >> 7);
|
||||
return alpha * scale >> 8;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha() {
|
||||
return mAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mPaint.setColorFilter(cf);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.yunbao.faceunity.seekbar.internal.Marker;
|
||||
|
||||
|
||||
/**
|
||||
* <h1>HACK</h1>
|
||||
* <p>
|
||||
* Special {@link StateDrawable} implementation
|
||||
* to draw the Thumb circle.
|
||||
* </p>
|
||||
* <p>
|
||||
* It's special because it will stop drawing once the state is pressed/focused BUT only after a small delay.
|
||||
* </p>
|
||||
* <p>
|
||||
* This special delay is meant to help avoiding frame glitches while the {@link Marker} is added to the Window
|
||||
* </p>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class ThumbDrawable extends StateDrawable implements Animatable {
|
||||
//The current size for this drawable. Must be converted to real DPs
|
||||
public static final int DEFAULT_SIZE_DP = 12;
|
||||
private final int mSize;
|
||||
private boolean mOpen;
|
||||
private boolean mRunning;
|
||||
|
||||
public ThumbDrawable(@NonNull ColorStateList tintStateList, int size) {
|
||||
super(tintStateList);
|
||||
mSize = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDraw(Canvas canvas, Paint paint) {
|
||||
if (!mOpen) {
|
||||
Rect bounds = getBounds();
|
||||
float radius = (mSize / 2);
|
||||
canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, paint);
|
||||
}
|
||||
}
|
||||
|
||||
public void animateToPressed() {
|
||||
scheduleSelf(opener, SystemClock.uptimeMillis() + 100);
|
||||
mRunning = true;
|
||||
}
|
||||
|
||||
public void animateToNormal() {
|
||||
mOpen = false;
|
||||
mRunning = false;
|
||||
unscheduleSelf(opener);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
private Runnable opener = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mOpen = true;
|
||||
invalidateSelf();
|
||||
mRunning = false;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
//NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
animateToNormal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
/**
|
||||
* Simple {@link StateDrawable} implementation
|
||||
* to draw circles/ovals
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class TrackOvalDrawable extends StateDrawable {
|
||||
private RectF mRectF = new RectF();
|
||||
|
||||
public TrackOvalDrawable(@NonNull ColorStateList tintStateList) {
|
||||
super(tintStateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
void doDraw(Canvas canvas, Paint paint) {
|
||||
mRectF.set(getBounds());
|
||||
canvas.drawOval(mRectF, paint);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.yunbao.faceunity.seekbar.internal.drawable;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
||||
/**
|
||||
* Simple {@link StateDrawable} implementation
|
||||
* to draw rectangles
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class TrackRectDrawable extends StateDrawable {
|
||||
public TrackRectDrawable(@NonNull ColorStateList tintStateList) {
|
||||
super(tintStateList);
|
||||
}
|
||||
|
||||
@Override
|
||||
void doDraw(Canvas canvas, Paint paint) {
|
||||
canvas.drawRect(getBounds(), paint);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
package com.yunbao.faceunity.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
import com.yunbao.faceunity.checkbox.CheckGroup;
|
||||
import com.yunbao.faceunity.control.BodyBeautyControlView;
|
||||
import com.yunbao.faceunity.control.FaceBeautyControlView;
|
||||
import com.yunbao.faceunity.control.MakeupControlView;
|
||||
import com.yunbao.faceunity.control.PropControlView;
|
||||
import com.yunbao.faceunity.data.FaceUnityDataFactory;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class FaceUnityView extends LinearLayout {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public FaceUnityView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
public FaceUnityView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
public FaceUnityView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
init();
|
||||
}
|
||||
|
||||
|
||||
private FaceUnityDataFactory mDataFactory;
|
||||
|
||||
private CheckGroup mCheckGroupView;//底部菜单
|
||||
private FaceBeautyControlView mFaceBeautyControlView;//美颜菜单
|
||||
private MakeupControlView mMakeupControlView;//美妆菜单
|
||||
private PropControlView mPropControlView;//道具菜单
|
||||
private BodyBeautyControlView mBodyBeautyControlView;//美体菜单
|
||||
private View lineView;//分割线
|
||||
|
||||
|
||||
private void init() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.layout_faceunity, this);
|
||||
initView();
|
||||
bindBottomView();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定数据工厂
|
||||
*
|
||||
* @param dataFactory FaceUnityDataFactory
|
||||
*/
|
||||
public void bindDataFactory(FaceUnityDataFactory dataFactory) {
|
||||
mDataFactory = dataFactory;
|
||||
mFaceBeautyControlView.bindDataFactory(dataFactory.mFaceBeautyDataFactory);
|
||||
mMakeupControlView.bindDataFactory(dataFactory.mMakeupDataFactory);
|
||||
mPropControlView.bindDataFactory(dataFactory.mPropDataFactory);
|
||||
mBodyBeautyControlView.bindDataFactory(dataFactory.mBodyBeautyDataFactory);
|
||||
switch (dataFactory.currentFunctionIndex) {
|
||||
case 0:
|
||||
mCheckGroupView.check(R.id.radio_beauty);
|
||||
break;
|
||||
case 1:
|
||||
mCheckGroupView.check(R.id.radio_sticker);
|
||||
break;
|
||||
case 2:
|
||||
mCheckGroupView.check(R.id.radio_makeup);
|
||||
break;
|
||||
case 3:
|
||||
mCheckGroupView.check(R.id.radio_body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化View
|
||||
*/
|
||||
private void initView() {
|
||||
mCheckGroupView = findViewById(R.id.group_function);
|
||||
mFaceBeautyControlView = findViewById(R.id.control_beauty);
|
||||
mMakeupControlView = findViewById(R.id.control_makeup);
|
||||
mPropControlView = findViewById(R.id.control_prop);
|
||||
mBodyBeautyControlView = findViewById(R.id.control_body);
|
||||
lineView = findViewById(R.id.line);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 底部功能菜单切换
|
||||
*/
|
||||
private void bindBottomView() {
|
||||
mCheckGroupView.setOnCheckedChangeListener((group, checkedId) -> {
|
||||
if (checkedId == R.id.radio_beauty) {
|
||||
showFunction(0);
|
||||
mDataFactory.onFunctionSelected(0);
|
||||
} else if (checkedId == R.id.radio_sticker) {
|
||||
showFunction(1);
|
||||
mDataFactory.onFunctionSelected(1);
|
||||
} else if (checkedId == R.id.radio_makeup) {
|
||||
showFunction(2);
|
||||
mDataFactory.onFunctionSelected(2);
|
||||
} else if (checkedId == R.id.radio_body) {
|
||||
showFunction(3);
|
||||
mDataFactory.onFunctionSelected(3);
|
||||
} else {
|
||||
showFunction(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UI菜单显示控制
|
||||
*
|
||||
* @param index Int
|
||||
*/
|
||||
private void showFunction(int index) {
|
||||
mFaceBeautyControlView.setVisibility((index == 0) ? View.VISIBLE : View.GONE);
|
||||
mPropControlView.setVisibility((index == 1) ? View.VISIBLE : View.GONE);
|
||||
mMakeupControlView.setVisibility((index == 2) ? View.VISIBLE : View.GONE);
|
||||
mBodyBeautyControlView.setVisibility((index == 3) ? View.VISIBLE : View.GONE);
|
||||
lineView.setVisibility((index != -1) ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
|
||||
}
|
1299
FaceUnity/src/main/java/com/yunbao/faceunity/utils/Authpack.java
Normal file
1299
FaceUnity/src/main/java/com/yunbao/faceunity/utils/Authpack.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,172 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
/**
|
||||
* cpu使用率获取工具类
|
||||
* Created by lirui on 2017/8/2.
|
||||
*/
|
||||
|
||||
public class CPUInfoUtil {
|
||||
private long lastTotalCpu = 0;
|
||||
private long lastProcessCpu = 0;
|
||||
|
||||
private final String PackageName;
|
||||
|
||||
private volatile boolean isRunningCPU = false;
|
||||
private volatile double cpuRate = 0;
|
||||
|
||||
public CPUInfoUtil(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
final String pn = context.getPackageName();
|
||||
if (pn.length() <= 16) {
|
||||
PackageName = pn;
|
||||
} else {
|
||||
PackageName = pn.substring(0, 15) + "+";
|
||||
}
|
||||
// Log.e(TAG, "CSVUtils PackageName " + PackageName);
|
||||
isRunningCPU = true;
|
||||
CPUInfoThread cpuinfothread = new CPUInfoThread();
|
||||
cpuinfothread.start();
|
||||
} else {
|
||||
PackageName = null;
|
||||
}
|
||||
}
|
||||
|
||||
public double getProcessCpuUsed() {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
return cpuRate;
|
||||
} else {
|
||||
double pcpu = 0;
|
||||
double tmp = 1.0;
|
||||
long nowTotalCpu = getTotalCpu();
|
||||
long nowProcessCpu = getMyProcessCpu();
|
||||
if (nowTotalCpu != 0 && (nowTotalCpu - lastTotalCpu) != 0) {
|
||||
// Log.e(TAG, "cpu used nowProcessCpu " + nowProcessCpu + " lastProcessCpu " + lastProcessCpu + " nowTotalCpu " + nowTotalCpu + " lastTotalCpu " + lastTotalCpu);
|
||||
pcpu = 100 * (tmp * (nowProcessCpu - lastProcessCpu) / (nowTotalCpu - lastTotalCpu));
|
||||
}
|
||||
lastProcessCpu = nowProcessCpu;
|
||||
lastTotalCpu = nowTotalCpu;
|
||||
return pcpu < 0 ? 0 : pcpu;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
isRunningCPU = false;
|
||||
}
|
||||
}
|
||||
|
||||
private long getTotalCpu() {
|
||||
String[] cpuInfos = null;
|
||||
try {
|
||||
RandomAccessFile reader = null;
|
||||
reader = new RandomAccessFile("/proc/stat", "r");
|
||||
String load = reader.readLine();
|
||||
reader.close();
|
||||
cpuInfos = load.split(" ");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
long totalCpu = 0;
|
||||
try {
|
||||
totalCpu = Long.parseLong(cpuInfos[2])
|
||||
+ Long.parseLong(cpuInfos[3]) + Long.parseLong(cpuInfos[4])
|
||||
+ Long.parseLong(cpuInfos[6]) + Long.parseLong(cpuInfos[5])
|
||||
+ Long.parseLong(cpuInfos[7]) + Long.parseLong(cpuInfos[8]);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
return totalCpu;
|
||||
}
|
||||
|
||||
private long getMyProcessCpu() {
|
||||
String[] cpuInfos = null;
|
||||
try {
|
||||
int pid = android.os.Process.myPid();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(
|
||||
new FileInputStream("/proc/" + pid + "/stat")), 1000);
|
||||
String load = reader.readLine();
|
||||
reader.close();
|
||||
cpuInfos = load.split(" ");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
long appCpuTime = 0;
|
||||
try {
|
||||
appCpuTime = Long.parseLong(cpuInfos[13])
|
||||
+ Long.parseLong(cpuInfos[14]) + Long.parseLong(cpuInfos[15])
|
||||
+ Long.parseLong(cpuInfos[16]);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
return appCpuTime;
|
||||
}
|
||||
|
||||
class CPUInfoThread extends Thread {
|
||||
|
||||
private double allCPU = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String line = null;
|
||||
InputStream is = null;
|
||||
try {
|
||||
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
Process proc = runtime.exec("top -d 1");
|
||||
is = proc.getInputStream();
|
||||
|
||||
// 换成BufferedReader
|
||||
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
|
||||
do {
|
||||
line = buf.readLine();
|
||||
if (allCPU == 0 && line.contains("user") && line.contains("nice") && line.contains("sys") && line.contains("idle") && line.contains("iow") && line.contains("irq") && line.contains("sirq") && line.contains("host")) {
|
||||
if (line.indexOf("%cpu ") > 0)
|
||||
allCPU = Double.parseDouble(line.split("%cpu ")[0]);
|
||||
if (allCPU == 0) {
|
||||
String[] s = line.split("%,");
|
||||
for (String st : s) {
|
||||
String[] sts = st.split(" ");
|
||||
if (sts.length > 0)
|
||||
allCPU += Double.parseDouble(sts[sts.length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 读取到相应pkgName跳出循环(或者未找到)
|
||||
if (line == null || line.endsWith(PackageName)) {
|
||||
// Log.e(TAG, "cpu line : " + line);
|
||||
String str[] = line.split(" ");
|
||||
int t = 0;
|
||||
for (int i = str.length - 1; i > 0; i--) {
|
||||
if (!str[i].isEmpty() && ++t == 4) {
|
||||
// Log.e(TAG, "cpu : " + str[i] + " allCPU " + allCPU);
|
||||
cpuRate = 100 * Double.parseDouble(str[i]) / allCPU;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} while (isRunningCPU);
|
||||
|
||||
if (is != null) {
|
||||
buf.close();
|
||||
is.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
168
FaceUnity/src/main/java/com/yunbao/faceunity/utils/CSVUtils.java
Normal file
168
FaceUnity/src/main/java/com/yunbao/faceunity/utils/CSVUtils.java
Normal file
@ -0,0 +1,168 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Created by tujh on 2017/11/2.
|
||||
*/
|
||||
public class CSVUtils {
|
||||
public static final String TAG = CSVUtils.class.getSimpleName();
|
||||
/* 每 100 帧统计一次 */
|
||||
public static final int FRAME_STEP = 100;
|
||||
|
||||
public static final String COMMA = ",";
|
||||
|
||||
private OutputStreamWriter mStreamWriter;
|
||||
|
||||
private ActivityManager mActivityManager;
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
private CPUInfoUtil mCPUInfoUtil;
|
||||
|
||||
private int mFrameRate;
|
||||
private volatile double mCpuUsed;
|
||||
private volatile double mAverageFps;
|
||||
private volatile double mAverageRenderTime;
|
||||
private volatile double mMemory;
|
||||
private long mSumRenderTimeInNano;
|
||||
private volatile long mTimestamp;
|
||||
|
||||
public CSVUtils(Context context) {
|
||||
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
mCPUInfoUtil = new CPUInfoUtil(context);
|
||||
|
||||
HandlerThread handlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
|
||||
handlerThread.start();
|
||||
mHandler = new Handler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
public void initHeader(String folderName, StringBuilder headerInfo) {
|
||||
Log.d(TAG, "initHeader() called with: folderName = [" + folderName + "], headerInfo = [" + headerInfo + "]");
|
||||
StringBuilder stringBuilder = new StringBuilder().append("时间").append(COMMA)
|
||||
.append("帧率").append(COMMA)
|
||||
.append("渲染耗时").append(COMMA)
|
||||
.append("CPU").append(COMMA)
|
||||
.append("内存").append(COMMA);
|
||||
if (headerInfo != null) {
|
||||
stringBuilder.append(headerInfo);
|
||||
}
|
||||
stringBuilder.append("\n");
|
||||
|
||||
File file = new File(folderName);
|
||||
File parentFile = file.getParentFile();
|
||||
if (!parentFile.exists()) {
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
try {
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
mStreamWriter = new OutputStreamWriter(new FileOutputStream(file, false), "GBK");
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "CSVUtils: ", e);
|
||||
}
|
||||
flush(stringBuilder);
|
||||
mTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void writeCsv(final StringBuilder extraInfo, long renderTimeInNano) {
|
||||
if (mStreamWriter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSumRenderTimeInNano += renderTimeInNano;
|
||||
if (mFrameRate % FRAME_STEP == FRAME_STEP - 1) {
|
||||
mTimestamp = System.currentTimeMillis();
|
||||
mAverageFps = FPSUtil.fpsAVG(FRAME_STEP);
|
||||
mAverageRenderTime = (double) mSumRenderTimeInNano / FRAME_STEP / 1_000_000;
|
||||
mSumRenderTimeInNano = 0;
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCpuUsed = mCPUInfoUtil.getProcessCpuUsed();
|
||||
mMemory = MemoryInfoUtil.getMemory(mActivityManager.getProcessMemoryInfo(new int[]{Process.myPid()}));
|
||||
String strCPU = String.format(Locale.getDefault(), "%.2f", mCpuUsed);
|
||||
String strMemory = String.format(Locale.getDefault(), "%.2f", mMemory);
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.getDefault());
|
||||
|
||||
stringBuilder.append(dateFormat.format(new Date(mTimestamp))).append(COMMA)
|
||||
.append(String.format(Locale.getDefault(), "%.2f", mAverageFps)).append(COMMA)
|
||||
.append(String.format(Locale.getDefault(), "%.2f", mAverageRenderTime)).append(COMMA)
|
||||
.append(strCPU).append(COMMA)
|
||||
.append(strMemory).append(COMMA);
|
||||
|
||||
if (extraInfo != null) {
|
||||
stringBuilder.append(extraInfo);
|
||||
}
|
||||
stringBuilder.append("\n");
|
||||
flush(stringBuilder);
|
||||
}
|
||||
});
|
||||
}
|
||||
mFrameRate++;
|
||||
}
|
||||
|
||||
private void flush(StringBuilder stringBuilder) {
|
||||
if (mStreamWriter == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mStreamWriter.write(stringBuilder.toString());
|
||||
mStreamWriter.flush();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "flush: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
Log.d(TAG, "close: ");
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (mStreamWriter != null) {
|
||||
try {
|
||||
mStreamWriter.close();
|
||||
mStreamWriter = null;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "close: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
mHandler.getLooper().quitSafely();
|
||||
mHandler = null;
|
||||
mCPUInfoUtil.close();
|
||||
}
|
||||
|
||||
public double getCpuUsed() {
|
||||
return mCpuUsed;
|
||||
}
|
||||
|
||||
public double getMemory() {
|
||||
return mMemory;
|
||||
}
|
||||
|
||||
public double getAverageRenderTime() {
|
||||
return mAverageRenderTime;
|
||||
}
|
||||
|
||||
public double getAverageFps() {
|
||||
return mAverageFps;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
/**
|
||||
* FPS工具类
|
||||
* Created by tujh on 2018/5/24.
|
||||
*/
|
||||
public class FPSUtil {
|
||||
private static final int NANO_IN_ONE_MILLI_SECOND = 1000000;
|
||||
private static final int NANO_IN_ONE_SECOND = 1000 * NANO_IN_ONE_MILLI_SECOND;
|
||||
|
||||
private static long sLastFrameTimeStamp = 0;
|
||||
|
||||
/**
|
||||
* 每帧都计算
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static double fps() {
|
||||
long tmp = System.nanoTime();
|
||||
double fps = ((double) NANO_IN_ONE_SECOND) / (tmp - sLastFrameTimeStamp);
|
||||
sLastFrameTimeStamp = tmp;
|
||||
// Log.e(TAG, "FPS : " + fps);
|
||||
return fps;
|
||||
}
|
||||
|
||||
private static long mStartTime = 0;
|
||||
|
||||
/**
|
||||
* 平均值
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static double fpsAVG(int time) {
|
||||
long tmp = System.nanoTime();
|
||||
double fps = ((double) NANO_IN_ONE_SECOND) * time / (tmp - mStartTime);
|
||||
mStartTime = tmp;
|
||||
// Log.e(TAG, "FPS : " + fps);
|
||||
return fps;
|
||||
}
|
||||
|
||||
private long mLimitMinTime = 33333333;
|
||||
private long mLimitStartTime;
|
||||
private int mLimitFrameRate;
|
||||
|
||||
public void setLimitMinTime(long limitMinTime) {
|
||||
mLimitMinTime = limitMinTime;
|
||||
}
|
||||
|
||||
public void limit() {
|
||||
try {
|
||||
if (mLimitFrameRate == 0 || mLimitFrameRate > 600000) {
|
||||
mLimitStartTime = System.nanoTime();
|
||||
mLimitFrameRate = 0;
|
||||
}
|
||||
long sleepTime = mLimitMinTime * mLimitFrameRate++ - (System.nanoTime() - mLimitStartTime);
|
||||
if (sleepTime > 0) {
|
||||
Thread.sleep(sleepTime / NANO_IN_ONE_MILLI_SECOND, (int) (sleepTime % NANO_IN_ONE_MILLI_SECOND));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,326 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Camera;
|
||||
|
||||
import com.faceunity.core.callback.OperateCallback;
|
||||
import com.faceunity.core.entity.FURenderInputData;
|
||||
import com.faceunity.core.entity.FURenderOutputData;
|
||||
import com.faceunity.core.enumeration.CameraFacingEnum;
|
||||
import com.faceunity.core.enumeration.FUAIProcessorEnum;
|
||||
import com.faceunity.core.enumeration.FUAITypeEnum;
|
||||
import com.faceunity.core.faceunity.FURenderConfig;
|
||||
import com.faceunity.core.faceunity.FURenderKit;
|
||||
import com.faceunity.core.faceunity.FURenderManager;
|
||||
import com.faceunity.core.utils.CameraUtils;
|
||||
import com.faceunity.core.utils.FULogger;
|
||||
import com.yunbao.faceunity.listener.FURendererListener;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
public class FURenderer extends IFURenderer {
|
||||
|
||||
private static final String TAG = "FURenderer";
|
||||
|
||||
public volatile static FURenderer INSTANCE;
|
||||
|
||||
public static FURenderer getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
synchronized (FURenderer.class) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new FURenderer();
|
||||
INSTANCE.mFURenderKit = FURenderKit.getInstance();
|
||||
}
|
||||
}
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态回调监听
|
||||
*/
|
||||
private FURendererListener mFURendererListener;
|
||||
|
||||
|
||||
/* 特效FURenderKit*/
|
||||
private FURenderKit mFURenderKit;
|
||||
|
||||
/* AI道具*/
|
||||
private String BUNDLE_AI_FACE = "model" + File.separator + "ai_face_processor.bundle";
|
||||
private String BUNDLE_AI_HUMAN = "model" + File.separator + "ai_human_processor.bundle";
|
||||
|
||||
/* GL 线程 ID */
|
||||
private Long mGlThreadId = 0L;
|
||||
/* 任务队列 */
|
||||
private ArrayList<Runnable> mEventQueue = new ArrayList<>(16);
|
||||
private final Object queueLock = new Object();
|
||||
/* 渲染开关标识 */
|
||||
private volatile boolean mRendererSwitch = false;
|
||||
/* 清除队列标识 */
|
||||
private volatile boolean mClearQueue = false;
|
||||
|
||||
/* 相机角度-朝向映射 */
|
||||
private HashMap<Integer, CameraFacingEnum> cameraOrientationMap = new HashMap<>();
|
||||
|
||||
/*检测类型*/
|
||||
private FUAIProcessorEnum aIProcess = FUAIProcessorEnum.FACE_PROCESSOR;
|
||||
/*检测标识*/
|
||||
private int aIProcessTrackStatus = -1;
|
||||
|
||||
public String getVersion() {
|
||||
return mFURenderKit.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化鉴权
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
@Override
|
||||
public void setup(Context context) {
|
||||
FURenderManager.setKitDebug(FULogger.LogLevel.TRACE);
|
||||
FURenderManager.setCoreDebug(FULogger.LogLevel.DEBUG);
|
||||
FURenderManager.registerFURender(context, Authpack.A(), new OperateCallback() {
|
||||
@Override
|
||||
public void onSuccess(int i, @NotNull String s) {
|
||||
if (i == FURenderConfig.OPERATE_SUCCESS_AUTH) {
|
||||
mFURenderKit.getFUAIController().loadAIProcessor(BUNDLE_AI_FACE, FUAITypeEnum.FUAITYPE_FACEPROCESSOR);
|
||||
mFURenderKit.getFUAIController().loadAIProcessor(BUNDLE_AI_HUMAN, FUAITypeEnum.FUAITYPE_HUMAN_PROCESSOR);
|
||||
mFURenderKit.setReadBackSync(true);
|
||||
int cameraFrontOrientation = CameraUtils.INSTANCE.getCameraOrientation(Camera.CameraInfo.CAMERA_FACING_FRONT);
|
||||
int cameraBackOrientation = CameraUtils.INSTANCE.getCameraOrientation(Camera.CameraInfo.CAMERA_FACING_BACK);
|
||||
cameraOrientationMap.put(cameraFrontOrientation, CameraFacingEnum.CAMERA_FRONT);
|
||||
cameraOrientationMap.put(cameraBackOrientation, CameraFacingEnum.CAMERA_BACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFail(int i, @NotNull String s) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启合成状态
|
||||
*/
|
||||
@Override
|
||||
public void prepareRenderer(FURendererListener mFURendererListener) {
|
||||
this.mFURendererListener = mFURendererListener;
|
||||
mRendererSwitch = true;
|
||||
mClearQueue = false;
|
||||
queueEvent(() -> mGlThreadId = Thread.currentThread().getId());
|
||||
mFURendererListener.onPrepare();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 双输入接口,输入 buffer 和 texture,必须在具有 GL 环境的线程调用
|
||||
* 由于省去数据拷贝,性能相对最优,优先推荐使用。
|
||||
* 缺点是无法保证 buffer 和纹理对齐,可能出现点位和效果对不上的情况。
|
||||
*
|
||||
* @param img NV21 buffer
|
||||
* @param texId 纹理 ID
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int onDrawFrameDualInput(byte[] img, int texId, int width, int height) {
|
||||
prepareDrawFrame();
|
||||
if (!mRendererSwitch) {
|
||||
return texId;
|
||||
}
|
||||
FURenderInputData inputData = new FURenderInputData(width, height);
|
||||
if (img != null) {
|
||||
inputData.setImageBuffer(new FURenderInputData.FUImageBuffer(inputBufferType, img));
|
||||
}
|
||||
if (texId != -1) {
|
||||
inputData.setTexture(new FURenderInputData.FUTexture(inputTextureType, texId));
|
||||
}
|
||||
FURenderInputData.FURenderConfig config = inputData.getRenderConfig();
|
||||
config.setExternalInputType(externalInputType);
|
||||
config.setInputOrientation(inputOrientation);
|
||||
config.setDeviceOrientation(deviceOrientation);
|
||||
config.setInputBufferMatrix(inputBufferMatrix);
|
||||
config.setInputTextureMatrix(inputTextureMatrix);
|
||||
config.setOutputMatrix(outputMatrix);
|
||||
config.setCameraFacing(cameraFacing);
|
||||
FURenderOutputData outputData = mFURenderKit.renderWithInput(inputData);
|
||||
if (outputData.getTexture() != null && outputData.getTexture().getTexId() > 0) {
|
||||
return outputData.getTexture().getTexId();
|
||||
}
|
||||
return texId;
|
||||
}
|
||||
|
||||
public FURenderOutputData onDrawFrameInputWithReturn(byte[] img, int width, int height) {
|
||||
prepareDrawFrame();
|
||||
if (!mRendererSwitch) {
|
||||
return null ;
|
||||
}
|
||||
FURenderInputData inputData = new FURenderInputData(width, height);
|
||||
if (img != null) {
|
||||
inputData.setImageBuffer(new FURenderInputData.FUImageBuffer(inputBufferType, img));
|
||||
}
|
||||
FURenderInputData.FURenderConfig config = inputData.getRenderConfig();
|
||||
config.setExternalInputType(externalInputType);
|
||||
config.setInputOrientation(inputOrientation);
|
||||
config.setDeviceOrientation(deviceOrientation);
|
||||
config.setInputBufferMatrix(inputBufferMatrix);
|
||||
config.setInputTextureMatrix(inputTextureMatrix);
|
||||
config.setCameraFacing(cameraFacing);
|
||||
config.setNeedBufferReturn(true);
|
||||
config.setOutputMatrix(outputMatrix);
|
||||
return mFURenderKit.renderWithInput(inputData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 类似 GLSurfaceView 的 queueEvent 机制,把任务抛到 GL 线程执行。
|
||||
*
|
||||
* @param runnable
|
||||
*/
|
||||
@Override
|
||||
public void queueEvent(Runnable runnable) {
|
||||
if (runnable == null) {
|
||||
return;
|
||||
}
|
||||
if (mGlThreadId == Thread.currentThread().getId()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
synchronized (queueLock) {
|
||||
mEventQueue.add(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 释放资源
|
||||
*/
|
||||
@Override
|
||||
public void release() {
|
||||
mRendererSwitch = false;
|
||||
mClearQueue = true;
|
||||
mGlThreadId = 0L;
|
||||
synchronized (queueLock) {
|
||||
mEventQueue.clear();
|
||||
mClearQueue = false;
|
||||
mFURenderKit.release();
|
||||
aIProcessTrackStatus = -1;
|
||||
if (mFURendererListener != null) {
|
||||
mFURendererListener.onRelease();
|
||||
mFURendererListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 渲染前置执行
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private void prepareDrawFrame() {
|
||||
benchmarkFPS();
|
||||
|
||||
// 执行任务队列中的任务
|
||||
synchronized (queueLock) {
|
||||
while (!mEventQueue.isEmpty() && !mClearQueue) {
|
||||
mEventQueue.remove(0).run();
|
||||
}
|
||||
}
|
||||
// AI检测
|
||||
trackStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置检测类型
|
||||
*
|
||||
* @param type
|
||||
*/
|
||||
@Override
|
||||
public void setAIProcessTrackType(FUAIProcessorEnum type) {
|
||||
aIProcess = type;
|
||||
aIProcessTrackStatus = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置FPS检测
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
@Override
|
||||
public void setMarkFPSEnable(boolean enable) {
|
||||
mIsRunBenchmark = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInputOrientation(int inputOrientation) {
|
||||
if (cameraOrientationMap.containsKey(inputOrientation)) {
|
||||
setCameraFacing(cameraOrientationMap.get(inputOrientation));
|
||||
}
|
||||
super.setInputOrientation(inputOrientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI识别数目检测
|
||||
*/
|
||||
private void trackStatus() {
|
||||
int trackCount;
|
||||
if (aIProcess == FUAIProcessorEnum.HAND_GESTURE_PROCESSOR) {
|
||||
trackCount = mFURenderKit.getFUAIController().handProcessorGetNumResults();
|
||||
} else if (aIProcess == FUAIProcessorEnum.HUMAN_PROCESSOR) {
|
||||
trackCount = mFURenderKit.getFUAIController().humanProcessorGetNumResults();
|
||||
} else {
|
||||
trackCount = mFURenderKit.getFUAIController().isTracking();
|
||||
}
|
||||
if (trackCount != aIProcessTrackStatus) {
|
||||
aIProcessTrackStatus = trackCount;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (mFURendererListener != null) {
|
||||
mFURendererListener.onTrackStatusChanged(aIProcess, trackCount);
|
||||
}
|
||||
}
|
||||
//endregion AI识别
|
||||
|
||||
//------------------------------FPS 渲染时长回调相关定义------------------------------------
|
||||
|
||||
private static final int NANO_IN_ONE_MILLI_SECOND = 1_000_000;
|
||||
private static final int NANO_IN_ONE_SECOND = 1_000_000_000;
|
||||
private static final int FRAME_COUNT = 20;
|
||||
private boolean mIsRunBenchmark = false;
|
||||
private int mCurrentFrameCount;
|
||||
private long mLastFrameTimestamp;
|
||||
private long mSumCallTime;
|
||||
private long mCallStartTime;
|
||||
|
||||
private void benchmarkFPS() {
|
||||
if (!mIsRunBenchmark) {
|
||||
return;
|
||||
}
|
||||
if (++mCurrentFrameCount == FRAME_COUNT) {
|
||||
long tmp = System.nanoTime();
|
||||
double fps = (double) NANO_IN_ONE_SECOND / ((double) (tmp - mLastFrameTimestamp) / FRAME_COUNT);
|
||||
double renderTime = (double) mSumCallTime / FRAME_COUNT / NANO_IN_ONE_MILLI_SECOND;
|
||||
mLastFrameTimestamp = tmp;
|
||||
mSumCallTime = 0;
|
||||
mCurrentFrameCount = 0;
|
||||
|
||||
if (mFURendererListener != null) {
|
||||
mFURendererListener.onFpsChanged(fps, renderTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import com.faceunity.core.entity.FUCameraConfig;
|
||||
|
||||
public class FaceCameraConfig {
|
||||
/**
|
||||
* 默认相机配置
|
||||
*/
|
||||
public static FUCameraConfig getDefFUCameraConfig(){
|
||||
return new FUCameraConfig();
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Environment;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 一些配置参数
|
||||
* DESC:
|
||||
* Created on 2021/3/1
|
||||
*/
|
||||
public class FaceUnityConfig {
|
||||
|
||||
/************************** 算法Model ******************************/
|
||||
// 人脸识别
|
||||
public static String BUNDLE_AI_FACE = "model" + File.separator + "ai_face_processor.bundle";
|
||||
// 手势
|
||||
public static String BUNDLE_AI_HAND = "model" + File.separator + "ai_hand_processor.bundle";
|
||||
|
||||
//获取人体bundle
|
||||
public static String getAIHumanBundle() {
|
||||
if (FaceUnityConfig.DEVICE_LEVEL > FuDeviceUtils.DEVICE_LEVEL_MID)
|
||||
return BUNDLE_AI_HUMAN_GPU;
|
||||
else
|
||||
return BUNDLE_AI_HUMAN;
|
||||
}
|
||||
|
||||
// 人体
|
||||
public static String BUNDLE_AI_HUMAN = "model" + File.separator + "ai_human_processor.bundle";
|
||||
// 人体
|
||||
public static String BUNDLE_AI_HUMAN_GPU = "model" + File.separator + "ai_human_processor_gpu.bundle";
|
||||
// 头发
|
||||
public static String BUNDLE_AI_HAIR_SEG = "model" + File.separator + "ai_hairseg.bundle";
|
||||
// 舌头
|
||||
public static String BUNDLE_AI_TONGUE = "graphics" + File.separator + "tongue.bundle";
|
||||
|
||||
/************************** 业务道具存储 ******************************/
|
||||
// 美颜
|
||||
public static String BUNDLE_FACE_BEAUTIFICATION = "graphics" + File.separator + "face_beautification.bundle";
|
||||
|
||||
// 美妆
|
||||
public static String BUNDLE_FACE_MAKEUP = "graphics" + File.separator + "face_makeup.bundle";
|
||||
// 美妆根目录
|
||||
private static String MAKEUP_RESOURCE_DIR = "makeup" + File.separator;
|
||||
//美妆单项颜色组合文件
|
||||
public static String MAKEUP_RESOURCE_COLOR_SETUP_JSON = MAKEUP_RESOURCE_DIR + "color_setup.json";
|
||||
// 美妆参数配置文件目录
|
||||
public static String MAKEUP_RESOURCE_JSON_DIR = MAKEUP_RESOURCE_DIR + "config_json" + File.separator;
|
||||
//美妆组合妆句柄文件目录
|
||||
public static String MAKEUP_RESOURCE_COMBINATION_BUNDLE_DIR = MAKEUP_RESOURCE_DIR + "combination_bundle" + File.separator;//
|
||||
//美妆妆容单项句柄文件目录
|
||||
public static String MAKEUP_RESOURCE_ITEM_BUNDLE_DIR = MAKEUP_RESOURCE_DIR + "item_bundle" + File.separator;
|
||||
|
||||
// 美体
|
||||
public static String BUNDLE_BODY_BEAUTY = "graphics" + File.separator + "body_slim.bundle";
|
||||
|
||||
//动漫滤镜
|
||||
public static String BUNDLE_ANIMATION_FILTER = "graphics" + File.separator + "fuzzytoonfilter.bundle";
|
||||
|
||||
// 美发正常色
|
||||
public static String BUNDLE_HAIR_NORMAL = "hair_seg" + File.separator + "hair_normal.bundle";
|
||||
// 美发渐变色
|
||||
public static String BUNDLE_HAIR_GRADIENT = "hair_seg" + File.separator + "hair_gradient.bundle";
|
||||
// 轻美妆
|
||||
public static String BUNDLE_LIGHT_MAKEUP = "light_makeup" + File.separator + "light_makeup.bundle";
|
||||
|
||||
// 海报换脸
|
||||
public static String BUNDLE_POSTER_CHANGE_FACE = "change_face" + File.separator + "change_face.bundle";
|
||||
|
||||
// 绿幕抠像
|
||||
public static String BUNDLE_BG_SEG_GREEN = "bg_seg_green" + File.separator + "green_screen.bundle";
|
||||
|
||||
// 3D抗锯齿
|
||||
public static String BUNDLE_ANTI_ALIASING = "graphics" + File.separator + "fxaa.bundle";
|
||||
|
||||
// 人像分割
|
||||
public static String BUNDLE_BG_SEG_CUSTOM = "effect" + File.separator + "segment" + File.separator + "bg_segment.bundle";
|
||||
|
||||
//mask bundle
|
||||
public static String BUNDLE_LANDMARKS = "effect" + File.separator + "landmarks.bundle";
|
||||
|
||||
//设备等级默认为中级
|
||||
public static int DEVICE_LEVEL = FuDeviceUtils.DEVICE_LEVEL_HIGH;
|
||||
|
||||
//人脸置信度 标准
|
||||
public static float FACE_CONFIDENCE_SCORE = 0.95f;
|
||||
|
||||
//测试使用 -> 是否开启人脸点位,目前仅在美颜,美妆 情况下使用
|
||||
public static boolean IS_OPEN_LAND_MARK = false;
|
||||
|
||||
//设备名称
|
||||
public static String DEVICE_NAME = "";
|
||||
|
||||
//是否开启日志重定向到文件
|
||||
public static boolean OPEN_FILE_LOG = false;
|
||||
//TAG
|
||||
public static final String APP_NAME = "KotlinFaceUnityDemo";
|
||||
//文件夹路径
|
||||
public static String OPEN_FILE_PATH = Environment.getExternalStoragePublicDirectory("") + File.separator + "FaceUnity" + File.separator + APP_NAME + File.separator;
|
||||
//文件夹名称
|
||||
public static String OPEN_FILE_NAME = "openFile.txt";
|
||||
//文件大小
|
||||
public static int OPEN_FILE_MAX_SIZE = 100 * 1024 * 1024;
|
||||
//文件数量
|
||||
public static int OPEN_FILES = 100;
|
||||
|
||||
//timeProfile是否开启
|
||||
public static boolean OPEN_TIME_PROFILE_LOG = false;
|
||||
//timeProfile文件夹路径
|
||||
public static String OPEN_TIME_PROFILE_PATH = Environment.getExternalStoragePublicDirectory("") + File.separator + "FaceUnity" + File.separator + APP_NAME + File.separator;
|
||||
|
||||
//是否开启美颜序列化到磁盘
|
||||
public static boolean OPEN_FACE_BEAUTY_TO_FILE = true;
|
||||
|
||||
//获取缓存路径
|
||||
public static String cacheFilePath(Application application){
|
||||
return application.getCacheDir() + File.separator + "attribute";
|
||||
}
|
||||
|
||||
//绿幕背景切换的时候跳过的帧数
|
||||
public static final int BG_GREEN_FILTER_FRAME = 1;
|
||||
|
||||
//测试用是否展示效果还原按钮
|
||||
public static final boolean IS_SHOW_RESET_BUTTON = false;
|
||||
}
|
@ -0,0 +1,720 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.opengl.GLES20;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.faceunity.core.faceunity.OffLineRenderHandler;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 工具类
|
||||
*/
|
||||
public class FuDeviceUtils {
|
||||
|
||||
public static final String TAG = "FuDeviceUtils";
|
||||
public static final String DEVICE_LEVEL = "device_level";
|
||||
|
||||
public static final int DEVICE_LEVEL_HIGH = 2;
|
||||
public static final int DEVICE_LEVEL_MID = 1;
|
||||
public static final int DEVICE_LEVEL_LOW = 0;
|
||||
|
||||
/**
|
||||
* The default return value of any method in this class when an
|
||||
* error occurs or when processing fails (Currently set to -1). Use this to check if
|
||||
* the information about the device in question was successfully obtained.
|
||||
*/
|
||||
public static final int DEVICEINFO_UNKNOWN = -1;
|
||||
|
||||
private static final FileFilter CPU_FILTER = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
String path = pathname.getName();
|
||||
//regex is slow, so checking char by char.
|
||||
if (path.startsWith("cpu")) {
|
||||
for (int i = 3; i < path.length(); i++) {
|
||||
if (!Character.isDigit(path.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the total RAM of the device through Android API or /proc/meminfo.
|
||||
*
|
||||
* @param c - Context object for current running activity.
|
||||
* @return Total RAM that the device has, or DEVICEINFO_UNKNOWN = -1 in the event of an error.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public static long getTotalMemory(Context c) {
|
||||
// memInfo.totalMem not supported in pre-Jelly Bean APIs.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
|
||||
ActivityManager am = (ActivityManager) c.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
am.getMemoryInfo(memInfo);
|
||||
if (memInfo != null) {
|
||||
return memInfo.totalMem;
|
||||
} else {
|
||||
return DEVICEINFO_UNKNOWN;
|
||||
}
|
||||
} else {
|
||||
long totalMem = DEVICEINFO_UNKNOWN;
|
||||
try {
|
||||
FileInputStream stream = new FileInputStream("/proc/meminfo");
|
||||
try {
|
||||
totalMem = parseFileForValue("MemTotal", stream);
|
||||
totalMem *= 1024;
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return totalMem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for reading the clock speed of a CPU core on the device. Will read from either
|
||||
* {@code /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq} or {@code /proc/cpuinfo}.
|
||||
*
|
||||
* @return Clock speed of a core on the device, or -1 in the event of an error.
|
||||
*/
|
||||
public static int getCPUMaxFreqKHz() {
|
||||
int maxFreq = DEVICEINFO_UNKNOWN;
|
||||
try {
|
||||
for (int i = 0; i < getNumberOfCPUCores(); i++) {
|
||||
String filename =
|
||||
"/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq";
|
||||
File cpuInfoMaxFreqFile = new File(filename);
|
||||
if (cpuInfoMaxFreqFile.exists() && cpuInfoMaxFreqFile.canRead()) {
|
||||
byte[] buffer = new byte[128];
|
||||
FileInputStream stream = new FileInputStream(cpuInfoMaxFreqFile);
|
||||
try {
|
||||
stream.read(buffer);
|
||||
int endIndex = 0;
|
||||
//Trim the first number out of the byte buffer.
|
||||
while (Character.isDigit(buffer[endIndex]) && endIndex < buffer.length) {
|
||||
endIndex++;
|
||||
}
|
||||
String str = new String(buffer, 0, endIndex);
|
||||
Integer freqBound = Integer.parseInt(str);
|
||||
if (freqBound > maxFreq) {
|
||||
maxFreq = freqBound;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
//Fall through and use /proc/cpuinfo.
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxFreq == DEVICEINFO_UNKNOWN) {
|
||||
FileInputStream stream = new FileInputStream("/proc/cpuinfo");
|
||||
try {
|
||||
int freqBound = parseFileForValue("cpu MHz", stream);
|
||||
freqBound *= 1024; //MHz -> kHz
|
||||
if (freqBound > maxFreq) maxFreq = freqBound;
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
maxFreq = DEVICEINFO_UNKNOWN; //Fall through and return unknown.
|
||||
}
|
||||
return maxFreq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the number of CPU cores from the first available information from
|
||||
* {@code /sys/devices/system/cpu/possible}, {@code /sys/devices/system/cpu/present},
|
||||
* then {@code /sys/devices/system/cpu/}.
|
||||
*
|
||||
* @return Number of CPU cores in the phone, or DEVICEINFO_UKNOWN = -1 in the event of an error.
|
||||
*/
|
||||
public static int getNumberOfCPUCores() {
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
|
||||
// Gingerbread doesn't support giving a single application access to both cores, but a
|
||||
// handful of devices (Atrix 4G and Droid X2 for example) were released with a dual-core
|
||||
// chipset and Gingerbread; that can let an app in the background run without impacting
|
||||
// the foreground application. But for our purposes, it makes them single core.
|
||||
return 1;
|
||||
}
|
||||
int cores;
|
||||
try {
|
||||
cores = getCoresFromFileInfo("/sys/devices/system/cpu/possible");
|
||||
if (cores == DEVICEINFO_UNKNOWN) {
|
||||
cores = getCoresFromFileInfo("/sys/devices/system/cpu/present");
|
||||
}
|
||||
if (cores == DEVICEINFO_UNKNOWN) {
|
||||
cores = new File("/sys/devices/system/cpu/").listFiles(CPU_FILTER).length;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
cores = DEVICEINFO_UNKNOWN;
|
||||
} catch (NullPointerException e) {
|
||||
cores = DEVICEINFO_UNKNOWN;
|
||||
}
|
||||
return cores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read file contents from the file location to determine the number of cores on device.
|
||||
*
|
||||
* @param fileLocation The location of the file with CPU information
|
||||
* @return Number of CPU cores in the phone, or DEVICEINFO_UKNOWN = -1 in the event of an error.
|
||||
*/
|
||||
private static int getCoresFromFileInfo(String fileLocation) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new FileInputStream(fileLocation);
|
||||
BufferedReader buf = new BufferedReader(new InputStreamReader(is));
|
||||
String fileContents = buf.readLine();
|
||||
buf.close();
|
||||
return getCoresFromFileString(fileContents);
|
||||
} catch (IOException e) {
|
||||
return DEVICEINFO_UNKNOWN;
|
||||
} finally {
|
||||
if (is != null) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts from a CPU core information format to number of cores.
|
||||
*
|
||||
* @param str The CPU core information string, in the format of "0-N"
|
||||
* @return The number of cores represented by this string
|
||||
*/
|
||||
private static int getCoresFromFileString(String str) {
|
||||
if (str == null || !str.matches("0-[\\d]+$")) {
|
||||
return DEVICEINFO_UNKNOWN;
|
||||
}
|
||||
return Integer.valueOf(str.substring(2)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for reading values from system files, using a minimised buffer.
|
||||
*
|
||||
* @param textToMatch - Text in the system files to read for.
|
||||
* @param stream - FileInputStream of the system file being read from.
|
||||
* @return A numerical value following textToMatch in specified the system file.
|
||||
* -1 in the event of a failure.
|
||||
*/
|
||||
private static int parseFileForValue(String textToMatch, FileInputStream stream) {
|
||||
byte[] buffer = new byte[1024];
|
||||
try {
|
||||
int length = stream.read(buffer);
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (buffer[i] == '\n' || i == 0) {
|
||||
if (buffer[i] == '\n') i++;
|
||||
for (int j = i; j < length; j++) {
|
||||
int textIndex = j - i;
|
||||
//Text doesn't match query at some point.
|
||||
if (buffer[j] != textToMatch.charAt(textIndex)) {
|
||||
break;
|
||||
}
|
||||
//Text matches query here.
|
||||
if (textIndex == textToMatch.length() - 1) {
|
||||
return extractValue(buffer, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//Ignore any exceptions and fall through to return unknown value.
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
return DEVICEINFO_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method used by {@link #parseFileForValue(String, FileInputStream) parseFileForValue}. Parses
|
||||
* the next available number after the match in the file being read and returns it as an integer.
|
||||
*
|
||||
* @param index - The index in the buffer array to begin looking.
|
||||
* @return The next number on that line in the buffer, returned as an int. Returns
|
||||
* DEVICEINFO_UNKNOWN = -1 in the event that no more numbers exist on the same line.
|
||||
*/
|
||||
private static int extractValue(byte[] buffer, int index) {
|
||||
while (index < buffer.length && buffer[index] != '\n') {
|
||||
if (Character.isDigit(buffer[index])) {
|
||||
int start = index;
|
||||
index++;
|
||||
while (index < buffer.length && Character.isDigit(buffer[index])) {
|
||||
index++;
|
||||
}
|
||||
String str = new String(buffer, 0, start, index - start);
|
||||
return Integer.parseInt(str);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return DEVICEINFO_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前剩余内存(ram)
|
||||
*
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public static long getAvailMemory(Context context) {
|
||||
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
|
||||
am.getMemoryInfo(mi);
|
||||
return mi.availMem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取厂商信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getBrand() {
|
||||
return Build.BRAND;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机机型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getModel() {
|
||||
return Build.MODEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取硬件信息(cpu型号)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getHardWare() {
|
||||
try {
|
||||
FileReader fr = new FileReader("/proc/cpuinfo");
|
||||
BufferedReader br = new BufferedReader(fr);
|
||||
String text;
|
||||
String last = "";
|
||||
while ((text = br.readLine()) != null) {
|
||||
last = text;
|
||||
}
|
||||
//一般机型的cpu型号都会在cpuinfo文件的最后一行
|
||||
if (last.contains("Hardware")) {
|
||||
String[] hardWare = last.split(":\\s+", 2);
|
||||
return hardWare[1];
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Build.HARDWARE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Level judgement based on current memory and CPU.
|
||||
*
|
||||
* @param context - Context object.
|
||||
* @return
|
||||
*/
|
||||
public static int judgeDeviceLevel(Context context) {
|
||||
//加一个sp读取 和 设置
|
||||
int cacheDeviceLevel = getCacheDeviceLevel(context);
|
||||
if (cacheDeviceLevel > -1) {
|
||||
return cacheDeviceLevel;
|
||||
}
|
||||
|
||||
int level;
|
||||
//有一些设备不符合下述的判断规则,则走一个机型判断模式
|
||||
int specialDevice = judgeDeviceLevelInDeviceName();
|
||||
if (specialDevice >= 0) return specialDevice;
|
||||
|
||||
int ramLevel = judgeMemory(context);
|
||||
int cpuLevel = judgeCPU();
|
||||
if (ramLevel == 0 || ramLevel == 1 || cpuLevel == 0) {
|
||||
level = DEVICE_LEVEL_LOW;
|
||||
} else {
|
||||
if (cpuLevel > 1) {
|
||||
level = DEVICE_LEVEL_HIGH;
|
||||
} else {
|
||||
level = DEVICE_LEVEL_MID;
|
||||
}
|
||||
}
|
||||
Log.d(TAG,"DeviceLevel: " + level);
|
||||
saveCacheDeviceLevel(context,level);
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Level judgement based on current GPU.
|
||||
* 需要GL环境
|
||||
* @return
|
||||
*/
|
||||
public static int judgeDeviceLevelGPU(Context context) {
|
||||
int cacheDeviceLevel = getCacheDeviceLevel(context);
|
||||
if (cacheDeviceLevel > -1) {
|
||||
return cacheDeviceLevel;
|
||||
}
|
||||
|
||||
OffLineRenderHandler.getInstance().onResume();
|
||||
CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
//加一个sp读取 和 设置
|
||||
final int[] level = {-1};
|
||||
//有一些设备不符合下述的判断规则,则走一个机型判断模式
|
||||
OffLineRenderHandler.getInstance().queueEvent(() -> {
|
||||
try {
|
||||
//高低端名单
|
||||
int specialDevice = judgeDeviceLevelInDeviceName();
|
||||
level[0] = specialDevice;
|
||||
if (specialDevice >= 0) return;
|
||||
|
||||
String glRenderer = GLES20.glGetString(GLES20.GL_RENDERER); //GPU 渲染器
|
||||
String glVendor = GLES20.glGetString(GLES20.GL_VENDOR); //GPU 供应商
|
||||
int GPUVersion;
|
||||
if ("Qualcomm".equals(glVendor)) {
|
||||
//高通
|
||||
if (glRenderer != null && glRenderer.startsWith("Adreno")) {
|
||||
//截取后面的数字
|
||||
String GPUVersionStr = glRenderer.substring(glRenderer.lastIndexOf(" ") + 1);
|
||||
try {
|
||||
GPUVersion = Integer.parseInt(GPUVersionStr);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
//可能是后面还包含了非数字的东西,那么截取三位
|
||||
String GPUVersionStrNew = GPUVersionStr.substring(0,3);
|
||||
GPUVersion = Integer.parseInt(GPUVersionStrNew);
|
||||
}
|
||||
|
||||
if (GPUVersion >= 512) {
|
||||
level[0] = DEVICE_LEVEL_HIGH;
|
||||
} else {
|
||||
level[0] = DEVICE_LEVEL_MID;
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
} else if ("ARM".equals(glVendor)) {
|
||||
//ARM
|
||||
if (glRenderer != null && glRenderer.startsWith("Mali")) {
|
||||
//截取-后面的东西
|
||||
String GPUVersionStr = glRenderer.substring(glRenderer.lastIndexOf("-") + 1);
|
||||
String strStart = GPUVersionStr.substring(0, 1);
|
||||
String strEnd = GPUVersionStr.substring(1);
|
||||
try {
|
||||
GPUVersion = Integer.parseInt(strEnd);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
//可能是后面还包含了非数字的东西,那么截取三位
|
||||
String strEndNew = strEnd.substring(0,2);
|
||||
GPUVersion = Integer.parseInt(strEndNew);
|
||||
}
|
||||
|
||||
if ("G".equals(strStart)) {
|
||||
if (GPUVersion >= 51) {
|
||||
level[0] = DEVICE_LEVEL_HIGH;
|
||||
} else {
|
||||
level[0] = DEVICE_LEVEL_MID;
|
||||
}
|
||||
} else if ("T".equals(strStart)) {
|
||||
if (GPUVersion > 880) {
|
||||
level[0] = DEVICE_LEVEL_HIGH;
|
||||
} else {
|
||||
level[0] = DEVICE_LEVEL_MID;
|
||||
}
|
||||
}
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
level[0] = -1;
|
||||
countDownLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
countDownLatch.await(200,TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
OffLineRenderHandler.getInstance().onPause();
|
||||
|
||||
//存储设备等级
|
||||
saveCacheDeviceLevel(context,level[0]);
|
||||
Log.d(TAG,"DeviceLevel: " + level);
|
||||
return level[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* -1 不是特定的高低端机型
|
||||
* @return
|
||||
*/
|
||||
private static int judgeDeviceLevelInDeviceName() {
|
||||
String currentDeviceName = getDeviceName();
|
||||
for (String deviceName:upscaleDevice) {
|
||||
if (deviceName.equals(currentDeviceName)) {
|
||||
return DEVICE_LEVEL_HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
for (String deviceName:middleDevice) {
|
||||
if (deviceName.equals(currentDeviceName)) {
|
||||
return DEVICE_LEVEL_MID;
|
||||
}
|
||||
}
|
||||
|
||||
for (String deviceName:lowDevice) {
|
||||
if (deviceName.equals(currentDeviceName)) {
|
||||
return DEVICE_LEVEL_LOW;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static final String[] upscaleDevice = {"vivo X6S A","MHA-AL00","VKY-AL00","V1838A"};
|
||||
public static final String[] lowDevice = {};
|
||||
public static final String[] middleDevice = {"OPPO R11s","PAR-AL00","MI 8 Lite","ONEPLUS A6000","PRO 6","PRO 7 Plus"};
|
||||
|
||||
/**
|
||||
* 评定内存的等级.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static int judgeMemory(Context context) {
|
||||
long ramMB = getTotalMemory(context) / (1024 * 1024);
|
||||
int level = -1;
|
||||
if (ramMB <= 2000) { //2G或以下的最低档
|
||||
level = 0;
|
||||
} else if (ramMB <= 3000) { //2-3G
|
||||
level = 1;
|
||||
} else if (ramMB <= 4000) { //4G档 2018主流中端机
|
||||
level = 2;
|
||||
} else if (ramMB <= 6000) { //6G档 高端机
|
||||
level = 3;
|
||||
} else { //6G以上 旗舰机配置
|
||||
level = 4;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 评定CPU等级.(按频率和厂商型号综合判断)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static int judgeCPU() {
|
||||
int level = 0;
|
||||
String cpuName = getHardWare();
|
||||
int freqMHz = getCPUMaxFreqKHz() / 1024;
|
||||
|
||||
//一个不符合下述规律的高级白名单
|
||||
//如果可以获取到CPU型号名称 -> 根据不同的名称走不同判定策略
|
||||
if (!TextUtils.isEmpty(cpuName)) {
|
||||
if (cpuName.contains("qcom") || cpuName.contains("Qualcomm")) { //高通骁龙
|
||||
return judgeQualcommCPU(cpuName, freqMHz);
|
||||
} else if (cpuName.contains("hi") || cpuName.contains("kirin")) { //海思麒麟
|
||||
return judgeSkinCPU(cpuName, freqMHz);
|
||||
} else if (cpuName.contains("MT")) {//联发科
|
||||
return judgeMTCPU(cpuName, freqMHz);
|
||||
}
|
||||
}
|
||||
|
||||
//cpu型号无法获取的普通规则
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else if (freqMHz <= 1950) { //2GHz 低中端
|
||||
level = 1;
|
||||
} else if (freqMHz <= 2500) { //2.2 2.3g 中高端
|
||||
level = 2;
|
||||
} else { //高端
|
||||
level = 3;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 联发科芯片等级判定
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static int judgeMTCPU(String cpuName, int freqMHz) {
|
||||
//P60之前的全是低端机 MT6771V/C
|
||||
int level = 0;
|
||||
int mtCPUVersion = getMTCPUVersion(cpuName);
|
||||
if (mtCPUVersion == -1) {
|
||||
//读取不出version 按照一个比较严格的方式来筛选出高端机
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else if (freqMHz <= 2200) { //2GHz 低中端
|
||||
level = 1;
|
||||
} else if (freqMHz <= 2700) { //2.2 2.3g 中高端
|
||||
level = 2;
|
||||
} else { //高端
|
||||
level = 3;
|
||||
}
|
||||
} else if (mtCPUVersion < 6771) {
|
||||
//均为中低端机
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else { //2GHz 中端
|
||||
level = 1;
|
||||
}
|
||||
} else {
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else if (freqMHz <= 1900) { //2GHz 低中端
|
||||
level = 1;
|
||||
} else if (freqMHz <= 2500) { //2.2 2.3g 中高端
|
||||
level = 2;
|
||||
} else { //高端
|
||||
level = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过联发科CPU型号定义 -> 获取cpu version
|
||||
*
|
||||
* @param cpuName
|
||||
* @return
|
||||
*/
|
||||
private static int getMTCPUVersion(String cpuName) {
|
||||
//截取MT后面的四位数字
|
||||
int cpuVersion = -1;
|
||||
if (cpuName.length() > 5) {
|
||||
String cpuVersionStr = cpuName.substring(2, 6);
|
||||
try {
|
||||
cpuVersion = Integer.valueOf(cpuVersionStr);
|
||||
} catch (NumberFormatException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return cpuVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高通骁龙芯片等级判定
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static int judgeQualcommCPU(String cpuName, int freqMHz) {
|
||||
int level = 0;
|
||||
//xxxx inc MSM8937 比较老的芯片
|
||||
//7 8 xxx inc SDM710
|
||||
if (cpuName.contains("MSM")) {
|
||||
//老芯片
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else { //2GHz 低中端
|
||||
level = 1;
|
||||
}
|
||||
} else {
|
||||
//新的芯片
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else if (freqMHz <= 2000) { //2GHz 低中端
|
||||
level = 1;
|
||||
} else if (freqMHz <= 2500) { //2.2 2.3g 中高端
|
||||
level = 2;
|
||||
} else { //高端
|
||||
level = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 麒麟芯片等级判定
|
||||
*
|
||||
* @param freqMHz
|
||||
* @return
|
||||
*/
|
||||
private static int judgeSkinCPU(String cpuName, int freqMHz) {
|
||||
//型号 -> kirin710之后 & 最高核心频率
|
||||
int level = 0;
|
||||
if (cpuName.startsWith("hi")) {
|
||||
//这个是海思的芯片中低端
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else if (freqMHz <= 2000) { //2GHz 低中端
|
||||
level = 1;
|
||||
}
|
||||
} else {
|
||||
//这个是海思麒麟的芯片
|
||||
if (freqMHz <= 1600) { //1.5G 低端
|
||||
level = 0;
|
||||
} else if (freqMHz <= 2000) { //2GHz 低中端
|
||||
level = 1;
|
||||
} else if (freqMHz <= 2500) { //2.2 2.3g 中高端
|
||||
level = 2;
|
||||
} else { //高端
|
||||
level = 3;
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
public static final String Nexus_6P = "Nexus 6P";
|
||||
|
||||
/**
|
||||
* 获取设备名
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String getDeviceName() {
|
||||
String deviceName = "";
|
||||
if (Build.MODEL != null) deviceName = Build.MODEL;
|
||||
Log.d(TAG,"deviceName: " + deviceName);
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存设备等级
|
||||
*
|
||||
* @param level
|
||||
*/
|
||||
public static void saveCacheDeviceLevel(Context context,int level) {
|
||||
SharedPreferences sp = context.getSharedPreferences(DEVICE_LEVEL, MODE_PRIVATE);
|
||||
sp.edit().putInt(DEVICE_LEVEL, level).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备等级
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static int getCacheDeviceLevel(Context context) {
|
||||
SharedPreferences sp = context.getSharedPreferences(DEVICE_LEVEL, MODE_PRIVATE);
|
||||
return sp.getInt(DEVICE_LEVEL, -1);
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.faceunity.core.enumeration.CameraFacingEnum;
|
||||
import com.faceunity.core.enumeration.FUAIProcessorEnum;
|
||||
import com.faceunity.core.enumeration.FUExternalInputEnum;
|
||||
import com.faceunity.core.enumeration.FUInputBufferEnum;
|
||||
import com.faceunity.core.enumeration.FUInputTextureEnum;
|
||||
import com.faceunity.core.enumeration.FUTransformMatrixEnum;
|
||||
import com.yunbao.faceunity.listener.FURendererListener;
|
||||
|
||||
/**
|
||||
* DESC:
|
||||
* Created on 2021/4/26
|
||||
*/
|
||||
abstract class IFURenderer {
|
||||
|
||||
/**
|
||||
* 渲染属性
|
||||
*/
|
||||
protected FUExternalInputEnum externalInputType = FUExternalInputEnum.EXTERNAL_INPUT_TYPE_CAMERA;//数据源类型
|
||||
protected FUInputTextureEnum inputTextureType = FUInputTextureEnum.FU_ADM_FLAG_EXTERNAL_OES_TEXTURE;//纹理类型
|
||||
protected FUInputBufferEnum inputBufferType = FUInputBufferEnum.FU_FORMAT_NV21_BUFFER;//数据类型
|
||||
protected int inputOrientation = 270;//数据源朝向
|
||||
protected int deviceOrientation = 90;//手机设备朝向
|
||||
protected CameraFacingEnum cameraFacing = CameraFacingEnum.CAMERA_FRONT; //数据源为相机时候->前后置相机
|
||||
protected FUTransformMatrixEnum inputTextureMatrix = FUTransformMatrixEnum.CCROT0_FLIPVERTICAL;//纹理旋转类型
|
||||
protected FUTransformMatrixEnum inputBufferMatrix = FUTransformMatrixEnum.CCROT0_FLIPVERTICAL;//图象旋转类型
|
||||
protected FUTransformMatrixEnum outputMatrix = FUTransformMatrixEnum.CCROT0;//图象旋转类型
|
||||
|
||||
|
||||
/**
|
||||
* 初始化鉴权
|
||||
*/
|
||||
public abstract void setup(Context context);
|
||||
|
||||
|
||||
/**
|
||||
* 双输入接口,输入 buffer 和 texture,必须在具有 GL 环境的线程调用
|
||||
* 由于省去数据拷贝,性能相对最优,优先推荐使用。
|
||||
* 缺点是无法保证 buffer 和纹理对齐,可能出现点位和效果对不上的情况。
|
||||
*
|
||||
* @param img NV21 buffer
|
||||
* @param texId 纹理 ID
|
||||
* @param width 宽
|
||||
* @param height 高
|
||||
* @return
|
||||
*/
|
||||
public abstract int onDrawFrameDualInput(byte[] img, int texId, int width, int height);
|
||||
|
||||
/**
|
||||
* 类似 GLSurfaceView 的 queueEvent 机制,把任务抛到 GL 线程执行。
|
||||
*
|
||||
* @param runnable
|
||||
*/
|
||||
public abstract void queueEvent(Runnable runnable);
|
||||
|
||||
|
||||
/**
|
||||
* 设置检测类型
|
||||
*
|
||||
* @param type
|
||||
*/
|
||||
public abstract void setAIProcessTrackType(FUAIProcessorEnum type);
|
||||
|
||||
/**
|
||||
* 资源释放
|
||||
*/
|
||||
public abstract void release();
|
||||
|
||||
/**
|
||||
* 开启合成状态
|
||||
*/
|
||||
public abstract void prepareRenderer(FURendererListener mFURendererListener);
|
||||
|
||||
|
||||
/**
|
||||
* 设置FPS检测
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
public abstract void setMarkFPSEnable(boolean enable);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取输入源类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FUExternalInputEnum getExternalInputType() {
|
||||
return externalInputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入源类型
|
||||
*
|
||||
* @param externalInputType
|
||||
*/
|
||||
public void setExternalInputType(FUExternalInputEnum externalInputType) {
|
||||
this.externalInputType = externalInputType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入纹理类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FUInputTextureEnum getInputTextureType() {
|
||||
return inputTextureType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入纹理类型
|
||||
*
|
||||
* @param inputTextureType
|
||||
*/
|
||||
public void setInputTextureType(FUInputTextureEnum inputTextureType) {
|
||||
this.inputTextureType = inputTextureType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入Buffer类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FUInputBufferEnum getInputBufferType() {
|
||||
return inputBufferType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入Buffer类型
|
||||
*
|
||||
* @param inputBufferType
|
||||
*/
|
||||
public void setInputBufferType(FUInputBufferEnum inputBufferType) {
|
||||
this.inputBufferType = inputBufferType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入数据朝向
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getInputOrientation() {
|
||||
return inputOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入数据朝向
|
||||
*
|
||||
* @param inputOrientation
|
||||
*/
|
||||
public void setInputOrientation(int inputOrientation) {
|
||||
this.inputOrientation = inputOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getDeviceOrientation() {
|
||||
return deviceOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置设备类型
|
||||
*
|
||||
* @param deviceOrientation
|
||||
*/
|
||||
public void setDeviceOrientation(int deviceOrientation) {
|
||||
this.deviceOrientation = deviceOrientation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备朝向
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CameraFacingEnum getCameraFacing() {
|
||||
return cameraFacing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置设备朝向
|
||||
*
|
||||
* @param cameraFacing
|
||||
*/
|
||||
public void setCameraFacing(CameraFacingEnum cameraFacing) {
|
||||
this.cameraFacing = cameraFacing;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入纹理旋转类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FUTransformMatrixEnum getInputTextureMatrix() {
|
||||
return inputTextureMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入纹理旋转类型
|
||||
*
|
||||
* @param inputTextureMatrix
|
||||
*/
|
||||
public void setInputTextureMatrix(FUTransformMatrixEnum inputTextureMatrix) {
|
||||
this.inputTextureMatrix = inputTextureMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入数据旋转类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FUTransformMatrixEnum getInputBufferMatrix() {
|
||||
return inputBufferMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入数据旋转类型
|
||||
*
|
||||
* @param inputBufferMatrix
|
||||
*/
|
||||
public void setInputBufferMatrix(FUTransformMatrixEnum inputBufferMatrix) {
|
||||
this.inputBufferMatrix = inputBufferMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出数据旋转类型
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public FUTransformMatrixEnum getOutputMatrix() {
|
||||
return outputMatrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输出数据旋转类型
|
||||
*
|
||||
* @param outputMatrix
|
||||
*/
|
||||
public void setOutputMatrix(FUTransformMatrixEnum outputMatrix) {
|
||||
this.outputMatrix = outputMatrix;
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package com.yunbao.faceunity.utils;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Debug;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 内存使用率获取工具类
|
||||
* Created by tujh on 2018/5/24.
|
||||
*/
|
||||
public abstract class MemoryInfoUtil {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static double getMemory(Debug.MemoryInfo[] memoryInfos) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 27) {
|
||||
return compute(memoryInfos);
|
||||
}
|
||||
|
||||
Field otherStats_Field = memoryInfos[0].getClass().getDeclaredField("otherStats");
|
||||
otherStats_Field.setAccessible(true);
|
||||
int[] otherStats = (int[]) otherStats_Field.get(memoryInfos[0]);
|
||||
|
||||
Field NUM_CATEGORIES_Field = memoryInfos[0].getClass().getDeclaredField("NUM_CATEGORIES");
|
||||
Field offsetPrivateClean_Field = memoryInfos[0].getClass().getDeclaredField("offsetPrivateClean");
|
||||
Field offsetPrivateDirty_Field = memoryInfos[0].getClass().getDeclaredField("offsetPrivateDirty");
|
||||
final int NUM_CATEGORIES = (int) NUM_CATEGORIES_Field.get(memoryInfos[0]);
|
||||
final int offsetPrivateClean = (int) offsetPrivateClean_Field.get(memoryInfos[0]);
|
||||
final int offsetPrivateDirty = (int) offsetPrivateDirty_Field.get(memoryInfos[0]);
|
||||
|
||||
int javaHeap = memoryInfos[0].dalvikPrivateDirty
|
||||
+ otherStats[12 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[12 * NUM_CATEGORIES + offsetPrivateDirty];
|
||||
int nativeHeap = memoryInfos[0].nativePrivateDirty;
|
||||
int code = otherStats[6 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[6 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[7 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[7 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[8 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[8 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[9 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[9 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[10 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[10 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[11 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[11 * NUM_CATEGORIES + offsetPrivateDirty];
|
||||
int stack = otherStats[NUM_CATEGORIES + offsetPrivateDirty];
|
||||
int graphics = otherStats[4 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[4 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[14 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[14 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[15 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[15 * NUM_CATEGORIES + offsetPrivateDirty];
|
||||
int other = otherStats[0 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[0 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[2 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[2 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[3 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[3 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[5 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[5 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[13 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[13 * NUM_CATEGORIES + offsetPrivateDirty]
|
||||
+ otherStats[16 * NUM_CATEGORIES + offsetPrivateClean] + otherStats[16 * NUM_CATEGORIES + offsetPrivateDirty];
|
||||
int all = javaHeap + nativeHeap + code + stack + graphics + other;
|
||||
// Log.d("MemoryAll", "javaHeap=" + javaHeap
|
||||
// + "\nnativeHeap=" + nativeHeap + "\ncode=" + code + "\nstack=" + stack
|
||||
// + "\ngraphics=" + graphics + "\nother=" + other);
|
||||
// Log.e(TAG, "memory " + memoryStr
|
||||
// + " java-heap " + String.format("%.2f", ((double) javaHeap) / 1024)
|
||||
// + " native-heap " + String.format("%.2f", ((double) nativeHeap) / 1024)
|
||||
// + " code " + String.format("%.2f", ((double) code) / 1024)
|
||||
// + " stack " + String.format("%.2f", ((double) stack) / 1024)
|
||||
// + " graphics " + String.format("%.2f", ((double) graphics) / 1024)
|
||||
// + " other " + String.format("%.2f", ((double) other) / 1024)
|
||||
// + " pps " + String.format("%.2f", ((double) memoryInfos[0].getTotalPss()) / 1024)
|
||||
// );
|
||||
return ((double) all) / 1024;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private static double compute(Debug.MemoryInfo[] memInfo) {
|
||||
String java_mem = memInfo[0].getMemoryStat("summary.java-heap");
|
||||
String native_mem = memInfo[0].getMemoryStat("summary.native-heap");
|
||||
String graphics_mem = memInfo[0].getMemoryStat("summary.graphics");
|
||||
String stack_mem = memInfo[0].getMemoryStat("summary.stack");
|
||||
String code_mem = memInfo[0].getMemoryStat("summary.code");
|
||||
String others_mem = memInfo[0].getMemoryStat("summary.system");
|
||||
int all = Integer.parseInt(java_mem)
|
||||
+ Integer.parseInt(native_mem)
|
||||
+ Integer.parseInt(graphics_mem)
|
||||
+ Integer.parseInt(stack_mem)
|
||||
+ Integer.parseInt(code_mem)
|
||||
+ Integer.parseInt(others_mem);
|
||||
return ((double) all) / 1024;
|
||||
}
|
||||
}
|
@ -0,0 +1,463 @@
|
||||
/*
|
||||
* Copyright 2014 - 2020 Henning Dodenhof
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.yunbao.faceunity.widget;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.yunbao.faceunity.R;
|
||||
|
||||
|
||||
/**
|
||||
* Thanks for https://github.com/hdodenhof/CircleImageView
|
||||
* Current version is 3.1.0
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class CircleImageView extends ImageView {
|
||||
|
||||
private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
|
||||
|
||||
private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
|
||||
private static final int COLORDRAWABLE_DIMENSION = 2;
|
||||
|
||||
private static final int DEFAULT_BORDER_WIDTH = 0;
|
||||
private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
|
||||
private static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT;
|
||||
private static final boolean DEFAULT_BORDER_OVERLAY = false;
|
||||
|
||||
private final RectF mDrawableRect = new RectF();
|
||||
private final RectF mBorderRect = new RectF();
|
||||
|
||||
private final Matrix mShaderMatrix = new Matrix();
|
||||
private final Paint mBitmapPaint = new Paint();
|
||||
private final Paint mBorderPaint = new Paint();
|
||||
private final Paint mCircleBackgroundPaint = new Paint();
|
||||
|
||||
private int mBorderColor = DEFAULT_BORDER_COLOR;
|
||||
private int mBorderWidth = DEFAULT_BORDER_WIDTH;
|
||||
private int mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR;
|
||||
|
||||
private Bitmap mBitmap;
|
||||
private BitmapShader mBitmapShader;
|
||||
private int mBitmapWidth;
|
||||
private int mBitmapHeight;
|
||||
|
||||
private float mDrawableRadius;
|
||||
private float mBorderRadius;
|
||||
|
||||
private ColorFilter mColorFilter;
|
||||
|
||||
private boolean mReady;
|
||||
private boolean mSetupPending;
|
||||
private boolean mBorderOverlay;
|
||||
private boolean mDisableCircularTransformation;
|
||||
|
||||
public CircleImageView(Context context) {
|
||||
super(context);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
public CircleImageView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);
|
||||
|
||||
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
|
||||
mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
|
||||
mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
|
||||
mCircleBackgroundColor = a.getColor(R.styleable.CircleImageView_civ_circle_background_color, DEFAULT_CIRCLE_BACKGROUND_COLOR);
|
||||
|
||||
a.recycle();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
super.setScaleType(SCALE_TYPE);
|
||||
mReady = true;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setOutlineProvider(new OutlineProvider());
|
||||
}
|
||||
|
||||
if (mSetupPending) {
|
||||
setup();
|
||||
mSetupPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScaleType getScaleType() {
|
||||
return SCALE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScaleType(ScaleType scaleType) {
|
||||
if (scaleType != SCALE_TYPE) {
|
||||
throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAdjustViewBounds(boolean adjustViewBounds) {
|
||||
if (adjustViewBounds) {
|
||||
throw new IllegalArgumentException("adjustViewBounds not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mDisableCircularTransformation) {
|
||||
super.onDraw(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCircleBackgroundColor != Color.TRANSPARENT) {
|
||||
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint);
|
||||
}
|
||||
canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
|
||||
if (mBorderWidth > 0) {
|
||||
canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
setup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPadding(int left, int top, int right, int bottom) {
|
||||
super.setPadding(left, top, right, bottom);
|
||||
setup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPaddingRelative(int start, int top, int end, int bottom) {
|
||||
super.setPaddingRelative(start, top, end, bottom);
|
||||
setup();
|
||||
}
|
||||
|
||||
public int getBorderColor() {
|
||||
return mBorderColor;
|
||||
}
|
||||
|
||||
public void setBorderColor(@ColorInt int borderColor) {
|
||||
if (borderColor == mBorderColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBorderColor = borderColor;
|
||||
mBorderPaint.setColor(mBorderColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getCircleBackgroundColor() {
|
||||
return mCircleBackgroundColor;
|
||||
}
|
||||
|
||||
public void setCircleBackgroundColor(@ColorInt int circleBackgroundColor) {
|
||||
if (circleBackgroundColor == mCircleBackgroundColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCircleBackgroundColor = circleBackgroundColor;
|
||||
mCircleBackgroundPaint.setColor(circleBackgroundColor);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void setCircleBackgroundColorResource(@ColorRes int circleBackgroundRes) {
|
||||
setCircleBackgroundColor(getContext().getResources().getColor(circleBackgroundRes));
|
||||
}
|
||||
|
||||
public int getBorderWidth() {
|
||||
return mBorderWidth;
|
||||
}
|
||||
|
||||
public void setBorderWidth(int borderWidth) {
|
||||
if (borderWidth == mBorderWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBorderWidth = borderWidth;
|
||||
setup();
|
||||
}
|
||||
|
||||
public boolean isBorderOverlay() {
|
||||
return mBorderOverlay;
|
||||
}
|
||||
|
||||
public void setBorderOverlay(boolean borderOverlay) {
|
||||
if (borderOverlay == mBorderOverlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBorderOverlay = borderOverlay;
|
||||
setup();
|
||||
}
|
||||
|
||||
public boolean isDisableCircularTransformation() {
|
||||
return mDisableCircularTransformation;
|
||||
}
|
||||
|
||||
public void setDisableCircularTransformation(boolean disableCircularTransformation) {
|
||||
if (mDisableCircularTransformation == disableCircularTransformation) {
|
||||
return;
|
||||
}
|
||||
|
||||
mDisableCircularTransformation = disableCircularTransformation;
|
||||
initializeBitmap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageBitmap(Bitmap bm) {
|
||||
super.setImageBitmap(bm);
|
||||
initializeBitmap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
super.setImageDrawable(drawable);
|
||||
initializeBitmap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageResource(@DrawableRes int resId) {
|
||||
super.setImageResource(resId);
|
||||
initializeBitmap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageURI(Uri uri) {
|
||||
super.setImageURI(uri);
|
||||
initializeBitmap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
if (cf == mColorFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
mColorFilter = cf;
|
||||
applyColorFilter();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ColorFilter getColorFilter() {
|
||||
return mColorFilter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void applyColorFilter() {
|
||||
// This might be called from setColorFilter during ImageView construction
|
||||
// before member initialization has finished on API level <= 19.
|
||||
if (mBitmapPaint != null) {
|
||||
mBitmapPaint.setColorFilter(mColorFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getBitmapFromDrawable(Drawable drawable) {
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
|
||||
try {
|
||||
Bitmap bitmap;
|
||||
|
||||
if (drawable instanceof ColorDrawable) {
|
||||
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
|
||||
} else {
|
||||
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
|
||||
}
|
||||
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
return bitmap;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeBitmap() {
|
||||
if (mDisableCircularTransformation) {
|
||||
mBitmap = null;
|
||||
} else {
|
||||
mBitmap = getBitmapFromDrawable(getDrawable());
|
||||
}
|
||||
setup();
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
if (!mReady) {
|
||||
mSetupPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (getWidth() == 0 && getHeight() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBitmap == null) {
|
||||
invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
|
||||
mBitmapPaint.setAntiAlias(true);
|
||||
mBitmapPaint.setDither(true);
|
||||
mBitmapPaint.setFilterBitmap(true);
|
||||
mBitmapPaint.setShader(mBitmapShader);
|
||||
|
||||
mBorderPaint.setStyle(Paint.Style.STROKE);
|
||||
mBorderPaint.setAntiAlias(true);
|
||||
mBorderPaint.setColor(mBorderColor);
|
||||
mBorderPaint.setStrokeWidth(mBorderWidth);
|
||||
|
||||
mCircleBackgroundPaint.setStyle(Paint.Style.FILL);
|
||||
mCircleBackgroundPaint.setAntiAlias(true);
|
||||
mCircleBackgroundPaint.setColor(mCircleBackgroundColor);
|
||||
|
||||
mBitmapHeight = mBitmap.getHeight();
|
||||
mBitmapWidth = mBitmap.getWidth();
|
||||
|
||||
mBorderRect.set(calculateBounds());
|
||||
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);
|
||||
|
||||
mDrawableRect.set(mBorderRect);
|
||||
if (!mBorderOverlay && mBorderWidth > 0) {
|
||||
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
|
||||
}
|
||||
mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);
|
||||
|
||||
applyColorFilter();
|
||||
updateShaderMatrix();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private RectF calculateBounds() {
|
||||
int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
||||
int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();
|
||||
|
||||
int sideLength = Math.min(availableWidth, availableHeight);
|
||||
|
||||
float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
|
||||
float top = getPaddingTop() + (availableHeight - sideLength) / 2f;
|
||||
|
||||
return new RectF(left, top, left + sideLength, top + sideLength);
|
||||
}
|
||||
|
||||
private void updateShaderMatrix() {
|
||||
float scale;
|
||||
float dx = 0;
|
||||
float dy = 0;
|
||||
|
||||
mShaderMatrix.set(null);
|
||||
|
||||
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
|
||||
scale = mDrawableRect.height() / (float) mBitmapHeight;
|
||||
dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
|
||||
} else {
|
||||
scale = mDrawableRect.width() / (float) mBitmapWidth;
|
||||
dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
|
||||
}
|
||||
|
||||
mShaderMatrix.setScale(scale, scale);
|
||||
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);
|
||||
|
||||
mBitmapShader.setLocalMatrix(mShaderMatrix);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mDisableCircularTransformation) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
return inTouchableArea(event.getX(), event.getY()) && super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private boolean inTouchableArea(float x, float y) {
|
||||
if (mBorderRect.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Math.pow(x - mBorderRect.centerX(), 2) + Math.pow(y - mBorderRect.centerY(), 2) <= Math.pow(mBorderRadius, 2);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private class OutlineProvider extends ViewOutlineProvider {
|
||||
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
if (mDisableCircularTransformation) {
|
||||
ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
|
||||
} else {
|
||||
Rect bounds = new Rect();
|
||||
mBorderRect.roundOut(bounds);
|
||||
outline.setRoundRect(bounds, bounds.width() / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.yunbao.faceunity.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
|
||||
/**
|
||||
* Touch 事件时支持 state 变换
|
||||
*
|
||||
* @author Richie on 2018.09.20
|
||||
*/
|
||||
public class TouchStateImageView extends AppCompatImageView {
|
||||
private OnTouchStateListener mOnTouchStateListener;
|
||||
|
||||
public TouchStateImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TouchStateImageView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public TouchStateImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setOnTouchStateListener(OnTouchStateListener onTouchStateListener) {
|
||||
mOnTouchStateListener = onTouchStateListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnTouchListener(final OnTouchListener l) {
|
||||
OnTouchListener onTouchListener = new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
setState(event);
|
||||
boolean ret = l.onTouch(v, event);
|
||||
if (!ret && mOnTouchStateListener != null) {
|
||||
return mOnTouchStateListener.onTouch(v, event);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
};
|
||||
super.setOnTouchListener(onTouchListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
setState(event);
|
||||
if (mOnTouchStateListener != null) {
|
||||
return mOnTouchStateListener.onTouch(this, event);
|
||||
} else {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void setState(MotionEvent event) {
|
||||
int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
setSelected(true);
|
||||
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when a touch event is
|
||||
* dispatched to this view. The callback will be invoked before the touch
|
||||
* event is given to the view.
|
||||
*/
|
||||
public interface OnTouchStateListener {
|
||||
/**
|
||||
* Called when a touch event is dispatched to a view. This allows listeners to
|
||||
* get a chance to respond before the target view.
|
||||
*
|
||||
* @param v The view the touch event has been dispatched to.
|
||||
* @param event The MotionEvent object containing full information about
|
||||
* the event.
|
||||
* @return True if the listener has consumed the event, false otherwise.
|
||||
*/
|
||||
boolean onTouch(View v, MotionEvent event);
|
||||
|
||||
|
||||
}
|
||||
}
|
6
FaceUnity/src/main/res/color/bottom_radio_color.xml
Normal file
6
FaceUnity/src/main/res/color/bottom_radio_color.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/main_color" android:state_checked="true" />
|
||||
<item android:color="@color/main_color" android:state_selected="true" />
|
||||
<item android:color="@android:color/white" />
|
||||
</selector>
|
20
FaceUnity/src/main/res/color/dsb_progress_color_list.xml
Normal file
20
FaceUnity/src/main/res/color/dsb_progress_color_list.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/dsb_disabled_color" android:state_enabled="false" />
|
||||
<item android:color="@color/dsb_progress_color" />
|
||||
</selector>
|
22
FaceUnity/src/main/res/color/dsb_ripple_color_list.xml
Normal file
22
FaceUnity/src/main/res/color/dsb_ripple_color_list.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/dsb_disabled_color" android:state_enabled="false" />
|
||||
<item android:color="@color/dsb_ripple_color_focused" android:state_focused="true" />
|
||||
<item android:color="@color/dsb_ripple_color_pressed" android:state_pressed="true" />
|
||||
<item android:color="@color/dsb_ripple_color_focused" />
|
||||
</selector>
|
20
FaceUnity/src/main/res/color/dsb_track_color_list.xml
Normal file
20
FaceUnity/src/main/res/color/dsb_track_color_list.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/dsb_disabled_color" android:state_enabled="false" />
|
||||
<item android:color="@color/dsb_track_color" />
|
||||
</selector>
|
20
FaceUnity/src/main/res/color/tv_main_color_selector.xml
Normal file
20
FaceUnity/src/main/res/color/tv_main_color_selector.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) Gustavo Claramunt (AnderWeb) 2014.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/main_color" android:state_selected="true" />
|
||||
<item android:color="@color/main_color_c5c5c5" />
|
||||
</selector>
|
5
FaceUnity/src/main/res/drawable/bg_confirm_dialog.xml
Normal file
5
FaceUnity/src/main/res/drawable/bg_confirm_dialog.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="10dp" />
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners
|
||||
android:bottomLeftRadius="10dp"
|
||||
android:bottomRightRadius="10dp" />
|
||||
<gradient
|
||||
android:endColor="#682EB8"
|
||||
android:startColor="#7D219E" />
|
||||
</shape>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_shape_oval_theme" android:state_selected="true" />
|
||||
<item android:drawable="@color/transparent" />
|
||||
</selector>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_shape_rect_theme" android:state_selected="true" />
|
||||
<item android:drawable="@color/transparent" />
|
||||
</selector>
|
8
FaceUnity/src/main/res/drawable/bg_radio_group.xml
Normal file
8
FaceUnity/src/main/res/drawable/bg_radio_group.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="4dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/white" />
|
||||
</shape>
|
11
FaceUnity/src/main/res/drawable/bg_radio_left.xml
Normal file
11
FaceUnity/src/main/res/drawable/bg_radio_left.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomLeftRadius="4dp"
|
||||
android:topLeftRadius="4dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/white" />
|
||||
</shape>
|
11
FaceUnity/src/main/res/drawable/bg_radio_left_check.xml
Normal file
11
FaceUnity/src/main/res/drawable/bg_radio_left_check.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomLeftRadius="4dp"
|
||||
android:topLeftRadius="4dp" />
|
||||
<solid android:color="@android:color/white" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/white" />
|
||||
</shape>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_radio_left_check" android:state_checked="true" />
|
||||
<item android:drawable="@drawable/bg_radio_left" />
|
||||
</selector>
|
21
FaceUnity/src/main/res/drawable/bg_radio_middle.xml
Normal file
21
FaceUnity/src/main/res/drawable/bg_radio_middle.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<layer-list >
|
||||
<item
|
||||
android:left="-1dp" >
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/white" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
</selector>
|
14
FaceUnity/src/main/res/drawable/bg_radio_right.xml
Normal file
14
FaceUnity/src/main/res/drawable/bg_radio_right.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:left="-1dp">
|
||||
<shape android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomRightRadius="4dp"
|
||||
android:topRightRadius="4dp" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/white" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
11
FaceUnity/src/main/res/drawable/bg_radio_right_check.xml
Normal file
11
FaceUnity/src/main/res/drawable/bg_radio_right_check.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:bottomRightRadius="4dp"
|
||||
android:topRightRadius="4dp" />
|
||||
<solid android:color="@android:color/white" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/white" />
|
||||
</shape>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/bg_radio_right_check" android:state_checked="true" />
|
||||
<item android:drawable="@drawable/bg_radio_right" />
|
||||
</selector>
|
20
FaceUnity/src/main/res/drawable/bg_seek_bar.xml
Normal file
20
FaceUnity/src/main/res/drawable/bg_seek_bar.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@android:id/background"
|
||||
android:drawable="@mipmap/bg_live_seek_bar_light" />
|
||||
<item android:id="@android:id/secondaryProgress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="#00FFFFFF" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<solid android:color="#00FFFFFF" />
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
</layer-list>
|
8
FaceUnity/src/main/res/drawable/bg_shape_oval_theme.xml
Normal file
8
FaceUnity/src/main/res/drawable/bg_shape_oval_theme.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<stroke
|
||||
android:width="3dp"
|
||||
android:color="@color/main_color" />
|
||||
<solid android:color="@android:color/transparent" />
|
||||
</shape>
|
8
FaceUnity/src/main/res/drawable/bg_shape_rect_theme.xml
Normal file
8
FaceUnity/src/main/res/drawable/bg_shape_rect_theme.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="3dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/main_color" />
|
||||
</shape>
|
6
FaceUnity/src/main/res/drawable/bg_toast_more.xml
Normal file
6
FaceUnity/src/main/res/drawable/bg_toast_more.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#BD050F14" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/icon_beauty_box_angle_close_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/icon_beauty_box_angle_close_normal" />
|
||||
</selector>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/icon_beauty_box_angle_open_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/icon_beauty_box_angle_open_normal" />
|
||||
</selector>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/icon_beauty_box_cheek_bones_close_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/icon_beauty_box_cheek_bones_close_normal" />
|
||||
</selector>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/icon_beauty_box_cheek_bones_open_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/icon_beauty_box_cheek_bones_open_normal" />
|
||||
</selector>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@mipmap/icon_beauty_box_chin_close_checked" android:state_selected="true" />
|
||||
<item android:drawable="@mipmap/icon_beauty_box_chin_close_normal" />
|
||||
</selector>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user