arrayList = getPickedLayout().getSelectItems(PickedLayout.TYPE_PICTURE);
+ listener.onPickedList(arrayList);
+ }
+ }
+ });
+ }
+
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoCut.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoCut.java
new file mode 100644
index 0000000..ab5d9e3
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoCut.java
@@ -0,0 +1,300 @@
+package com.tencent.qcloud.ugckit;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.tencent.qcloud.ugckit.basic.JumpActivityMgr;
+import com.tencent.qcloud.ugckit.basic.OnUpdateUIListener;
+import com.tencent.qcloud.ugckit.basic.UGCKitResult;
+import com.tencent.qcloud.ugckit.module.PlayerManagerKit;
+import com.tencent.qcloud.ugckit.module.ProcessKit;
+import com.tencent.qcloud.ugckit.module.VideoGenerateKit;
+import com.tencent.qcloud.ugckit.module.cut.AbsVideoCutUI;
+import com.tencent.qcloud.ugckit.module.cut.IVideoCutLayout;
+import com.tencent.qcloud.ugckit.utils.BackgroundTasks;
+import com.tencent.qcloud.ugckit.utils.DialogUtil;
+import com.tencent.qcloud.ugckit.utils.TelephonyUtil;
+import com.tencent.qcloud.ugckit.utils.ToastUtil;
+import com.tencent.qcloud.ugckit.component.dialog.ProgressDialogUtil;
+import com.tencent.qcloud.ugckit.component.dialogfragment.ProgressFragmentUtil;
+import com.tencent.qcloud.ugckit.module.effect.VideoEditerSDK;
+import com.tencent.ugc.TXVideoEditConstants;
+import com.tencent.ugc.TXVideoEditer;
+import com.tencent.ugc.TXVideoInfoReader;
+
+/**
+ * 腾讯云短视频UGCKit:视频裁剪控件
+ *
+ * 功能:用于实现长时间视频裁剪其中一段生成一段短时间的视频。
+ */
+public class UGCKitVideoCut extends AbsVideoCutUI implements PlayerManagerKit.OnPreviewListener {
+ private static final String TAG = "UGCKitVideoCut";
+
+ private ProgressDialogUtil mProgressDialogUtil;
+ private ProgressFragmentUtil mProgressFragmentUtil;
+ private boolean mComplete = false;
+
+ public UGCKitVideoCut(Context context) {
+ super(context);
+ initDefault();
+ }
+
+ public UGCKitVideoCut(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDefault();
+ }
+
+ public UGCKitVideoCut(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDefault();
+ }
+
+ private void initDefault() {
+ mProgressDialogUtil = new ProgressDialogUtil(getContext());
+ mProgressFragmentUtil = new ProgressFragmentUtil((FragmentActivity) getContext(), getResources().getString(R.string.ugckit_video_cutting));
+
+ VideoEditerSDK.getInstance().releaseSDK();
+ VideoEditerSDK.getInstance().clear();
+ VideoEditerSDK.getInstance().initSDK();
+
+ // 点击"下一步"
+ getTitleBar().setOnRightClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setEnableRightButton(false);
+ mProgressFragmentUtil.showLoadingProgress(new ProgressFragmentUtil.IProgressListener() {
+ @Override
+ public void onStop() {
+ // 取消裁剪
+ mProgressFragmentUtil.dismissLoadingProgress();
+ setEnableRightButton(true);
+ boolean editFlag = JumpActivityMgr.getInstance().getEditFlagFromCut();
+ if (editFlag) {
+ ProcessKit.getInstance().stopProcess();
+ } else {
+ VideoGenerateKit.getInstance().stopGenerate();
+ }
+ PlayerManagerKit.getInstance().startPlay();
+ // 未加载完缩略图,重新进行加载
+ if (!mComplete) {
+ Log.i(TAG, "[UGCKit][VideoCut]last load uncomplete, reload thunmail");
+ loadThumbnail();
+ }
+ }
+ });
+ PlayerManagerKit.getInstance().stopPlay();
+ //如果图片没有加载完,先停止加载
+ ProcessKit.getInstance().stopProcess();
+
+ boolean editFlag = JumpActivityMgr.getInstance().getEditFlagFromCut();
+ if (editFlag) {
+ ProcessKit.getInstance().startProcess();
+ } else {
+ VideoGenerateKit.getInstance().startGenerate();
+ }
+ }
+ });
+ // 监听电话
+ TelephonyUtil.getInstance().initPhoneListener();
+ }
+
+ @Override
+ public void setVideoPath(final String videoPath) {
+ Log.i(TAG, "[UGCKit][VideoCut]setVideoPath:" + videoPath);
+ if (TextUtils.isEmpty(videoPath)) {
+ ToastUtil.toastShortMessage(getResources().getString(R.string.ugckit_video_cutter_activity_oncreate_an_unknown_error_occurred_the_path_cannot_be_empty));
+ return;
+ }
+
+ VideoEditerSDK.getInstance().setVideoPath(videoPath);
+ // 初始化播放器界面[必须在setPictureList/setVideoPath设置数据源之后]
+ getVideoPlayLayout().initPlayerLayout();
+
+ // 显示圆形进度条
+ mProgressDialogUtil.showProgressDialog();
+
+ // 重新设置路径,缩略图重新加载
+ mComplete = false;
+ // 加载视频基本信息
+ loadVideoInfo(videoPath);
+ // 圆形进度条消失
+ mProgressDialogUtil.dismissProgressDialog();
+ }
+
+ private void loadVideoInfo(String videoPath) {
+ // 加载视频信息
+ TXVideoEditConstants.TXVideoInfo info = TXVideoInfoReader.getInstance(UGCKit.getAppContext()).getVideoFileInfo(videoPath);
+ if (info == null) {
+ DialogUtil.showDialog(getContext(), getResources().getString(R.string.ugckit_video_cutter_activity_video_main_handler_edit_failed), getResources().getString(R.string.ugckit_does_not_support_android_version_below_4_3), null);
+ } else {
+ VideoEditerSDK.getInstance().setTXVideoInfo(info);
+ getVideoCutLayout().setVideoInfo(info);
+ getVideoCutLayout().setOnRotateVideoListener(new IVideoCutLayout.OnRotateVideoListener() {
+ @Override
+ public void onRotate(int rotation) {
+ VideoEditerSDK.getInstance().getEditer().setRenderRotation(rotation);
+ }
+ });
+ Log.i(TAG, "[UGCKit][VideoCut]load thunmail");
+ loadThumbnail();
+ // 播放视频
+ PlayerManagerKit.getInstance().startPlayCutTime();
+ }
+ }
+
+ private void loadThumbnail() {
+ // 初始化缩略图列表,裁剪缩略图时间间隔3秒钟一张
+ getVideoCutLayout().clearThumbnail();
+ final int interval = 3000;
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ final int count = (int) (VideoEditerSDK.getInstance().getVideoDuration() / interval);
+ VideoEditerSDK.getInstance().initThumbnailList(new TXVideoEditer.TXThumbnailListener() {
+ @Override
+ public void onThumbnail(final int index, long timeMs, final Bitmap bitmap) {
+ Log.d(TAG, "onThumbnail index:" + index + ",timeMs:" + timeMs);
+ BackgroundTasks.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getVideoCutLayout().addThumbnail(index, bitmap);
+ if (index >= count - 1) { // Note: index从0开始增长
+ Log.i(TAG, "Load Thumbnail Complete");
+ mComplete = true;
+ }
+ }
+ });
+ }
+ }, interval);
+ }
+ }).start();
+ }
+
+ @Override
+ public void setOnCutListener(@Nullable final OnCutListener listener) {
+ if (listener == null) {
+ ProcessKit.getInstance().setOnUpdateUIListener(null);
+ VideoGenerateKit.getInstance().setOnUpdateUIListener(null);
+ return;
+ }
+ boolean editFlag = JumpActivityMgr.getInstance().getEditFlagFromCut();
+ // 设置生成的监听器,用来更新控件
+ if (editFlag) {
+ // 裁剪后进入编辑
+ ProcessKit.getInstance().setOnUpdateUIListener(new OnUpdateUIListener() {
+ @Override
+ public void onUIProgress(float progress) {
+ mProgressFragmentUtil.updateGenerateProgress((int) (progress * 100));
+ }
+
+ @Override
+ public void onUIComplete(int retCode, String descMsg) {
+ mProgressFragmentUtil.dismissLoadingProgress();
+
+ if (listener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.errorCode = retCode;
+ ugcKitResult.descMsg = descMsg;
+ listener.onCutterCompleted(ugcKitResult);
+ }
+ setEnableRightButton(true);
+ }
+
+ @Override
+ public void onUICancel() {
+ if (listener != null) {
+ listener.onCutterCanceled();
+ }
+ }
+ });
+ } else {
+ // 裁剪后输出视频
+ VideoGenerateKit.getInstance().setOnUpdateUIListener(new OnUpdateUIListener() {
+ @Override
+ public void onUIProgress(float progress) {
+ mProgressFragmentUtil.updateGenerateProgress((int) (progress * 100));
+ }
+
+ @Override
+ public void onUIComplete(int retCode, String descMsg) {
+ mProgressFragmentUtil.dismissLoadingProgress();
+
+ if (listener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.errorCode = retCode;
+ ugcKitResult.descMsg = descMsg;
+ ugcKitResult.outputPath = VideoGenerateKit.getInstance().getVideoOutputPath();
+ ugcKitResult.coverPath = VideoGenerateKit.getInstance().getCoverPath();
+ listener.onCutterCompleted(ugcKitResult);
+ }
+ setEnableRightButton(true);
+ }
+
+ @Override
+ public void onUICancel() {
+ if (listener != null) {
+ listener.onCutterCanceled();
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void startPlay() {
+ PlayerManagerKit.getInstance().addOnPreviewLitener(this);
+ PlayerManagerKit.getInstance().startPlay();
+ }
+
+ @Override
+ public void stopPlay() {
+ setEnableRightButton(true);
+ PlayerManagerKit.getInstance().stopPlay();
+ PlayerManagerKit.getInstance().removeOnPreviewListener(this);
+ boolean editFlag = JumpActivityMgr.getInstance().getEditFlagFromCut();
+ if (editFlag) {
+ ProcessKit.getInstance().stopProcess();
+ } else {
+ VideoGenerateKit.getInstance().stopGenerate();
+ }
+ mProgressFragmentUtil.dismissLoadingProgress();
+ }
+
+ @Override
+ public void release() {
+ TelephonyUtil.getInstance().uninitPhoneListener();
+ }
+
+ @Override
+ public void setVideoEditFlag(boolean flag) {
+ JumpActivityMgr.getInstance().setEditFlagFromCut(flag);
+ }
+
+ @Override
+ public String getVideoOutputPath() {
+ return VideoGenerateKit.getInstance().getVideoOutputPath();
+ }
+
+ @Override
+ public void onPreviewProgress(int time) {
+
+ }
+
+ private void setEnableRightButton(boolean isOpen) {
+ if(null != getTitleBar()) {
+ getTitleBar().getRightButton().setEnabled(isOpen);
+ }
+ }
+
+ @Override
+ public void onPreviewFinish() {
+ // 循环播放
+ PlayerManagerKit.getInstance().startPlay();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoEdit.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoEdit.java
new file mode 100644
index 0000000..f8437c7
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoEdit.java
@@ -0,0 +1,308 @@
+package com.tencent.qcloud.ugckit;
+
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+
+import com.tencent.qcloud.ugckit.basic.ITitleBarLayout;
+import com.tencent.qcloud.ugckit.basic.JumpActivityMgr;
+import com.tencent.qcloud.ugckit.basic.OnUpdateUIListener;
+import com.tencent.qcloud.ugckit.basic.UGCKitResult;
+import com.tencent.qcloud.ugckit.module.PlayerManagerKit;
+import com.tencent.qcloud.ugckit.module.VideoGenerateKit;
+import com.tencent.qcloud.ugckit.module.editer.AbsVideoEditUI;
+import com.tencent.qcloud.ugckit.module.editer.UGCKitEditConfig;
+import com.tencent.qcloud.ugckit.utils.LogReport;
+import com.tencent.qcloud.ugckit.utils.TelephonyUtil;
+import com.tencent.qcloud.ugckit.component.dialog.ActionSheetDialog;
+import com.tencent.qcloud.ugckit.component.dialogfragment.ProgressFragmentUtil;
+import com.tencent.qcloud.ugckit.module.effect.Config;
+import com.tencent.qcloud.ugckit.module.effect.VideoEditerSDK;
+import com.tencent.ugc.TXVideoEditConstants;
+import com.tencent.ugc.TXVideoEditer;
+import com.tencent.ugc.TXVideoInfoReader;
+
+public class UGCKitVideoEdit extends AbsVideoEditUI {
+ private static final String TAG = "UGCKitVideoEdit";
+
+ private ProgressFragmentUtil mProgressFragmentUtil;
+ @Nullable
+ private OnEditListener mOnEditListener;
+ private boolean mIsPublish;
+
+ public UGCKitVideoEdit(Context context) {
+ super(context);
+ initDefault();
+ }
+
+ public UGCKitVideoEdit(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDefault();
+ }
+
+ public UGCKitVideoEdit(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDefault();
+ }
+
+ private void initDefault() {
+ mProgressFragmentUtil = new ProgressFragmentUtil((FragmentActivity) getContext());
+
+ // 点击"完成"
+ getTitleBar().setTitle(getResources().getString(R.string.ugckit_complete), ITitleBarLayout.POSITION.RIGHT);
+ getTitleBar().setOnBackClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ backPressed();
+ }
+ });
+ getTitleBar().setOnRightClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showPublishDialog();
+ }
+ });
+ // 监听电话
+ TelephonyUtil.getInstance().setOnTelephoneListener(new TelephonyUtil.OnTelephoneListener() {
+ @Override
+ public void onRinging() {
+ // 生成状态 取消生成
+ stopGenerate();
+ // 直接停止播放
+ PlayerManagerKit.getInstance().stopPlay();
+ }
+
+ @Override
+ public void onOffhook() {
+ stopGenerate();
+ // 直接停止播放
+ PlayerManagerKit.getInstance().stopPlay();
+ }
+
+ @Override
+ public void onIdle() {
+ // 重新开始播放
+ PlayerManagerKit.getInstance().restartPlay();
+ }
+ });
+ TelephonyUtil.getInstance().initPhoneListener();
+
+ // 设置默认为全功能导入视频方式
+ JumpActivityMgr.getInstance().setQuickImport(false);
+ }
+
+ @Override
+ public void backPressed() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ AlertDialog alertDialog = builder.setTitle(getContext().getString(R.string.ugckit_tips)).setCancelable(false).setMessage(R.string.ugckit_confirm_cancel_edit_content)
+ .setPositiveButton(R.string.ugckit_btn_back, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+ Log.i(TAG, "[UGCKit][VideoEdit]backPressed call stopPlay");
+ PlayerManagerKit.getInstance().stopPlay();
+ // 取消设置的特效
+ VideoEditerSDK.getInstance().releaseSDK();
+ VideoEditerSDK.getInstance().clear();
+ if (mOnEditListener != null) {
+ mOnEditListener.onEditCanceled();
+ }
+ }
+ })
+ .setNegativeButton(getContext().getString(R.string.ugckit_wrong_click), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ }).create();
+ alertDialog.show();
+ }
+
+ /**
+ * 快速导入始终 {@code setVideoPath}
+ * 全功能导入不使用此方法
+ *
+ * @param videoPath 视频裁剪的源路径
+ */
+ @Override
+ public void setVideoPath(String videoPath) {
+ // 获取TXVideoEditer,兼容"快速导入"之前没有初始化TXVideoEditer;"全功能导入",裁剪时已经预处理了视频,此时初始化了TXVideoEditer
+ TXVideoEditer editer = VideoEditerSDK.getInstance().getEditer();
+ if (editer == null) {
+ VideoEditerSDK.getInstance().initSDK();
+ }
+ Log.i(TAG, "[UGCKit][VideoEdit][QuickImport]setVideoPath:" + videoPath);
+ VideoEditerSDK.getInstance().setVideoPath(videoPath);
+
+ // 获取TXVideoInfo,兼容"快速导入"新传入videoPath,之前没有获取视频信息;"全功能导入",裁剪时已经获取视频基本信息。
+ TXVideoEditConstants.TXVideoInfo info = VideoEditerSDK.getInstance().getTXVideoInfo();
+ if (info == null) {
+ // 从"录制"进入,录制勾选了"进入编辑",info在录制界面已设置,此处不为null
+ // 从"录制"进入,录制不勾选"进入编辑",info为null,需要重新获取
+ info = TXVideoInfoReader.getInstance(UGCKit.getAppContext()).getVideoFileInfo(videoPath);
+ VideoEditerSDK.getInstance().setTXVideoInfo(info);
+ }
+
+ // 初始化缩略图列表,编辑缩略图1秒钟一张(先清空缩略图列表)
+ VideoEditerSDK.getInstance().clearThumbnails();
+
+ long startTime = VideoEditerSDK.getInstance().getCutterStartTime();
+ long endTime = VideoEditerSDK.getInstance().getCutterEndTime();
+ if (endTime > startTime) {
+ Log.i(TAG, "[UGCKit][VideoEdit][QuickImport]load thumbnail start time:" + startTime + ",end time:" + endTime);
+ }
+
+ VideoEditerSDK.getInstance().setCutterStartTime(0, info.duration);
+ VideoEditerSDK.getInstance().setVideoDuration(info.duration);
+ VideoEditerSDK.getInstance().initThumbnailList(new TXVideoEditer.TXThumbnailListener() {
+ @Override
+ public void onThumbnail(final int index, long timeMs, final Bitmap bitmap) {
+ Log.d(TAG, "onThumbnail index:" + index + ",timeMs:" + timeMs);
+ VideoEditerSDK.getInstance().addThumbnailBitmap(timeMs, bitmap);
+ }
+ }, 1000);
+
+ JumpActivityMgr.getInstance().setQuickImport(true);
+ }
+
+ /**
+ * 显示发布对话框
+ */
+ private void showPublishDialog() {
+ ActionSheetDialog actionSheetDialog = new ActionSheetDialog(getContext());
+ actionSheetDialog.builder();
+ actionSheetDialog.setCancelable(false);
+ actionSheetDialog.setCancelable(false);
+ actionSheetDialog.addSheetItem(getResources().getString(R.string.ugckit_video_editer_activity_show_publish_dialog_save),
+ ActionSheetDialog.SheetItemColor.Blue, new ActionSheetDialog.OnSheetItemClickListener() {
+ @Override
+ public void onClick(int which) {
+ VideoEditerSDK.getInstance().setPublishFlag(false);
+ startGenerate();
+ }
+ });
+
+ if (mIsPublish) {
+ actionSheetDialog.addSheetItem(getResources().getString(R.string.ugckit_video_editer_activity_show_publish_dialog_publish),
+ ActionSheetDialog.SheetItemColor.Blue, new ActionSheetDialog.OnSheetItemClickListener() {
+ @Override
+ public void onClick(int which) {
+ VideoEditerSDK.getInstance().setPublishFlag(true);
+ startGenerate();
+ }
+
+ });
+ }
+ actionSheetDialog.show();
+ }
+
+ @Override
+ public void initPlayer() {
+ getVideoPlayLayout().initPlayerLayout();
+
+ VideoEditerSDK.getInstance().resetDuration();
+ }
+
+ @Override
+ public void setConfig(UGCKitEditConfig config) {
+ VideoGenerateKit.getInstance().setCustomVideoBitrate(config.videoBitrate);
+ VideoGenerateKit.getInstance().setVideoResolution(config.resolution);
+ VideoGenerateKit.getInstance().setCoverGenerate(config.isCoverGenerate);
+ VideoGenerateKit.getInstance().saveVideoToDCIM(config.isSaveToDCIM);
+ VideoGenerateKit.getInstance().setWaterMark(config.mWaterMarkConfig);
+ VideoGenerateKit.getInstance().setTailWaterMark(config.mTailWaterMarkConfig);
+ mIsPublish = config.isPublish;
+ }
+
+ @Override
+ public void start() {
+ KeyguardManager manager = (KeyguardManager) UGCKit.getAppContext().getSystemService(Context.KEYGUARD_SERVICE);
+ if (!manager.inKeyguardRestrictedInputMode()) {
+ PlayerManagerKit.getInstance().restartPlay();
+ }
+ }
+
+ @Override
+ public void stop() {
+ Log.i(TAG, "[UGCKit][VideoEdit]onStop call stopPlay");
+ PlayerManagerKit.getInstance().stopPlay();
+
+ stopGenerate();
+ }
+
+ @Override
+ public void release() {
+ Config.getInstance().clearConfig();
+ TelephonyUtil.getInstance().uninitPhoneListener();
+ TelephonyUtil.getInstance().setOnTelephoneListener(null);
+ }
+
+ @Override
+ public void setOnVideoEditListener(@Nullable final OnEditListener listener) {
+ if (listener == null) {
+ mOnEditListener = null;
+ VideoGenerateKit.getInstance().setOnUpdateUIListener(null);
+ return;
+ }
+ mOnEditListener = listener;
+
+ VideoGenerateKit.getInstance().setOnUpdateUIListener(new OnUpdateUIListener() {
+ @Override
+ public void onUIProgress(float progress) {
+ mProgressFragmentUtil.updateGenerateProgress((int) (progress * 100));
+ }
+
+ @Override
+ public void onUIComplete(int retCode, String descMsg) {
+ mProgressFragmentUtil.dismissLoadingProgress();
+
+ LogReport.getInstance().reportVideoEdit(retCode);
+
+ final UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.outputPath = VideoGenerateKit.getInstance().getVideoOutputPath();
+ ugcKitResult.coverPath = VideoGenerateKit.getInstance().getCoverPath();
+ ugcKitResult.errorCode = retCode;
+ ugcKitResult.descMsg = descMsg;
+ ugcKitResult.isPublish = VideoEditerSDK.getInstance().isPublish();
+ if (listener != null) {
+ listener.onEditCompleted(ugcKitResult);
+ }
+ }
+
+ @Override
+ public void onUICancel() {
+ // 视频编辑生成取消,UI仅去掉进度条
+ }
+ });
+ }
+
+ private void startGenerate() {
+ mProgressFragmentUtil.showLoadingProgress(new ProgressFragmentUtil.IProgressListener() {
+ @Override
+ public void onStop() {
+ PlayerManagerKit.getInstance().restartPlay();
+
+ stopGenerate();
+ }
+ });
+ PlayerManagerKit.getInstance().stopPlay();
+
+ VideoGenerateKit.getInstance().addTailWaterMark();
+ VideoGenerateKit.getInstance().startGenerate();
+ }
+
+ private void stopGenerate() {
+ VideoGenerateKit.getInstance().stopGenerate();
+ mProgressFragmentUtil.dismissLoadingProgress();
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoEffect.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoEffect.java
new file mode 100644
index 0000000..f8cf5d6
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoEffect.java
@@ -0,0 +1,198 @@
+package com.tencent.qcloud.ugckit;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentTransaction;
+import android.util.AttributeSet;
+import android.view.View;
+
+
+import com.tencent.qcloud.ugckit.module.PlayerManagerKit;
+import com.tencent.qcloud.ugckit.module.effect.AbsVideoEffectUI;
+import com.tencent.qcloud.ugckit.module.effect.ConfigureLoader;
+import com.tencent.qcloud.ugckit.module.effect.TimelineViewUtil;
+import com.tencent.qcloud.ugckit.module.effect.VideoEditerSDK;
+import com.tencent.qcloud.ugckit.utils.TelephonyUtil;
+import com.tencent.qcloud.ugckit.utils.UIAttributeUtil;
+import com.tencent.qcloud.ugckit.component.timeline.VideoProgressController;
+import com.tencent.qcloud.ugckit.module.effect.utils.DraftEditer;
+
+public class UGCKitVideoEffect extends AbsVideoEffectUI implements VideoProgressController.VideoProgressSeekListener {
+
+ private FragmentActivity mActivity;
+ private int mConfirmIcon;
+ private OnVideoEffectListener mOnVideoEffectListener;
+
+ public UGCKitVideoEffect(Context context) {
+ super(context);
+ initDefault();
+ }
+
+ public UGCKitVideoEffect(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDefault();
+ }
+
+ public UGCKitVideoEffect(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDefault();
+ }
+
+ private void initDefault() {
+ mActivity = (FragmentActivity) getContext();
+ // 当上个界面为裁剪界面,此时重置裁剪的开始时间和结束时间
+ VideoEditerSDK.getInstance().resetDuration();
+ // 加载草稿配置
+ ConfigureLoader.getInstance().loadConfigToDraft();
+
+ TelephonyUtil.getInstance().initPhoneListener();
+
+ initTitlebar();
+
+ preivewVideo();
+ }
+
+ private void preivewVideo() {
+ // 初始化图片时间轴
+ getTimelineView().initVideoProgressLayout();
+ // 初始化播放器
+ getVideoPlayLayout().initPlayerLayout();
+ // 开始播放
+ PlayerManagerKit.getInstance().startPlay();
+ }
+
+ private void initTitlebar() {
+ mConfirmIcon = UIAttributeUtil.getResResources(mActivity, R.attr.editerConfirmIcon, R.drawable.ugckit_ic_edit_effect_confirm_selector);
+ getTitleBar().getRightButton().setBackgroundResource(mConfirmIcon);
+ getTitleBar().getRightButton().setText("");
+ getTitleBar().setOnBackClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 点击"返回",清除当前设置的视频特效
+ DraftEditer.getInstance().clear();
+ // 还原已经设置给SDK的特效
+ VideoEditerSDK.getInstance().restore();
+
+ PlayerManagerKit.getInstance().stopPlay();
+
+ if (mOnVideoEffectListener != null) {
+ mOnVideoEffectListener.onEffectCancel();
+ }
+ }
+ });
+ getTitleBar().setOnRightClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // 清理时间轴的滑动事件,防止与上一级页面的播放状态冲突
+ getTimelineView().getVideoProgressView().getRecyclerView().clearOnScrollListeners();
+
+ // 点击"完成",应用当前设置的视频特效
+ ConfigureLoader.getInstance().saveConfigFromDraft();
+
+ PlayerManagerKit.getInstance().stopPlay();
+
+ if (mOnVideoEffectListener != null) {
+ mOnVideoEffectListener.onEffectApply();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void start() {
+ KeyguardManager manager = (KeyguardManager) UGCKit.getAppContext().getSystemService(Context.KEYGUARD_SERVICE);
+ if (!manager.inKeyguardRestrictedInputMode()) {
+ PlayerManagerKit.getInstance().restartPlay();
+ }
+ }
+
+ @Override
+ public void stop() {
+ PlayerManagerKit.getInstance().stopPlay();
+ }
+
+ @Override
+ public void release() {
+ PlayerManagerKit.getInstance().removeAllPreviewListener();
+ PlayerManagerKit.getInstance().removeAllPlayStateListener();
+ TelephonyUtil.getInstance().uninitPhoneListener();
+ TimelineViewUtil.getInstance().release();
+ }
+
+ @Override
+ public void setEffectType(int type) {
+ TimelineViewUtil.getInstance().setTimelineView(getTimelineView());
+ getPlayControlLayout().updateUIByFragment(type);
+ getTimelineView().updateUIByFragment(type);
+ showFragmentByType(type);
+ }
+
+ @Override
+ public void setOnVideoEffectListener(OnVideoEffectListener listener) {
+ mOnVideoEffectListener = listener;
+ }
+
+ @Override
+ public void backPressed() {
+ DraftEditer.getInstance().clear();
+ PlayerManagerKit.getInstance().stopPlay();
+
+ if (mOnVideoEffectListener != null) {
+ mOnVideoEffectListener.onEffectCancel();
+ }
+ }
+
+ @Override
+ public void onVideoProgressSeek(long currentTimeMs) {
+ PlayerManagerKit.getInstance().previewAtTime(currentTimeMs);
+ }
+
+ @Override
+ public void onVideoProgressSeekFinish(long currentTimeMs) {
+ PlayerManagerKit.getInstance().previewAtTime(currentTimeMs);
+ }
+
+ public void showFragmentByType(int type) {
+ switch (type) {
+ case UGCKitConstants.TYPE_EDITER_BGM:
+ showFragment(getMusicFragment(), "bgm_setting_fragment");
+ break;
+ case UGCKitConstants.TYPE_EDITER_MOTION:
+ showFragment(getMotionFragment(), "motion_fragment");
+ break;
+ case UGCKitConstants.TYPE_EDITER_SPEED:
+ showFragment(getTimeFragment(), "time_fragment");
+ break;
+ case UGCKitConstants.TYPE_EDITER_FILTER:
+ showFragment(getStaticFilterFragment(), "static_filter_fragment");
+ break;
+ case UGCKitConstants.TYPE_EDITER_PASTER:
+ showFragment(getPasterFragment(), "paster_fragment");
+ break;
+ case UGCKitConstants.TYPE_EDITER_SUBTITLE:
+ showFragment(getBubbleFragment(), "bubble_fragment");
+ break;
+ case UGCKitConstants.TYPE_EDITER_TRANSITION:
+ showFragment(getTransitionFragment(), "transition_fragment");
+ break;
+ }
+ }
+
+ private void showFragment(@NonNull Fragment fragment, String tag) {
+ if (fragment == getCurrentFragment()) return;
+ FragmentTransaction transaction = mActivity.getSupportFragmentManager().beginTransaction();
+ if (getCurrentFragment() != null) {
+ transaction.hide(getCurrentFragment());
+ }
+ if (!fragment.isAdded()) {
+ transaction.add(R.id.fragment_layout, fragment, tag);
+ } else {
+ transaction.show(fragment);
+ }
+ setCurrentFragment(fragment);
+ transaction.commit();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoJoin.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoJoin.java
new file mode 100644
index 0000000..4f3e2b3
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoJoin.java
@@ -0,0 +1,167 @@
+package com.tencent.qcloud.ugckit;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
+import android.util.Log;
+import android.view.View;
+
+
+import com.tencent.qcloud.ugckit.basic.UGCKitResult;
+import com.tencent.qcloud.ugckit.module.join.IVideoJoinKit;
+import com.tencent.qcloud.ugckit.utils.DialogUtil;
+import com.tencent.qcloud.ugckit.utils.LogReport;
+import com.tencent.qcloud.ugckit.utils.ToastUtil;
+import com.tencent.qcloud.ugckit.utils.VideoPathUtil;
+import com.tencent.qcloud.ugckit.component.dialogfragment.ProgressFragmentUtil;
+import com.tencent.qcloud.ugckit.module.picker.data.TCVideoFileInfo;
+
+import com.tencent.ugc.TXVideoEditConstants;
+import com.tencent.ugc.TXVideoJoiner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 腾讯云短视频UGCKit:多个视频合成组件
+ *
+ * VideoJoinKit功能:
+ * 1、调用{@link UGCKitVideoJoin#setVideoJoinList(ArrayList)} 设置多个视频路径
+ * 2、调用{@link UGCKitVideoJoin#setVideoJoinListener(IVideoJoinKit.OnVideoJoinListener)} 设置视频合成监听器
+ * {@link IVideoJoinKit.OnVideoJoinListener#onJoinCompleted(UGCKitResult)} ()} 表示视频合成完成,返回合成的视频路径
+ * {@link IVideoJoinKit.OnVideoJoinListener#onJoinCanceled()} ()} 表示当前合成视频动作取消。
+ *
+ * SDK调用步骤:
+ * 1、创建TXVideoJoiner
+ * 2、调用{@link TXVideoJoiner#setVideoPathList(List)} 设置多个视频路径
+ * 3、调用{@link TXVideoJoiner#setVideoJoinerListener(TXVideoJoiner.TXVideoJoinerListener)} 设置合成的监听器
+ * 4、调用{@link TXVideoJoiner#joinVideo(int, String)}
+ */
+public class UGCKitVideoJoin implements IVideoJoinKit, TXVideoJoiner.TXVideoJoinerListener {
+ private static final String TAG = "UGCKitVideoJoin";
+
+ private final FragmentActivity mContext;
+ private TXVideoJoiner mTXVideoJoiner;
+ private boolean mGenerateSuccess;
+ private String mOutputPath;
+ private IVideoJoinKit.OnVideoJoinListener mOnVideoJoinListener;
+ private ProgressFragmentUtil mProgressFragmentUtil;
+ private ArrayList mTCVideoFileInfoList;
+
+ public UGCKitVideoJoin(FragmentActivity context) {
+ mContext = context;
+ }
+
+ @Override
+ public void setVideoJoinList(ArrayList videoList) {
+ mTCVideoFileInfoList = videoList;
+ if (mTCVideoFileInfoList == null || mTCVideoFileInfoList.size() == 0) {
+ if (mOnVideoJoinListener != null) {
+ mOnVideoJoinListener.onJoinCanceled();
+ }
+ return;
+ }
+ startJoin();
+ }
+
+ @Override
+ public void setVideoJoinListener(IVideoJoinKit.OnVideoJoinListener videoJoinListener) {
+ mOnVideoJoinListener = videoJoinListener;
+ }
+
+ /**
+ * 开始视频合成
+ */
+ private void startJoin() {
+ ArrayList videoSourceList = convertJoinPathList();
+ mTXVideoJoiner = new TXVideoJoiner(mContext);
+ int ret = mTXVideoJoiner.setVideoPathList(videoSourceList);
+ if (ret == 0) {
+ } else if (ret == TXVideoEditConstants.ERR_UNSUPPORT_VIDEO_FORMAT) {
+ DialogUtil.showDialog(mContext, "视频合成失败", "本机型暂不支持此视频格式", null);
+ } else if (ret == TXVideoEditConstants.ERR_UNSUPPORT_AUDIO_FORMAT) {
+ DialogUtil.showDialog(mContext, "视频合成失败", "暂不支持非单双声道的视频格式", new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mOnVideoJoinListener != null) {
+ mOnVideoJoinListener.onJoinCanceled();
+ }
+ }
+ });
+ }
+ mTXVideoJoiner.setVideoJoinerListener(this);
+ mOutputPath = VideoPathUtil.generateVideoPath();
+
+ mProgressFragmentUtil = new ProgressFragmentUtil(mContext, mContext.getResources().getString(R.string.ugckit_video_joining));
+ mProgressFragmentUtil.showLoadingProgress(new ProgressFragmentUtil.IProgressListener() {
+ @Override
+ public void onStop() {
+ mProgressFragmentUtil.dismissLoadingProgress();
+ ToastUtil.toastShortMessage(mContext.getString(R.string.ugckit_cancel_joining));
+ cancelJoin();
+ }
+ });
+ mTXVideoJoiner.joinVideo(TXVideoEditConstants.VIDEO_COMPRESSED_540P, mOutputPath);
+ }
+
+ @NonNull
+ private ArrayList convertJoinPathList() {
+ ArrayList sourceList = new ArrayList();
+ for (int i = 0; i < mTCVideoFileInfoList.size(); i++) {
+ sourceList.add(mTCVideoFileInfoList.get(i).getFilePath());
+ }
+ return sourceList;
+ }
+
+ /**
+ * 取消视频合成
+ */
+ private void cancelJoin() {
+ if (!mGenerateSuccess) {
+ if (mTXVideoJoiner != null) {
+ mTXVideoJoiner.cancel();
+ }
+
+ if (mOnVideoJoinListener != null) {
+ mOnVideoJoinListener.onJoinCanceled();
+ }
+ }
+ }
+
+ /************************************************************************/
+ /***** SDK回调函数 *****/
+ /************************************************************************/
+ @Override
+ public void onJoinProgress(float progress) {
+ Log.d(TAG, "onJoinProgress = " + progress);
+ mProgressFragmentUtil.updateGenerateProgress((int) (progress * 100));
+ }
+
+ /**
+ * 功能:合成视频回调函数
+ * 您可以通过{@link com.tencent.ugc.TXVideoEditConstants.TXJoinerResult#retCode} 来判断合成成功或者失败
+ * 当retCode值为 TXVideoEditConstants.JOIN_RESULT_OK 表示生成成功
+ * 当retCode值为 TXVideoEditConstants.JOIN_RESULT_FAILED 表示生成失败,此时您可以用 {@link com.tencent.ugc.TXVideoEditConstants.TXGenerateResult#descMsg} 来获取失败原因的详细描述
+ * 当retCode值为 TXVideoEditConstants.JOIN_RESULT_LICENCE_VERIFICATION_FAILED 表示Licence校验失败
+ *
+ * @param result 返回生成视频的结果
+ */
+ @Override
+ public void onJoinComplete(@NonNull TXVideoEditConstants.TXJoinerResult result) {
+ LogReport.getInstance().reportVideoJoin(result.retCode);
+
+ mProgressFragmentUtil.dismissLoadingProgress();
+
+ if (result.retCode == TXVideoEditConstants.JOIN_RESULT_OK) {
+ if (mOnVideoJoinListener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.errorCode = result.retCode;
+ ugcKitResult.descMsg = result.descMsg;
+ ugcKitResult.outputPath = mOutputPath;
+ mOnVideoJoinListener.onJoinCompleted(ugcKitResult);
+ }
+ mGenerateSuccess = true;
+ }
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoMixRecord.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoMixRecord.java
new file mode 100644
index 0000000..e3dad70
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoMixRecord.java
@@ -0,0 +1,450 @@
+package com.tencent.qcloud.ugckit;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.tencent.qcloud.ugckit.module.mixrecord.MixRecordConfigBuildInfo;
+import com.tencent.qcloud.ugckit.utils.BackgroundTasks;
+import com.tencent.qcloud.ugckit.utils.VideoPathUtil;
+import com.tencent.liteav.demo.beauty.BeautyParams;
+import com.tencent.qcloud.ugckit.basic.ITitleBarLayout;
+import com.tencent.qcloud.ugckit.basic.OnUpdateUIListener;
+import com.tencent.qcloud.ugckit.basic.UGCKitResult;
+import com.tencent.qcloud.ugckit.component.dialog.ProgressDialogUtil;
+import com.tencent.qcloud.ugckit.component.dialogfragment.ProgressFragmentUtil;
+import com.tencent.qcloud.ugckit.module.ProcessKit;
+import com.tencent.qcloud.ugckit.module.effect.VideoEditerSDK;
+import com.tencent.qcloud.ugckit.module.mixrecord.CountDownTimerView;
+import com.tencent.qcloud.ugckit.module.mixrecord.ICountDownTimerView;
+import com.tencent.qcloud.ugckit.module.mixrecord.IMixRecordRightLayout;
+import com.tencent.qcloud.ugckit.module.mixrecord.AbsVideoTripleMixRecordUI;
+import com.tencent.qcloud.ugckit.module.mixrecord.IMixRecordJoinListener;
+import com.tencent.qcloud.ugckit.module.mixrecord.MixRecordActionData;
+import com.tencent.qcloud.ugckit.module.mixrecord.MixRecordConfig;
+import com.tencent.qcloud.ugckit.module.mixrecord.MixRecordJoiner;
+import com.tencent.qcloud.ugckit.module.record.AudioFocusManager;
+import com.tencent.qcloud.ugckit.module.record.RecordButton;
+import com.tencent.qcloud.ugckit.module.record.ScrollFilterView;
+import com.tencent.qcloud.ugckit.module.record.UGCKitRecordConfig;
+import com.tencent.qcloud.ugckit.module.record.VideoRecordSDK;
+import com.tencent.qcloud.ugckit.utils.DialogUtil;
+import com.tencent.qcloud.ugckit.utils.LogReport;
+import com.tencent.ugc.TXRecordCommon;
+import com.tencent.ugc.TXUGCRecord;
+import com.tencent.ugc.TXVideoEditConstants;
+import com.tencent.ugc.TXVideoInfoReader;
+
+import java.util.List;
+
+public class UGCKitVideoMixRecord extends AbsVideoTripleMixRecordUI implements IMixRecordRightLayout.OnItemClickListener, RecordButton.OnRecordButtonListener,
+ ScrollFilterView.OnRecordFilterListener, VideoRecordSDK.OnVideoRecordListener,
+ IMixRecordJoinListener {
+ private static final String TAG = "UGCKitVideoTripleRecord";
+ private OnMixRecordListener mOnMixRecordListener;
+ private ProgressDialogUtil mProgressDialogUtil;
+ private ProgressFragmentUtil mProgressFragmentUtil;
+ private FragmentActivity mActivity;
+ private MixRecordJoiner mJoiner;
+ private MixRecordConfig mConfig;
+
+ public UGCKitVideoMixRecord(Context context) {
+ super(context);
+ initDefault();
+ }
+
+ public UGCKitVideoMixRecord(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDefault();
+ }
+
+ public UGCKitVideoMixRecord(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDefault();
+ }
+
+ private void initDefault() {
+ mActivity = (FragmentActivity) getContext();
+ // 初始化SDK:TXUGCRecord
+ VideoRecordSDK.getInstance().initSDK();
+ VideoRecordSDK.getInstance().setVideoRecordListener(this);
+
+ // 设置默认的录制模式
+ getFollowRecordBottomLayout().getRecordButton().setCurrentRecordMode(UGCKitRecordConfig.getInstance().mRecordMode);
+
+
+ // 点击"右侧工具栏"(包括"美颜","倒计时")
+ getFollowRecordRightLayout().setOnItemClickListener(this);
+
+ // 点击"录制按钮"(包括"单击拍","按住拍")
+ getFollowRecordBottomLayout().setOnRecordButtonListener(this);
+
+ getScrollFilterView().setOnRecordFilterListener(this);
+
+ mProgressDialogUtil = new ProgressDialogUtil(mActivity);
+ mJoiner = new MixRecordJoiner(getContext());
+ getTitleBar().setOnRightClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MixRecordActionData data = new MixRecordActionData();
+ mOnMixRecordListener.onMixRecordAction(MixRecordActionT.MIX_RECORD_ACTION_T_SELECT, data);
+ }
+ });
+
+ TXUGCRecord txugcRecord = VideoRecordSDK.getInstance().getRecorder();
+ getBeautyPanel().setBeautyManager(txugcRecord.getBeautyManager());
+ }
+
+ @Override
+ public void start() {
+ // 打开合唱预览界面
+ VideoRecordSDK.getInstance().startCameraPreview(getPlayViews().getVideoView());
+ // 播放跟拍视频
+ getPlayViews().startVideo();
+ }
+
+ @Override
+ public void stop() {
+ Log.d(TAG, "stop");
+ getFollowRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ getFollowRecordBottomLayout().closeTorch();
+ // 停止录制预览界面
+ VideoRecordSDK.getInstance().stopCameraPreview();
+ // 暂停录制
+ VideoRecordSDK.getInstance().pauseRecord();
+ // 停止播放跟拍视频
+ getPlayViews().stopVideo();
+ }
+
+ @Override
+ public void release() {
+ getFollowRecordBottomLayout().getRecordProgressView().release();
+ // 停止录制
+ VideoRecordSDK.getInstance().releaseRecord();
+ getPlayViews().releaseVideo();
+
+ // 录制TXUGCRecord是单例,需要释放时还原配置
+ getBeautyPanel().clear();
+ AudioFocusManager.getInstance().setAudioFocusListener(null);
+ VideoRecordSDK.getInstance().setVideoRecordListener(null);
+ }
+
+ @Override
+ public void setMixRecordInfo(MixRecordConfigBuildInfo buildInfo) {
+ MixRecordConfig info = new MixRecordConfig();
+ info.setInfo(buildInfo.getVideoPaths(), buildInfo.getRecordIndex(), buildInfo.getWidth(), buildInfo.getHeight(), buildInfo.getRecordRatio());
+ mConfig = info;
+ mConfig.mBeautyParams = new BeautyParams();
+ mConfig.mIsMute = buildInfo.isMute();
+ // 设置默认美颜
+ mConfig.mBeautyParams.mBeautyStyle = 0;
+ mConfig.mBeautyParams.mBeautyLevel = 4;
+ mConfig.mBeautyParams.mWhiteLevel = 1;
+ mConfig.mRenderMode = TXRecordCommon.VIDEO_RENDER_MODE_FULL_FILL_SCREEN;
+ mConfig.mAECType = buildInfo.getAecType();
+
+ List paths = mConfig.getPaths();
+ for (int i = 0; i < paths.size(); i++) {
+ getPlayViews().init(i, paths.get(i));
+ }
+ // 初始化最大/最小视频录制时长
+ getFollowRecordBottomLayout().setDuration(mConfig.mMinDuration, mConfig.mMaxDuration);
+ // 初始化默认配置
+ VideoRecordSDK.getInstance().initConfig(mConfig);
+ VideoRecordSDK.getInstance().updateBeautyParam(mConfig.mBeautyParams);
+ }
+
+ @Override
+ public void screenOrientationChange() {
+ Log.d(TAG, "screenOrientationChange");
+ VideoRecordSDK.getInstance().stopCameraPreview();
+
+ VideoRecordSDK.getInstance().pauseRecord();
+
+ VideoRecordSDK.getInstance().startCameraPreview(getPlayViews().getVideoView());//
+ }
+
+ @Override
+ public void backPressed() {
+ Log.d(TAG, "backPressed");
+ // 录制已停止,则回调"录制被取消"
+ if (VideoRecordSDK.getInstance().getRecordState() == VideoRecordSDK.STATE_STOP) {
+ getPlayViews().releaseVideo();
+
+ if (getCountDownTimerView() != null) {
+ getCountDownTimerView().cancelDownAnimation();
+ }
+
+ if (mOnMixRecordListener != null) {
+ mOnMixRecordListener.onMixRecordCanceled();
+ }
+ return;
+ }
+ // 录制已开始,点击返回键,暂停录制
+ if (VideoRecordSDK.getInstance().getRecordState() == VideoRecordSDK.STATE_START) {
+ VideoRecordSDK.getInstance().pauseRecord();
+ }
+
+ if (mOnMixRecordListener != null) {
+ mOnMixRecordListener.onMixRecordCanceled();
+ }
+ }
+
+ @Override
+ public void setOnMixRecordListener(OnMixRecordListener listener) {
+ mOnMixRecordListener = listener;
+ }
+
+ @Override
+ public void updateMixFile(int index, String filePath) {
+ if (mConfig != null) {
+ mConfig.updateInfo(index, filePath);
+ getFollowRecordBottomLayout().setDuration(mConfig.mMinDuration, mConfig.mMaxDuration);
+ }
+ getPlayViews().updateFile(index, filePath);
+ }
+
+ /**
+ * 点击录制开始按钮
+ */
+ @Override
+ public void onRecordStart() {
+ Log.d(TAG, "onRecordStart");
+ getTitleBar().setVisible(false, ITitleBarLayout.POSITION.RIGHT);
+ getFollowRecordBottomLayout().disableFunction();
+ getFollowRecordRightLayout().setVisibility(View.INVISIBLE);
+
+ int retCode = VideoRecordSDK.getInstance().startRecord();
+ if (retCode == VideoRecordSDK.START_RECORD_FAIL) { //点击开始录制失败,录制按钮状态变为暂停
+ getFollowRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ return;
+ }
+ if (VideoRecordSDK.getInstance().getPartManager().getPartsPathList().size() == 0) {
+ getPlayViews().seekVideo(0);
+ }
+ getPlayViews().startVideo();
+
+ AudioFocusManager.getInstance().setAudioFocusListener(new AudioFocusManager.OnAudioFocusListener() {
+ @Override
+ public void onAudioFocusChange() {
+ getFollowRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ }
+ });
+ AudioFocusManager.getInstance().requestAudioFocus();
+ }
+
+ /**
+ * 点击录制暂停按钮
+ */
+ @Override
+ public void onRecordPause() {
+ Log.d(TAG, "onRecordPause");
+ getFollowRecordBottomLayout().enableFunction();
+ getFollowRecordRightLayout().setVisibility(View.VISIBLE);
+
+ VideoRecordSDK.getInstance().pauseRecord();
+ getPlayViews().pauseVideo();
+
+ AudioFocusManager.getInstance().abandonAudioFocus();
+ }
+
+ @Override
+ public void onTakePhoto() {
+
+ }
+
+ @Override
+ public void onDeleteParts(int partsSize, long duration) {
+ if (partsSize == 0) { //可以编辑
+ getTitleBar().setVisible(true, ITitleBarLayout.POSITION.RIGHT);
+ }
+ //seek to duration
+ getPlayViews().seekVideo(duration);
+ }
+
+ @Override
+ public void onShowBeautyPanel() {
+ // 隐藏底部工具栏
+ getFollowRecordBottomLayout().setVisibility(View.GONE);
+ // 隐藏右侧工具栏
+ getFollowRecordRightLayout().setVisibility(View.GONE);
+ // 显示美颜Pannel
+ getBeautyPanel().setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void countDownTimer() {
+ getFollowRecordRightLayout().setVisibility(View.GONE);
+ getFollowRecordBottomLayout().setVisibility(View.GONE);
+
+ getCountDownTimerView().setOnCountDownListener(new ICountDownTimerView.ICountDownListener() {
+ @Override
+ public void onCountDownComplete() {
+ getFollowRecordBottomLayout().getRecordButton().startRecordAnim();
+ getFollowRecordRightLayout().setVisibility(View.VISIBLE);
+ getFollowRecordBottomLayout().setVisibility(View.VISIBLE);
+ }
+ });
+ getCountDownTimerView().countDownAnimation(CountDownTimerView.DEFAULT_COUNTDOWN_NUMBER);
+ }
+
+ @Override
+ public void onSingleClick(float x, float y) {
+ getBeautyPanel().setVisibility(View.GONE);
+ getFollowRecordBottomLayout().setVisibility(View.VISIBLE);
+ getFollowRecordRightLayout().setVisibility(View.VISIBLE);
+
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ record.setFocusPosition(x, y);
+ }
+ }
+
+ @Override
+ public void onRecordProgress(long milliSecond) {
+ getFollowRecordBottomLayout().updateProgress(milliSecond);
+ }
+
+ @Override
+ public void onRecordEvent(int event) {
+ getFollowRecordBottomLayout().getRecordProgressView().clipComplete();
+ }
+
+ @Override
+ public void onRecordComplete(@NonNull final TXRecordCommon.TXRecordResult result) {
+ Log.d(TAG, "onRecordComplete result:" + result.retCode);
+
+ LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VIDEO_RECORD, result.retCode, result.descMsg);
+
+ if (result.retCode >= 0) {
+ getFollowRecordBottomLayout().getRecordButton().pauseRecordAnim();
+
+ mProgressDialogUtil.showProgressDialog();
+ mProgressDialogUtil.setProgressDialogMessage(getResources().getString(R.string.ugckit_video_record_activity_on_record_complete_synthesizing));
+
+ mConfig.setRecordPath(result.videoPath);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // 开始合成
+ MixRecordJoiner.JoinerParams params = new MixRecordJoiner.JoinerParams();
+ params.videoSourceList = mConfig.getPaths();
+ params.mCavasWith = mConfig.getWidth();
+ params.mCavasHeight = mConfig.getHeight();
+ params.mRects = getPlayViews().getCombineRects(mConfig);
+ params.mVideoOutputPath = VideoPathUtil.getCustomVideoOutputPath("Triple_Shot_");
+ MixRecordJoiner joiner = new MixRecordJoiner(getContext());
+ params.mVolumes = mConfig.getVolumes();
+ joiner.setmListener(UGCKitVideoMixRecord.this);
+ joiner.setRecordPath(result.videoPath);
+ joiner.joinVideo(params);
+ }
+ }, "MixRecordT").start();
+ }
+ }
+
+ @Override
+ public void onChorusProgress(float progress) {
+ int progressInt = (int) (progress * 100);
+ mProgressDialogUtil.setProgressDialogMessage(getResources().getString(R.string.ugckit_video_record_activity_on_join_progress_synthesizing) + progressInt + "%");
+ }
+
+ @Override
+ public void onChorusCompleted(String outputPath, boolean success) {
+ Log.d(TAG, "onMixRecordCompleted outputPath:" + outputPath);
+ mProgressDialogUtil.dismissProgressDialog();
+
+ if (!success) {
+ return;
+ }
+
+ boolean editFlag = mConfig.mIsNeedEdit;
+ Log.d(TAG, "onMixRecordCompleted editFlag:" + editFlag);
+ if (editFlag) {
+ startPreprocess(outputPath);
+ } else {
+ if (mOnMixRecordListener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.errorCode = 0;
+ ugcKitResult.outputPath = outputPath;
+
+ mOnMixRecordListener.onMixRecordCompleted(ugcKitResult);
+ }
+ }
+ }
+
+ private void startPreprocess(final String videoPath) {
+ mProgressFragmentUtil = new ProgressFragmentUtil(mActivity);
+ mProgressFragmentUtil.showLoadingProgress(new ProgressFragmentUtil.IProgressListener() {
+ @Override
+ public void onStop() {
+ mProgressFragmentUtil.dismissLoadingProgress();
+
+ ProcessKit.getInstance().stopProcess();
+ }
+ });
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ loadVideoInfo(videoPath);
+ }
+ }, "MixRecord_preprocess").start();
+ }
+
+ private void loadVideoInfo(String videoPath) {
+ // 加载视频信息
+ TXVideoEditConstants.TXVideoInfo info = TXVideoInfoReader.getInstance(UGCKit.getAppContext()).getVideoFileInfo(videoPath);
+ if (info == null) {
+ BackgroundTasks.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ DialogUtil.showDialog(mActivity, getResources().getString(R.string.ugckit_video_preprocess_activity_edit_failed),
+ getResources().getString(R.string.ugckit_video_preprocess_activity_does_not_support_android_version_below_4_3), null);
+ }
+ });
+ } else {
+ // 设置视频基本信息
+ VideoEditerSDK.getInstance().initSDK();
+ VideoEditerSDK.getInstance().getEditer().setVideoPath(videoPath);
+ VideoEditerSDK.getInstance().setTXVideoInfo(info);
+ VideoEditerSDK.getInstance().setVideoDuration(info.duration);
+ VideoEditerSDK.getInstance().setCutterStartTime(0, info.duration);
+ // 开始视频预处理,产生录制的缩略图
+ ProcessKit.getInstance().startProcess();
+ ProcessKit.getInstance().setOnUpdateUIListener(new OnUpdateUIListener() {
+ @Override
+ public void onUIProgress(float progress) {
+ mProgressFragmentUtil.updateGenerateProgress((int) (progress * 100));
+ }
+
+ @Override
+ public void onUIComplete(int retCode, String desc) {
+ // 更新UI控件
+ mProgressFragmentUtil.dismissLoadingProgress();
+ if (mOnMixRecordListener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.errorCode = 0;
+ mOnMixRecordListener.onMixRecordCompleted(ugcKitResult);
+ }
+ }
+
+ @Override
+ public void onUICancel() {
+ // 更新Activity
+ if (mOnMixRecordListener != null) {
+ mOnMixRecordListener.onMixRecordCanceled();
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public void setEditVideoFlag(boolean enable) {
+ mConfig.mIsNeedEdit = enable;
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoPicker.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoPicker.java
new file mode 100755
index 0000000..5365b8b
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoPicker.java
@@ -0,0 +1,115 @@
+package com.tencent.qcloud.ugckit;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Handler;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import android.util.AttributeSet;
+
+import com.tencent.qcloud.ugckit.basic.ITitleBarLayout;
+import com.tencent.qcloud.ugckit.module.picker.data.ItemView;
+import com.tencent.qcloud.ugckit.module.picker.data.PickerManagerKit;
+import com.tencent.qcloud.ugckit.module.picker.data.TCVideoFileInfo;
+import com.tencent.qcloud.ugckit.module.picker.view.AbsPickerUI;
+import com.tencent.qcloud.ugckit.module.picker.view.PickedLayout;
+
+import java.util.ArrayList;
+
+/**
+ * Module:视频选择
+ */
+public class UGCKitVideoPicker extends AbsPickerUI implements ActivityCompat.OnRequestPermissionsResultCallback {
+ private Activity mActivity;
+ @NonNull
+ private Handler mHandlder = new Handler();
+
+ public UGCKitVideoPicker(Context context) {
+ super(context);
+ initDefault();
+ }
+
+ public UGCKitVideoPicker(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDefault();
+ }
+
+ public UGCKitVideoPicker(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDefault();
+ }
+
+ @Override
+ public void initDefault() {
+ // 设置标题
+ getTitleBar().setTitle(getResources().getString(R.string.ugckit_video_choose), ITitleBarLayout.POSITION.MIDDLE);
+ getTitleBar().setVisible(false, ITitleBarLayout.POSITION.RIGHT);
+ getPickerListLayout().setOnItemAddListener(new ItemView.OnAddListener() {
+ @Override
+ public void onAdd(TCVideoFileInfo fileInfo) {
+ // 选中一个视频
+ getPickedLayout().addItem(fileInfo);
+ }
+ });
+ // 加载视频
+ loadVideoList();
+ }
+
+ private void loadVideoList() {
+ mActivity = (Activity) getContext();
+ if (ContextCompat.checkSelfPermission(mActivity, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ mHandlder.post(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList list = PickerManagerKit.getInstance(mActivity).getAllVideo();
+ getPickerListLayout().updateItems(list);
+ }
+ });
+ } else {
+ if (Build.VERSION.SDK_INT >= 23) {
+ ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
+ }
+ }
+ }
+
+ /**
+ * Glide停止加载图片
+ */
+ @Override
+ public void pauseRequestBitmap() {
+ getPickerListLayout().pauseRequestBitmap();
+ }
+
+ /**
+ * Glide继续加载图片
+ */
+ @Override
+ public void resumeRequestBitmap() {
+ getPickerListLayout().resumeRequestBitmap();
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (grantResults != null && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ loadVideoList();
+ }
+ }
+
+ @Override
+ public void setOnPickerListener(@Nullable final OnPickerListener listener) {
+ getPickedLayout().setOnNextStepListener(new PickedLayout.OnNextStepListener() {
+ @Override
+ public void onNextStep() {
+ if (listener != null) {
+ ArrayList arrayList = getPickedLayout().getSelectItems(PickedLayout.TYPE_VIDEO);
+ listener.onPickedList(arrayList);
+ }
+ }
+ });
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoPublish.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoPublish.java
new file mode 100644
index 0000000..ffc3d14
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoPublish.java
@@ -0,0 +1,394 @@
+package com.tencent.qcloud.ugckit;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.tencent.qcloud.ugckit.utils.TCUserMgr;
+import com.tencent.qcloud.ugckit.module.upload.TXUGCPublish;
+import com.tencent.qcloud.ugckit.module.upload.TXUGCPublishTypeDef;
+import com.tencent.qcloud.ugckit.utils.BackgroundTasks;
+import com.tencent.qcloud.ugckit.utils.LogReport;
+import com.tencent.qcloud.ugckit.utils.NetworkUtil;
+import com.tencent.qcloud.ugckit.utils.ToastUtil;
+
+import org.greenrobot.eventbus.EventBus;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+
+public class UGCKitVideoPublish extends RelativeLayout implements View.OnClickListener, TXUGCPublishTypeDef.ITXVideoPublishListener {
+ @NonNull
+ private String TAG = "UGCKitVideoPublish";
+
+ private Context mContext;
+ private ImageView mImageBack; // 返回
+ private ImageView mImageViewBg;
+ private ProgressBar mProgressBar; // 发布视频进度条
+ private TextView mTextProgress; // 发布视频进度文字
+ @Nullable
+ private TXUGCPublish mVideoPublish = null;
+ private boolean mIsFetchCosSig = false;
+ @Nullable
+ private String mCosSignature = null;
+ @NonNull
+ private Handler mHandler = new Handler();
+ private boolean mAllDone = false;
+ private boolean mDisableCache;
+ private String mLocalVideoPath;
+ @Nullable
+ private String mVideoPath = null; // 视频路径
+ @Nullable
+ private String mCoverPath = null; // 视频封面路径
+ private OnPublishListener mOnPublishListener;
+
+ public UGCKitVideoPublish(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public UGCKitVideoPublish(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public UGCKitVideoPublish(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ /**
+ * 功能:
+ * 1、UGCKit控件初始化
+ * 2、加载视频封面
+ * 3、发布视频
+ *
+ * @param context
+ */
+ private void init(Context context) {
+ mContext = context;
+ inflate(getContext(), R.layout.ugckit_publish_video_layout, this);
+
+ mImageBack = (ImageView) findViewById(R.id.btn_back);
+ mImageBack.setOnClickListener(this);
+
+ mProgressBar = (ProgressBar) findViewById(R.id.progressbar);
+ mTextProgress = (TextView) findViewById(R.id.tv_progress);
+ mImageViewBg = (ImageView) findViewById(R.id.bg_iv);
+
+ publishVideo();
+ }
+
+ /**
+ * 检测有网络时获取签名
+ */
+ private void publishVideo() {
+ if (mAllDone) {
+// Intent intent = new Intent(TCVideoPublisherActivity.this, TCMainActivity.class);
+// startActivity(intent);
+ } else {
+ if (!NetworkUtil.isNetworkAvailable(mContext)) {
+ ToastUtil.toastShortMessage(getResources().getString(R.string.ugckit_video_publisher_activity_no_network_connection));
+ return;
+ }
+ fetchSignature();
+ }
+ }
+
+ /**
+ * Step1:获取签名
+ */
+ private void fetchSignature() {
+ if (mIsFetchCosSig) {
+ return;
+ }
+ mIsFetchCosSig = true;
+
+ TCUserMgr.getInstance().getVodSig(new TCUserMgr.Callback() {
+ @Override
+ public void onSuccess(@NonNull JSONObject data) {
+ try {
+ mCosSignature = data.getString("signature");
+ startPublish();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ /**
+ * ELK数据上报:获取签名成功
+ */
+ LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VIDEO_SIGN, TCUserMgr.SUCCESS_CODE, "获取签名成功");
+ }
+
+ @Override
+ public void onFailure(int code, final String msg) {
+ /**
+ * ELK数据上报:获取签名失败
+ */
+ LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VIDEO_SIGN, code, "获取签名失败");
+ }
+ });
+ }
+
+ /**
+ * Step2:开始发布视频(子线程)
+ */
+ private void startPublish() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mVideoPublish == null) {
+ mVideoPublish = new TXUGCPublish(UGCKit.getAppContext(), TCUserMgr.getInstance().getUserId());
+ }
+ /**
+ * 设置视频发布监听器
+ */
+ mVideoPublish.setListener(UGCKitVideoPublish.this);
+
+ TXUGCPublishTypeDef.TXPublishParam param = new TXUGCPublishTypeDef.TXPublishParam();
+ param.signature = mCosSignature;
+ param.videoPath = mVideoPath;
+ param.coverPath = mCoverPath;
+ int publishCode = mVideoPublish.publishVideo(param);
+// if (publishCode != 0) {
+// mTVPublish.setText("发布失败,错误码:" + publishCode);
+// }
+ NetworkUtil.getInstance(UGCKit.getAppContext()).setNetchangeListener(new NetworkUtil.NetchangeListener() {
+ @Override
+ public void onNetworkAvailable() {
+ mTextProgress.setText(getResources().getString(R.string.ugckit_video_publisher_activity_network_connection_is_disconnected_video_upload_failed));
+ }
+ });
+ NetworkUtil.getInstance(UGCKit.getAppContext()).registerNetChangeReceiver();
+ }
+ });
+ }
+
+ /**
+ * 设置发布视频的路径和封面
+ * 注意:请检查路径是否正确
+ *
+ * @param videoPath 视频的路径
+ * @param coverPath 封面的路径
+ */
+ public void setPublishPath(String videoPath, String coverPath) {
+ mVideoPath = videoPath;
+ mCoverPath = coverPath;
+
+ loadCoverImage();
+ }
+
+ /**
+ * 开启本地缓存,若关闭本地缓存,则发布完成后删除"已发布"的视频和封面
+ *
+ * @param disableCache {@code true} 开启本地缓存,设置的视频文件和封面文件不会被删除。
+ * {@code false} 关闭本地缓存,则发布完成后删除"已发布"的视频和封面;
+ * 默认为true
+ */
+ public void setCacheEnable(boolean disableCache) {
+ mDisableCache = disableCache;
+ }
+
+ /**
+ * 设置发布视频的监听器
+ *
+ * @param onUIClickListener
+ */
+ public void setOnPublishListener(OnPublishListener onUIClickListener) {
+ mOnPublishListener = onUIClickListener;
+ }
+
+ @Override
+ public void onClick(@NonNull View view) {
+ int i = view.getId();
+ if (i == R.id.btn_back) {
+ showCancelPublishDialog();
+ }
+ }
+
+ /**
+ * 加载视频封面
+ */
+ private void loadCoverImage() {
+ if (mCoverPath != null) {
+ Glide.with(mContext)
+ .load(Uri.fromFile(new File(mCoverPath)))
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+ .skipMemoryCache(true)
+ .into(mImageViewBg);
+ }
+ }
+
+ /**
+ * 显示取消发布的Dialog
+ */
+ private void showCancelPublishDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
+ AlertDialog alertDialog = builder.setTitle(mContext.getString(R.string.ugckit_cancel_publish_title)).setCancelable(false).setMessage(R.string.ugckit_cancel_publish_msg)
+ .setPositiveButton(R.string.ugckit_cancel_publish_title, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ if (mVideoPublish != null) {
+ mVideoPublish.canclePublish();
+ }
+ dialog.dismiss();
+ if (mOnPublishListener != null) {
+ mOnPublishListener.onPublishCancel();
+ }
+ }
+ })
+ .setNegativeButton(mContext.getString(R.string.ugckit_wrong_click), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ }).create();
+ alertDialog.show();
+ }
+
+ /**
+ * 视频发布进度
+ *
+ * @param uploadBytes
+ * @param totalBytes
+ */
+ @Override
+ public void onPublishProgress(long uploadBytes, long totalBytes) {
+ int progress = (int) (uploadBytes * 100 / totalBytes);
+ Log.d(TAG, "onPublishProgress:" + progress);
+ mProgressBar.setProgress(progress);
+ mTextProgress.setText(getResources().getString(R.string.ugckit_video_publisher_activity_is_uploading) + progress + "%");
+ }
+
+ /**
+ * 视频发布结果回调
+ * 当视频发布成功后,发布到点播系统,此时就可以在视频列表看到"已发布的视频"
+ *
+ * @param publishResult
+ */
+ @Override
+ public void onPublishComplete(@NonNull TXUGCPublishTypeDef.TXPublishResult publishResult) {
+ Log.d(TAG, "onPublishComplete:" + publishResult.retCode);
+
+ /**
+ * ELK数据上报:视频发布到点播系统
+ */
+ LogReport.getInstance().reportPublishVideo(publishResult);
+
+ if (publishResult.retCode == TXUGCPublishTypeDef.PUBLISH_RESULT_OK) {
+ mImageBack.setVisibility(View.GONE);
+ UploadUGCVideo(publishResult.videoId, publishResult.videoURL, publishResult.coverURL);
+ } else {
+ if (publishResult.descMsg.contains("java.net.UnknownHostException") || publishResult.descMsg.contains("java.net.ConnectException")) {
+ mTextProgress.setText(mContext.getResources().getString(R.string.ugckit_video_publisher_activity_network_connection_is_disconnected_video_upload_failed));
+ } else {
+ mTextProgress.setText(publishResult.descMsg);
+ }
+ Log.e(TAG, publishResult.descMsg);
+ }
+ }
+
+ /**
+ * 发布视频后删除本地缓存的视频和封面
+ */
+ private void deleteCache() {
+ if (mDisableCache) {
+ File file = new File(mVideoPath);
+ if (file.exists()) {
+ file.delete();
+ }
+ if (!TextUtils.isEmpty(mCoverPath)) {
+ file = new File(mCoverPath);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+ if (mLocalVideoPath != null) {
+ Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ scanIntent.setData(Uri.fromFile(new File(mLocalVideoPath)));
+ mContext.sendBroadcast(scanIntent);
+ }
+ }
+ }
+
+ /**
+ * 发布到服务器
+ *
+ * @param videoId
+ * @param videoURL
+ * @param coverURL
+ */
+ private void UploadUGCVideo(final String videoId, final String videoURL, final String coverURL) {
+ String title = null; //TODO:传入本地视频文件名称
+ if (TextUtils.isEmpty(title)) {
+ title = "小视频";
+ }
+ try {
+ JSONObject body = new JSONObject().put("file_id", videoId)
+ .put("title", title)
+ .put("frontcover", coverURL)
+ .put("location", "未知")
+ .put("play_url", videoURL);
+ TCUserMgr.getInstance().request("/upload_ugc", body, new TCUserMgr.HttpCallback("upload_ugc", new TCUserMgr.Callback() {
+ @Override
+ public void onSuccess(JSONObject data) {
+ /**
+ * ELK上报:发布视频到服务器
+ */
+ LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VIDEO_UPLOAD_SERVER, TCUserMgr.SUCCESS_CODE, "UploadUGCVideo Sucess");
+
+ BackgroundTasks.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ EventBus.getDefault().post(UGCKitConstants.EVENT_MSG_PUBLISH_DONE);
+
+ if (mOnPublishListener != null) {
+ mOnPublishListener.onPublishComplete();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(int code, final String msg) {
+ /**
+ * ELK上报:发布视频到服务器
+ */
+ LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VIDEO_UPLOAD_SERVER, code, msg);
+ }
+ }));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void release() {
+ NetworkUtil.getInstance(UGCKit.getAppContext()).unregisterNetChangeReceiver();
+
+ deleteCache();
+ }
+
+
+ public interface OnPublishListener {
+
+ void onPublishComplete();
+
+ void onPublishCancel();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoRecord.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoRecord.java
new file mode 100644
index 0000000..3e7ced3
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/UGCKitVideoRecord.java
@@ -0,0 +1,726 @@
+package com.tencent.qcloud.ugckit;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.tencent.liteav.demo.beauty.Beauty;
+import com.tencent.liteav.demo.beauty.BeautyParams;
+import com.tencent.liteav.demo.beauty.model.ItemInfo;
+import com.tencent.liteav.demo.beauty.model.TabInfo;
+import com.tencent.liteav.demo.beauty.view.BeautyPanel;
+import com.tencent.qcloud.ugckit.basic.ITitleBarLayout;
+import com.tencent.qcloud.ugckit.basic.OnUpdateUIListener;
+import com.tencent.qcloud.ugckit.basic.UGCKitResult;
+import com.tencent.qcloud.ugckit.component.dialog.ProgressDialogUtil;
+import com.tencent.qcloud.ugckit.component.dialogfragment.ProgressFragmentUtil;
+import com.tencent.qcloud.ugckit.module.ProcessKit;
+import com.tencent.qcloud.ugckit.module.effect.VideoEditerSDK;
+import com.tencent.qcloud.ugckit.module.effect.bgm.view.SoundEffectsPannel;
+import com.tencent.qcloud.ugckit.module.record.AbsVideoRecordUI;
+import com.tencent.qcloud.ugckit.module.record.AudioFocusManager;
+import com.tencent.qcloud.ugckit.module.record.MusicInfo;
+import com.tencent.qcloud.ugckit.module.record.PhotoSoundPlayer;
+import com.tencent.qcloud.ugckit.module.record.RecordBottomLayout;
+import com.tencent.qcloud.ugckit.module.record.RecordModeView;
+import com.tencent.qcloud.ugckit.module.record.RecordMusicManager;
+import com.tencent.qcloud.ugckit.module.record.ScrollFilterView;
+import com.tencent.qcloud.ugckit.module.record.UGCKitRecordConfig;
+import com.tencent.qcloud.ugckit.module.record.VideoRecordSDK;
+import com.tencent.qcloud.ugckit.module.record.interfaces.IRecordButton;
+import com.tencent.qcloud.ugckit.module.record.interfaces.IRecordMusicPannel;
+import com.tencent.qcloud.ugckit.module.record.interfaces.IRecordRightLayout;
+import com.tencent.qcloud.ugckit.utils.BackgroundTasks;
+import com.tencent.qcloud.ugckit.utils.DialogUtil;
+import com.tencent.qcloud.ugckit.utils.LogReport;
+import com.tencent.qcloud.ugckit.utils.TelephonyUtil;
+import com.tencent.ugc.TXRecordCommon;
+import com.tencent.ugc.TXUGCRecord;
+import com.tencent.ugc.TXVideoEditConstants;
+import com.tencent.ugc.TXVideoInfoReader;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class UGCKitVideoRecord extends AbsVideoRecordUI implements
+ IRecordRightLayout.OnItemClickListener,
+ IRecordButton.OnRecordButtonListener,
+ SoundEffectsPannel.SoundEffectsSettingPannelListener,
+ IRecordMusicPannel.MusicChangeListener,
+ ScrollFilterView.OnRecordFilterListener,
+ VideoRecordSDK.OnVideoRecordListener {
+ private static final String TAG = "UGCKitVideoRecord";
+
+ private OnRecordListener mOnRecordListener;
+ private OnMusicChooseListener mOnMusicListener;
+ private FragmentActivity mActivity;
+ private ProgressFragmentUtil mProgressFragmentUtil;
+ private ProgressDialogUtil mProgressDialogUtil;
+ private boolean isInStopProcessing = false;
+ private ExecutorService videoProcessExecutor;
+
+ public UGCKitVideoRecord(Context context) {
+ super(context);
+ initDefault(context);
+ }
+
+ public UGCKitVideoRecord(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initDefault(context);
+ }
+
+ public UGCKitVideoRecord(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initDefault(context);
+ }
+
+ private void initDefault(Context context) {
+ mActivity = (FragmentActivity) getContext();
+ // 初始化SDK:TXUGCRecord
+ VideoRecordSDK.getInstance().initSDK();
+ // 初始化视频草稿箱
+ VideoRecordSDK.getInstance().initRecordDraft(context);
+ VideoRecordSDK.getInstance().setOnRestoreDraftListener(new VideoRecordSDK.OnRestoreDraftListener() {
+ @Override
+ public void onDraftProgress(long duration) {
+ getRecordBottomLayout().updateProgress((int) duration);
+ getRecordBottomLayout().getRecordProgressView().clipComplete();
+ }
+
+ @Override
+ public void onDraftTotal(long duration) {
+ getRecordRightLayout().setMusicIconEnable(false);
+ getRecordRightLayout().setAspectIconEnable(false);
+
+ float second = duration / 1000f;
+ boolean enable = second >= UGCKitRecordConfig.getInstance().mMinDuration / 1000;
+ getTitleBar().setVisible(enable, ITitleBarLayout.POSITION.RIGHT);
+ }
+ });
+
+ VideoRecordSDK.getInstance().setVideoRecordListener(this);
+ // 点击"下一步"
+ getTitleBar().setVisible(false, ITitleBarLayout.POSITION.RIGHT);
+ getTitleBar().setOnRightClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ //录制stop状态,由于stop的过程比较长,可长达一秒以上,做防重复点击的最小点击时间就需要设置的比较长。
+ //使用录制的currentState状态来判断是否是STOP状态,虽然可以完美解决防重复点击问题,但是如果用户按返回回到该界面,
+ //无法再次点击下一步,currentState状态仍然是stop。
+ //所以这里采用一个新的布尔值进行限制
+ if(isInStopProcessing) {
+ return;
+ }
+ isInStopProcessing = true;
+ mProgressDialogUtil.showProgressDialog();
+
+ VideoRecordSDK.getInstance().stopRecord();
+ }
+ });
+
+ // 点击"右侧工具栏"(包括"美颜","音乐","音效")
+ getRecordRightLayout().setOnItemClickListener(this);
+
+ // 点击"录制按钮"(包括"拍照","单击拍","按住拍")
+ getRecordBottomLayout().setOnRecordButtonListener(this);
+ getRecordBottomLayout().setOnDeleteLastPartListener(new RecordBottomLayout.OnDeleteLastPartListener() {
+ @Override
+ public void onUpdateTitle(boolean enable) {
+ getTitleBar().setVisible(enable, ITitleBarLayout.POSITION.RIGHT);
+ getRecordPauseSnapView().clearBitmap();
+ }
+
+ @Override
+ public void onReRecord() {
+ getRecordRightLayout().setMusicIconEnable(true);
+ getRecordRightLayout().setAspectIconEnable(true);
+ }
+ });
+
+ // 设置"音乐面板"监听器
+ getRecordMusicPannel().setOnMusicChangeListener(this);
+ // 设置"音效面板"监听器
+ getSoundEffectPannel().setSoundEffectsSettingPannelListener(this);
+
+ getScrollFilterView().setOnRecordFilterListener(this);
+
+ TelephonyUtil.getInstance().initPhoneListener();
+ mProgressDialogUtil = new ProgressDialogUtil(mActivity);
+
+ UGCKitRecordConfig config = UGCKitRecordConfig.getInstance();
+ config.mBeautyParams = new BeautyParams();
+
+ // 设置默认美颜
+ config.mBeautyParams.mBeautyStyle = 0;
+ config.mBeautyParams.mBeautyLevel = 4;
+ config.mBeautyParams.mWhiteLevel = 1;
+ // 初始化默认配置
+ VideoRecordSDK.getInstance().initConfig(config);
+ VideoRecordSDK.getInstance().updateBeautyParam(config.mBeautyParams);
+
+ TXUGCRecord txugcRecord = VideoRecordSDK.getInstance().getRecorder();
+ getBeautyPanel().setBeautyManager(txugcRecord.getBeautyManager());
+ getBeautyPanel().setOnFilterChangeListener(new Beauty.OnFilterChangeListener() {
+ @Override
+ public void onChanged(Bitmap filterImage, int index) {
+ getScrollFilterView().doTextAnimator(index);
+ }
+ });
+ getBeautyPanel().setOnBeautyListener(new BeautyPanel.OnBeautyListener() {
+ @Override
+ public void onTabChange(TabInfo tabInfo, int position) {
+
+ }
+
+ @Override
+ public boolean onClose() {
+ getBeautyPanel().setVisibility(View.GONE);
+ getRecordMusicPannel().setVisibility(View.GONE);
+ getSoundEffectPannel().setVisibility(View.GONE);
+
+ getRecordBottomLayout().setVisibility(View.VISIBLE);
+ getRecordRightLayout().setVisibility(View.VISIBLE);
+ return true;
+ }
+
+ @Override
+ public boolean onClick(TabInfo tabInfo, int tabPosition, ItemInfo itemInfo, int itemPosition) {
+ return false;
+ }
+
+ @Override
+ public boolean onLevelChanged(TabInfo tabInfo, int tabPosition, ItemInfo itemInfo, int itemPosition, int beautyLevel) {
+ return false;
+ }
+ });
+ }
+
+ @Override
+ public void setOnRecordListener(OnRecordListener listener) {
+ mOnRecordListener = listener;
+ }
+
+ @Override
+ public void setOnMusicChooseListener(OnMusicChooseListener listener) {
+ mOnMusicListener = listener;
+ }
+
+ @Override
+ public void start() {
+ // 打开录制预览界面
+ VideoRecordSDK.getInstance().startCameraPreview(getRecordVideoView());
+ }
+
+ @Override
+ public void stop() {
+ Log.d(TAG, "stop");
+ isInStopProcessing = false;
+ TelephonyUtil.getInstance().uninitPhoneListener();
+
+ getRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ getRecordBottomLayout().closeTorch();
+ // 停止录制预览界面
+ VideoRecordSDK.getInstance().stopCameraPreview();
+ // 暂停录制
+ VideoRecordSDK.getInstance().pauseRecord();
+ }
+
+ @Override
+ public void release() {
+ Log.d(TAG, "release");
+ getRecordBottomLayout().getRecordProgressView().release();
+ // 停止录制
+ VideoRecordSDK.getInstance().releaseRecord();
+
+ UGCKitRecordConfig.getInstance().clear();
+ // 录制TXUGCRecord是单例,需要释放时还原配置
+ getBeautyPanel().clear();
+ AudioFocusManager.getInstance().setAudioFocusListener(null);
+ VideoRecordSDK.getInstance().setVideoRecordListener(null);
+ getBeautyPanel().setOnFilterChangeListener(null);
+ ProcessKit.getInstance().setOnUpdateUIListener(null);
+ VideoRecordSDK.getInstance().setOnRestoreDraftListener(null);
+ }
+
+ @Override
+ public void screenOrientationChange() {
+ Log.d(TAG, "screenOrientationChange");
+ VideoRecordSDK.getInstance().stopCameraPreview();
+
+ VideoRecordSDK.getInstance().pauseRecord();
+
+ VideoRecordSDK.getInstance().startCameraPreview(getRecordVideoView());
+ }
+
+ @Override
+ public void setRecordMusicInfo(@NonNull MusicInfo musicInfo) {
+ if (musicInfo != null) {
+ Log.d(TAG, "music name:" + musicInfo.name + ", path:" + musicInfo.path);
+ }
+ getRecordBottomLayout().setVisibility(View.INVISIBLE);
+ getRecordRightLayout().setVisibility(View.INVISIBLE);
+
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ long duration = record.setBGM(musicInfo.path);
+ musicInfo.duration = duration;
+ Log.d(TAG, "music duration:" + musicInfo.duration);
+ }
+ // 设置音乐信息
+ RecordMusicManager.getInstance().setRecordMusicInfo(musicInfo);
+ // 更新音乐Pannel
+ getRecordMusicPannel().setMusicInfo(musicInfo);
+ getRecordMusicPannel().setVisibility(View.VISIBLE);
+
+ // 音乐试听
+ RecordMusicManager.getInstance().startPreviewMusic();
+ }
+
+ @Override
+ public void backPressed() {
+ Log.d(TAG, "backPressed");
+ // 录制已停止,则回调"录制被取消"
+ if (VideoRecordSDK.getInstance().getRecordState() == VideoRecordSDK.STATE_STOP) {
+ if (mOnRecordListener != null) {
+ mOnRecordListener.onRecordCanceled();
+ }
+ return;
+ }
+ // 录制已开始,点击返回键,暂停录制
+ if (VideoRecordSDK.getInstance().getRecordState() == VideoRecordSDK.STATE_START) {
+ //相当于点击了暂停按钮
+ getRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ }
+
+ int size = VideoRecordSDK.getInstance().getPartManager().getPartsPathList().size();
+ if (size == 0) {
+ if (mOnRecordListener != null) {
+ mOnRecordListener.onRecordCanceled();
+ }
+ return;
+ }
+
+ showGiveupRecordDialog();
+ }
+
+ /**
+ * 显示放弃录制对话框
+ */
+ private void showGiveupRecordDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ AlertDialog alertDialog = builder.setTitle(getResources().getString(R.string.ugckit_cancel_record)).setCancelable(false).setMessage(R.string.ugckit_confirm_cancel_record_content)
+ .setPositiveButton(R.string.ugckit_give_up, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ VideoRecordSDK.getInstance().deleteAllParts();
+
+ if (mOnRecordListener != null) {
+ mOnRecordListener.onRecordCanceled();
+ }
+ return;
+ }
+ })
+ .setNegativeButton(getResources().getString(R.string.ugckit_wrong_click), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ }).create();
+ alertDialog.show();
+ }
+
+ /**
+ * 点击录制开始按钮
+ */
+ @Override
+ public void onRecordStart() {
+ getRecordRightLayout().setVisibility(View.INVISIBLE);
+ getRecordBottomLayout().startRecord();
+ // 开始录制后不能再选择音乐
+ getRecordRightLayout().setMusicIconEnable(false);
+ // 开始录制后不能切换屏比
+ getRecordRightLayout().setAspectIconEnable(false);
+
+ // 开始/继续录制
+ int retCode = VideoRecordSDK.getInstance().startRecord();
+ if (retCode == VideoRecordSDK.START_RECORD_FAIL) { //点击开始录制失败,录制按钮状态变为暂停
+ getRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ return;
+ }
+
+ AudioFocusManager.getInstance().setAudioFocusListener(new AudioFocusManager.OnAudioFocusListener() {
+ @Override
+ public void onAudioFocusChange() {
+ VideoRecordSDK.getInstance().pauseRecord();
+ }
+ });
+ AudioFocusManager.getInstance().requestAudioFocus();
+ getRecordPauseSnapView().clearBitmap();
+ }
+
+ /**
+ * 点击录制暂停按钮
+ */
+ @Override
+ public void onRecordPause() {
+ Log.d(TAG, "onRecordPause");
+
+ if(UGCKitRecordConfig.getInstance().mPauseSnapOpacity > 0) {
+ getRecordPauseSnapView().catchPauseImage();
+ }
+
+ getRecordRightLayout().setVisibility(View.VISIBLE);
+ getRecordBottomLayout().pauseRecord();
+
+ VideoRecordSDK.getInstance().pauseRecord();
+ RecordMusicManager.getInstance().pauseMusic();
+
+ AudioFocusManager.getInstance().abandonAudioFocus();
+ }
+
+ /**
+ * 点击照相
+ */
+ @Override
+ public void onTakePhoto() {
+ PhotoSoundPlayer.playPhotoSound();
+
+ VideoRecordSDK.getInstance().takePhoto(new RecordModeView.OnSnapListener() {
+ @Override
+ public void onSnap(Bitmap bitmap) {
+ getSnapshotView().showSnapshotAnim(bitmap);
+ }
+ });
+ }
+
+ @Override
+ public void onDeleteParts(int partsSize, long duration) {
+
+ }
+
+ @Override
+ public void onShowBeautyPanel() {
+ // 隐藏底部工具栏
+ getRecordBottomLayout().setVisibility(View.GONE);
+ // 隐藏右侧工具栏
+ getRecordRightLayout().setVisibility(View.GONE);
+ // 显示美颜Pannel
+ getBeautyPanel().setVisibility(View.VISIBLE);
+ }
+
+ /**
+ * 点击工具栏按钮"音乐"
+ */
+ @Override
+ public void onShowMusicPanel() {
+ boolean isChooseMusicFlag = RecordMusicManager.getInstance().isChooseMusic();
+ if (isChooseMusicFlag) {
+ // 隐藏底部工具栏
+ getRecordBottomLayout().setVisibility(View.GONE);
+ // 隐藏右侧工具栏
+ getRecordRightLayout().setVisibility(View.GONE);
+ // 显示音乐Pannel
+ getRecordMusicPannel().setVisibility(View.VISIBLE);
+
+ RecordMusicManager.getInstance().startMusic();
+ } else {
+ if (mOnMusicListener != null) {
+ mOnMusicListener.onChooseMusic(UGCKitRecordConfig.getInstance().musicInfo.position);
+ }
+ }
+ }
+
+ @Override
+ public void onShowSoundEffectPanel() {
+ // 隐藏底部工具栏
+ getRecordBottomLayout().setVisibility(View.GONE);
+ // 隐藏右侧工具栏
+ getRecordRightLayout().setVisibility(View.GONE);
+ // 显示音效Pannel
+ getSoundEffectPannel().setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAspectSelect(int aspectType) {
+ UGCKitRecordConfig.getInstance().mAspectRatio = aspectType;
+ VideoRecordSDK.getInstance().updateAspectRatio();
+ }
+
+ /************************************ 音效Pannel回调接口 Begin ********************************************/
+ @Override
+ public void onMicVolumeChanged(float volume) {
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ record.setMicVolume(volume);
+ }
+ }
+
+ @Override
+ public void onClickVoiceChanger(int type) {
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ record.setVoiceChangerType(type);
+ }
+ }
+
+ @Override
+ public void onClickReverb(int type) {
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ record.setReverb(type);
+ }
+ }
+
+ /************************************ 音效Pannel回调接口 End ********************************************/
+
+ /************************************ 音乐Pannel回调接口 Begin ********************************************/
+ @Override
+ public void onMusicVolumChanged(float volume) {
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ record.setBGMVolume(volume);
+ }
+ }
+
+ /**
+ * 背景音乐裁剪
+ *
+ * @param startTime
+ * @param endTime
+ */
+ @Override
+ public void onMusicTimeChanged(long startTime, long endTime) {
+ MusicInfo musicInfo = RecordMusicManager.getInstance().getMusicInfo();
+ musicInfo.startTime = startTime;
+ musicInfo.endTime = endTime;
+
+ RecordMusicManager.getInstance().startPreviewMusic();
+ }
+
+ /**
+ * 点击"音乐Pannel"的确定
+ * 1、关闭音乐Pannel
+ * 2、停止音乐试听
+ */
+ @Override
+ public void onMusicSelect() {
+ getRecordBottomLayout().setVisibility(View.VISIBLE);
+ getRecordRightLayout().setVisibility(View.VISIBLE);
+ // 录制添加BGM后是录制不了人声的,而音效是针对人声有效的
+ getRecordRightLayout().setSoundEffectsEnabled(false);
+
+ getRecordMusicPannel().setVisibility(View.GONE);
+
+ // 停止音乐试听
+ RecordMusicManager.getInstance().stopPreviewMusic();
+ }
+
+ /**
+ * 点击"音乐Pannel"的切换音乐
+ */
+ @Override
+ public void onMusicReplace() {
+ if (mOnMusicListener != null) {
+ mOnMusicListener.onChooseMusic(UGCKitRecordConfig.getInstance().musicInfo.position);
+ }
+ }
+
+ /**
+ * 点击"音乐Pannel"删除背景音乐
+ */
+ @Override
+ public void onMusicDelete() {
+ showDeleteMusicDialog();
+ }
+
+ private void showDeleteMusicDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ AlertDialog alertDialog = builder.setTitle(getResources().getString(R.string.ugckit_tips)).setCancelable(false).setMessage(R.string.ugckit_delete_bgm_or_not)
+ .setPositiveButton(R.string.ugckit_confirm_delete, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+
+ RecordMusicManager.getInstance().deleteMusic();
+ // 录制添加BGM后是录制不了人声的,而音效是针对人声有效的
+ getRecordRightLayout().setSoundEffectIconEnable(true);
+
+// getRecordMusicPannel().setMusicName("");
+ getRecordMusicPannel().setVisibility(View.GONE);
+ }
+ })
+ .setNegativeButton(getResources().getString(R.string.ugckit_btn_cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(@NonNull DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ }).create();
+ alertDialog.show();
+ }
+
+ /************************************ 音乐Pannel回调接口 End ********************************************/
+
+ @Override
+ public void onSingleClick(float x, float y) {
+ getBeautyPanel().setVisibility(View.GONE);
+ getRecordMusicPannel().setVisibility(View.GONE);
+ getSoundEffectPannel().setVisibility(View.GONE);
+
+ getRecordBottomLayout().setVisibility(View.VISIBLE);
+ getRecordRightLayout().setVisibility(View.VISIBLE);
+ TXUGCRecord record = VideoRecordSDK.getInstance().getRecorder();
+ if (record != null) {
+ record.setFocusPosition(x, y);
+ }
+ }
+
+ @Override
+ public void onRecordProgress(long milliSecond) {
+ getRecordBottomLayout().updateProgress(milliSecond);
+
+ float second = milliSecond / 1000f;
+ boolean enable = second >= UGCKitRecordConfig.getInstance().mMinDuration / 1000;
+ getTitleBar().setVisible(enable, ITitleBarLayout.POSITION.RIGHT);
+ }
+
+ @Override
+ public void onRecordEvent(int event) {
+ getRecordBottomLayout().getRecordProgressView().clipComplete();
+ if (event == TXRecordCommon.EVT_ID_PAUSE) {
+ Log.d(TAG, "onRecordEvent: event=EVT_ID_PAUSE");
+ //相当于点击了暂停按钮
+ getRecordBottomLayout().getRecordButton().pauseRecordAnim();
+ }
+ }
+
+ @Override
+ public void onRecordComplete(@NonNull TXRecordCommon.TXRecordResult result) {
+ LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VIDEO_RECORD, result.retCode, result.descMsg);
+
+ if (result.retCode >= 0) {
+ mProgressDialogUtil.dismissProgressDialog();
+ boolean editFlag = UGCKitRecordConfig.getInstance().mIsNeedEdit;
+ if (editFlag) {
+ // 录制后需要进行编辑,预处理产生视频缩略图
+ startPreprocess(result.videoPath);
+ } else {
+ // 录制后不需要进行编辑视频,直接输出录制视频路径
+ if (mOnRecordListener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ String outputPath = VideoRecordSDK.getInstance().getRecordVideoPath();
+ ugcKitResult.errorCode = result.retCode;
+ ugcKitResult.descMsg = result.descMsg;
+ ugcKitResult.outputPath = outputPath;
+ ugcKitResult.coverPath = result.coverPath;
+ mOnRecordListener.onRecordCompleted(ugcKitResult);
+ }
+ }
+ }
+ }
+
+ private void startPreprocess(String videoPath) {
+ mProgressFragmentUtil = new ProgressFragmentUtil(mActivity);
+ mProgressFragmentUtil.showLoadingProgress(new ProgressFragmentUtil.IProgressListener() {
+ @Override
+ public void onStop() {
+ mProgressFragmentUtil.dismissLoadingProgress();
+
+ ProcessKit.getInstance().stopProcess();
+ }
+ });
+
+ loadVideoInfo(videoPath);
+ }
+
+ /**
+ * 加载视频信息
+ *
+ * @param videoPath
+ */
+ private void loadVideoInfo(final String videoPath) {
+ if(null == videoProcessExecutor) {
+ videoProcessExecutor = Executors.newSingleThreadExecutor();
+ }
+ //使用单线程池,loadVideoInfo线程处理按照FIFO顺序执行,避免多并发产生的潜在问题
+ videoProcessExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ final TXVideoEditConstants.TXVideoInfo info = TXVideoInfoReader.getInstance(UGCKit.getAppContext()).getVideoFileInfo(videoPath);
+ BackgroundTasks.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ processVideo(info,videoPath);
+ }
+ });
+ }
+ });
+ }
+
+ private void processVideo(TXVideoEditConstants.TXVideoInfo info,final String videoPath) {
+ if (info == null) {
+ DialogUtil.showDialog(getContext(), getResources().getString(R.string.ugckit_video_preprocess_activity_edit_failed), getResources().getString(R.string.ugckit_does_not_support_android_version_below_4_3), null);
+ } else {
+ // 设置视频基本信息
+ VideoEditerSDK.getInstance().initSDK();
+ VideoEditerSDK.getInstance().setVideoPath(videoPath);
+ VideoEditerSDK.getInstance().setTXVideoInfo(info);
+ VideoEditerSDK.getInstance().setVideoDuration(info.duration);
+ VideoEditerSDK.getInstance().setCutterStartTime(0, info.duration);
+
+ ProcessKit.getInstance().setOnUpdateUIListener(new OnUpdateUIListener() {
+ @Override
+ public void onUIProgress(float progress) {
+ mProgressFragmentUtil.updateGenerateProgress((int) (progress * 100));
+ }
+
+ @Override
+ public void onUIComplete(int retCode, String descMsg) {
+ // 更新UI控件
+ mProgressFragmentUtil.dismissLoadingProgress();
+ if (mOnRecordListener != null) {
+ UGCKitResult ugcKitResult = new UGCKitResult();
+ ugcKitResult.errorCode = retCode;
+ ugcKitResult.descMsg = descMsg;
+ ugcKitResult.outputPath = videoPath;
+ mOnRecordListener.onRecordCompleted(ugcKitResult);
+ }
+ }
+
+ @Override
+ public void onUICancel() {
+ // 更新Activity
+ if (mOnRecordListener != null) {
+ mOnRecordListener.onRecordCanceled();
+ }
+ }
+ });
+ // 开始视频预处理
+ ProcessKit.getInstance().startProcess();
+ }
+ }
+
+ @Override
+ public void setConfig(UGCKitRecordConfig config) {
+ VideoRecordSDK.getInstance().setConfig(config);
+ // 初始化最大/最小视频录制时长
+ getRecordBottomLayout().initDuration();
+ // 设置默认的录制模式
+ getRecordBottomLayout().getRecordButton().setCurrentRecordMode(UGCKitRecordConfig.getInstance().mRecordMode);
+ // 设置视频比例UI
+ getRecordRightLayout().setAspect(config.mAspectRatio);
+ }
+
+ @Override
+ public void setEditVideoFlag(boolean enable) {
+ UGCKitRecordConfig.getInstance().mIsNeedEdit = enable;
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/BaseGenerateKit.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/BaseGenerateKit.java
new file mode 100644
index 0000000..a48a038
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/BaseGenerateKit.java
@@ -0,0 +1,13 @@
+package com.tencent.qcloud.ugckit.basic;
+
+/**
+ * 视频生成器
+ */
+public class BaseGenerateKit {
+
+ protected OnUpdateUIListener mOnUpdateUIListener;
+
+ public void setOnUpdateUIListener(OnUpdateUIListener listener) {
+ mOnUpdateUIListener = listener;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/ITitleBar.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/ITitleBar.java
new file mode 100644
index 0000000..f16abff
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/ITitleBar.java
@@ -0,0 +1,13 @@
+package com.tencent.qcloud.ugckit.basic;
+
+import com.tencent.qcloud.ugckit.component.TitleBarLayout;
+
+public interface ITitleBar {
+
+ /**
+ * 获取标题栏
+ *
+ * @return
+ */
+ TitleBarLayout getTitleBar();
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/ITitleBarLayout.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/ITitleBarLayout.java
new file mode 100644
index 0000000..519c20e
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/ITitleBarLayout.java
@@ -0,0 +1,94 @@
+package com.tencent.qcloud.ugckit.basic;
+
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * 标题栏接口,标题栏设计为左中右三部分标题,左边为图片+文字,中间为文字,右边也会图片+文字
+ */
+public interface ITitleBarLayout {
+
+ /**
+ * 设置左边标题的图片
+ *
+ * @param resId
+ */
+ void setLeftIcon(int resId);
+
+ /**
+ * 设置左边标题的点击事件
+ *
+ * @param listener
+ */
+ void setOnBackClickListener(View.OnClickListener listener);
+
+ /**
+ * 设置右边标题的点击事件
+ *
+ * @param listener
+ */
+ void setOnRightClickListener(View.OnClickListener listener);
+
+ /**
+ * 设置标题
+ *
+ * @param title 标题内容
+ * @param position 标题位置
+ */
+ void setTitle(String title, POSITION position);
+
+ /**
+ * @param enable
+ * @param position
+ */
+ void setVisible(boolean enable, POSITION position);
+
+ /**
+ * 返回左边标题区域
+ *
+ * @return
+ */
+ LinearLayout getLeftGroup();
+
+ /**
+ * 返回左边标题的图片
+ *
+ * @return
+ */
+ ImageView getLeftIcon();
+
+ /**
+ * 返回左边标题的文字
+ *
+ * @return
+ */
+ TextView getLeftTitle();
+
+ /**
+ * 返回中间标题的文字
+ *
+ * @return
+ */
+ TextView getMiddleTitle();
+
+ Button getRightButton();
+
+ enum POSITION {
+ /**
+ * 左边标题
+ */
+ LEFT,
+ /**
+ * 中间标题
+ */
+ MIDDLE,
+ /**
+ * 右侧
+ */
+ RIGHT
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/JumpActivityMgr.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/JumpActivityMgr.java
new file mode 100644
index 0000000..6289926
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/JumpActivityMgr.java
@@ -0,0 +1,44 @@
+package com.tencent.qcloud.ugckit.basic;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Activity跳转的信息保存
+ */
+public class JumpActivityMgr {
+
+ @NonNull
+ private static JumpActivityMgr sInstance = new JumpActivityMgr();
+ private boolean mCutVideoFlag = true;
+ private boolean mQuickImport = false;
+
+ private JumpActivityMgr() {
+ mCutVideoFlag = true;
+ }
+
+ @NonNull
+ public static JumpActivityMgr getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * 设置"视频裁剪页面"是否进行"视频编辑"
+ *
+ * @param flag
+ */
+ public void setEditFlagFromCut(boolean flag) {
+ mCutVideoFlag = flag;
+ }
+
+ public boolean getEditFlagFromCut() {
+ return mCutVideoFlag;
+ }
+
+ public void setQuickImport(boolean quickImport) {
+ mQuickImport = quickImport;
+ }
+
+ public boolean isQuickImport() {
+ return mQuickImport;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/OnUpdateUIListener.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/OnUpdateUIListener.java
new file mode 100644
index 0000000..7d366e2
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/OnUpdateUIListener.java
@@ -0,0 +1,26 @@
+package com.tencent.qcloud.ugckit.basic;
+
+/**
+ * 更新UI监听者
+ */
+public interface OnUpdateUIListener {
+ /**
+ * 更新进度条进度
+ *
+ * @param progress
+ */
+ void onUIProgress(float progress);
+
+ /**
+ * 操作执行完成,更新UI
+ *
+ * @param retCode
+ * @param descMsg
+ */
+ void onUIComplete(int retCode, String descMsg);
+
+ /**
+ * 操作取消,更新UI
+ */
+ void onUICancel();
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/TUIBuild.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/TUIBuild.java
new file mode 100644
index 0000000..4552c89
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/TUIBuild.java
@@ -0,0 +1,169 @@
+package com.tencent.qcloud.ugckit.basic;
+
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+public final class TUIBuild {
+
+ private static final String TAG = "TUIBuild";
+
+ private static String sModel = ""; //Build.MODEL;
+ private static String sBrand = ""; //Build.BRAND;
+ private static String sManufacturer = ""; //Build.MANUFACTURER;
+ private static String sHardware = ""; //Build.HARDWARE;
+ private static String sReleaseVersion = ""; //Build.VERSION.RELEASE;
+ private static String sBoard = ""; //Build.BOARD;
+ private static String sVersionIncremental = ""; //Build.VERSION.INCREMENTAL
+ private static int sSdkInt = 0; //Build.VERSION.SDK_INT;
+
+ public static void setModel(final String model) {
+ synchronized (TUIBuild.class) {
+ sModel = model;
+ }
+ }
+
+ public static String getModel() {
+ if (TextUtils.isEmpty(sModel)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sModel)) {
+ sModel = Build.MODEL;
+ Log.i(TAG, "get MODEL by Build.MODEL :" + sModel);
+ }
+ }
+ }
+ return sModel;
+ }
+
+ public static void setBrand(final String brand) {
+ synchronized (TUIBuild.class) {
+ sBrand = brand;
+ }
+ }
+
+ public static String getBrand() {
+ if (TextUtils.isEmpty(sBrand)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sBrand)) {
+ sBrand = Build.BRAND;
+ Log.i(TAG, "get BRAND by Build.BRAND :" + sBrand);
+ }
+ }
+ }
+
+ return sBrand;
+ }
+
+ public static void setManufacturer(final String manufacturer) {
+ synchronized (TUIBuild.class) {
+ sManufacturer = manufacturer;
+ }
+ }
+
+ public static String getManufacturer() {
+ if (TextUtils.isEmpty(sManufacturer)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sManufacturer)) {
+ sManufacturer = Build.MANUFACTURER;
+ Log.i(TAG, "get MANUFACTURER by Build.MANUFACTURER :" + sManufacturer);
+ }
+ }
+ }
+
+ return sManufacturer;
+ }
+
+ public static void setHardware(final String hardware) {
+ synchronized (TUIBuild.class) {
+ sHardware = hardware;
+ }
+ }
+
+ public static String getHardware() {
+ if (TextUtils.isEmpty(sHardware)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sHardware)) {
+ sHardware = Build.HARDWARE;
+ Log.i(TAG, "get HARDWARE by Build.HARDWARE :" + sHardware);
+ }
+ }
+ }
+
+ return sHardware;
+ }
+
+ public static void setReleaseVersion(final String version) {
+ synchronized (TUIBuild.class) {
+ sReleaseVersion = version;
+ }
+ }
+
+ public static String getReleaseVersion() {
+ if (TextUtils.isEmpty(sReleaseVersion)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sReleaseVersion)) {
+ sReleaseVersion = Build.VERSION.RELEASE;
+ Log.i(TAG, "get VERSION by Build.VERSION.RELEASE :" + sReleaseVersion);
+ }
+ }
+ }
+
+ return sReleaseVersion;
+ }
+
+ public static void setSdkInt(final int versionInt) {
+ synchronized (TUIBuild.class) {
+ sSdkInt = versionInt;
+ }
+ }
+
+ public static int getSdkInt() {
+ if (sSdkInt == 0) {
+ synchronized (TUIBuild.class) {
+ if (sSdkInt == 0) {
+ sSdkInt = Build.VERSION.SDK_INT;
+ Log.i(TAG, "get VERSION_INT by Build.VERSION.SDK_INT :" + sSdkInt);
+ }
+ }
+ }
+
+ return sSdkInt;
+ }
+
+ public static void setVersionIncremental(final String versionIncremental) {
+ synchronized (TUIBuild.class) {
+ sVersionIncremental = versionIncremental;
+ }
+ }
+
+ public static String getVersionIncremental() {
+ if (TextUtils.isEmpty(sVersionIncremental)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sVersionIncremental)) {
+ sVersionIncremental = Build.VERSION.INCREMENTAL;
+ Log.i(TAG, "get VERSION_INCREMENTAL by Build.VERSION.INCREMENTAL :" + sVersionIncremental);
+ }
+ }
+ }
+ return sVersionIncremental;
+ }
+
+ public static void setBoard(final String board) {
+ synchronized (TUIBuild.class) {
+ sBoard = board;
+ }
+ }
+
+ public static String getBoard() {
+ if (TextUtils.isEmpty(sBoard)) {
+ synchronized (TUIBuild.class) {
+ if (TextUtils.isEmpty(sBoard)) {
+ sBoard = Build.BOARD;
+ Log.i(TAG, "get BOARD by Build.BOARD :" + sBoard);
+ }
+ }
+ }
+
+ return sBoard;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/UGCKitResult.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/UGCKitResult.java
new file mode 100644
index 0000000..59bb6e7
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/basic/UGCKitResult.java
@@ -0,0 +1,25 @@
+package com.tencent.qcloud.ugckit.basic;
+
+public class UGCKitResult {
+ /**
+ * 错误码
+ */
+ public int errorCode;
+ /**
+ * 详细描述
+ */
+ public String descMsg;
+ /**
+ * 封面
+ */
+ public String coverPath;
+ /**
+ * 输出视频路径
+ */
+ public String outputPath;
+
+ /**
+ * 是否需要发布
+ */
+ public boolean isPublish;
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/TitleBarLayout.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/TitleBarLayout.java
new file mode 100644
index 0000000..cb76b56
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/TitleBarLayout.java
@@ -0,0 +1,120 @@
+package com.tencent.qcloud.ugckit.component;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.tencent.qcloud.ugckit.basic.ITitleBarLayout;
+import com.tencent.qcloud.ugckit.R;
+
+public class TitleBarLayout extends LinearLayout implements ITitleBarLayout {
+
+ private LinearLayout mLeftGroup;
+ private TextView mLeftTitle;
+ private TextView mCenterTitle;
+ private ImageView mLeftIcon;
+ private Button mRightButton;
+ private RelativeLayout mTitleLayout;
+
+ public TitleBarLayout(Context context) {
+ super(context);
+ init();
+ }
+
+ public TitleBarLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public TitleBarLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ inflate(getContext(), R.layout.ugckit_title_bar_layout, this);
+ mTitleLayout = (RelativeLayout) findViewById(R.id.page_title_layout);
+ mLeftGroup = (LinearLayout) findViewById(R.id.page_title_left_group);
+ mLeftTitle = (TextView) findViewById(R.id.page_title_left_text);
+ mCenterTitle = (TextView) findViewById(R.id.page_title);
+ mLeftIcon = (ImageView) findViewById(R.id.page_title_left_icon);
+ mRightButton = (Button) findViewById(R.id.btn_next);
+ }
+
+ @Override
+ public void setLeftIcon(int resId) {
+ mLeftIcon.setImageResource(resId);
+ }
+
+ @Override
+ public void setOnBackClickListener(OnClickListener listener) {
+ mLeftGroup.setOnClickListener(listener);
+ }
+
+ @Override
+ public void setOnRightClickListener(OnClickListener listener) {
+ mRightButton.setOnClickListener(listener);
+ }
+
+ @Override
+ public void setTitle(String title, @NonNull POSITION position) {
+ switch (position) {
+ case LEFT:
+ mLeftTitle.setText(title);
+ break;
+ case MIDDLE:
+ mCenterTitle.setText(title);
+ break;
+ case RIGHT:
+ mRightButton.setText(title);
+ break;
+ }
+ }
+
+ @Override
+ public void setVisible(boolean enable, @NonNull POSITION position) {
+ switch (position) {
+ case LEFT:
+ mLeftIcon.setVisibility(enable ? View.VISIBLE : View.GONE);
+ break;
+ case RIGHT:
+ mRightButton.setVisibility(enable ? View.VISIBLE : View.GONE);
+ break;
+ }
+ }
+
+ @Override
+ public LinearLayout getLeftGroup() {
+ return mLeftGroup;
+ }
+
+ @Override
+ public ImageView getLeftIcon() {
+ return mLeftIcon;
+ }
+
+ @Override
+ public Button getRightButton() {
+ return mRightButton;
+ }
+
+ @Override
+ public TextView getLeftTitle() {
+ return mCenterTitle;
+ }
+
+ @Nullable
+ @Override
+ public TextView getMiddleTitle() {
+ return null;
+ }
+
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleView.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleView.java
new file mode 100644
index 0000000..17cc059
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleView.java
@@ -0,0 +1,73 @@
+package com.tencent.qcloud.ugckit.component.bubbleview;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.tencent.qcloud.ugckit.component.floatlayer.FloatLayerView;
+
+/**
+ * 气泡字幕的View
+ *
+ * 根绝参数 初始化气泡字幕
+ */
+public class BubbleView extends FloatLayerView {
+ private static final String TAG = "BubbleView";
+
+ private long mStartTime;
+ private long mEndTime;
+
+ @Nullable
+ private BubbleViewParams mBubbleViewParams;
+
+ public BubbleView(Context context) {
+ super(context);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public BubbleView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public void setBubbleParams(@Nullable BubbleViewParams params) {
+ mBubbleViewParams = params;
+ if (params == null) {
+ return;
+ }
+ if (params.text == null) {
+ params.text = "";
+ Log.w(TAG, "setBubbleParams: bubble text is null");
+ }
+ BubbleViewHelper helper = new BubbleViewHelper();
+ helper.setBubbleTextParams(params);
+ Bitmap bitmap = helper.createBubbleTextBitmap();
+ setImageBitamp(bitmap);
+ mBubbleViewParams.bubbleBitmap = null;
+ invalidate();
+ }
+
+ @Nullable
+ public BubbleViewParams getBubbleParams() {
+ return mBubbleViewParams;
+ }
+
+ public void setStartToEndTime(long startTime, long endTime) {
+ mStartTime = startTime;
+ mEndTime = endTime;
+ }
+
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ public long getEndTime() {
+ return mEndTime;
+ }
+
+
+}
\ No newline at end of file
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewFactory.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewFactory.java
new file mode 100644
index 0000000..4b6165a
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewFactory.java
@@ -0,0 +1,19 @@
+package com.tencent.qcloud.ugckit.component.bubbleview;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import android.view.View;
+
+import com.tencent.qcloud.ugckit.R;
+
+
+/**
+ * 创建 OperationView的工厂
+ */
+public class BubbleViewFactory {
+
+ @NonNull
+ public static BubbleView newOperationView(Context context) {
+ return (BubbleView) View.inflate(context, R.layout.ugckit_layout_default_bubble_view, null);
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewHelper.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewHelper.java
new file mode 100644
index 0000000..943c7a7
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewHelper.java
@@ -0,0 +1,296 @@
+package com.tencent.qcloud.ugckit.component.bubbleview;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 根据外部传入的Params,构建一张合适的气泡Bitmap图
+ *
+ * 1. 根据默认字体大小、可绘制的字幕区域来计算出 最终绘制的字体大小,以及行数
+ * 2. 根据字体大小、行数对字体进行排版
+ * 3. 绘制: A.绘制已经排好版的字体 B.绘制气泡字幕背景
+ * 4. 返回最终的Bitmap图
+ */
+public class BubbleViewHelper {
+ @Nullable
+ private Bitmap mBubbleBitmap;
+ private BubbleViewParams mParams;
+ private float mTextDefaultSize = 36; // 字幕的默认代销
+ private String mText;
+ private Paint mPaint;
+ private int mTextAreaTop;
+ private int mTextAreaLeft;
+ private int mTextAreaRight;
+ private int mTextAreaBottom;
+ private int mTextAreaHeight;
+ private int mTextAreaWidth;
+ private int mTextAreaCenterX;
+ private int mTextAreaCenterY;
+
+ public BubbleViewHelper() {
+
+ }
+
+ public void setBubbleTextParams(@NonNull BubbleViewParams params) {
+ mParams = params;
+ mTextDefaultSize = params.wordParamsInfo.getBubbleInfo().getDefaultSize();
+ mBubbleBitmap = params.bubbleBitmap;
+ mText = params.text;
+
+ if (mBubbleBitmap != null && mBubbleBitmap.isRecycled()) {
+ return;
+ }
+
+ initPaint();
+
+ // 如果气泡字幕背景为空, 那么创建一张刚好可以包裹文字的"空背景"
+ if (mBubbleBitmap == null) {
+ // 创建一张空的背景
+ int height = (int) getFontHeight() * 2;
+ int width = (int) mPaint.measureText(mText) + 1;
+ mBubbleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ }
+ initTextArea(
+ params.wordParamsInfo.getBubbleInfo().getTop(),
+ params.wordParamsInfo.getBubbleInfo().getLeft(),
+ params.wordParamsInfo.getBubbleInfo().getRight(),
+ params.wordParamsInfo.getBubbleInfo().getBottom());
+ }
+
+ /**
+ * 初始化画笔
+ */
+ private void initPaint() {
+ mPaint = new Paint();
+ mPaint.setColor(mParams.wordParamsInfo.getTextColor() != 0 ? mParams.wordParamsInfo.getTextColor() : Color.WHITE);
+ mPaint.setTextSize(mTextDefaultSize);
+ mPaint.setAntiAlias(true);
+ }
+
+ /**
+ * 初始化可绘制区域的大小
+ *
+ * @param top
+ * @param left
+ * @param right
+ * @param bottom
+ */
+ private void initTextArea(float top, float left, float right, float bottom) {
+ mTextAreaTop = (int) (top * mBubbleBitmap.getHeight());
+ mTextAreaBottom = (int) (bottom * mBubbleBitmap.getHeight());
+ mTextAreaLeft = (int) (left * mBubbleBitmap.getWidth());
+ mTextAreaRight = (int) (right * mBubbleBitmap.getWidth());
+
+ mTextAreaWidth = mBubbleBitmap.getWidth() - mTextAreaRight - mTextAreaLeft;
+ mTextAreaHeight = mBubbleBitmap.getHeight() - mTextAreaBottom - mTextAreaTop;
+
+ mTextAreaCenterX = mTextAreaWidth / 2 + mTextAreaLeft;
+ mTextAreaCenterY = mTextAreaHeight / 2 + mTextAreaTop;
+ }
+
+
+ /**
+ * 生成最终bitmap的方法
+ *
+ * @return
+ */
+ @Nullable
+ public Bitmap createBubbleTextBitmap() {
+ if (mBubbleBitmap == null || mBubbleBitmap.isRecycled()) {
+ return null;
+ }
+ float textSize = measureFontSize(mText);
+ mPaint.setTextSize(textSize);
+ Bitmap bitmap = Bitmap.createBitmap(mBubbleBitmap.getWidth(), mBubbleBitmap.getHeight(), Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawBitmap(mBubbleBitmap, 0, 0, mPaint);
+ drawText(canvas);
+ if (!mBubbleBitmap.isRecycled()) {
+ mBubbleBitmap.recycle();
+ mBubbleBitmap = null;
+ }
+ return bitmap;
+ }
+
+ //------------------------------------------------测量文字相关--------------------------------------------------------------
+
+ /**
+ * 测量文字最合适的大小
+ *
+ * @param text
+ * @return
+ */
+ private float measureFontSize(String text) {
+ float trySize = mTextDefaultSize;
+ mPaint.setTextSize(trySize);
+ int lines = measureTextLines(trySize, text);
+ double height = measureTextAreaHeight(trySize, lines);
+ if (height > mTextAreaHeight) {
+ do {
+ trySize -= 1;
+ lines = measureTextLines(trySize, text) + 1;// +1是为了留出更多的空间
+ height = measureTextAreaHeight(trySize, lines);
+ } while (height > mTextAreaHeight);
+ }
+ return trySize;
+ }
+
+
+ /**
+ * 测量文字所占用的高度
+ *
+ * @param fontSize
+ * @param lines
+ * @return
+ */
+ private double measureTextAreaHeight(float fontSize, int lines) {
+ mPaint.setTextSize(fontSize);
+ double fontHeight = getFontHeight();
+ double height = lines * fontHeight;
+ return height;
+ }
+
+ /**
+ * 测量输入的文字 所需要占用的行数
+ *
+ * @param fontSize
+ * @param s
+ * @return
+ */
+ private int measureTextLines(float fontSize, String s) {
+ mPaint.setTextSize(fontSize);
+ float textWidth = mPaint.measureText(s);
+ float lines = textWidth / mTextAreaWidth;
+ return (int) Math.ceil(lines);
+ }
+
+
+ //------------------------------------------------渲染文字相关--------------------------------------------------------------
+
+ private void drawText(@NonNull Canvas canvas) {
+ List list = locateText(); //定位字幕
+
+ for (TextParams params : list) {
+ // 具体绘制字幕
+ canvas.drawText(params.text, params.x, params.y, mPaint);
+ }
+
+ }
+
+ /**
+ * 定位字符
+ *
+ * 1. 分割字幕
+ * 2. 每行字幕定位
+ */
+ @NonNull
+ private List locateText() {
+ List list = new ArrayList<>();
+
+ List text = splitText();// 将字幕分割为一行一行
+
+ int middlePos = (text.size() + 1) / 2 - 1;
+
+ float baseX = mTextAreaLeft, baseY;
+
+ float fontHeight = getFontHeight();
+
+ if (text.size() % 2 == 1) {//行数奇数
+ baseY = mTextAreaCenterY + fontHeight / 2;
+ } else {
+ baseY = mTextAreaCenterY;
+ }
+
+ list.add(new TextParams(text.get(middlePos), mTextAreaLeft, baseY));
+
+
+ int loopTime = 0;
+ for (int i = middlePos - 1; i >= 0; i--) {
+ loopTime += 1;
+ list.add(new TextParams(text.get(i), baseX, baseY - fontHeight * loopTime));
+ }
+
+ loopTime = 0;
+ for (int i = middlePos + 1; i < text.size(); i++) {
+ loopTime += 1;
+ list.add(new TextParams(text.get(i), baseX, baseY + fontHeight * loopTime));
+ }
+ return list;
+ }
+
+
+ /**
+ * 分割字幕
+ *
+ * 对文字进行排版,分割成一行一行
+ *
+ * @return
+ */
+ @NonNull
+ private List splitText() {
+ List text = new ArrayList<>();
+
+ char[] chars = mText.toCharArray();
+
+ StringBuilder sb = new StringBuilder();
+
+ int lineWidth = 0;
+
+ int lineNumber = 0;
+
+ for (int i = 0; i < chars.length; i++) {
+ char c = chars[i];
+ float charSize = mPaint.measureText(chars, i, 1);
+ lineWidth += charSize;
+
+ if (lineWidth < mTextAreaWidth) {
+ sb.append(c);
+ } else {
+ text.add(sb.toString());
+ lineNumber++;
+
+ lineWidth = 0;
+ lineWidth += charSize;
+ sb = new StringBuilder();
+ sb.append(c);
+ }
+
+ if (i == chars.length - 1) {
+ text.add(sb.toString());
+ break;
+ }
+ }
+
+ return text;
+ }
+
+
+ /**
+ * 获取一个字体的高度
+ *
+ * @return
+ */
+ private float getFontHeight() {
+ Paint.FontMetrics metrics = new Paint.FontMetrics();
+ mPaint.getFontMetrics(metrics);
+ return metrics.bottom - metrics.ascent - metrics.descent;
+ }
+
+ private static class TextParams {
+ public float x, y;
+ public String text;
+
+ public TextParams(String text, float x, float y) {
+ this.x = x;
+ this.y = y;
+ this.text = text;
+ }
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewParams.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewParams.java
new file mode 100644
index 0000000..893bd6d
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/bubbleview/BubbleViewParams.java
@@ -0,0 +1,44 @@
+package com.tencent.qcloud.ugckit.component.bubbleview;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.tencent.qcloud.ugckit.module.effect.bubble.TCBubbleInfo;
+import com.tencent.qcloud.ugckit.module.effect.bubble.TCSubtitleInfo;
+
+/**
+ * 用于初始化气泡字幕控件{@link BubbleView} 的参数配置
+ */
+public class BubbleViewParams {
+ @Nullable
+ public Bitmap bubbleBitmap;
+ public TCSubtitleInfo wordParamsInfo;
+ public String text;
+
+ @NonNull
+ public static BubbleViewParams createDefaultParams(String text) {
+ BubbleViewParams params = new BubbleViewParams();
+ params.bubbleBitmap = null;
+ params.text = text;
+
+ TCSubtitleInfo info = new TCSubtitleInfo();
+ info.setTextColor(Color.WHITE);
+
+ // 初始化为无字幕的 配置信息
+ // 创建一个默认的
+ TCBubbleInfo bubbleInfo = new TCBubbleInfo();
+ bubbleInfo.setHeight(0);
+ bubbleInfo.setWidth(0);
+ bubbleInfo.setDefaultSize(40);
+ bubbleInfo.setBubblePath(null);
+ bubbleInfo.setIconPath(null);
+ bubbleInfo.setRect(0, 0, 0, 0);
+
+ info.setBubbleInfo(bubbleInfo);
+
+ params.wordParamsInfo = info;
+ return params;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/circlebmp/TCCircleView.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/circlebmp/TCCircleView.java
new file mode 100644
index 0000000..8bab0d7
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/circlebmp/TCCircleView.java
@@ -0,0 +1,66 @@
+package com.tencent.qcloud.ugckit.component.circlebmp;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * 一个圆形的View
+ */
+public class TCCircleView extends View {
+ private int mColor = Color.RED;
+ private Paint mPaint;
+ private RectF mRectF;
+
+ public TCCircleView(Context context) {
+ super(context);
+ init();
+ }
+
+ public TCCircleView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public TCCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(mColor);
+ mRectF = new RectF();
+ }
+
+
+ public void setColor(int color) {
+ mColor = color;
+ mPaint.setColor(mColor);
+ invalidate();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ mRectF.left = 0;
+ mRectF.top = 0;
+ mRectF.right = w;
+ mRectF.bottom = h;
+ }
+
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawOval(mRectF, mPaint);
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/circlebmp/TCGlideCircleTransform.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/circlebmp/TCGlideCircleTransform.java
new file mode 100644
index 0000000..4986124
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/circlebmp/TCGlideCircleTransform.java
@@ -0,0 +1,65 @@
+package com.tencent.qcloud.ugckit.component.circlebmp;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import androidx.annotation.NonNull;
+
+import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
+import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
+
+import java.security.MessageDigest;
+import java.util.Objects;
+
+/**
+ * Glide图像裁剪
+ */
+public class TCGlideCircleTransform extends BitmapTransformation {
+
+ private final String id = getClass().getName();
+
+ public TCGlideCircleTransform(Context context) {
+ }
+
+ @Override
+ protected Bitmap transform(BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
+ return createCircleImage(toTransform, 0);
+ }
+
+ // 根据原图绘制圆形图片
+ public static Bitmap createCircleImage(@NonNull Bitmap source, int min) {
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ if (0 == min) {
+ min = source.getHeight() > source.getWidth() ? source.getWidth() : source.getHeight();
+ }
+ Bitmap target = Bitmap.createBitmap(min, min, Bitmap.Config.ARGB_8888);
+ // 创建画布
+ Canvas canvas = new Canvas(target);
+ // 绘圆
+ canvas.drawCircle(min / 2, min / 2, min / 2, paint);
+ // 设置交叉模式
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+ // 绘制图片
+ canvas.drawBitmap(source, 0, 0, paint);
+ return target;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof TCGlideCircleTransform;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
+ messageDigest.update((id).getBytes());
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialog/ActionSheetDialog.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialog/ActionSheetDialog.java
new file mode 100755
index 0000000..7308a08
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialog/ActionSheetDialog.java
@@ -0,0 +1,235 @@
+package com.tencent.qcloud.ugckit.component.dialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import androidx.annotation.NonNull;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+
+import com.tencent.qcloud.ugckit.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActionSheetDialog {
+
+ private Context mContext;
+ private Dialog mDialog;
+ private TextView mTextTitle;
+ private TextView mTextCancel;
+ private LinearLayout mLayoutContent;
+ private ScrollView mScrollContent;
+ private List mSheetItemList;
+ private Display mDisplay;
+ private boolean mShowTitle = false;
+
+ public ActionSheetDialog(@NonNull Context context) {
+ this.mContext = context;
+ WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = windowManager.getDefaultDisplay();
+ }
+
+ @NonNull
+ public ActionSheetDialog builder() {
+ // 获取Dialog布局
+ View view = LayoutInflater.from(mContext).inflate(R.layout.ugckit_view_actionsheet, null);
+
+ // 设置Dialog最小宽度为屏幕宽度
+ view.setMinimumWidth(mDisplay.getWidth());
+
+ // 获取自定义Dialog布局中的控件
+ mScrollContent = (ScrollView) view.findViewById(R.id.sLayout_content);
+ mLayoutContent = (LinearLayout) view
+ .findViewById(R.id.lLayout_content);
+ mTextTitle = (TextView) view.findViewById(R.id.txt_title);
+ mTextCancel = (TextView) view.findViewById(R.id.txt_cancel);
+ mTextCancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mDialog.dismiss();
+ }
+ });
+
+ // 定义Dialog布局和参数
+ mDialog = new Dialog(mContext, R.style.UGCKitActionSheetDialogStyle);
+ mDialog.setContentView(view);
+ Window dialogWindow = mDialog.getWindow();
+ dialogWindow.setGravity(Gravity.LEFT | Gravity.BOTTOM);
+ WindowManager.LayoutParams lp = dialogWindow.getAttributes();
+ lp.x = 0;
+ lp.y = 0;
+ dialogWindow.setAttributes(lp);
+
+ return this;
+ }
+
+ @NonNull
+ public ActionSheetDialog setTitle(String title) {
+ mShowTitle = true;
+ mTextTitle.setVisibility(View.VISIBLE);
+ mTextTitle.setText(title);
+ return this;
+ }
+
+ @NonNull
+ public ActionSheetDialog setCancelable(boolean cancel) {
+ mDialog.setCancelable(cancel);
+ return this;
+ }
+
+ @NonNull
+ public ActionSheetDialog setCanceledOnTouchOutside(boolean cancel) {
+ mDialog.setCanceledOnTouchOutside(cancel);
+ return this;
+ }
+
+ /**
+ * @param strItem 条目名称
+ * @param color 条目字体颜色,设置null则默认蓝色
+ * @param listener
+ * @return
+ */
+ @NonNull
+ public ActionSheetDialog addSheetItem(String strItem, SheetItemColor color,
+ OnSheetItemClickListener listener) {
+ if (mSheetItemList == null) {
+ mSheetItemList = new ArrayList();
+ }
+ mSheetItemList.add(new SheetItem(strItem, color, listener));
+ return this;
+ }
+
+ /**
+ * 设置条目布局
+ */
+ private void setSheetItems() {
+ if (mSheetItemList == null || mSheetItemList.size() <= 0) {
+ return;
+ }
+
+ int size = mSheetItemList.size();
+
+ // TODO 高度控制,非最佳解决办法
+ // 添加条目过多的时候控制高度
+ if (size >= 7) {
+ LayoutParams params = (LayoutParams) mScrollContent
+ .getLayoutParams();
+ params.height = mDisplay.getHeight() / 2;
+ mScrollContent.setLayoutParams(params);
+ }
+
+ // 循环添加条目
+ for (int i = 1; i <= size; i++) {
+ final int index = i;
+ SheetItem sheetItem = mSheetItemList.get(i - 1);
+ String strItem = sheetItem.name;
+ SheetItemColor color = sheetItem.color;
+ final OnSheetItemClickListener listener = (OnSheetItemClickListener) sheetItem.itemClickListener;
+
+ TextView textView = new TextView(mContext);
+ textView.setText(strItem);
+ textView.setTextSize(18);
+ textView.setGravity(Gravity.CENTER);
+
+ // 背景图片
+ if (size == 1) {
+ if (mShowTitle) {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_bottom_selector);
+ } else {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_single_selector);
+ }
+ } else {
+ if (mShowTitle) {
+ if (i >= 1 && i < size) {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_middle_selector);
+ } else {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_bottom_selector);
+ }
+ } else {
+ if (i == 1) {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_top_selector);
+ } else if (i < size) {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_middle_selector);
+ } else {
+ textView.setBackgroundResource(R.drawable.ugckit_actionsheet_bottom_selector);
+ }
+ }
+ }
+
+ // 字体颜色
+ if (color == null) {
+ textView.setTextColor(Color.parseColor(SheetItemColor.Blue
+ .getName()));
+ } else {
+ textView.setTextColor(Color.parseColor(color.getName()));
+ }
+
+ // 高度
+ float scale = mContext.getResources().getDisplayMetrics().density;
+ int height = (int) (45 * scale + 0.5f);
+ textView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, height));
+
+ // 点击事件
+ textView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ listener.onClick(index);
+ mDialog.dismiss();
+ }
+ });
+
+ mLayoutContent.addView(textView);
+ }
+ }
+
+ public void show() {
+ setSheetItems();
+ mDialog.show();
+ }
+
+ public interface OnSheetItemClickListener {
+ void onClick(int which);
+ }
+
+ public class SheetItem {
+ String name;
+ OnSheetItemClickListener itemClickListener;
+ SheetItemColor color;
+
+ public SheetItem(String name, SheetItemColor color,
+ OnSheetItemClickListener itemClickListener) {
+ this.name = name;
+ this.color = color;
+ this.itemClickListener = itemClickListener;
+ }
+ }
+
+ public enum SheetItemColor {
+ Blue("#037BFF"), Red("#FD4A2E");
+
+ private String name;
+
+ SheetItemColor(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialog/ProgressDialogUtil.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialog/ProgressDialogUtil.java
new file mode 100644
index 0000000..c904385
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialog/ProgressDialogUtil.java
@@ -0,0 +1,34 @@
+package com.tencent.qcloud.ugckit.component.dialog;
+
+import android.app.ProgressDialog;
+import android.content.Context;
+
+public class ProgressDialogUtil {
+
+ private Context mContext;
+ private ProgressDialog mProgressDialog;
+
+ public ProgressDialogUtil(Context context) {
+ mContext = context;
+ }
+
+ public void showProgressDialog() {
+ mProgressDialog = new ProgressDialog(mContext);
+ mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialog.setCancelable(false);
+ mProgressDialog.setCanceledOnTouchOutside(false);
+ mProgressDialog.show();
+ }
+
+ public void dismissProgressDialog() {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ }
+ }
+
+ public void setProgressDialogMessage(String message) {
+ if (mProgressDialog != null) {
+ mProgressDialog.setMessage(message);
+ }
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/ProgressFragmentUtil.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/ProgressFragmentUtil.java
new file mode 100644
index 0000000..a67ee26
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/ProgressFragmentUtil.java
@@ -0,0 +1,81 @@
+package com.tencent.qcloud.ugckit.component.dialogfragment;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+import android.view.View;
+
+
+import com.tencent.qcloud.ugckit.utils.BackgroundTasks;
+
+public class ProgressFragmentUtil {
+ private String mTitle;
+ private FragmentActivity mActivity;
+ private VideoWorkProgressFragment mLoadingProgress;
+
+ public ProgressFragmentUtil(FragmentActivity context) {
+ mActivity = context;
+ }
+
+ /**
+ * 带标题的进度条
+ *
+ * @param context
+ * @param title
+ */
+ public ProgressFragmentUtil(FragmentActivity context, String title) {
+ mActivity = context;
+ mTitle = title;
+ }
+
+ /**
+ * 显示生成进度条
+ */
+ public void showLoadingProgress(@Nullable final IProgressListener listener) {
+ if (mLoadingProgress == null) {
+ mLoadingProgress = VideoWorkProgressFragment.newInstance(mTitle);
+ mLoadingProgress.setOnClickStopListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mLoadingProgress.setProgress(0);
+ if (listener != null) {
+ listener.onStop();
+ }
+ }
+ });
+ }
+ mLoadingProgress.setProgress(0);
+ mLoadingProgress.setCancelable(false);
+
+ mLoadingProgress.show(mActivity.getSupportFragmentManager(), "progress_dialog");
+ }
+
+ /**
+ * 生成进度条取消
+ */
+ public void dismissLoadingProgress() {
+ if (mLoadingProgress != null) {
+ mLoadingProgress.setProgress(0);
+ mLoadingProgress.dismiss();
+ }
+ }
+
+ /**
+ * 更新生成进度条进度
+ *
+ * @param progress
+ */
+ public void updateGenerateProgress(final int progress) {
+ BackgroundTasks.getInstance().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mLoadingProgress != null) {
+ mLoadingProgress.setProgress(progress);
+ }
+ }
+ });
+ }
+
+ public interface IProgressListener {
+ void onStop();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/TCConfirmDialog.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/TCConfirmDialog.java
new file mode 100644
index 0000000..204222b
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/TCConfirmDialog.java
@@ -0,0 +1,134 @@
+package com.tencent.qcloud.ugckit.component.dialogfragment;
+
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+
+import com.tencent.qcloud.ugckit.utils.ScreenUtils;
+import com.tencent.qcloud.ugckit.R;
+
+
+public class TCConfirmDialog extends DialogFragment {
+ private static final String KEY_TITLE = "key_title";
+ private static final String KEY_MSG = "key_msg";
+ private static final String KEY_CANCEL = "key_cancel";
+ private static final String KEY_SURE_TXT = "key_sure_txt";
+ private static final String KEY_CANCEL_TXT = "key_cancel_txt";
+
+ private TextView mTextTitle;
+ private TextView mTextContent;
+ private TextView mTextSure;
+ private TextView mTextCancel;
+
+ @NonNull
+ public static TCConfirmDialog newInstance(String title, String msg, boolean isHaveCancel, String sureTxt, String cancalTxt) {
+ TCConfirmDialog dialog = new TCConfirmDialog();
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_TITLE, title);
+ bundle.putString(KEY_MSG, msg);
+ bundle.putString(KEY_SURE_TXT, sureTxt);
+ bundle.putString(KEY_CANCEL_TXT, cancalTxt);
+ bundle.putBoolean(KEY_CANCEL, isHaveCancel);
+ dialog.setArguments(bundle);
+ return dialog;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ Window window = dialog.getWindow();
+ if (window != null)
+ window.setLayout((int) (ScreenUtils.getScreenWidth(dialog.getContext()) * 0.9),//设置宽度最小为 90%
+ WindowManager.LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ /**
+ * 去掉标题栏
+ */
+ private void setDialogStyle() {
+ getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
+ if (getDialog().getWindow() != null)
+ getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ setDialogStyle();
+ return inflater.inflate(R.layout.ugckit_fragment_confirm, container, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ String title = getArguments().getString(KEY_TITLE);
+ String msg = getArguments().getString(KEY_MSG, "");
+ boolean isHaveCancel = getArguments().getBoolean(KEY_CANCEL, true);
+ String cancelTxt = getArguments().getString(KEY_CANCEL_TXT);
+ String sureTxt = getArguments().getString(KEY_SURE_TXT);
+
+ mTextTitle = (TextView) view.findViewById(R.id.confirm_tv_title);
+ mTextTitle.setText(title);
+
+ mTextContent = (TextView) view.findViewById(R.id.confirm_et_content);
+ mTextContent.setText(msg);
+ mTextSure = (TextView) view.findViewById(R.id.confirm_tv_done);
+ if (sureTxt != null) {
+ mTextSure.setText(sureTxt);
+ }
+ mTextSure.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ if (mCallback != null)
+ mCallback.onSureCallback();
+ }
+ });
+
+ mTextCancel = (TextView) view.findViewById(R.id.confirm_tv_cancel);
+ if (!isHaveCancel) {
+ mTextCancel.setVisibility(View.GONE);
+ } else {
+ mTextCancel.setVisibility(View.VISIBLE);
+ }
+ if (cancelTxt != null) {
+ mTextCancel.setText(cancelTxt);
+ }
+ mTextCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ if (mCallback != null) {
+ mCallback.onCancelCallback();
+ }
+ }
+ });
+ }
+
+ private OnConfirmCallback mCallback;
+
+ public void setOnConfirmCallback(OnConfirmCallback callback) {
+ mCallback = callback;
+ }
+
+ public interface OnConfirmCallback {
+ void onSureCallback();
+
+ void onCancelCallback();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/TCWordInputDialog.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/TCWordInputDialog.java
new file mode 100644
index 0000000..624be06
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/TCWordInputDialog.java
@@ -0,0 +1,152 @@
+package com.tencent.qcloud.ugckit.component.dialogfragment;
+
+import android.app.Dialog;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentTransaction;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.TextView;
+
+
+import com.tencent.qcloud.ugckit.utils.ScreenUtils;
+import com.tencent.qcloud.ugckit.utils.ToastUtil;
+import com.tencent.qcloud.ugckit.R;
+
+
+import java.lang.ref.WeakReference;
+
+/**
+ * 字幕输入框
+ */
+public class TCWordInputDialog extends DialogFragment implements View.OnClickListener {
+ private static final String TAG = "TCWordInputDialog";
+ private TextView mTextSure;
+ private TextView mTextCancel;
+ private EditText mEditContent;
+ @Nullable
+ private String mDefaultText;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+ setDialogStyle();//去掉标题栏
+ return inflater.inflate(R.layout.ugckit_fragment_input_word, null, false);
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mTextCancel = (TextView) view.findViewById(R.id.word_tv_cancel);
+ mTextCancel.setOnClickListener(this);
+ mTextSure = (TextView) view.findViewById(R.id.word_tv_done);
+ mTextSure.setOnClickListener(this);
+ mEditContent = (EditText) view.findViewById(R.id.word_et_content);
+ if (!TextUtils.isEmpty(mDefaultText)) {
+ mEditContent.setText(mDefaultText);
+ }
+ }
+
+ @Override
+ public void onClick(@NonNull View v) {
+ int i = v.getId();
+ if (i == R.id.word_tv_cancel) {
+ onClickCancel();
+
+ } else if (i == R.id.word_tv_done) {
+ String text = mEditContent.getText().toString();
+ if (TextUtils.isEmpty(text)) {
+
+ ToastUtil.toastShortMessage(getResources().getString(R.string.ugckit_word_input_dialog_please_enter_subtitles));
+ return;
+ }
+ onClickSure();
+
+ }
+ }
+
+ private void onClickCancel() {
+ mEditContent.setText("");
+ dismissDialog();
+ if (mWefCallback.get() != null) {
+ mWefCallback.get().onInputCancel();
+ }
+ }
+
+ private void onClickSure() {
+ String text = mEditContent.getText().toString();
+ dismissDialog();
+ if (mWefCallback.get() != null) {
+ mWefCallback.get().onInputSure(text);
+ }
+ }
+
+
+ private void dismissDialog() {
+ try {
+ dismiss();
+ } catch (Exception e) {
+ if (getFragmentManager() != null && isAdded()) {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.remove(this);
+ transaction.commitAllowingStateLoss();
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Dialog dialog = getDialog();
+ if (dialog != null) {
+ Window window = dialog.getWindow();
+ if (window != null)
+ window.setLayout((int) (ScreenUtils.getScreenWidth(dialog.getContext()) * 0.9),//设置宽度最小为 90%
+ WindowManager.LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ private void setDialogStyle() {
+ getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
+ if (getDialog().getWindow() != null)
+ getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ }
+
+ private WeakReference mWefCallback;
+
+ public void setOnWordInputCallback(OnWordInputCallback callback) {
+ mWefCallback = new WeakReference(callback);
+ }
+
+ public void setDefaultText(@Nullable final String defaultText) {
+ Log.i(TAG, "setDefaultText: defaultText = " + defaultText);
+ if (defaultText != null) {
+ mDefaultText = defaultText;
+ if (mEditContent != null) {
+ mEditContent.post(new Runnable() {
+ @Override
+ public void run() {
+ mEditContent.setText(defaultText);
+ }
+ });
+ }
+ }
+ }
+
+ public interface OnWordInputCallback {
+ void onInputSure(String text);
+
+ void onInputCancel();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/VideoWorkProgressFragment.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/VideoWorkProgressFragment.java
new file mode 100644
index 0000000..affa609
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/dialogfragment/VideoWorkProgressFragment.java
@@ -0,0 +1,151 @@
+package com.tencent.qcloud.ugckit.component.dialogfragment;
+
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentManager;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+
+import com.tencent.qcloud.ugckit.R;
+import com.tencent.qcloud.ugckit.component.progressbar.NumberProgressBar;
+
+public class VideoWorkProgressFragment extends DialogFragment {
+ private static final String KEY_TITLE = "key_title";
+ private View mViewContent;
+ private ImageView mImageStop;
+ private TextView mTextTips;
+ private NumberProgressBar mProgressLoading;
+ private View.OnClickListener mListener;
+ private int mProgress;
+ private boolean mCanCancel = true;
+
+ @NonNull
+ public static VideoWorkProgressFragment newInstance(String title) {
+ VideoWorkProgressFragment fragment = new VideoWorkProgressFragment();
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_TITLE, title);
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setStyle(R.style.UGCKitConfirmDialogStyle, R.style.UGCKitDialogFragmentStyle);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+ mViewContent = inflater.inflate(R.layout.ugckit_layout_joiner_progress, null);
+ mTextTips = (TextView) mViewContent.findViewById(R.id.joiner_tv_msg);
+ Bundle bundle = getArguments();
+ if (bundle != null) {
+ String msg = bundle.getString(KEY_TITLE);
+ if (!TextUtils.isEmpty(msg)) {
+ mTextTips.setText(msg);
+ }
+ }
+ mImageStop = (ImageView) mViewContent.findViewById(R.id.joiner_iv_stop);
+ mProgressLoading = (NumberProgressBar) mViewContent.findViewById(R.id.joiner_pb_loading);
+ mProgressLoading.setMax(100);
+ mProgressLoading.setProgress(mProgress);
+ mImageStop.setOnClickListener(mListener);
+ if (mCanCancel) {
+ mImageStop.setVisibility(View.VISIBLE);
+ } else {
+ mImageStop.setVisibility(View.INVISIBLE);
+ }
+ getDialog().setOnKeyListener(new DialogInterface.OnKeyListener() {
+ @Override
+ public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return true;
+ }
+ return false;
+ }
+ });
+ return mViewContent;
+ }
+
+
+ /**
+ * 设置停止按钮的监听
+ *
+ * @param listener
+ */
+ public void setOnClickStopListener(View.OnClickListener listener) {
+ if (mImageStop == null) {
+ mListener = listener;
+ } else {
+ mListener = listener;
+ mImageStop.setOnClickListener(listener);
+ }
+ }
+
+ /**
+ * 设置进度条
+ *
+ * @param progress
+ */
+ public void setProgress(int progress) {
+ if (mProgressLoading == null) {
+ mProgress = progress;
+ return;
+ }
+ mProgressLoading.setProgress(progress);
+ }
+
+ @Override
+ public void show(FragmentManager manager, String tag) {
+ try {
+ if (!isAdded() && null == manager.findFragmentByTag(tag)) {
+ manager.beginTransaction().add(this, tag).commitAllowingStateLoss();
+ } else {
+ manager.beginTransaction().show(this).commitAllowingStateLoss();
+ }
+ //Fragment already added FIXBUG:commit()并不立即执行transaction中包含的动作,而是把它加入到UI线程队列中.
+ //如果想要立即执行,可以在commit之后立即调用FragmentManager的executePendingTransactions()方法
+ manager.executePendingTransactions();
+ } catch (Exception e) {
+ e.printStackTrace();
+ try {
+ manager.beginTransaction().remove(this).add(this, tag).commitAllowingStateLoss();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ // 和show对应
+ if (getFragmentManager() != null && isAdded()) {
+ getFragmentManager().beginTransaction().remove(this).commitAllowingStateLoss();
+ }
+ if (mProgressLoading != null) {
+ mProgressLoading.setProgress(0);
+ }
+ }
+
+ public void setCanCancel(boolean canCancel) {
+ mCanCancel = canCancel;
+ if (mImageStop == null) {
+ } else {
+ if (canCancel) {
+ mImageStop.setVisibility(View.VISIBLE);
+ } else {
+ mImageStop.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/floatlayer/FloatLayerView.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/floatlayer/FloatLayerView.java
new file mode 100644
index 0000000..8245267
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/floatlayer/FloatLayerView.java
@@ -0,0 +1,973 @@
+package com.tencent.qcloud.ugckit.component.floatlayer;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+import com.tencent.qcloud.ugckit.module.effect.IFloatLayerView;
+import com.tencent.qcloud.ugckit.R;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 文字操作的View(基于Bitmap的内容,所以一切Bitmap都支持)
+ *
+ * 单手进行缩放,旋转,平移操作
+ */
+public class FloatLayerView extends View implements IFloatLayerView {
+ /**
+ * 控制缩放,旋转图标所在四个点得位置
+ */
+ private static final int LEFT_TOP = 0;
+ private static final int RIGHT_TOP = 1;
+ private static final int RIGHT_BOTTOM = 2;
+ private static final int LEFT_BOTTOM = 3;
+
+ /**
+ * 一些默认的常量
+ */
+ private static final int DEFAULT_FRAME_PADDING = 0;
+ private static final int DEFAULT_FRAME_WIDTH = 2;
+ private static final int DEFAULT_FRAME_COLOR = Color.WHITE;
+ private static final float DEFAULT_SCALE = 1.0f;
+ private static final float DEFAULT_DEGREE = 0;
+ private static final int DEFAULT_CONTROL_LOCATION = RIGHT_TOP;
+ private static final boolean DEFAULT_EDITABLE = true;
+ private static final int DEFAULT_OTHER_DRAWABLE_WIDTH = 50;
+ private static final int DEFAULT_OTHER_DRAWABLE_HEIGHT = 50;
+
+ private boolean isMeasured;
+
+ /**
+ * 用于旋转缩放的Bitmap
+ */
+ @Nullable
+ private Bitmap mBitmap;
+
+ /**
+ * SingleTouchView的中心点坐标,相对于其父类布局而言的
+ */
+ @NonNull
+ private PointF mCenterPoint = new PointF();
+
+ /**
+ * View的宽度和高度,随着图片的旋转而变化(不包括控制旋转,缩放图片的宽高)
+ */
+ private int mViewWidth, mViewHeight;
+
+ /**
+ * 图片的旋转角度
+ */
+ private float mDegree = DEFAULT_DEGREE;
+
+ /**
+ * 图片的缩放比例
+ */
+ private float mScale = DEFAULT_SCALE;
+
+ /**
+ * 用于缩放,旋转,平移的矩阵
+ */
+ @NonNull
+ private Matrix matrix = new Matrix();
+
+ /**
+ * SingleTouchView距离父类布局的左间距
+ */
+ private int mViewPaddingLeft;
+
+ /**
+ * SingleTouchView距离父类布局的上间距
+ */
+ private int mViewPaddingTop;
+
+ /**
+ * 图片四个点坐标
+ */
+ private Point mLTPoint;
+ private Point mRTPoint;
+ private Point mRBPoint;
+ private Point mLBPoint;
+ /**
+ * 用于编辑的控制点的坐标
+ */
+ private Point mEditPoint = new Point();
+
+ @Nullable
+ private Drawable mEditDrawble;
+
+ private int mEditDrawableWidth, mEditDrawableHeight;
+
+ /**
+ * 用于删除的控制点的坐标
+ */
+ private Point mDeletePoint = new Point();
+
+ @Nullable
+ private Drawable mDeleteDrawable;
+
+ private int mDeleteDrawbleWidth, mDeleteDrawableHeight;
+
+ /**
+ * 用于缩放,旋转的控制点的坐标
+ */
+ private Point mControlPoint = new Point();
+
+ /**
+ * 用于缩放,旋转的图标
+ */
+ @Nullable
+ private Drawable mRotateDrawable;
+
+ /**
+ * 缩放,旋转图标的宽和高
+ */
+ private int mRotateDrawableWidth, mRotateDrawableHeight;
+
+ /**
+ * 画外围框的Path
+ */
+ @NonNull
+ private Path mPath = new Path();
+
+ /**
+ * 画外围框的画笔
+ */
+ private Paint mPaint;
+
+ /**
+ * 初始状态
+ */
+ private static final int STATUS_INIT = 0;
+
+ /**
+ * 拖动状态
+ */
+ private static final int STATUS_DRAG = 1;
+
+ /**
+ * 旋转或者放大状态
+ */
+ private static final int STATUS_ROTATE_ZOOM = 2;
+ /**
+ * 点击编辑状态
+ */
+ private static final int STATUS_EDIT = 3;
+ /**
+ * 点击删除状态
+ */
+ private static final int STATUS_DELETE = 4;
+
+ /**
+ * 当前所处的状态
+ */
+ private int mStatus = STATUS_INIT;
+
+ /**
+ * 外边框与图片之间的间距, 单位是dip
+ */
+ private int framePadding = DEFAULT_FRAME_PADDING;
+
+ /**
+ * 外边框颜色
+ */
+ private int frameColor = DEFAULT_FRAME_COLOR;
+
+ /**
+ * 外边框线条粗细, 单位是 dip
+ */
+ private int frameWidth = DEFAULT_FRAME_WIDTH;
+
+ /**
+ * 是否处于可以缩放,平移,旋转状态
+ */
+ private boolean isEditable = DEFAULT_EDITABLE;
+
+ private DisplayMetrics metrics;
+
+
+ @NonNull
+ private PointF mPreMovePointF = new PointF();
+ @NonNull
+ private PointF mCurMovePointF = new PointF();
+
+ /**
+ * 图片在旋转时x方向的偏移量
+ */
+ private int offsetX;
+ /**
+ * 图片在旋转时y方向的偏移量
+ */
+ private int offsetY;
+
+ /**
+ * 控制图标所在的位置(比如左上,右上,左下,右下)
+ */
+ private int mControlLocation = RIGHT_BOTTOM;
+
+ private int mEditLocation = RIGHT_TOP;
+
+ private int mDeleteLocatoin = LEFT_TOP;
+ private int mX;
+ private int mY;
+ private int width;
+
+ private long mStartTime, mEndTime;
+
+ private boolean mShowDelete = true;
+ private boolean mShowEdit = true;
+
+ @Override
+ public void showDelete(boolean showDelete) {
+ mShowDelete = showDelete;
+ }
+
+ @Override
+ public void showEdit(boolean showEdit) {
+ mShowEdit = showEdit;
+ }
+
+ @Override
+ public void setStartToEndTime(long startTime, long endTime) {
+ mStartTime = startTime;
+ mEndTime = endTime;
+ }
+
+ @Override
+ public long getStartTime() {
+ return mStartTime;
+ }
+
+ @Override
+ public long getEndTime() {
+ return mEndTime;
+ }
+
+ public FloatLayerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FloatLayerView(Context context) {
+ this(context, null);
+ }
+
+ public FloatLayerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ obtainStyledAttributes(attrs);
+ init();
+ }
+
+ /**
+ * 获取自定义属性
+ *
+ * @param attrs
+ */
+ private void obtainStyledAttributes(AttributeSet attrs) {
+ metrics = getContext().getResources().getDisplayMetrics();
+ framePadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_PADDING, metrics);
+ frameWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_WIDTH, metrics);
+
+ TypedArray mTypedArray = getContext().obtainStyledAttributes(attrs, R.styleable.UGCKitFloatLayerView);
+
+ Drawable srcDrawable = mTypedArray.getDrawable(R.styleable.UGCKitFloatLayerView_src);
+ mBitmap = drawable2Bitmap(srcDrawable);
+
+ framePadding = mTypedArray.getDimensionPixelSize(R.styleable.UGCKitFloatLayerView_framePadding, framePadding);
+ frameWidth = mTypedArray.getDimensionPixelSize(R.styleable.UGCKitFloatLayerView_frameWidth, frameWidth);
+ frameColor = mTypedArray.getColor(R.styleable.UGCKitFloatLayerView_frameColor, DEFAULT_FRAME_COLOR);
+ mScale = mTypedArray.getFloat(R.styleable.UGCKitFloatLayerView_scale, DEFAULT_SCALE);
+ mDegree = mTypedArray.getFloat(R.styleable.UGCKitFloatLayerView_degree, DEFAULT_DEGREE);
+ mRotateDrawable = mTypedArray.getDrawable(R.styleable.UGCKitFloatLayerView_controlDrawable);
+ mControlLocation = mTypedArray.getInt(R.styleable.UGCKitFloatLayerView_controlLocation, RIGHT_BOTTOM);
+
+ mEditDrawble = mTypedArray.getDrawable(R.styleable.UGCKitFloatLayerView_editDrawable);
+ mEditLocation = mTypedArray.getInt(R.styleable.UGCKitFloatLayerView_editLocation, RIGHT_TOP);
+
+ mDeleteDrawable = mTypedArray.getDrawable(R.styleable.UGCKitFloatLayerView_deleteDrawable);
+ mDeleteLocatoin = mTypedArray.getInt(R.styleable.UGCKitFloatLayerView_deleteLocation, LEFT_TOP);
+
+ isEditable = mTypedArray.getBoolean(R.styleable.UGCKitFloatLayerView_editable, DEFAULT_EDITABLE);
+
+ mTypedArray.recycle();
+ }
+
+ private void init() {
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(frameColor);
+ mPaint.setStrokeWidth(frameWidth);
+ mPaint.setStyle(Paint.Style.STROKE);
+
+ if (mRotateDrawable != null) {
+ mRotateDrawableWidth = (int) (mRotateDrawable.getIntrinsicWidth() * 1.5);
+ mRotateDrawableHeight = (int) (mRotateDrawable.getIntrinsicHeight() * 1.5);
+ }
+ if (mEditDrawble != null) {
+ mEditDrawableWidth = (int) (mEditDrawble.getIntrinsicWidth() * 1.5);
+ mEditDrawableHeight = (int) (mEditDrawble.getIntrinsicHeight() * 1.5);
+ }
+ if (mDeleteDrawable != null) {
+ mDeleteDrawbleWidth = (int) (mDeleteDrawable.getIntrinsicWidth() * 1.5);
+ mDeleteDrawableHeight = (int) (mDeleteDrawable.getIntrinsicHeight() * 1.5);
+ }
+
+ transformDraw();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (!isMeasured) {
+ //获取SingleTouchView所在父布局的中心点
+ ViewGroup mViewGroup = (ViewGroup) getParent();
+ if (null != mViewGroup) {
+ int parentWidth = mViewGroup.getWidth();
+ int parentHeight = mViewGroup.getHeight();
+ mCenterPoint.set(parentWidth / 2, parentHeight / 2);
+ }
+ isMeasured = true;
+ adjustLayout();
+ }
+ }
+
+ /**
+ * 调整View的大小,位置
+ */
+ private void adjustLayout() {
+ if (mCenterPoint.x <= 0 || mCenterPoint.y <= 0) {
+ mCenterPoint.set(mCenterX, mCenterY);
+ }
+ int actualWidth = mViewWidth + mRotateDrawableWidth;
+ int actualHeight = mViewHeight + mRotateDrawableHeight;
+ int newPaddingLeft = (int) (mCenterPoint.x - actualWidth / 2);
+ int newPaddingTop = (int) (mCenterPoint.y - actualHeight / 2);
+
+ if (mViewPaddingLeft != newPaddingLeft || mViewPaddingTop != newPaddingTop) {
+ mViewPaddingLeft = newPaddingLeft;
+ mViewPaddingTop = newPaddingTop;
+ }
+ layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth, newPaddingTop + actualHeight);
+ this.mX = newPaddingLeft + mRotateDrawableWidth / 2;
+ this.mY = newPaddingTop + mRotateDrawableHeight / 2;
+ this.width = mViewWidth;
+ }
+
+ @Nullable
+ private IOperationViewClickListener mClickListener = null;
+
+ @Override
+ public void setIOperationViewClickListener(IOperationViewClickListener listener) {
+ mClickListener = listener;
+ }
+
+ @Override
+ public void setPadding(int padding) {
+ framePadding = padding;
+ }
+
+ @Override
+ public void setBorderWidth(int borderWidth) {
+ frameWidth = borderWidth;
+ }
+
+ @Override
+ public void setBorderColor(int color) {
+ frameColor = getResources().getColor(color);
+ }
+
+ @Override
+ public void setEditIconResource(int resid) {
+ mEditDrawble = getResources().getDrawable(resid);
+ }
+
+ @Override
+ public void setRotateIconResource(int resid) {
+ mRotateDrawable = getResources().getDrawable(resid);
+ }
+
+ /**
+ * 设置旋转图
+ *
+ * @param bitmap
+ */
+ @Override
+ public void setImageBitamp(@Nullable Bitmap bitmap) {
+ if (this.mBitmap != null && bitmap != null && !this.mBitmap.equals(bitmap)) {
+ this.mBitmap.recycle();
+ }
+ this.mBitmap = bitmap;
+ transformDraw();
+ }
+
+ @Nullable
+ public Bitmap getImageBitmap() {
+ return this.mBitmap;
+ }
+
+ public Bitmap getRotateBitmap() {
+ Bitmap bitmap = Bitmap.createBitmap(this.mBitmap, 0, 0, this.mBitmap.getWidth(), this.mBitmap.getHeight(), matrix, true);
+ return bitmap;
+ }
+
+ public int getImageX() {
+ return this.mX;
+ }
+
+ public int getImageY() {
+ return this.mY;
+ }
+
+ public int getImageWidth() {
+ return this.width;
+ }
+
+ /**
+ * 设置旋转图
+ *
+ * @param drawable
+ */
+ public void setImageDrawable(Drawable drawable) {
+ this.mBitmap = drawable2Bitmap(drawable);
+ transformDraw();
+ }
+
+ /**
+ * 从Drawable中获取Bitmap对象
+ *
+ * @param drawable
+ * @return
+ */
+ private Bitmap drawable2Bitmap(@Nullable Drawable drawable) {
+ try {
+ if (drawable == null) {
+ return null;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ int intrinsicWidth = drawable.getIntrinsicWidth();
+ int intrinsicHeight = drawable.getIntrinsicHeight();
+ Bitmap bitmap = Bitmap.createBitmap(intrinsicWidth <= 0 ? DEFAULT_OTHER_DRAWABLE_WIDTH
+ : intrinsicWidth, intrinsicHeight <= 0 ? DEFAULT_OTHER_DRAWABLE_HEIGHT
+ : intrinsicHeight, Bitmap.Config.ARGB_8888);
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ } catch (OutOfMemoryError e) {
+ return null;
+ }
+
+ }
+
+ /**
+ * 根据id设置旋转图
+ *
+ * @param resId
+ */
+ public void setImageResource(int resId) {
+ Drawable drawable = getContext().getResources().getDrawable(resId);
+ setImageDrawable(drawable);
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ //每次draw之前调整View的位置和大小
+ super.onDraw(canvas);
+
+ if (mBitmap == null) return;
+ canvas.drawBitmap(mBitmap, matrix, mPaint);
+
+
+ //处于可编辑状态才画边框和控制图标
+ if (isEditable) {
+ mPath.reset();
+ mPath.moveTo(mLTPoint.x, mLTPoint.y);
+ mPath.lineTo(mRTPoint.x, mRTPoint.y);
+ mPath.lineTo(mRBPoint.x, mRBPoint.y);
+ mPath.lineTo(mLBPoint.x, mLBPoint.y);
+ mPath.lineTo(mLTPoint.x, mLTPoint.y);
+ mPath.lineTo(mRTPoint.x, mRTPoint.y);
+ canvas.drawPath(mPath, mPaint);
+ //画旋转, 缩放图标
+
+ if (mRotateDrawable != null) {
+ mRotateDrawable.setBounds(mControlPoint.x - mRotateDrawableWidth / 2,
+ mControlPoint.y - mRotateDrawableHeight / 2, mControlPoint.x + mRotateDrawableWidth
+ / 2, mControlPoint.y + mRotateDrawableHeight / 2);
+ mRotateDrawable.draw(canvas);
+ }
+
+ if (mEditDrawble != null && mShowEdit) {
+ mEditDrawble.setBounds(mEditPoint.x - mRotateDrawableWidth / 2,
+ mEditPoint.y - mRotateDrawableHeight / 2, mEditPoint.x + mRotateDrawableWidth
+ / 2, mEditPoint.y + mRotateDrawableHeight / 2);
+ mEditDrawble.draw(canvas);
+ }
+
+ if (mDeleteDrawable != null && mShowDelete) {
+ mDeleteDrawable.setBounds(mDeletePoint.x - mRotateDrawableWidth / 2,
+ mDeletePoint.y - mRotateDrawableHeight / 2, mDeletePoint.x + mRotateDrawableWidth
+ / 2, mDeletePoint.y + mRotateDrawableHeight / 2);
+ mDeleteDrawable.draw(canvas);
+ }
+ }
+ adjustLayout();
+ }
+
+
+ /**
+ * 设置Matrix, 强制刷新
+ */
+ private void transformDraw() {
+ if (mBitmap == null) return;
+ int bitmapWidth = (int) (mBitmap.getWidth() * mScale);
+ int bitmapHeight = (int) (mBitmap.getHeight() * mScale);
+ computeRect(-framePadding, -framePadding, bitmapWidth + framePadding, bitmapHeight + framePadding, mDegree);
+
+ //设置缩放比例
+ matrix.setScale(mScale, mScale);
+ //绕着图片中心进行旋转
+ matrix.postRotate(mDegree % 360, bitmapWidth / 2, bitmapHeight / 2);
+ //设置画该图片的起始点
+ matrix.postTranslate(offsetX + mRotateDrawableWidth / 2, offsetY + mRotateDrawableHeight / 2);
+
+ adjustLayout();
+ }
+
+ private float mDownX, mDownY;
+
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ if (!isEditable) {
+ return super.onTouchEvent(event);
+ }
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mPreMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
+
+ mStatus = judgeStatus(event.getX(), event.getY());
+ if (mStatus == STATUS_DRAG) {
+ mDownX = event.getX();
+ mDownY = event.getY();
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mClickListener != null/* && (mStatus == STATUS_DELETE || mStatus == STATUS_EDIT)*/) {
+ //再次判定抬起点 是否处于icon的范围之内
+ int secondJudgeState = judgeStatus(event.getX(), event.getY());
+ //满足才触发回调
+ if (mStatus == STATUS_EDIT && secondJudgeState == mStatus) {
+ mClickListener.onEditClick();
+ }
+ if (mStatus == STATUS_DELETE && secondJudgeState == mStatus) {
+ mClickListener.onDeleteClick();
+ }
+ if (mStatus == STATUS_ROTATE_ZOOM || mStatus == STATUS_DRAG) {
+ mClickListener.onRotateClick();
+ }
+ }
+ if (mStatus == STATUS_DRAG && mDownX == event.getX() && mDownY == event.getY()) {
+ // 响应点击事件
+ performClick();
+ }
+ mStatus = STATUS_INIT;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mCurMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
+ if (mStatus == STATUS_ROTATE_ZOOM) {
+ float scale = 1f;
+
+ int halfBitmapWidth = mBitmap.getWidth() / 2;
+ int halfBitmapHeight = mBitmap.getHeight() / 2;
+
+ //图片某个点到图片中心的距离
+ float bitmapToCenterDistance = (float) Math.sqrt(halfBitmapWidth * halfBitmapWidth + halfBitmapHeight * halfBitmapHeight);
+
+ //移动的点到图片中心的距离
+ float moveToCenterDistance = distance4PointF(mCenterPoint, mCurMovePointF);
+
+ //计算缩放比例
+ scale = moveToCenterDistance / bitmapToCenterDistance;
+
+
+ //缩放比例的界限判断
+ if (scale <= MIN_SCALE) {
+ scale = MIN_SCALE;
+ } else if (scale >= MAX_SCALE) {
+ scale = MAX_SCALE;
+ }
+
+
+ // 角度
+ double a = distance4PointF(mCenterPoint, mPreMovePointF);
+ double b = distance4PointF(mPreMovePointF, mCurMovePointF);
+ double c = distance4PointF(mCenterPoint, mCurMovePointF);
+
+ double cosb = (a * a + c * c - b * b) / (2 * a * c);
+
+ if (cosb >= 1) {
+ cosb = 1f;
+ }
+
+ double radian = Math.acos(cosb);
+ float newDegree = (float) radianToDegree(radian);
+
+ //center -> proMove的向量, 我们使用PointF来实现
+ PointF centerToProMove = new PointF((mPreMovePointF.x - mCenterPoint.x), (mPreMovePointF.y - mCenterPoint.y));
+
+ //center -> curMove 的向量
+ PointF centerToCurMove = new PointF((mCurMovePointF.x - mCenterPoint.x), (mCurMovePointF.y - mCenterPoint.y));
+
+ //向量叉乘结果, 如果结果为负数, 表示为逆时针, 结果为正数表示顺时针
+ float result = centerToProMove.x * centerToCurMove.y - centerToProMove.y * centerToCurMove.x;
+
+ if (result < 0) {
+ newDegree = -newDegree;
+ }
+
+ mDegree = mDegree + newDegree;
+ mScale = scale;
+
+ transformDraw();
+ } else if (mStatus == STATUS_DRAG) {
+ // 修改中心点
+ mCenterPoint.x += mCurMovePointF.x - mPreMovePointF.x;
+ mCenterPoint.y += mCurMovePointF.y - mPreMovePointF.y;
+ System.out.println(this + "move = " + mCenterPoint);
+
+ adjustLayout();
+ }
+
+ mPreMovePointF.set(mCurMovePointF);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * 获取四个点和View的大小
+ *
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
+ * @param degree
+ */
+ private void computeRect(int left, int top, int right, int bottom, float degree) {
+ Point lt = new Point(left, top);
+ Point rt = new Point(right, top);
+ Point rb = new Point(right, bottom);
+ Point lb = new Point(left, bottom);
+ Point cp = new Point((left + right) / 2, (top + bottom) / 2);
+ mLTPoint = obtainRotationPoint(cp, lt, degree);
+ mRTPoint = obtainRotationPoint(cp, rt, degree);
+ mRBPoint = obtainRotationPoint(cp, rb, degree);
+ mLBPoint = obtainRotationPoint(cp, lb, degree);
+
+ //计算X坐标最大的值和最小的值
+ int maxCoordinateX = getMaxValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);
+ int minCoordinateX = getMinValue(mLTPoint.x, mRTPoint.x, mRBPoint.x, mLBPoint.x);
+
+ mViewWidth = maxCoordinateX - minCoordinateX;
+
+
+ //计算Y坐标最大的值和最小的值
+ int maxCoordinateY = getMaxValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
+ int minCoordinateY = getMinValue(mLTPoint.y, mRTPoint.y, mRBPoint.y, mLBPoint.y);
+
+ mViewHeight = maxCoordinateY - minCoordinateY;
+
+
+ //View中心点的坐标
+ Point viewCenterPoint = new Point((maxCoordinateX + minCoordinateX) / 2, (maxCoordinateY + minCoordinateY) / 2);
+
+ offsetX = mViewWidth / 2 - viewCenterPoint.x;
+ offsetY = mViewHeight / 2 - viewCenterPoint.y;
+
+
+ int halfDrawableWidth = mRotateDrawableWidth / 2;
+ int halfDrawableHeight = mRotateDrawableHeight / 2;
+
+ //将Bitmap的四个点的X的坐标移动offsetX + halfDrawableWidth
+ mLTPoint.x += (offsetX + halfDrawableWidth);
+ mRTPoint.x += (offsetX + halfDrawableWidth);
+ mRBPoint.x += (offsetX + halfDrawableWidth);
+ mLBPoint.x += (offsetX + halfDrawableWidth);
+
+ //将Bitmap的四个点的Y坐标移动offsetY + halfDrawableHeight
+ mLTPoint.y += (offsetY + halfDrawableHeight);
+ mRTPoint.y += (offsetY + halfDrawableHeight);
+ mRBPoint.y += (offsetY + halfDrawableHeight);
+ mLBPoint.y += (offsetY + halfDrawableHeight);
+
+ mControlPoint = locatePoint(mControlLocation);
+ mEditPoint = locatePoint(mEditLocation);
+ mDeletePoint = locatePoint(mDeleteLocatoin);
+ }
+
+ /**
+ * 根据位置判断控制图标处于那个点
+ *
+ * @return
+ */
+ private Point locatePoint(int location) {
+ switch (location) {
+ case LEFT_TOP:
+ return mLTPoint;
+ case RIGHT_TOP:
+ return mRTPoint;
+ case RIGHT_BOTTOM:
+ return mRBPoint;
+ case LEFT_BOTTOM:
+ return mLBPoint;
+ }
+ return mLTPoint;
+ }
+
+
+ /**
+ * 获取变长参数最大的值
+ *
+ * @param array
+ * @return
+ */
+ public int getMaxValue(Integer... array) {
+ List list = Arrays.asList(array);
+ Collections.sort(list);
+ return list.get(list.size() - 1);
+ }
+
+ /**
+ * 获取变长参数最大的值
+ *
+ * @param array
+ * @return
+ */
+ public int getMinValue(Integer... array) {
+ List list = Arrays.asList(array);
+ Collections.sort(list);
+ return list.get(0);
+ }
+
+ /**
+ * 获取旋转某个角度之后的点
+ *
+ * @param source
+ * @param degree
+ * @return
+ */
+ @NonNull
+ private Point obtainRotationPoint(@NonNull Point center, @NonNull Point source, float degree) {
+ //两者之间的距离
+ Point disPoint = new Point();
+ disPoint.x = source.x - center.x;
+ disPoint.y = source.y - center.y;
+
+ //没旋转之前的弧度
+ double originRadian = 0;
+
+ //没旋转之前的角度
+ double originDegree = 0;
+
+ //旋转之后的角度
+ double resultDegree = 0;
+
+ //旋转之后的弧度
+ double resultRadian = 0;
+
+ //经过旋转之后点的坐标
+ Point resultPoint = new Point();
+
+ double distance = Math.sqrt(disPoint.x * disPoint.x + disPoint.y * disPoint.y);
+ if (disPoint.x == 0 && disPoint.y == 0) {
+ return center;
+ // 第一象限
+ } else if (disPoint.x >= 0 && disPoint.y >= 0) {
+ // 计算与x正方向的夹角
+ originRadian = Math.asin(disPoint.y / distance);
+
+ // 第二象限
+ } else if (disPoint.x < 0 && disPoint.y >= 0) {
+ // 计算与x正方向的夹角
+ originRadian = Math.asin(Math.abs(disPoint.x) / distance);
+ originRadian = originRadian + Math.PI / 2;
+
+ // 第三象限
+ } else if (disPoint.x < 0 && disPoint.y < 0) {
+ // 计算与x正方向的夹角
+ originRadian = Math.asin(Math.abs(disPoint.y) / distance);
+ originRadian = originRadian + Math.PI;
+ } else if (disPoint.x >= 0 && disPoint.y < 0) {
+ // 计算与x正方向的夹角
+ originRadian = Math.asin(disPoint.x / distance);
+ originRadian = originRadian + Math.PI * 3 / 2;
+ }
+
+ // 弧度换算成角度
+ originDegree = radianToDegree(originRadian);
+ resultDegree = originDegree + degree;
+
+ // 角度转弧度
+ resultRadian = degreeToRadian(resultDegree);
+
+ resultPoint.x = (int) Math.round(distance * Math.cos(resultRadian));
+ resultPoint.y = (int) Math.round(distance * Math.sin(resultRadian));
+ resultPoint.x += center.x;
+ resultPoint.y += center.y;
+
+ return resultPoint;
+ }
+
+ /**
+ * 弧度换算成角度
+ *
+ * @return
+ */
+ private double radianToDegree(double radian) {
+ return radian * 180 / Math.PI;
+ }
+
+ /**
+ * 角度换算成弧度
+ *
+ * @param degree
+ * @return
+ */
+ private double degreeToRadian(double degree) {
+ return degree * Math.PI / 180;
+ }
+
+ /**
+ * 根据点击的位置判断是否点中控制旋转,缩放的图片, 初略的计算
+ *
+ * @param x
+ * @param y
+ * @return
+ */
+ private int judgeStatus(float x, float y) {
+ PointF touchPoint = new PointF(x, y);
+ PointF controlPointF = new PointF(mControlPoint);
+
+ //点击的点到控制旋转,缩放点的距离
+ float distanceToControl = distance4PointF(touchPoint, controlPointF);
+
+ //如果两者之间的距离小于 控制图标的宽度,高度的最小值,则认为点中了控制图标
+ if (distanceToControl < Math.min(mRotateDrawableWidth / 2, mRotateDrawableHeight / 2)) {
+ return STATUS_ROTATE_ZOOM;
+ }
+
+ float disToEdit = distance4PointF(touchPoint, new PointF(mEditPoint));
+ if (disToEdit < Math.min(mEditDrawableWidth / 2, mEditDrawableHeight / 2)) {
+ return STATUS_EDIT;
+ }
+
+ float disToDelete = distance4PointF(touchPoint, new PointF(mDeletePoint));
+ if (disToDelete < Math.min(mDeleteDrawbleWidth / 2, mDeleteDrawableHeight / 2)) {
+ return STATUS_DELETE;
+ }
+ return STATUS_DRAG;
+ }
+
+ public float getImageRotate() {
+ return mDegree;
+ }
+
+ /**
+ * 设置图片旋转角度
+ *
+ * @param degree
+ */
+ public void setImageRotate(float degree) {
+ if (this.mDegree != degree) {
+ this.mDegree = degree;
+ transformDraw();
+ }
+ }
+
+ public float getImageScale() {
+ return mScale;
+ }
+
+ /**
+ * 设置图片缩放比例
+ *
+ * @param scale
+ */
+ public void setImageScale(float scale) {
+ if (this.mScale != scale) {
+ this.mScale = scale;
+ transformDraw();
+ }
+ }
+
+ public boolean isEditable() {
+ return isEditable;
+ }
+
+ /**
+ * 设置是否处于可缩放,平移,旋转状态
+ *
+ * @param isEditable
+ */
+ public void setEditable(boolean isEditable) {
+ this.isEditable = isEditable;
+ invalidate();
+ }
+
+ /**
+ * 两个点之间的距离
+ *
+ * @return
+ */
+ private float distance4PointF(@NonNull PointF pf1, @NonNull PointF pf2) {
+ float disX = pf2.x - pf1.x;
+ float disY = pf2.y - pf1.y;
+ return (float) Math.sqrt(disX * disX + disY * disY);
+ }
+
+ @Override
+ public float getCenterX() {
+ return mCenterPoint.x;
+ }
+
+ @Override
+ public float getCenterY() {
+ return mCenterPoint.y;
+ }
+
+ private float mCenterX, mCenterY;
+
+ @Override
+ public void setCenterX(float x) {
+ mCenterX = x;
+ }
+
+ @Override
+ public void setCenterY(float y) {
+ mCenterY = y;
+ }
+
+}
\ No newline at end of file
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/floatlayer/FloatLayerViewGroup.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/floatlayer/FloatLayerViewGroup.java
new file mode 100644
index 0000000..6d010f5
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/floatlayer/FloatLayerViewGroup.java
@@ -0,0 +1,139 @@
+package com.tencent.qcloud.ugckit.component.floatlayer;
+
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 用于统一管理{@link FloatLayerView}的layout
+ */
+public class FloatLayerViewGroup extends FrameLayout implements View.OnClickListener {
+ private final String TAG = "FloatLayerViewGroup";
+
+ private List mFloatLayerViewList;
+ private int mLastSelectedPos = -1;
+ private boolean mEnableChildSingleClick = true;
+ private boolean mEnableChildDoubleClick = false;
+ long mLastTime = 0;
+ long mCurTime = 0;
+
+ public FloatLayerViewGroup(@NonNull Context context) {
+ super(context);
+ init();
+ }
+
+ public FloatLayerViewGroup(@NonNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public FloatLayerViewGroup(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ mFloatLayerViewList = new ArrayList();
+ }
+
+ public void addOperationView(@NonNull FloatLayerView view) {
+ mFloatLayerViewList.add(view);
+ selectOperationView(mFloatLayerViewList.size() - 1);
+ addView(view);
+ view.setOnClickListener(this);
+ }
+
+ public void removeOperationView(@NonNull FloatLayerView view) {
+ int viewIndex = mFloatLayerViewList.indexOf(view);
+ mFloatLayerViewList.remove(view);
+ mLastSelectedPos = -1;
+ removeView(view);
+ view.setOnClickListener(null);
+ }
+
+ public FloatLayerView getOperationView(int index) {
+ return mFloatLayerViewList.get(index);
+ }
+
+ public void selectOperationView(int pos) {
+ if (pos < mFloatLayerViewList.size() && pos >= 0) {
+ if (mLastSelectedPos != -1)
+ mFloatLayerViewList.get(mLastSelectedPos).setEditable(false);//不显示编辑的边框
+ mFloatLayerViewList.get(pos).setEditable(true);//显示编辑的边框
+ mLastSelectedPos = pos;
+ }
+ }
+
+ private void unSelectOperationView(int pos) {
+ if (pos < mFloatLayerViewList.size() && mLastSelectedPos != -1) {
+ mFloatLayerViewList.get(mLastSelectedPos).setEditable(false);//不显示编辑的边框
+ mLastSelectedPos = -1;
+ }
+ }
+
+ @Nullable
+ public FloatLayerView getSelectedLayerOperationView() {
+ if (mLastSelectedPos < 0 || mLastSelectedPos >= mFloatLayerViewList.size()) return null;
+ return mFloatLayerViewList.get(mLastSelectedPos);
+ }
+
+ public int getSelectedViewIndex() {
+ return mLastSelectedPos;
+ }
+
+ public int getChildCount() {
+ return mFloatLayerViewList.size();
+ }
+
+ public void enableChildSingleClick(boolean enable) {
+ mEnableChildSingleClick = enable;
+ }
+
+ public void enableDoubleChildClick(boolean enable) {
+ mEnableChildDoubleClick = enable;
+ }
+
+ @Override
+ public void onClick(View v) {
+ mLastTime = mCurTime;
+ mCurTime = System.currentTimeMillis();
+ if (mCurTime - mLastTime < 300) {//双击事件
+ mCurTime = 0;
+ mLastTime = 0;
+ if (mEnableChildDoubleClick) {
+ onItemClick(v);
+ }
+ } else {//单击事件
+ if (mEnableChildSingleClick) {
+ onItemClick(v);
+ }
+ }
+ }
+
+ private void onItemClick(View v) {
+ FloatLayerView floatLayerView = (FloatLayerView) v;
+ int pos = mFloatLayerViewList.indexOf(floatLayerView);
+ int lastPos = mLastSelectedPos;
+ selectOperationView(pos); //选中编辑
+ if (mListener != null) {
+ mListener.onLayerOperationViewItemClick(floatLayerView, lastPos, pos);
+ }
+ }
+
+ private OnItemClickListener mListener;
+
+ public void setOnItemClickListener(OnItemClickListener listener) {
+ mListener = listener;
+ }
+
+ public interface OnItemClickListener {
+ void onLayerOperationViewItemClick(FloatLayerView view, int lastSelectedPos, int currentSelectedPos);
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/progressbar/NumberProgressBar.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/progressbar/NumberProgressBar.java
new file mode 100644
index 0000000..091d2f4
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/progressbar/NumberProgressBar.java
@@ -0,0 +1,499 @@
+package com.tencent.qcloud.ugckit.component.progressbar;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Parcelable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.tencent.qcloud.ugckit.R;
+
+
+public class NumberProgressBar extends View {
+
+ private long mMaxProgress = 100;
+
+ /**
+ * Current progress, can not exceed the max progress.
+ */
+ private long mCurrentProgress = 0;
+
+ /**
+ * The progress area bar color.
+ */
+ private int mReachedBarColor;
+
+ /**
+ * The bar unreached area color.
+ */
+ private int mUnreachedBarColor;
+
+ /**
+ * The progress text color.
+ */
+ private int mTextColor;
+
+ /**
+ * The progress text size.
+ */
+ private float mTextSize;
+
+ /**
+ * The height of the reached area.
+ */
+ private float mReachedBarHeight;
+
+ /**
+ * The height of the unreached area.
+ */
+ private float mUnreachedBarHeight;
+
+ /**
+ * The suffix of the number.
+ */
+ @Nullable
+ private String mSuffix = "%";
+
+ /**
+ * The prefix.
+ */
+ @Nullable
+ private String mPrefix = "";
+
+
+ private final int default_text_color = Color.rgb(66, 145, 241);
+ private final int default_reached_color = Color.rgb(66, 145, 241);
+ private final int default_unreached_color = Color.rgb(204, 204, 204);
+ private final float default_progress_text_offset;
+ private final float default_text_size;
+ private final float default_reached_bar_height;
+ private final float default_unreached_bar_height;
+
+ /**
+ * For save and restore instance of progressbar.
+ */
+ private static final String INSTANCE_STATE = "saved_instance";
+ private static final String INSTANCE_TEXT_COLOR = "text_color";
+ private static final String INSTANCE_TEXT_SIZE = "text_size";
+ private static final String INSTANCE_REACHED_BAR_HEIGHT = "reached_bar_height";
+ private static final String INSTANCE_REACHED_BAR_COLOR = "reached_bar_color";
+ private static final String INSTANCE_UNREACHED_BAR_HEIGHT = "unreached_bar_height";
+ private static final String INSTANCE_UNREACHED_BAR_COLOR = "unreached_bar_color";
+ private static final String INSTANCE_MAX = "max";
+ private static final String INSTANCE_PROGRESS = "progress";
+ private static final String INSTANCE_SUFFIX = "suffix";
+ private static final String INSTANCE_PREFIX = "prefix";
+ private static final String INSTANCE_TEXT_VISIBILITY = "text_visibility";
+
+ private static final int PROGRESS_TEXT_VISIBLE = 0;
+
+
+ /**
+ * The width of the text that to be drawn.
+ */
+ private float mDrawTextWidth;
+
+ /**
+ * The drawn text start.
+ */
+ private float mDrawTextStart;
+
+ /**
+ * The drawn text end.
+ */
+ private float mDrawTextEnd;
+
+ /**
+ * The text that to be drawn in onDraw().
+ */
+ @Nullable
+ private String mCurrentDrawText;
+
+ /**
+ * The Paint of the reached area.
+ */
+ private Paint mReachedBarPaint;
+ /**
+ * The Paint of the unreached area.
+ */
+ private Paint mUnreachedBarPaint;
+ /**
+ * The Paint of the progress text.
+ */
+ private Paint mTextPaint;
+
+ /**
+ * Unreached bar area to draw rect.
+ */
+ @NonNull
+ private RectF mUnreachedRectF = new RectF(0, 0, 0, 0);
+ /**
+ * Reached bar area rect.
+ */
+ @NonNull
+ private RectF mReachedRectF = new RectF(0, 0, 0, 0);
+
+ /**
+ * The progress text offset.
+ */
+ private float mOffset;
+
+ /**
+ * Determine if need to draw unreached area.
+ */
+ private boolean mDrawUnreachedBar = true;
+
+ private boolean mDrawReachedBar = true;
+
+ private boolean mIfDrawText = true;
+
+ public enum ProgressTextVisibility {
+ Visible, Invisible
+ }
+
+ public NumberProgressBar(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public NumberProgressBar(@NonNull Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NumberProgressBar(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ default_reached_bar_height = dp2px(1.5f);
+ default_unreached_bar_height = dp2px(1.0f);
+ default_text_size = sp2px(10);
+ default_progress_text_offset = dp2px(3.0f);
+
+ //load styled attributes.
+ final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.UGCKitNumberProgressBar,
+ defStyleAttr, 0);
+
+ mReachedBarColor = attributes.getColor(R.styleable.UGCKitNumberProgressBar_progress_reached_color, default_reached_color);
+ mUnreachedBarColor = attributes.getColor(R.styleable.UGCKitNumberProgressBar_progress_unreached_color, default_unreached_color);
+ mTextColor = attributes.getColor(R.styleable.UGCKitNumberProgressBar_progress_text_color, default_text_color);
+ mTextSize = attributes.getDimension(R.styleable.UGCKitNumberProgressBar_progress_text_size, default_text_size);
+
+ mReachedBarHeight = attributes.getDimension(R.styleable.UGCKitNumberProgressBar_progress_reached_bar_height, default_reached_bar_height);
+ mUnreachedBarHeight = attributes.getDimension(R.styleable.UGCKitNumberProgressBar_progress_unreached_bar_height, default_unreached_bar_height);
+ mOffset = attributes.getDimension(R.styleable.UGCKitNumberProgressBar_progress_text_offset, default_progress_text_offset);
+
+ int textVisible = attributes.getInt(R.styleable.UGCKitNumberProgressBar_progress_text_visibility, PROGRESS_TEXT_VISIBLE);
+ if (textVisible != PROGRESS_TEXT_VISIBLE) {
+ mIfDrawText = false;
+ }
+
+ setProgress(attributes.getInt(R.styleable.UGCKitNumberProgressBar_progress_current, 0));
+ setMax(attributes.getInt(R.styleable.UGCKitNumberProgressBar_progress_max, 100));
+
+ attributes.recycle();
+ initializePainters();
+ }
+
+ @Override
+ protected int getSuggestedMinimumWidth() {
+ return (int) mTextSize;
+ }
+
+ @Override
+ protected int getSuggestedMinimumHeight() {
+ return Math.max((int) mTextSize, Math.max((int) mReachedBarHeight, (int) mUnreachedBarHeight));
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(measure(widthMeasureSpec, true), measure(heightMeasureSpec, false));
+ }
+
+ private int measure(int measureSpec, boolean isWidth) {
+ int result;
+ int mode = MeasureSpec.getMode(measureSpec);
+ int size = MeasureSpec.getSize(measureSpec);
+ int padding = isWidth ? getPaddingLeft() + getPaddingRight() : getPaddingTop() + getPaddingBottom();
+ if (mode == MeasureSpec.EXACTLY) {
+ result = size;
+ } else {
+ result = isWidth ? getSuggestedMinimumWidth() : getSuggestedMinimumHeight();
+ result += padding;
+ if (mode == MeasureSpec.AT_MOST) {
+ if (isWidth) {
+ result = Math.max(result, size);
+ } else {
+ result = Math.min(result, size);
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ if (mIfDrawText) {
+ calculateDrawRectF();
+ } else {
+ calculateDrawRectFWithoutProgressText();
+ }
+
+ if (mDrawReachedBar) {
+ canvas.drawRect(mReachedRectF, mReachedBarPaint);
+ }
+
+ if (mDrawUnreachedBar) {
+ canvas.drawRect(mUnreachedRectF, mUnreachedBarPaint);
+ }
+
+ if (mIfDrawText)
+ canvas.drawText(mCurrentDrawText, mDrawTextStart, mDrawTextEnd, mTextPaint);
+ }
+
+ private void initializePainters() {
+ mReachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mReachedBarPaint.setColor(mReachedBarColor);
+
+ mUnreachedBarPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mUnreachedBarPaint.setColor(mUnreachedBarColor);
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mTextColor);
+ mTextPaint.setTextSize(mTextSize);
+ }
+
+
+ private void calculateDrawRectFWithoutProgressText() {
+ mReachedRectF.left = getPaddingLeft();
+ mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
+ mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() + getPaddingLeft();
+ mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
+
+ mUnreachedRectF.left = mReachedRectF.right;
+ mUnreachedRectF.right = getWidth() - getPaddingRight();
+ mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f;
+ mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
+ }
+
+ private void calculateDrawRectF() {
+
+ mCurrentDrawText = String.format("%d", getProgress() * 100 / getMax());
+ mCurrentDrawText = mPrefix + mCurrentDrawText + mSuffix;
+ mDrawTextWidth = mTextPaint.measureText(mCurrentDrawText);
+
+ if (getProgress() == 0) {
+ mDrawReachedBar = false;
+ mDrawTextStart = getPaddingLeft();
+ } else {
+ mDrawReachedBar = true;
+ mReachedRectF.left = getPaddingLeft();
+ mReachedRectF.top = getHeight() / 2.0f - mReachedBarHeight / 2.0f;
+ mReachedRectF.right = (getWidth() - getPaddingLeft() - getPaddingRight()) / (getMax() * 1.0f) * getProgress() - mOffset + getPaddingLeft();
+ mReachedRectF.bottom = getHeight() / 2.0f + mReachedBarHeight / 2.0f;
+ mDrawTextStart = (mReachedRectF.right + mOffset);
+ }
+
+ mDrawTextEnd = (int) ((getHeight() / 2.0f) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
+
+ if ((mDrawTextStart + mDrawTextWidth) >= getWidth() - getPaddingRight()) {
+ mDrawTextStart = getWidth() - getPaddingRight() - mDrawTextWidth;
+ mReachedRectF.right = mDrawTextStart - mOffset;
+ }
+
+ float unreachedBarStart = mDrawTextStart + mDrawTextWidth + mOffset;
+ if (unreachedBarStart >= getWidth() - getPaddingRight()) {
+ mDrawUnreachedBar = false;
+ } else {
+ mDrawUnreachedBar = true;
+ mUnreachedRectF.left = unreachedBarStart;
+ mUnreachedRectF.right = getWidth() - getPaddingRight();
+ mUnreachedRectF.top = getHeight() / 2.0f + -mUnreachedBarHeight / 2.0f;
+ mUnreachedRectF.bottom = getHeight() / 2.0f + mUnreachedBarHeight / 2.0f;
+ }
+ }
+
+ /**
+ * Get progress text color.
+ *
+ * @return progress text color.
+ */
+ public int getTextColor() {
+ return mTextColor;
+ }
+
+ /**
+ * Get progress text size.
+ *
+ * @return progress text size.
+ */
+ public float getProgressTextSize() {
+ return mTextSize;
+ }
+
+ public int getUnreachedBarColor() {
+ return mUnreachedBarColor;
+ }
+
+ public int getReachedBarColor() {
+ return mReachedBarColor;
+ }
+
+ public long getProgress() {
+ return mCurrentProgress;
+ }
+
+ public long getMax() {
+ return mMaxProgress;
+ }
+
+ public float getReachedBarHeight() {
+ return mReachedBarHeight;
+ }
+
+ public float getUnreachedBarHeight() {
+ return mUnreachedBarHeight;
+ }
+
+ public void setProgressTextSize(float textSize) {
+ this.mTextSize = textSize;
+ mTextPaint.setTextSize(mTextSize);
+ invalidate();
+ }
+
+ public void setProgressTextColor(int textColor) {
+ this.mTextColor = textColor;
+ mTextPaint.setColor(mTextColor);
+ invalidate();
+ }
+
+ public void setUnreachedBarColor(int barColor) {
+ this.mUnreachedBarColor = barColor;
+ mUnreachedBarPaint.setColor(mUnreachedBarColor);
+ invalidate();
+ }
+
+ public void setReachedBarColor(int progressColor) {
+ this.mReachedBarColor = progressColor;
+ mReachedBarPaint.setColor(mReachedBarColor);
+ invalidate();
+ }
+
+ public void setReachedBarHeight(float height) {
+ mReachedBarHeight = height;
+ }
+
+ public void setUnreachedBarHeight(float height) {
+ mUnreachedBarHeight = height;
+ }
+
+ public void setMax(long maxProgress) {
+ if (maxProgress > 0) {
+ this.mMaxProgress = maxProgress;
+ invalidate();
+ }
+ }
+
+ public void setSuffix(@Nullable String suffix) {
+ if (suffix == null) {
+ mSuffix = "";
+ } else {
+ mSuffix = suffix;
+ }
+ }
+
+ @Nullable
+ public String getSuffix() {
+ return mSuffix;
+ }
+
+ public void setPrefix(@Nullable String prefix) {
+ if (prefix == null)
+ mPrefix = "";
+ else {
+ mPrefix = prefix;
+ }
+ }
+
+ @Nullable
+ public String getPrefix() {
+ return mPrefix;
+ }
+
+ public void incrementProgressBy(int by) {
+ if (by > 0) {
+ setProgress(getProgress() + by);
+ }
+ }
+
+ public void setProgress(long progress) {
+ if (progress <= getMax() && progress >= 0) {
+ this.mCurrentProgress = progress;
+ invalidate();
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(INSTANCE_STATE, super.onSaveInstanceState());
+ bundle.putInt(INSTANCE_TEXT_COLOR, getTextColor());
+ bundle.putFloat(INSTANCE_TEXT_SIZE, getProgressTextSize());
+ bundle.putFloat(INSTANCE_REACHED_BAR_HEIGHT, getReachedBarHeight());
+ bundle.putFloat(INSTANCE_UNREACHED_BAR_HEIGHT, getUnreachedBarHeight());
+ bundle.putInt(INSTANCE_REACHED_BAR_COLOR, getReachedBarColor());
+ bundle.putInt(INSTANCE_UNREACHED_BAR_COLOR, getUnreachedBarColor());
+ bundle.putLong(INSTANCE_MAX, getMax());
+ bundle.putLong(INSTANCE_PROGRESS, getProgress());
+ bundle.putString(INSTANCE_SUFFIX, getSuffix());
+ bundle.putString(INSTANCE_PREFIX, getPrefix());
+ bundle.putBoolean(INSTANCE_TEXT_VISIBILITY, getProgressTextVisibility());
+ return bundle;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state instanceof Bundle) {
+ final Bundle bundle = (Bundle) state;
+ mTextColor = bundle.getInt(INSTANCE_TEXT_COLOR);
+ mTextSize = bundle.getFloat(INSTANCE_TEXT_SIZE);
+ mReachedBarHeight = bundle.getFloat(INSTANCE_REACHED_BAR_HEIGHT);
+ mUnreachedBarHeight = bundle.getFloat(INSTANCE_UNREACHED_BAR_HEIGHT);
+ mReachedBarColor = bundle.getInt(INSTANCE_REACHED_BAR_COLOR);
+ mUnreachedBarColor = bundle.getInt(INSTANCE_UNREACHED_BAR_COLOR);
+ initializePainters();
+ setMax(bundle.getLong(INSTANCE_MAX));
+ setProgress(bundle.getLong(INSTANCE_PROGRESS));
+ setPrefix(bundle.getString(INSTANCE_PREFIX));
+ setSuffix(bundle.getString(INSTANCE_SUFFIX));
+ setProgressTextVisibility(bundle.getBoolean(INSTANCE_TEXT_VISIBILITY) ? ProgressTextVisibility.Visible : ProgressTextVisibility.Invisible);
+ super.onRestoreInstanceState(bundle.getParcelable(INSTANCE_STATE));
+ return;
+ }
+ super.onRestoreInstanceState(state);
+ }
+
+ public float dp2px(float dp) {
+ final float scale = getResources().getDisplayMetrics().density;
+ return dp * scale + 0.5f;
+ }
+
+ public float sp2px(float sp) {
+ final float scale = getResources().getDisplayMetrics().scaledDensity;
+ return sp * scale;
+ }
+
+ public void setProgressTextVisibility(ProgressTextVisibility visibility) {
+ mIfDrawText = visibility == ProgressTextVisibility.Visible;
+ invalidate();
+ }
+
+ public boolean getProgressTextVisibility() {
+ return mIfDrawText;
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/progressbutton/SampleProgressButton.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/progressbutton/SampleProgressButton.java
new file mode 100755
index 0000000..2f3bc76
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/progressbutton/SampleProgressButton.java
@@ -0,0 +1,218 @@
+package com.tencent.qcloud.ugckit.component.progressbutton;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.tencent.qcloud.ugckit.R;
+
+
+/**
+ * Button点击切换为Progress
+ */
+public class SampleProgressButton extends View {
+
+ public static final int STATE_NORMAL = 1;
+ public static final int STATE_PROGRESS = 2;
+
+ private FontMetrics mFontMetrics;
+ private Paint mTextPaint;
+ private Paint mBackgroundPaintNormal;
+ private Paint mBackgroundPaintProgress;
+ private RectF mBackgroundBounds;
+ private LinearGradient mProgressBgGradient;
+ private int mForegroundColor;
+ private int mBackgroundColor;
+ private int mNormalColor;
+ private int mProgress = 0;
+ private int mTextColor = Color.WHITE;
+ private float mTextSize = 10;
+ private int mMaxProgress = 100;
+ private int mState = STATE_NORMAL;
+ @Nullable
+ private String mText = "";
+
+ public SampleProgressButton(@NonNull Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ public SampleProgressButton(@NonNull Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context, attrs);
+ }
+
+ private void init(@NonNull Context context, AttributeSet attrs) {
+ TypedArray typedArray = null;
+ try {
+ typedArray = context.obtainStyledAttributes(attrs, R.styleable.UGCKitSampleProgressButton);
+ mBackgroundColor = typedArray.getInteger(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonBackgroundColor, Color.GRAY);
+ mForegroundColor = typedArray.getInteger(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonForegroundColor, Color.RED);
+ mNormalColor = typedArray.getInteger(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonNormalColor, Color.BLUE);
+ mTextColor = typedArray.getInteger(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonTextcolor, Color.WHITE);
+ mMaxProgress = typedArray.getInteger(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonMax, 100);
+ mProgress = typedArray.getInteger(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonProgress, 0);
+ mText = typedArray.getString(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonText);
+ mTextSize = typedArray.getDimension(R.styleable.UGCKitSampleProgressButton_sampleProgressButtonTextSize, 20);
+ } finally {
+ if (typedArray != null) {
+ typedArray.recycle();
+ }
+ }
+
+ mBackgroundBounds = new RectF();
+
+ mBackgroundPaintNormal = new Paint();
+ mBackgroundPaintNormal.setAntiAlias(true);
+ mBackgroundPaintNormal.setStyle(Paint.Style.FILL);
+
+ mBackgroundPaintProgress = new Paint();
+ mBackgroundPaintProgress.setAntiAlias(true);
+ mBackgroundPaintProgress.setStyle(Paint.Style.FILL);
+
+ mTextPaint = new Paint();
+ mTextPaint.setAntiAlias(true);
+ mTextPaint.setStrokeWidth(5);
+
+ float mProgressPercent = mProgress / (mMaxProgress + 0f);
+
+ mProgressBgGradient = new LinearGradient(0, 0, getMeasuredWidth(), 0,
+ new int[]{mForegroundColor, mBackgroundColor},
+ new float[]{mProgressPercent, mProgressPercent + 0.001f},
+ Shader.TileMode.CLAMP
+ );
+ }
+
+ public int getForegroundColor() {
+ return mForegroundColor;
+ }
+
+ public int getBackgroundColor() {
+ return mBackgroundColor;
+ }
+
+ public int getNormalColor() {
+ return mNormalColor;
+ }
+
+ public void setForegroundColor(int foregroundColor) {
+ mForegroundColor = foregroundColor;
+ invalidate();
+ }
+
+ public void setBackgroundColor(int backgroundColor) {
+ mBackgroundColor = backgroundColor;
+ invalidate();
+ }
+
+ public void setNormalColor(int normalColor) {
+ mNormalColor = normalColor;
+ invalidate();
+ }
+
+ public void setTextsize(float textSize) {
+ mTextSize = textSize;
+ }
+
+ public float getTextsize() {
+ return mTextSize;
+ }
+
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+
+ mBackgroundBounds.left = 0;
+ mBackgroundBounds.top = 0;
+ mBackgroundBounds.right = getMeasuredWidth();
+ mBackgroundBounds.bottom = getMeasuredHeight();
+
+ int cornerRadius = getMeasuredHeight() / 2;
+
+ if (mState == STATE_NORMAL) {
+ mBackgroundPaintNormal.setColor(mNormalColor);
+ canvas.drawRoundRect(mBackgroundBounds, cornerRadius, cornerRadius, mBackgroundPaintNormal);
+ } else {
+ mBackgroundPaintProgress.setShader(mProgressBgGradient);
+ canvas.drawRoundRect(mBackgroundBounds, cornerRadius, cornerRadius, mBackgroundPaintProgress);
+ }
+
+ if ("".equals(mText) || mText == null) {
+ return;
+ }
+
+ mTextPaint.setTextSize(this.mTextSize);
+ mFontMetrics = mTextPaint.getFontMetrics();
+ mTextPaint.setColor(this.mTextColor);
+
+ float textCenterVerticalBaselineY = getHeight() / 2 - mFontMetrics.descent + (mFontMetrics.descent - mFontMetrics.ascent) / 2;
+ canvas.drawText(this.mText, (getMeasuredWidth() - mTextPaint.measureText(this.mText)) / 2, textCenterVerticalBaselineY,
+ mTextPaint);
+
+ }
+
+ public void setMax(int max) {
+ mMaxProgress = max;
+
+ float mProgressPercent = mProgress / (mMaxProgress + 0f);
+
+ mProgressBgGradient = new LinearGradient(0, 0, getMeasuredWidth(), 0,
+ new int[]{mForegroundColor, mBackgroundColor},
+ new float[]{mProgressPercent, mProgressPercent + 0.001f},
+ Shader.TileMode.CLAMP
+ );
+ postInvalidate();
+ }
+
+
+ public void setText(String text) {
+ this.mText = text;
+ invalidate();
+ }
+
+
+ public void setProgress(int progress) {
+ if (progress > mMaxProgress) {
+ return;
+ }
+ mProgress = progress;
+
+ float mProgressPercent = mProgress / (mMaxProgress + 0f);
+
+ mProgressBgGradient = new LinearGradient(0, 0, getMeasuredWidth(), 0,
+ new int[]{mForegroundColor, mBackgroundColor},
+ new float[]{mProgressPercent, mProgressPercent + 0.001f},
+ Shader.TileMode.CLAMP
+ );
+ invalidate();
+ }
+
+ public int getMax() {
+ return mMaxProgress;
+ }
+
+ public int getProgress() {
+ return mProgress;
+ }
+
+ public void setState(int state) {
+ mState = state;
+ invalidate();
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+}
\ No newline at end of file
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/RangeSeekBar.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/RangeSeekBar.java
new file mode 100644
index 0000000..827fc66
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/RangeSeekBar.java
@@ -0,0 +1,396 @@
+package com.tencent.qcloud.ugckit.component.seekbar;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import com.tencent.qcloud.ugckit.R;
+
+public class RangeSeekBar extends View {
+ private static final String TAG = "RangeSeekBar";
+ private int mViewWidth;
+ private int mViewHeight;
+
+ private int mSeekBarWidth;
+ private int mSeekBarHeight;
+ private float mSbLeft;
+ private float mSbTop;
+ private float mSbRight;
+ private float mSbBottom;
+ private float mSbRound;
+ private float mLPLeft; //left-pointer left
+ private float mLPRight; //right-pointer right
+ private float mLPOffset;
+ private float mLPLastX;
+ private float mRPLeft; //right-pointer left
+ private float mRPRight; //right-pointer right
+ private float mRPOffset;
+ private float mRPLastX;
+ @Nullable
+ private Drawable mPointerDrawable;
+ private Paint mNormalPaint;
+ private Paint mProgressPaint;
+ private boolean mIsDragLeft;
+ private boolean mIsDragRight;
+ private boolean mLastIsDragLeft;
+ private boolean mIsRangeEnable;
+ private int mRange;
+ private int mBackgroundColor;
+ private int mProgressColor;
+ private int mLeftIndex;
+ private int mRightIndex;
+
+ private OnRangeProgressListener mListener;
+
+ public RangeSeekBar(Context context) {
+ super(context);
+ init(null);
+ }
+
+ public RangeSeekBar(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs);
+ }
+
+ public RangeSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(attrs);
+ }
+
+ public void setRangeEnable(boolean isRangeEnable) {
+ mIsRangeEnable = isRangeEnable;
+ invalidate();
+ }
+
+ /**
+ * 重置Range位置
+ */
+ public void resetRangePos() {
+ mLPOffset = 0;
+ mRPOffset = 0;
+ if (mLPLeft != 0) {
+ ValueAnimator lpResetAni = ValueAnimator.ofFloat(mLPLeft, 0);
+ lpResetAni.setDuration(200);
+ lpResetAni.setInterpolator(new AccelerateDecelerateInterpolator());
+ lpResetAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ mLPLeft = (float) animation.getAnimatedValue();
+ calculateLPRect();
+ invalidate();
+ }
+ });
+ lpResetAni.start();
+ }
+ if (mRPRight != mViewWidth) {
+ ValueAnimator rpResetAni = ValueAnimator.ofFloat(mRPRight, mViewWidth);
+ rpResetAni.setDuration(200);
+ rpResetAni.setInterpolator(new AccelerateDecelerateInterpolator());
+ rpResetAni.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ mRPRight = (float) animation.getAnimatedValue();
+ calculateRPRect();
+ invalidate();
+ }
+ });
+ rpResetAni.start();
+ }
+ }
+
+ /**
+ * 设置range范围
+ *
+ * @param range
+ */
+ public void setRange(int range) {
+ mRange = range;
+ }
+
+ private void init(@Nullable AttributeSet attrs) {
+ mIsRangeEnable = true;
+ mRange = 100;
+ if (attrs != null) {
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.UGCKitRangeSeekBar);
+ mPointerDrawable = a.getDrawable(R.styleable.UGCKitRangeSeekBar_rsb_pointerBackground);
+ mProgressColor = a.getColor(R.styleable.UGCKitRangeSeekBar_rsb_progressColor,
+ Color.parseColor("#FF4081"));
+ mBackgroundColor = a.getColor(R.styleable.UGCKitRangeSeekBar_rsb_backgroundColor,
+ getResources().getColor(R.color.ugckit_line_btn));
+ mRange = a.getInt(R.styleable.UGCKitRangeSeekBar_rsb_range, 100);
+ a.recycle();
+ }
+
+ mNormalPaint = new Paint();
+ mNormalPaint.setColor(mBackgroundColor);
+
+ mProgressPaint = new Paint();
+ mProgressPaint.setColor(mProgressColor);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mViewWidth = w;
+ mViewHeight = h;
+
+ mSeekBarWidth = mViewWidth - mPointerDrawable.getIntrinsicWidth();
+ mSeekBarHeight = mViewHeight - 20;
+
+ int halfDrawableWidth = mPointerDrawable.getIntrinsicWidth() / 2;
+ mSbLeft = halfDrawableWidth;
+ mSbTop = 18;
+ mSbBottom = mViewHeight - 18;
+ mSbRight = mViewWidth - halfDrawableWidth;
+ mSbRound = mSeekBarHeight / 2;
+
+ mLPLeft = 0;
+ mRPRight = mViewWidth;
+
+ calculateLPRect();
+ calculateRPRect();
+
+ mLPLastX = mLPLeft;
+ mRPLastX = mRPRight;
+ }
+
+ private void calculateRPRect() {
+ mRPRight = getRPRight(mRPOffset);
+ mRPLeft = mRPRight - mPointerDrawable.getIntrinsicWidth();
+ }
+
+ private void calculateLPRect() {
+ mLPLeft = getLPLeft(mLPOffset);
+ mLPRight = mLPLeft + mPointerDrawable.getIntrinsicWidth();
+ }
+
+ private float getLPLeft(float offset) {
+ return mLPLeft + offset;
+ }
+
+ private float getRPRight(float offset) {
+ return mRPRight + offset;
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ //draw seek bar bg
+ RectF rectF = new RectF();
+ rectF.left = mSbLeft;
+ rectF.right = mSbRight;
+ rectF.top = mSbTop;
+ rectF.bottom = mSbBottom;
+ //draw bg
+ canvas.drawRoundRect(rectF, mSbRound, mSbRound, mNormalPaint);
+
+ if (mIsRangeEnable) {
+ //draw progress
+ canvas.drawRect(
+ mLPLeft + mPointerDrawable.getIntrinsicWidth() / 2,
+ mSbTop,
+ mRPRight - mPointerDrawable.getIntrinsicWidth() / 2,
+ mSbBottom,
+ mProgressPaint);
+ //draw left pointer
+ Rect leftRect = new Rect();
+ leftRect.left = (int) mLPLeft;
+ leftRect.right = (int) mLPRight;
+ leftRect.top = 0;
+ leftRect.bottom = mViewHeight;
+
+ mPointerDrawable.setBounds(leftRect);
+ mPointerDrawable.draw(canvas);
+
+ //draw right pointer
+ Rect rightRect = new Rect();
+ rightRect.left = (int) mRPLeft;
+ rightRect.right = (int) mRPRight;
+ rightRect.top = 0;
+ rightRect.bottom = mViewHeight;
+
+ mPointerDrawable.setBounds(rightRect);
+ mPointerDrawable.draw(canvas);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ if (!isEnabled()) return false;
+
+ boolean isHandle = false;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isHandle = handleDownEvent(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ isHandle = handleMoveEvent(event);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ isHandle = handleUpEvent(event);
+ break;
+
+ }
+ return isHandle;
+ }
+
+ private boolean handleUpEvent(MotionEvent event) {
+ if (mIsDragLeft || mIsDragRight) {
+ mIsDragLeft = false;
+ mIsDragRight = false;
+ return true;
+ }
+ return false;
+ }
+
+
+ private boolean handleMoveEvent(@NonNull MotionEvent event) {
+ if (mIsDragLeft || mIsDragRight) {
+ float x = event.getX();
+ if (mIsDragLeft) {
+ mLPOffset = x - mLPLastX;
+ mLPLastX = x;
+ float tmpLPRight = mLPLeft + mLPOffset + mPointerDrawable.getIntrinsicWidth();
+ if (tmpLPRight > mRPRight) {
+ mLPOffset = mRPRight - mLPRight;
+ mLPLastX = mRPRight;
+ mIsDragLeft = false;
+ }
+ float tmpLPLeft = tmpLPRight - mPointerDrawable.getIntrinsicWidth() / 2;
+ if (tmpLPLeft <= mSbLeft) {
+
+ } else {
+ calculateLPRect();
+ invalidate();
+ }
+ }
+ if (mIsDragRight) {
+ mRPOffset = x - mRPLastX;
+ mRPLastX = x;
+ float tmpRPLeft = mRPRight + mRPOffset - mPointerDrawable.getIntrinsicWidth();
+ if (tmpRPLeft < mLPLeft) {
+ mRPOffset = mLPLeft - mRPLeft;
+ mRPLastX = mLPLeft;
+ mIsDragRight = false;
+ }
+ if (tmpRPLeft + mPointerDrawable.getIntrinsicWidth() >= mViewWidth) {
+ } else {
+ calculateRPRect();
+ invalidate();
+ }
+ }
+ callbackListener();
+ return true;
+ }
+ return false;
+ }
+
+ private void callbackListener() {
+ int lpProgress = calculateLPProgress();
+ int rpProgress = calculateRPProgress();
+ if (mListener != null) {
+ mListener.onSeekProgress(lpProgress, rpProgress);
+ }
+ }
+
+ public void setLeftIndex(int leftIndex) {
+ mLPLeft = leftIndex * 1.f / mRange * mSeekBarWidth + mSbLeft - mPointerDrawable.getIntrinsicWidth() / 2;
+// if (mLPLeft < mSbLeft) {
+// mLPLeft = mSbLeft;
+// }
+ mLPRight = mLPLeft + mPointerDrawable.getIntrinsicWidth();
+ invalidate();
+ }
+
+ public void setRightIndex(int rightIndex) {
+ mRPLeft = mSbRight - (1 - rightIndex * 1.f / mRange) * mSeekBarWidth + mPointerDrawable.getIntrinsicWidth() / 2;
+ mRPRight = mRPLeft + mPointerDrawable.getIntrinsicWidth();
+ if (mRPRight > mSbRight) {
+ mRPRight = mSbRight;
+ mRPLeft = mRPRight - mPointerDrawable.getIntrinsicWidth();
+ }
+ invalidate();
+ }
+
+ private boolean handleDownEvent(@NonNull MotionEvent event) {
+ if (!mIsRangeEnable) return false;
+ float x = event.getX();
+ float y = event.getY();
+ if (x >= mLPLeft - 80 && x <= mLPRight + 80) {
+ mIsDragLeft = true;
+ mLPLastX = x;
+ }
+ if (x <= mRPRight + 80 && x >= mRPLeft - 80) {
+ mIsDragRight = true;
+ mRPLastX = x;
+ }
+
+ if (mIsDragLeft && mIsDragRight) {
+ if (mLastIsDragLeft) {
+ mIsDragLeft = true;
+ mIsDragRight = false;
+ mLastIsDragLeft = true;
+ } else {
+ mIsDragLeft = false;
+ mIsDragRight = true;
+ mLastIsDragLeft = false;
+ }
+ }
+
+ mLastIsDragLeft = mIsDragLeft;
+
+ if (mIsDragRight || mIsDragLeft) {
+ return true;
+ }
+ return false;
+ }
+
+
+ private int calculateLPProgress() {
+ float lpCenter = mLPLeft + mPointerDrawable.getIntrinsicWidth() / 2;
+ if (lpCenter == mSbLeft) {
+ return 0;
+ } else {
+ float leftDis = lpCenter - mSbLeft;
+ float percent = leftDis / mSeekBarWidth;
+ if (percent <= 0.005) percent = 0;
+ return (int) (percent * mRange);
+ }
+ }
+
+ private int calculateRPProgress() {
+ float rpCenter = mRPRight - mPointerDrawable.getIntrinsicWidth() / 2;
+ if (rpCenter == mSbRight) {
+ return 1 * mRange;
+ } else {
+ float rightDis = mSbRight - rpCenter;
+ float percent = rightDis / mSeekBarWidth;
+ if (percent <= 0.005) percent = 0;
+ return (int) ((1 - percent) * mRange);
+ }
+ }
+
+
+ public void setOnRangeProgressListener(OnRangeProgressListener listener) {
+ mListener = listener;
+ }
+
+ public interface OnRangeProgressListener {
+
+ void onSeekProgress(int lpProgress, int rpProgress);
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCColorView.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCColorView.java
new file mode 100644
index 0000000..c2de07a
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCColorView.java
@@ -0,0 +1,272 @@
+package com.tencent.qcloud.ugckit.component.seekbar;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.tencent.qcloud.ugckit.R;
+
+
+/**
+ * 选择颜色的View
+ */
+public class TCColorView extends View {
+ private Context mContext;
+ @Nullable
+ private LinearGradient linearGradient = null;
+ @Nullable
+ private Paint mHuePaint = null;
+ @Nullable
+ private Paint mValuePaint = null;
+ @Nullable
+ private RectF mHueRectF = null;
+ @Nullable
+ private RectF mValueRectF = null;
+ private int mWidth;
+ private Paint mSwipePaint;
+ private Bitmap mSwipeBitmap;
+ private OnSelectColorListener mOnSelectColorListener;
+ private int mColorHeight;
+ private float mSwipeRadius;
+ private int marginTopAndBottom;
+ @NonNull
+ private float[] colorHSV = new float[]{0f, 1f, 0f};
+ private float mSwipeHueCx = 0;
+ private float mSwipeValueCx = 0;
+
+ public TCColorView(Context context) {
+ super(context);
+
+ init(context);
+ }
+
+ public TCColorView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public TCColorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context);
+ }
+
+ private void init(Context context) {
+ this.mContext = context;
+ mHuePaint = new Paint();
+ mValuePaint = new Paint();
+ mSwipePaint = new Paint();
+ mSwipePaint.setAntiAlias(true);
+ mSwipeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ugckit_color_swipe);
+ mSwipeRadius = lastValueX = mSwipeBitmap.getWidth() / 2;
+
+ mColorHeight = dp2px(10);
+ marginTopAndBottom = dp2px(10);
+ }
+
+
+ public int dp2px(float dpVal) {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ dpVal, mContext.getResources().getDisplayMetrics());
+ }
+
+ public float px2dp(int pxVal) {
+ final float scale = mContext.getResources().getDisplayMetrics().density;
+ return (pxVal / scale);
+ }
+
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ //绘制色相选择区域
+ drawHuePanel(canvas);
+ //绘制明度颜色条
+ drawValuePanel(canvas);
+ }
+
+ @NonNull
+ private int[] buildHueColorArray() {
+ int[] hue = new int[361];
+ for (int i = 0; i < hue.length; i++) {
+ hue[i] = Color.HSVToColor(new float[]{i, 1f, 1f});
+ }
+ return hue;
+ }
+
+ /**
+ * 绘制色相选择区域
+ *
+ * @param canvas
+ */
+ private void drawHuePanel(@NonNull Canvas canvas) {
+ //绘制颜色条
+ if (mHueRectF == null)
+ mHueRectF = new RectF(mSwipeRadius, mSwipeRadius - mColorHeight / 2 + marginTopAndBottom, mWidth - mSwipeRadius, mSwipeRadius + mColorHeight / 2 + marginTopAndBottom);
+ if (linearGradient == null) {
+ linearGradient = new LinearGradient(mHueRectF.left, mHueRectF.top, mHueRectF.right, mHueRectF.top, buildHueColorArray(), null,
+ Shader.TileMode.CLAMP);
+ //设置渲染器
+ mHuePaint.setShader(linearGradient);
+ }
+ canvas.drawRoundRect(mHueRectF, 15, 15, mHuePaint);
+ //绘制滑块
+ if (mSwipeHueCx < mSwipeRadius)
+ mSwipeHueCx = mSwipeRadius;
+ else if (mSwipeHueCx > mWidth - mSwipeRadius)
+ mSwipeHueCx = mWidth - mSwipeRadius;
+ canvas.drawBitmap(mSwipeBitmap, mSwipeHueCx - mSwipeRadius, marginTopAndBottom, mSwipePaint);
+ }
+
+ /**
+ * 明度数组
+ *
+ * @return
+ */
+ @NonNull
+ private int[] buildValueColorArray() {
+ int[] value = new int[11];
+ for (int i = 0; i < value.length; i++) {
+ value[i] = Color.HSVToColor(new float[]{colorHSV[0], 1f, (float) i / 10});
+ }
+ return value;
+ }
+
+ /**
+ * 绘制明度选择区域
+ *
+ * @param canvas
+ */
+ private void drawValuePanel(@NonNull Canvas canvas) {
+ if (mValueRectF == null)
+ mValueRectF = new RectF(mSwipeRadius, mSwipeRadius - mColorHeight / 2 + 3 * mSwipeRadius + marginTopAndBottom,
+ mWidth - mSwipeRadius, mSwipeRadius + mColorHeight / 2 + 3 * mSwipeRadius + marginTopAndBottom);
+ final RectF rect = mValueRectF;
+
+ //明度线性渲染器
+ LinearGradient mValueShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
+ buildValueColorArray(), null, Shader.TileMode.CLAMP);
+
+ mValuePaint.setShader(mValueShader);
+
+ canvas.drawRoundRect(mValueRectF, 15, 15, mValuePaint);
+ //绘制滑块
+ if (mSwipeValueCx < mSwipeRadius)
+ mSwipeValueCx = mSwipeRadius;
+ else if (mSwipeValueCx > mWidth - mSwipeRadius)
+ mSwipeValueCx = mWidth - mSwipeRadius;
+ canvas.drawBitmap(mSwipeBitmap, mSwipeValueCx - mSwipeRadius, 3 * mSwipeRadius + marginTopAndBottom, mSwipePaint);
+
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int measureWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
+ setMeasuredDimension(measureWidth,
+ measureHeight(heightMeasureSpec));
+ mWidth = measureWidth;
+ }
+
+
+ private int measureHeight(int heightMeasureSpec) {
+ int result = (int) (5 * mSwipeRadius + 2 * marginTopAndBottom);
+ int specMode = MeasureSpec.getMode(heightMeasureSpec);
+ int specSize = MeasureSpec.getSize(heightMeasureSpec);
+ if (specMode == MeasureSpec.EXACTLY && specSize < result) {
+ //throw new IllegalArgumentException("Height is too small to display completely , the height needs to be greater than " + px2dp(result) + "dp !");
+ }
+ return result;
+ }
+
+ private int clickPanel = -1;
+ private float lastValueX;
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ float x = event.getX();
+ if (x < mSwipeRadius || x > mWidth - mSwipeRadius)
+ return super.onTouchEvent(event);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (event.getY() < (5 * mSwipeRadius + 2 * marginTopAndBottom) / 2) { //色相区域
+ clickPanel = 1;
+ updateHueDate(x);
+ } else if (event.getY() < 5 * mSwipeRadius + 2 * marginTopAndBottom) {
+ clickPanel = 2;
+ mSwipeValueCx = lastValueX = x;
+ callbackProgress();
+ invalidate();
+ } else return super.onTouchEvent(event);
+
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (clickPanel == 1) {
+ updateHueDate(x);
+ } else if (clickPanel == 2) {
+ mSwipeValueCx = lastValueX = x;
+ callbackProgress();
+ invalidate();
+ } else return super.onTouchEvent(event);
+ break;
+ case MotionEvent.ACTION_UP:
+ callbackFinish();
+ clickPanel = -1;
+ break;
+
+ }
+ return true;
+
+ }
+
+ private void updateHueDate(float x) {
+ mSwipeHueCx = x;
+ colorHSV[0] = 360 * (x - mSwipeRadius) / (mWidth - mSwipeBitmap.getWidth());
+ callbackProgress();
+ invalidate();
+ }
+
+ private void callbackProgress() {
+ colorHSV[2] = (lastValueX - mSwipeRadius) / (mWidth - mSwipeBitmap.getWidth());
+ if (mOnSelectColorListener != null) {
+ mOnSelectColorListener.onProgressColor(Color.HSVToColor(colorHSV));
+ }
+ }
+
+
+ private void callbackFinish() {
+ colorHSV[2] = (lastValueX - mSwipeRadius) / (mWidth - mSwipeBitmap.getWidth());
+ if (mOnSelectColorListener != null) {
+ mOnSelectColorListener.onFinishColor(Color.HSVToColor(colorHSV));
+ }
+ }
+
+ public void setOnSelectColorListener(@NonNull OnSelectColorListener listener) {
+ this.mOnSelectColorListener = listener;
+ listener.onProgressColor(Color.HSVToColor(colorHSV)); //初始
+ listener.onFinishColor(Color.HSVToColor(colorHSV)); //初始
+ }
+
+
+ public interface OnSelectColorListener {
+ void onFinishColor(@ColorInt int color);
+
+ void onProgressColor(@ColorInt int color);
+ }
+
+ @Override
+ public int getSolidColor() {
+ return Color.HSVToColor(colorHSV);
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCReversalSeekBar.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCReversalSeekBar.java
new file mode 100644
index 0000000..5e025b9
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCReversalSeekBar.java
@@ -0,0 +1,295 @@
+package com.tencent.qcloud.ugckit.component.seekbar;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+import com.tencent.qcloud.ugckit.R;
+
+public class TCReversalSeekBar extends View {
+ private static final String TAG = "ReversalSeekBar";
+ private int mWidth;
+ private int mHeight;
+ private int mSeekBarLeft;
+ private int mSeekBarRight;
+ private int mBgTop;
+ private int mBgBottom;
+ private int mRoundSize;
+ private int mViewEnd;
+ private Paint mNormalPaint;
+ private Paint mPointerPaint;
+ private Paint mProgressPaint;
+ private float mPointerLeft;
+ private float mPointerRight;
+ private float mPointerTop;
+ private float mPointerBottom;
+ private boolean mIsOnDrag;
+ private float mCurrentLeftOffset = 0;
+ private float mLastX;
+
+ @Nullable
+ private Drawable mPointerDrawable;
+ private int mHalfDrawableWidth;
+
+ private float mCurrentProgress;
+
+ public TCReversalSeekBar(Context context) {
+ super(context);
+ init(null);
+ }
+
+ public TCReversalSeekBar(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs);
+ }
+
+ public TCReversalSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(attrs);
+ }
+
+ /**
+ * 1~0
+ */
+ public void setProgress(final float progress) {
+ if (progress < 0 || progress > 1.0)
+ throw new IllegalArgumentException("progress must between 0 and 1");
+ mCurrentProgress = progress;
+ }
+
+ public float getProgress() {
+ return mCurrentProgress;
+ }
+
+ private void init(@Nullable AttributeSet attrs) {
+ int progressColor = Color.parseColor("#FF4081");
+ int backgroundColor = Color.parseColor("#BBBBBB");
+ if (attrs != null) {
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.UGCKitTCReversalSeekBar);
+ mPointerDrawable = a.getDrawable(R.styleable.UGCKitTCReversalSeekBar_rs_pointerBackground);
+ mHalfDrawableWidth = mPointerDrawable.getIntrinsicWidth() / 2;
+ progressColor = a.getColor(R.styleable.UGCKitTCReversalSeekBar_rs_progressColor,
+ Color.parseColor("#FF4081"));
+ backgroundColor = a.getColor(R.styleable.UGCKitTCReversalSeekBar_rs_backgroundColor,
+ Color.parseColor("#BBBBBB"));
+ mCurrentProgress = a.getFloat(R.styleable.UGCKitTCReversalSeekBar_rs_progress, 0f);
+ a.recycle();
+ }
+ mNormalPaint = new Paint();
+ mNormalPaint.setColor(backgroundColor);
+
+ mPointerPaint = new Paint();
+ mPointerPaint.setColor(Color.RED);
+
+ mProgressPaint = new Paint();
+ mProgressPaint.setColor(progressColor);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mWidth = w;
+ mHeight = h;
+
+ mSeekBarLeft = mHalfDrawableWidth;
+ mSeekBarRight = mWidth - mHalfDrawableWidth;
+
+ mBgTop = 18;
+ mBgBottom = mHeight - 18;
+ mRoundSize = mHeight / 2;
+
+ mViewEnd = mWidth;
+
+ float dis = (mSeekBarRight - mSeekBarLeft) * mCurrentProgress;
+ mPointerLeft = mViewEnd - dis - mHalfDrawableWidth;
+ mLastX = mPointerLeft;
+ calculatePointerRect();
+
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ //draw bg
+ RectF rectF = new RectF();
+ rectF.left = mSeekBarLeft;
+ rectF.right = mSeekBarRight;
+ rectF.top = mBgTop;
+ rectF.bottom = mBgBottom;
+ canvas.drawRoundRect(rectF, mRoundSize, mRoundSize, mNormalPaint);
+
+ //draw progress
+ if (mPointerRight < mViewEnd) {
+ RectF pRecf = new RectF();
+ pRecf.left = mPointerRight - mHalfDrawableWidth;
+ pRecf.top = mBgTop;
+ pRecf.right = mViewEnd;
+ pRecf.bottom = mBgBottom;
+ canvas.drawRoundRect(pRecf,
+ mRoundSize, mRoundSize, mProgressPaint);
+ }
+
+ //draw pointer
+ Rect rect = new Rect();
+ rect.left = (int) mPointerLeft;
+ rect.top = (int) mPointerTop;
+ rect.right = (int) mPointerRight;
+ rect.bottom = (int) mPointerBottom;
+
+ mPointerDrawable.setBounds(rect);
+ mPointerDrawable.draw(canvas);
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ if (!isEnabled()) return false;
+
+ boolean isHandle = false;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isHandle = handleDownEvent(event);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ isHandle = handleMoveEvent(event);
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ isHandle = handleUpEvent(event);
+ break;
+
+ }
+ return isHandle;
+ }
+
+ private boolean handleUpEvent(@NonNull MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (mIsOnDrag) {
+ mIsOnDrag = false;
+ if (mListener != null) {
+ mListener.onSeekUp();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean handleMoveEvent(@NonNull MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (mIsOnDrag) {
+ mCurrentLeftOffset = x - mLastX;
+ //计算出标尺的Rect
+ calculatePointerRect();
+ if (mPointerRight - mHalfDrawableWidth <= mSeekBarLeft) {
+ mPointerLeft = 0;
+ mPointerRight = mPointerLeft + mPointerDrawable.getIntrinsicWidth();
+ }
+ if (mPointerLeft + mHalfDrawableWidth >= mSeekBarRight) {
+ mPointerRight = mWidth;
+ mPointerLeft = mWidth - mPointerDrawable.getIntrinsicWidth();
+ }
+ invalidate();
+ callbackProgress();
+ mLastX = x;
+ return true;
+ }
+ return false;
+ }
+
+ private void callbackProgress() {
+ if (mPointerLeft == 0) {
+ callbackProgressInternal(1);
+ } else if (mPointerRight == mWidth) {
+ callbackProgressInternal(0);
+
+ } else {
+ float pointerMiddle = mPointerLeft + mHalfDrawableWidth;
+ if (pointerMiddle == mViewEnd) {
+ callbackProgressInternal(0);
+ } else {
+ float percent = Math.abs(mViewEnd - pointerMiddle) / mViewEnd * 1.0f;
+ callbackProgressInternal(percent);
+ }
+ }
+ }
+
+ private void callbackProgressInternal(float progress) {
+ mCurrentProgress = progress;
+ if (mListener != null) {
+ mListener.onSeekProgress(progress);
+ }
+ }
+
+
+ private boolean handleDownEvent(@NonNull MotionEvent event) {
+ float x = event.getX();
+ float y = event.getY();
+ if (x >= mPointerLeft - 100 && x <= mPointerRight + 100) {
+ if (mListener != null)
+ mListener.onSeekDown();
+ mIsOnDrag = true;
+ mLastX = x;
+ return true;
+ }
+ return false;
+ }
+
+ private void calculatePointerRect() {
+ //draw pointer
+ float pointerLeft = getPointerLeft(mCurrentLeftOffset);
+ float pointerRight = pointerLeft + mPointerDrawable.getIntrinsicWidth();
+ mPointerLeft = pointerLeft;
+ mPointerRight = pointerRight;
+ mPointerTop = 0;
+ mPointerBottom = mHeight;
+ }
+
+ /**
+ * 进行复位
+ */
+ public void resetSeekBar() {
+ ValueAnimator valueAnimator = ValueAnimator.ofFloat(mPointerLeft, mViewEnd - mPointerDrawable.getIntrinsicWidth());
+ valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ valueAnimator.setDuration(200);
+ valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ mPointerLeft = (float) animation.getAnimatedValue();
+ mPointerRight = mPointerLeft + mPointerDrawable.getIntrinsicWidth();
+ invalidate();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private float getPointerLeft(float offset) {
+ return mPointerLeft + offset;
+ }
+
+ private OnSeekProgressListener mListener;
+
+ public void setOnSeekProgressListener(OnSeekProgressListener listener) {
+ mListener = listener;
+ }
+
+ public interface OnSeekProgressListener {
+ void onSeekDown();
+
+ void onSeekUp();
+
+ void onSeekProgress(float progress);
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCTouchSeekBar.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCTouchSeekBar.java
new file mode 100644
index 0000000..bcff601
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/seekbar/TCTouchSeekBar.java
@@ -0,0 +1,181 @@
+package com.tencent.qcloud.ugckit.component.seekbar;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+
+import com.tencent.qcloud.ugckit.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class TCTouchSeekBar extends View {
+ private int downX = 0;
+ private int downY = 0;
+ private int upX = 0;
+ private int upY = 0;
+ private int moveX = 0;
+ private int moveY = 0;
+ private int mViewWidth;
+ private int mViewHeight;
+ private Bitmap mDotDefaultBitmap;
+ private Bitmap mDotCheckedBitmap;
+ private OnTouchCallback mCallback;
+ private int mCurrentPos = 2;
+ private ArrayList mSelectionList;
+ private int mUnitWidth;
+ private Paint mPaint;
+ private int mTextSize = 40;
+
+ public TCTouchSeekBar(Context context) {
+ super(context);
+ }
+
+ public TCTouchSeekBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TCTouchSeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ if (attrs != null) {
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.UGCKitTCTouchSeekBar);
+ mDotDefaultBitmap = ((BitmapDrawable) a.getDrawable(R.styleable.UGCKitTCTouchSeekBar_tsb_dotDefault)).getBitmap();
+ mDotCheckedBitmap = ((BitmapDrawable) a.getDrawable(R.styleable.UGCKitTCTouchSeekBar_tsb_dotChecked)).getBitmap();
+ a.recycle();
+ }
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true); // 去锯齿
+ mPaint.setColor(Color.GRAY);
+ mPaint.setStyle(Paint.Style.STROKE);
+ mPaint.setTextSize(mTextSize);
+
+ setSelectionList(null);
+ }
+
+ /**
+ * 实例化后调用,设置bar的段数和文字
+ */
+ public void setSelectionList(@Nullable String[] section) {
+ if (section != null) {
+ mSelectionList = new ArrayList();
+ mSelectionList.addAll(Arrays.asList(section));
+ } else {
+ //随便写的
+ String[] str = new String[]{
+ getResources().getString(R.string.ugckit_touch_seekbar_low),
+ getResources().getString(R.string.ugckit_touch_seekbar_mid),
+ getResources().getString(R.string.ugckit_touch_seekbar_high),
+ };
+ mSelectionList = new ArrayList();
+ mSelectionList.addAll(Arrays.asList(str));
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
+ mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(mViewWidth, mViewHeight);
+ //计算一个刻度的长度
+ mUnitWidth = (mViewWidth - mDotDefaultBitmap.getWidth()) / (mSelectionList.size() - 1);
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ super.onDraw(canvas);
+ //先画一个背景 X轴横线
+ canvas.drawLine(mDotDefaultBitmap.getWidth() / 2, mViewHeight / 2, mViewWidth - mDotDefaultBitmap.getWidth() / 2, mViewHeight / 2, mPaint);
+
+ //画刻度点和刻度
+ for (int i = 0; i < mSelectionList.size(); i++) {
+ if (i == mCurrentPos) {
+ canvas.drawBitmap(mDotCheckedBitmap,
+ mCurrentPos * mUnitWidth - (mDotCheckedBitmap.getWidth() - mDotDefaultBitmap.getWidth()) / 2,
+ mViewHeight / 2 - mDotCheckedBitmap.getHeight() / 2, mPaint);
+ } else {
+ canvas.drawBitmap(mDotDefaultBitmap, i * mUnitWidth, mViewHeight / 2 - mDotDefaultBitmap.getHeight() / 2, mPaint);
+ }
+ canvas.drawText(mSelectionList.get(i), i * mUnitWidth + (mDotDefaultBitmap.getWidth() - mTextSize / 2 * mSelectionList.get(i).length()) / 2, mViewHeight / 2 - mDotDefaultBitmap.getHeight() / 2 - 5, mPaint);
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ super.onTouchEvent(event);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ downX = (int) event.getX();
+ downY = (int) event.getY();
+ responseTouch(downX, downY);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ moveX = (int) event.getX();
+ moveY = (int) event.getY();
+ responseTouch(moveX, moveY);
+ break;
+ case MotionEvent.ACTION_UP:
+ upX = (int) event.getX();
+ upY = (int) event.getY();
+ responseTouch(upX, upY);
+ mCallback.onCallback(mCurrentPos);
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * 刷新点
+ *
+ * @param x
+ * @param y
+ */
+ private void responseTouch(int x, int y) {
+ if (x <= 0)
+ mCurrentPos = 0;
+ else if (x % mUnitWidth >= mUnitWidth / 2)
+ mCurrentPos = x / mUnitWidth + 1;
+ else
+ mCurrentPos = x / mUnitWidth;
+
+ invalidate();
+ }
+
+ //设置监听
+ public void setOnTouchCallback(OnTouchCallback callback) {
+ mCallback = callback;
+ }
+
+ //设置进度
+ public void setProgress(int progress) {
+ if (progress < 0)
+ mCurrentPos = 0;
+ else if (progress > mSelectionList.size() - 1)
+ mCurrentPos = mSelectionList.size() - 1;
+ else
+ mCurrentPos = progress;
+ invalidate();
+ }
+
+ //获取进度
+ public int getProgress() {
+ return mCurrentPos;
+ }
+
+ public interface OnTouchCallback {
+ void onCallback(int position);
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/RangeSlider.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/RangeSlider.java
new file mode 100644
index 0000000..ce54240
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/RangeSlider.java
@@ -0,0 +1,488 @@
+package com.tencent.qcloud.ugckit.component.slider;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateDecelerateInterpolator;
+
+
+import com.tencent.qcloud.ugckit.R;
+
+
+public class RangeSlider extends ViewGroup {
+ private static final String TAG = "RangeSlider";
+
+ private static final int DEFAULT_LINE_SIZE = 1;
+ private static final int DEFAULT_THUMB_WIDTH = 7;
+ private static final int DEFAULT_TICK_START = 0;
+ private static final int DEFAULT_TICK_END = 5;
+ private static final int DEFAULT_TICK_INTERVAL = 1;
+ public static final int TYPE_LEFT = 1;
+ public static final int TYPE_RIGHT = 2;
+ private static int DEFAULT_MASK_BACKGROUND = 0xA0000000;
+ private static int DEFAULT_LINE_COLOR = 0xFFFF584C;
+
+ @NonNull
+ private final Paint mLinePaint;
+ @NonNull
+ private final Paint mBgPaint;
+ @NonNull
+ private final ThumbView mLeftThumb;
+ @NonNull
+ private final ThumbView mRightThumb;
+ @Nullable
+ private Drawable mRightIcon;
+ @Nullable
+ private Drawable mLeftIcon;
+ private int mTouchSlop;
+ private int mOriginalX;
+ private int mLastX;
+ private int mThumbWidth;
+ private int mTickStart = DEFAULT_TICK_START;
+ private int mTickEnd = DEFAULT_TICK_END;
+ private int mTickInterval = DEFAULT_TICK_INTERVAL;
+ private int mTickCount = (mTickEnd - mTickStart) / mTickInterval;
+ private float mLineSize;
+ private boolean mIsDragging;
+ private OnRangeChangeListener mRangeChangeListener;
+
+ public RangeSlider(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public RangeSlider(@NonNull Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public RangeSlider(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.UGCKitRangeSlider, 0, 0);
+ mThumbWidth = array.getDimensionPixelOffset(R.styleable.UGCKitRangeSlider_thumbWidth, DEFAULT_THUMB_WIDTH);
+ mLineSize = array.getDimensionPixelOffset(R.styleable.UGCKitRangeSlider_lineHeight, DEFAULT_LINE_SIZE);
+ mBgPaint = new Paint();
+ mBgPaint.setColor(array.getColor(R.styleable.UGCKitRangeSlider_maskColor, DEFAULT_MASK_BACKGROUND));
+
+ mLinePaint = new Paint();
+ mLinePaint.setColor(array.getColor(R.styleable.UGCKitRangeSlider_lineColor, DEFAULT_LINE_COLOR));
+
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+
+ mLeftIcon = array.getDrawable(R.styleable.UGCKitRangeSlider_leftThumbDrawable);
+ mRightIcon = array.getDrawable(R.styleable.UGCKitRangeSlider_rightThumbDrawable);
+ mLeftThumb = new ThumbView(context, mThumbWidth, mLeftIcon == null ? new ColorDrawable(DEFAULT_LINE_COLOR) : mLeftIcon);
+ mRightThumb = new ThumbView(context, mThumbWidth, mRightIcon == null ? new ColorDrawable(DEFAULT_LINE_COLOR) : mRightIcon);
+ setTickCount(array.getInteger(R.styleable.UGCKitRangeSlider_tickCount, DEFAULT_TICK_END));
+ setRangeIndex(array.getInteger(R.styleable.UGCKitRangeSlider_leftThumbIndex, DEFAULT_TICK_START),
+ array.getInteger(R.styleable.UGCKitRangeSlider_rightThumbIndex, mTickCount));
+ array.recycle();
+
+ addView(mLeftThumb);
+ addView(mRightThumb);
+
+ setWillNotDraw(false);
+ }
+
+ public void setThumbWidth(int thumbWidth) {
+ mThumbWidth = thumbWidth;
+ mLeftThumb.setThumbWidth(thumbWidth);
+ mRightThumb.setThumbWidth(thumbWidth);
+ }
+
+ /**
+ * 设置左边拖动条Icon
+ *
+ * @param drawable
+ */
+ public void setLeftThumbDrawable(Drawable drawable) {
+ mLeftThumb.setThumbDrawable(drawable);
+ }
+
+ /**
+ * 设置右边拖动条Icon
+ *
+ * @param drawable
+ */
+ public void setRightThumbDrawable(Drawable drawable) {
+ mRightThumb.setThumbDrawable(drawable);
+ }
+
+ public void setLineSize(float lineSize) {
+ mLineSize = lineSize;
+ }
+
+ /**
+ * 设置横线颜色
+ *
+ * @param color
+ */
+ public void setLineColor(@ColorInt int color) {
+ mLinePaint.setColor(color);
+ }
+
+ /**
+ * 设置背景颜色
+ *
+ * @param color
+ */
+ public void setMaskColor(int color) {
+ mBgPaint.setColor(color);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mLeftThumb.measure(widthMeasureSpec, heightMeasureSpec);
+ mRightThumb.measure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int lThumbWidth = mLeftThumb.getMeasuredWidth();
+ final int lThumbHeight = mLeftThumb.getMeasuredHeight();
+ mLeftThumb.layout(0, 0, lThumbWidth, lThumbHeight);
+ mRightThumb.layout(0, 0, lThumbWidth, lThumbHeight);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ moveThumbByIndex(mLeftThumb, mLeftThumb.getRangeIndex());
+ moveThumbByIndex(mRightThumb, mRightThumb.getRangeIndex());
+ }
+
+ @Override
+ protected void onDraw(@NonNull Canvas canvas) {
+ final int width = getMeasuredWidth();
+ final int height = getMeasuredHeight();
+
+ final int lThumbWidth = mLeftThumb.getMeasuredWidth();
+ final float lThumbOffset = mLeftThumb.getX();
+ final float rThumbOffset = mRightThumb.getX();
+
+ final float lineTop = mLineSize;
+ final float lineBottom = height - mLineSize;
+
+
+ // top line
+ canvas.drawRect(lThumbWidth + lThumbOffset, 0, rThumbOffset, lineTop, mLinePaint);
+
+ // bottom line
+ canvas.drawRect(lThumbWidth + lThumbOffset, lineBottom, rThumbOffset, height, mLinePaint);
+
+ if (lThumbOffset > mThumbWidth) {
+ canvas.drawRect(0, 0, lThumbOffset + mThumbWidth, height, mBgPaint);
+ }
+ if (rThumbOffset < width - mThumbWidth) {
+ canvas.drawRect(rThumbOffset, 0, width, height, mBgPaint);
+ }
+
+ }
+
+ /**
+ * 对游标进行复位
+ */
+ public void resetRangePos() {
+ ValueAnimator leftValueAnimator = ValueAnimator.ofFloat(mLeftThumb.getX(), 0f);
+ leftValueAnimator.setDuration(200);
+ leftValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+ ValueAnimator rightValueAnimator = ValueAnimator.ofFloat(mRightThumb.getX(), this.getMeasuredWidth());
+
+ rightValueAnimator.setDuration(200);
+ rightValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
+
+ leftValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ int moveX = (int) ((float) animation.getAnimatedValue() - mLeftThumb.getX());
+ if (moveX != 0) {
+ moveLeftThumbByPixel(moveX);
+ invalidate();
+ }
+ }
+ });
+ rightValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ Log.i(TAG, "right onAnimationUpdate: " + animation.getAnimatedValue());
+ int moveX = (int) ((float) animation.getAnimatedValue() - mRightThumb.getX());
+ Log.i(TAG, "move x = " + moveX);
+ if (moveX != 0) {
+ moveRightThumbByPixel(moveX);
+ invalidate();
+ }
+ }
+ });
+
+ leftValueAnimator.start();
+ rightValueAnimator.start();
+ }
+
+ @Override
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ if (!isEnabled()) {
+ return false;
+ }
+
+ boolean handle = false;
+
+ switch (event.getAction()) {
+
+ case MotionEvent.ACTION_DOWN:
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ mLastX = mOriginalX = x;
+ mIsDragging = false;
+
+ if (!mLeftThumb.isPressed() && mLeftThumb.inInTarget(x, y)) {
+ mLeftThumb.setPressed(true);
+ handle = true;
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onKeyDown(TYPE_LEFT);
+ }
+ } else if (!mRightThumb.isPressed() && mRightThumb.inInTarget(x, y)) {
+ mRightThumb.setPressed(true);
+ handle = true;
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onKeyDown(TYPE_RIGHT);
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mIsDragging = false;
+ mOriginalX = mLastX = 0;
+ getParent().requestDisallowInterceptTouchEvent(false);
+ if (mLeftThumb.isPressed()) {
+ releaseLeftThumb();
+ invalidate();
+ handle = true;
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onKeyUp(TYPE_LEFT, mLeftThumb.getRangeIndex(), mRightThumb.getRangeIndex());
+ }
+ } else if (mRightThumb.isPressed()) {
+ releaseRightThumb();
+ invalidate();
+ handle = true;
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onKeyUp(TYPE_RIGHT, mLeftThumb.getRangeIndex(), mRightThumb.getRangeIndex());
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ x = (int) event.getX();
+
+ if (!mIsDragging && Math.abs(x - mOriginalX) > mTouchSlop) {
+ mIsDragging = true;
+ }
+ if (mIsDragging) {
+ int moveX = x - mLastX;
+ if (mLeftThumb.isPressed()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ moveLeftThumbByPixel(moveX);
+ handle = true;
+ invalidate();
+ } else if (mRightThumb.isPressed()) {
+ getParent().requestDisallowInterceptTouchEvent(true);
+ moveRightThumbByPixel(moveX);
+ handle = true;
+ invalidate();
+ }
+ }
+
+ mLastX = x;
+ break;
+ }
+
+ return handle;
+ }
+
+ private boolean isValidTickCount(int tickCount) {
+ return (tickCount > 1);
+ }
+
+ private boolean indexOutOfRange(int leftThumbIndex, int rightThumbIndex) {
+ return (leftThumbIndex < 0 || leftThumbIndex > mTickCount
+ || rightThumbIndex < 0
+ || rightThumbIndex > mTickCount);
+ }
+
+ private float getRangeLength() {
+ int width = getMeasuredWidth();
+ if (width < mThumbWidth) {
+ return 0;
+ }
+ return width - mThumbWidth;
+ }
+
+ private float getIntervalLength() {
+ return getRangeLength() / mTickCount;
+ }
+
+ public int getNearestIndex(float x) {
+ return Math.round(x / getIntervalLength());
+ }
+
+ public int getLeftIndex() {
+ return mLeftThumb.getRangeIndex();
+ }
+
+ public int getRightIndex() {
+ return mRightThumb.getRangeIndex();
+ }
+
+ private void notifyRangeChange(int type) {
+ if (mRangeChangeListener != null) {
+// mRangeChangeListener.onRangeChange(this, type, mLeftThumb.getRangeIndex(), mRightThumb.getRangeIndex());
+ }
+ }
+
+ public void setRangeChangeListener(OnRangeChangeListener rangeChangeListener) {
+ mRangeChangeListener = rangeChangeListener;
+ }
+
+ /**
+ * Sets the tick count in the RangeSlider.
+ *
+ * @param count Integer specifying the number of ticks.
+ */
+ public void setTickCount(int count) {
+ int tickCount = (count - mTickStart) / mTickInterval;
+ if (isValidTickCount(tickCount)) {
+ mTickEnd = count;
+ mTickCount = tickCount;
+ mRightThumb.setTickIndex(mTickCount);
+ } else {
+ throw new IllegalArgumentException("tickCount less than 2; invalid tickCount.");
+ }
+ }
+
+ /**
+ * The location of the thumbs according by the supplied index.
+ * Numbered from 0 to mTickCount - 1 from the left.
+ *
+ * @param leftIndex Integer specifying the index of the left thumb
+ * @param rightIndex Integer specifying the index of the right thumb
+ */
+ public void setRangeIndex(int leftIndex, int rightIndex) {
+ if (indexOutOfRange(leftIndex, rightIndex)) {
+ throw new IllegalArgumentException(
+ "Thumb index left " + leftIndex + ", or right " + rightIndex
+ + " is out of bounds. Check that it is greater than the minimum ("
+ + mTickStart + ") and less than the maximum value ("
+ + mTickEnd + ")");
+ } else {
+ if (mLeftThumb.getRangeIndex() != leftIndex) {
+ mLeftThumb.setTickIndex(leftIndex);
+ }
+ if (mRightThumb.getRangeIndex() != rightIndex) {
+ mRightThumb.setTickIndex(rightIndex);
+ }
+ }
+ }
+
+ private boolean moveThumbByIndex(@NonNull ThumbView view, int index) {
+ view.setX(index * getIntervalLength());
+ if (view.getRangeIndex() != index) {
+ view.setTickIndex(index);
+ return true;
+ }
+ return false;
+ }
+
+ private void moveLeftThumbByPixel(int pixel) {
+ float x = mLeftThumb.getX() + pixel;
+ float interval = getIntervalLength();
+ float start = mTickStart / mTickInterval * interval;
+ float end = mTickEnd / mTickInterval * interval;
+
+ if (x > start && x < end && x < mRightThumb.getX() - mThumbWidth) {
+ mLeftThumb.setX(x);
+ int index = getNearestIndex(x);
+ if (mLeftThumb.getRangeIndex() != index) {
+ mLeftThumb.setTickIndex(index);
+ notifyRangeChange(TYPE_LEFT);
+ }
+ }
+ }
+
+ private void moveRightThumbByPixel(int pixel) {
+ float x = mRightThumb.getX() + pixel;
+ float interval = getIntervalLength();
+ float start = mTickStart / mTickInterval * interval;
+ float end = mTickEnd / mTickInterval * interval;
+
+ if (x > start && x < end && x > mLeftThumb.getX() + mThumbWidth) {
+ mRightThumb.setX(x);
+ int index = getNearestIndex(x);
+ if (mRightThumb.getRangeIndex() != index) {
+ mRightThumb.setTickIndex(index);
+ notifyRangeChange(TYPE_RIGHT);
+ }
+ }
+ }
+
+ private void releaseLeftThumb() {
+ int index = getNearestIndex(mLeftThumb.getX());
+ int endIndex = mRightThumb.getRangeIndex();
+ if (index >= endIndex) {
+ index = endIndex - 1;
+ }
+ if (moveThumbByIndex(mLeftThumb, index)) {
+ notifyRangeChange(TYPE_LEFT);
+ }
+ mLeftThumb.setPressed(false);
+ }
+
+ private void releaseRightThumb() {
+ int index = getNearestIndex(mRightThumb.getX());
+ int endIndex = mLeftThumb.getRangeIndex();
+ if (index <= endIndex) {
+ index = endIndex + 1;
+ }
+ if (moveThumbByIndex(mRightThumb, index)) {
+ notifyRangeChange(TYPE_RIGHT);
+ }
+ mRightThumb.setPressed(false);
+ }
+
+ public void setCutRange(int leftTick, int rightTick) {
+ if (mLeftThumb.getRangeIndex() != leftTick) {
+ moveThumbByIndex(mLeftThumb, leftTick);
+ }
+ if (mRightThumb.getRangeIndex() != rightTick) {
+ moveThumbByIndex(mRightThumb, rightTick);
+ }
+ invalidate();
+ }
+
+ public void setLeftIconResource(int resid) {
+ mLeftThumb.setThumbDrawable(getResources().getDrawable(resid));
+ }
+
+ public void setRightIconResource(int resid) {
+ mRightThumb.setThumbDrawable(getResources().getDrawable(resid));
+ }
+
+ public interface OnRangeChangeListener {
+ void onKeyDown(int type);
+
+ void onKeyUp(int type, int leftPinIndex, int rightPinIndex);
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/ThumbView.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/ThumbView.java
new file mode 100644
index 0000000..46655f2
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/ThumbView.java
@@ -0,0 +1,73 @@
+package com.tencent.qcloud.ugckit.component.slider;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import androidx.annotation.NonNull;
+import android.util.TypedValue;
+import android.view.View;
+
+public class ThumbView extends View {
+
+ private static final int EXTEND_TOUCH_SLOP = 15;
+
+ private final int mExtendTouchSlop;
+ private Drawable mThumbDrawable;
+ private boolean mPressed;
+ private int mThumbWidth;
+ private int mTickIndex;
+
+ public ThumbView(@NonNull Context context, int thumbWidth, Drawable drawable) {
+ super(context);
+ mThumbWidth = thumbWidth;
+ mThumbDrawable = drawable;
+ mExtendTouchSlop = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ EXTEND_TOUCH_SLOP, context.getResources().getDisplayMetrics());
+ setBackgroundDrawable(mThumbDrawable);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(MeasureSpec.makeMeasureSpec(mThumbWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY));
+
+ mThumbDrawable.setBounds(0, 0, mThumbWidth, getMeasuredHeight());
+ }
+
+ public void setThumbWidth(int thumbWidth) {
+ mThumbWidth = thumbWidth;
+ }
+
+ public void setThumbDrawable(Drawable thumbDrawable) {
+ mThumbDrawable = thumbDrawable;
+ setBackgroundDrawable(mThumbDrawable);
+ }
+
+ public boolean inInTarget(int x, int y) {
+ Rect rect = new Rect();
+ getHitRect(rect);
+ rect.left -= mExtendTouchSlop;
+ rect.right += mExtendTouchSlop;
+ rect.top -= mExtendTouchSlop;
+ rect.bottom += mExtendTouchSlop;
+ return rect.contains(x, y);
+ }
+
+ public int getRangeIndex() {
+ return mTickIndex;
+ }
+
+ public void setTickIndex(int tickIndex) {
+ mTickIndex = tickIndex;
+ }
+
+ @Override
+ public boolean isPressed() {
+ return mPressed;
+ }
+
+ @Override
+ public void setPressed(boolean pressed) {
+ mPressed = pressed;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/VideoCutView.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/VideoCutView.java
new file mode 100644
index 0000000..97a6a29
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/slider/VideoCutView.java
@@ -0,0 +1,236 @@
+package com.tencent.qcloud.ugckit.component.slider;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+
+import com.tencent.qcloud.ugckit.module.effect.time.TCVideoEditerAdapter;
+import com.tencent.qcloud.ugckit.module.effect.utils.Edit;
+import com.tencent.qcloud.ugckit.R;
+import com.tencent.ugc.TXVideoEditConstants;
+
+/**
+ * 裁剪View
+ */
+public class VideoCutView extends RelativeLayout implements RangeSlider.OnRangeChangeListener {
+
+ @NonNull
+ private String TAG = "VideoCutView";
+ private Context mContext;
+ private RecyclerView mRecyclerView;
+ private RangeSlider mRangeSlider;
+ private float mCurrentScroll;
+ /**
+ * 单个缩略图的宽度
+ */
+ private int mSingleWidth;
+ /**
+ * 所有缩略图的宽度
+ */
+ private int mAllWidth;
+ /**
+ * 整个视频的时长
+ */
+ private long mVideoDuration;
+ /**
+ * 控件最大时长16s
+ */
+ private long mViewMaxDuration;
+ /**
+ * 如果视频时长超过了控件的最大时长,底部在滑动时最左边的起始位置时间
+ */
+ private long mStartTime = 0;
+ /**
+ * 裁剪的起始时间,最左边是0
+ */
+ private int mViewLeftTime;
+ /**
+ * 裁剪的结束时间,最右边最大是16000ms
+ */
+ private int mViewRightTime;
+ /**
+ * 最终视频的起始时间
+ */
+ private long mVideoStartPos;
+ /**
+ * 最终视频的结束时间
+ */
+ private long mVideoEndPos;
+ private TCVideoEditerAdapter mAdapter;
+ private Edit.OnCutChangeListener mRangeChangeListener;
+
+ public VideoCutView(Context context) {
+ super(context);
+
+ init(context);
+ }
+
+ public VideoCutView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ init(context);
+ }
+
+ public VideoCutView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ init(context);
+ }
+
+ private void init(Context context) {
+ mContext = context;
+ inflate(getContext(), R.layout.ugckit_item_edit_view, this);
+
+ mRangeSlider = (RangeSlider) findViewById(R.id.range_slider);
+ mRangeSlider.setRangeChangeListener(this);
+
+ LinearLayoutManager manager = new LinearLayoutManager(mContext);
+ manager.setOrientation(LinearLayoutManager.HORIZONTAL);
+ mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
+ mRecyclerView.setLayoutManager(manager);
+ mRecyclerView.addOnScrollListener(mOnScrollListener);
+
+ mAdapter = new TCVideoEditerAdapter(mContext);
+ mRecyclerView.setAdapter(mAdapter);
+
+ mSingleWidth = mContext.getResources().getDimensionPixelOffset(R.dimen.ugckit_item_thumb_height);
+ }
+
+ /**
+ * 设置缩略图个数
+ *
+ * @param count
+ */
+ public void setCount(int count) {
+ ViewGroup.LayoutParams layoutParams = getLayoutParams();
+ int width = count * mSingleWidth;
+ mAllWidth = width;
+ Resources resources = getResources();
+ DisplayMetrics dm = resources.getDisplayMetrics();
+ int screenWidth = dm.widthPixels;
+ if (width > screenWidth) {
+ width = screenWidth;
+ }
+ layoutParams.width = width + 2 * resources.getDimensionPixelOffset(R.dimen.ugckit_cut_margin);
+ setLayoutParams(layoutParams);
+ }
+
+ /**
+ * 设置裁剪Listener
+ *
+ * @param listener
+ */
+ public void setCutChangeListener(Edit.OnCutChangeListener listener) {
+ mRangeChangeListener = listener;
+ }
+
+ public void setMediaFileInfo(@Nullable TXVideoEditConstants.TXVideoInfo videoInfo) {
+ if (videoInfo == null) {
+ return;
+ }
+ mVideoDuration = videoInfo.duration;
+
+ if (mVideoDuration >= 16000) {
+ mViewMaxDuration = 16000;
+ } else {
+ mViewMaxDuration = mVideoDuration;
+ }
+
+ mViewLeftTime = 0;
+ mViewRightTime = (int) mViewMaxDuration;
+
+ mVideoStartPos = 0;
+ mVideoEndPos = mViewMaxDuration;
+ }
+
+ public void addBitmap(int index, Bitmap bitmap) {
+ mAdapter.add(index, bitmap);
+ }
+
+ public void clearAllBitmap() {
+ mAdapter.clearAllBitmap();
+ }
+
+ @Override
+ public void onKeyDown(int type) {
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onCutChangeKeyDown();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mAdapter != null) {
+ Log.i(TAG, "onDetachedFromWindow");
+ mAdapter.clearAllBitmap();
+ }
+ }
+
+ @Override
+ public void onKeyUp(int type, int leftPinIndex, int rightPinIndex) {
+ mViewLeftTime = (int) (mViewMaxDuration * leftPinIndex / 100); //ms
+ mViewRightTime = (int) (mViewMaxDuration * rightPinIndex / 100);
+
+ onTimeChanged();
+ }
+
+ private void onTimeChanged() {
+ mVideoStartPos = mStartTime + mViewLeftTime;
+ mVideoEndPos = mStartTime + mViewRightTime;
+
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onCutChangeKeyUp((int) mVideoStartPos, (int) mVideoEndPos, 0);
+ }
+ }
+
+ @NonNull
+ private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ super.onScrollStateChanged(recyclerView, newState);
+ Log.i(TAG, "onScrollStateChanged, new state = " + newState);
+
+ switch (newState) {
+ case RecyclerView.SCROLL_STATE_IDLE:
+ onTimeChanged();
+ break;
+ case RecyclerView.SCROLL_STATE_DRAGGING:
+ if (mRangeChangeListener != null) {
+ mRangeChangeListener.onCutChangeKeyDown();
+ }
+ break;
+ case RecyclerView.SCROLL_STATE_SETTLING:
+
+ break;
+ }
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+
+ mCurrentScroll = mCurrentScroll + dx;
+ float rate = mCurrentScroll / mAllWidth;
+ if (mCurrentScroll + mRecyclerView.getWidth() >= mAllWidth) {
+ mStartTime = mVideoDuration - mViewMaxDuration;
+ } else {
+ mStartTime = (int) (rate * mVideoDuration);
+ }
+ }
+ };
+
+ public RangeSlider getRangeSlider() {
+ return mRangeSlider;
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/Closeable.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/Closeable.java
new file mode 100644
index 0000000..66f181f
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/Closeable.java
@@ -0,0 +1,26 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+public interface Closeable {
+
+ /**
+ * Smooth closed the menu.
+ */
+ void smoothCloseMenu();
+
+ /**
+ * Smooth closed the menu on the left.
+ */
+ void smoothCloseLeftMenu();
+
+ /**
+ * Smooth closed the menu on the right.
+ */
+ void smoothCloseRightMenu();
+
+ /**
+ * Smooth closed the menu for the duration.
+ *
+ * @param duration duration time.
+ */
+ void smoothCloseMenu(int duration);
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/MenuAdapter.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/MenuAdapter.java
new file mode 100644
index 0000000..8c47338
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/MenuAdapter.java
@@ -0,0 +1,150 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+import android.content.Context;
+import android.text.TextUtils;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+
+import com.tencent.qcloud.ugckit.module.picker.data.ItemView;
+import com.tencent.qcloud.ugckit.module.picker.data.TCVideoFileInfo;
+import com.tencent.qcloud.ugckit.utils.DateTimeUtil;
+import com.tencent.qcloud.ugckit.R;
+
+
+import java.util.ArrayList;
+
+public class MenuAdapter extends SwipeMenuAdapter {
+
+ private Context mContext;
+ private ArrayList mTCVideoFileInfoList;
+ private ItemView.OnDeleteListener mOnDeleteListener;
+ public int mBitmapWidth;
+ public int mBitmapHeight;
+ private int mRemoveIconId;
+
+ public MenuAdapter(Context context, ArrayList fileInfos) {
+ mContext = context;
+ this.mTCVideoFileInfoList = fileInfos;
+ }
+
+ public void removeIndex(int position) {
+ this.mTCVideoFileInfoList.remove(position);
+ notifyItemRemoved(position);
+ notifyItemRangeChanged(position, mTCVideoFileInfoList.size());
+ }
+
+ public void addItem(TCVideoFileInfo fileInfo) {
+ this.mTCVideoFileInfoList.add(fileInfo);
+ notifyItemInserted(mTCVideoFileInfoList.size());
+ }
+
+ public boolean contains(TCVideoFileInfo fileInfo) {
+ return this.mTCVideoFileInfoList.contains(fileInfo);
+ }
+
+ public void setOnItemDeleteListener(ItemView.OnDeleteListener onDeleteListener) {
+ this.mOnDeleteListener = onDeleteListener;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mTCVideoFileInfoList == null ? 0 : mTCVideoFileInfoList.size();
+ }
+
+ public TCVideoFileInfo getItem(int position) {
+ return mTCVideoFileInfoList.get(position);
+ }
+
+ @Override
+ public View onCreateContentView(@NonNull ViewGroup parent, int viewType) {
+ return LayoutInflater.from(parent.getContext()).inflate(R.layout.ugckit_swipe_menu_item, parent, false);
+ }
+
+ @NonNull
+ @Override
+ public MenuAdapter.DefaultViewHolder onCompatCreateViewHolder(@NonNull View realContentView, int viewType) {
+ return new DefaultViewHolder(realContentView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MenuAdapter.DefaultViewHolder holder, int position) {
+ TCVideoFileInfo fileInfo = mTCVideoFileInfoList.get(position);
+ holder.setOnDeleteListener(mOnDeleteListener);
+
+ // 自定义宽高
+ if (mBitmapHeight != 0 && mBitmapWidth != 0) {
+ RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(mBitmapWidth, mBitmapHeight);
+ holder.ivThumb.setLayoutParams(params);
+ }
+ // 自定义删除按钮
+ if (mRemoveIconId != 0) {
+ holder.ivDelete.setImageResource(mRemoveIconId);
+ }
+ if (fileInfo.getFileType() == TCVideoFileInfo.FILE_TYPE_PICTURE) {
+ holder.tvDuration.setVisibility(View.GONE);
+ } else {
+ holder.setDuration(DateTimeUtil.duration(fileInfo.getDuration()));
+ holder.tvDuration.setVisibility(View.VISIBLE);
+ }
+ if (!TextUtils.isEmpty(fileInfo.getFilePath())) {
+ Glide.with(mContext).load(fileInfo.getFilePath()).into(holder.ivThumb);
+ } else {
+ Glide.with(mContext).load(fileInfo.getFileUri()).into(holder.ivThumb);
+ }
+ }
+
+ public ArrayList getAll() {
+ return mTCVideoFileInfoList;
+ }
+
+ static class DefaultViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ ImageView ivThumb;
+ TextView tvDuration;
+ ImageView ivDelete;
+ ItemView.OnDeleteListener mOnDeleteListener;
+
+ public DefaultViewHolder(@NonNull View itemView) {
+ super(itemView);
+ ivThumb = (ImageView) itemView.findViewById(R.id.iv_icon);
+
+ tvDuration = (TextView) itemView.findViewById(R.id.tv_duration);
+ ivDelete = (ImageView) itemView.findViewById(R.id.iv_close);
+ ivDelete.setOnClickListener(this);
+ }
+
+ public void setOnDeleteListener(ItemView.OnDeleteListener onDeleteListener) {
+ this.mOnDeleteListener = onDeleteListener;
+ }
+
+ public void setDuration(String duration) {
+ this.tvDuration.setText(duration);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mOnDeleteListener != null) {
+ mOnDeleteListener.onDelete(getAdapterPosition());
+ }
+ }
+ }
+
+ public void setBitmapWidth(int bitmapWidth) {
+ mBitmapWidth = bitmapWidth;
+ }
+
+ public void setBitmapHeight(int bitmapHeight) {
+ mBitmapHeight = bitmapHeight;
+ }
+
+ public void setRemoveIconId(int resId) {
+ mRemoveIconId = resId;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/OnSwipeMenuItemClickListener.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/OnSwipeMenuItemClickListener.java
new file mode 100644
index 0000000..0a7b4d6
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/OnSwipeMenuItemClickListener.java
@@ -0,0 +1,15 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+public interface OnSwipeMenuItemClickListener {
+
+ /**
+ * Invoke when the menu item is clicked.
+ *
+ * @param closeable closeable.
+ * @param adapterPosition adapterPosition.
+ * @param menuPosition menuPosition.
+ * @param direction can be {@link SwipeMenuRecyclerView#LEFT_DIRECTION}, {@link SwipeMenuRecyclerView#RIGHT_DIRECTION}.
+ */
+ void onItemClick(Closeable closeable, int adapterPosition, int menuPosition, @SwipeMenuRecyclerView.DirectionMode int direction);
+
+}
\ No newline at end of file
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/Openable.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/Openable.java
new file mode 100644
index 0000000..ffb5375
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/Openable.java
@@ -0,0 +1,98 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+
+public interface Openable {
+
+ /**
+ * The menu is open?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isMenuOpen();
+
+ /**
+ * The menu is open on the left?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isLeftMenuOpen();
+
+ /**
+ * The menu is open on the right?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isRightMenuOpen();
+
+ /**
+ * The menu is completely open?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isCompleteOpen();
+
+ /**
+ * The menu is completely open on the left?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isLeftCompleteOpen();
+
+ /**
+ * The menu is completely open on the right?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isRightCompleteOpen();
+
+ /**
+ * The menu is open?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isMenuOpenNotEqual();
+
+ /**
+ * The menu is open on the left?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isLeftMenuOpenNotEqual();
+
+ /**
+ * The menu is open on the right?
+ *
+ * @return true, otherwise false.
+ */
+ boolean isRightMenuOpenNotEqual();
+
+ /**
+ * Open the current menu.
+ */
+ void smoothOpenMenu();
+
+ /**
+ * Open the menu on left.
+ */
+ void smoothOpenLeftMenu();
+
+ /**
+ * Open the menu on right.
+ */
+ void smoothOpenRightMenu();
+
+ /**
+ * Open the menu on left for the duration.
+ *
+ * @param duration duration time.
+ */
+ void smoothOpenLeftMenu(int duration);
+
+ /**
+ * Open the menu on right for the duration.
+ *
+ * @param duration duration time.
+ */
+ void smoothOpenRightMenu(int duration);
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/ResCompat.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/ResCompat.java
new file mode 100644
index 0000000..b2acb3b
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/ResCompat.java
@@ -0,0 +1,105 @@
+
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.view.View;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+
+public class ResCompat {
+
+ @Nullable
+ public static Drawable getDrawable(@NonNull Context context, int drawableId) {
+ return getDrawable(context, drawableId, null);
+ }
+
+ public static Drawable getDrawable(@NonNull Context context, int drawableId, Theme theme) {
+ Resources resources = context.getResources();
+ Class> resourcesClass = resources.getClass();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
+ try {
+ Method getDrawableMethod = resourcesClass.getMethod("getDrawable", int.class, Theme.class);
+ getDrawableMethod.setAccessible(true);
+ return (Drawable) getDrawableMethod.invoke(resources, drawableId, theme);
+ } catch (Throwable e) {
+ }
+ else
+ try {
+ Method getDrawableMethod = resourcesClass.getMethod("getDrawable", int.class);
+ getDrawableMethod.setAccessible(true);
+ return (Drawable) getDrawableMethod.invoke(resources, drawableId);
+ } catch (Throwable e) {
+ }
+ return null;
+ }
+
+ public static int getColor(@NonNull Context context, int colorId) {
+ return getColor(context, colorId, null);
+ }
+
+ public static int getColor(@NonNull Context context, int colorId, Theme theme) {
+ Resources resources = context.getResources();
+ Class> resourcesClass = resources.getClass();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ try {
+ Method getColorMethod = resourcesClass.getMethod("getColor", int.class, Theme.class);
+ getColorMethod.setAccessible(true);
+ return (Integer) getColorMethod.invoke(resources, colorId, theme);
+ } catch (Throwable e) {
+ }
+ else
+ try {
+ Method getColorMethod = resourcesClass.getMethod("getColor", int.class);
+ getColorMethod.setAccessible(true);
+ return (Integer) getColorMethod.invoke(resources, colorId);
+ } catch (Throwable e) {
+ }
+ return Color.BLACK;
+ }
+
+ public static void setBackground(@NonNull View view, int drawableId) {
+ setBackground(view, getDrawable(view.getContext(), drawableId));
+ }
+
+ public static void setBackground(@NonNull View view, Drawable background) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+ setBackground("setBackground", view, background);
+ else
+ setBackground("setBackgroundDrawable", view, background);
+ }
+
+ public static void setBackground(String method, @NonNull View view, Drawable background) {
+ try {
+ Method viewMethod = view.getClass().getMethod(method, Drawable.class);
+ viewMethod.setAccessible(true);
+ viewMethod.invoke(view, background);
+ } catch (Throwable e) {
+ }
+ }
+
+ public static void setTextAppearance(@NonNull TextView view, int textAppearance) {
+ Class> resourcesClass = view.getClass();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ try {
+ Method getColorMethod = resourcesClass.getMethod("setTextAppearance", Context.class, int.class);
+ getColorMethod.setAccessible(true);
+ getColorMethod.invoke(view, view.getContext(), textAppearance);
+ } catch (Throwable e) {
+ }
+ else
+ try {
+ Method getColorMethod = resourcesClass.getMethod("setTextAppearance", int.class);
+ getColorMethod.setAccessible(true);
+ getColorMethod.invoke(view, textAppearance);
+ } catch (Throwable e) {
+ }
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeHorizontal.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeHorizontal.java
new file mode 100644
index 0000000..2990059
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeHorizontal.java
@@ -0,0 +1,61 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.OverScroller;
+
+public abstract class SwipeHorizontal {
+
+ private int direction;
+ private View menuView;
+ protected Checker mChecker;
+
+ public SwipeHorizontal(int direction, View menuView) {
+ this.direction = direction;
+ this.menuView = menuView;
+ mChecker = new Checker();
+ }
+
+ public boolean canSwipe() {
+ if (menuView instanceof ViewGroup) {
+ return ((ViewGroup) menuView).getChildCount() > 0;
+ }
+ return false;
+ }
+
+ public boolean isCompleteClose(int scrollX) {
+ int i = -getMenuView().getWidth() * getDirection();
+ return scrollX == 0 && i != 0;
+ }
+
+ public abstract boolean isMenuOpen(int scrollX);
+
+ public abstract boolean isMenuOpenNotEqual(int scrollX);
+
+ public abstract void autoOpenMenu(OverScroller scroller, int scrollX, int duration);
+
+ public abstract void autoCloseMenu(OverScroller scroller, int scrollX, int duration);
+
+ public abstract Checker checkXY(int x, int y);
+
+ public abstract boolean isClickOnContentView(int contentViewWidth, float x);
+
+ public int getDirection() {
+ return direction;
+ }
+
+ public View getMenuView() {
+ return menuView;
+ }
+
+ public int getMenuWidth() {
+ return menuView.getWidth();
+ }
+
+ public static final class Checker {
+ public int x;
+ public int y;
+ public boolean shouldResetSwipe;
+ }
+
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeLeftHorizontal.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeLeftHorizontal.java
new file mode 100644
index 0000000..f787f1d
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeLeftHorizontal.java
@@ -0,0 +1,55 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+import androidx.annotation.NonNull;
+import android.view.View;
+import android.widget.OverScroller;
+
+public class SwipeLeftHorizontal extends SwipeHorizontal {
+
+ public SwipeLeftHorizontal(View menuView) {
+ super(SwipeMenuRecyclerView.LEFT_DIRECTION, menuView);
+ }
+
+ @Override
+ public boolean isMenuOpen(int scrollX) {
+ int i = -getMenuView().getWidth() * getDirection();
+ return scrollX <= i && i != 0;
+ }
+
+ @Override
+ public boolean isMenuOpenNotEqual(int scrollX) {
+ return scrollX < -getMenuView().getWidth() * getDirection();
+ }
+
+ @Override
+ public void autoOpenMenu(@NonNull OverScroller scroller, int scrollX, int duration) {
+ scroller.startScroll(Math.abs(scrollX), 0, getMenuView().getWidth() - Math.abs(scrollX), 0, duration);
+ }
+
+ @Override
+ public void autoCloseMenu(@NonNull OverScroller scroller, int scrollX, int duration) {
+ scroller.startScroll(-Math.abs(scrollX), 0, Math.abs(scrollX), 0, duration);
+ }
+
+ @Override
+ public Checker checkXY(int x, int y) {
+ mChecker.x = x;
+ mChecker.y = y;
+ mChecker.shouldResetSwipe = false;
+ if (mChecker.x == 0) {
+ mChecker.shouldResetSwipe = true;
+ }
+ if (mChecker.x >= 0) {
+ mChecker.x = 0;
+ }
+ if (mChecker.x <= -getMenuView().getWidth()) {
+ mChecker.x = -getMenuView().getWidth();
+ }
+ return mChecker;
+ }
+
+ @Override
+ public boolean isClickOnContentView(int contentViewWidth, float x) {
+ return x > getMenuView().getWidth();
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeMenu.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeMenu.java
new file mode 100644
index 0000000..b5113c4
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeMenu.java
@@ -0,0 +1,100 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+import android.content.Context;
+import androidx.annotation.IntDef;
+import android.widget.LinearLayout;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SwipeMenu {
+
+ public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
+ public static final int VERTICAL = LinearLayout.VERTICAL;
+
+ private int mViewType;
+ private int orientation = HORIZONTAL;
+ private SwipeMenuLayout mSwipeMenuLayout;
+ private List mSwipeMenuItems;
+
+ @IntDef({HORIZONTAL, VERTICAL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OrientationMode {
+ }
+
+ public SwipeMenu(SwipeMenuLayout swipeMenuLayout, int viewType) {
+ this.mSwipeMenuLayout = swipeMenuLayout;
+ this.mViewType = viewType;
+ this.mSwipeMenuItems = new ArrayList<>(2);
+ }
+
+ /**
+ * Set a percentage.
+ *
+ * @param openPercent such as 0.5F.
+ */
+ public void setOpenPercent(float openPercent) {
+ if (openPercent != mSwipeMenuLayout.getOpenPercent()) {
+ openPercent = openPercent > 1 ? 1 : (openPercent < 0 ? 0 : openPercent);
+ mSwipeMenuLayout.setOpenPercent(openPercent);
+ }
+ }
+
+ /**
+ * The duration of the set.
+ *
+ * @param scrollerDuration such 500.
+ */
+ public void setScrollerDuration(int scrollerDuration) {
+ this.mSwipeMenuLayout.setScrollerDuration(scrollerDuration);
+ }
+
+ /**
+ * Set the menu orientation.
+ *
+ * @param orientation use {@link SwipeMenu#HORIZONTAL} or {@link SwipeMenu#VERTICAL}.
+ * @see SwipeMenu#HORIZONTAL
+ * @see SwipeMenu#VERTICAL
+ */
+ public void setOrientation(@OrientationMode int orientation) {
+ if (orientation != HORIZONTAL && orientation != VERTICAL)
+ throw new IllegalArgumentException("Use SwipeMenu#HORIZONTAL or SwipeMenu#VERTICAL.");
+ this.orientation = orientation;
+ }
+
+ /**
+ * Get the menu orientation.
+ *
+ * @return {@link SwipeMenu#HORIZONTAL} or {@link SwipeMenu#VERTICAL}.
+ */
+ @OrientationMode
+ public int getOrientation() {
+ return orientation;
+ }
+
+ public void addMenuItem(SwipeMenuItem item) {
+ mSwipeMenuItems.add(item);
+ }
+
+ public void removeMenuItem(SwipeMenuItem item) {
+ mSwipeMenuItems.remove(item);
+ }
+
+ public List getMenuItems() {
+ return mSwipeMenuItems;
+ }
+
+ public SwipeMenuItem getMenuItem(int index) {
+ return mSwipeMenuItems.get(index);
+ }
+
+ public Context getContext() {
+ return mSwipeMenuLayout.getContext();
+ }
+
+ public int getViewType() {
+ return mViewType;
+ }
+}
diff --git a/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeMenuAdapter.java b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeMenuAdapter.java
new file mode 100644
index 0000000..b9f2879
--- /dev/null
+++ b/ugckit/src/main/java/com/tencent/qcloud/ugckit/component/swipemenu/SwipeMenuAdapter.java
@@ -0,0 +1,126 @@
+package com.tencent.qcloud.ugckit.component.swipemenu;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+import com.tencent.qcloud.ugckit.R;
+
+import java.util.List;
+
+
+public abstract class SwipeMenuAdapter extends RecyclerView.Adapter {
+
+ private SwipeMenuCreator mSwipeMenuCreator; // Swipe menu creator。
+ private OnSwipeMenuItemClickListener mSwipeMenuItemClickListener; // Swipe menu click listener。
+
+ /**
+ * Set to create menu listener.
+ *
+ * @param swipeMenuCreator listener.
+ */
+ void setSwipeMenuCreator(SwipeMenuCreator swipeMenuCreator) {
+ this.mSwipeMenuCreator = swipeMenuCreator;
+ }
+
+ /**
+ * Set to click menu listener.
+ *
+ * @param swipeMenuItemClickListener listener.
+ */
+ void setSwipeMenuItemClickListener(OnSwipeMenuItemClickListener swipeMenuItemClickListener) {
+ this.mSwipeMenuItemClickListener = swipeMenuItemClickListener;
+ }
+
+ @NonNull
+ @Override
+ public final VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View contentView = onCreateContentView(parent, viewType);
+ if (mSwipeMenuCreator != null) {
+ SwipeMenuLayout swipeMenuLayout = (SwipeMenuLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout
+ .ugckit_swipe_item, parent, false);
+
+ SwipeMenu swipeLeftMenu = new SwipeMenu(swipeMenuLayout, viewType);
+ SwipeMenu swipeRightMenu = new SwipeMenu(swipeMenuLayout, viewType);
+
+ mSwipeMenuCreator.onCreateMenu(swipeLeftMenu, swipeRightMenu, viewType);
+
+ int leftMenuCount = swipeLeftMenu.getMenuItems().size();
+ if (leftMenuCount > 0) {
+ SwipeMenuView swipeLeftMenuView = (SwipeMenuView) swipeMenuLayout.findViewById(R.id.swipe_left);
+ // noinspection WrongConstant
+ swipeLeftMenuView.setOrientation(swipeLeftMenu.getOrientation());
+ swipeLeftMenuView.bindMenu(swipeLeftMenu, SwipeMenuRecyclerView.LEFT_DIRECTION);
+ swipeLeftMenuView.bindMenuItemClickListener(mSwipeMenuItemClickListener, swipeMenuLayout);
+ }
+
+ int rightMenuCount = swipeRightMenu.getMenuItems().size();
+ if (rightMenuCount > 0) {
+ SwipeMenuView swipeRightMenuView = (SwipeMenuView) swipeMenuLayout.findViewById(R.id.swipe_right);
+ // noinspection WrongConstant
+ swipeRightMenuView.setOrientation(swipeRightMenu.getOrientation());
+ swipeRightMenuView.bindMenu(swipeRightMenu, SwipeMenuRecyclerView.RIGHT_DIRECTION);
+ swipeRightMenuView.bindMenuItemClickListener(mSwipeMenuItemClickListener, swipeMenuLayout);
+ }
+
+ if (leftMenuCount > 0 || rightMenuCount > 0) {
+ ViewGroup viewGroup = (ViewGroup) swipeMenuLayout.findViewById(R.id.swipe_content);
+ viewGroup.addView(contentView);
+ contentView = swipeMenuLayout;
+ }
+ }
+ return onCompatCreateViewHolder(contentView, viewType);
+ }
+
+ /**
+ * Create view for item.
+ *
+ * @param parent The ViewGroup into which the new View will be added after it is bound to an adapter position.
+ * @param viewType The view type of the new view.
+ * @return A new ViewHolder that holds a View of the given view type.
+ */
+ public abstract View onCreateContentView(ViewGroup parent, int viewType);
+
+ /**
+ * Instead {@link #onCreateViewHolder(ViewGroup, int)}.
+ *
+ * @param realContentView Is this Item real view, {@link SwipeMenuLayout} or {@link #onCreateContentView(ViewGroup, int)}.
+ * @param viewType The view type of the new View.
+ * @return A new ViewHolder that holds a View of the given view type.
+ * @see #onCompatBindViewHolder(RecyclerView.ViewHolder, int, List)
+ */
+ @NonNull
+ public abstract VH onCompatCreateViewHolder(View realContentView, int viewType);
+
+ @Override
+ public final void onBindViewHolder(@NonNull VH holder, int position, List