第一次提交

This commit is contained in:
被淹死的鱼
2026-03-11 18:26:29 +08:00
commit cd3b53759e
8532 changed files with 522078 additions and 0 deletions

View File

@@ -0,0 +1 @@
<manifest package="com.faceunity.nama" />

View File

@@ -0,0 +1,5 @@
{
"delspot_level": [
"PGEM10"
]
}

Binary file not shown.

Binary file not shown.

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,11 @@
package com.faceunity.nama;
import com.faceunity.nama.utils.FuDeviceUtils;
import java.io.File;
public class FUConfig {
public static final String BLACK_LIST = "config" + File.separator + "blackList.json";;
//设备等级默认为中级
public static int DEVICE_LEVEL = FuDeviceUtils.DEVICE_LEVEL_MID;
}

View File

@@ -0,0 +1,274 @@
package com.faceunity.nama;
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
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.enumeration.FUTransformMatrixEnum;
import com.faceunity.core.faceunity.FUAIKit;
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.faceunity.nama.listener.FURendererListener;
import java.io.File;
import java.util.HashMap;
/**
* DESC
* Created on 2021/4/26
*/
public class FURenderer extends IFURenderer {
public volatile static FURenderer INSTANCE;
public static FURenderer getInstance() {
if (INSTANCE == null) {
synchronized (FURenderer.class) {
if (INSTANCE == null) {
INSTANCE = new FURenderer();
}
}
}
return INSTANCE;
}
/**
* 状态回调监听
*/
private FURendererListener mFURendererListener;
/* 特效FURenderKit*/
private FURenderKit mFURenderKit = FURenderKit.getInstance();
/* 特效FURenderKit*/
private FUAIKit mFUAIKit = FUAIKit.getInstance();
/* 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";
/* 相机角度-朝向映射 */
private HashMap<Integer, CameraFacingEnum> cameraOrientationMap = new HashMap<>();
/*检测类型*/
private FUAIProcessorEnum aIProcess = FUAIProcessorEnum.FACE_PROCESSOR;
/*检测标识*/
private int aIProcessTrackStatus = -1;
/**
* @return version
*/
public String getVersion() {
return mFURenderKit.getVersion();
}
/**
* 初始化鉴权
*
* @param context
*/
@Override
public void setup(Context context) {
FURenderManager.setKitDebug(FULogger.LogLevel.TRACE);
FURenderManager.setCoreDebug(FULogger.LogLevel.ERROR);
FURenderManager.registerFURender(context, authpack.A(), new OperateCallback() {
@Override
public void onSuccess(int i, String s) {
if (i == FURenderConfig.OPERATE_SUCCESS_AUTH) {
mFUAIKit.loadAIProcessor(BUNDLE_AI_FACE, FUAITypeEnum.FUAITYPE_FACEPROCESSOR);
mFUAIKit.loadAIProcessor(BUNDLE_AI_HUMAN, FUAITypeEnum.FUAITYPE_HUMAN_PROCESSOR);
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, String s) {
}
});
}
/**
* 开启合成状态
*/
@Override
public void bindListener(FURendererListener mFURendererListener) {
this.mFURendererListener = mFURendererListener;
}
/**
* 双输入接口,输入 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();
FURenderInputData inputData = new FURenderInputData(width, height);
/*注释掉Buffer配置启用单纹理模式防止Buffer跟纹理存在不对齐造成美妆偏移*/
// inputData.setImageBuffer(new FURenderInputData.FUImageBuffer(inputBufferType, img));//设置为单Buffer输入
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.setCameraFacing(cameraFacing);
config.setOutputMatrix(outputMatrix);
mCallStartTime = System.nanoTime();
FURenderOutputData outputData = mFURenderKit.renderWithInput(inputData);
mSumCallTime += System.nanoTime() - mCallStartTime;
if (outputData.getTexture() != null && outputData.getTexture().getTexId() > 0) {
return outputData.getTexture().getTexId();
}
return texId;
}
/**
* 释放资源
*/
@Override
public void release() {
mFURenderKit.release();
aIProcessTrackStatus = -1;
mFURendererListener = null;
}
/**
* 渲染前置执行
*
* @return
*/
private void prepareDrawFrame() {
benchmarkFPS();
// AI检测
trackStatus();
}
//region AI识别
/**
* 设置输入数据朝向
*
* @param inputOrientation
*/
@Override
public void setInputOrientation(int inputOrientation) {
if (cameraOrientationMap.containsKey(inputOrientation)) {
CameraFacingEnum cameraFacingEnum = cameraOrientationMap.get(inputOrientation);
setCameraFacing(cameraFacingEnum);
if (cameraFacingEnum == CameraFacingEnum.CAMERA_FRONT) {
setInputBufferMatrix(FUTransformMatrixEnum.CCROT90_FLIPHORIZONTAL);
setInputTextureMatrix(FUTransformMatrixEnum.CCROT90_FLIPHORIZONTAL);
setOutputMatrix(FUTransformMatrixEnum.CCROT270);
} else {
setInputBufferMatrix(FUTransformMatrixEnum.CCROT270);
setInputTextureMatrix(FUTransformMatrixEnum.CCROT270);
setOutputMatrix(FUTransformMatrixEnum.CCROT90_FLIPVERTICAL);
}
}
super.setInputOrientation(inputOrientation);
}
/**
* 设置检测类型
*
* @param type
*/
@Override
public void setAIProcessTrackType(FUAIProcessorEnum type) {
aIProcess = type;
aIProcessTrackStatus = -1;
}
/**
* 设置FPS检测
*
* @param enable
*/
@Override
public void setMarkFPSEnable(boolean enable) {
mIsRunBenchmark = enable;
}
/**
* 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;
Log.i("美颜","fps:"+fps);
if (mFURendererListener != null) {
mFURendererListener.onFpsChanged(fps, renderTime);
}
}
}
}

View File

@@ -0,0 +1,240 @@
package com.faceunity.nama;
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.faceunity.nama.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 = 0;//数据源朝向
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);
/**
* 设置检测类型
*
* @param type
*/
public abstract void setAIProcessTrackType(FUAIProcessorEnum type);
/**
* 资源释放
*/
public abstract void release();
/**
* 开启合成状态
*/
public abstract void bindListener(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,57 @@
package com.faceunity.nama.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,100 @@
package com.faceunity.nama.base;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.faceunity.nama.listener.OnMultiClickListener;
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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.dialog.BaseDialogFragment;
import com.faceunity.nama.dialog.ConfirmDialogFragment;
import com.faceunity.nama.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,247 @@
package com.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.base.BaseDelegate;
import com.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.base.BaseViewHolder;
import com.faceunity.nama.entity.BodyBeautyBean;
import com.faceunity.nama.entity.ModelAttributeData;
import com.faceunity.nama.infe.AbstractBodyBeautyDataFactory;
import com.faceunity.nama.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 = -1;
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();
if (mBodyIndex > 0) {
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);
} else {
discreteSeekBar.setVisibility(INVISIBLE);
}
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() {
if (mBodyBeautyBeans.size() > mBodyIndex && mBodyIndex >= 0) {
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,581 @@
package com.faceunity.nama.control;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.faceunity.core.model.facebeauty.FaceBeautyFilterEnum;
import com.faceunity.core.utils.DecimalUtils;
import com.faceunity.nama.R;
import com.faceunity.nama.base.BaseDelegate;
import com.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.base.BaseViewHolder;
import com.faceunity.nama.checkbox.CheckGroup;
import com.faceunity.nama.entity.FaceBeautyBean;
import com.faceunity.nama.entity.FaceBeautyFilterBean;
import com.faceunity.nama.entity.ModelAttributeData;
import com.faceunity.nama.infe.AbstractFaceBeautyDataFactory;
import com.faceunity.nama.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 ImageView ivDown;
private DiscreteSeekBar discreteSeekBar;
private CheckGroup checkGroup;
/*private LinearLayout recoverLayout;
private ImageView recoverImageView;
private TextView recoverTextView;
private View lineView;*/
private LinearLayout bottomLayout;
//private SwitchCompat switchCompat;
private boolean isFiltersChange = false; //滤镜是否改变
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(R.id.beauty_radio_skin_beauty);
}
public void setmModelAttributeRange() {
}
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);
ivDown = findViewById(R.id.iv_down);
/*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<FaceBeautyFilterBean>(), 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);
}
isFiltersChange = true;
} else {
isFiltersChange = false;
}
}
}, R.layout.list_item_control_title_image_square);
mBeautyAdapter = new BaseListAdapter<>(new ArrayList<FaceBeautyBean>(), 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(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
/*菜单控制*/
bindBottomRadioListener();
/*滑动条控制*/
bindSeekBarListener();
/*还原数据*/
/*recoverLayout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
FaceBeautyControlView.this.showDialog(mContext.getString(R.string.dialog_reset_avatar_model), new Runnable() {
@Override
public void run() {
FaceBeautyControlView.this.recoverData();
}
});
}
});*/
/*渲染开关*/
/*switchCompat.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mDataFactory.enableFaceBeauty(isChecked);
}
});*/
ivDown.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.ViewDown();
}
// if (checkGroup.getCheckedCheckBoxId() != View.NO_ID) {
// View checkedView = findViewById(checkGroup.getCheckedCheckBoxId());
// if (checkedView instanceof CheckBox) {
// ((CheckBox) checkedView).setChecked(false);
// }
// }
// FaceBeautyControlView.this.changeBottomLayoutAnimator(false);
// mDataFactory.enableFaceBeauty(true);
}
});
}
/**
* 滚动条绑定事件
*/
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(new CheckGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CheckGroup group, int 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();
FaceBeautyControlView.this.seekToSeekBar(value, stand, maxRange);
FaceBeautyControlView.this.setRecoverEnable(FaceBeautyControlView.this.checkFaceSkinChanged());
FaceBeautyControlView.this.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();
FaceBeautyControlView.this.seekToSeekBar(value, stand, maxRange);
FaceBeautyControlView.this.setRecoverEnable(FaceBeautyControlView.this.checkFaceShapeChanged());
FaceBeautyControlView.this.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 {
FaceBeautyControlView.this.seekToSeekBar(mFilters.get(mDataFactory.getCurrentFilterIndex()).getIntensity(), 0.0, 1.0);
}
FaceBeautyControlView.this.changeBottomLayoutAnimator(true);
} else if (checkedId == View.NO_ID) {
FaceBeautyControlView.this.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());
}
}
/**
* 重置还原按钮状态
* 废弃于1.3.2版本
*
* @param enable Boolean
*/
@Deprecated
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
*/
public 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);
onlyRecoverData(mShapeBeauty);
} else if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_face_shape) {
recoverData(mShapeBeauty, mShapeIndex);
onlyRecoverData(mSkinBeauty);
} else if (checkGroup.getCheckedCheckBoxId() == R.id.beauty_radio_filter) {
onlyRecoverData(mSkinBeauty);
onlyRecoverData(mShapeBeauty);
} else {
onlyRecoverData(mSkinBeauty);
onlyRecoverData(mShapeBeauty);
}
recoverFitterData();
}
/**
* 重置数据
*
* @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);
}
private void onlyRecoverData(ArrayList<FaceBeautyBean> beautyBeans) {
for (FaceBeautyBean bean : beautyBeans) {
double intensity = mModelAttributeRange.get(bean.getKey()).getDefaultV();
mDataFactory.updateParamIntensity(bean.getKey(), intensity);
}
}
private void recoverFitterData() {
for (FaceBeautyFilterBean filterBean : mFilters) {
mDataFactory.updateParamIntensity(filterBean.getKey(), 0.4);
}
changeAdapterSelected(mFiltersAdapter, mDataFactory.getCurrentFilterIndex(), 1);
mDataFactory.setCurrentFilterIndex(1);
mDataFactory.onFilterSelected(FaceBeautyFilterEnum.ZIRAN_1, 0.4, mFilters.get(1).getDesRes());
seekToSeekBar(0.4, 0.0, 1.0);
}
/**
* 底部动画处理
*
* @param isOpen Boolean
*/
private void changeBottomLayoutAnimator(final boolean isOpen) {
if (isBottomShow == isOpen) {
return;
}
final int start = isOpen ? getResources().getDimensionPixelSize(R.dimen.x1) : getResources().getDimensionPixelSize(R.dimen.x328);
final int end = isOpen ? getResources().getDimensionPixelSize(R.dimen.x328) : getResources().getDimensionPixelSize(R.dimen.x1);
if (bottomLayoutAnimator != null && bottomLayoutAnimator.isRunning()) {
bottomLayoutAnimator.end();
}
bottomLayoutAnimator = ValueAnimator.ofInt(start, end).setDuration(150);
bottomLayoutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator 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;
}
/**
* 设置美颜开关
*
* @param isChecked
*/
public void setEnableFaceBeauty(boolean isChecked) {
mDataFactory.enableFaceBeauty(isChecked);
}
/**
* 还原美颜数据
*/
public void faceBeautyRecoverData() {
recoverData();
}
/**
* 检测数据是否改变
*
* @return
*/
public boolean isFaceBeautyDataChange() {
return isFiltersChange || checkFaceSkinChanged() || checkFaceShapeChanged();
}
private ViewDownListener listener;
public void setListener(ViewDownListener listener) {
this.listener = listener;
}
public interface ViewDownListener {
void ViewDown();
}
}

View File

@@ -0,0 +1,300 @@
package com.faceunity.nama.control;
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.faceunity.nama.R;
import com.faceunity.nama.base.BaseDelegate;
import com.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.base.BaseViewHolder;
import com.faceunity.nama.dialog.ToastHelper;
import com.faceunity.nama.entity.FaceBeautyBean;
import com.faceunity.nama.entity.ModelAttributeData;
import com.faceunity.nama.infe.AbstractFaceBeautyDataFactory;
import com.faceunity.nama.seekbar.DiscreteSeekBar;
import java.util.ArrayList;
import java.util.HashMap;
/**
* DESC
* Created on 2021/4/26
*/
public class FaceBeautyShapeControlView extends BaseControlView {
private AbstractFaceBeautyDataFactory mDataFactory;
/* 美型 */
private HashMap<String, ModelAttributeData> mModelAttributeRange;
private ArrayList<FaceBeautyBean> mShapeBeauty;
private int mShapeIndex = -1;
private BaseListAdapter<FaceBeautyBean> mBeautyAdapter;
private RecyclerView recyclerView;
private DiscreteSeekBar discreteSeekBar;
private LinearLayout recoverLayout;
private ImageView recoverImageView;
private TextView recoverTextView;
private SwitchCompat switchCompat;
public FaceBeautyShapeControlView(@NonNull Context context) {
super(context);
init();
}
public FaceBeautyShapeControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FaceBeautyShapeControlView(@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();
mShapeBeauty = dataFactory.getShapeBeauty();
mBeautyAdapter.setData(mShapeBeauty);
recyclerView.setAdapter(mBeautyAdapter);
if (mShapeIndex > 0) {
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);
} else {
discreteSeekBar.setVisibility(INVISIBLE);
}
setRecoverEnable(checkFaceShapeChanged());
}
private void init() {
LayoutInflater.from(mContext).inflate(R.layout.layout_face_beauty_control, this);
initView();
initAdapter();
bindListener();
}
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);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) discreteSeekBar.getLayoutParams();
params.weight = 0f;
params.width = (int) getResources().getDimension(R.dimen.x528);
discreteSeekBar.requestLayout();
initHorizontalRecycleView(recyclerView);
}
/**
* 构造Adapter
*/
private void initAdapter() {
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());
}
helper.itemView.setSelected(mShapeIndex == position);
ImageView ivControl = helper.getView(R.id.iv_control);
if (!data.isCanUseFunction()) {
ivControl.setImageAlpha(154);
} else {
ivControl.setImageAlpha(255);
}
}
@Override
public void onItemClickListener(View view, FaceBeautyBean data, int position) {
if (position == mShapeIndex) {
return;
}
if (!data.isCanUseFunction()) {
ToastHelper.showNormalToast(mContext, mContext.getString(R.string.face_beauty_function_tips, mContext.getString(data.getDesRes())));
return;
}
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);
/*滑动条控制*/
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;
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);
}
}
});
}
/**
* 设置滚动条数值
*
* @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());
}
mBeautyAdapter.notifyDataSetChanged();
}
/**
* 重置还原按钮状态
*
* @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 checkFaceShapeChanged() {
if (mShapeBeauty.size() > mShapeIndex && mShapeIndex >= 0) {
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() {
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);
}
}

View File

@@ -0,0 +1,168 @@
package com.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.base.BaseDelegate;
import com.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.base.BaseViewHolder;
import com.faceunity.nama.entity.FaceBeautyFilterBean;
import com.faceunity.nama.infe.AbstractFaceBeautyDataFactory;
import com.faceunity.nama.seekbar.DiscreteSeekBar;
import java.util.ArrayList;
/**
* DESC
* Created on 2021/4/26
*/
public class FilterControlView extends BaseControlView {
private AbstractFaceBeautyDataFactory mDataFactory;
/* 滤镜 */
private ArrayList<FaceBeautyFilterBean> mFilters;
private BaseListAdapter<FaceBeautyFilterBean> mFiltersAdapter;
private RecyclerView recyclerView;
private DiscreteSeekBar discreteSeekBar;
public FilterControlView(@NonNull Context context) {
super(context);
init();
}
public FilterControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public FilterControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
/**
* 给控制绑定FaceBeautyController数据工厂
*
* @param dataFactory IFaceBeautyDataFactory
*/
public void bindDataFactory(AbstractFaceBeautyDataFactory dataFactory) {
mDataFactory = dataFactory;
mFilters = dataFactory.getBeautyFilters();
mFiltersAdapter.setData(mFilters);
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);
}
}
private void init() {
LayoutInflater.from(mContext).inflate(R.layout.layout_filter_control, this);
initView();
initAdapter();
bindListener();
}
private void initView() {
recyclerView = findViewById(R.id.recycler_view);
discreteSeekBar = findViewById(R.id.seek_bar);
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);
}
/**
* 绑定监听事件
*/
@SuppressLint("ClickableViewAccessibility")
private void bindListener() {
/*拦截触碰事件*/
findViewById(R.id.fyt_bottom_view).setOnTouchListener((v, event) -> true);
/*滑动条控制*/
bindSeekBarListener();
}
/**
* 滚动条绑定事件
*/
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;
FaceBeautyFilterBean bean = mFilters.get(mDataFactory.getCurrentFilterIndex());
if (!DecimalUtils.doubleEquals(bean.getIntensity(), valueF)) {
bean.setIntensity(valueF);
mDataFactory.updateFilterIntensity(valueF);
}
}
});
}
/**
* 设置滚动条数值
*
* @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);
}
}

View File

@@ -0,0 +1,149 @@
package com.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.base.BaseDelegate;
import com.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.base.BaseViewHolder;
import com.faceunity.nama.entity.MakeupCombinationBean;
import com.faceunity.nama.infe.AbstractMakeupDataFactory;
import com.faceunity.nama.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,101 @@
package com.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.base.BaseDelegate;
import com.faceunity.nama.base.BaseListAdapter;
import com.faceunity.nama.base.BaseViewHolder;
import com.faceunity.nama.entity.PropBean;
import com.faceunity.nama.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_square);
recyclerView.setAdapter(mPropAdapter);
}
// endregion
}

View File

@@ -0,0 +1,149 @@
package com.faceunity.nama.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.faceunity.nama.entity.BodyBeautyBean;
import com.faceunity.nama.entity.ModelAttributeData;
import com.faceunity.nama.infe.AbstractBodyBeautyDataFactory;
import com.faceunity.nama.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,320 @@
package com.faceunity.nama.data;
import androidx.annotation.NonNull;
import com.faceunity.core.controller.facebeauty.FaceBeautyParam;
import com.faceunity.core.faceunity.FUAIKit;
import com.faceunity.core.faceunity.FURenderKit;
import com.faceunity.core.model.facebeauty.FaceBeauty;
import com.faceunity.nama.FUConfig;
import com.faceunity.nama.entity.FaceBeautyBean;
import com.faceunity.nama.entity.FaceBeautyFilterBean;
import com.faceunity.nama.entity.ModelAttributeData;
import com.faceunity.nama.infe.AbstractFaceBeautyDataFactory;
import com.faceunity.nama.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_M2, defaultFaceBeauty::setCheekNarrowIntensity);
put(FaceBeautyParam.CHEEK_SHORT_INTENSITY, defaultFaceBeauty::setCheekShortIntensity);
put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_M2, defaultFaceBeauty::setCheekSmallIntensity);
put(FaceBeautyParam.INTENSITY_CHEEKBONES_INTENSITY, defaultFaceBeauty::setCheekBonesIntensity);
put(FaceBeautyParam.INTENSITY_LOW_JAW_INTENSITY, defaultFaceBeauty::setLowerJawIntensity);
put(FaceBeautyParam.EYE_ENLARGING_INTENSITY_M2, defaultFaceBeauty::setEyeEnlargingIntensity);
put(FaceBeautyParam.EYE_CIRCLE_INTENSITY, defaultFaceBeauty::setEyeCircleIntensity);
put(FaceBeautyParam.CHIN_INTENSITY, defaultFaceBeauty::setChinIntensity);
put(FaceBeautyParam.FOREHEAD_INTENSITY_M2, defaultFaceBeauty::setForHeadIntensity);
put(FaceBeautyParam.NOSE_INTENSITY_M2, defaultFaceBeauty::setNoseIntensity);
put(FaceBeautyParam.MOUTH_INTENSITY_M2, defaultFaceBeauty::setMouthIntensity);
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_M2, defaultFaceBeauty::getCheekNarrowIntensity);
put(FaceBeautyParam.CHEEK_SHORT_INTENSITY, defaultFaceBeauty::getCheekShortIntensity);
put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_M2, defaultFaceBeauty::getCheekSmallIntensity);
put(FaceBeautyParam.INTENSITY_CHEEKBONES_INTENSITY, defaultFaceBeauty::getCheekBonesIntensity);
put(FaceBeautyParam.INTENSITY_LOW_JAW_INTENSITY, defaultFaceBeauty::getLowerJawIntensity);
put(FaceBeautyParam.EYE_ENLARGING_INTENSITY_M2, defaultFaceBeauty::getEyeEnlargingIntensity);
put(FaceBeautyParam.EYE_CIRCLE_INTENSITY, defaultFaceBeauty::getEyeCircleIntensity);
put(FaceBeautyParam.CHIN_INTENSITY, defaultFaceBeauty::getChinIntensity);
put(FaceBeautyParam.FOREHEAD_INTENSITY_M2, defaultFaceBeauty::getForHeadIntensity);
put(FaceBeautyParam.NOSE_INTENSITY_M2, defaultFaceBeauty::getNoseIntensity);
put(FaceBeautyParam.MOUTH_INTENSITY_M2, defaultFaceBeauty::getMouthIntensity);
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() {
FUAIKit.getInstance().faceProcessorSetFaceLandmarkQuality(FUConfig.DEVICE_LEVEL);
mFURenderKit.setFaceBeauty(defaultFaceBeauty);
}
/**
* 获取当前美颜设置
* @return
*/
public FaceBeauty getCurrentFaceBeauty() {
return mFURenderKit.getFaceBeauty();
}
public void setFaceBeautyData(FaceBeauty faceBeautyData) {
defaultFaceBeauty = faceBeautyData;
}
public HashMap<String,ModelAttributeData> getFaceBeautyMapping(FaceBeauty faceBeauty) {
HashMap<String, ModelAttributeData> params = new HashMap<>();
/*美肤*/
params.put(FaceBeautyParam.COLOR_INTENSITY, new ModelAttributeData(faceBeauty.getColorIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.BLUR_INTENSITY, new ModelAttributeData(faceBeauty.getBlurIntensity(), 0.0, 0.0, 6.0));
params.put(FaceBeautyParam.RED_INTENSITY, new ModelAttributeData(faceBeauty.getRedIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.SHARPEN_INTENSITY, new ModelAttributeData(faceBeauty.getSharpenIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.EYE_BRIGHT_INTENSITY, new ModelAttributeData(faceBeauty.getEyeBrightIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.TOOTH_WHITEN_INTENSITY, new ModelAttributeData(faceBeauty.getToothIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.REMOVE_POUCH_INTENSITY, new ModelAttributeData(faceBeauty.getRemovePouchIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY, new ModelAttributeData(faceBeauty.getRemoveLawPatternIntensity(), 0.0, 0.0, 1.0));
/*美型*/
params.put(FaceBeautyParam.FACE_SHAPE_INTENSITY, new ModelAttributeData(faceBeauty.getFaceShapeIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_THINNING_INTENSITY, new ModelAttributeData(faceBeauty.getCheekThinningIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_LONG_INTENSITY, new ModelAttributeData(faceBeauty.getCheekLongIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_CIRCLE_INTENSITY, new ModelAttributeData(faceBeauty.getCheekCircleIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_V_INTENSITY, new ModelAttributeData(faceBeauty.getCheekVIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_NARROW_INTENSITY_M2, new ModelAttributeData(faceBeauty.getCheekNarrowIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_SHORT_INTENSITY, new ModelAttributeData(faceBeauty.getCheekShortIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_M2, new ModelAttributeData(faceBeauty.getCheekSmallIntensity(), 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_M2, new ModelAttributeData(faceBeauty.getEyeEnlargingIntensity(), 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(faceBeauty.getChinIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.FOREHEAD_INTENSITY_M2, new ModelAttributeData(faceBeauty.getForHeadIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.NOSE_INTENSITY_M2, new ModelAttributeData(faceBeauty.getNoseIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.MOUTH_INTENSITY_M2, new ModelAttributeData(faceBeauty.getMouthIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.CANTHUS_INTENSITY, new ModelAttributeData(faceBeauty.getCanthusIntensity(), 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.EYE_SPACE_INTENSITY, new ModelAttributeData(faceBeauty.getEyeSpaceIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.EYE_ROTATE_INTENSITY, new ModelAttributeData(faceBeauty.getEyeRotateIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.LONG_NOSE_INTENSITY, new ModelAttributeData(faceBeauty.getLongNoseIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.PHILTRUM_INTENSITY, new ModelAttributeData(faceBeauty.getPhiltrumIntensity(), 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.SMILE_INTENSITY, new ModelAttributeData(faceBeauty.getSmileIntensity(), 0.0, 0.0, 1.0));
return params;
}
}

View File

@@ -0,0 +1,133 @@
package com.faceunity.nama.data;
import com.faceunity.core.enumeration.FUAIProcessorEnum;
import com.faceunity.core.faceunity.FURenderKit;
import com.faceunity.core.model.facebeauty.FaceBeauty;
import com.faceunity.nama.FURenderer;
/**
* DESC
* Created on 2021/4/25
* 下标 0美肤、1美型、2滤镜、3贴纸、4美妆、5美体
*/
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();
/**
* 道具加载标识
*/
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;
}
}
public void setFaceBeautyData(FaceBeauty faceBeautyData) {
mFaceBeautyDataFactory.setFaceBeautyData(faceBeautyData);
}
}

View File

@@ -0,0 +1,127 @@
package com.faceunity.nama.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.faceunity.nama.entity.MakeupCombinationBean;
import com.faceunity.nama.infe.AbstractMakeupDataFactory;
import com.faceunity.nama.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());
// makeup.setMachineLevel(FUConfig.DEVICE_LEVEL > FuDeviceUtils.DEVICE_LEVEL_ONE);//更新设备等级去设置是否开启人脸遮挡
makeupMap.put(bean.getKey(), makeup);
return makeup;
}
}
//endregion 组合妆
/**
* FURenderKit加载当前特效
*/
public void bindCurrentRenderer() {
mFURenderKit.setMakeup(currentMakeup);
}
}

View File

@@ -0,0 +1,94 @@
package com.faceunity.nama.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.faceunity.nama.entity.PropBean;
import com.faceunity.nama.infe.AbstractPropDataFactory;
import com.faceunity.nama.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.faceunity.nama.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,104 @@
package com.faceunity.nama.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.faceunity.nama.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,116 @@
package com.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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,139 @@
package com.faceunity.nama.entity;
/**
* DESC美颜
* Created on 2021/4/26
*/
public class FaceBeautyBean {
private String key;//名称标识
private int desRes;//描述
private int closeRes;//图片
private int openRes;//图片
private boolean canUseFunction = true;
private String relevanceKey;//关联的名称标识
private boolean showRadioButton = false;
private boolean enableRadioButton;
private int leftRadioButtonDesRes;
private int rightRadioButtonDesRes;
private int enableRadioButtonDesRes;
public FaceBeautyBean(String key, int desRes, int closeRes, int openRes) {
this.key = key;
this.desRes = desRes;
this.closeRes = closeRes;
this.openRes = openRes;
this.canUseFunction = true;
}
public FaceBeautyBean(String key, int desRes, int closeRes, int openRes, boolean canUseFunction) {
this.key = key;
this.desRes = desRes;
this.closeRes = closeRes;
this.openRes = openRes;
this.canUseFunction = canUseFunction;
}
public FaceBeautyBean(String key, int desRes, int closeRes, int openRes, boolean canUseFunction, String relevanceKey, boolean showRadioButton, boolean enableRadioButton, int leftRadioButtonDesRes, int rightRadioButtonDesRes, int enableRadioButtonDesRes) {
this.key = key;
this.desRes = desRes;
this.closeRes = closeRes;
this.openRes = openRes;
this.canUseFunction = canUseFunction;
this.relevanceKey = relevanceKey;
this.showRadioButton = showRadioButton;
this.enableRadioButton = enableRadioButton;
this.leftRadioButtonDesRes = leftRadioButtonDesRes;
this.rightRadioButtonDesRes = rightRadioButtonDesRes;
this.enableRadioButtonDesRes = enableRadioButtonDesRes;
}
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;
}
public boolean isCanUseFunction() {
return canUseFunction;
}
public void setCanUseFunction(boolean canUseFunction) {
this.canUseFunction = canUseFunction;
}
public boolean isShowRadioButton() {
return showRadioButton;
}
public void setShowRadioButton(boolean showRadioButton) {
this.showRadioButton = showRadioButton;
}
public boolean isEnableRadioButton() {
return enableRadioButton;
}
public void setEnableRadioButton(boolean enableRadioButton) {
this.enableRadioButton = enableRadioButton;
}
public int getLeftRadioButtonDesRes() {
return leftRadioButtonDesRes;
}
public void setLeftRadioButtonDesRes(int leftRadioButtonDesRes) {
this.leftRadioButtonDesRes = leftRadioButtonDesRes;
}
public int getRightRadioButtonDesRes() {
return rightRadioButtonDesRes;
}
public void setRightRadioButtonDesRes(int rightRadioButtonDesRes) {
this.rightRadioButtonDesRes = rightRadioButtonDesRes;
}
public int getEnableRadioButtonDesRes() {
return enableRadioButtonDesRes;
}
public void setEnableRadioButtonDesRes(int enableRadioButtonDesRes) {
this.enableRadioButtonDesRes = enableRadioButtonDesRes;
}
public String getRelevanceKey() {
return relevanceKey;
}
public void setRelevanceKey(String relevanceKey) {
this.relevanceKey = relevanceKey;
}
}

View File

@@ -0,0 +1,58 @@
package com.faceunity.nama.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.faceunity.nama.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 = 0.7;//强度
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.faceunity.nama.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.faceunity.nama.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,53 @@
package com.faceunity.nama.infe;
import com.faceunity.nama.entity.BodyBeautyBean;
import com.faceunity.nama.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,102 @@
package com.faceunity.nama.infe;
import com.faceunity.nama.entity.FaceBeautyBean;
import com.faceunity.nama.entity.FaceBeautyFilterBean;
import com.faceunity.nama.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.faceunity.nama.infe;
import com.faceunity.nama.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.faceunity.nama.infe;
import com.faceunity.nama.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,30 @@
package com.faceunity.nama.listener;
import com.faceunity.core.enumeration.FUAIProcessorEnum;
/**
* DESCFURenderer状态回调监听
* Created on 2021/4/29
*/
public interface FURendererListener {
/**
* 识别到的人脸或人体数量发生变化
*
* @param type 类型
* @param status 数量
*/
void onTrackStatusChanged(FUAIProcessorEnum type, int status);
/**
* 统计每 10 帧的平均数据FPS 和渲染函数调用时间
*
* @param fps FPS
* @param callTime 渲染函数调用时间
*/
void onFpsChanged(double fps, double callTime);
}

View File

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

View File

@@ -0,0 +1,40 @@
package com.faceunity.nama.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,55 @@
package com.faceunity.nama.repo;
import com.faceunity.core.controller.bodyBeauty.BodyBeautyParam;
import com.faceunity.nama.R;
import com.faceunity.nama.entity.BodyBeautyBean;
import com.faceunity.nama.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,424 @@
package com.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.entity.FaceBeautyBean;
import com.faceunity.nama.entity.FaceBeautyFilterBean;
import com.faceunity.nama.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";
public static FaceBeauty customFaceBeauty;
/**
* 获取默认推荐美颜模型
*
* @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.setBlurType(FaceBeautyBlurTypeEnum.FineSkin);
recommendFaceBeauty.setBlurIntensity(5); // 赋值范围0-6
recommendFaceBeauty.setColorIntensity(0.3);
recommendFaceBeauty.setRedIntensity(0.3);
recommendFaceBeauty.setSharpenIntensity(0.2);
recommendFaceBeauty.setEyeBrightIntensity(0);
recommendFaceBeauty.setToothIntensity(0);
recommendFaceBeauty.setRemovePouchIntensity(0);
recommendFaceBeauty.setRemoveLawPatternIntensity(0);
/*美型*/
recommendFaceBeauty.setCheekThinningIntensity(0);//瘦脸
recommendFaceBeauty.setCheekVIntensity(0.5);//V脸
//recommendFaceBeauty.setCheekNarrowIntensity(0);//窄脸
recommendFaceBeauty.setCheekNarrowIntensity(0);//窄脸
recommendFaceBeauty.setCheekSmallIntensity(0);//小脸
recommendFaceBeauty.setEyeEnlargingIntensity(0.4);//大眼
recommendFaceBeauty.setChinIntensity(0.3);//下巴
recommendFaceBeauty.setForHeadIntensity(0.3);//额头
recommendFaceBeauty.setNoseIntensity(0.3);//瘦鼻
recommendFaceBeauty.setMouthIntensity(0.4);//嘴型
recommendFaceBeauty.setCanthusIntensity(0);//开眼角
recommendFaceBeauty.setEyeSpaceIntensity(0.5);//眼距
recommendFaceBeauty.setEyeRotateIntensity(0.5);//眼睛角度
recommendFaceBeauty.setLongNoseIntensity(0.5);//鼻子长度
recommendFaceBeauty.setPhiltrumIntensity(0.5);//缩人中
recommendFaceBeauty.setSmileIntensity(0); //微笑嘴角
/*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);*/
if (customFaceBeauty == null) customFaceBeauty = recommendFaceBeauty;
return customFaceBeauty;
}
public static void setCustomFaceBeauty(FaceBeauty faceBeauty) {
customFaceBeauty = faceBeauty;
}
/**
* 初始化美肤参数
*
* @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_M2, 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_M2, 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_M2, 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_M2, 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_M2, 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_M2, 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(3, 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_M2, 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_M2, 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_M2, 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_M2, new ModelAttributeData(0.3, 0.5, 0.0, 1.0));
params.put(FaceBeautyParam.NOSE_INTENSITY_M2, new ModelAttributeData(0.5, 0.0, 0.0, 1.0));
params.put(FaceBeautyParam.MOUTH_INTENSITY_M2, 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;
}
public static HashMap<String,Object> faceBeautyToHashMap(FaceBeauty faceBeauty) {
HashMap<String,Object> hashMap = new HashMap<>();
//FaceBeauty recommendFaceBeauty = new FaceBeauty(new FUBundleData(FaceBeautySource.BUNDLE_FACE_BEAUTIFICATION));
//recommendFaceBeauty.setFilterName(FaceBeautyFilterEnum.ZIRAN_1);
hashMap.put(FaceBeautyParam.FILTER_NAME,faceBeauty.getFilterName());
//recommendFaceBeauty.setFilterIntensity(0.4);
hashMap.put(FaceBeautyParam.FILTER_INTENSITY,faceBeauty.getFilterIntensity());
/*美肤*/
//recommendFaceBeauty.setBlurType(FaceBeautyBlurTypeEnum.FineSkin);
hashMap.put(FaceBeautyParam.BLUR_TYPE,(Integer)faceBeauty.getBlurType());
//recommendFaceBeauty.setBlurIntensity(3); // 赋值范围0-6
hashMap.put(FaceBeautyParam.BLUR_INTENSITY,faceBeauty.getBlurIntensity());
//recommendFaceBeauty.setColorIntensity(0.3);
hashMap.put(FaceBeautyParam.COLOR_INTENSITY,faceBeauty.getColorIntensity());
//recommendFaceBeauty.setRedIntensity(0.3);
hashMap.put(FaceBeautyParam.RED_INTENSITY,faceBeauty.getRedIntensity());
//recommendFaceBeauty.setSharpenIntensity(0.2);
hashMap.put(FaceBeautyParam.SHARPEN_INTENSITY,faceBeauty.getSharpenIntensity());
//recommendFaceBeauty.setEyeBrightIntensity(0);
hashMap.put(FaceBeautyParam.EYE_BRIGHT_INTENSITY,faceBeauty.getEyeBrightIntensity());
//recommendFaceBeauty.setToothIntensity(0);
hashMap.put(FaceBeautyParam.TOOTH_WHITEN_INTENSITY,faceBeauty.getToothIntensity());
//recommendFaceBeauty.setRemovePouchIntensity(0);
hashMap.put(FaceBeautyParam.REMOVE_POUCH_INTENSITY,faceBeauty.getRemovePouchIntensity());
//recommendFaceBeauty.setRemoveLawPatternIntensity(0);
hashMap.put(FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY,faceBeauty.getRemoveLawPatternIntensity());
/*美型*/
//recommendFaceBeauty.setCheekThinningIntensity(0);//瘦脸
hashMap.put(FaceBeautyParam.CHEEK_THINNING_INTENSITY,faceBeauty.getCheekThinningIntensity());
//recommendFaceBeauty.setCheekVIntensity(0.5);//V脸
hashMap.put(FaceBeautyParam.CHEEK_V_INTENSITY,faceBeauty.getCheekVIntensity());
//recommendFaceBeauty.setCheekNarrowIntensityV2(0);//窄脸
hashMap.put(FaceBeautyParam.CHEEK_NARROW_INTENSITY_M2,faceBeauty.getCheekNarrowIntensity());
//recommendFaceBeauty.setCheekSmallIntensityV2(0);//小脸
hashMap.put(FaceBeautyParam.CHEEK_SMALL_INTENSITY_M2,faceBeauty.getCheekSmallIntensity());
//recommendFaceBeauty.setEyeEnlargingIntensityV2(0.4);//大眼
hashMap.put(FaceBeautyParam.EYE_ENLARGING_INTENSITY_M2,faceBeauty.getEyeEnlargingIntensity());
//recommendFaceBeauty.setChinIntensity(0.3);//下巴
hashMap.put(FaceBeautyParam.CHIN_INTENSITY,faceBeauty.getChinIntensity());
//recommendFaceBeauty.setForHeadIntensityV2(0.3);//额头
hashMap.put(FaceBeautyParam.FOREHEAD_INTENSITY_M2,faceBeauty.getForHeadIntensity());
//recommendFaceBeauty.setNoseIntensityV2(0.3);//瘦鼻
hashMap.put(FaceBeautyParam.NOSE_INTENSITY_M2,faceBeauty.getNoseIntensity());
//recommendFaceBeauty.setMouthIntensityV2(0.4);//嘴型
hashMap.put(FaceBeautyParam.MOUTH_INTENSITY_M2,faceBeauty.getMouthIntensity());
//recommendFaceBeauty.setCanthusIntensity(0);//开眼角
hashMap.put(FaceBeautyParam.CANTHUS_INTENSITY,faceBeauty.getCanthusIntensity());
//recommendFaceBeauty.setEyeSpaceIntensity(0.5);//眼距
hashMap.put(FaceBeautyParam.EYE_SPACE_INTENSITY,faceBeauty.getEyeSpaceIntensity());
//recommendFaceBeauty.setEyeRotateIntensity(0.5);//眼睛角度
hashMap.put(FaceBeautyParam.EYE_ROTATE_INTENSITY,faceBeauty.getEyeRotateIntensity());
//recommendFaceBeauty.setLongNoseIntensity(0.5);//鼻子长度
hashMap.put(FaceBeautyParam.LONG_NOSE_INTENSITY,faceBeauty.getLongNoseIntensity());
//recommendFaceBeauty.setPhiltrumIntensity(0.5);//缩人中
hashMap.put(FaceBeautyParam.PHILTRUM_INTENSITY,faceBeauty.getPhiltrumIntensity());
//recommendFaceBeauty.setSmileIntensity(0); //微笑嘴角
hashMap.put(FaceBeautyParam.SMILE_INTENSITY,faceBeauty.getSmileIntensity());
return hashMap;
}
public static FaceBeauty hashMapToFaceBeauty(HashMap<String,Object> hashMap) {
FaceBeauty recommendFaceBeauty = new FaceBeauty(new FUBundleData(FaceBeautySource.BUNDLE_FACE_BEAUTIFICATION));
recommendFaceBeauty.setFilterName((String) hashMap.get(FaceBeautyParam.FILTER_NAME));
recommendFaceBeauty.setFilterIntensity((Double) hashMap.get(FaceBeautyParam.FILTER_INTENSITY));
/*美肤*/
double blerType = (double) hashMap.get(FaceBeautyParam.BLUR_TYPE);
recommendFaceBeauty.setBlurType((int) blerType);
recommendFaceBeauty.setBlurIntensity((Double) hashMap.get(FaceBeautyParam.BLUR_INTENSITY)); // 赋值范围0-6
recommendFaceBeauty.setColorIntensity((Double) hashMap.get(FaceBeautyParam.COLOR_INTENSITY));
recommendFaceBeauty.setRedIntensity((Double) hashMap.get(FaceBeautyParam.RED_INTENSITY));
recommendFaceBeauty.setSharpenIntensity((Double) hashMap.get(FaceBeautyParam.SHARPEN_INTENSITY));
recommendFaceBeauty.setEyeBrightIntensity((Double) hashMap.get(FaceBeautyParam.EYE_BRIGHT_INTENSITY));
recommendFaceBeauty.setToothIntensity((Double) hashMap.get(FaceBeautyParam.TOOTH_WHITEN_INTENSITY));
recommendFaceBeauty.setRemovePouchIntensity((Double) hashMap.get(FaceBeautyParam.REMOVE_POUCH_INTENSITY));
recommendFaceBeauty.setRemoveLawPatternIntensity((Double) hashMap.get(FaceBeautyParam.REMOVE_NASOLABIAL_FOLDS_INTENSITY));
/*美型*/
recommendFaceBeauty.setCheekThinningIntensity((Double) hashMap.get(FaceBeautyParam.CHEEK_THINNING_INTENSITY));//瘦脸
recommendFaceBeauty.setCheekVIntensity((Double) hashMap.get(FaceBeautyParam.CHEEK_V_INTENSITY));//V脸
recommendFaceBeauty.setCheekNarrowIntensity((Double) hashMap.get(FaceBeautyParam.CHEEK_NARROW_INTENSITY_M2));//窄脸
recommendFaceBeauty.setCheekSmallIntensity((Double) hashMap.get(FaceBeautyParam.CHEEK_SMALL_INTENSITY_M2));//小脸
recommendFaceBeauty.setEyeEnlargingIntensity((Double) hashMap.get(FaceBeautyParam.EYE_ENLARGING_INTENSITY_M2));//大眼
recommendFaceBeauty.setChinIntensity((Double) hashMap.get(FaceBeautyParam.CHIN_INTENSITY));//下巴
recommendFaceBeauty.setForHeadIntensity((Double) hashMap.get(FaceBeautyParam.FOREHEAD_INTENSITY_M2));//额头
recommendFaceBeauty.setNoseIntensity((Double) hashMap.get(FaceBeautyParam.NOSE_INTENSITY_M2));//瘦鼻
recommendFaceBeauty.setMouthIntensity((Double) hashMap.get(FaceBeautyParam.MOUTH_INTENSITY_M2));//嘴型
recommendFaceBeauty.setCanthusIntensity((Double) hashMap.get(FaceBeautyParam.CANTHUS_INTENSITY));//开眼角
recommendFaceBeauty.setEyeSpaceIntensity((Double) hashMap.get(FaceBeautyParam.EYE_SPACE_INTENSITY));//眼距
recommendFaceBeauty.setEyeRotateIntensity((Double) hashMap.get(FaceBeautyParam.EYE_ROTATE_INTENSITY));//眼睛角度
recommendFaceBeauty.setLongNoseIntensity((Double) hashMap.get(FaceBeautyParam.LONG_NOSE_INTENSITY));//鼻子长度
recommendFaceBeauty.setPhiltrumIntensity((Double) hashMap.get(FaceBeautyParam.PHILTRUM_INTENSITY));//缩人中
recommendFaceBeauty.setSmileIntensity((Double) hashMap.get(FaceBeautyParam.SMILE_INTENSITY)); //微笑嘴角
return recommendFaceBeauty;
}
}

View File

@@ -0,0 +1,34 @@
package com.faceunity.nama.repo;
import com.faceunity.nama.R;
import com.faceunity.nama.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("gangfeng", R.mipmap.icon_makeup_combination_harbour_wind, R.string.makeup_combination_gangfeng, "makeup/gangfeng.bundle"));
combinations.add(new MakeupCombinationBean("linjia", R.mipmap.icon_makeup_combination_neighbor_girl, R.string.makeup_combination_neighbor, "makeup/linjia.bundle"));
combinations.add(new MakeupCombinationBean("wumei", R.mipmap.icon_makeup_combination_charming, R.string.makeup_combination_charming, "makeup/wumei.bundle"));
return combinations;
}
//endregion
}

View File

@@ -0,0 +1,30 @@
package com.faceunity.nama.repo;
import com.faceunity.nama.R;
import com.faceunity.nama.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_none, null));
propBeans.add(new PropBean(R.mipmap.icon_sticker_cat_sparks, "sticker/cat_sparks.bundle"));
propBeans.add(new PropBean(R.mipmap.icon_sticker_sdlr, "sticker/sdlr.bundle"));
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.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.seekbar.internal.compat.SeekBarCompat;
import com.faceunity.nama.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.faceunity.nama.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.faceunity.nama.seekbar.internal.compat.SeekBarCompat;
import com.faceunity.nama.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,72 @@
package com.faceunity.nama.seekbar.internal.compat;
import com.faceunity.nama.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.faceunity.nama.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.faceunity.nama.seekbar.internal.drawable.AlmostRippleDrawable;
import com.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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,142 @@
package com.faceunity.nama.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.faceunity.nama.R;
import com.faceunity.nama.checkbox.CheckGroup;
import com.faceunity.nama.control.BodyBeautyControlView;
import com.faceunity.nama.control.FaceBeautyControlView;
import com.faceunity.nama.control.MakeupControlView;
import com.faceunity.nama.control.PropControlView;
import com.faceunity.nama.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);
}
public FaceBeautyControlView getFaceBeautyControlView() {
return mFaceBeautyControlView;
}
}

View File

@@ -0,0 +1,582 @@
package com.faceunity.nama.utils;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
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;
public class FuDeviceUtils {
public static final String TAG = "FuDeviceUtils";
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) {
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);
return level;
}
/**
* -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.e(TAG,"deviceName: " + deviceName);
return deviceName;
}
}

View File

@@ -0,0 +1,47 @@
package com.faceunity.nama.utils.device;
import java.util.ArrayList;
import java.util.Collections;
/**
* DESC
* Created on 2021/3/12
*/
public class DefaultScoreProvider implements DeviceScoreProvider {
@Override
public double getCpuScore(String cpuName) {
int cpuCores = Runtime.getRuntime().availableProcessors();
ArrayList<Long> list = DeviceCpuUtils.getCPUFrequencies();
Collections.sort(list);
Collections.reverse(list);
DeviceScoreUtils.CPUFrequencies.clear();
for (int i = 0; i < list.size(); i++) {
DeviceScoreUtils.CPUFrequencies.add(list.get(i) / 1000 / 1000f + "GHz");
}
if (cpuCores <= 4) {
return 65;
}
if (list == null || list.isEmpty()) {
return 65;
}
double maxFre = list.get(0) / 1000 / 1000f;
if (maxFre < 2.2) {
return 65;
}
if (maxFre <= 2.4) {
return 70 - MathUtils.getScore(maxFre, 2.4, 5, 0.2);
}
if (maxFre <= 2.8) {
return 75 - MathUtils.getScore(maxFre, 2.8, 5, 0.4);
}
if (maxFre < 3.2) {
return 85 - MathUtils.getScore(maxFre, 3.2, 10, 0.4);
}
return 90 + (maxFre - 3.2) / 0.2;
}
@Override
public double getGpuScore(String glRenderer) {
return 65;
}
}

View File

@@ -0,0 +1,48 @@
package com.faceunity.nama.utils.device;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
/**
* DESC
* Created on 2021/3/12
*/
public class DeviceCpuUtils {
public static ArrayList<Long> getCPUFrequencies() {
File cpuFolder = new File("/sys/devices/system/cpu/");
File[] cpuFiles = cpuFolder.listFiles();
ArrayList<Long> frequencies = new ArrayList<>();
for (File file : cpuFiles) {
if (file.getName().startsWith("cpu")) {
String fileName = file.getName();
String frequency = readFrequencyFromFile(file.getAbsolutePath() + "/cpufreq/cpuinfo_max_freq");
if (!TextUtils.isEmpty(frequency)) {
frequencies.add(Long.valueOf(frequency));
}
}
}
return frequencies;
}
private static String readFrequencyFromFile(String filePath) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new FileReader(filePath));
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}

View File

@@ -0,0 +1,23 @@
package com.faceunity.nama.utils.device;
/**
* DESC
* Created on 2021/3/12
*/
interface DeviceScoreProvider {
/**
* 获取CPU分数
*
* @param cpuName String
* @return double
*/
double getCpuScore(String cpuName);
/**
* 获取GPU分数
*
* @param glRenderer String GPU名称
* @return double
*/
double getGpuScore(String glRenderer);
}

View File

@@ -0,0 +1,158 @@
package com.faceunity.nama.utils.device;
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.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @author changwei on 2023/11/8 15:00
*/
public class DeviceScoreUtils {
public static final String TAG = "DeviceLevelUtils";
public static final double CPU_PROPORTION = 0.6;
public static final double GPU_PROPORTION = 0.4;
public static final double MEM_PROPORTION = 0.1;
public static ArrayList<String> CPUFrequencies = new ArrayList<>();
public static String CPUName;
public static String GPUName;
public static double CPUScore;
public static double GPUScore;
public static double getDeviceScore() {
return getCpuScore() * CPU_PROPORTION + getGpuScore() * GPU_PROPORTION;
}
public static double getCpuScore() {
String cpuName = getCpuName();
CPUName = cpuName;
DeviceScoreProvider provider = new DefaultScoreProvider();
if (!TextUtils.isEmpty(cpuName)) {
//高通骁龙
if (cpuName.contains("qcom") || cpuName.contains("Qualcomm")) {
provider = new QualcommScoreProvider();
//联发科
} else if (cpuName.contains("MT")) {
provider = new MTKScoreProvider();
}
}
CPUScore = provider.getCpuScore(cpuName);
return CPUScore;
}
public static double getGpuScore() {
OffLineRenderHandler.getInstance().onResume();
final double[] score = new double[1];
CountDownLatch countDownLatch = new CountDownLatch(1);
//有一些设备不符合下述的判断规则,则走一个机型判断模式
OffLineRenderHandler.getInstance().queueEvent(() -> {
DeviceScoreProvider provider;
String glRenderer = GLES20.glGetString(GLES20.GL_RENDERER); //GPU 渲染器
String glVendor = GLES20.glGetString(GLES20.GL_VENDOR); //GPU 供应商
GPUName = glRenderer;
Log.d(TAG, "glRenderer: " + glRenderer + ",glVendor: " + glVendor);
countDownLatch.countDown();
switch (glVendor) {
case "Qualcomm":
provider = new QualcommScoreProvider();
break;
case "ARM":
provider = new MTKScoreProvider();
break;
default:
provider = new DefaultScoreProvider();
break;
}
score[0] = provider.getGpuScore(glRenderer);
});
try {
countDownLatch.await(200, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
OffLineRenderHandler.getInstance().onPause();
GPUScore = score[0];
return score[0];
}
public static double getMemScore() {
long ramMemory = getTotalRam();
if (ramMemory <= 4) {
return 65;
}
if (ramMemory <= 6) {
return 75 - MathUtils.getScore(ramMemory, 6, 10, 2);
}
if (ramMemory <= 8) {
return 85 - MathUtils.getScore(ramMemory, 8, 5, 2);
}
if (ramMemory <= 12) {
return 90 - MathUtils.getScore(ramMemory, 12, 5, 4);
}
if (ramMemory <= 16) {
return 95 - MathUtils.getScore(ramMemory, 16, 5, 4);
}
return 95 + (ramMemory - 16) / 2;
}
/**
* 获取硬件信息(cpu型号)
*
* @return String
*/
public static String getCpuName() {
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;
}
public static long getTotalRam() {
String path = "/proc/meminfo";
String ramMemorySize = null;
int totalRam = 0;
try {
FileReader fileReader = new FileReader(path);
BufferedReader br = new BufferedReader(fileReader, 4096);
ramMemorySize = br.readLine().split("\\s+")[1];
br.close();
} catch (Exception e) {
e.printStackTrace();
}
if (ramMemorySize != null) {
totalRam = (int) Math.ceil((Float.valueOf(Float.parseFloat(ramMemorySize) / (1024 * 1024)).doubleValue()));
}
return totalRam;
}
}

View File

@@ -0,0 +1,107 @@
package com.faceunity.nama.utils.device;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collections;
/**
* DESC 联发科芯片
* Created on 2021/3/12
*/
public class MTKScoreProvider implements DeviceScoreProvider {
@Override
public double getCpuScore(String cpuName) {
DeviceScoreUtils.CPUFrequencies.clear();
int cpuCores = Runtime.getRuntime().availableProcessors();
ArrayList<Long> list = DeviceCpuUtils.getCPUFrequencies();
if (list.isEmpty()) {
return 65;
}
Collections.sort(list);
Collections.reverse(list);
for (int i = 0; i < list.size(); i++) {
DeviceScoreUtils.CPUFrequencies.add(list.get(i) / 1000 / 1000f + "GHz");
}
double maxFre = list.get(0) / 1000 / 1000f;
if (cpuCores <= 4) {
return 65;
}
if (!TextUtils.isEmpty(cpuName) && cpuName.startsWith("MT") && cpuName.length() > 7) {
long version = Long.valueOf(cpuName.substring(2, 6));
if (version < 6797) {
return 65;
}
if (version <= 6833) {
return 70 - MathUtils.getScore(version, 6833, 5, 36);
}
if (version <= 6895) {
return 75 - MathUtils.getScore(version, 6895, 5, 60);
}
if (version <= 6983) {
return 80 - MathUtils.getScore(version, 6983, 5, 88);
}
if (version < 6985) {
return 85 - MathUtils.getScore(version, 6985, 5, 2);
}
if (version >= 6985) {
return 90 + (version - 6985) / 3;
}
} else {
if (maxFre < 2.2) {
return 65;
}
if (maxFre <= 2.9) {
return 75 - MathUtils.getScore(maxFre, 2.9, 10, 0.7);
}
if (maxFre < 3.3) {
return 85 - MathUtils.getScore(maxFre, 3.3, 10, 0.4);
}
return 90 + (maxFre - 3.3) / 0.2;
}
return 65;
}
@Override
public double getGpuScore(String glRenderer) {
if (TextUtils.isEmpty(glRenderer)) {
return 65;
}
if (glRenderer.startsWith("Mali")) {
glRenderer = glRenderer.split(" ")[0];
String GPUVersionStr = glRenderer.substring(glRenderer.indexOf("-") + 1);
String strStart = GPUVersionStr.substring(0, 1);
Integer version = Integer.valueOf(GPUVersionStr.substring(1));
if (strStart.equals("T")) {
return 65;
}
if (strStart.equals("G")) {
if (version >= 700 && version < 800) {
return 85;
}
if (version >= 600 && version < 700) {
return 80;
}
if (version < 50) {
return 65;
}
if (version < 60) {
return 66 + (version - 50);
}
if (version < 70) {
return 67 + (version - 60);
}
if (version < 80) {
return 68 + (version - 70) * 1.5;
}
}
}
if (glRenderer.startsWith("PowerVR")) {
return 70;
}
if (glRenderer.startsWith("Immortalis")) {
return 85;
}
return 65;
}
}

View File

@@ -0,0 +1,11 @@
package com.faceunity.nama.utils.device;
/**
* DESC
* Created on 2021/3/12
*/
public class MathUtils {
public static double getScore(double num, double baseNum, double intervalScore, double intervalNum) {
return (baseNum - num) * intervalScore / intervalNum;
}
}

View File

@@ -0,0 +1,81 @@
package com.faceunity.nama.utils.device;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Collections;
/**
* DESC高通芯片
* Created on 2021/3/12
*/
public class QualcommScoreProvider implements DeviceScoreProvider {
@Override
public double getCpuScore(String cpuName) {
DeviceScoreUtils.CPUFrequencies.clear();
int cpuCores = Runtime.getRuntime().availableProcessors();
ArrayList<Long> list = DeviceCpuUtils.getCPUFrequencies();
if (list.isEmpty()) {
return 65;
}
Collections.sort(list);
Collections.reverse(list);
for (int i = 0; i < list.size(); i++) {
DeviceScoreUtils.CPUFrequencies.add(list.get(i) / 1000 / 1000f + "GHz");
}
double maxFre = list.get(0) / 1000 / 1000f;
if (cpuCores <= 4) {
return 65;
}
if (maxFre < 1.9) {
return 65;
}
if (maxFre <= 2.2) {
return 70 - MathUtils.getScore(maxFre, 2.2, 5, 0.3);
}
if (maxFre <= 2.7) {
return 75 - MathUtils.getScore(maxFre, 2.7, 5, 0.5);
}
if (maxFre <= 2.9) {
return 80 - MathUtils.getScore(maxFre, 2.9, 5, 0.2);
}
if (maxFre <= 3.2) {
return 90 - MathUtils.getScore(maxFre, 3.2, 10, 0.3);
}
return 95 + (maxFre - 3.3) / 0.1;
}
@Override
public double getGpuScore(String glRenderer) {
if (TextUtils.isEmpty(glRenderer) || !glRenderer.startsWith("Adreno")) {
return 65;
}
int GPUVersion;
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 < 506) {
return 65;
}
if (GPUVersion <= 610) {
return 70 - MathUtils.getScore(GPUVersion, 610, 5, 104);
}
if (GPUVersion <= 630) {
return 75 - MathUtils.getScore(GPUVersion, 630, 5, 20);
}
if (GPUVersion <= 730) {
return 85 - MathUtils.getScore(GPUVersion, 730, 10, 100);
}
if (GPUVersion < 740) {
return 95 - MathUtils.getScore(GPUVersion, 740, 10, 20);
}
return 95 + (GPUVersion - 740) / 20;
}
}

View File

@@ -0,0 +1,462 @@
/*
* 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.faceunity.nama.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.faceunity.nama.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.faceunity.nama.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,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/main_color_090017" android:state_selected="true" />
<item android:color="#99FFFFFF" android:state_selected="false"/>
</selector>

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="25dp" />
<stroke
android:width="1dp"
android:color="@color/colorWhite" />
</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="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,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomLeftRadius="25dp"
android:topLeftRadius="25dp" />
<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="25dp"
android:topLeftRadius="25dp" />
<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_skin_seg_left_check" android:state_selected="true" />
<item android:drawable="@drawable/bg_radio_skin_seg_left" android:state_selected="false"/>
</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="25dp"
android:topRightRadius="25dp" />
<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="25dp"
android:topRightRadius="25dp" />
<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_skin_seg_right_check" android:state_selected="true" />
<item android:drawable="@drawable/bg_radio_skin_seg_right" android:state_selected="false"/>
</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>

Some files were not shown because too many files have changed in this diff Show More