新接入FaceUnity美颜SDK

This commit is contained in:
zlzw 2022-09-17 16:54:58 +08:00
parent c6770c1d51
commit 333e4fc1e6
396 changed files with 58390 additions and 47 deletions

1
FaceUnity/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

70
FaceUnity/build.gradle Normal file
View 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' //-
}

View File

21
FaceUnity/proguard-rules.pro vendored Normal file
View 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

View File

@ -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());
}
}

View 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>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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);
}
}

View File

@ -0,0 +1,57 @@
package com.yunbao.faceunity.base;
import android.view.View;
/**
* DESCRecycleView 通用业务调用
* 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;
}
}

View File

@ -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;
/**
* DESCRecycleView 通用适配器
* 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];
}
}

View File

@ -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;
/**
* DESCDESC控件布局绑定
* 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;
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
/**
* 给控制绑定FaceBeautyControllerMakeupController 数据工厂
*
* @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
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -0,0 +1,40 @@
package com.yunbao.faceunity.listener;
import com.faceunity.core.enumeration.FUAIProcessorEnum;
/**
* DESCFURenderer状态回调监听
* 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();
}

View File

@ -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);
}

View File

@ -0,0 +1,9 @@
package com.yunbao.faceunity.listener;
/**
* DESC底部菜单动画回调
* Created on 2021/4/26
*/
public interface OnBottomAnimatorChangeListener {
void onBottomAnimatorChangeListener(float showRate);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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();
}
}
}
}

View 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;
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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