初始代码
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Android 12 or lower -->
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
|
||||
<!-- Android 13 or higher -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
|
||||
<!-- Android 14 or higher -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
|
||||
<application android:supportsRtl="true">
|
||||
<activity
|
||||
android:name="com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name="com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaEditActivity"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
|
||||
android:screenOrientation="portrait" />
|
||||
|
||||
<activity
|
||||
android:name="com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker.AlbumPickerActivity"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode|locale"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 94 KiB |
@@ -0,0 +1,161 @@
|
||||
{
|
||||
"beauty_type_list": [
|
||||
{
|
||||
"beauty_type_name": "@string/multimedia_plugin_setting_panel_beauty",
|
||||
"beauty_item_list": [
|
||||
{
|
||||
"item_type": 0,
|
||||
"item_name": "@string/multimedia_plugin_beauty_cancel",
|
||||
"item_level": -1,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_beauty_cancel",
|
||||
"item_filter_bitmap": ""
|
||||
},
|
||||
{
|
||||
"item_type": 13,
|
||||
"item_name": "@string/multimedia_plugin_panel_white",
|
||||
"item_level": 1,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_beauty_white",
|
||||
"item_filter_bitmap": ""
|
||||
},
|
||||
{
|
||||
"item_type": 10,
|
||||
"item_name": "@string/multimedia_plugin_panel_style_smooth",
|
||||
"item_level": 4,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_beauty_smooth",
|
||||
"item_filter_bitmap": ""
|
||||
},
|
||||
{
|
||||
"item_type": 14,
|
||||
"item_name": "@string/multimedia_plugin_panel_ruddy",
|
||||
"item_level": 0,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_beauty_ruddy",
|
||||
"item_filter_bitmap": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"beauty_type_name": "@string/multimedia_plugin_setting_panel_filter",
|
||||
"beauty_item_list": [
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_beauty_cancel",
|
||||
"item_level": -1,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cancel",
|
||||
"item_filter_bitmap": ""
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_standard",
|
||||
"item_level": 4,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_standard",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_biaozhun"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_cheery",
|
||||
"item_level": 8,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cheery",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_yinghong"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_cloud",
|
||||
"item_level": 8,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cloud",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_yunshang"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_pure",
|
||||
"item_level": 8,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_pure",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_chunzhen"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_orchid",
|
||||
"item_level": 9,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_orchid",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_bailan"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_vitality",
|
||||
"item_level": 8,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_vitality",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_yuanqi"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_super",
|
||||
"item_level": 9,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_super",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_chaotuo"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_fragrance",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_fragrance",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_xiangfen"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_romantic",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_romantic",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_langman"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_fresh",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_fresh",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_qingxin"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_beautiful",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_beautiful",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_weimei"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_pink",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_pink",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_fennen"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_reminiscence",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_reminiscence",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_huaijiu"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_blues",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_blues",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_landiao"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_cool",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_cool",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_qingliang"
|
||||
},
|
||||
{
|
||||
"item_type": 20,
|
||||
"item_name": "@string/multimedia_plugin_panel_filter_Japanese",
|
||||
"item_level": 5,
|
||||
"item_icon_normal": "@drawable/multimedia_plugin_filter_icon_japanese",
|
||||
"item_filter_bitmap": "@drawable/multimedia_plugin_filter_rixi"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"bgm_list": [
|
||||
{
|
||||
"bgm_type_name": "@string/multimedia_plugin_bgm_type_name_default",
|
||||
"bgm_item_list": [
|
||||
{
|
||||
"item_bgm_path": "file:///asset/bgm/test1.mp3",
|
||||
"item_bgm_name": "测试音乐1",
|
||||
"item_bgm_author": "未知",
|
||||
"item_start_time": 0,
|
||||
"item_end_time": 0
|
||||
},
|
||||
{
|
||||
"item_bgm_path": "file:///asset/bgm/test2.mp3",
|
||||
"item_bgm_name": "测试音乐2",
|
||||
"item_bgm_author": "未知",
|
||||
"item_start_time": 0,
|
||||
"item_end_time": 0
|
||||
},
|
||||
{
|
||||
"item_bgm_path": "file:///asset/bgm/test3.mp3",
|
||||
"item_bgm_name": "测试音乐3",
|
||||
"item_bgm_author": "未知",
|
||||
"item_start_time": 0,
|
||||
"item_end_time": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bgm_type_name": "@string/multimedia_plugin_bgm_type_name_chosen",
|
||||
"bgm_item_list": [
|
||||
{
|
||||
"item_bgm_path": "file:///asset/bgm/test1.mp3",
|
||||
"item_bgm_name": "测试音乐1",
|
||||
"item_bgm_author": "未知",
|
||||
"item_start_time": 0,
|
||||
"item_end_time": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"max_record_duration_ms": "15000",
|
||||
"min_record_duration_ms": "2000",
|
||||
"video_quality": "2",
|
||||
"picture_paster_config_file_path": "file:///asset/config/picture_paster_data.json",
|
||||
"bgm_config_file_path": "file:///asset/config/bgm_data.json",
|
||||
"support_record_torch": "true",
|
||||
"support_record_beauty": "true",
|
||||
"support_record_aspect": "true",
|
||||
"support_record_scroll_filter": "true",
|
||||
"support_video_edit_graffiti": "true",
|
||||
"support_video_edit_paster": "true",
|
||||
"support_video_edit_subtitle": "true",
|
||||
"support_video_edit_bgm": "true",
|
||||
"support_picture_edit_graffiti": "true",
|
||||
"support_picture_edit_mosaic": "true",
|
||||
"support_picture_edit_paster": "true",
|
||||
"support_picture_edit_subtitle": "true",
|
||||
"support_picture_edit_crop": "true"
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"paster_type_list": [
|
||||
{
|
||||
"type_name": "",
|
||||
"type_icon_path": "@drawable/emoji_48",
|
||||
"paster_item_list": [
|
||||
{
|
||||
"item_icon_path": "@drawable/multimedia_plugin_edit_paster_file_selector",
|
||||
"item_is_file_selector": true
|
||||
},
|
||||
{
|
||||
"item_icon_path": "@drawable/emoji_31",
|
||||
"item_image_path": "@drawable/emoji_31"
|
||||
},
|
||||
{
|
||||
"item_icon_path": "@drawable/emoji_4",
|
||||
"item_image_path": "@drawable/emoji_4"
|
||||
},
|
||||
{
|
||||
"item_icon_path": "@drawable/emoji_10",
|
||||
"item_image_path": "@drawable/emoji_10"
|
||||
},
|
||||
{
|
||||
"item_icon_path": "@drawable/emoji_35",
|
||||
"item_image_path": "@drawable/emoji_35"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type_icon_path": "file:///asset/4351/ys02@2x.png",
|
||||
"type_name": "@string/multimedia_plugin_default",
|
||||
"paster_item_list": [
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys00@2x.png",
|
||||
"item_icon_path": "file:///asset/4351/ys00@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys01@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys02@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys03@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys04@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys05@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys06@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys07@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys08@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys09@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys10@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys11@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys12@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys13@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys14@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4351/ys15@2x.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type_icon_path": "file:///asset/4352/gcs05@2x.png",
|
||||
"type_name": "@string/multimedia_plugin_default",
|
||||
"paster_item_list": [
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs00@2x.png",
|
||||
"item_icon_path": "file:///asset/4352/gcs00@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs01@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs02@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs03@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs04@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs05@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs06@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs07@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs08@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs09@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs10@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs11@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs12@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs13@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs14@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs15@2x.png"
|
||||
},
|
||||
{
|
||||
"item_image_path": "file:///asset/4352/gcs16@2x.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin;
|
||||
|
||||
public class TUIMultimediaConstants {
|
||||
public static final String RECORD_TYPE_KEY = "record_type";
|
||||
public static final String PARAM_NAME_RECORD_TYPE = "recordType";
|
||||
public static final String PARAM_NAME_EDIT_FILE_PATH = "editFilePath";
|
||||
public static final String PARAM_NAME_EDITED_FILE_PATH = "editedFilePath";
|
||||
public static final String PARAM_NAME_EDIT_FILE_RATIO = "recordFileRatio";
|
||||
public static final String PARAM_NAME_IS_RECODE_FILE = "isRecordFile";
|
||||
|
||||
public static final int RECORD_TYPE_VIDEO = 0;
|
||||
public static final int RECORD_TYPE_PHOTO = 1;
|
||||
|
||||
public enum VideoQuality {
|
||||
LOW(1),
|
||||
MEDIUM(2),
|
||||
HIGH(3);
|
||||
|
||||
private final int value;
|
||||
|
||||
VideoQuality(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static VideoQuality fromInteger(int value) {
|
||||
for (VideoQuality code : VideoQuality.values()) {
|
||||
if (value == code.getValue()) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
return LOW;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants.VideoQuality;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
|
||||
public class TUIMultimediaIConfig {
|
||||
|
||||
static final String TAG = TUIMultimediaIConfig.class.getSimpleName();
|
||||
static final String DEFAULT_JSON_PATH = "file:///asset/config/default_config.json";
|
||||
static final String DEFAULT_BGM_JSON_PATH = "file:///asset/config/bgm_data.json";
|
||||
static final String DEFAULT_PASTER_JSON_PATH = "file:///asset/config/picture_paster_data.json";
|
||||
static final int DEFAULT_MAX_RECORD_DURATION_MS = 15000;
|
||||
static final int DEFAULT_MIN_RECORD_DURATION_MS = 2000;
|
||||
|
||||
@SerializedName("max_record_duration_ms")
|
||||
private int mMaxRecordDurationMs = DEFAULT_MAX_RECORD_DURATION_MS;
|
||||
|
||||
@SerializedName("min_record_duration_ms")
|
||||
private int mMinRecordDurationMs = DEFAULT_MIN_RECORD_DURATION_MS;
|
||||
|
||||
@SerializedName("video_quality")
|
||||
private int mVideoQuality = VideoQuality.MEDIUM.getValue();
|
||||
|
||||
@SerializedName("picture_paster_config_file_path")
|
||||
private String mPicturePasterConfigFilePath = DEFAULT_PASTER_JSON_PATH;
|
||||
|
||||
@SerializedName("bgm_config_file_path")
|
||||
private String mBGMConfigFilePath = DEFAULT_BGM_JSON_PATH;
|
||||
|
||||
@SerializedName("support_record_torch")
|
||||
private boolean mIsSupportRecordTorch = true;
|
||||
|
||||
@SerializedName("support_record_beauty")
|
||||
private boolean mIsSupportRecordBeauty = true;
|
||||
|
||||
@SerializedName("support_record_aspect")
|
||||
private boolean mIsSupportRecordAspect = true;
|
||||
|
||||
@SerializedName("support_record_scroll_filter")
|
||||
private boolean mIsSupportRecordScrollFilter = true;
|
||||
|
||||
@SerializedName("support_video_edit_graffiti")
|
||||
private boolean mIsSupportVideoEditGraffiti = true;
|
||||
|
||||
@SerializedName("support_video_edit_paster")
|
||||
private boolean mIsSupportVideoEditPaster = true;
|
||||
|
||||
@SerializedName("support_video_edit_subtitle")
|
||||
private boolean mIsSupportVideoEditSubtitle = true;
|
||||
|
||||
@SerializedName("support_video_edit_bgm")
|
||||
private boolean mIsSupportVideoEditBGM = true;
|
||||
|
||||
@SerializedName("support_picture_edit_graffiti")
|
||||
private boolean mIsSupportPictureEditGraffiti = true;
|
||||
|
||||
@SerializedName("support_picture_edit_mosaic")
|
||||
private boolean mIsSupportPictureEditMosaic = true;
|
||||
|
||||
@SerializedName("support_picture_edit_paster")
|
||||
private boolean mIsSupportPictureEditPaster = true;
|
||||
|
||||
@SerializedName("support_picture_edit_subtitle")
|
||||
private boolean mIsSupportPictureEditSubtitle = true;
|
||||
|
||||
@SerializedName("support_picture_edit_crop")
|
||||
private boolean mIsSupportPictureEditCrop = true;
|
||||
|
||||
|
||||
private JsonObject mJsonObject;
|
||||
|
||||
private static TUIMultimediaIConfig sInstance;
|
||||
|
||||
public static TUIMultimediaIConfig getInstance() {
|
||||
if (sInstance == null) {
|
||||
synchronized (TUIMultimediaIConfig.class) {
|
||||
if (sInstance == null) {
|
||||
String json = TUIMultimediaFileUtil.readTextFromFile(DEFAULT_JSON_PATH);
|
||||
LiteavLog.i(TAG, "initDefaultConfig json = " + json);
|
||||
Gson gson = new Gson();
|
||||
sInstance = gson.fromJson(json, TUIMultimediaIConfig.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void setConfig(String configJsonString) {
|
||||
if (configJsonString == null || configJsonString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LiteavLog.i(TAG, "setConfigJsonString json = " + configJsonString);
|
||||
Gson gson = new Gson();
|
||||
mJsonObject = gson.fromJson(configJsonString, JsonObject.class);
|
||||
}
|
||||
|
||||
public boolean isSupportRecordBeauty() {
|
||||
return getBoolFromJsonObject("support_record_beauty", mIsSupportRecordBeauty);
|
||||
}
|
||||
|
||||
public boolean isSupportRecordAspect() {
|
||||
return getBoolFromJsonObject("support_record_aspect", mIsSupportRecordAspect);
|
||||
}
|
||||
|
||||
public boolean isSupportRecordTorch() {
|
||||
return getBoolFromJsonObject("support_record_torch", mIsSupportRecordTorch);
|
||||
}
|
||||
|
||||
public boolean isSupportRecordScrollFilter() {
|
||||
return getBoolFromJsonObject("support_record_scroll_filter", mIsSupportRecordScrollFilter);
|
||||
|
||||
}
|
||||
|
||||
public boolean isSupportVideoEditGraffiti() {
|
||||
return getBoolFromJsonObject("support_video_edit_graffiti", mIsSupportVideoEditGraffiti);
|
||||
}
|
||||
|
||||
public boolean isSupportVideoEditPaster() {
|
||||
return getBoolFromJsonObject("support_video_edit_paster", mIsSupportVideoEditPaster);
|
||||
}
|
||||
|
||||
public boolean isSupportVideoEditSubtitle() {
|
||||
return getBoolFromJsonObject("support_video_edit_subtitle", mIsSupportVideoEditSubtitle);
|
||||
|
||||
}
|
||||
|
||||
public boolean isSupportVideoEditBGM() {
|
||||
return getBoolFromJsonObject("support_video_edit_bgm", mIsSupportVideoEditBGM);
|
||||
}
|
||||
|
||||
public boolean isSupportPictureEditGraffiti() {
|
||||
return getBoolFromJsonObject("support_picture_edit_graffiti", mIsSupportPictureEditGraffiti);
|
||||
}
|
||||
|
||||
public boolean isSupportPictureEditMosaic() {
|
||||
return getBoolFromJsonObject("support_picture_edit_mosaic", mIsSupportPictureEditMosaic);
|
||||
}
|
||||
|
||||
public boolean isSupportPictureEditPaster() {
|
||||
return getBoolFromJsonObject("support_picture_edit_paster", mIsSupportPictureEditPaster);
|
||||
}
|
||||
|
||||
public boolean isSupportPictureEditSubtitle() {
|
||||
return getBoolFromJsonObject("support_picture_edit_subtitle", mIsSupportPictureEditSubtitle);
|
||||
}
|
||||
|
||||
public boolean isSupportPictureEditCrop() {
|
||||
return getBoolFromJsonObject("support_picture_edit_crop", mIsSupportPictureEditCrop);
|
||||
}
|
||||
|
||||
public int getThemeColor() {
|
||||
return TUIMultimediaResourceUtils.getResources()
|
||||
.getColor(TUIThemeManager.getAttrResId(TUIMultimediaPlugin.getAppContext(),
|
||||
com.tencent.qcloud.tuicore.R.attr.core_primary_color));
|
||||
}
|
||||
|
||||
public int getMaxRecordDurationMs() {
|
||||
return getIntFromJsonObject("max_record_duration_ms", mMaxRecordDurationMs);
|
||||
}
|
||||
|
||||
public int getMinRecordDurationMs() {
|
||||
return getIntFromJsonObject("min_record_duration_ms", sInstance.mMinRecordDurationMs);
|
||||
}
|
||||
|
||||
public VideoQuality getVideoQuality() {
|
||||
return VideoQuality.fromInteger(sInstance.getIntFromJsonObject("video_quality", sInstance.mVideoQuality));
|
||||
}
|
||||
|
||||
public String getPicturePasterConfigFilePath() {
|
||||
return getStringFromJsonObject("picture_paster_config_file_path", mPicturePasterConfigFilePath);
|
||||
}
|
||||
|
||||
public String getBGMConfigFilePath() {
|
||||
return getStringFromJsonObject("bgm_config_file_path", mBGMConfigFilePath);
|
||||
}
|
||||
|
||||
private int getIntFromJsonObject(String key, int defaultValue) {
|
||||
if (mJsonObject == null || !mJsonObject.has(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return mJsonObject.get(key).getAsInt();
|
||||
}
|
||||
|
||||
private boolean getBoolFromJsonObject(String key, boolean defaultValue) {
|
||||
if (mJsonObject == null || !mJsonObject.has(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return mJsonObject.get(key).getAsBoolean();
|
||||
}
|
||||
|
||||
private String getStringFromJsonObject(String key, String defaultValue) {
|
||||
if (mJsonObject == null || !mJsonObject.has(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return mJsonObject.get(key).getAsString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.tencent.qcloud.tuicore.ServiceInitializer;
|
||||
import com.tencent.qcloud.tuicore.TUIConstants;
|
||||
import com.tencent.qcloud.tuicore.TUICore;
|
||||
import com.tencent.qcloud.tuicore.annotations.TUIInitializerDependency;
|
||||
import com.tencent.qcloud.tuicore.annotations.TUIInitializerID;
|
||||
import com.tencent.qcloud.tuicore.interfaces.ITUINotification;
|
||||
import com.tencent.qcloud.tuicore.interfaces.TUIInitializer;
|
||||
import com.tencent.qcloud.tuikit.tuichat.component.album.AlbumPicker;
|
||||
import com.tencent.qcloud.tuikit.tuichat.component.album.VideoRecorder;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker.SignatureCheckListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.TUIMultimediaAlbumPicker;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecorder;
|
||||
import java.util.Map;
|
||||
|
||||
@AutoService(TUIInitializer.class)
|
||||
@TUIInitializerDependency({"TUIChat"})
|
||||
@TUIInitializerID("TUIMultimediaRecordService")
|
||||
public class TUIMultimediaPlugin implements TUIInitializer, ITUINotification {
|
||||
public static final String TAG = TUIMultimediaPlugin.class.getSimpleName();
|
||||
|
||||
public static Context getAppContext() {
|
||||
return ServiceInitializer.getAppContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Context context) {
|
||||
TUICore.registerEvent(TUIConstants.TUILogin.EVENT_LOGIN_STATE_CHANGED, TUIConstants.TUILogin.EVENT_SUB_KEY_USER_LOGIN_SUCCESS, this);
|
||||
AlbumPicker.registerAdvancedAlbumPicker(new TUIMultimediaAlbumPicker());
|
||||
VideoRecorder.registerAdvancedVideoRecorder(new TUIMultimediaRecorder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotifyEvent(String key, String subKey, Map<String, Object> param) {
|
||||
if (TextUtils.equals(key, TUIConstants.TUILogin.EVENT_LOGIN_STATE_CHANGED)) {
|
||||
if (TextUtils.equals(subKey, TUIConstants.TUILogin.EVENT_SUB_KEY_USER_LOGIN_SUCCESS)) {
|
||||
TUICore.registerEvent(
|
||||
TUIConstants.NetworkConnection.EVENT_CONNECTION_STATE_CHANGED, TUIConstants.NetworkConnection.EVENT_SUB_KEY_CONNECT_SUCCESS, this);
|
||||
updateSignature();
|
||||
}
|
||||
} else if (TUIConstants.NetworkConnection.EVENT_CONNECTION_STATE_CHANGED.equals(key)) {
|
||||
if (TUIConstants.NetworkConnection.EVENT_SUB_KEY_CONNECT_SUCCESS.equals(subKey)) {
|
||||
updateSignature();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSignature() {
|
||||
TUIMultimediaSignatureChecker.getInstance().startUpdateSignature(() -> {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import com.tencent.imsdk.base.ThreadUtils;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
|
||||
public class TUIHorizontalScrollView extends HorizontalScrollView {
|
||||
|
||||
private final String TAG = TUIHorizontalScrollView.class + "_" + hashCode();
|
||||
|
||||
private DataSetObserver mObserver;
|
||||
private TUIMultimediaScrollViewAdapter mAdapter;
|
||||
|
||||
public TUIHorizontalScrollView(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public TUIHorizontalScrollView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public TUIHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public void setAdapter(TUIMultimediaScrollViewAdapter adapter) {
|
||||
LiteavLog.i(TAG, "set Adapter adapter = " + adapter);
|
||||
if (mAdapter != null) {
|
||||
mAdapter.unregisterDataSetObserver(mObserver);
|
||||
}
|
||||
mAdapter = adapter;
|
||||
mAdapter.registerDataSetObserver(mObserver);
|
||||
updateAdapter();
|
||||
ThreadUtils.getUiThreadHandler().post(this::scrollToSelectView);
|
||||
}
|
||||
|
||||
private void updateAdapter() {
|
||||
ViewGroup group = (ViewGroup) getChildAt(0);
|
||||
group.removeAllViews();
|
||||
for (int i = 0; i < mAdapter.getCount(); i++) {
|
||||
View view = mAdapter.getView(i, null, group);
|
||||
group.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
mObserver = new DataSetObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
super.onChanged();
|
||||
updateAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated() {
|
||||
super.onInvalidated();
|
||||
((ViewGroup) getChildAt(0)).removeAllViews();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void setClicked(int position) {
|
||||
LiteavLog.i(TAG, "setClicked position = " + position + " old position = " + mAdapter.getSelectPosition());
|
||||
if (position == mAdapter.getSelectPosition()) {
|
||||
return;
|
||||
}
|
||||
View itemView = ((ViewGroup) getChildAt(0)).getChildAt(position);
|
||||
if (itemView != null) {
|
||||
itemView.performClick();
|
||||
}
|
||||
scrollToSelectView();
|
||||
}
|
||||
|
||||
private void scrollToSelectView() {
|
||||
View view = ((ViewGroup) getChildAt(0)).getChildAt(mAdapter.getSelectPosition());
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect scrollBounds = new Rect();
|
||||
getHitRect(scrollBounds);
|
||||
|
||||
int scrollX = getScrollX();
|
||||
int itemViewLeft = view.getLeft();
|
||||
int itemViewWidth = view.getWidth();
|
||||
|
||||
int visibleZoomLeft = scrollBounds.left + scrollX + TUIMultimediaResourceUtils.dip2px(getContext(), 20);
|
||||
int visibleZoomRight = scrollBounds.right + scrollX - TUIMultimediaResourceUtils.dip2px(getContext(), 20);
|
||||
|
||||
int newScrollX = scrollX;
|
||||
if (itemViewLeft < visibleZoomLeft) {
|
||||
newScrollX = scrollX - ((visibleZoomLeft - itemViewLeft));
|
||||
}
|
||||
|
||||
if (itemViewLeft + itemViewWidth > visibleZoomRight) {
|
||||
newScrollX = scrollX + (itemViewLeft + itemViewWidth - visibleZoomRight);
|
||||
}
|
||||
|
||||
if (newScrollX < 0) {
|
||||
newScrollX = 0;
|
||||
}
|
||||
|
||||
scrollTo(newScrollX, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.tencent.imsdk.BuildConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.utils.TUIMultimediaLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import java.util.Objects;
|
||||
|
||||
public class TUIMultimediaAuthorizationPrompter {
|
||||
|
||||
private static final String IM_MULTIMEDIA_PLUGIN_DOCUMENT_URL = "https://cloud.tencent.com/document/product/269/113290";
|
||||
|
||||
public static boolean verifyPermissionGranted(Context context) {
|
||||
if (TUIMultimediaSignatureChecker.getInstance().isSupportFunction()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
TUIMultimediaLog.e("AuthorizationPrompter", "Signature checker do not support function");
|
||||
if (context != null) {
|
||||
showPrompterDialog(context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void showPrompterDialog(Context context) {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
View dialogView = LayoutInflater.from(context).inflate(R.layout.multimedia_plugin_authorization_prompter, null);
|
||||
|
||||
TextView linkText = dialogView.findViewById(R.id.authorization_prompter_accessing_documents_tv);
|
||||
setHyperlink(linkText,TUIMultimediaResourceUtils.getString(R.string.authorization_prompter_accessing_documents),
|
||||
TUIMultimediaResourceUtils.getString(R.string.authorization_prompter_documents_title),
|
||||
IM_MULTIMEDIA_PLUGIN_DOCUMENT_URL);
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context, R.style.AuthorizationPrompterDialog)
|
||||
.setView(dialogView)
|
||||
.setPositiveButton(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_confirm), null)
|
||||
.setOnDismissListener(dialog -> {
|
||||
});
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||
window.setAttributes(params);
|
||||
}
|
||||
|
||||
Objects.requireNonNull(dialog.getWindow())
|
||||
.setBackgroundDrawableResource(R.drawable.multimedia_plugin_bg_dialog_rounded);
|
||||
|
||||
dialog.show();
|
||||
|
||||
Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
if (positiveButton != null) {
|
||||
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) positiveButton.getLayoutParams();
|
||||
params.leftMargin = TUIMultimediaResourceUtils.dip2px(context, 24);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setHyperlink(TextView textView, String fullText,
|
||||
String linkText, String url) {
|
||||
SpannableString spannable = new SpannableString(fullText);
|
||||
|
||||
int start = fullText.indexOf(linkText);
|
||||
int end = start + linkText.length();
|
||||
|
||||
if (start == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClickableSpan clickableSpan = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(@NonNull View widget) {
|
||||
openUrl(widget.getContext(), url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(true);
|
||||
}
|
||||
};
|
||||
|
||||
spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#007AFF")),
|
||||
start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
textView.setText(spannable);
|
||||
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
textView.setHighlightColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
private static void openUrl(Context context, String url) {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
context.startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(context,TUIMultimediaResourceUtils.getString(R.string.link_error), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2024 Tencent. All rights reserved.
|
||||
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 通用线程池,核心线程个数为1,最大线程个数为3,非核心线程空闲时间为100ms
|
||||
*/
|
||||
public class TUIMultimediaCommonThreadPool extends ThreadPoolExecutor {
|
||||
|
||||
public static final int MAX_POOL_SIZE = 3;
|
||||
public static ThreadPoolExecutor sCommonThreadPool;
|
||||
|
||||
private TUIMultimediaCommonThreadPool(int poolSize) {
|
||||
super(1, poolSize, 100L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingDeque<Runnable>(),
|
||||
Executors.defaultThreadFactory(), new AbortPolicy());
|
||||
}
|
||||
|
||||
public static synchronized ThreadPoolExecutor getThreadExecutor() {
|
||||
if (sCommonThreadPool == null || sCommonThreadPool.isShutdown()) {
|
||||
sCommonThreadPool = new TUIMultimediaCommonThreadPool(MAX_POOL_SIZE);
|
||||
}
|
||||
return sCommonThreadPool;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class TUIMultimediaData<T> {
|
||||
|
||||
private static final Object DEFAULT_OBJECT = new Object();
|
||||
private final List<TUIMultimediaDataObserver<? super T>> mObservers = new ArrayList<>();
|
||||
private volatile Object mData;
|
||||
|
||||
public TUIMultimediaData(T value) {
|
||||
mData = value;
|
||||
}
|
||||
|
||||
public void observe(TUIMultimediaDataObserver<? super T> observer) {
|
||||
if (observer == null || mObservers.contains(observer)) {
|
||||
return;
|
||||
}
|
||||
mObservers.add(observer);
|
||||
}
|
||||
|
||||
public void removeObserver(final TUIMultimediaDataObserver<? super T> observer) {
|
||||
mObservers.remove(observer);
|
||||
}
|
||||
|
||||
public void set(T value) {
|
||||
mData = value;
|
||||
dispatchingValue();
|
||||
}
|
||||
|
||||
public void add(Object item) {
|
||||
if (mData != null) {
|
||||
if (mData instanceof List) {
|
||||
((List) mData).add(item);
|
||||
dispatchingValue();
|
||||
} else if (mData instanceof Set) {
|
||||
((Set) mData).add(item);
|
||||
dispatchingValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T get() {
|
||||
Object data = mData;
|
||||
if (data != DEFAULT_OBJECT) {
|
||||
return (T) data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void dispatchingValue() {
|
||||
for (int i = 0; i < mObservers.size(); i++) {
|
||||
mObservers.get(i).onChanged((T) mData);
|
||||
}
|
||||
}
|
||||
|
||||
public interface TUIMultimediaDataObserver<T> {
|
||||
|
||||
void onChanged(T t);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.TypedValue;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||
import com.tencent.qcloud.tuicore.util.TUIBuild;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
|
||||
public class TUIMultimediaFileUtil {
|
||||
|
||||
public static final String TAG = TUIMultimediaFileUtil.class.getSimpleName();
|
||||
public static final String ASSET_FILE_PREFIX = "file:///asset/";
|
||||
public static final String LOCAL_FILE_PREFIX = "/";
|
||||
public static final String CONTENT_FILE_PREFIX = "content:";
|
||||
|
||||
public static boolean isFileExists(String path) {
|
||||
try {
|
||||
File file = new File(path);
|
||||
return file.exists() && file.isFile();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File generateFileName(@Nullable String name, File directory) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
File file = new File(directory, name);
|
||||
|
||||
if (file.exists()) {
|
||||
String fileName = name;
|
||||
String extension = "";
|
||||
int dotIndex = name.lastIndexOf('.');
|
||||
if (dotIndex > 0) {
|
||||
fileName = name.substring(0, dotIndex);
|
||||
extension = name.substring(dotIndex);
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
|
||||
while (file.exists()) {
|
||||
index++;
|
||||
name = fileName + '(' + index + ')' + extension;
|
||||
file = new File(directory, name);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!file.createNewFile()) {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public static boolean saveFileFromUri(Context context, Uri uri, String destinationPath) {
|
||||
InputStream is = null;
|
||||
BufferedOutputStream bos = null;
|
||||
try {
|
||||
is = context.getContentResolver().openInputStream(uri);
|
||||
bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false));
|
||||
byte[] buf = new byte[1024];
|
||||
|
||||
int actualBytes;
|
||||
while ((actualBytes = is.read(buf)) != -1) {
|
||||
bos.write(buf, 0, actualBytes);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
if (bos != null) {
|
||||
bos.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean saveBitmap(String path, Bitmap b) {
|
||||
try {
|
||||
FileOutputStream outputStream = new FileOutputStream(path);
|
||||
BufferedOutputStream bufferOutputStream = new BufferedOutputStream(outputStream);
|
||||
b.compress(Bitmap.CompressFormat.JPEG, 100, bufferOutputStream);
|
||||
bufferOutputStream.flush();
|
||||
bufferOutputStream.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String readTextFromFile(String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
boolean isAssetFile = fileName.startsWith(ASSET_FILE_PREFIX);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
InputStream is = null;
|
||||
BufferedReader br = null;
|
||||
try {
|
||||
if (isAssetFile) {
|
||||
is = TUIMultimediaPlugin.getAppContext().getAssets()
|
||||
.open(fileName.substring(TUIMultimediaFileUtil.ASSET_FILE_PREFIX.length()));
|
||||
} else {
|
||||
is = new FileInputStream(fileName);
|
||||
}
|
||||
br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||
String readLine;
|
||||
while ((readLine = br.readLine()) != null) {
|
||||
sb.append(readLine);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (is != null) {
|
||||
is.close();
|
||||
}
|
||||
if (br != null) {
|
||||
br.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static Bitmap readBitmapFromUrl(Uri uri) {
|
||||
try {
|
||||
ContentResolver contentResolver = TUIMultimediaPlugin.getAppContext().getContentResolver();
|
||||
ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r");
|
||||
FileInputStream fileInputStream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(fileInputStream);
|
||||
fileInputStream.close();
|
||||
parcelFileDescriptor.close();
|
||||
return bitmap;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateFilePath(MultimediaPluginFileType fileType) {
|
||||
String fileName = "";
|
||||
String suffix = "";
|
||||
switch (fileType) {
|
||||
case RECODE_FILE:
|
||||
fileName = "multimedia_plugin_record_";
|
||||
suffix = ".mp4";
|
||||
break;
|
||||
case EDIT_FILE:
|
||||
fileName = "multimedia_plugin_edit_";
|
||||
suffix = ".mp4";
|
||||
break;
|
||||
case PICTURE_FILE:
|
||||
fileName = "multimedia_plugin_pic_";
|
||||
suffix = ".jpg";
|
||||
break;
|
||||
}
|
||||
|
||||
if (TUIBuild.getVersionInt() < Build.VERSION_CODES.N) {
|
||||
File dir = new File(
|
||||
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar + TUIConfig
|
||||
.getAppContext().getPackageName()
|
||||
+ TUIConfig.RECORD_DIR_SUFFIX);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
return dir.getAbsolutePath() + File.separatorChar + fileName + System.nanoTime() + "_" + Math
|
||||
.abs(new Random().nextInt()) + suffix;
|
||||
} else {
|
||||
String name = System.nanoTime() + "_" + Math.abs(new Random().nextInt());
|
||||
return TUIConfig.getRecordDir() + fileName + name + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean copyAssetsFile(String assetsFileName, String outputFilePath) {
|
||||
try {
|
||||
InputStream inputStream = TUIMultimediaPlugin.getAppContext().getAssets().open(assetsFileName);
|
||||
OutputStream outputStream = new FileOutputStream(outputFilePath);
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, read);
|
||||
}
|
||||
inputStream.close();
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap decodeBitmap(String path) {
|
||||
LiteavLog.i(TAG, "decodeBitmap. path = " + path);
|
||||
if (path != null && path.startsWith(TUIMultimediaFileUtil.LOCAL_FILE_PREFIX)) {
|
||||
return decodeLocalFile(path);
|
||||
} else if (path != null && path.startsWith(TUIMultimediaResourceUtils.DRAWABLE_RESOURCE_PREFIX)) {
|
||||
return decodeDrawableRes(path);
|
||||
} else if (path != null && path.startsWith(TUIMultimediaFileUtil.ASSET_FILE_PREFIX)) {
|
||||
return decodeAssetFile(path);
|
||||
} else if (path != null && path.startsWith(TUIMultimediaFileUtil.CONTENT_FILE_PREFIX)) {
|
||||
return readBitmapFromUrl(Uri.parse(path));
|
||||
} else {
|
||||
LiteavLog.i(TAG, "decodeBitmap " + path + " is not supported path");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap decodeLocalFile(String localFilePath) {
|
||||
return BitmapFactory.decodeFile(localFilePath);
|
||||
}
|
||||
|
||||
private static Bitmap decodeDrawableRes(String resName) {
|
||||
LiteavLog.i(TAG, "decode drawable res. resName = " + resName);
|
||||
TypedValue value = new TypedValue();
|
||||
int resId = TUIMultimediaResourceUtils.getDrawableId(resName);
|
||||
TUIMultimediaResourceUtils.getResources().openRawResource(resId, value);
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inTargetDensity = value.density;
|
||||
return BitmapFactory.decodeResource(TUIMultimediaResourceUtils.getResources(), resId, opts);
|
||||
}
|
||||
|
||||
private static Bitmap decodeAssetFile(String assetFileName) {
|
||||
LiteavLog.i("PicturePasterItemInfo", "decodeAssetFile assetFileName = " + assetFileName);
|
||||
try {
|
||||
assetFileName = assetFileName.substring(TUIMultimediaFileUtil.ASSET_FILE_PREFIX.length());
|
||||
InputStream is = TUIMultimediaPlugin.getAppContext().getAssets().open(assetFileName);
|
||||
return BitmapFactory.decodeStream(is);
|
||||
} catch (Exception e) {
|
||||
LiteavLog.e("PicturePasterItemInfo", "decode asset file error " + e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean saveBmpToFile(Bitmap bmp, File file, CompressFormat format) {
|
||||
if (null != bmp && null != file) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
bmp.compress(format, 100, baos);
|
||||
return writeToFile(baos.toByteArray(), file);
|
||||
} else {
|
||||
LiteavLog.e("DebugUtils", "bmp or file is null");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean writeToFile(byte[] data, File file) {
|
||||
FileOutputStream fos = null;
|
||||
boolean ret = false;
|
||||
|
||||
try {
|
||||
fos = new FileOutputStream(file);
|
||||
fos.write(data);
|
||||
fos.flush();
|
||||
ret = true;
|
||||
} catch (IOException var8) {
|
||||
} finally {
|
||||
closeQuietly(fos);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static void closeQuietly(final Closeable closeable) {
|
||||
try {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
} catch (IOException var2) {
|
||||
}
|
||||
}
|
||||
|
||||
public enum MultimediaPluginFileType {
|
||||
RECODE_FILE,
|
||||
EDIT_FILE,
|
||||
PICTURE_FILE
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class TUIMultimediaFullScreen extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initStatusBar();
|
||||
}
|
||||
|
||||
private void initStatusBar() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Window window = getWindow();
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
| View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
window.setStatusBarColor(Color.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
initStatusBar();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class TUIMultimediaGraphComputerUtils {
|
||||
|
||||
public static 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);
|
||||
}
|
||||
|
||||
public static double radianToDegree(double radian) {
|
||||
return radian * 180 / Math.PI;
|
||||
}
|
||||
|
||||
public static double degreeToRadian(double degree) {
|
||||
return degree * Math.PI / 180;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static 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;
|
||||
double resultDegree;
|
||||
double resultRadian;
|
||||
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) {
|
||||
originRadian = Math.asin(disPoint.y / distance);
|
||||
} else if (disPoint.x < 0 && disPoint.y >= 0) {
|
||||
originRadian = Math.asin(Math.abs(disPoint.x) / distance);
|
||||
originRadian = originRadian + Math.PI / 2;
|
||||
} else if (disPoint.x < 0 && disPoint.y < 0) {
|
||||
originRadian = Math.asin(Math.abs(disPoint.y) / distance);
|
||||
originRadian = originRadian + Math.PI;
|
||||
} else if (disPoint.x >= 0 && disPoint.y < 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.TypedValue;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import java.util.Locale;
|
||||
|
||||
public class TUIMultimediaResourceUtils {
|
||||
public static final String DRAWABLE_RESOURCE_PREFIX = "@drawable/";
|
||||
public static final String STRING_RESOURCE_PREFIX = "@string/";
|
||||
|
||||
public static final String TYPE_STRING = "string";
|
||||
public static final String TYPE_DRAWABLE = "drawable";
|
||||
|
||||
public static int getDrawableId(String resName) {
|
||||
if (resName != null && resName.startsWith(DRAWABLE_RESOURCE_PREFIX)) {
|
||||
return getResources().getIdentifier(resName, TYPE_DRAWABLE, TUIMultimediaResourceUtils.getPackageName());
|
||||
}
|
||||
throw new IllegalArgumentException("\"" + resName + "\" is illegal, must start with \"@\".");
|
||||
}
|
||||
|
||||
public static int getStringId(String resName) {
|
||||
return getResources().getIdentifier(resName, TYPE_STRING, TUIMultimediaResourceUtils.getPackageName());
|
||||
}
|
||||
|
||||
public static String getString(String resName) {
|
||||
if (resName != null && resName.startsWith(STRING_RESOURCE_PREFIX)) {
|
||||
return getResources().getString(getStringId(resName.substring(STRING_RESOURCE_PREFIX.length())));
|
||||
}
|
||||
return resName;
|
||||
}
|
||||
|
||||
public static String getString(@StringRes int resId) {
|
||||
return getResources().getString(resId);
|
||||
}
|
||||
|
||||
public static int getColor(int colorId) {
|
||||
return getResources().getColor(colorId);
|
||||
}
|
||||
|
||||
|
||||
public static Resources getResources() {
|
||||
return TUIMultimediaPlugin.getAppContext().getResources();
|
||||
}
|
||||
|
||||
public static Drawable getColorDrawable(int colorId) {
|
||||
int color = ContextCompat.getColor(TUIMultimediaPlugin.getAppContext(), colorId); // 获取颜色
|
||||
return new ColorDrawable(color);
|
||||
}
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
public static Drawable getDrawable(int drawableId) {
|
||||
Resources res = getResources();
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
|
||||
return res.getDrawable(drawableId, null);
|
||||
} else {
|
||||
return res.getDrawable(drawableId);
|
||||
}
|
||||
}
|
||||
|
||||
public static Drawable getDrawable(@NonNull Context context, int drawableId, @ColorInt int tint) {
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
||||
if (drawable == null) {
|
||||
return null;
|
||||
}
|
||||
Drawable drawableUp = DrawableCompat.wrap(drawable);
|
||||
DrawableCompat.setTint(drawableUp, tint);
|
||||
return drawableUp;
|
||||
}
|
||||
|
||||
|
||||
public static float spToPx(Context context, float sp) {
|
||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
public static Point getScreenSize(Context context) {
|
||||
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||
Point point = new Point();
|
||||
windowManager.getDefaultDisplay().getRealSize(point);
|
||||
return point;
|
||||
}
|
||||
|
||||
public static int dip2px(Context context, float dpValue) {
|
||||
final float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
public static void setImageResource(ImageView imageView, String res) {
|
||||
imageView.setImageResource(TUIMultimediaResourceUtils.getDrawableId(res));
|
||||
}
|
||||
|
||||
public static String getPackageName() {
|
||||
return TUIMultimediaPlugin.getAppContext().getPackageName();
|
||||
}
|
||||
|
||||
public static String secondsToTimeString(long timeMs) {
|
||||
long seconds = timeMs / 1000;
|
||||
int minutes = (int) (seconds / 60);
|
||||
seconds = (int) (seconds % 60);
|
||||
return String.format(Locale.ENGLISH, "%02d:%02d", minutes, seconds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.common;
|
||||
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
public abstract class TUIMultimediaScrollViewAdapter extends BaseAdapter {
|
||||
|
||||
protected int mSelectPosition = -1;
|
||||
|
||||
public int getSelectPosition() {
|
||||
return mSelectPosition;
|
||||
}
|
||||
|
||||
public void setSelectPosition(int position) {
|
||||
mSelectPosition = position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,755 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ImageView.ScaleType;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMEditListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMManager;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMPanelView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop.PictureCropControlView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop.PictureCropControlView.PictureCropControlViewListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerViewGroup;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView.BrushMode;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.PicturePasterFloatLayerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.PicturePasterManager;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.PicturePasterPanelView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster.SubtitlePanelView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster.SubtitlePasterFloatLayerView;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXRect;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class TUIMultiMediaEditCommonCtrlView extends RelativeLayout {
|
||||
|
||||
private final static int COMMON_FUNCTION_ICON_SIZE_DP = 28;
|
||||
private final static int BGM_FUNCTION_ICON_MARGIN_DP = 34;
|
||||
|
||||
private final String TAG = TUIMultiMediaEditCommonCtrlView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final EditType mEditType;
|
||||
private final Boolean mIsRecordFile;
|
||||
|
||||
private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener;
|
||||
private RelativeLayout mOperationLayout;
|
||||
private LinearLayout mFunctionButtonLayout;
|
||||
private ImageView mReturnBackView;
|
||||
private View mRootView;
|
||||
private FloatLayerViewGroup mFloatLayerViewGroup;
|
||||
private float mAspectRatio = 9.0f / 16.0f;
|
||||
private View mMediaView;
|
||||
|
||||
private TUIMultimediaTransformLayout mPreviewContainer;
|
||||
private PictureCropControlView mPictureCropControlView;
|
||||
|
||||
|
||||
private BrushControlView mBrushControlView;
|
||||
private ImageButton mGraffitiBrushBtn;
|
||||
private ImageButton mMosaicBrushBtn;
|
||||
|
||||
private PicturePasterPanelView mPicturePasterPanelView;
|
||||
private PicturePasterManager mPicturePasterManager;
|
||||
|
||||
private SubtitlePanelView mSubtitlePanelView;
|
||||
|
||||
private BGMPanelView mBGMPanelView;
|
||||
private BGMManager mBGMManager;
|
||||
private ImageButton mBGMBtn;
|
||||
private Rect mPreviewRect;
|
||||
|
||||
private PictureCropListener mPictureCropListener;
|
||||
private BGMEditListener mVideoFragmentBgmEditListener;
|
||||
private CommonMediaEditListener mCommonMediaEditListener;
|
||||
|
||||
private Bitmap mPicture;
|
||||
|
||||
public enum EditType {
|
||||
VIDEO,
|
||||
PHOTO
|
||||
}
|
||||
|
||||
public interface PictureCropListener {
|
||||
|
||||
void onConfirmCrop(RectF rectF, int rotation);
|
||||
|
||||
void onRotation(int rotation);
|
||||
|
||||
void onStartCrop();
|
||||
|
||||
void onCancelCrop();
|
||||
}
|
||||
|
||||
public interface CommonMediaEditListener {
|
||||
|
||||
void onGenerateMedia();
|
||||
|
||||
void onCancelEdit();
|
||||
}
|
||||
|
||||
private final OnClickListener mOnPreviewClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mPictureCropControlView != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPicturePasterPanelView != null) {
|
||||
mPicturePasterPanelView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (mBGMPanelView != null) {
|
||||
mBGMPanelView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.unSelectOperationView();
|
||||
}
|
||||
|
||||
showOperationView(true);
|
||||
}
|
||||
};
|
||||
|
||||
private final BGMEditListener mBgmEditListener = new BGMEditListener() {
|
||||
@Override
|
||||
public void onAddBGM(String bgmPath) {
|
||||
if (mVideoFragmentBgmEditListener != null) {
|
||||
mVideoFragmentBgmEditListener.onAddBGM(bgmPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCutBGM(long startTime, long endTime) {
|
||||
if (mVideoFragmentBgmEditListener != null) {
|
||||
mVideoFragmentBgmEditListener.onCutBGM(startTime, endTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMuteSourceAudio(boolean isMute) {
|
||||
if (mVideoFragmentBgmEditListener != null) {
|
||||
mVideoFragmentBgmEditListener.onMuteSourceAudio(isMute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMuteBGM(boolean isMute) {
|
||||
if (mVideoFragmentBgmEditListener != null) {
|
||||
mVideoFragmentBgmEditListener.onMuteBGM(isMute);
|
||||
}
|
||||
|
||||
if (mBGMBtn != null) {
|
||||
mBGMBtn.setSelected(!isMute);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public TUIMultiMediaEditCommonCtrlView(Context context, EditType editType, boolean isRecordFile) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mEditType = editType;
|
||||
mIsRecordFile = isRecordFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
if (mPicturePasterManager != null) {
|
||||
mPicturePasterManager.uninit();
|
||||
}
|
||||
mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustFunctionButtonLayout();
|
||||
adjustPreviewLayout();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
mRootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_common_view, this);
|
||||
|
||||
mOperationLayout = mRootView.findViewById(R.id.operation_layout);
|
||||
mReturnBackView = mRootView.findViewById(R.id.edit_return_back);
|
||||
mPreviewContainer = mRootView.findViewById(R.id.rl_preview);
|
||||
mFunctionButtonLayout = mRootView.findViewById(R.id.function_button_layout);
|
||||
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
|
||||
mPreviewContainer.setOnClickListener(mOnPreviewClickListener);
|
||||
|
||||
mReturnBackView.setOnClickListener(view -> {
|
||||
if (mCommonMediaEditListener != null) {
|
||||
mCommonMediaEditListener.onCancelEdit();
|
||||
}
|
||||
});
|
||||
|
||||
initSendBtn();
|
||||
initGraffitiBrushBtn();
|
||||
initMosaicBrushBtn();
|
||||
initPasterFunction();
|
||||
initSubtitleBtn();
|
||||
initBGMFunction();
|
||||
initCropFunction();
|
||||
|
||||
adjustPreviewLayout();
|
||||
setMediaView(mMediaView);
|
||||
mOnGlobalLayoutListener = this::adjustFunctionButtonLayout;
|
||||
mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
|
||||
}
|
||||
|
||||
public void setMediaView(View view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustPreviewLayout();
|
||||
if (mPreviewContainer != null) {
|
||||
RelativeLayout.LayoutParams layoutParams = new LayoutParams(mPreviewRect.width(), mPreviewRect.height());
|
||||
layoutParams.leftMargin = mPreviewRect.left;
|
||||
layoutParams.topMargin = mPreviewRect.top;
|
||||
mPreviewContainer.addView(view, 0, layoutParams);
|
||||
}
|
||||
mMediaView = view;
|
||||
}
|
||||
|
||||
public void setMediaAspectRatio(float aspectRatio) {
|
||||
LiteavLog.i(TAG, "set media aspect ratio. aspect ratio:" + aspectRatio);
|
||||
mAspectRatio = aspectRatio;
|
||||
adjustPreviewLayout();
|
||||
}
|
||||
|
||||
public void setPicture(Bitmap bitmap) {
|
||||
mPicture = bitmap;
|
||||
if (mBrushControlView != null) {
|
||||
mBrushControlView.setMosaicImage(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBGMListener(BGMEditListener listener) {
|
||||
mVideoFragmentBgmEditListener = listener;
|
||||
}
|
||||
|
||||
private void initSendBtn() {
|
||||
Button button = mRootView.findViewById(R.id.send_btn);
|
||||
button.setBackground(TUIMultimediaResourceUtils
|
||||
.getDrawable(mContext, R.drawable.multimedia_plugin_edit_send_button,
|
||||
TUIMultimediaIConfig.getInstance().getThemeColor()));
|
||||
button.setGravity(Gravity.CENTER);
|
||||
if (mIsRecordFile) {
|
||||
button.setText(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_send));
|
||||
} else {
|
||||
button.setText(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_done));
|
||||
}
|
||||
|
||||
button.setOnClickListener(view -> {
|
||||
showOperationView(false);
|
||||
if (mCommonMediaEditListener != null) {
|
||||
mCommonMediaEditListener.onGenerateMedia();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initGraffitiBrushBtn() {
|
||||
if (!isSupportEditGraffiti()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int[][] states = new int[][]{
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_selected},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_selected},
|
||||
};
|
||||
|
||||
int[] colors = new int[]{
|
||||
Color.WHITE, TUIMultimediaIConfig.getInstance().getThemeColor()
|
||||
};
|
||||
|
||||
mGraffitiBrushBtn = addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
|
||||
R.drawable.multimedia_plugin_edit_graffiti, new ColorStateList(states, colors),
|
||||
v -> switchEnableBrush(BrushMode.GRAFFITI));
|
||||
}
|
||||
|
||||
private void initMosaicBrushBtn() {
|
||||
if (!isSupportEditMosaic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int[][] states = new int[][]{
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_selected},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_selected},
|
||||
};
|
||||
|
||||
int[] colors = new int[]{
|
||||
Color.WHITE, TUIMultimediaIConfig.getInstance().getThemeColor()
|
||||
};
|
||||
|
||||
mMosaicBrushBtn = addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
|
||||
R.drawable.multimedia_plugin_edit_mosaic, new ColorStateList(states, colors),
|
||||
v -> switchEnableBrush(BrushMode.MOSAIC));
|
||||
}
|
||||
|
||||
private void initPasterFunction() {
|
||||
if (!isSupportEditPaster()) {
|
||||
return;
|
||||
}
|
||||
addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
|
||||
R.drawable.multimedia_plugin_edit_paster,
|
||||
null, v -> addPicturePaster());
|
||||
|
||||
initPicturePasterSetManager();
|
||||
}
|
||||
|
||||
private void initSubtitleBtn() {
|
||||
if (!isSupportEditSubtitle()) {
|
||||
return;
|
||||
}
|
||||
|
||||
addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
|
||||
R.drawable.multimedia_plugin_edit_subtitle, null, v -> addSubtitlePaster());
|
||||
}
|
||||
|
||||
private void initBGMFunction() {
|
||||
if (!isSupportEditBGM()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int[][] states = new int[][]{
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_selected},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_selected},
|
||||
};
|
||||
|
||||
int[] colors = new int[]{
|
||||
Color.WHITE, TUIMultimediaIConfig.getInstance().getThemeColor()
|
||||
};
|
||||
|
||||
mBGMBtn = addFunctionButton(BGM_FUNCTION_ICON_MARGIN_DP, BGM_FUNCTION_ICON_MARGIN_DP,
|
||||
R.drawable.multimedia_plugin_edit_bgm, new ColorStateList(states, colors),
|
||||
v -> addBGMPanel());
|
||||
|
||||
initBGMManager();
|
||||
}
|
||||
|
||||
private void initCropFunction() {
|
||||
if (!isSupportEditCrop()) {
|
||||
return;
|
||||
}
|
||||
addFunctionButton(COMMON_FUNCTION_ICON_SIZE_DP, COMMON_FUNCTION_ICON_SIZE_DP,
|
||||
R.drawable.multimedia_plugin_edit_crop, null, v -> startCrop());
|
||||
}
|
||||
|
||||
private ImageButton addFunctionButton(int widthDp, int heightDp, int drawableId, ColorStateList tint,
|
||||
OnClickListener onClickListener) {
|
||||
ImageButton imageButton = new ImageButton(mContext);
|
||||
Drawable drawable = ContextCompat.getDrawable(mContext, drawableId);
|
||||
if (tint != null) {
|
||||
DrawableCompat.setTintList(drawable, tint);
|
||||
} else {
|
||||
DrawableCompat.setTint(drawable, Color.WHITE);
|
||||
}
|
||||
imageButton.setBackground(drawable);
|
||||
imageButton.setScaleType(ScaleType.FIT_CENTER);
|
||||
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
|
||||
TUIMultimediaResourceUtils.dip2px(mContext, widthDp),
|
||||
TUIMultimediaResourceUtils.dip2px(mContext, heightDp));
|
||||
mFunctionButtonLayout.addView(imageButton, layoutParams);
|
||||
imageButton.setOnClickListener(onClickListener);
|
||||
return imageButton;
|
||||
}
|
||||
|
||||
private void addSubtitlePaster() {
|
||||
initFloatLayerViewGroup();
|
||||
|
||||
if (mSubtitlePanelView == null) {
|
||||
mSubtitlePanelView = new SubtitlePanelView(mContext);
|
||||
RelativeLayout subtitlePasterInputViewContainer = mRootView
|
||||
.findViewById(R.id.rl_subtitle_paster_input_view_container);
|
||||
subtitlePasterInputViewContainer.removeAllViews();
|
||||
subtitlePasterInputViewContainer.addView(mSubtitlePanelView,
|
||||
new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
}
|
||||
|
||||
mSubtitlePanelView.inputText(null, subtitleInfo -> {
|
||||
if (subtitleInfo != null && !subtitleInfo.getText().isEmpty()) {
|
||||
new SubtitlePasterFloatLayerView(getContext(),
|
||||
mFloatLayerViewGroup, subtitleInfo, mSubtitlePanelView);
|
||||
}
|
||||
showOperationView(true);
|
||||
mFloatLayerViewGroup.setCanTouch(true);
|
||||
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
|
||||
});
|
||||
disableBrush();
|
||||
showOperationView(false);
|
||||
mPreviewContainer.enableTransform(false);
|
||||
mFloatLayerViewGroup.setCanTouch(false);
|
||||
mFloatLayerViewGroup.unSelectOperationView();
|
||||
}
|
||||
|
||||
private void addPicturePaster() {
|
||||
initFloatLayerViewGroup();
|
||||
|
||||
if (mPicturePasterPanelView == null) {
|
||||
RelativeLayout picturePasterPanelViewContainer = mRootView
|
||||
.findViewById(R.id.rl_picture_paster_panel_view_container);
|
||||
picturePasterPanelViewContainer.removeAllViews();
|
||||
picturePasterPanelViewContainer.setVisibility(View.VISIBLE);
|
||||
mPicturePasterPanelView = new PicturePasterPanelView(mContext, mPicturePasterManager);
|
||||
picturePasterPanelViewContainer
|
||||
.addView(mPicturePasterPanelView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
}
|
||||
mPicturePasterPanelView.setVisibility(View.VISIBLE);
|
||||
mPicturePasterPanelView.selectPaster(bitmap -> {
|
||||
if (bitmap != null) {
|
||||
new PicturePasterFloatLayerView(getContext(), mFloatLayerViewGroup, bitmap);
|
||||
}
|
||||
mPicturePasterPanelView.setVisibility(View.GONE);
|
||||
showOperationView(true);
|
||||
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
|
||||
});
|
||||
disableBrush();
|
||||
showOperationView(false);
|
||||
mFloatLayerViewGroup.unSelectOperationView();
|
||||
mPreviewContainer.enableTransform(false);
|
||||
}
|
||||
|
||||
private void addBGMPanel() {
|
||||
if (mBGMPanelView == null) {
|
||||
mBGMPanelView = new BGMPanelView(mContext, mBGMManager);
|
||||
RelativeLayout bgmPanelViewContainer = mRootView.findViewById(R.id.rl_bgm_panel_view_container);
|
||||
bgmPanelViewContainer.setVisibility(View.VISIBLE);
|
||||
bgmPanelViewContainer.addView(mBGMPanelView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
mBGMPanelView.setBGMEditListener(mBgmEditListener);
|
||||
}
|
||||
mBGMPanelView.showBGMPanel();
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.unSelectOperationView();
|
||||
}
|
||||
|
||||
disableBrush();
|
||||
showOperationView(false);
|
||||
}
|
||||
|
||||
private void initPicturePasterSetManager() {
|
||||
if (mPicturePasterManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPicturePasterManager = new PicturePasterManager(
|
||||
TUIMultimediaIConfig.getInstance().getPicturePasterConfigFilePath());
|
||||
mPicturePasterManager.init();
|
||||
}
|
||||
|
||||
private void initFloatLayerViewGroup() {
|
||||
if (mFloatLayerViewGroup == null) {
|
||||
mFloatLayerViewGroup = new FloatLayerViewGroup(mContext);
|
||||
mPreviewContainer.addView(mFloatLayerViewGroup, 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void initBGMManager() {
|
||||
if (mBGMManager != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBGMManager = new BGMManager(TUIMultimediaIConfig.getInstance().getBGMConfigFilePath());
|
||||
mBGMManager.init();
|
||||
}
|
||||
|
||||
private void switchEnableBrush(BrushMode brushMode) {
|
||||
boolean enableBrush =
|
||||
mBrushControlView == null || !mBrushControlView.isEnableDraw();
|
||||
|
||||
if (!enableBrush && mBrushControlView != null) {
|
||||
enableBrush = (brushMode != mBrushControlView.getBrushMode());
|
||||
}
|
||||
|
||||
if (enableBrush) {
|
||||
enableBrush(brushMode);
|
||||
} else {
|
||||
disableBrush();
|
||||
}
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.unSelectOperationView();
|
||||
}
|
||||
}
|
||||
|
||||
private void enableBrush(BrushMode brushMode) {
|
||||
if (mBrushControlView == null) {
|
||||
mBrushControlView = new BrushControlView(mContext,
|
||||
mRootView.findViewById(R.id.rl_brush_tool_view_container));
|
||||
mPreviewContainer.addView(mBrushControlView.getBrushDrawView(), 1);
|
||||
mBrushControlView.tuiDataIsDrawing.observe(isDrawing -> showOperationView(!isDrawing));
|
||||
mBrushControlView.setMosaicImage(mPicture);
|
||||
}
|
||||
mBrushControlView.showToolView(true);
|
||||
mBrushControlView.enableDraw(true);
|
||||
mBrushControlView.setBrushMode(brushMode);
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.setCanTouch(false);
|
||||
}
|
||||
|
||||
mPreviewContainer.enableTransform(false);
|
||||
if (mGraffitiBrushBtn != null) {
|
||||
mGraffitiBrushBtn.setSelected(mBrushControlView.getBrushMode() == BrushMode.GRAFFITI);
|
||||
}
|
||||
|
||||
if (mMosaicBrushBtn != null) {
|
||||
mMosaicBrushBtn.setSelected(mBrushControlView.getBrushMode() == BrushMode.MOSAIC);
|
||||
}
|
||||
|
||||
mOperationLayout.setBackgroundColor(
|
||||
TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_edit_function_button_view_background));
|
||||
}
|
||||
|
||||
private void disableBrush() {
|
||||
if (mBrushControlView != null) {
|
||||
mBrushControlView.showToolView(false);
|
||||
mBrushControlView.enableDraw(false);
|
||||
}
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.setCanTouch(true);
|
||||
}
|
||||
|
||||
if (mGraffitiBrushBtn != null) {
|
||||
mGraffitiBrushBtn.setSelected(false);
|
||||
}
|
||||
|
||||
if (mMosaicBrushBtn != null) {
|
||||
mMosaicBrushBtn.setSelected(false);
|
||||
}
|
||||
|
||||
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
|
||||
mOperationLayout.setBackgroundColor(
|
||||
TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_edit_function_view_background));
|
||||
}
|
||||
|
||||
public void setPictureCropListener(PictureCropListener pictureCropListener) {
|
||||
mPictureCropListener = pictureCropListener;
|
||||
}
|
||||
|
||||
public void setCommonMediaEditListener(CommonMediaEditListener commonMediaEditListener) {
|
||||
mCommonMediaEditListener = commonMediaEditListener;
|
||||
}
|
||||
|
||||
private void startCrop() {
|
||||
showOperationView(false);
|
||||
mPreviewContainer.reset();
|
||||
mPictureCropControlView = new PictureCropControlView(mContext, mPreviewContainer);
|
||||
((RelativeLayout) mRootView).addView(mPictureCropControlView);
|
||||
mPictureCropControlView.layout(0, 0, mRootView.getWidth(), mRootView.getHeight());
|
||||
mPictureCropControlView.setCropMainViewListener(new PictureCropControlViewListener() {
|
||||
@Override
|
||||
public void onCancelCrop() {
|
||||
showOperationView(true);
|
||||
((RelativeLayout) mRootView).removeView(mPictureCropControlView);
|
||||
mPictureCropControlView = null;
|
||||
|
||||
if (mBrushControlView != null) {
|
||||
mBrushControlView.showDrawView(true);
|
||||
}
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (mPictureCropListener != null) {
|
||||
mPictureCropListener.onCancelCrop();
|
||||
}
|
||||
|
||||
mPreviewContainer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfirmCrop(RectF rectF, int rotation) {
|
||||
if (mPictureCropListener != null) {
|
||||
mPictureCropListener.onConfirmCrop(rectF, rotation);
|
||||
}
|
||||
|
||||
showOperationView(true);
|
||||
|
||||
((RelativeLayout) mRootView).removeView(mPictureCropControlView);
|
||||
mPictureCropControlView = null;
|
||||
|
||||
mPreviewContainer.reset();
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.removeAllFloatLayerView();
|
||||
mFloatLayerViewGroup.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
if (mBrushControlView != null) {
|
||||
mBrushControlView.clean();
|
||||
mBrushControlView.showDrawView(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotation(int rotation) {
|
||||
if (mPictureCropListener != null) {
|
||||
mPictureCropListener.onRotation(rotation);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mPictureCropListener != null) {
|
||||
mPictureCropListener.onStartCrop();
|
||||
}
|
||||
|
||||
if (mBrushControlView != null) {
|
||||
mBrushControlView.showDrawView(false);
|
||||
disableBrush();
|
||||
}
|
||||
|
||||
if (mFloatLayerViewGroup != null) {
|
||||
mFloatLayerViewGroup.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public List<TUIMultimediaPasterInfo> getPaster() {
|
||||
List<TUIMultimediaPasterInfo> pasterInfoList = mFloatLayerViewGroup == null ? new LinkedList<>() : mFloatLayerViewGroup.getAllPaster();
|
||||
TUIMultimediaPasterInfo paster = null;
|
||||
if (mBrushControlView != null) {
|
||||
paster = mBrushControlView.getPaster();
|
||||
}
|
||||
|
||||
if (paster != null) {
|
||||
pasterInfoList.add(0, paster);
|
||||
}
|
||||
|
||||
return pasterInfoList;
|
||||
}
|
||||
|
||||
public List<TXPaster> getNormalizedPaster() {
|
||||
List<TUIMultimediaPasterInfo> pasterInfoList = getPaster();
|
||||
|
||||
Rect previewLayout = mPreviewContainer.getContentRect();
|
||||
int mPreviewWidth = previewLayout != null ? previewLayout.width() : getWidth();
|
||||
int mPreviewHeight = previewLayout != null ? previewLayout.height() : getHeight();
|
||||
|
||||
List<TXPaster> pasterList = new LinkedList<>();
|
||||
for (TUIMultimediaPasterInfo pasterInfo : pasterInfoList) {
|
||||
TXPaster txPaster = new TXPaster();
|
||||
txPaster.frame = new TXRect();
|
||||
txPaster.frame.x = pasterInfo.frame.x / mPreviewWidth;
|
||||
txPaster.frame.y = pasterInfo.frame.y / mPreviewHeight;
|
||||
txPaster.frame.width = pasterInfo.frame.width / mPreviewWidth;
|
||||
txPaster.pasterImage = pasterInfo.image;
|
||||
pasterList.add(txPaster);
|
||||
}
|
||||
return pasterList;
|
||||
}
|
||||
|
||||
public void showOperationView(boolean show) {
|
||||
int visible = show ? View.VISIBLE : View.GONE;
|
||||
mReturnBackView.setVisibility(visible);
|
||||
mOperationLayout.setVisibility(visible);
|
||||
if (mBrushControlView != null) {
|
||||
mBrushControlView.showToolView(show && mBrushControlView.isEnableDraw());
|
||||
}
|
||||
mPreviewContainer.enableTransform(mEditType == EditType.PHOTO);
|
||||
}
|
||||
|
||||
private void adjustPreviewLayout() {
|
||||
if (mPreviewContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int previewContainerWidth = mPreviewContainer.getWidth();
|
||||
int previewContainerHeight = mPreviewContainer.getHeight();
|
||||
|
||||
if (previewContainerWidth == 0 || previewContainerHeight == 0) {
|
||||
Point screenSize = TUIMultimediaResourceUtils.getScreenSize(mContext);
|
||||
previewContainerWidth = screenSize.x;
|
||||
previewContainerHeight = screenSize.y;
|
||||
}
|
||||
|
||||
int previewHeight = (int) (previewContainerWidth / mAspectRatio);
|
||||
int previewTop = (previewContainerHeight - previewHeight) / 2;
|
||||
Rect previewRect = new Rect(0, previewTop, previewContainerWidth, previewTop + previewHeight);
|
||||
|
||||
if (previewRect.equals(mPreviewRect)) {
|
||||
return;
|
||||
}
|
||||
mPreviewRect = previewRect;
|
||||
mPreviewContainer.initContentLayout(mPreviewRect);
|
||||
}
|
||||
|
||||
private void adjustFunctionButtonLayout() {
|
||||
int width = mFunctionButtonLayout.getWidth();
|
||||
int count = mFunctionButtonLayout.getChildCount();
|
||||
int viewWidth = count * TUIMultimediaResourceUtils.dip2px(mContext, COMMON_FUNCTION_ICON_SIZE_DP);
|
||||
int margin = (width - viewWidth) / (count + 1);
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = mFunctionButtonLayout.getChildAt(i);
|
||||
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) child.getLayoutParams();
|
||||
layoutParams.setMarginStart(margin);
|
||||
layoutParams.setMarginEnd(0);
|
||||
child.setLayoutParams(layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSupportEditGraffiti() {
|
||||
return mEditType == EditType.VIDEO ? TUIMultimediaIConfig.getInstance().isSupportVideoEditGraffiti()
|
||||
: TUIMultimediaIConfig.getInstance().isSupportPictureEditGraffiti();
|
||||
}
|
||||
|
||||
public boolean isSupportEditMosaic() {
|
||||
return mEditType == EditType.PHOTO && TUIMultimediaIConfig.getInstance().isSupportPictureEditMosaic();
|
||||
}
|
||||
|
||||
public boolean isSupportEditPaster() {
|
||||
return mEditType == EditType.VIDEO ? TUIMultimediaIConfig.getInstance().isSupportVideoEditPaster()
|
||||
: TUIMultimediaIConfig.getInstance().isSupportPictureEditPaster();
|
||||
}
|
||||
|
||||
public boolean isSupportEditSubtitle() {
|
||||
return mEditType == EditType.VIDEO ? TUIMultimediaIConfig.getInstance().isSupportVideoEditSubtitle()
|
||||
: TUIMultimediaIConfig.getInstance().isSupportPictureEditSubtitle();
|
||||
}
|
||||
|
||||
public boolean isSupportEditBGM() {
|
||||
return mEditType == EditType.VIDEO && TUIMultimediaIConfig.getInstance().isSupportVideoEditBGM();
|
||||
}
|
||||
|
||||
public boolean isSupportEditCrop() {
|
||||
return mEditType == EditType.PHOTO && TUIMultimediaIConfig.getInstance().isSupportPictureEditCrop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFullScreen;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXVideoInfo;
|
||||
|
||||
public class TUIMultimediaEditActivity extends TUIMultimediaFullScreen {
|
||||
|
||||
private final String TAG = TUIMultimediaEditActivity.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
LiteavLog.i(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.multimedia_plugin_edit_activity);
|
||||
startUpMediaEdit();
|
||||
}
|
||||
|
||||
private void startUpMediaEdit() {
|
||||
Fragment fragment = null;
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
if (bundle == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TXVideoInfo videoInfo = TUIMultimediaVideoEditorCore
|
||||
.getVideoFileInfo(this, bundle.getString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH));
|
||||
|
||||
if (TUIMultimediaVideoEditorCore.isValidVideo(videoInfo)) {
|
||||
fragment = new TUIMultimediaVideoEditFragment(this);
|
||||
bundle.putFloat(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_RATIO, TUIMultimediaVideoEditorCore.getVideoAspect(videoInfo));
|
||||
}
|
||||
|
||||
if (fragment == null) {
|
||||
fragment = new TUIMultimediaPictureEditFragment(this);
|
||||
}
|
||||
|
||||
fragment.setArguments(bundle);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.fl_edit_fragment_container, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.HandlerThread;
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import com.tencent.liteav.base.util.CustomHandler;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuicore.TUICore;
|
||||
import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
|
||||
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXGenerateResult;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXVideoInfo;
|
||||
import com.tencent.ugc.TXVideoEditer.TXVideoGenerateListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
public class TUIMultimediaMediaProcessor {
|
||||
|
||||
private final String TAG = TUIMultimediaMediaProcessor.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
public interface TUIMultimediaMediaEditListener {
|
||||
|
||||
void onEditCompleted(Uri uri);
|
||||
}
|
||||
|
||||
public interface TUIMultimediaMediaTranscodeListener {
|
||||
|
||||
void onTranscodeFinished(TranscodeResult transcodeResult);
|
||||
|
||||
void onTranscodeProgress(Uri originUri, int transcodeProgress);
|
||||
}
|
||||
|
||||
public static class TranscodeResult {
|
||||
|
||||
public int errorCode;
|
||||
public String errorString;
|
||||
public Uri originalUri;
|
||||
public Uri transcodeMediaUri;
|
||||
}
|
||||
|
||||
private static TUIMultimediaMediaProcessor sInstance;
|
||||
|
||||
public static TUIMultimediaMediaProcessor getInstance() {
|
||||
if (sInstance == null) {
|
||||
synchronized (TUIMultimediaMediaProcessor.class) {
|
||||
sInstance = new TUIMultimediaMediaProcessor();
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public void editMedia(Context context, Uri uri, TUIMultimediaMediaEditListener listener) {
|
||||
LiteavLog.i(TAG, "edit media uri = " + uri.toString());
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH, uri.toString());
|
||||
TUICore.startActivityForResult((ActivityResultCaller) context, TUIMultimediaEditActivity.class,
|
||||
bundle, result -> {
|
||||
Uri editedMediaUri = null;
|
||||
if (result.getData() != null) {
|
||||
Bundle resultBundle = result.getData().getExtras();
|
||||
if (resultBundle != null) {
|
||||
String editedFilePath = resultBundle
|
||||
.getString(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH);
|
||||
editedMediaUri = editedFilePath != null ? FileUtil.getUriFromPath(editedFilePath) : null;
|
||||
}
|
||||
}
|
||||
if (listener != null) {
|
||||
LiteavLog.i(TAG, "onEditCompleted editedMediaUri = " + editedMediaUri);
|
||||
listener.onEditCompleted(editedMediaUri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void transcodeMedia(List<Uri> uris, TUIMultimediaMediaTranscodeListener listener) {
|
||||
LiteavLog.i(TAG, "transcodeMedia uris size is " + uris.size());
|
||||
MultiMediaFileTranscoder multiVideoTranscoder = new MultiMediaFileTranscoder(uris, listener);
|
||||
multiVideoTranscoder.startTranscode();
|
||||
}
|
||||
|
||||
private TUIMultimediaMediaProcessor() {
|
||||
}
|
||||
|
||||
private static class MultiMediaFileTranscoder {
|
||||
|
||||
private static final int CONCURRENT_TRANSCODE_NUMBER = 2;
|
||||
private final String TAG = MultiMediaFileTranscoder.class.getSimpleName() + "_" + hashCode();
|
||||
private final Queue<Uri> mUriQueue;
|
||||
private final TUIMultimediaMediaTranscodeListener mListener;
|
||||
private final CustomHandler mCustomHandler;
|
||||
private final Map<Uri, TXVideoInfo> mVideoInfoMap = new HashMap<>();
|
||||
|
||||
private boolean transcoding = false;
|
||||
|
||||
public MultiMediaFileTranscoder(List<Uri> uris, TUIMultimediaMediaTranscodeListener listener) {
|
||||
mUriQueue = new LinkedList<>(uris);
|
||||
mListener = listener;
|
||||
HandlerThread handlerThread = new HandlerThread("multi_video_transcode");
|
||||
handlerThread.start();
|
||||
mCustomHandler = new CustomHandler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
public void startTranscode() {
|
||||
mCustomHandler.runOrPost(() -> {
|
||||
if (transcoding) {
|
||||
return;
|
||||
}
|
||||
|
||||
transcoding = true;
|
||||
getVideoInfo();
|
||||
for (int i = 0; i < CONCURRENT_TRANSCODE_NUMBER; i++) {
|
||||
transcodeMediaInQueue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void transcodeMediaInQueue() {
|
||||
if (mUriQueue.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
do {
|
||||
uri = mUriQueue.poll();
|
||||
if (uri != null) {
|
||||
transcodeMedia(uri);
|
||||
}
|
||||
} while (!mUriQueue.isEmpty() && uri == null);
|
||||
}
|
||||
|
||||
private void getVideoInfo() {
|
||||
for (Uri uri : mUriQueue) {
|
||||
TXVideoInfo videoInfo = TUIMultimediaVideoEditorCore
|
||||
.getVideoFileInfo(TUIMultimediaPlugin.getAppContext(), uri.toString());
|
||||
mVideoInfoMap.put(uri, videoInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private void transcodeMedia(Uri uri) {
|
||||
LiteavLog.i(TAG, "transcode media uri is " + uri.toString());
|
||||
if (!isNeedTranscode(mVideoInfoMap.get(uri))) {
|
||||
LiteavLog.i(TAG, "do not need transcode. uri = " + uri);
|
||||
mCustomHandler.runOrPost(this::transcodeMediaInQueue);
|
||||
ThreadUtils.postOnUiThread(() -> {
|
||||
mListener.onTranscodeProgress(uri, 100);
|
||||
notifyTranscodeFinish(0, "", uri, uri);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
TUIMultimediaVideoEditorCore tuiMultimediaVideoEditorCore = new TUIMultimediaVideoEditorCore(TUIMultimediaPlugin.getAppContext() );
|
||||
tuiMultimediaVideoEditorCore.setSource(uri.toString());
|
||||
String outVideoPath = TUIMultimediaFileUtil
|
||||
.generateFilePath(MultimediaPluginFileType.EDIT_FILE);
|
||||
tuiMultimediaVideoEditorCore.generateVideo(outVideoPath, TUIMultimediaIConfig.getInstance().getVideoQuality(),
|
||||
new TXVideoGenerateListener() {
|
||||
@Override
|
||||
public void onGenerateProgress(float progress) {
|
||||
mListener.onTranscodeProgress(uri, (int) (progress * 100));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenerateComplete(TXGenerateResult txGenerateResult) {
|
||||
mCustomHandler.runOrPost(() -> transcodeMediaInQueue());
|
||||
notifyTranscodeFinish(txGenerateResult.retCode, txGenerateResult.descMsg, uri,
|
||||
FileUtil.getUriFromPath(outVideoPath));
|
||||
tuiMultimediaVideoEditorCore.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyTranscodeFinish(int errorCode, String errorString, Uri originalUri, Uri transcodeMediaUri) {
|
||||
TranscodeResult transcodeResult = new TranscodeResult();
|
||||
transcodeResult.errorCode = errorCode;
|
||||
transcodeResult.errorString = errorString;
|
||||
transcodeResult.transcodeMediaUri = transcodeMediaUri;
|
||||
transcodeResult.originalUri = originalUri;
|
||||
if (mListener != null) {
|
||||
mListener.onTranscodeFinished(transcodeResult);
|
||||
}
|
||||
LiteavLog.i(TAG, "onTranscodeFinished source media uri is " + originalUri
|
||||
+ " new media uri is " + transcodeMediaUri);
|
||||
}
|
||||
|
||||
private boolean isNeedTranscode(TXVideoInfo videoInfo) {
|
||||
if (videoInfo == null) {
|
||||
LiteavLog.i(TAG, "isNeedTranscode false. because video info is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TUIMultimediaVideoEditorCore.isValidVideo(videoInfo)) {
|
||||
LiteavLog.i(TAG, "isNeedTranscode false. because is not valid video");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isNeedTranscode = videoInfo.bitrate == 0 ||
|
||||
videoInfo.bitrate > TUIMultimediaVideoEditorCore
|
||||
.getBitrateAccordQuality(TUIMultimediaIConfig.getInstance().getVideoQuality()) * 1.2;
|
||||
if (!isNeedTranscode) {
|
||||
LiteavLog.i(TAG,
|
||||
"isNeedTranscode false. because is source bitrate is low. bitrate is " + videoInfo.bitrate);
|
||||
}
|
||||
return isNeedTranscode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXRect;
|
||||
|
||||
public class TUIMultimediaPasterInfo {
|
||||
|
||||
public Bitmap image;
|
||||
public TXRect frame;
|
||||
public PasterType pasterType;
|
||||
|
||||
public enum PasterType {
|
||||
SUBTITLE_PASTER,
|
||||
STATIC_PICTURE_PASTER
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaAuthorizationPrompter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.CommonMediaEditListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.EditType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.PictureCropListener;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
|
||||
import com.tencent.ugc.videobase.utils.CollectionUtils;
|
||||
import java.io.File;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.List;
|
||||
|
||||
public class TUIMultimediaPictureEditFragment extends Fragment {
|
||||
|
||||
private final String TAG = TUIMultimediaPictureEditFragment.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
|
||||
private TUIMultimediaPictureEditorCore mTuiMultimediaPictureEditorCore;
|
||||
private View mRootView;
|
||||
private String mPictureFilePath;
|
||||
private Bitmap mCurrentBitmap;
|
||||
private Bitmap mBeforeCropBitmap;
|
||||
private Bitmap mOriginBitmap;
|
||||
private ImageView mImageView;
|
||||
private boolean mIsRecordFile = true;
|
||||
|
||||
public TUIMultimediaPictureEditFragment(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static class RotateTransformation extends BitmapTransformation {
|
||||
|
||||
private final float rotateRotationAngle;
|
||||
|
||||
public RotateTransformation(float rotateRotationAngle) {
|
||||
this.rotateRotationAngle = rotateRotationAngle;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitmap transform(@NonNull BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postRotate(rotateRotationAngle, toTransform.getWidth() / 2.0f, toTransform.getHeight() / 2.0f);
|
||||
return Bitmap
|
||||
.createBitmap(toTransform, 0, 0, toTransform.getWidth(), toTransform.getHeight(), matrix, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDiskCacheKey(MessageDigest messageDigest) {
|
||||
messageDigest.update(("rotate" + rotateRotationAngle).getBytes(CHARSET));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
LiteavLog.i(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
mTuiMultimediaPictureEditorCore = new TUIMultimediaPictureEditorCore(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
LiteavLog.i(TAG, "onCreateView");
|
||||
mRootView = inflater.inflate(R.layout.multimedia_plugin_edit_picture_fragment, container, false);
|
||||
initView();
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
LiteavLog.i(TAG, "onDestroyView");
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LiteavLog.i(TAG, "onDestroy");
|
||||
super.onDestroy();
|
||||
mTuiMultimediaPictureEditorCore.release();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
initExternalParameters();
|
||||
|
||||
TUIMultiMediaEditCommonCtrlView editCommonCtrlView = new TUIMultiMediaEditCommonCtrlView(mContext,
|
||||
EditType.PHOTO, mIsRecordFile);
|
||||
((RelativeLayout) mRootView.findViewById(R.id.edit_common_ctrl_view_container))
|
||||
.addView(editCommonCtrlView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
|
||||
LiteavLog.i(TAG, "preview picture. file path = " + mPictureFilePath);
|
||||
mOriginBitmap = TUIMultimediaFileUtil.decodeBitmap(mPictureFilePath);
|
||||
mCurrentBitmap = mOriginBitmap;
|
||||
if (mCurrentBitmap == null) {
|
||||
LiteavLog.e(TAG, "decode bitmap fail");
|
||||
return;
|
||||
}
|
||||
|
||||
mImageView = new ImageView(mContext);
|
||||
editCommonCtrlView.setMediaView(mImageView);
|
||||
editCommonCtrlView.setMediaAspectRatio(mCurrentBitmap.getWidth() * 1.0f / mCurrentBitmap.getHeight());
|
||||
editCommonCtrlView.setPicture(mCurrentBitmap);
|
||||
previewPicture(0);
|
||||
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
|
||||
editCommonCtrlView.setCommonMediaEditListener(new CommonMediaEditListener() {
|
||||
@Override
|
||||
public void onGenerateMedia() {
|
||||
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<TXPaster> pasterList = editCommonCtrlView.getNormalizedPaster();
|
||||
LiteavLog.i(TAG, "on generate media." + " is recordFile: " + mIsRecordFile + " pasterList size is:" + (
|
||||
pasterList != null ? pasterList.size() : 0));
|
||||
if (CollectionUtils.isEmpty(pasterList)) {
|
||||
Bitmap bitmap = mTuiMultimediaPictureEditorCore.getSourcePicture();
|
||||
if (bitmap == mOriginBitmap && !mIsRecordFile) {
|
||||
finishEdit(null);
|
||||
} else {
|
||||
finishEdit(savaBitmapToLocalFile(bitmap));
|
||||
}
|
||||
} else {
|
||||
mTuiMultimediaPictureEditorCore.setPasterList(pasterList);
|
||||
mTuiMultimediaPictureEditorCore.processPicture(bitmap -> finishEdit(savaBitmapToLocalFile(bitmap)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelEdit() {
|
||||
cancelEdit();
|
||||
}
|
||||
});
|
||||
editCommonCtrlView.setPictureCropListener(new PictureCropListener() {
|
||||
@Override
|
||||
public void onConfirmCrop(RectF rectF, int rotation) {
|
||||
|
||||
mTuiMultimediaPictureEditorCore.setCropRectF(rectF);
|
||||
mTuiMultimediaPictureEditorCore.setOutputRotation(rotation);
|
||||
|
||||
mTuiMultimediaPictureEditorCore.processPicture(bitmap -> {
|
||||
mCurrentBitmap = bitmap;
|
||||
previewPicture(0);
|
||||
mTuiMultimediaPictureEditorCore.resetEditor();
|
||||
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
|
||||
editCommonCtrlView.setMediaAspectRatio(mCurrentBitmap.getWidth() * 1.0f / mCurrentBitmap.getHeight());
|
||||
editCommonCtrlView.setPicture(mCurrentBitmap);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRotation(int rotation) {
|
||||
LiteavLog.i(TAG, "onRotation rotation:" + rotation);
|
||||
previewPicture(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartCrop() {
|
||||
TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext);
|
||||
mTuiMultimediaPictureEditorCore
|
||||
.setPasterList(editCommonCtrlView.getNormalizedPaster());
|
||||
mBeforeCropBitmap = mCurrentBitmap;
|
||||
mTuiMultimediaPictureEditorCore.processPicture(bitmap -> {
|
||||
mCurrentBitmap = bitmap;
|
||||
previewPicture(0);
|
||||
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelCrop() {
|
||||
mCurrentBitmap = mBeforeCropBitmap;
|
||||
previewPicture(0);
|
||||
mTuiMultimediaPictureEditorCore.setSourcePicture(mCurrentBitmap);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String savaBitmapToLocalFile(Bitmap bitmap) {
|
||||
String outVideoPath = TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE);
|
||||
TUIMultimediaFileUtil.saveBmpToFile(bitmap, new File(outVideoPath), CompressFormat.JPEG);
|
||||
return outVideoPath;
|
||||
}
|
||||
|
||||
private void previewPicture(float rotation) {
|
||||
if (mImageView == null || mCurrentBitmap == null) {
|
||||
return;
|
||||
}
|
||||
LiteavLog.i(TAG, "previewPicture rotation : " + rotation);
|
||||
Glide.with(mContext)
|
||||
.load(mCurrentBitmap).apply(RequestOptions.bitmapTransform(new RotateTransformation(rotation)))
|
||||
.into(mImageView);
|
||||
}
|
||||
|
||||
private void cancelEdit() {
|
||||
if (mIsRecordFile) {
|
||||
((AppCompatActivity) mContext).getSupportFragmentManager().popBackStack();
|
||||
} else {
|
||||
finishEdit(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void finishEdit(String editedFilePath) {
|
||||
LiteavLog.i(TAG, "finish edit. path = " + editedFilePath);
|
||||
Intent resultIntent = new Intent();
|
||||
if (editedFilePath != null) {
|
||||
resultIntent.putExtra(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH, editedFilePath);
|
||||
resultIntent.putExtra(TUIMultimediaConstants.RECORD_TYPE_KEY, TUIMultimediaConstants.RECORD_TYPE_PHOTO);
|
||||
}
|
||||
((Activity) mContext).setResult(Activity.RESULT_OK, resultIntent);
|
||||
((Activity) mContext).finish();
|
||||
}
|
||||
|
||||
private void initExternalParameters() {
|
||||
Bundle bundle = getArguments();
|
||||
if (bundle == null) {
|
||||
return;
|
||||
}
|
||||
mPictureFilePath = bundle.getString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH);
|
||||
mIsRecordFile = bundle.getBoolean(TUIMultimediaConstants.PARAM_NAME_IS_RECODE_FILE, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import androidx.core.math.MathUtils;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.ugc.TXPictureEditer;
|
||||
import com.tencent.ugc.TXPictureEditer.PictureProcessListener;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
|
||||
import java.util.List;
|
||||
|
||||
public class TUIMultimediaPictureEditorCore {
|
||||
private final String TAG = TUIMultimediaPictureEditorCore.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
private final Context mContext;
|
||||
private TXPictureEditer mPictureEditer;
|
||||
private Bitmap mBitmap;
|
||||
|
||||
public TUIMultimediaPictureEditorCore(Context context) {
|
||||
LiteavLog.i(TAG,"TUIMultimedia Picture Editor Core Construct");
|
||||
mContext = context;
|
||||
mPictureEditer = TXPictureEditer.create(context);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (mPictureEditer != null) {
|
||||
TXPictureEditer.destroy(mPictureEditer);
|
||||
}
|
||||
}
|
||||
|
||||
public void resetEditor() {
|
||||
LiteavLog.i(TAG,"reset editor");
|
||||
if (mPictureEditer != null) {
|
||||
TXPictureEditer.destroy(mPictureEditer);
|
||||
mPictureEditer = TXPictureEditer.create(mContext);
|
||||
}
|
||||
mBitmap = null;
|
||||
}
|
||||
|
||||
public void setSourcePicture(Bitmap bitmap) {
|
||||
LiteavLog.i(TAG,"set source picture. bitmap: " + bitmap);
|
||||
mPictureEditer.setPicture(bitmap);
|
||||
mBitmap = bitmap;
|
||||
}
|
||||
|
||||
public Bitmap getSourcePicture() {
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
public void setCropRectF(RectF rectf) {
|
||||
if (mBitmap == null) {
|
||||
LiteavLog.e(TAG,"set crop rect. bitmap is null");
|
||||
return;
|
||||
}
|
||||
|
||||
int width = mBitmap.getWidth();
|
||||
int height = mBitmap.getHeight();
|
||||
|
||||
Rect rect = new Rect();
|
||||
rect.left = MathUtils.clamp((int) (width * rectf.left), 0, width);
|
||||
rect.right = MathUtils.clamp((int) (width * rectf.right), 0, width);
|
||||
rect.top = MathUtils.clamp((int) (height * rectf.top), 0, height);
|
||||
rect.bottom = MathUtils.clamp((int) (height * rectf.bottom), 0, height);
|
||||
mPictureEditer.setCropRect(rect);
|
||||
}
|
||||
|
||||
public void setPasterList(List<TXPaster> pasterList) {
|
||||
mPictureEditer.setPasterList(pasterList);
|
||||
}
|
||||
|
||||
public void setOutputRotation(int rotation) {
|
||||
mPictureEditer.setOutputRotation(rotation);
|
||||
}
|
||||
|
||||
public void processPicture(PictureProcessListener pictureProcessListener) {
|
||||
LiteavLog.i(TAG,"process picture");
|
||||
mPictureEditer.processPicture(pictureProcessListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.GestureDetector.OnGestureListener;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.ScaleGestureDetector.OnScaleGestureListener;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.math.MathUtils;
|
||||
import com.tencent.liteav.base.ThreadUtils;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
|
||||
public class TUIMultimediaTransformLayout extends RelativeLayout implements View.OnTouchListener {
|
||||
|
||||
private final String TAG = TUIMultimediaTransformLayout.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
private GestureDetector mGestureDetector;
|
||||
private ScaleGestureDetector mScaleGestureDetector;
|
||||
|
||||
private float mLastScaleFactor;
|
||||
private float mScaleFactor = 1.0f;
|
||||
private boolean isOnScaling = false;
|
||||
|
||||
private Rect mInitContentLayout;
|
||||
private Rect mContentLayout;
|
||||
private Rect mLimitLayout;
|
||||
|
||||
private final OnScaleGestureListener mOnScaleGestureListener = new OnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
float factorOffset = detector.getScaleFactor() - mLastScaleFactor;
|
||||
mLastScaleFactor = detector.getScaleFactor();
|
||||
mScaleFactor = MathUtils.clamp(mScaleFactor + factorOffset, 0.5f, 1.5f);
|
||||
scaleContent(factorOffset + 1, new PointF(detector.getFocusX(), detector.getFocusY()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
mLastScaleFactor = detector.getScaleFactor();
|
||||
isOnScaling = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
|
||||
ThreadUtils.getUiThreadHandler().postDelayed(() -> isOnScaling = false, 200);
|
||||
}
|
||||
};
|
||||
|
||||
private final OnGestureListener mOnGestureListener = new OnGestureListener() {
|
||||
@Override
|
||||
public boolean onDown(@NonNull MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(@NonNull MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) {
|
||||
if (isOnScaling) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Math.abs(distanceX) > 300 || Math.abs(distanceY) > 300) {
|
||||
return true;
|
||||
}
|
||||
|
||||
moveContent(distanceX, distanceY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(@NonNull MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(@NonNull MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
public TUIMultimediaTransformLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TUIMultimediaTransformLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public void initContentLayout(Rect rect) {
|
||||
mInitContentLayout = rect;
|
||||
mLimitLayout = mInitContentLayout;
|
||||
setContentLayout(rect);
|
||||
}
|
||||
|
||||
public Rect getInitContentLayout() {
|
||||
return mInitContentLayout;
|
||||
}
|
||||
|
||||
public void moveContent(float x, float y) {
|
||||
if (mContentLayout == null || mLimitLayout == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int left = (int) (mContentLayout.left - x);
|
||||
int top = (int) (mContentLayout.top - y);
|
||||
setContentLayout(new Rect(left, top, left + mContentLayout.width(), top + mContentLayout.height()));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void scaleContent(float scale, PointF center) {
|
||||
if (mContentLayout == null) {
|
||||
return;
|
||||
}
|
||||
int w = (int) (mContentLayout.width() * scale);
|
||||
int h = (int) (mContentLayout.height() * scale);
|
||||
int l = (int) (mContentLayout.left * scale + center.x - center.x * scale);
|
||||
int t = (int) (mContentLayout.top * scale + center.y - center.y * scale);
|
||||
setContentLayout(new Rect(l, t, w + l, h + t));
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void rotation90Content(Point rotationCenter) {
|
||||
if (mContentLayout == null) {
|
||||
return;
|
||||
}
|
||||
int newTop = rotationCenter.y - rotationCenter.x + mContentLayout.left;
|
||||
int newLeft = rotationCenter.x + rotationCenter.y - mContentLayout.top - mContentLayout.height();
|
||||
setContentLayout(new Rect(newLeft, newTop, newLeft + mContentLayout.height(), newTop + mContentLayout.width()));
|
||||
}
|
||||
|
||||
public void enableTransform(boolean enable) {
|
||||
LiteavLog.i(TAG, enable ? "enable " : "disable " + "transform.");
|
||||
setOnTouchListener(enable ? this : null);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mLastScaleFactor = 0.0f;
|
||||
mScaleFactor = 1.0f;
|
||||
mLimitLayout = mInitContentLayout;
|
||||
setContentLayout(mInitContentLayout);
|
||||
}
|
||||
|
||||
public Rect getContentRect() {
|
||||
return mContentLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(View view, int index) {
|
||||
if (view.getParent() == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index > getChildCount()) {
|
||||
index = getChildCount();
|
||||
}
|
||||
|
||||
super.addView(view, index);
|
||||
|
||||
if (mContentLayout != null) {
|
||||
view.layout(mContentLayout.left, mContentLayout.top, mContentLayout.right, mContentLayout.bottom);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLimitRect(Rect rect) {
|
||||
mLimitLayout = rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
|
||||
mGestureDetector = new GestureDetector(getContext(), mOnGestureListener);
|
||||
mScaleGestureDetector = new ScaleGestureDetector(getContext(), mOnScaleGestureListener);
|
||||
setOnTouchListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
removeAllViews();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, @NonNull MotionEvent event) {
|
||||
if (v != this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int pointerCount = event.getPointerCount();
|
||||
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
adjustContentToLimitRect();
|
||||
}
|
||||
|
||||
if (pointerCount >= 2) {
|
||||
mScaleGestureDetector.onTouchEvent(event);
|
||||
return true;
|
||||
} else if (pointerCount == 1) {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
super.performClick();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
mLastScaleFactor = 0.0f;
|
||||
mScaleFactor = 1.0f;
|
||||
setContentLayout(mInitContentLayout);
|
||||
}
|
||||
|
||||
private void setContentLayout(Rect rect) {
|
||||
if (rect == null) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View child = getChildAt(i);
|
||||
child.layout(rect.left, rect.top, rect.right, rect.bottom);
|
||||
}
|
||||
mContentLayout = rect;
|
||||
}
|
||||
|
||||
private void adjustContentToLimitRect() {
|
||||
LiteavLog.i(TAG,"adjust content to limit rect.");
|
||||
double scaleLimitX = mLimitLayout.width() * 1.0f / mContentLayout.width();
|
||||
double scaleLimitY = mLimitLayout.height() * 1.0f / mContentLayout.height();
|
||||
|
||||
double scaleLimit = Math.max(scaleLimitX, scaleLimitY);
|
||||
Rect rect = new Rect(mContentLayout);
|
||||
Point center = new Point(rect.centerX(), rect.centerY());
|
||||
if (scaleLimit > 1.0f) {
|
||||
int w = (int) (mContentLayout.width() * scaleLimit);
|
||||
int h = (int) (mContentLayout.height() * scaleLimit);
|
||||
int l = (int) (mContentLayout.left * scaleLimit + center.x - center.x * scaleLimit);
|
||||
int t = (int) (mContentLayout.top * scaleLimit + center.y - center.y * scaleLimit);
|
||||
rect = new Rect(l, t, l + w, t + h);
|
||||
}
|
||||
|
||||
int l = MathUtils.clamp(rect.left, mLimitLayout.right - rect.width(), mLimitLayout.left);
|
||||
int t = MathUtils.clamp(rect.top, mLimitLayout.bottom - rect.height(), mLimitLayout.top);
|
||||
rect = new Rect(l, t, l + rect.width(), t + rect.height());
|
||||
setContentLayout(rect);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaAuthorizationPrompter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.CommonMediaEditListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultiMediaEditCommonCtrlView.EditType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.BGMEditListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.ProgressRingView;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXGenerateResult;
|
||||
import com.tencent.ugc.TXVideoEditer.TXVideoGenerateListener;
|
||||
|
||||
public class TUIMultimediaVideoEditFragment extends Fragment {
|
||||
|
||||
private final String TAG = TUIMultimediaVideoEditFragment.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
|
||||
private TUIMultiMediaEditCommonCtrlView mTuiMultiMediaEditCommonCtrlView;
|
||||
private TUIMultimediaVideoEditorCore mTUIMultimediaVideoEditorCore;
|
||||
private View mRootView;
|
||||
private View mGenerateVideoView;
|
||||
private ProgressRingView mCircleProgressBar;
|
||||
private String mEditFilePath;
|
||||
private float mAspectRatio = 9.0f / 16.0f;
|
||||
|
||||
private boolean mIsRecordFile = false;
|
||||
|
||||
public TUIMultimediaVideoEditFragment(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
LiteavLog.i(TAG, "onCreate");
|
||||
mTUIMultimediaVideoEditorCore = new TUIMultimediaVideoEditorCore(mContext);
|
||||
initExternalParameters();
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
LiteavLog.i(TAG, "onCreateView");
|
||||
mRootView = inflater.inflate(R.layout.multimedia_plugin_edit_video_fragment, container, false);
|
||||
initView();
|
||||
return mRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
LiteavLog.i(TAG, "onDestroyView");
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LiteavLog.i(TAG, "onDestroy");
|
||||
super.onDestroy();
|
||||
mTUIMultimediaVideoEditorCore.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mTUIMultimediaVideoEditorCore.resumePreview();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
mTUIMultimediaVideoEditorCore.pausePreview();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
mTuiMultiMediaEditCommonCtrlView = new TUIMultiMediaEditCommonCtrlView(mContext, EditType.VIDEO, mIsRecordFile);
|
||||
mTuiMultiMediaEditCommonCtrlView.setMediaAspectRatio(mAspectRatio);
|
||||
((RelativeLayout) mRootView.findViewById(R.id.edit_common_ctrl_view_container))
|
||||
.addView(mTuiMultiMediaEditCommonCtrlView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
mGenerateVideoView = mRootView.findViewById(R.id.rl_generate_video_view);
|
||||
mCircleProgressBar = mRootView.findViewById(R.id.generate_video_progressBar);
|
||||
|
||||
mRootView.findViewById(R.id.generate_video_cancel_btn).setOnClickListener(view -> cancelGenerateVideo());
|
||||
|
||||
previewVideo(mEditFilePath);
|
||||
|
||||
mTuiMultiMediaEditCommonCtrlView.setBGMListener(new BGMEditListener() {
|
||||
@Override
|
||||
public void onAddBGM(String bgmPath) {
|
||||
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
|
||||
return;
|
||||
}
|
||||
mTUIMultimediaVideoEditorCore.setBGMPath(bgmPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCutBGM(long startTime, long endTime) {
|
||||
mTUIMultimediaVideoEditorCore.setBGMStartTime(startTime, endTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMuteSourceAudio(boolean isMute) {
|
||||
mTUIMultimediaVideoEditorCore.muteSourceAudio(isMute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMuteBGM(boolean isMute) {
|
||||
mTUIMultimediaVideoEditorCore.muteBGM(isMute);
|
||||
}
|
||||
});
|
||||
|
||||
mTuiMultiMediaEditCommonCtrlView.setCommonMediaEditListener(new CommonMediaEditListener() {
|
||||
@Override
|
||||
public void onGenerateMedia() {
|
||||
generateVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelEdit() {
|
||||
if (mIsRecordFile) {
|
||||
((AppCompatActivity) mContext).getSupportFragmentManager().popBackStack();
|
||||
} else {
|
||||
finishEdit(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void previewVideo(String videoFilePath) {
|
||||
LiteavLog.i(TAG, "preview video. file path = " + videoFilePath);
|
||||
if (videoFilePath == null || videoFilePath.isEmpty()) {
|
||||
LiteavLog.e(TAG, "vide file path is null");
|
||||
return;
|
||||
}
|
||||
|
||||
mTUIMultimediaVideoEditorCore.setSource(videoFilePath);
|
||||
TUIMultimediaVideoPreview editorVideoView = new TUIMultimediaVideoPreview(mContext,
|
||||
mTUIMultimediaVideoEditorCore);
|
||||
mTuiMultiMediaEditCommonCtrlView.setMediaView(editorVideoView);
|
||||
}
|
||||
|
||||
private void generateVideo() {
|
||||
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTUIMultimediaVideoEditorCore.stopPreview();
|
||||
mTUIMultimediaVideoEditorCore.setPasterList(mTuiMultiMediaEditCommonCtrlView.getPaster());
|
||||
|
||||
if (!mIsRecordFile && !mTUIMultimediaVideoEditorCore.isVideoEdited()) {
|
||||
finishEdit(null);
|
||||
return;
|
||||
}
|
||||
|
||||
mGenerateVideoView.setVisibility(View.VISIBLE);
|
||||
mCircleProgressBar.setProgress(0);
|
||||
String outVideoPath = TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.EDIT_FILE);
|
||||
mTUIMultimediaVideoEditorCore.generateVideo(outVideoPath, TUIMultimediaIConfig.getInstance().getVideoQuality(),
|
||||
new TXVideoGenerateListener() {
|
||||
@Override
|
||||
public void onGenerateProgress(float progress) {
|
||||
mCircleProgressBar.setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGenerateComplete(TXGenerateResult txGenerateResult) {
|
||||
LiteavLog.i(TAG, "onGenerateComplete = " + txGenerateResult.retCode);
|
||||
finishEdit(outVideoPath);
|
||||
mGenerateVideoView.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void cancelGenerateVideo() {
|
||||
((ProgressRingView) mRootView.findViewById(R.id.generate_video_progressBar)).setProgress(0);
|
||||
mGenerateVideoView.setVisibility(View.GONE);
|
||||
|
||||
mTuiMultiMediaEditCommonCtrlView.bringToFront();
|
||||
mTuiMultiMediaEditCommonCtrlView.showOperationView(true);
|
||||
|
||||
mTUIMultimediaVideoEditorCore.cancelGenerateVideo();
|
||||
mTUIMultimediaVideoEditorCore.reStartPreview();
|
||||
mTUIMultimediaVideoEditorCore.clearPaster();
|
||||
}
|
||||
|
||||
private void finishEdit(String editedFilePath) {
|
||||
LiteavLog.i(TAG, "finish edit. path = " + editedFilePath);
|
||||
Intent resultIntent = new Intent();
|
||||
if (editedFilePath != null) {
|
||||
resultIntent.putExtra(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH, editedFilePath);
|
||||
resultIntent.putExtra(TUIMultimediaConstants.RECORD_TYPE_KEY, TUIMultimediaConstants.RECORD_TYPE_VIDEO);
|
||||
}
|
||||
((Activity) mContext).setResult(Activity.RESULT_OK, resultIntent);
|
||||
((Activity) mContext).finish();
|
||||
}
|
||||
|
||||
private void initExternalParameters() {
|
||||
Bundle bundle = getArguments();
|
||||
if (bundle == null) {
|
||||
return;
|
||||
}
|
||||
mEditFilePath = bundle.getString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH, "");
|
||||
mAspectRatio = bundle.getFloat(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_RATIO, 9.0f / 16.0f);
|
||||
mIsRecordFile = bundle.getBoolean(TUIMultimediaConstants.PARAM_NAME_IS_RECODE_FILE, false);
|
||||
LiteavLog.i(TAG, "init external parameters mEditFilePath = " + mEditFilePath
|
||||
+ " video quality = " + TUIMultimediaIConfig.getInstance().getVideoQuality()
|
||||
+ ", video ratio = " + mAspectRatio);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants.VideoQuality;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
|
||||
import com.tencent.rtmp.ui.TXCloudVideoView;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
import com.tencent.ugc.TXVideoEditConstants;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXPaster;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXPreviewParam;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXSubtitle;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXVideoInfo;
|
||||
import com.tencent.ugc.TXVideoEditer;
|
||||
import com.tencent.ugc.TXVideoEditer.TXVideoPreviewListener;
|
||||
import com.tencent.ugc.TXVideoInfoReader;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class TUIMultimediaVideoEditorCore {
|
||||
|
||||
private static final int LOW_QUALITY_BITRATE = 1000;
|
||||
private static final int MEDIUM_QUALITY_BITRATE = 3000;
|
||||
private static final int HIGH_QUALITY_BITRATE = 5000;
|
||||
|
||||
private final String TAG = TUIMultimediaVideoEditorCore.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
private TXVideoEditer mEditorSDK;
|
||||
private boolean mIsLoopPlay = false;
|
||||
private boolean mIsAddBgm = false;
|
||||
private boolean mIsAddPaster = false;
|
||||
private boolean mIsMute = false;
|
||||
|
||||
private final TXVideoPreviewListener mTXVideoPreviewListener = new TXVideoPreviewListener() {
|
||||
@Override
|
||||
public void onPreviewProgress(int i) {}
|
||||
|
||||
@Override
|
||||
public void onPreviewFinished() {
|
||||
LiteavLog.i(TAG, "on preview finished.");
|
||||
if (mIsLoopPlay && mEditorSDK != null) {
|
||||
mEditorSDK.stopPlay();
|
||||
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public TUIMultimediaVideoEditorCore(Context context) {
|
||||
LiteavLog.i(TAG, "TUIMultimediaEditorCore construct.");
|
||||
mEditorSDK = new TXVideoEditer(context);
|
||||
}
|
||||
|
||||
public void setSource(String path) {
|
||||
LiteavLog.i(TAG, "set source path is " + path);
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.setVideoPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
public void startPreview(TXCloudVideoView videoView, boolean isLoopPlay) {
|
||||
if (mEditorSDK == null) {
|
||||
LiteavLog.e(TAG,"start preview. editor SDK has release.");
|
||||
return;
|
||||
}
|
||||
|
||||
LiteavLog.i(TAG, "start preview videoView is " + videoView + " loop play flag is " + isLoopPlay);
|
||||
mIsLoopPlay = isLoopPlay;
|
||||
TXPreviewParam previewParam = new TXPreviewParam();
|
||||
previewParam.videoView = videoView;
|
||||
previewParam.renderMode = TXVideoEditConstants.PREVIEW_RENDER_MODE_FILL_EDGE;
|
||||
mEditorSDK.initWithPreview(previewParam);
|
||||
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
|
||||
mEditorSDK.setTXVideoPreviewListener(mTXVideoPreviewListener);
|
||||
}
|
||||
|
||||
public void muteBGM(boolean isMute) {
|
||||
LiteavLog.i(TAG," mute bgm. is mute:" + isMute);
|
||||
mIsAddBgm = !isMute;
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.setBGMVolume(isMute ? 0f : 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void muteSourceAudio(boolean isMute) {
|
||||
LiteavLog.i(TAG," mute source audio. is mute:" + isMute);
|
||||
mIsMute = isMute;
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.setVideoVolume(isMute ? 0f : 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopPreview() {
|
||||
LiteavLog.i(TAG, "stop preview");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.stopPlay();
|
||||
mEditorSDK.setTXVideoPreviewListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void pausePreview() {
|
||||
LiteavLog.i(TAG,"pause preview");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.pausePlay();
|
||||
}
|
||||
}
|
||||
|
||||
public void resumePreview() {
|
||||
LiteavLog.i(TAG,"resume preview");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.resumePlay();
|
||||
}
|
||||
}
|
||||
|
||||
public void reStartPreview() {
|
||||
LiteavLog.i(TAG,"restart preview");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
|
||||
mEditorSDK.setTXVideoPreviewListener(mTXVideoPreviewListener);
|
||||
}
|
||||
}
|
||||
|
||||
public void generateVideo(String videoOutputPath, VideoQuality videoQuality,
|
||||
TXVideoEditer.TXVideoGenerateListener listener) {
|
||||
if (mEditorSDK == null) {
|
||||
LiteavLog.e(TAG,"generate video. editor SDK has release.");
|
||||
return;
|
||||
}
|
||||
LiteavLog.i(TAG, "generate video.videoOutputPath = " + videoOutputPath + " VideoQuality = " + videoQuality);
|
||||
mEditorSDK.setVideoGenerateListener(listener);
|
||||
mEditorSDK.setVideoBitrate(getBitrateAccordQuality(videoQuality));
|
||||
mEditorSDK.generateVideo(getCompressLeveAccordQuality(videoQuality), videoOutputPath);
|
||||
}
|
||||
|
||||
public void cancelGenerateVideo() {
|
||||
LiteavLog.i(TAG, "cancel generate video");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.cancel();
|
||||
mEditorSDK.processVideo();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPasterList(@NonNull List<TUIMultimediaPasterInfo> pasterInfoList) {
|
||||
if (mEditorSDK == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LiteavLog.i(TAG,"set Paster List. size = " + pasterInfoList.size());
|
||||
List<TXSubtitle> subtitleList = new LinkedList<>();
|
||||
List<TXPaster> pasterList = new LinkedList<>();
|
||||
for (TUIMultimediaPasterInfo pasterInfo : pasterInfoList) {
|
||||
if (pasterInfo.pasterType == PasterType.SUBTITLE_PASTER) {
|
||||
TXSubtitle subtitle = new TXSubtitle();
|
||||
subtitle.frame = pasterInfo.frame;
|
||||
subtitle.titleImage = pasterInfo.image;
|
||||
subtitle.startTime = 0;
|
||||
subtitle.endTime = Integer.MAX_VALUE;
|
||||
subtitleList.add(subtitle);
|
||||
} else if (pasterInfo.pasterType == PasterType.STATIC_PICTURE_PASTER) {
|
||||
TXPaster staticPaster = new TXPaster();
|
||||
staticPaster.frame = pasterInfo.frame;
|
||||
staticPaster.pasterImage = pasterInfo.image;
|
||||
staticPaster.startTime = 0;
|
||||
staticPaster.endTime = Integer.MAX_VALUE;
|
||||
pasterList.add(staticPaster);
|
||||
}
|
||||
mIsAddPaster = true;
|
||||
}
|
||||
|
||||
if (!subtitleList.isEmpty()) {
|
||||
mEditorSDK.setSubtitleList(subtitleList);
|
||||
}
|
||||
|
||||
if (!pasterList.isEmpty()) {
|
||||
mEditorSDK.setPasterList(pasterList);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearPaster() {
|
||||
LiteavLog.i(TAG, "clear Paster");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.setSubtitleList(null);
|
||||
mEditorSDK.setPasterList(null);
|
||||
}
|
||||
mIsAddPaster = false;
|
||||
}
|
||||
|
||||
public void setBGMPath(String path) {
|
||||
LiteavLog.i(TAG, "set bgm path. path is " + path);
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.setBGM(path);
|
||||
mEditorSDK.setBGMLoop(true);
|
||||
mEditorSDK.setBGMVolume(1.0f);
|
||||
mEditorSDK.startPlayFromTime(0, Long.MAX_VALUE);
|
||||
}
|
||||
mIsAddBgm = (path != null && !path.isEmpty());
|
||||
}
|
||||
|
||||
public void setBGMStartTime(long startTime, long endTime) {
|
||||
LiteavLog.i(TAG, "set BGM start time. " + " start time :" + startTime + " end time : " + endTime);
|
||||
if (startTime >= 0 && endTime > startTime && mEditorSDK != null) {
|
||||
mEditorSDK.setBGMStartTime(startTime, endTime);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVideoEdited() {
|
||||
LiteavLog.i(TAG,"is add paster:[%s],is add bgm:[%s],",mIsAddPaster, mIsAddBgm);
|
||||
return mIsAddPaster || mIsAddBgm || mIsMute;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
LiteavLog.i(TAG, "stop preview");
|
||||
if (mEditorSDK != null) {
|
||||
mEditorSDK.release();
|
||||
mEditorSDK = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getCompressLeveAccordQuality(VideoQuality videoQuality) {
|
||||
switch (videoQuality) {
|
||||
case HIGH:
|
||||
return TXRecordCommon.VIDEO_RESOLUTION_1080_1920;
|
||||
case LOW:
|
||||
case MEDIUM:
|
||||
default:
|
||||
return TXRecordCommon.VIDEO_RESOLUTION_720_1280;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getBitrateAccordQuality(VideoQuality videoQuality) {
|
||||
switch (videoQuality) {
|
||||
case LOW:
|
||||
return LOW_QUALITY_BITRATE;
|
||||
case HIGH:
|
||||
return HIGH_QUALITY_BITRATE;
|
||||
case MEDIUM:
|
||||
default:
|
||||
return MEDIUM_QUALITY_BITRATE;
|
||||
}
|
||||
}
|
||||
|
||||
public static TXVideoInfo getVideoFileInfo(Context context, String filePath) {
|
||||
try {
|
||||
return TXVideoInfoReader.getInstance(context).getVideoFileInfo(filePath);
|
||||
} catch (Exception e) {
|
||||
LiteavLog.i(TUIMultimediaVideoEditorCore.class.getSimpleName(),"getVideoFileInfo Exception e = ",e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValidVideo(TXVideoInfo videoInfo) {
|
||||
if (videoInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return videoInfo.duration != 0 || videoInfo.width != 0 || videoInfo.height != 0
|
||||
|| videoInfo.bitrate != 0 || videoInfo.fps != 0;
|
||||
}
|
||||
|
||||
public static float getVideoAspect(TXVideoInfo videoInfo) {
|
||||
float aspect = 9.0f / 16.0f;
|
||||
if (videoInfo == null) {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
if (videoInfo.width != 0 && videoInfo.height != 0) {
|
||||
aspect = videoInfo.width * 1.0f / videoInfo.height;
|
||||
}
|
||||
return aspect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.rtmp.ui.TXCloudVideoView;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class TUIMultimediaVideoPreview extends FrameLayout {
|
||||
|
||||
private final String TAG = TUIMultimediaVideoPreview.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaVideoEditorCore mEditorCore;
|
||||
|
||||
public TUIMultimediaVideoPreview(@NonNull Context context, TUIMultimediaVideoEditorCore editorCore) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mEditorCore = editorCore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
mEditorCore.stopPreview();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
View rootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_video_play_view, this, true);
|
||||
TXCloudVideoView videoView = rootView.findViewById(R.id.edit_video_view);
|
||||
mEditorCore.startPreview(videoView, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
public interface BGMEditListener {
|
||||
void onAddBGM(String bgmPath);
|
||||
|
||||
void onCutBGM(long startTime, long endTime);
|
||||
|
||||
void onMuteSourceAudio(boolean isMute);
|
||||
|
||||
void onMuteBGM(boolean isMute);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.RelativeLayout.LayoutParams;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo.ItemPosition;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMType;
|
||||
|
||||
public class BGMItemAdapter extends BaseAdapter {
|
||||
|
||||
private final Context mContext;
|
||||
private final BGMInfo mBGMInfo;
|
||||
private final int mBGMTypeIndex;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition;
|
||||
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable;
|
||||
|
||||
private BGMItemView mLastSelectedItemView;
|
||||
|
||||
public BGMItemAdapter(Context context, BGMInfo bgmInfo, int bgmTypeIndex,
|
||||
TUIMultimediaData<ItemPosition> tuiDataSelectedPosition, TUIMultimediaData<Boolean> tuiDataBGMEnable) {
|
||||
mContext = context;
|
||||
mBGMInfo = bgmInfo;
|
||||
mBGMTypeIndex = bgmTypeIndex;
|
||||
mTuiDataSelectedPosition = tuiDataSelectedPosition;
|
||||
mTuiDataSelectedPosition.observe(position -> mLastSelectedItemView = null);
|
||||
|
||||
mTuiDataBGMEnable = tuiDataBGMEnable;
|
||||
mTuiDataBGMEnable.observe(enable -> {
|
||||
if (mLastSelectedItemView != null) {
|
||||
mLastSelectedItemView.setEnabled(enable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
BGMType bgmType = mBGMInfo.getBGMType(mBGMTypeIndex);
|
||||
return bgmType != null ? bgmType.getItemSize() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
BGMType bgmType = mBGMInfo.getBGMType(mBGMTypeIndex);
|
||||
return bgmType != null ? bgmType.getBGMItem(position) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
final ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.multimedia_plugin_edit_bgm_item_view_container, parent, false);
|
||||
holder = new ViewHolder(convertView);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
ItemPosition currentPosition = new ItemPosition(mBGMTypeIndex, position);
|
||||
BGMItem bgmItem = mBGMInfo.getBGMItem(currentPosition);
|
||||
BGMItemView bgmItemView = holder.bgmItemView;
|
||||
bgmItemView.bindBGMItem(bgmItem);
|
||||
|
||||
boolean isSelected = currentPosition.equals(mTuiDataSelectedPosition.get());
|
||||
bgmItemView.setSelected(isSelected);
|
||||
if (isSelected) {
|
||||
bgmItemView.setEnabled(mTuiDataBGMEnable.get());
|
||||
if (mLastSelectedItemView != null && mLastSelectedItemView != bgmItemView) {
|
||||
mLastSelectedItemView.setSelected(false);
|
||||
}
|
||||
mLastSelectedItemView = bgmItemView;
|
||||
}
|
||||
|
||||
convertView.setOnClickListener(v -> {
|
||||
if (currentPosition.equals(mTuiDataSelectedPosition.get())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLastSelectedItemView != null) {
|
||||
mLastSelectedItemView.setSelected(false);
|
||||
}
|
||||
mTuiDataSelectedPosition.set(currentPosition);
|
||||
mLastSelectedItemView = bgmItemView;
|
||||
bgmItemView.setSelected(true);
|
||||
bgmItemView.setEnabled(true);
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public BGMItemView bgmItemView;
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
bgmItemView = new BGMItemView(itemView.getContext());
|
||||
((RelativeLayout) itemView).addView(bgmItemView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMItem;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class BGMItemView extends RelativeLayout {
|
||||
|
||||
private final String TAG = BGMItemView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
|
||||
private TextView mBGMNameTextView;
|
||||
private TextView mBGMAuthorTextView;
|
||||
private TextView mBGMDurationTextView;
|
||||
private BGMSpectrumView mSpectrumView;
|
||||
private BGMItem mBgmItem;
|
||||
|
||||
public BGMItemView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void bindBGMItem(@NonNull BGMItem bgmItem) {
|
||||
mBgmItem = bgmItem;
|
||||
initData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
mSpectrumView.stop();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_bgm_item_view, this, true);
|
||||
|
||||
mBGMNameTextView = findViewById(R.id.bgm_name);
|
||||
mBGMAuthorTextView = findViewById(R.id.bgm_author_name);
|
||||
mBGMDurationTextView = findViewById(R.id.bgm_duration);
|
||||
mSpectrumView = findViewById(R.id.bgm_spectrum_view);
|
||||
initData();
|
||||
setViewStatus();
|
||||
}
|
||||
|
||||
private void initData() {
|
||||
if (!isAttachedToWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String bgmName = mBgmItem.getBgmName();
|
||||
if (bgmName != null) {
|
||||
mBGMNameTextView.setText(bgmName);
|
||||
}
|
||||
|
||||
String bgmAuthorName = mBgmItem.getBGMAuthorName();
|
||||
if (bgmAuthorName != null && !bgmAuthorName.isEmpty()) {
|
||||
mBGMAuthorTextView.setText(bgmAuthorName);
|
||||
}
|
||||
|
||||
TUIMultimediaData<Long> tuiDataBGMDuration = new TUIMultimediaData<>(0L);
|
||||
tuiDataBGMDuration.observe(
|
||||
duration -> mBGMDurationTextView.setText(TUIMultimediaResourceUtils.secondsToTimeString(duration)));
|
||||
mBgmItem.getBGMDuration(tuiDataBGMDuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelected(boolean selected) {
|
||||
super.setSelected(selected);
|
||||
setViewStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
setViewStatus();
|
||||
}
|
||||
|
||||
private void setViewStatus() {
|
||||
if (!isAttachedToWindow()) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isSelected = isSelected();
|
||||
boolean isEnable = isEnabled();
|
||||
|
||||
if (isSelected) {
|
||||
mSpectrumView.setVisibility(VISIBLE);
|
||||
mSpectrumView.setEnabled(isEnable);
|
||||
} else {
|
||||
mSpectrumView.setVisibility(GONE);
|
||||
mSpectrumView.stop();
|
||||
}
|
||||
|
||||
@ColorInt int bgmNameTextColor = TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_bgm_item_name_text_color_normal);
|
||||
if (isSelected && isEnable) {
|
||||
bgmNameTextColor = TUIMultimediaIConfig.getInstance().getThemeColor();
|
||||
}
|
||||
mBGMNameTextView.setTextColor(bgmNameTextColor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaCommonThreadPool;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BGMManager {
|
||||
|
||||
private final String TAG = BGMManager.class.getSimpleName() + "_" + hashCode();
|
||||
private final String mBGMJsonFilePath;
|
||||
private Future<BGMInfo> mCreateBGMInfoFuture;
|
||||
|
||||
public BGMManager(String bgmJsonFilePath) {
|
||||
mBGMJsonFilePath = bgmJsonFilePath;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
mCreateBGMInfoFuture = TUIMultimediaCommonThreadPool.getThreadExecutor().submit(this::createBGMInfoFromJsonFile);
|
||||
}
|
||||
|
||||
protected BGMInfo getBGMInfo() {
|
||||
try {
|
||||
return mCreateBGMInfoFuture.get(500, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
LiteavLog.e(TAG, "create picture paster set info error " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private BGMInfo createBGMInfoFromJsonFile() {
|
||||
LiteavLog.i(TAG, "bgm json file path =" + mBGMJsonFilePath);
|
||||
if (mBGMJsonFilePath == null || mBGMJsonFilePath.isEmpty() || !mBGMJsonFilePath.endsWith(".json")) {
|
||||
LiteavLog.e(TAG, "pasterJsonFilePath =" + mBGMJsonFilePath + " is invalid paster json file");
|
||||
return null;
|
||||
}
|
||||
String json = TUIMultimediaFileUtil.readTextFromFile(mBGMJsonFilePath);
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(json, BGMInfo.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.StateListDrawable;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
|
||||
import com.google.android.material.tabs.TabLayout.Tab;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo.ItemPosition;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMType;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class BGMPanelView extends RelativeLayout {
|
||||
|
||||
private final String TAG = BGMPanelView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final BGMInfo mBGMInfo;
|
||||
private final BGMManager mBGMManager;
|
||||
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable = new TUIMultimediaData<>(null);
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition = new TUIMultimediaData<>(null);
|
||||
|
||||
private TabLayout mBGMTypeTabLayout;
|
||||
private BGMTypeAdapter mBGMTypeAdapter;
|
||||
private CheckBox mBGMEnableCheckBox;
|
||||
private ItemPosition mLastSelectedItemPosition;
|
||||
private BGMEnableListener mBgmEnableListener;
|
||||
private BGMEditListener mBGMEditListener;
|
||||
|
||||
private final OnTabSelectedListener onTabSelectedListener = new OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(Tab tab) {
|
||||
if (mBGMTypeAdapter != null) {
|
||||
mBGMTypeAdapter.setSelectedBGMItemView(mTuiDataSelectedPosition.get());
|
||||
}
|
||||
changeTabSelectedStatus(tab, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(Tab tab) {
|
||||
changeTabSelectedStatus(tab, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(Tab tab) {
|
||||
}
|
||||
};
|
||||
|
||||
public BGMPanelView(Context context, BGMManager bgmManager) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mBGMManager = bgmManager;
|
||||
mBGMInfo = mBGMManager.getBGMInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
addObserver();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
removeObserver();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void setBGMEditListener(BGMEditListener bgmEditListener) {
|
||||
mBGMEditListener = bgmEditListener;
|
||||
}
|
||||
|
||||
public void showBGMPanel() {
|
||||
setVisibility(View.VISIBLE);
|
||||
ItemPosition itemPosition = mTuiDataSelectedPosition.get();
|
||||
if (itemPosition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemPosition.bgmTypeIndex != mBGMTypeTabLayout.getSelectedTabPosition()) {
|
||||
Tab tab = mBGMTypeTabLayout.getTabAt(itemPosition.bgmTypeIndex);
|
||||
if (tab != null) {
|
||||
tab.select();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mBGMTypeAdapter != null) {
|
||||
mBGMTypeAdapter.setSelectedBGMItemView(itemPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_bgm_panel_view, this, true);
|
||||
initBgmListView();
|
||||
intBGMEnableView();
|
||||
}
|
||||
|
||||
private void intBGMEnableView() {
|
||||
mBGMEnableCheckBox = findViewById(R.id.bgm_enable);
|
||||
mBGMEnableCheckBox.setButtonDrawable(getCheckBoxButtonDrawable());
|
||||
mBGMEnableCheckBox.setEnabled(false);
|
||||
mBGMEnableCheckBox.setChecked(false);
|
||||
mBGMEnableCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (mBGMEditListener != null) {
|
||||
mBGMEditListener.onMuteBGM(!isChecked);
|
||||
}
|
||||
mTuiDataBGMEnable.set(isChecked);
|
||||
});
|
||||
|
||||
CheckBox sourceAudioEnableCheckBox = findViewById(R.id.source_sound_enable);
|
||||
sourceAudioEnableCheckBox.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> {
|
||||
if (mBGMEditListener != null) {
|
||||
mBGMEditListener.onMuteSourceAudio(!isChecked);
|
||||
}
|
||||
});
|
||||
sourceAudioEnableCheckBox.setButtonDrawable(getCheckBoxButtonDrawable());
|
||||
}
|
||||
|
||||
private Drawable getCheckBoxButtonDrawable() {
|
||||
StateListDrawable stateListDrawable = new StateListDrawable();
|
||||
|
||||
Drawable selectedDrawable = ContextCompat
|
||||
.getDrawable(mContext, R.drawable.multimedia_plugin_edit_bgm_checkbox_selected);
|
||||
DrawableCompat.setTint(selectedDrawable, TUIMultimediaIConfig.getInstance().getThemeColor());
|
||||
|
||||
Drawable unSelectedDrawable = ContextCompat
|
||||
.getDrawable(mContext, R.drawable.multimedia_plugin_edit_bgm_checkbox_unselected);
|
||||
DrawableCompat.setTint(unSelectedDrawable, TUIMultimediaIConfig.getInstance().getThemeColor());
|
||||
|
||||
stateListDrawable.addState(new int[]{android.R.attr.state_checked}, selectedDrawable);
|
||||
stateListDrawable.addState(new int[]{-android.R.attr.state_checked}, unSelectedDrawable);
|
||||
return stateListDrawable;
|
||||
}
|
||||
|
||||
private void initBgmListView() {
|
||||
ViewPager2 beautyPager = findViewById(R.id.bgm_panel_view_pager);
|
||||
mBGMTypeAdapter = new BGMTypeAdapter(mContext, mBGMInfo, mTuiDataSelectedPosition, mTuiDataBGMEnable);
|
||||
beautyPager.setAdapter(mBGMTypeAdapter);
|
||||
|
||||
mBGMTypeTabLayout = findViewById(R.id.bgm_panel_tab_layout);
|
||||
mBGMTypeTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
|
||||
mBGMTypeTabLayout.addOnTabSelectedListener(onTabSelectedListener);
|
||||
new TabLayoutMediator(mBGMTypeTabLayout, beautyPager, this::configTab).attach();
|
||||
}
|
||||
|
||||
private void configTab(@NonNull Tab tab, int position) {
|
||||
if (mBGMInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BGMType typeInfo = mBGMInfo.getBGMType(position);
|
||||
String text = typeInfo != null ? typeInfo.getTypeName()
|
||||
: TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_bgm_type_name_default);
|
||||
|
||||
TextView customTextView = new TextView(getContext());
|
||||
customTextView.setGravity(Gravity.CENTER);
|
||||
|
||||
customTextView.setText(text);
|
||||
customTextView.setMaxLines(1);
|
||||
customTextView.setTextColor(
|
||||
TUIMultimediaResourceUtils.getResources()
|
||||
.getColor(R.color.multimedia_plugin_bgm_type_text_color_normal));
|
||||
|
||||
float textSize = TUIMultimediaResourceUtils.getResources()
|
||||
.getDimension(R.dimen.multimedia_plugin_common_tab_text_size);
|
||||
customTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
|
||||
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
|
||||
customTextView.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
|
||||
tab.setCustomView(customTextView);
|
||||
}
|
||||
|
||||
private void changeTabSelectedStatus(Tab tab, boolean isSelected) {
|
||||
TextView tabTextView = (TextView) tab.getCustomView();
|
||||
if (tabTextView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int textColorResourceId = R.color.multimedia_plugin_bgm_type_text_color_normal;
|
||||
int textColorFontStyle = Typeface.NORMAL;
|
||||
if (isSelected) {
|
||||
textColorResourceId = R.color.multimedia_plugin_bgm_type_text_color_selected;
|
||||
textColorFontStyle = Typeface.BOLD;
|
||||
}
|
||||
|
||||
tabTextView.setTextColor(
|
||||
TUIMultimediaResourceUtils.getResources().getColor(textColorResourceId));
|
||||
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
|
||||
tabTextView.setTypeface(Typeface.create(fontName, textColorFontStyle));
|
||||
}
|
||||
|
||||
private void onBGMItemSelectedChanged(ItemPosition bgmItemPosition) {
|
||||
if (bgmItemPosition == mLastSelectedItemPosition) {
|
||||
return;
|
||||
}
|
||||
setBGM(mBGMInfo.getBGMItem(bgmItemPosition));
|
||||
if (mBGMEnableCheckBox != null) {
|
||||
mBGMEnableCheckBox.setChecked(true);
|
||||
}
|
||||
mTuiDataBGMEnable.set(true);
|
||||
mLastSelectedItemPosition = bgmItemPosition;
|
||||
}
|
||||
|
||||
private void setBGM(BGMItem bgmItem) {
|
||||
if (bgmItem == null) {
|
||||
return;
|
||||
}
|
||||
mBGMEnableCheckBox.setEnabled(true);
|
||||
TUIMultimediaData<String> tuiDataBGMFilePath = new TUIMultimediaData<>("");
|
||||
tuiDataBGMFilePath.observe(path -> {
|
||||
if (mBGMEditListener != null) {
|
||||
mBGMEditListener.onAddBGM(path);
|
||||
}
|
||||
});
|
||||
bgmItem.getBGMFilePath(tuiDataBGMFilePath);
|
||||
|
||||
long bgmStatTime = bgmItem.startTime > 0 ? bgmItem.startTime : 0;
|
||||
long bgmEndTime = bgmItem.endTime > bgmStatTime ? bgmItem.endTime : Integer.MAX_VALUE;
|
||||
if (mBGMEditListener != null) {
|
||||
mBGMEditListener.onCutBGM(bgmStatTime, bgmEndTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void addObserver() {
|
||||
mTuiDataSelectedPosition.observe(this::onBGMItemSelectedChanged);
|
||||
mTuiDataBGMEnable.observe(this::notifyBGMEnable);
|
||||
}
|
||||
|
||||
private void removeObserver() {
|
||||
mBGMTypeTabLayout.removeOnTabSelectedListener(onTabSelectedListener);
|
||||
mTuiDataSelectedPosition.removeObserver(this::onBGMItemSelectedChanged);
|
||||
mTuiDataBGMEnable.removeObserver(this::notifyBGMEnable);
|
||||
}
|
||||
|
||||
private void notifyBGMEnable(boolean enable) {
|
||||
if (mBgmEnableListener != null) {
|
||||
mBgmEnableListener.onBGMEnable(enable);
|
||||
}
|
||||
}
|
||||
|
||||
public interface BGMEnableListener {
|
||||
|
||||
void onBGMEnable(boolean enable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
|
||||
public class BGMSpectrumView extends View {
|
||||
|
||||
private static final int SPECTRUM_POINT_NUM = 20;
|
||||
private static final float[] DEFAULT_FREQUENCY_SPECTRUM = new float[SPECTRUM_POINT_NUM];
|
||||
|
||||
static {
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < DEFAULT_FREQUENCY_SPECTRUM.length; i++) {
|
||||
DEFAULT_FREQUENCY_SPECTRUM[i] = random.nextFloat();
|
||||
}
|
||||
}
|
||||
|
||||
private final float[] mFrequencySpectrumArray = new float[SPECTRUM_POINT_NUM];
|
||||
private Paint mPaint;
|
||||
private float mStrokePreWidth;
|
||||
private float mViewHeight;
|
||||
private Timer mUpdateFrequencyTimer;
|
||||
|
||||
public BGMSpectrumView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public BGMSpectrumView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public BGMSpectrumView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
Arrays.fill(mFrequencySpectrumArray, 0);
|
||||
initPaint();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mUpdateFrequencyTimer != null) {
|
||||
return;
|
||||
}
|
||||
mUpdateFrequencyTimer = new Timer();
|
||||
mUpdateFrequencyTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < mFrequencySpectrumArray.length; i++) {
|
||||
mFrequencySpectrumArray[i] = random.nextFloat();
|
||||
}
|
||||
postInvalidate();
|
||||
}
|
||||
}, 0, 100);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mUpdateFrequencyTimer != null) {
|
||||
mUpdateFrequencyTimer.cancel();
|
||||
mUpdateFrequencyTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setColor(TUIMultimediaIConfig.getInstance().getThemeColor());
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (enabled) {
|
||||
start();
|
||||
mPaint.setColor(TUIMultimediaIConfig.getInstance().getThemeColor());
|
||||
} else {
|
||||
stop();
|
||||
mPaint.setColor(TUIMultimediaResourceUtils.getColor(R.color.multimedia_plugin_bgm_item_spectrum_color_disable));
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
float[] frequencySpectrumArray =
|
||||
mUpdateFrequencyTimer != null ? mFrequencySpectrumArray : DEFAULT_FREQUENCY_SPECTRUM;
|
||||
for (int i = 0; i < frequencySpectrumArray.length; i++) {
|
||||
canvas.drawLine(mStrokePreWidth * i, mViewHeight, mStrokePreWidth * i,
|
||||
mViewHeight - mViewHeight * frequencySpectrumArray[i], mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldW, int oldH) {
|
||||
super.onSizeChanged(w, h, oldW, oldH);
|
||||
float mViewWidth = getWidth();
|
||||
mViewHeight = getHeight();
|
||||
mStrokePreWidth = (mViewWidth / SPECTRUM_POINT_NUM);
|
||||
mPaint.setStrokeWidth(mStrokePreWidth - 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data.BGMInfo.ItemPosition;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class BGMTypeAdapter extends FragmentStateAdapter {
|
||||
|
||||
private final Context mContext;
|
||||
private final BGMInfo mBGMInfo;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition;
|
||||
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable;
|
||||
private final List<BGMTypeItemFragment> mBGMTypeItemFragmentList = new LinkedList<>();
|
||||
|
||||
public BGMTypeAdapter(@NonNull Context context, @NonNull BGMInfo bgmInfo,
|
||||
@NonNull TUIMultimediaData<ItemPosition> tuiDataSelectedPosition, TUIMultimediaData<Boolean> tuiDataBGMEnable) {
|
||||
super((FragmentActivity) context);
|
||||
mBGMInfo = bgmInfo;
|
||||
mContext = context;
|
||||
mTuiDataSelectedPosition = tuiDataSelectedPosition;
|
||||
mTuiDataBGMEnable = tuiDataBGMEnable;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int bgmTypeIndex) {
|
||||
BGMTypeItemFragment fragment = new BGMTypeItemFragment(mContext, mBGMInfo, bgmTypeIndex,
|
||||
mTuiDataSelectedPosition, mTuiDataBGMEnable);
|
||||
if (!mBGMTypeItemFragmentList.contains(fragment)) {
|
||||
mBGMTypeItemFragmentList.add(bgmTypeIndex, fragment);
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mBGMInfo != null ? mBGMInfo.getBGMTypeSize() : 0;
|
||||
}
|
||||
|
||||
public void setSelectedBGMItemView(ItemPosition position) {
|
||||
if (position == null) {
|
||||
return;
|
||||
}
|
||||
if (position.bgmTypeIndex >= 0 && position.bgmTypeIndex < mBGMTypeItemFragmentList.size()) {
|
||||
BGMTypeItemFragment fragment = mBGMTypeItemFragmentList.get(position.bgmTypeIndex);
|
||||
fragment.setSelectedBGMItemView(position.bgmItemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BGMTypeItemFragment extends Fragment {
|
||||
|
||||
private final BGMInfo mBGMInfo;
|
||||
private final Context mContext;
|
||||
private final int mBGMTypeIndex;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedPosition;
|
||||
private final TUIMultimediaData<Boolean> mTuiDataBGMEnable;
|
||||
|
||||
private BGMItemAdapter mAdapter;
|
||||
private ListView mListView;
|
||||
|
||||
public BGMTypeItemFragment(Context context, BGMInfo BGMInfo, int typeIndex,
|
||||
TUIMultimediaData<ItemPosition> tuiDataSelectedPosition, TUIMultimediaData<Boolean> tuiDataBGMEnable) {
|
||||
mContext = context;
|
||||
mBGMInfo = BGMInfo;
|
||||
mBGMTypeIndex = typeIndex;
|
||||
mTuiDataSelectedPosition = tuiDataSelectedPosition;
|
||||
mTuiDataBGMEnable = tuiDataBGMEnable;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.multimedia_plugin_edit_bgm_type_fragment, container, false);
|
||||
mListView = view.findViewById(R.id.bgm_list_view);
|
||||
mAdapter = new BGMItemAdapter(mContext, mBGMInfo, mBGMTypeIndex, mTuiDataSelectedPosition,
|
||||
mTuiDataBGMEnable);
|
||||
mListView.setAdapter(mAdapter);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void setSelectedBGMItemView(int position) {
|
||||
mListView.setSelection(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import java.io.FileWriter;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class BGMInfo {
|
||||
|
||||
@SerializedName("bgm_list")
|
||||
private List<BGMType> mBGMTypeItemList;
|
||||
|
||||
public int getSize() {
|
||||
return mBGMTypeItemList.size();
|
||||
}
|
||||
|
||||
public void addBGMItem(BGMInfo.ItemPosition position, BGMItem bgmItem) {
|
||||
if (position.bgmTypeIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBGMTypeItemList == null) {
|
||||
mBGMTypeItemList = new LinkedList<>();
|
||||
}
|
||||
|
||||
while (position.bgmTypeIndex + 1 - mBGMTypeItemList.size() > 0) {
|
||||
mBGMTypeItemList.add(new BGMType());
|
||||
}
|
||||
mBGMTypeItemList.get(position.bgmTypeIndex).addBGMItem(position.bgmItemIndex, bgmItem);
|
||||
}
|
||||
|
||||
public int getBGMTypeSize() {
|
||||
return mBGMTypeItemList.size();
|
||||
}
|
||||
|
||||
public BGMType getBGMType(int BGMTypeIndex) {
|
||||
if (mBGMTypeItemList == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (BGMTypeIndex < 0 || BGMTypeIndex >= mBGMTypeItemList.size()) {
|
||||
return null;
|
||||
}
|
||||
return mBGMTypeItemList.get(BGMTypeIndex);
|
||||
}
|
||||
|
||||
public BGMItem getBGMItem(BGMInfo.ItemPosition itemPosition) {
|
||||
BGMType typeInfo = getBGMType(itemPosition.bgmTypeIndex);
|
||||
return typeInfo != null ? typeInfo.getBGMItem(itemPosition.bgmItemIndex) : null;
|
||||
}
|
||||
|
||||
public void addBGMType(BGMType bgmType) {
|
||||
if (mBGMTypeItemList == null) {
|
||||
mBGMTypeItemList = new LinkedList<>();
|
||||
}
|
||||
|
||||
mBGMTypeItemList.add(bgmType);
|
||||
}
|
||||
|
||||
public void append(BGMInfo otherSetInfo) {
|
||||
if (otherSetInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<BGMType> otherBGMTypeItemList = otherSetInfo.mBGMTypeItemList;
|
||||
if (otherBGMTypeItemList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < otherBGMTypeItemList.size(); i++) {
|
||||
BGMType typeItemInfo = otherBGMTypeItemList.get(i);
|
||||
if (typeItemInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<BGMItem> itemInfoList = typeItemInfo.getBGMItemList();
|
||||
if (itemInfoList == null) {
|
||||
continue;
|
||||
}
|
||||
for (BGMItem itemInfo : itemInfoList) {
|
||||
addBGMItem(new ItemPosition(i, 0), itemInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void saveToJsonFile(String filePath) {
|
||||
Gson gson = new Gson();
|
||||
try {
|
||||
FileWriter fileWriter = new FileWriter(filePath);
|
||||
gson.toJson(this, fileWriter);
|
||||
fileWriter.flush();
|
||||
fileWriter.close();
|
||||
} catch (Exception e) {
|
||||
LiteavLog.e("save json file error", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemPosition {
|
||||
|
||||
public final int bgmTypeIndex;
|
||||
public final int bgmItemIndex;
|
||||
|
||||
public ItemPosition(int bgmTypeIndex, int bgmItemIndex) {
|
||||
this.bgmTypeIndex = bgmTypeIndex;
|
||||
this.bgmItemIndex = bgmItemIndex;
|
||||
}
|
||||
|
||||
public boolean equals(BGMInfo.ItemPosition other) {
|
||||
if (other == null) {
|
||||
return false;
|
||||
}
|
||||
return bgmTypeIndex == other.bgmTypeIndex && bgmItemIndex == other.bgmItemIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data;
|
||||
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.os.Looper;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.liteav.base.util.CustomHandler;
|
||||
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaCommonThreadPool;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BGMItem {
|
||||
|
||||
private static final String BGM_CACHE_FILE_DIR = "/multimediaBGM/";
|
||||
|
||||
@SerializedName("item_bgm_path")
|
||||
public String bgmFilePath;
|
||||
|
||||
@SerializedName("item_bgm_name")
|
||||
public String bgmName;
|
||||
@SerializedName("item_start_time")
|
||||
public long startTime;
|
||||
@SerializedName("item_end_time")
|
||||
public long endTime;
|
||||
public Long duration;
|
||||
@SerializedName("item_bgm_author")
|
||||
private String bgmAuthorName;
|
||||
|
||||
public String getBGMAuthorName() {
|
||||
return bgmAuthorName;
|
||||
}
|
||||
|
||||
public String getBgmName() {
|
||||
return bgmName;
|
||||
}
|
||||
|
||||
public void getBGMFilePath(TUIMultimediaData<String> tuiDataBGMFilePath) {
|
||||
if (bgmFilePath.isEmpty()) {
|
||||
tuiDataBGMFilePath.set(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSupportDirectReadFile(bgmFilePath)) {
|
||||
tuiDataBGMFilePath.set(bgmFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
String bgmCacheFilePath = getBGMCacheFilePath(bgmFilePath);
|
||||
if (TUIMultimediaFileUtil.isFileExists(bgmCacheFilePath)) {
|
||||
tuiDataBGMFilePath.set(bgmCacheFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomHandler handler = new CustomHandler(Looper.myLooper());
|
||||
TUIMultimediaCommonThreadPool.getThreadExecutor().submit(() -> {
|
||||
String filePath = copyFileToBGMCache(bgmFilePath);
|
||||
handler.post(() -> {
|
||||
tuiDataBGMFilePath.set(filePath);
|
||||
bgmFilePath = filePath;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void getBGMDuration(TUIMultimediaData<Long> tuiDataBGMDuration) {
|
||||
if (duration != null) {
|
||||
tuiDataBGMDuration.set(duration);
|
||||
return;
|
||||
}
|
||||
|
||||
CustomHandler handler = new CustomHandler(Looper.myLooper());
|
||||
|
||||
TUIMultimediaData<String> tuiDataBGMFilePath = new TUIMultimediaData<>("");
|
||||
tuiDataBGMFilePath.observe(bgmFilePath -> {
|
||||
TUIMultimediaCommonThreadPool.getThreadExecutor().submit(() -> {
|
||||
duration = getAudioDuration(bgmFilePath);
|
||||
handler.post(() -> tuiDataBGMDuration.set(duration));
|
||||
});
|
||||
});
|
||||
getBGMFilePath(tuiDataBGMFilePath);
|
||||
}
|
||||
|
||||
public boolean isSameBGM(BGMItem bgmItem) {
|
||||
return bgmItem.bgmAuthorName.equals(bgmAuthorName)
|
||||
&& bgmItem.bgmName.equals(bgmName)
|
||||
&& bgmItem.bgmFilePath.equals(bgmAuthorName);
|
||||
}
|
||||
|
||||
private boolean isSupportDirectReadFile(String path) {
|
||||
if (path.startsWith(TUIMultimediaFileUtil.ASSET_FILE_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String copyFileToBGMCache(String path) {
|
||||
File f = new File(getBGMCacheFileDir());
|
||||
boolean isSuccess = true;
|
||||
if (!f.exists()) {
|
||||
isSuccess = f.mkdirs();
|
||||
}
|
||||
if (!isSuccess) {
|
||||
return path;
|
||||
}
|
||||
|
||||
String cacheFilePath = getBGMCacheFilePath(path);
|
||||
if (path.startsWith(TUIMultimediaFileUtil.ASSET_FILE_PREFIX)) {
|
||||
isSuccess = TUIMultimediaFileUtil
|
||||
.copyAssetsFile(path.substring(TUIMultimediaFileUtil.ASSET_FILE_PREFIX.length()), cacheFilePath);
|
||||
return isSuccess ? cacheFilePath : path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private String getBGMCacheFilePath(String srcBGMFilePath) {
|
||||
return getBGMCacheFileDir() + Integer.toHexString(srcBGMFilePath.hashCode());
|
||||
}
|
||||
|
||||
private String getBGMCacheFileDir() {
|
||||
return TUIConfig.getDefaultAppDir() + BGM_CACHE_FILE_DIR;
|
||||
}
|
||||
|
||||
private long getAudioDuration(String audioFilePath) {
|
||||
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
|
||||
try {
|
||||
metadataRetriever.setDataSource(audioFilePath);
|
||||
String durationStr = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
|
||||
return Long.parseLong(durationStr);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
metadataRetriever.release();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.bgm.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class BGMType {
|
||||
|
||||
@SerializedName("bgm_type_name")
|
||||
private String mTypeName;
|
||||
|
||||
@SerializedName("bgm_item_list")
|
||||
private List<BGMItem> mBGMItemList;
|
||||
|
||||
public void addBGMItem(int itemIndex, BGMItem bgmItem) {
|
||||
if (itemIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mBGMItemList == null) {
|
||||
mBGMItemList = new LinkedList<>();
|
||||
}
|
||||
|
||||
mBGMItemList.add(itemIndex, bgmItem);
|
||||
}
|
||||
|
||||
public boolean isContainSameBGM(BGMItem bgmItem) {
|
||||
if (bgmItem == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mBGMItemList == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (BGMItem item : mBGMItemList) {
|
||||
if (bgmItem.isSameBGM(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public BGMItem getBGMItem(int itemIndex) {
|
||||
if (itemIndex < 0 || itemIndex >= mBGMItemList.size()) {
|
||||
return null;
|
||||
}
|
||||
return mBGMItemList.get(itemIndex);
|
||||
}
|
||||
|
||||
public List<BGMItem> getBGMItemList() {
|
||||
return mBGMItemList;
|
||||
}
|
||||
|
||||
public int getItemSize() {
|
||||
return mBGMItemList.size();
|
||||
}
|
||||
|
||||
public String getTypeName() {
|
||||
if (mTypeName.startsWith(TUIMultimediaResourceUtils.STRING_RESOURCE_PREFIX)) {
|
||||
return TUIMultimediaResourceUtils.getString(mTypeName);
|
||||
}
|
||||
return mTypeName;
|
||||
}
|
||||
|
||||
public void setTypeName(String typeName) {
|
||||
mTypeName = typeName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.widget.RelativeLayout;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushToolView.BrushToolListener;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXRect;
|
||||
|
||||
public class BrushControlView {
|
||||
|
||||
public final TUIMultimediaData<Boolean> tuiDataIsDrawing = new TUIMultimediaData<>(false);
|
||||
private final String TAG = BrushControlView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final RelativeLayout mBrushToolViewContainer;
|
||||
|
||||
private BrushDrawView mBrushDrawView;
|
||||
private BrushToolView mBrushToolView;
|
||||
private Boolean mEnableDraw = false;
|
||||
private BrushMode mBrushMode = BrushMode.GRAFFITI;
|
||||
|
||||
public enum BrushMode {
|
||||
GRAFFITI,
|
||||
MOSAIC
|
||||
}
|
||||
|
||||
public BrushControlView(Context context,
|
||||
RelativeLayout brushToolViewContainer) {
|
||||
mBrushToolViewContainer = brushToolViewContainer;
|
||||
mContext = context;
|
||||
initView();
|
||||
}
|
||||
|
||||
public void enableDraw(boolean enable) {
|
||||
LiteavLog.i(TAG, (enable ? " enable" : "disEnable") + " draw");
|
||||
mEnableDraw = enable;
|
||||
mBrushDrawView.enableDraw(enable);
|
||||
}
|
||||
|
||||
public void showToolView(boolean show) {
|
||||
mBrushToolViewContainer.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void showDrawView(boolean show) {
|
||||
mBrushDrawView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
public void setBrushMode(BrushMode brushMode) {
|
||||
mBrushMode = brushMode;
|
||||
mBrushDrawView.setBrushMode(mBrushMode);
|
||||
mBrushToolView.setBrushMode(mBrushMode);
|
||||
mBrushToolView.enableRedo(mBrushDrawView.canRedo());
|
||||
mBrushToolView.enableUndo(mBrushDrawView.canUndo());
|
||||
}
|
||||
|
||||
public void setMosaicImage(Bitmap bitmap) {
|
||||
mBrushDrawView.setMosaicImage(bitmap);
|
||||
}
|
||||
|
||||
public BrushMode getBrushMode() {
|
||||
return mBrushMode;
|
||||
}
|
||||
|
||||
public boolean isEnableDraw() {
|
||||
return mEnableDraw;
|
||||
}
|
||||
|
||||
public TUIMultimediaPasterInfo getPaster() {
|
||||
Bitmap image = mBrushDrawView.getDrawingBitmap();
|
||||
if (image == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TUIMultimediaPasterInfo pasterInfo = new TUIMultimediaPasterInfo();
|
||||
TXRect frame = new TXRect();
|
||||
frame.width = image.getWidth();
|
||||
frame.x = 0;
|
||||
frame.y = 0;
|
||||
pasterInfo.frame = frame;
|
||||
pasterInfo.image = image;
|
||||
pasterInfo.pasterType = PasterType.STATIC_PICTURE_PASTER;
|
||||
return pasterInfo;
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
mBrushDrawView.clean();
|
||||
}
|
||||
|
||||
public View getBrushDrawView() {
|
||||
return mBrushDrawView;
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
mBrushToolView = new BrushToolView(mContext);
|
||||
mBrushToolViewContainer.removeAllViews();
|
||||
mBrushToolViewContainer.addView(mBrushToolView, new LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
|
||||
|
||||
mBrushDrawView = new BrushDrawView(mContext, mBrushToolView.mTuiDataSelectedTextColor,
|
||||
tuiDataIsDrawing);
|
||||
|
||||
tuiDataIsDrawing.observe(isDrawing -> updateToolStatus());
|
||||
|
||||
mBrushToolView.setBrushToolListener(new BrushToolListener() {
|
||||
@Override
|
||||
public void redo() {
|
||||
mBrushDrawView.redo();
|
||||
updateToolStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
mBrushDrawView.undo();
|
||||
updateToolStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateToolStatus() {
|
||||
mBrushToolView.enableRedo(mBrushDrawView.canRedo());
|
||||
mBrushToolView.enableUndo(mBrushDrawView.canUndo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.liteav.base.util.Size;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaGraphComputerUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView.BrushMode;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class BrushDrawView extends View {
|
||||
|
||||
private final String TAG = BrushDrawView.class + "_" + hashCode();
|
||||
|
||||
private static final int GRAFFITI_STROKE_WIDTH = 16;
|
||||
private static final int MOSAIC_STROKE_WIDTH = 40;
|
||||
private static final int MIN_MOVE_DST_PX = 10;
|
||||
|
||||
private BrushMode mBrushMode = BrushMode.GRAFFITI;
|
||||
private final Stack<DrawPath> mGraffitiPaths = new Stack<>();
|
||||
private final Stack<DrawPath> mEraseGraffitiPaths = new Stack<>();
|
||||
private final Stack<DrawPath> mMosaicPaths = new Stack<>();
|
||||
private final Stack<DrawPath> mEraseMosaicPaths = new Stack<>();
|
||||
private final TUIMultimediaData<Integer> mTuiDataPaintColor;
|
||||
private final TUIMultimediaData<Boolean> mTuiDataIsDrawing;
|
||||
|
||||
private Paint mPathPaint;
|
||||
private Paint mMosaicImagePaint;
|
||||
private Bitmap mMosaicImage;
|
||||
|
||||
private List<PointF> mCurrentPointList;
|
||||
private boolean mEnableDraw = true;
|
||||
private boolean mIsMotionActionMove = false;
|
||||
private PointF mActionDownPoint;
|
||||
|
||||
public BrushDrawView(Context context, TUIMultimediaData<Integer> tuiDataPaintColor,
|
||||
TUIMultimediaData<Boolean> tuiDataIsDrawing) {
|
||||
super(context);
|
||||
mTuiDataPaintColor = tuiDataPaintColor;
|
||||
mTuiDataPaintColor.observe(color -> {
|
||||
if (mPathPaint != null) {
|
||||
mPathPaint.setColor(color);
|
||||
}
|
||||
});
|
||||
mTuiDataIsDrawing = tuiDataIsDrawing;
|
||||
setupPathPaint();
|
||||
}
|
||||
|
||||
public void enableDraw(boolean enable) {
|
||||
mEnableDraw = enable;
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
mGraffitiPaths.clear();
|
||||
mEraseGraffitiPaths.clear();
|
||||
|
||||
mMosaicPaths.clear();
|
||||
mEraseMosaicPaths.clear();
|
||||
|
||||
if (mCurrentPointList != null) {
|
||||
mCurrentPointList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void setMosaicImage(Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
LiteavLog.i(TAG,"source picture is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMosaicImage != null) {
|
||||
mMosaicImage.recycle();
|
||||
}
|
||||
|
||||
int w = Math.max(Math.round(bitmap.getWidth() / 40f), 8);
|
||||
int h = Math.max(Math.round(bitmap.getHeight() / 40f), 8);
|
||||
mMosaicImage = Bitmap.createScaledBitmap(bitmap, w, h, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
drawMosaic(canvas);
|
||||
drawGraffiti(canvas);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
LiteavLog.i(TAG, "onTouchEvent");
|
||||
if (!mEnableDraw) {
|
||||
performClick();
|
||||
return false;
|
||||
}
|
||||
|
||||
float touchX = event.getX();
|
||||
float touchY = event.getY();
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mCurrentPointList = new LinkedList<>();
|
||||
mCurrentPointList.add(new PointF(touchX, touchY));
|
||||
mActionDownPoint = new PointF(touchX, touchY);
|
||||
mIsMotionActionMove = false;
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (TUIMultimediaGraphComputerUtils.distance4PointF(mActionDownPoint, new PointF(touchX, touchY))
|
||||
> MIN_MOVE_DST_PX) {
|
||||
mTuiDataIsDrawing.set(true);
|
||||
mIsMotionActionMove = true;
|
||||
mCurrentPointList.add(new PointF(touchX, touchY));
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (mIsMotionActionMove) {
|
||||
if (mBrushMode == BrushMode.MOSAIC) {
|
||||
mMosaicPaths.add(new DrawPath(
|
||||
mCurrentPointList, mTuiDataPaintColor.get(), new Size(getWidth(), getHeight())));
|
||||
mEraseMosaicPaths.clear();
|
||||
} else {
|
||||
mGraffitiPaths.add(new DrawPath(
|
||||
mCurrentPointList, mTuiDataPaintColor.get(), new Size(getWidth(), getHeight())));
|
||||
mEraseGraffitiPaths.clear();
|
||||
}
|
||||
} else {
|
||||
mTuiDataIsDrawing.set(!mTuiDataIsDrawing.get());
|
||||
}
|
||||
mCurrentPointList = null;
|
||||
break;
|
||||
}
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
public Bitmap getDrawingBitmap() {
|
||||
if (mGraffitiPaths.empty() && mMosaicPaths.empty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RectF rect = new RectF(0, 0, getWidth(), getHeight());
|
||||
Bitmap canvasBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(canvasBitmap);
|
||||
|
||||
if (!mMosaicPaths.empty() && mMosaicImage != null) {
|
||||
int layerCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);
|
||||
drawPathList(canvas, mPathPaint, mMosaicPaths, MOSAIC_STROKE_WIDTH);
|
||||
mMosaicImagePaint.setFilterBitmap(false);
|
||||
canvas.drawBitmap(mMosaicImage, null, rect, mMosaicImagePaint);
|
||||
canvas.restoreToCount(layerCount);
|
||||
}
|
||||
|
||||
if (!mGraffitiPaths.empty()) {
|
||||
drawPathList(canvas, mPathPaint, mGraffitiPaths, GRAFFITI_STROKE_WIDTH);
|
||||
}
|
||||
return canvasBitmap;
|
||||
}
|
||||
|
||||
public void setBrushMode(BrushMode brushMode) {
|
||||
mBrushMode = brushMode;
|
||||
}
|
||||
|
||||
public void undo() {
|
||||
if (mBrushMode == BrushMode.GRAFFITI && !mGraffitiPaths.empty()) {
|
||||
mEraseGraffitiPaths.push(mGraffitiPaths.pop());
|
||||
} else if (mBrushMode == BrushMode.MOSAIC && !mMosaicPaths.empty()) {
|
||||
mEraseMosaicPaths.push(mMosaicPaths.pop());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void redo() {
|
||||
if (mBrushMode == BrushMode.GRAFFITI && !mEraseGraffitiPaths.empty()) {
|
||||
mGraffitiPaths.push(mEraseGraffitiPaths.pop());
|
||||
} else if (mBrushMode == BrushMode.MOSAIC && !mEraseMosaicPaths.empty()) {
|
||||
mMosaicPaths.push(mEraseMosaicPaths.pop());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
invalidate();
|
||||
|
||||
if (mEraseGraffitiPaths.empty()) {
|
||||
return;
|
||||
}
|
||||
mGraffitiPaths.push(mEraseGraffitiPaths.pop());
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean canUndo() {
|
||||
return mBrushMode == BrushMode.GRAFFITI ? !mGraffitiPaths.empty() : !mMosaicPaths.empty();
|
||||
}
|
||||
|
||||
public boolean canRedo() {
|
||||
return mBrushMode == BrushMode.GRAFFITI ? !mEraseGraffitiPaths.empty() : !mEraseMosaicPaths.empty();
|
||||
}
|
||||
|
||||
private void setupPathPaint() {
|
||||
if (mPathPaint != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPathPaint = new Paint();
|
||||
mPathPaint.setColor(mTuiDataPaintColor.get());
|
||||
mPathPaint.setAntiAlias(true);
|
||||
mPathPaint.setStrokeWidth(GRAFFITI_STROKE_WIDTH);
|
||||
mPathPaint.setStyle(Paint.Style.STROKE);
|
||||
mPathPaint.setStrokeJoin(Paint.Join.ROUND);
|
||||
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
}
|
||||
|
||||
private void setupMosaicImagePaint() {
|
||||
if (mMosaicImagePaint != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMosaicImagePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mMosaicImagePaint.setFilterBitmap(false);
|
||||
mMosaicImagePaint.setStrokeWidth(MOSAIC_STROKE_WIDTH);
|
||||
mMosaicImagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||
}
|
||||
|
||||
private void drawGraffiti(Canvas canvas) {
|
||||
if (mBrushMode != BrushMode.GRAFFITI && mGraffitiPaths.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RectF rect = new RectF(0, 0, getWidth(), getHeight());
|
||||
int layerCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);
|
||||
drawPathList(canvas, mPathPaint, mGraffitiPaths, GRAFFITI_STROKE_WIDTH);
|
||||
if (mBrushMode == BrushMode.GRAFFITI) {
|
||||
mPathPaint.setColor(mTuiDataPaintColor.get());
|
||||
drawCurrentPoint(canvas, mPathPaint, GRAFFITI_STROKE_WIDTH);
|
||||
}
|
||||
canvas.restoreToCount(layerCount);
|
||||
}
|
||||
|
||||
private void drawMosaic(Canvas canvas) {
|
||||
if (mBrushMode != BrushMode.MOSAIC && mMosaicPaths.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setupMosaicImagePaint();
|
||||
RectF rect = new RectF(0, 0, getWidth(), getHeight());
|
||||
int layerCount = canvas.saveLayer(rect, null, Canvas.ALL_SAVE_FLAG);
|
||||
if (mBrushMode == BrushMode.MOSAIC) {
|
||||
drawCurrentPoint(canvas, mPathPaint, MOSAIC_STROKE_WIDTH);
|
||||
}
|
||||
drawPathList(canvas, mPathPaint, mMosaicPaths, MOSAIC_STROKE_WIDTH);
|
||||
if (mMosaicImage != null) {
|
||||
mMosaicImagePaint.setFilterBitmap(false);
|
||||
canvas.drawBitmap(mMosaicImage, null, rect, mMosaicImagePaint);
|
||||
}
|
||||
canvas.restoreToCount(layerCount);
|
||||
}
|
||||
|
||||
private void drawCurrentPoint(Canvas canvas, Paint paint, int strokeWidth) {
|
||||
if (mCurrentPointList != null) {
|
||||
paint.setStrokeWidth(strokeWidth);
|
||||
canvas.drawPath(getPathFromPoints(mCurrentPointList, 1.0f), paint);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawPathList(Canvas canvas, Paint paint, Stack<DrawPath> paths, int paintWidth) {
|
||||
for (DrawPath drawPath : paths) {
|
||||
double originSize = Math.sqrt(drawPath.originViewSize.width * drawPath.originViewSize.width +
|
||||
drawPath.originViewSize.height * drawPath.originViewSize.height);
|
||||
double size = Math.sqrt(getWidth() * getWidth() + getHeight() * getHeight());
|
||||
float scale = (float) (size / originSize);
|
||||
paint.setStrokeWidth((int) (paintWidth * scale));
|
||||
paint.setColor(drawPath.color);
|
||||
canvas.drawPath(getPathFromPoints(drawPath.pathPoints, scale), paint);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getPathFromPoints(List<PointF> pathPoints, float scale) {
|
||||
Path path = new Path();
|
||||
for (int i = 0; i < pathPoints.size(); i++) {
|
||||
if (i == 0) {
|
||||
path.moveTo(pathPoints.get(i).x * scale, pathPoints.get(i).y * scale);
|
||||
}
|
||||
path.lineTo(pathPoints.get(i).x * scale, pathPoints.get(i).y * scale);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private static class DrawPath {
|
||||
|
||||
private final List<PointF> pathPoints;
|
||||
int color;
|
||||
Size originViewSize;
|
||||
|
||||
public DrawPath(List<PointF> pathPoints, int color, Size size) {
|
||||
this.pathPoints = pathPoints;
|
||||
this.color = color;
|
||||
this.originViewSize = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.RelativeLayout;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIHorizontalScrollView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.brush.BrushControlView.BrushMode;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster.SubtitleColorItemAdapter;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class BrushToolView extends RelativeLayout {
|
||||
|
||||
public final TUIMultimediaData<Integer> mTuiDataSelectedTextColor = new TUIMultimediaData<>(Color.RED);
|
||||
private final String TAG = BrushToolView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private BrushToolListener mListener;
|
||||
private BrushMode mBrushMode;
|
||||
private TUIHorizontalScrollView mColorSelectScrollItemView;
|
||||
|
||||
public BrushToolView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void setBrushToolListener(BrushToolListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
removeAllViews();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
public void setBrushMode(BrushMode brushMode) {
|
||||
mBrushMode = brushMode;
|
||||
mColorSelectScrollItemView.setVisibility(brushMode != BrushMode.MOSAIC ? VISIBLE : GONE);
|
||||
}
|
||||
|
||||
public void enableRedo(boolean enable) {
|
||||
findViewById(R.id.graffiti_operation_redo).setEnabled(enable);
|
||||
}
|
||||
|
||||
public void enableUndo(boolean enable) {
|
||||
findViewById(R.id.graffiti_operation_undo).setEnabled(enable);
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_graffiti_operation_view,
|
||||
this, true);
|
||||
|
||||
mColorSelectScrollItemView = findViewById(R.id.graffiti_paster_color_select_view);
|
||||
mColorSelectScrollItemView.setAdapter(new SubtitleColorItemAdapter(mContext, mTuiDataSelectedTextColor));
|
||||
if (mBrushMode == BrushMode.MOSAIC) {
|
||||
mColorSelectScrollItemView.setVisibility(GONE);
|
||||
}
|
||||
|
||||
findViewById(R.id.graffiti_operation_redo).setEnabled(false);
|
||||
findViewById(R.id.graffiti_operation_redo).setOnClickListener((v) -> {
|
||||
if (mListener != null) {
|
||||
mListener.redo();
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.graffiti_operation_undo).setEnabled(false);
|
||||
findViewById(R.id.graffiti_operation_undo).setOnClickListener((v) -> {
|
||||
if (mListener != null) {
|
||||
mListener.undo();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface BrushToolListener {
|
||||
void redo();
|
||||
void undo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.RelativeLayout;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaTransformLayout;
|
||||
|
||||
public class PictureCropControlView extends RelativeLayout implements PictureCropView.CropListener {
|
||||
private final String TAG = PictureCropControlView.class + "_" + hashCode();
|
||||
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaTransformLayout mPreviewLayout;
|
||||
|
||||
private int mRotation = 0;
|
||||
private PictureCropView mPictureCropView;
|
||||
private PictureCropControlViewListener mCropControlViewListener;
|
||||
private Button mRestoreCropButton;
|
||||
|
||||
public interface PictureCropControlViewListener {
|
||||
void onCancelCrop();
|
||||
|
||||
void onConfirmCrop(RectF rectF, int rotation);
|
||||
|
||||
void onRotation(int rotation);
|
||||
}
|
||||
|
||||
public PictureCropControlView(Context context, TUIMultimediaTransformLayout previewLayout) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mPreviewLayout = previewLayout;
|
||||
}
|
||||
|
||||
public void setCropMainViewListener(PictureCropControlViewListener cropMainViewListener) {
|
||||
mCropControlViewListener = cropMainViewListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
View mRootView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.multimedia_plugin_edit_picture_crop_view, this);
|
||||
mPictureCropView = new PictureCropView(mContext, mPreviewLayout.getInitContentLayout());
|
||||
mPictureCropView.setCropListener(this);
|
||||
((RelativeLayout) mRootView).addView(mPictureCropView, 0);
|
||||
|
||||
mRootView.setOnTouchListener((view, motionEvent) -> false);
|
||||
|
||||
mRestoreCropButton = mRootView.findViewById(R.id.crop_restore);
|
||||
mRestoreCropButton.setOnClickListener(view -> onRestoreCrop());
|
||||
mRestoreCropButton.setEnabled(false);
|
||||
|
||||
mRootView.findViewById(R.id.crop_ok).setOnClickListener(view -> onConfirmCrop());
|
||||
mRootView.findViewById(R.id.crop_cancel).setOnClickListener(view -> onCancelCrop());
|
||||
mRootView.findViewById(R.id.crop_rotation).setOnClickListener(view -> onRotation());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
updateCropMarkViewLayoutParams();
|
||||
}
|
||||
|
||||
private void updateCropMarkViewLayoutParams() {
|
||||
if (mPictureCropView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RelativeLayout.LayoutParams layoutParams = (LayoutParams) mPictureCropView.getLayoutParams();
|
||||
layoutParams.topMargin = 0;
|
||||
layoutParams.leftMargin = 0;
|
||||
layoutParams.height = getHeight();
|
||||
layoutParams.width = getWidth();
|
||||
|
||||
LiteavLog.i(TAG, " width = " + layoutParams.width + " height = " + layoutParams.height);
|
||||
mPictureCropView.setLayoutParams(layoutParams);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCrop(float scaleCenterX, float scaleCenterY, float scale, int moveX, int moveY) {
|
||||
if (mPreviewLayout != null) {
|
||||
mPreviewLayout.setLimitRect(mPictureCropView.getCropRect());
|
||||
mPreviewLayout.scaleContent(scale, new PointF(scaleCenterX, scaleCenterY));
|
||||
mPreviewLayout.moveContent(moveX, moveY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouch() {
|
||||
mRestoreCropButton.setEnabled(isNeedCropOrRotation());
|
||||
}
|
||||
|
||||
private void onRestoreCrop() {
|
||||
mPreviewLayout.reset();
|
||||
mPictureCropView.resetCropRect();
|
||||
if (mCropControlViewListener != null && mRotation != 0) {
|
||||
mCropControlViewListener.onRotation(0);
|
||||
}
|
||||
mRotation = 0;
|
||||
mRestoreCropButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void onRotation() {
|
||||
Rect corpRectInMarkView = mPictureCropView.getCropRect();
|
||||
mPreviewLayout.rotation90Content(new Point(corpRectInMarkView.centerX(), corpRectInMarkView.centerY()));
|
||||
mPictureCropView.rotation90();
|
||||
mRotation = (mRotation + 90 + 360) % 360;
|
||||
if (mCropControlViewListener != null) {
|
||||
mCropControlViewListener.onRotation(mRotation);
|
||||
}
|
||||
mRestoreCropButton.setEnabled(isNeedCropOrRotation());
|
||||
}
|
||||
|
||||
private void onCancelCrop() {
|
||||
if (mCropControlViewListener != null) {
|
||||
mCropControlViewListener.onCancelCrop();
|
||||
}
|
||||
}
|
||||
|
||||
private void onConfirmCrop() {
|
||||
LiteavLog.i(TAG, "on confirm crop.");
|
||||
if (!isNeedCropOrRotation()) {
|
||||
LiteavLog.i(TAG,"do not need crop or rotation.");
|
||||
onCancelCrop();
|
||||
}
|
||||
|
||||
RectF normalizedCropRect = getNormalizedCropRect();
|
||||
if (mCropControlViewListener != null) {
|
||||
mCropControlViewListener.onConfirmCrop(normalizedCropRect, mRotation);
|
||||
}
|
||||
}
|
||||
|
||||
// private Size getCropPictureSize(RectF normalizedCropRect) {
|
||||
// Size srcBitmapSize = TUIMultimediaPictureEditorCore.getInstance(mContext).getBitmapSize();
|
||||
// int outSizeWidth = Math.min(1080, srcBitmapSize.width);
|
||||
// int outSizeHeight = (int) (outSizeWidth * normalizedCropRect.height() / normalizedCropRect.width());
|
||||
// if (mRotation == 90 || mRotation == 270) {
|
||||
// return new Size(outSizeHeight, outSizeWidth);
|
||||
// } else {
|
||||
// return new Size(outSizeWidth, outSizeHeight);
|
||||
// }
|
||||
// }
|
||||
|
||||
private RectF getNormalizedCropRect() {
|
||||
Rect corpRect = mPictureCropView.getCropRect();
|
||||
Rect previewRect = mPreviewLayout.getContentRect();
|
||||
|
||||
Rect cropRectInMedia = new Rect(corpRect.left - previewRect.left,
|
||||
corpRect.top - previewRect.top,
|
||||
corpRect.right - previewRect.left,
|
||||
corpRect.bottom - previewRect.top);
|
||||
|
||||
int mediaWidth = previewRect.right - previewRect.left;
|
||||
int mediaHeight = previewRect.bottom - previewRect.top;
|
||||
RectF cropRectF = new RectF();
|
||||
|
||||
cropRectF.left = cropRectInMedia.left * 1.0f / mediaWidth;
|
||||
cropRectF.right = cropRectInMedia.right * 1.0f / mediaWidth;
|
||||
cropRectF.top = cropRectInMedia.top * 1.0f / mediaHeight;
|
||||
cropRectF.bottom = cropRectInMedia.bottom * 1.0f / mediaHeight;
|
||||
|
||||
if (mRotation == 90) {
|
||||
float left = cropRectF.left;
|
||||
float right = cropRectF.right;
|
||||
cropRectF.left = cropRectF.top;
|
||||
cropRectF.right = cropRectF.bottom;
|
||||
cropRectF.top = 1 - right;
|
||||
cropRectF.bottom = 1 - left;
|
||||
} else if (mRotation == 180) {
|
||||
cropRectF.top = 1 - cropRectF.bottom;
|
||||
cropRectF.bottom = 1 - cropRectF.top;
|
||||
} else if (mRotation == 270) {
|
||||
float left = cropRectF.left;
|
||||
float right = cropRectF.right;
|
||||
cropRectF.left = 1 - cropRectF.bottom;
|
||||
cropRectF.right = 1 - cropRectF.top;
|
||||
cropRectF.top = left;
|
||||
cropRectF.bottom = right;
|
||||
}
|
||||
return cropRectF;
|
||||
}
|
||||
|
||||
private boolean isNeedCropOrRotation() {
|
||||
if ((mRotation + 360) % 360 != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Rect corpRect = mPictureCropView.getCropRect();
|
||||
Rect previewRect = mPreviewLayout.getContentRect();
|
||||
return !isApproximateRect(corpRect, previewRect);
|
||||
}
|
||||
|
||||
private boolean isApproximateRect(Rect rect1, Rect rect2) {
|
||||
return Math.abs(rect1.left - rect2.left) < 2 && Math.abs(rect1.right - rect2.right) < 2 &&
|
||||
Math.abs(rect1.top - rect2.top) < 2 && Math.abs(rect1.bottom - rect2.bottom) < 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.crop;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import com.tencent.liteav.base.ThreadUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
|
||||
public class PictureCropView extends View {
|
||||
private final static int RECT_LINE_WIDTH = 3;
|
||||
private final static int GRID_LINE_WIDTH = 1;
|
||||
private final static int RECT_CORNER_LINE_WIDTH = 10;
|
||||
private final static int CORNER_LENGTH = 50;
|
||||
private final static int TOUCH_DISTANCE = 50;
|
||||
|
||||
private final static int CANCEL_BACKGROUND_TRANSPARENT_DELAY_TIME = 2000;
|
||||
private final static int CANCEL_DRAW_GRID_DELAY_TIME = 4000;
|
||||
|
||||
private final static int LIMIT_LEFT_DP = 30;
|
||||
private final static int LIMIT_BOTTOM_DP = 100;
|
||||
private final static int LIMIT_TOP_DP = 15;
|
||||
|
||||
private final Rect mOriginPreviewRect;
|
||||
private final Rect mMaxCropRect = new Rect();
|
||||
private final Rect mCropRect = new Rect();
|
||||
|
||||
boolean mIsMoveLeft = false;
|
||||
boolean mIsMoveRight = false;
|
||||
boolean mIsMoveTop = false;
|
||||
boolean mIsMoveBottom = false;
|
||||
|
||||
private Paint mCropRectDrawPaint;
|
||||
private Paint mBackGroundDrawPaint;
|
||||
private Path mCropRectPath;
|
||||
private CropListener mCropListener;
|
||||
private Boolean mIsDrawGrid = false;
|
||||
|
||||
Runnable mCancelBackGroundTransparentRunnable = () -> {
|
||||
mBackGroundDrawPaint.setColor(Color.argb(255, 0, 0, 0));
|
||||
invalidate();
|
||||
};
|
||||
|
||||
Runnable mCancelDrawGridRunnable = () -> {
|
||||
mIsDrawGrid = false;
|
||||
invalidate();
|
||||
};
|
||||
|
||||
public interface CropListener {
|
||||
void onTouch();
|
||||
void onCrop(float scaleCenterX, float scaleCenterY, float scale, int moveX, int moveY);
|
||||
}
|
||||
|
||||
public PictureCropView(Context context, Rect originPreviewRect) {
|
||||
super(context);
|
||||
mOriginPreviewRect = originPreviewRect;
|
||||
setupDrawing();
|
||||
}
|
||||
|
||||
public void setCropListener(CropListener cropListener) {
|
||||
mCropListener = cropListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b);
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
initMaxCropRect();
|
||||
initCropRect();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void resetCropRect() {
|
||||
initCropRect();
|
||||
delayCancelBackGroundTransparent();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
drawBackGround(canvas);
|
||||
drawCropRect(canvas);
|
||||
drawCorner(canvas);
|
||||
if (mIsDrawGrid) {
|
||||
drawGrid(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (mCropListener != null) {
|
||||
mCropListener.onTouch();
|
||||
}
|
||||
|
||||
float touchX = event.getX();
|
||||
float touchY = event.getY();
|
||||
boolean isAdjustCropRect = true;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mIsMoveLeft = Math.abs(touchX - mCropRect.left) < TOUCH_DISTANCE;
|
||||
mIsMoveRight = Math.abs(touchX - mCropRect.right) < TOUCH_DISTANCE;
|
||||
mIsMoveTop = Math.abs(touchY - mCropRect.top) < TOUCH_DISTANCE;
|
||||
mIsMoveBottom = Math.abs(touchY - mCropRect.bottom) < TOUCH_DISTANCE;
|
||||
setBackGroundTransparent();
|
||||
isAdjustCropRect = mIsMoveBottom || mIsMoveTop || mIsMoveLeft || mIsMoveRight;
|
||||
if (!isAdjustCropRect) {
|
||||
delayCancelBackGroundTransparent();
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (mIsMoveLeft && touchX > mMaxCropRect.left && touchX < mCropRect.right) {
|
||||
mCropRect.left = (int) touchX;
|
||||
}
|
||||
|
||||
if (mIsMoveRight && touchX > mCropRect.left && touchX < mMaxCropRect.right) {
|
||||
mCropRect.right = (int) touchX;
|
||||
}
|
||||
|
||||
if (mIsMoveTop && touchY > mMaxCropRect.top && touchY < mCropRect.bottom) {
|
||||
mCropRect.top = (int) touchY;
|
||||
}
|
||||
|
||||
if (mIsMoveBottom && touchY > mCropRect.top && touchY < mMaxCropRect.bottom) {
|
||||
mCropRect.bottom = (int) touchY;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
delayCancelBackGroundTransparent();
|
||||
adjustCropRect();
|
||||
break;
|
||||
}
|
||||
invalidate();
|
||||
return isAdjustCropRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
public Rect getCropRect() {
|
||||
return new Rect(mCropRect);
|
||||
}
|
||||
|
||||
public void rotation90() {
|
||||
int centerX = mCropRect.centerX();
|
||||
int centerY = mCropRect.centerY();
|
||||
int width = mCropRect.width();
|
||||
int height = mCropRect.height();
|
||||
|
||||
mCropRect.top = centerY - width / 2;
|
||||
mCropRect.bottom = mCropRect.top + width;
|
||||
mCropRect.left = centerX - height / 2;
|
||||
mCropRect.right = mCropRect.left + height;
|
||||
|
||||
adjustCropRect();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private void setupDrawing() {
|
||||
mCropRectPath = new Path();
|
||||
mCropRectDrawPaint = new Paint();
|
||||
mCropRectDrawPaint.setColor(Color.WHITE);
|
||||
mCropRectDrawPaint.setAntiAlias(true);
|
||||
mCropRectDrawPaint.setStyle(Paint.Style.STROKE);
|
||||
mCropRectDrawPaint.setStrokeJoin(Paint.Join.ROUND);
|
||||
mCropRectDrawPaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
|
||||
mBackGroundDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mBackGroundDrawPaint.setColor(Color.argb(255, 0, 0, 0));
|
||||
mBackGroundDrawPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
private void drawGrid(Canvas canvas) {
|
||||
mCropRectDrawPaint.setStrokeWidth(GRID_LINE_WIDTH);
|
||||
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo((float) (mCropRect.left + mCropRect.width() / 3.0), mCropRect.top);
|
||||
mCropRectPath.lineTo((float) (mCropRect.left + mCropRect.width() / 3.0), mCropRect.bottom);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo((float) (mCropRect.left + mCropRect.width() * 2 / 3.0), mCropRect.top);
|
||||
mCropRectPath.lineTo((float) (mCropRect.left + mCropRect.width() * 2 / 3.0), mCropRect.bottom);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo(mCropRect.left, (float)(mCropRect.top + mCropRect.height() / 3.0));
|
||||
mCropRectPath.lineTo(mCropRect.right, (float)(mCropRect.top + mCropRect.height() / 3.0));
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo(mCropRect.left, (float)(mCropRect.top + mCropRect.height() * 2 / 3.0));
|
||||
mCropRectPath.lineTo(mCropRect.right, (float)(mCropRect.top + mCropRect.height() * 2 / 3.0));
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
}
|
||||
|
||||
private void drawBackGround(Canvas canvas) {
|
||||
canvas.drawRect(0, 0, getWidth(), mCropRect.top, mBackGroundDrawPaint);
|
||||
canvas.drawRect(0, mCropRect.bottom, getWidth(), getHeight(), mBackGroundDrawPaint);
|
||||
canvas.drawRect(0, mCropRect.top, mCropRect.left, mCropRect.bottom, mBackGroundDrawPaint);
|
||||
canvas.drawRect(mCropRect.right, mCropRect.top, getWidth(), mCropRect.bottom, mBackGroundDrawPaint);
|
||||
}
|
||||
|
||||
private void drawCropRect(Canvas canvas) {
|
||||
mCropRectDrawPaint.setStrokeWidth(RECT_LINE_WIDTH);
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo(mCropRect.left, mCropRect.top);
|
||||
mCropRectPath.lineTo(mCropRect.right, mCropRect.top);
|
||||
mCropRectPath.lineTo(mCropRect.right, mCropRect.bottom);
|
||||
mCropRectPath.lineTo(mCropRect.left, mCropRect.bottom);
|
||||
mCropRectPath.lineTo(mCropRect.left, mCropRect.top);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
}
|
||||
|
||||
private void drawCorner(Canvas canvas) {
|
||||
mCropRectDrawPaint.setStrokeWidth(RECT_CORNER_LINE_WIDTH);
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo(mCropRect.left, mCropRect.top + CORNER_LENGTH);
|
||||
mCropRectPath.lineTo(mCropRect.left, mCropRect.top);
|
||||
mCropRectPath.lineTo(mCropRect.left + CORNER_LENGTH, mCropRect.top);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo(mCropRect.right, mCropRect.top + CORNER_LENGTH);
|
||||
mCropRectPath.lineTo(mCropRect.right, mCropRect.top);
|
||||
mCropRectPath.lineTo(mCropRect.right - CORNER_LENGTH, mCropRect.top);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
|
||||
mCropRectPath.reset();
|
||||
mCropRectPath.moveTo(mCropRect.right, mCropRect.bottom - CORNER_LENGTH);
|
||||
mCropRectPath.lineTo(mCropRect.right, mCropRect.bottom);
|
||||
mCropRectPath.lineTo(mCropRect.right - CORNER_LENGTH, mCropRect.bottom);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
|
||||
mCropRectPath.moveTo(mCropRect.left, mCropRect.bottom - CORNER_LENGTH);
|
||||
mCropRectPath.lineTo(mCropRect.left, mCropRect.bottom);
|
||||
mCropRectPath.lineTo(mCropRect.left + CORNER_LENGTH, mCropRect.bottom);
|
||||
canvas.drawPath(mCropRectPath, mCropRectDrawPaint);
|
||||
}
|
||||
|
||||
private void delayCancelBackGroundTransparent() {
|
||||
ThreadUtils.getUiThreadHandler().postDelayed(mCancelBackGroundTransparentRunnable,
|
||||
CANCEL_BACKGROUND_TRANSPARENT_DELAY_TIME);
|
||||
ThreadUtils.getUiThreadHandler().postDelayed(mCancelDrawGridRunnable, CANCEL_DRAW_GRID_DELAY_TIME);
|
||||
}
|
||||
|
||||
private void setBackGroundTransparent() {
|
||||
mBackGroundDrawPaint.setColor(Color.argb(0, 0, 0, 0));
|
||||
mIsDrawGrid = true;
|
||||
ThreadUtils.getUiThreadHandler().removeCallbacks(mCancelBackGroundTransparentRunnable);
|
||||
ThreadUtils.getUiThreadHandler().removeCallbacks(mCancelDrawGridRunnable);
|
||||
}
|
||||
|
||||
private void initMaxCropRect() {
|
||||
mMaxCropRect.left = TUIMultimediaResourceUtils.dip2px(getContext(), LIMIT_LEFT_DP);
|
||||
mMaxCropRect.right = getWidth() - mMaxCropRect.left;
|
||||
mMaxCropRect.top = TUIMultimediaResourceUtils.dip2px(getContext(), LIMIT_TOP_DP);
|
||||
mMaxCropRect.bottom = getHeight() - TUIMultimediaResourceUtils.dip2px(getContext(), LIMIT_BOTTOM_DP);
|
||||
}
|
||||
|
||||
private void initCropRect() {
|
||||
if (mOriginPreviewRect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
float previewAspect = mOriginPreviewRect.width() * 1.0f / mOriginPreviewRect.height();
|
||||
float maxCropAspect = mMaxCropRect.width() * 1.0f / mMaxCropRect.height();
|
||||
|
||||
if (previewAspect > maxCropAspect) {
|
||||
mCropRect.left = mMaxCropRect.left;
|
||||
mCropRect.right = mMaxCropRect.right;
|
||||
int cropHeight = (int) (mCropRect.width() / previewAspect);
|
||||
mCropRect.top = (mMaxCropRect.bottom - cropHeight) / 2;
|
||||
mCropRect.bottom = mCropRect.top + cropHeight;
|
||||
} else {
|
||||
mCropRect.top = mMaxCropRect.top;
|
||||
mCropRect.bottom = mMaxCropRect.bottom;
|
||||
int cropWidth = (int) (mCropRect.height() * maxCropAspect);
|
||||
mCropRect.left = (getWidth() - cropWidth) / 2;
|
||||
mCropRect.right = mCropRect.left + cropWidth;
|
||||
}
|
||||
|
||||
float scale = mCropRect.width() * 1.0f / mOriginPreviewRect.width();
|
||||
int mediaAreaLeft = (int) (getWidth() / 2.0 - mOriginPreviewRect.width() / 2.0 * scale);
|
||||
int mediaAreaTop = (int) (getHeight() / 2.0 - mOriginPreviewRect.height() / 2.0 * scale);
|
||||
|
||||
if (mCropListener != null) {
|
||||
mCropListener.onCrop(getWidth() / 2.0f, getHeight() / 2.0f, scale,
|
||||
mediaAreaLeft - mCropRect.left, mediaAreaTop - mCropRect.top);
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustCropRect() {
|
||||
float maxCropAreaAspect = mMaxCropRect.width() * 1.0f / mMaxCropRect.height();
|
||||
float cropAspect = mCropRect.width() * 1.0f / mCropRect.height();
|
||||
|
||||
int sourceLeft = mCropRect.left;
|
||||
int sourceTop = mCropRect.top;
|
||||
int sourceRight = mCropRect.right;
|
||||
|
||||
if (cropAspect > maxCropAreaAspect) {
|
||||
mCropRect.left = mMaxCropRect.left;
|
||||
mCropRect.right = mMaxCropRect.right;
|
||||
int cropHeight = (int) (mCropRect.width() / cropAspect);
|
||||
mCropRect.top = (mMaxCropRect.bottom - cropHeight) / 2;
|
||||
mCropRect.bottom = mCropRect.top + cropHeight;
|
||||
} else {
|
||||
mCropRect.top = mMaxCropRect.top;
|
||||
mCropRect.bottom = mMaxCropRect.bottom;
|
||||
int cropWidth = (int) (mCropRect.height() * cropAspect);
|
||||
mCropRect.left = (getWidth() - cropWidth) / 2;
|
||||
mCropRect.right = mCropRect.left + cropWidth;
|
||||
}
|
||||
|
||||
float scale = mCropRect.width() * 1.0f / (sourceRight - sourceLeft);
|
||||
int moveX = sourceLeft - mCropRect.left;
|
||||
int moveY = sourceTop - mCropRect.top;
|
||||
|
||||
if (mCropListener != null) {
|
||||
mCropListener.onCrop(sourceLeft, sourceTop, scale, moveX, moveY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,613 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer;
|
||||
|
||||
import android.content.Context;
|
||||
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.Drawable;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.liteav.base.util.Size;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaGraphComputerUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class FloatLayerView extends View {
|
||||
private final String TAG = FloatLayerView.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
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 MIN_DISTANCE_TO_EDGE_DP = 10;
|
||||
private static final float MAX_SCALE = 10.0f;
|
||||
private static final float MIN_SCALE = 0.02f;
|
||||
private static final float DEFAULT_SCALE = 1.0f;
|
||||
private static final float DEFAULT_DEGREE = 0;
|
||||
private static final int DEFAULT_FRAME_PADDING = 3;
|
||||
private static final int DEFAULT_FRAME_WIDTH = 1;
|
||||
private static final int DEFAULT_FRAME_COLOR = Color.WHITE;
|
||||
private static final int DEFAULT_DELETE_DRAWABLE_ID = R.drawable.multimedia_plugin_edit_floatlayer_delete;
|
||||
private static final int DEFAULT_EDIT_DRAWABLE_ID = R.drawable.multimedia_plugin_edit_floatlayer_edit;
|
||||
private static final int DEFAULT_ROTATION_DRAWABLE_ID = R.drawable.multimedia_plugin_edit_floatlayer_rotation;
|
||||
private static final int DRAWABLE_SIZE_DP = 25;
|
||||
|
||||
private final PointF mCenterPoint = new PointF();
|
||||
private final PointF mMoveSize = new PointF();
|
||||
private final Matrix mMatrix = new Matrix();
|
||||
private final int mControlLocation = RIGHT_BOTTOM;
|
||||
private final int mEditLocation = RIGHT_TOP;
|
||||
private final int mDeleteLocation = LEFT_TOP;
|
||||
private final Path mPath = new Path();
|
||||
@NonNull
|
||||
private final PointF mPreMovePointF = new PointF();
|
||||
@NonNull
|
||||
private final PointF mCurMovePointF = new PointF();
|
||||
protected TUIMultimediaData<Status> mTuiDataStatus = new TUIMultimediaData<>(Status.STATUS_INIT);
|
||||
private boolean mShowDelete = true;
|
||||
private boolean mShowEdit = true;
|
||||
private PasterType mPasterType;
|
||||
private boolean isMeasured = false;
|
||||
private Bitmap mBitmap;
|
||||
private int mViewWidth, mViewHeight;
|
||||
private float mDegree = DEFAULT_DEGREE;
|
||||
private float mScale = DEFAULT_SCALE;
|
||||
private int mViewPaddingLeft;
|
||||
private int mViewPaddingTop;
|
||||
private Point mLTPoint;
|
||||
private Point mRTPoint;
|
||||
private Point mRBPoint;
|
||||
private Point mLBPoint;
|
||||
private Point mEditPoint = new Point();
|
||||
private Point mDeletePoint = new Point();
|
||||
private Point mRotatePoint = new Point();
|
||||
private Drawable mEditDrawable;
|
||||
private Drawable mDeleteDrawable;
|
||||
private Drawable mRotateDrawable;
|
||||
private Paint mPaint;
|
||||
|
||||
private int mFramePadding = DEFAULT_FRAME_PADDING;
|
||||
private int mFrameColor = DEFAULT_FRAME_COLOR;
|
||||
private int mFrameWidth = DEFAULT_FRAME_WIDTH;
|
||||
private int mOffsetX;
|
||||
private int mOffsetY;
|
||||
private int mImageLeft;
|
||||
private int mImageTop;
|
||||
private int mImageWidth;
|
||||
private int mOperationIconSize = DRAWABLE_SIZE_DP;
|
||||
private Zoom touchDownZoom = Zoom.IMAGE_ZOOM;
|
||||
private boolean mCanTouch = true;
|
||||
|
||||
private Size mOriginParentSize;
|
||||
private Size mLastParentPosition;
|
||||
|
||||
float mViewScaleX = 1.0f;
|
||||
float mViewScaleY = 1.0f;
|
||||
|
||||
int mRotation = 0;
|
||||
|
||||
public FloatLayerView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public void showDelete(boolean showDelete) {
|
||||
mShowDelete = showDelete;
|
||||
}
|
||||
|
||||
public void showEdit(boolean showEdit) {
|
||||
mShowEdit = showEdit;
|
||||
}
|
||||
|
||||
public void setCanTouch(boolean touch) {
|
||||
mCanTouch = touch;
|
||||
}
|
||||
|
||||
public void setImageScale(float scale) {
|
||||
LiteavLog.i(TAG, "set image scale. scale = " + scale);
|
||||
if (this.mScale != scale) {
|
||||
this.mScale = scale;
|
||||
transformDraw();
|
||||
}
|
||||
}
|
||||
|
||||
public void unSelect() {
|
||||
LiteavLog.i(TAG, "unSelect");
|
||||
if (mTuiDataStatus.get() == Status.STATUS_SELECTED) {
|
||||
mTuiDataStatus.set(Status.STATUS_INIT);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public TUIMultimediaData<Status> tuiDataGetStatus() {
|
||||
return mTuiDataStatus;
|
||||
}
|
||||
|
||||
public void setImageBitmap(@Nullable Bitmap bitmap) {
|
||||
if (mBitmap != null && bitmap != null && !mBitmap.equals(bitmap)) {
|
||||
mBitmap.recycle();
|
||||
}
|
||||
mBitmap = bitmap;
|
||||
transformDraw();
|
||||
}
|
||||
|
||||
public PasterType getPasterType() {
|
||||
return mPasterType;
|
||||
}
|
||||
|
||||
public void setPasterType(PasterType type) {
|
||||
mPasterType = type;
|
||||
}
|
||||
|
||||
public Bitmap getRotateBitmap() {
|
||||
if (mBitmap == null) {
|
||||
LiteavLog.e(TAG, "get rotate bitmap is null");
|
||||
return null;
|
||||
}
|
||||
return Bitmap.createBitmap(this.mBitmap, 0, 0, this.mBitmap.getWidth(),
|
||||
this.mBitmap.getHeight(), mMatrix, true);
|
||||
}
|
||||
|
||||
public int getImageLeft() {
|
||||
return this.mImageLeft;
|
||||
}
|
||||
|
||||
public int getImageTop() {
|
||||
return this.mImageTop;
|
||||
}
|
||||
|
||||
public int getImageWidth() {
|
||||
return this.mImageWidth;
|
||||
}
|
||||
|
||||
public void setPadding(int padding) {
|
||||
mFramePadding = padding;
|
||||
}
|
||||
|
||||
public void setBorderWidth(int borderWidth) {
|
||||
mFrameWidth = borderWidth;
|
||||
}
|
||||
|
||||
public void setBorderColor(int color) {
|
||||
mFrameColor = getResources().getColor(color);
|
||||
}
|
||||
|
||||
public void setEditIconResource(int resId) {
|
||||
mEditDrawable = getResources().getDrawable(resId);
|
||||
}
|
||||
|
||||
public void setRotateIconResource(int resId) {
|
||||
mRotateDrawable = getResources().getDrawable(resId);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mOperationIconSize = TUIMultimediaResourceUtils.dip2px(getContext(), DRAWABLE_SIZE_DP);
|
||||
|
||||
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
|
||||
mFramePadding = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_PADDING, metrics);
|
||||
mFrameWidth = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, DEFAULT_FRAME_WIDTH, metrics);
|
||||
|
||||
mFrameColor = DEFAULT_FRAME_COLOR;
|
||||
mScale = DEFAULT_SCALE;
|
||||
mDegree = DEFAULT_DEGREE;
|
||||
|
||||
mRotateDrawable = TUIMultimediaResourceUtils.getDrawable(DEFAULT_ROTATION_DRAWABLE_ID);
|
||||
mEditDrawable = TUIMultimediaResourceUtils.getDrawable(DEFAULT_EDIT_DRAWABLE_ID);
|
||||
mDeleteDrawable = TUIMultimediaResourceUtils.getDrawable(DEFAULT_DELETE_DRAWABLE_ID);
|
||||
|
||||
mPaint = new Paint();
|
||||
mPaint.setAntiAlias(true);
|
||||
mPaint.setColor(mFrameColor);
|
||||
mPaint.setStrokeWidth(mFrameWidth);
|
||||
mPaint.setStyle(Paint.Style.STROKE);
|
||||
|
||||
transformDraw();
|
||||
}
|
||||
|
||||
public void onParentSizeChange(Size parentSize) {
|
||||
if (mOriginParentSize != null) {
|
||||
if (mOriginParentSize.getWidth() != 0) {
|
||||
mViewScaleX = parentSize.width * 1.0f / mOriginParentSize.getWidth();
|
||||
}
|
||||
|
||||
if (mOriginParentSize.getHeight() != 0) {
|
||||
mViewScaleY = parentSize.height * 1.0f / mOriginParentSize.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
float mViewScaleAddX = 1.0f;
|
||||
float mViewScaleAddY = 1.0f;
|
||||
if (mLastParentPosition != null) {
|
||||
mViewScaleAddX = parentSize.width * 1.0f / mLastParentPosition.width;
|
||||
mViewScaleAddY = parentSize.height * 1.0f / mLastParentPosition.height;
|
||||
}
|
||||
mLastParentPosition = new Size(parentSize.width, parentSize.height);
|
||||
|
||||
mCenterPoint.x = mCenterPoint.x * mViewScaleAddX;
|
||||
mCenterPoint.y = mCenterPoint.y * mViewScaleAddY;
|
||||
transformDraw();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (!isMeasured) {
|
||||
ViewGroup viewGroup = (ViewGroup) getParent();
|
||||
if (null != viewGroup) {
|
||||
int parentWidth = viewGroup.getWidth();
|
||||
int parentHeight = viewGroup.getHeight();
|
||||
mCenterPoint.set(parentWidth * 1.0f / 2, parentHeight * 1.0f / 2);
|
||||
|
||||
if (mOriginParentSize == null) {
|
||||
mOriginParentSize = new Size(parentWidth, parentHeight);
|
||||
mLastParentPosition = mOriginParentSize;
|
||||
LiteavLog.i(TAG, "viewGroup = " + viewGroup);
|
||||
}
|
||||
}
|
||||
isMeasured = true;
|
||||
adjustLayout();
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustLayout() {
|
||||
int minDistanceToEdge = TUIMultimediaResourceUtils.dip2px(getContext(), MIN_DISTANCE_TO_EDGE_DP);
|
||||
int halfViewWidth = (int) (mViewWidth * 1.0f / 2);
|
||||
int halfViewHeight = (int) (mViewHeight * 1.0f / 2);
|
||||
|
||||
if (mCenterPoint.x < minDistanceToEdge - halfViewWidth) {
|
||||
mCenterPoint.x = minDistanceToEdge - halfViewWidth;
|
||||
}
|
||||
|
||||
if (mCenterPoint.y < minDistanceToEdge - halfViewHeight) {
|
||||
mCenterPoint.y = minDistanceToEdge - halfViewHeight;
|
||||
}
|
||||
|
||||
ViewGroup mViewGroup = (ViewGroup) getParent();
|
||||
if (null != mViewGroup) {
|
||||
int parentWidth = mViewGroup.getWidth();
|
||||
if (mCenterPoint.x > parentWidth - minDistanceToEdge + halfViewWidth) {
|
||||
mCenterPoint.x = parentWidth - minDistanceToEdge + halfViewWidth;
|
||||
}
|
||||
|
||||
int parentHeight = mViewGroup.getHeight();
|
||||
if (mCenterPoint.y > parentHeight - minDistanceToEdge + halfViewHeight) {
|
||||
mCenterPoint.y = parentHeight - minDistanceToEdge + halfViewHeight;
|
||||
}
|
||||
}
|
||||
|
||||
int actualWidth = mViewWidth + mOperationIconSize;
|
||||
int actualHeight = mViewHeight + mOperationIconSize;
|
||||
int newPaddingLeft = (int) (mCenterPoint.x - actualWidth / 2.0);
|
||||
int newPaddingTop = (int) (mCenterPoint.y - actualHeight / 2.0);
|
||||
|
||||
if (mViewPaddingLeft != newPaddingLeft || mViewPaddingTop != newPaddingTop) {
|
||||
mViewPaddingLeft = newPaddingLeft;
|
||||
mViewPaddingTop = newPaddingTop;
|
||||
}
|
||||
layout(newPaddingLeft, newPaddingTop, newPaddingLeft + actualWidth,
|
||||
newPaddingTop + actualHeight);
|
||||
mImageLeft = newPaddingLeft + mOperationIconSize / 2;
|
||||
mImageTop = newPaddingTop + mOperationIconSize / 2;
|
||||
mImageWidth = mViewWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(@NonNull Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (mBitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
transformDraw();
|
||||
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
|
||||
if (mTuiDataStatus.get() == Status.STATUS_SELECTED) {
|
||||
drawOuterFrame(canvas);
|
||||
drawOperationIcon(canvas);
|
||||
}
|
||||
adjustLayout();
|
||||
}
|
||||
|
||||
private void drawOuterFrame(@NonNull Canvas canvas) {
|
||||
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);
|
||||
canvas.drawPath(mPath, mPaint);
|
||||
}
|
||||
|
||||
private void drawOperationIcon(@NonNull Canvas canvas) {
|
||||
if (mRotateDrawable != null) {
|
||||
mRotateDrawable.setBounds(mRotatePoint.x - mOperationIconSize / 2,
|
||||
mRotatePoint.y - mOperationIconSize / 2,
|
||||
mRotatePoint.x + mOperationIconSize / 2,
|
||||
mRotatePoint.y + mOperationIconSize / 2);
|
||||
canvas.save();
|
||||
canvas.rotate(-45.0f, mRotatePoint.x, mRotatePoint.y);
|
||||
mRotateDrawable.draw(canvas);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
if (mEditDrawable != null && mShowEdit) {
|
||||
mEditDrawable.setBounds(mEditPoint.x - mOperationIconSize / 2,
|
||||
mEditPoint.y - mOperationIconSize / 2,
|
||||
mEditPoint.x + mOperationIconSize / 2,
|
||||
mEditPoint.y + mOperationIconSize / 2);
|
||||
mEditDrawable.draw(canvas);
|
||||
}
|
||||
|
||||
if (mDeleteDrawable != null && mShowDelete) {
|
||||
mDeleteDrawable.setBounds(mDeletePoint.x - mOperationIconSize / 2,
|
||||
mDeletePoint.y - mOperationIconSize / 2,
|
||||
mDeletePoint.x + mOperationIconSize / 2,
|
||||
mDeletePoint.y + mOperationIconSize / 2);
|
||||
mDeleteDrawable.draw(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
if (!mCanTouch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mPreMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
|
||||
touchDownZoom = judgeZoom(event.getX(), event.getY());
|
||||
if (touchDownZoom != Zoom.ROTATE_ZOOM) {
|
||||
mMoveSize.x = 0;
|
||||
mMoveSize.y = 0;
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (touchDownZoom == Zoom.ROTATE_ZOOM) {
|
||||
break;
|
||||
} else if (Math.abs(mMoveSize.x) > 3 || Math.abs(mMoveSize.y) > 3) {
|
||||
break;
|
||||
}
|
||||
|
||||
Zoom touchUpZoom = judgeZoom(event.getX(), event.getY());
|
||||
switch (touchUpZoom) {
|
||||
case EDIT_ZOOM:
|
||||
mTuiDataStatus.set(Status.STATUS_EDIT);
|
||||
break;
|
||||
case DELETE_ZOOM:
|
||||
mTuiDataStatus.set(Status.STATUS_DELETE);
|
||||
break;
|
||||
case IMAGE_ZOOM:
|
||||
if (mTuiDataStatus.get() == Status.STATUS_INIT) {
|
||||
mTuiDataStatus.set(Status.STATUS_SELECTED);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mShowEdit && mTuiDataStatus.get() == Status.STATUS_SELECTED) {
|
||||
mTuiDataStatus.set(Status.STATUS_EDIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
invalidate();
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mCurMovePointF.set(event.getX() + mViewPaddingLeft, event.getY() + mViewPaddingTop);
|
||||
if (touchDownZoom == Zoom.ROTATE_ZOOM) {
|
||||
rotateImage();
|
||||
} else {
|
||||
moveImage();
|
||||
}
|
||||
mPreMovePointF.set(mCurMovePointF);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void rotateImage() {
|
||||
if (mBitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int halfBitmapWidth = mBitmap.getWidth() / 2;
|
||||
int halfBitmapHeight = mBitmap.getHeight() / 2;
|
||||
|
||||
float bitmapToCenterDistance = TUIMultimediaGraphComputerUtils.distance4PointF(new PointF(0, 0),
|
||||
new PointF(halfBitmapWidth * mViewScaleX, halfBitmapHeight * mViewScaleY));
|
||||
float moveToCenterDistance = TUIMultimediaGraphComputerUtils.distance4PointF(mCenterPoint, mCurMovePointF);
|
||||
float scale = moveToCenterDistance / bitmapToCenterDistance;
|
||||
|
||||
if (scale <= MIN_SCALE) {
|
||||
scale = MIN_SCALE;
|
||||
} else if (scale >= MAX_SCALE) {
|
||||
scale = MAX_SCALE;
|
||||
}
|
||||
|
||||
double a = TUIMultimediaGraphComputerUtils.distance4PointF(mCenterPoint, mPreMovePointF);
|
||||
double b = TUIMultimediaGraphComputerUtils.distance4PointF(mPreMovePointF, mCurMovePointF);
|
||||
double c = TUIMultimediaGraphComputerUtils.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) TUIMultimediaGraphComputerUtils.radianToDegree(radian);
|
||||
|
||||
PointF centerToProMove = new PointF((mPreMovePointF.x - mCenterPoint.x),
|
||||
(mPreMovePointF.y - mCenterPoint.y));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void moveImage() {
|
||||
mCenterPoint.x += mCurMovePointF.x - mPreMovePointF.x;
|
||||
mCenterPoint.y += mCurMovePointF.y - mPreMovePointF.y;
|
||||
|
||||
mMoveSize.x += mCurMovePointF.x - mPreMovePointF.x;
|
||||
mMoveSize.y += mCurMovePointF.y - mPreMovePointF.y;
|
||||
|
||||
adjustLayout();
|
||||
}
|
||||
|
||||
private void transformDraw() {
|
||||
if (mBitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int bitmapWidth = (int) (mBitmap.getWidth() * mScale * mViewScaleX);
|
||||
int bitmapHeight = (int) (mBitmap.getHeight() * mScale * mViewScaleY);
|
||||
int framePadding = (int) (mFramePadding * Math.max(mViewScaleX, mViewScaleY));
|
||||
computeRect(-framePadding, -framePadding, bitmapWidth + framePadding,
|
||||
bitmapHeight + framePadding, mDegree);
|
||||
|
||||
LiteavLog.i(TAG, "mScale = " + mScale + " mViewScaleX = " + mViewScaleX + " mViewScaleY= " + mViewScaleY);
|
||||
mMatrix.setScale(mScale * mViewScaleX, mScale * mViewScaleY);
|
||||
mMatrix.postRotate(mDegree % 360, bitmapWidth * 1.0f / 2, bitmapHeight * 1.0f / 2);
|
||||
mMatrix.postTranslate(
|
||||
mOffsetX + mOperationIconSize * 1.0f / 2, mOffsetY + mOperationIconSize * 1.0f / 2);
|
||||
|
||||
adjustLayout();
|
||||
}
|
||||
|
||||
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 = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, lt, degree);
|
||||
mRTPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, rt, degree);
|
||||
mRBPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, rb, degree);
|
||||
mLBPoint = TUIMultimediaGraphComputerUtils.obtainRotationPoint(cp, lb, degree);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
Point viewCenterPoint = new Point(
|
||||
(maxCoordinateX + minCoordinateX) / 2, (maxCoordinateY + minCoordinateY) / 2);
|
||||
|
||||
mOffsetX = mViewWidth / 2 - viewCenterPoint.x;
|
||||
mOffsetY = mViewHeight / 2 - viewCenterPoint.y;
|
||||
|
||||
int halfDrawableWidth = mOperationIconSize / 2;
|
||||
int halfDrawableHeight = mOperationIconSize / 2;
|
||||
|
||||
mLTPoint.x += (mOffsetX + halfDrawableWidth);
|
||||
mRTPoint.x += (mOffsetX + halfDrawableWidth);
|
||||
mRBPoint.x += (mOffsetX + halfDrawableWidth);
|
||||
mLBPoint.x += (mOffsetX + halfDrawableWidth);
|
||||
|
||||
mLTPoint.y += (mOffsetY + halfDrawableHeight);
|
||||
mRTPoint.y += (mOffsetY + halfDrawableHeight);
|
||||
mRBPoint.y += (mOffsetY + halfDrawableHeight);
|
||||
mLBPoint.y += (mOffsetY + halfDrawableHeight);
|
||||
|
||||
mRotatePoint = locatePoint(mControlLocation);
|
||||
mEditPoint = locatePoint(mEditLocation);
|
||||
mDeletePoint = locatePoint(mDeleteLocation);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private Zoom judgeZoom(float x, float y) {
|
||||
PointF touchPoint = new PointF(x, y);
|
||||
PointF controlPointF = new PointF(mRotatePoint);
|
||||
|
||||
float distanceToControl = TUIMultimediaGraphComputerUtils.distance4PointF(touchPoint, controlPointF);
|
||||
if (distanceToControl < mOperationIconSize / 2.0) {
|
||||
return Zoom.ROTATE_ZOOM;
|
||||
}
|
||||
|
||||
if (mShowEdit) {
|
||||
float disToEdit = TUIMultimediaGraphComputerUtils.distance4PointF(touchPoint, new PointF(mEditPoint));
|
||||
if (disToEdit < mOperationIconSize / 2.0) {
|
||||
return Zoom.EDIT_ZOOM;
|
||||
}
|
||||
}
|
||||
|
||||
if (mShowDelete) {
|
||||
float disToDelete = TUIMultimediaGraphComputerUtils.distance4PointF(touchPoint, new PointF(mDeletePoint));
|
||||
if (disToDelete < mOperationIconSize / 2.0) {
|
||||
return Zoom.DELETE_ZOOM;
|
||||
}
|
||||
}
|
||||
|
||||
return Zoom.IMAGE_ZOOM;
|
||||
}
|
||||
|
||||
private int getMaxValue(Integer... array) {
|
||||
List<Integer> list = Arrays.asList(array);
|
||||
Collections.sort(list);
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
private int getMinValue(Integer... array) {
|
||||
List<Integer> list = Arrays.asList(array);
|
||||
Collections.sort(list);
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
STATUS_INIT,
|
||||
STATUS_EDIT,
|
||||
STATUS_DELETE,
|
||||
STATUS_SELECTED
|
||||
}
|
||||
|
||||
private enum Zoom {
|
||||
IMAGE_ZOOM,
|
||||
ROTATE_ZOOM,
|
||||
EDIT_ZOOM,
|
||||
DELETE_ZOOM
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.liteav.base.util.Size;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView.Status;
|
||||
import com.tencent.ugc.TXVideoEditConstants.TXRect;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class FloatLayerViewGroup extends FrameLayout {
|
||||
|
||||
private final String TAG = FloatLayerViewGroup.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
private final List<FloatLayerView> mFloatLayerViewList = new ArrayList<>();
|
||||
private FloatLayerView mSelectedFloatLayerView;
|
||||
|
||||
public FloatLayerViewGroup(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public FloatLayerViewGroup(@NonNull Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public FloatLayerViewGroup(@NonNull Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void addFloatLayerView(FloatLayerView view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
mFloatLayerViewList.add(view);
|
||||
addView(view, MATCH_PARENT, MATCH_PARENT);
|
||||
view.tuiDataGetStatus().observe(status -> {
|
||||
if (status == Status.STATUS_SELECTED) {
|
||||
if (mSelectedFloatLayerView != null && view != mSelectedFloatLayerView) {
|
||||
mSelectedFloatLayerView.unSelect();
|
||||
}
|
||||
mSelectedFloatLayerView = view;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeFloatLayerView(FloatLayerView view) {
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
mFloatLayerViewList.remove(view);
|
||||
removeView(view);
|
||||
}
|
||||
|
||||
public void removeAllFloatLayerView() {
|
||||
for(FloatLayerView view : mFloatLayerViewList) {
|
||||
removeView(view);
|
||||
}
|
||||
mFloatLayerViewList.clear();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<TUIMultimediaPasterInfo> getAllPaster() {
|
||||
List<TUIMultimediaPasterInfo> pasterInfoList = new LinkedList<>();
|
||||
|
||||
for (FloatLayerView view : mFloatLayerViewList) {
|
||||
Bitmap image = view.getRotateBitmap();
|
||||
if (image == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TUIMultimediaPasterInfo pasterInfo = new TUIMultimediaPasterInfo();
|
||||
TXRect frame = new TXRect();
|
||||
frame.width = view.getImageWidth();
|
||||
frame.x = view.getImageLeft();
|
||||
frame.y = view.getImageTop();
|
||||
pasterInfo.frame = frame;
|
||||
pasterInfo.image = image;
|
||||
pasterInfo.pasterType = view.getPasterType();
|
||||
|
||||
pasterInfoList.add(pasterInfo);
|
||||
}
|
||||
|
||||
return pasterInfoList;
|
||||
}
|
||||
|
||||
public void unSelectOperationView() {
|
||||
if (mSelectedFloatLayerView != null) {
|
||||
mSelectedFloatLayerView.unSelect();
|
||||
}
|
||||
}
|
||||
|
||||
public void setCanTouch(boolean touch) {
|
||||
for (FloatLayerView floatLayerView : mFloatLayerViewList) {
|
||||
if (floatLayerView != null) {
|
||||
floatLayerView.setCanTouch(touch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChildCount() {
|
||||
return mFloatLayerViewList.size();
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation")
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
View view = getChildAt(i);
|
||||
if (view instanceof FloatLayerView) {
|
||||
((FloatLayerView)view).onParentSizeChange(new Size(r - l, b - t));
|
||||
}
|
||||
view.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerViewGroup;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class PicturePasterFloatLayerView extends FloatLayerView {
|
||||
|
||||
private final static int MAX_BITMAP_SIDE_SIZE_DP = 150;
|
||||
private final String TAG = PicturePasterFloatLayerView.class + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final FloatLayerViewGroup mFloatLayerViewGroup;
|
||||
|
||||
private final TUIMultimediaDataObserver<Status> mOnStatusChanged = status -> {
|
||||
if (status == Status.STATUS_DELETE) {
|
||||
onDeleted();
|
||||
}
|
||||
};
|
||||
|
||||
public PicturePasterFloatLayerView(Context context, FloatLayerViewGroup floatLayerViewGroup, Bitmap bitmap) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mFloatLayerViewGroup = floatLayerViewGroup;
|
||||
mFloatLayerViewGroup.addFloatLayerView(this);
|
||||
setPasterType(PasterType.STATIC_PICTURE_PASTER);
|
||||
showDelete(true);
|
||||
showEdit(false);
|
||||
setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
addObserve();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
removeObserve();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageBitmap(Bitmap bitmap) {
|
||||
if (bitmap == null) {
|
||||
return;
|
||||
}
|
||||
int sideLength = Math.max(bitmap.getWidth(), bitmap.getHeight());
|
||||
float maxSideLength = TUIMultimediaResourceUtils.spToPx(mContext, MAX_BITMAP_SIDE_SIZE_DP);
|
||||
if (sideLength > maxSideLength) {
|
||||
setImageScale(maxSideLength / sideLength);
|
||||
}
|
||||
super.setImageBitmap(bitmap);
|
||||
}
|
||||
|
||||
private void onDeleted() {
|
||||
mFloatLayerViewGroup.removeFloatLayerView(this);
|
||||
}
|
||||
|
||||
private void addObserve() {
|
||||
mTuiDataStatus.observe(mOnStatusChanged);
|
||||
}
|
||||
|
||||
private void removeObserve() {
|
||||
mTuiDataStatus.removeObserver(mOnStatusChanged);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterType;
|
||||
|
||||
|
||||
public class PicturePasterItemAdapter extends BaseAdapter {
|
||||
|
||||
private final Context mContext;
|
||||
private final PicturePasterInfo mPasterInfo;
|
||||
private final int mPasterTypeIndex;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedItem;
|
||||
|
||||
public PicturePasterItemAdapter(Context context, PicturePasterInfo pasterInfo, int pasterTypeIndex,
|
||||
TUIMultimediaData<ItemPosition> tuiDataSelectedItem) {
|
||||
mContext = context;
|
||||
mPasterInfo = pasterInfo;
|
||||
mPasterTypeIndex = pasterTypeIndex;
|
||||
mTuiDataSelectedItem = tuiDataSelectedItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
PicturePasterType typeInfo = mPasterInfo.getPasterType(mPasterTypeIndex);
|
||||
if (typeInfo != null) {
|
||||
return typeInfo.getItemSize();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
final ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.multimedia_plugin_edit_picture_paster_item_view, parent, false);
|
||||
holder = new ViewHolder(convertView);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
if (position >= 0) {
|
||||
PicturePasterItem pasterItem = mPasterInfo.getPasterItem(new ItemPosition(mPasterTypeIndex, position));
|
||||
Bitmap bitmap = pasterItem != null ? pasterItem.getPasterIcon() : null;
|
||||
if (bitmap != null && !bitmap.isRecycled()) {
|
||||
holder.icon.setImageBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
convertView.setOnClickListener(v -> mTuiDataSelectedItem.set(new ItemPosition(mPasterTypeIndex, position)));
|
||||
return convertView;
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ImageView icon;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
icon = itemView.findViewById(R.id.paster_iv_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import com.google.gson.Gson;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaCommonThreadPool;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterItem;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class PicturePasterManager {
|
||||
|
||||
private static final int USER_PASTER_ICON_MAX_SIZE = 360;
|
||||
private final String TAG = PicturePasterManager.class + "_" + hashCode();
|
||||
|
||||
private PicturePasterInfo mPicturePasterSet;
|
||||
private PicturePasterInfo mUserPicturePasterSet;
|
||||
private String mPasterJsonFilePath = "paster_info_data.json";
|
||||
|
||||
private Future<PicturePasterInfo> mCratePicturePasterSetFuture;
|
||||
|
||||
public PicturePasterManager(String pasterJsonFilePath) {
|
||||
LiteavLog.i(TAG, "pasterJsonFilePath =" + pasterJsonFilePath);
|
||||
if (pasterJsonFilePath == null || pasterJsonFilePath.isEmpty() || !pasterJsonFilePath.endsWith(".json")) {
|
||||
LiteavLog.e(TAG, "pasterJsonFilePath =" + pasterJsonFilePath + " is invalid paster json file");
|
||||
} else {
|
||||
mPasterJsonFilePath = pasterJsonFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
static public String getUserPasterJsonFilePath() {
|
||||
File file = new File(TUIConfig.getDefaultAppDir(), "user_picture_paster.json");
|
||||
if (file.exists()) {
|
||||
return file.getPath();
|
||||
}
|
||||
try {
|
||||
if (!file.createNewFile()) {
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return file.getPath();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
mCratePicturePasterSetFuture = TUIMultimediaCommonThreadPool.getThreadExecutor().submit(this::cratePicturePasterSet);
|
||||
}
|
||||
|
||||
public PicturePasterInfo getPicturePasterSetInfo() {
|
||||
try {
|
||||
return mCratePicturePasterSetFuture.get(500, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
LiteavLog.e(TAG, "create picture paster set info error " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public PicturePasterItem getPasterItem(ItemPosition itemPosition) {
|
||||
return mPicturePasterSet.getPasterItem(itemPosition);
|
||||
}
|
||||
|
||||
protected Bitmap getPaster(ItemPosition position) {
|
||||
LiteavLog.i(TAG, "select paster position TypeIndex = " +
|
||||
position.pasterTypeIndex + " ItemIndex = " + position.pasterItemIndex);
|
||||
PicturePasterItem pasterItem = mPicturePasterSet.getPasterItem(position);
|
||||
return pasterItem != null ? pasterItem.getPasterImage() : null;
|
||||
}
|
||||
|
||||
public boolean addUserPasterFromUri(Uri uri, ItemPosition itemPosition) {
|
||||
Bitmap bitmap = TUIMultimediaFileUtil.readBitmapFromUrl(uri);
|
||||
if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
|
||||
LiteavLog.w(TAG, "select image is error");
|
||||
return false;
|
||||
}
|
||||
|
||||
Bitmap iconBitmap = createIcon(bitmap);
|
||||
ItemPosition newPasterPosition = new ItemPosition(itemPosition.pasterTypeIndex,
|
||||
itemPosition.pasterItemIndex + 1);
|
||||
if (mPicturePasterSet != null) {
|
||||
mPicturePasterSet.addPasterItem(newPasterPosition, new PicturePasterItem(bitmap, iconBitmap));
|
||||
}
|
||||
saveUserPicturePaster(itemPosition.pasterTypeIndex, uri, iconBitmap);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void uninit() {
|
||||
TUIMultimediaCommonThreadPool.getThreadExecutor().execute(() -> {
|
||||
if (mUserPicturePasterSet != null) {
|
||||
mUserPicturePasterSet.saveToJsonFile(getUserPasterJsonFilePath());
|
||||
}
|
||||
releaseSource();
|
||||
});
|
||||
}
|
||||
|
||||
public void releaseSource() {
|
||||
if (mPicturePasterSet != null) {
|
||||
mPicturePasterSet.release();
|
||||
mPicturePasterSet = null;
|
||||
}
|
||||
|
||||
if (mUserPicturePasterSet != null) {
|
||||
mUserPicturePasterSet.release();
|
||||
mUserPicturePasterSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
private PicturePasterInfo cratePicturePasterSet() {
|
||||
LiteavLog.i(TAG, "crate picture paster set");
|
||||
mPicturePasterSet = getDefaultPicturePaster();
|
||||
mUserPicturePasterSet = getUserPicturePaster();
|
||||
if (mPicturePasterSet == null) {
|
||||
mPicturePasterSet = new PicturePasterInfo();
|
||||
}
|
||||
mPicturePasterSet.append(mUserPicturePasterSet);
|
||||
return mPicturePasterSet;
|
||||
}
|
||||
|
||||
private PicturePasterInfo getDefaultPicturePaster() {
|
||||
String json = TUIMultimediaFileUtil.readTextFromFile(mPasterJsonFilePath);
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(json, PicturePasterInfo.class);
|
||||
}
|
||||
|
||||
public PicturePasterInfo getUserPicturePaster() {
|
||||
String json = TUIMultimediaFileUtil.readTextFromFile(getUserPasterJsonFilePath());
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(json, PicturePasterInfo.class);
|
||||
}
|
||||
|
||||
private void saveUserPicturePaster(int pasterTypeIndex, Uri imageUrl, Bitmap iconBitmap) {
|
||||
TUIMultimediaCommonThreadPool.getThreadExecutor().execute(() -> {
|
||||
String imagePath = savePasterImage(imageUrl);
|
||||
String iconPath = savePasterIcon(iconBitmap);
|
||||
if (imagePath != null && iconPath != null) {
|
||||
addUserPicturePasterInfo(pasterTypeIndex, imagePath, iconPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addUserPicturePasterInfo(int pasterTypeIndex, String imageFilePath, String iconFilePath) {
|
||||
if (mUserPicturePasterSet == null) {
|
||||
mUserPicturePasterSet = getUserPicturePaster();
|
||||
if (mUserPicturePasterSet == null) {
|
||||
mUserPicturePasterSet = new PicturePasterInfo();
|
||||
}
|
||||
}
|
||||
mUserPicturePasterSet.addPasterItem(new ItemPosition(pasterTypeIndex, 0),
|
||||
new PicturePasterItem(imageFilePath, iconFilePath));
|
||||
}
|
||||
|
||||
private String savePasterImage(Uri uri) {
|
||||
String time = System.nanoTime() + "_" + Math.abs(new Random().nextInt());
|
||||
String fileName = "paster_image_" + time;
|
||||
File pasterFile = TUIMultimediaFileUtil.generateFileName(fileName, new File(TUIConfig.getImageBaseDir()));
|
||||
if (pasterFile == null) {
|
||||
return null;
|
||||
}
|
||||
TUIMultimediaFileUtil.saveFileFromUri(TUIMultimediaPlugin.getAppContext(), uri, pasterFile.getPath());
|
||||
return pasterFile.getPath();
|
||||
}
|
||||
|
||||
private String savePasterIcon(Bitmap bitmap) {
|
||||
String time = System.nanoTime() + "_" + Math.abs(new Random().nextInt());
|
||||
String fileName = "paster_icon_" + time;
|
||||
File pasterFile = TUIMultimediaFileUtil.generateFileName(fileName, new File(TUIConfig.getImageBaseDir()));
|
||||
if (pasterFile == null) {
|
||||
return null;
|
||||
}
|
||||
TUIMultimediaFileUtil.saveBitmap(pasterFile.getPath(), bitmap);
|
||||
return pasterFile.getPath();
|
||||
}
|
||||
|
||||
private Bitmap createIcon(Bitmap image) {
|
||||
int side = Math.max(image.getWidth(), image.getHeight());
|
||||
float scale = USER_PASTER_ICON_MAX_SIZE * 1.0f / side;
|
||||
|
||||
if (scale >= 1.0f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Bitmap.createScaledBitmap(image, (int) (image.getWidth() * scale),
|
||||
(int) (image.getHeight() * scale), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
|
||||
import com.google.android.material.tabs.TabLayout.Tab;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
|
||||
import com.tencent.qcloud.tuikit.timcommon.util.ActivityResultResolver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterType;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class PicturePasterPanelView extends RelativeLayout {
|
||||
|
||||
private final String TAG = PicturePasterPanelView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final PicturePasterManager mPicturePasterManager;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedItem = new TUIMultimediaData<>(null);
|
||||
private final OnTabSelectedListener onTabSelectedListener = new OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(Tab tab) {
|
||||
configTabSelectedStatus(tab, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(Tab tab) {
|
||||
configTabSelectedStatus(tab, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(Tab tab) {
|
||||
}
|
||||
};
|
||||
private PicturePasterTypeAdapter mPasterTypeAdapter;
|
||||
private PicturePasterInfo mPicturePasterInfo;
|
||||
private PicturePasterSelectListener mPicturePasterSelectListener;
|
||||
private final TUIMultimediaDataObserver<ItemPosition> mOnSelectItemChanged = new TUIMultimediaDataObserver<ItemPosition>() {
|
||||
@Override
|
||||
public void onChanged(ItemPosition itemPosition) {
|
||||
PicturePasterItem pasterItem = mPicturePasterManager.getPasterItem(itemPosition);
|
||||
if (pasterItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pasterItem.isFileSelector()) {
|
||||
selectPicturePasterFromLocalFile(itemPosition);
|
||||
} else {
|
||||
if (mPicturePasterSelectListener != null) {
|
||||
mPicturePasterSelectListener
|
||||
.PicturePasterSelectCompeted(mPicturePasterManager.getPaster(itemPosition));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PicturePasterPanelView(@NonNull Context context,
|
||||
@NonNull PicturePasterManager picturePasterManager) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mPicturePasterManager = picturePasterManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
mTuiDataSelectedItem.observe(mOnSelectItemChanged);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
removeAllViews();
|
||||
super.onDetachedFromWindow();
|
||||
mTuiDataSelectedItem.removeObserver(mOnSelectItemChanged);
|
||||
}
|
||||
|
||||
public void selectPaster(PicturePasterSelectListener picturePasterSelectListener) {
|
||||
mPicturePasterSelectListener = picturePasterSelectListener;
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_picture_paster_panel_view, this);
|
||||
mPicturePasterInfo = mPicturePasterManager.getPicturePasterSetInfo();
|
||||
if (mPicturePasterInfo == null) {
|
||||
LiteavLog.i(TAG, "get picture paster set info null");
|
||||
return;
|
||||
}
|
||||
|
||||
ViewPager2 beautyPager = findViewById(R.id.picture_paster_panel_view_pager);
|
||||
mPasterTypeAdapter = new PicturePasterTypeAdapter(mContext, mPicturePasterInfo,
|
||||
mTuiDataSelectedItem);
|
||||
beautyPager.setAdapter(mPasterTypeAdapter);
|
||||
|
||||
TabLayout tabLayout = findViewById(R.id.picture_paster_panel_tab_layout);
|
||||
tabLayout.addOnTabSelectedListener(onTabSelectedListener);
|
||||
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
|
||||
new TabLayoutMediator(tabLayout, beautyPager, this::configTab).attach();
|
||||
}
|
||||
|
||||
private void configTab(@NonNull Tab tab, int position) {
|
||||
if (mPicturePasterInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PicturePasterType typeInfo = mPicturePasterInfo.getPasterType(position);
|
||||
Bitmap pasterIcon = typeInfo != null ? typeInfo.getPasterIcon() : null;
|
||||
if (pasterIcon != null) {
|
||||
configTabImage(tab, pasterIcon);
|
||||
return;
|
||||
}
|
||||
|
||||
configTabText(tab, typeInfo != null ? typeInfo.getTypeName() : "");
|
||||
}
|
||||
|
||||
private void configTabText(Tab tab, String text) {
|
||||
TextView customTextView = new TextView(getContext());
|
||||
customTextView.setGravity(Gravity.CENTER);
|
||||
|
||||
customTextView.setText(text);
|
||||
customTextView.setMaxLines(1);
|
||||
customTextView.setTextColor(
|
||||
TUIMultimediaResourceUtils.getResources().getColor(R.color.multimedia_plugin_record_beauty_type_text_color_normal));
|
||||
|
||||
float textSize = TUIMultimediaResourceUtils.getResources().getDimension(R.dimen.multimedia_plugin_common_text_size);
|
||||
customTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
|
||||
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
|
||||
customTextView.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
|
||||
tab.setCustomView(customTextView);
|
||||
}
|
||||
|
||||
private void configTabImage(Tab tab, Bitmap image) {
|
||||
View tabView = LayoutInflater.from(getContext())
|
||||
.inflate(R.layout.multimedia_plugin_edit_picture_paster_type_image_item, null);
|
||||
ImageView imageView = tabView.findViewById(R.id.paster_type_icon);
|
||||
imageView.setImageBitmap(image);
|
||||
tab.setCustomView(tabView);
|
||||
}
|
||||
|
||||
private void configTabSelectedStatus(Tab tab, boolean isSelected) {
|
||||
View customView = tab.getCustomView();
|
||||
if (customView instanceof TextView) {
|
||||
configTabSelectedText((TextView) customView, isSelected);
|
||||
} else {
|
||||
configTabSelectedImage(customView, isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
private void configTabSelectedText(TextView tabTextView, boolean isSelected) {
|
||||
if (tabTextView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_normal;
|
||||
int textColorFontStyle = Typeface.NORMAL;
|
||||
if (isSelected) {
|
||||
textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_selected;
|
||||
textColorFontStyle = Typeface.BOLD;
|
||||
}
|
||||
|
||||
tabTextView.setTextColor(
|
||||
TUIMultimediaResourceUtils.getResources().getColor(textColorResourceId));
|
||||
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
|
||||
tabTextView.setTypeface(Typeface.create(fontName, textColorFontStyle));
|
||||
}
|
||||
|
||||
private void configTabSelectedImage(View tabView, boolean isSelected) {
|
||||
if (tabView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageView backgroundImage = tabView.findViewById(R.id.paster_type_icon_background);
|
||||
if (isSelected) {
|
||||
backgroundImage.setVisibility(VISIBLE);
|
||||
} else {
|
||||
backgroundImage.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectPicturePasterFromLocalFile(ItemPosition itemPosition) {
|
||||
ActivityResultResolver.getSingleContent((ActivityResultCaller) mContext,
|
||||
new String[]{ActivityResultResolver.CONTENT_TYPE_IMAGE, ActivityResultResolver.CONTENT_TYPE_ALL},
|
||||
new TUIValueCallback<Uri>() {
|
||||
@Override
|
||||
public void onSuccess(Uri uri) {
|
||||
if (mPicturePasterManager.addUserPasterFromUri(uri, itemPosition)) {
|
||||
mPasterTypeAdapter.notifySubDataSetChanged(itemPosition.pasterTypeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public interface PicturePasterSelectListener {
|
||||
|
||||
void PicturePasterSelectCompeted(Bitmap bitmap);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.GridView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data.PicturePasterInfo.ItemPosition;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PicturePasterTypeAdapter extends FragmentStateAdapter {
|
||||
|
||||
private final Context mContext;
|
||||
private final PicturePasterInfo mPasterInfo;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedBeautyItem;
|
||||
private final PasterTypeItemFragment[] mPasterTypeFragmentList;
|
||||
|
||||
public PicturePasterTypeAdapter(@NonNull Context context, @NonNull PicturePasterInfo pasterInfo,
|
||||
@NonNull TUIMultimediaData<ItemPosition> tuiDataSelectItem) {
|
||||
super((FragmentActivity) context);
|
||||
mPasterInfo = pasterInfo;
|
||||
mContext = context;
|
||||
mTuiDataSelectedBeautyItem = tuiDataSelectItem;
|
||||
mPasterTypeFragmentList = new PasterTypeItemFragment[pasterInfo.getPasterTypeSize()];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
PasterTypeItemFragment fragment = new PasterTypeItemFragment(mContext, mPasterInfo, position,
|
||||
mTuiDataSelectedBeautyItem);
|
||||
if (position >= 0 && position < mPasterInfo.getPasterTypeSize()) {
|
||||
mPasterTypeFragmentList[position] = fragment;
|
||||
}
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mPasterInfo.getPasterTypeSize();
|
||||
}
|
||||
|
||||
public void notifySubDataSetChanged(int position) {
|
||||
if (mPasterTypeFragmentList[position] != null) {
|
||||
mPasterTypeFragmentList[position].notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PasterTypeItemFragment extends Fragment {
|
||||
|
||||
private final PicturePasterInfo mPasterInfo;
|
||||
private final Context mContext;
|
||||
private final int mPasterTypeIndex;
|
||||
private final TUIMultimediaData<ItemPosition> mTuiDataSelectedBeautyItem;
|
||||
|
||||
private BaseAdapter mAdapter;
|
||||
|
||||
public PasterTypeItemFragment(Context context, PicturePasterInfo pasterInfo, int typeIndex,
|
||||
TUIMultimediaData<ItemPosition> tuiDataSelectedBeautyItem) {
|
||||
mContext = context;
|
||||
mPasterInfo = pasterInfo;
|
||||
mPasterTypeIndex = typeIndex;
|
||||
mTuiDataSelectedBeautyItem = tuiDataSelectedBeautyItem;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.multimedia_plugin_edit_picture_paster_type_fragment, container, false);
|
||||
GridView gridView = view.findViewById(R.id.grid_view);
|
||||
mAdapter = new PicturePasterItemAdapter(mContext, mPasterInfo, mPasterTypeIndex,
|
||||
mTuiDataSelectedBeautyItem);
|
||||
gridView.setAdapter(mAdapter);
|
||||
return view;
|
||||
}
|
||||
|
||||
public void notifyDataSetChanged() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import java.io.FileWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PicturePasterInfo {
|
||||
|
||||
@SerializedName("paster_type_list")
|
||||
private List<PicturePasterType> mPasterTypeItemList;
|
||||
|
||||
public void addPasterItem(ItemPosition position, PicturePasterItem pasterItemInfo) {
|
||||
if (position.pasterTypeIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPasterTypeItemList == null) {
|
||||
mPasterTypeItemList = new LinkedList<>();
|
||||
}
|
||||
|
||||
while (position.pasterTypeIndex + 1 - mPasterTypeItemList.size() > 0) {
|
||||
mPasterTypeItemList.add(new PicturePasterType());
|
||||
}
|
||||
mPasterTypeItemList.get(position.pasterTypeIndex).addPasterItem(position.pasterItemIndex, pasterItemInfo);
|
||||
}
|
||||
|
||||
public int getPasterTypeSize() {
|
||||
return mPasterTypeItemList != null ? mPasterTypeItemList.size() : 0;
|
||||
}
|
||||
|
||||
public PicturePasterType getPasterType(int pasterTypeIndex) {
|
||||
if (pasterTypeIndex < 0 || pasterTypeIndex >= getPasterTypeSize()) {
|
||||
return null;
|
||||
}
|
||||
return mPasterTypeItemList.get(pasterTypeIndex);
|
||||
}
|
||||
|
||||
public PicturePasterItem getPasterItem(ItemPosition itemPosition) {
|
||||
PicturePasterType typeInfo = getPasterType(itemPosition.pasterTypeIndex);
|
||||
return typeInfo != null ? typeInfo.getPasterItem(itemPosition.pasterItemIndex) : null;
|
||||
}
|
||||
|
||||
public void append(PicturePasterInfo otherSetInfo) {
|
||||
if (otherSetInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<PicturePasterType> otherPasterTypeItemList = otherSetInfo.mPasterTypeItemList;
|
||||
if (otherPasterTypeItemList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < otherPasterTypeItemList.size(); i++) {
|
||||
PicturePasterType typeItemInfo = otherPasterTypeItemList.get(i);
|
||||
if (typeItemInfo == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<PicturePasterItem> itemInfoList = typeItemInfo.getPasterItemList();
|
||||
if (itemInfoList == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PicturePasterType pasterType = getPasterType(i);
|
||||
int firstFileSelectorIndex = pasterType != null ? pasterType.getFirstFileSelectorIndex() : 0;
|
||||
ItemPosition position = new ItemPosition(i, firstFileSelectorIndex + 1);
|
||||
Collections.reverse(itemInfoList);
|
||||
for (PicturePasterItem itemInfo : itemInfoList) {
|
||||
addPasterItem(position, itemInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void saveToJsonFile(String filePath) {
|
||||
Gson gson = new Gson();
|
||||
try {
|
||||
FileWriter fileWriter = new FileWriter(filePath);
|
||||
gson.toJson(this, fileWriter);
|
||||
fileWriter.flush();
|
||||
fileWriter.close();
|
||||
} catch (Exception e) {
|
||||
LiteavLog.e("save json file error", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (mPasterTypeItemList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (PicturePasterType pasterTypeItemInfo : mPasterTypeItemList) {
|
||||
if (pasterTypeItemInfo != null) {
|
||||
pasterTypeItemInfo.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ItemPosition {
|
||||
|
||||
public int pasterTypeIndex;
|
||||
public int pasterItemIndex;
|
||||
|
||||
public ItemPosition(int pasterTypeIndex, int pasterItemIndex) {
|
||||
this.pasterTypeIndex = pasterTypeIndex;
|
||||
this.pasterItemIndex = pasterItemIndex;
|
||||
}
|
||||
|
||||
public boolean equals(ItemPosition other) {
|
||||
return pasterTypeIndex == other.pasterTypeIndex && pasterItemIndex == other.pasterItemIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
|
||||
public class PicturePasterItem {
|
||||
|
||||
private final String TAG = PicturePasterItem.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
@SerializedName("item_image_path")
|
||||
private String mImageFilePath;
|
||||
|
||||
@SerializedName("item_icon_path")
|
||||
private String mIconFilePath;
|
||||
|
||||
@SerializedName("item_is_file_selector")
|
||||
private boolean mIsFileSelector = false;
|
||||
|
||||
transient private Bitmap mImage;
|
||||
transient private Bitmap mIcon;
|
||||
|
||||
public PicturePasterItem(String imageFilePath, String iconFilePath) {
|
||||
mImageFilePath = imageFilePath;
|
||||
mIconFilePath = iconFilePath;
|
||||
}
|
||||
|
||||
public PicturePasterItem(Bitmap pasterImage, Bitmap pasterIcon) {
|
||||
mImage = pasterImage;
|
||||
mIcon = pasterIcon;
|
||||
}
|
||||
|
||||
public Bitmap getPasterImage() {
|
||||
if (mImage == null) {
|
||||
mImage = TUIMultimediaFileUtil.decodeBitmap(mImageFilePath);
|
||||
}
|
||||
return mImage;
|
||||
}
|
||||
|
||||
public Bitmap getPasterIcon() {
|
||||
if (mIcon != null) {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
mIcon = TUIMultimediaFileUtil.decodeBitmap(mIconFilePath);
|
||||
if (mIcon != null) {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
return getPasterImage();
|
||||
}
|
||||
|
||||
public boolean isFileSelector() {
|
||||
return mIsFileSelector;
|
||||
}
|
||||
|
||||
|
||||
public void recycleBitmap() {
|
||||
if (mImage != null) {
|
||||
mImage.recycle();
|
||||
mImage = null;
|
||||
}
|
||||
|
||||
if (mIcon != null) {
|
||||
mIcon.recycle();
|
||||
mIcon = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.picturePaster.data;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PicturePasterType {
|
||||
|
||||
@SerializedName("type_name")
|
||||
private String mTypeName;
|
||||
@SerializedName("type_icon_path")
|
||||
private String mTypeIconPath;
|
||||
@SerializedName("paster_item_list")
|
||||
private List<PicturePasterItem> mPasterItemList;
|
||||
|
||||
transient private Bitmap mIcon;
|
||||
|
||||
public void addPasterItem(int itemIndex, PicturePasterItem pasterItemInfo) {
|
||||
if (itemIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPasterItemList == null) {
|
||||
mPasterItemList = new LinkedList<>();
|
||||
}
|
||||
|
||||
mPasterItemList.add(itemIndex, pasterItemInfo);
|
||||
}
|
||||
|
||||
public PicturePasterItem getPasterItem(int itemIndex) {
|
||||
if (itemIndex < 0 || itemIndex >= mPasterItemList.size()) {
|
||||
return null;
|
||||
}
|
||||
return mPasterItemList.get(itemIndex);
|
||||
}
|
||||
|
||||
public List<PicturePasterItem> getPasterItemList() {
|
||||
return mPasterItemList;
|
||||
}
|
||||
|
||||
public int getFirstFileSelectorIndex() {
|
||||
for (int i = 0; i < mPasterItemList.size(); i++) {
|
||||
PicturePasterItem pasterItemInfo = mPasterItemList.get(i);
|
||||
if (pasterItemInfo != null && pasterItemInfo.isFileSelector()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int getItemSize() {
|
||||
return mPasterItemList.size();
|
||||
}
|
||||
|
||||
public String getTypeName() {
|
||||
if (mTypeName.startsWith(TUIMultimediaResourceUtils.STRING_RESOURCE_PREFIX)) {
|
||||
return TUIMultimediaResourceUtils.getString(mTypeName);
|
||||
}
|
||||
return mTypeName;
|
||||
}
|
||||
|
||||
public Bitmap getPasterIcon() {
|
||||
if (mIcon != null) {
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
mIcon = TUIMultimediaFileUtil.decodeBitmap(mTypeIconPath);
|
||||
return mIcon;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (mPasterItemList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (PicturePasterItem pasterItemInfo : mPasterItemList) {
|
||||
if (pasterItemInfo != null) {
|
||||
pasterItemInfo.recycleBitmap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaScrollViewAdapter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
|
||||
public class SubtitleColorItemAdapter extends TUIMultimediaScrollViewAdapter {
|
||||
|
||||
private static final int[] COLOR_ARRAY = {Color.WHITE, Color.BLACK, Color.RED, Color.GREEN, Color.BLUE, Color.CYAN,
|
||||
Color.DKGRAY, Color.GRAY, Color.LTGRAY, Color.MAGENTA, Color.YELLOW};
|
||||
private static final int CIRCLE_SHAPE_SIZE_DP = 18;
|
||||
private static final int SELECT_CIRCLE_SHAPE_SIZE_DP = 25;
|
||||
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaData<Integer> mTuiDataSelectColor;
|
||||
|
||||
public SubtitleColorItemAdapter(Context context, TUIMultimediaData<Integer> tuiDataSelectColor) {
|
||||
mContext = context;
|
||||
mTuiDataSelectColor = tuiDataSelectColor;
|
||||
mSelectPosition = getColorIndex(tuiDataSelectColor.get());
|
||||
}
|
||||
|
||||
public static int getColorIndex(int color) {
|
||||
for (int i = 0; i < COLOR_ARRAY.length; i++) {
|
||||
if (COLOR_ARRAY[i] == color) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectPosition(int position) {
|
||||
if (mSelectPosition != position) {
|
||||
mSelectPosition = position;
|
||||
if (mTuiDataSelectColor.get() != COLOR_ARRAY[mSelectPosition]) {
|
||||
mTuiDataSelectColor.set(COLOR_ARRAY[mSelectPosition]);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return COLOR_ARRAY.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return COLOR_ARRAY[position];
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
final ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.multimedia_plugin_edit_subtitle_color_item, parent, false);
|
||||
holder = new ViewHolder(convertView);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
holder.innerCircleShape.setImageResource(R.drawable.multimedia_plugin_subtitle_color_selector_circle_shape);
|
||||
GradientDrawable circleShape = (GradientDrawable) holder.innerCircleShape.getDrawable();
|
||||
circleShape.setColor(COLOR_ARRAY[position]);
|
||||
|
||||
int innerCircleShapeSize = (mSelectPosition == position ? SELECT_CIRCLE_SHAPE_SIZE_DP : CIRCLE_SHAPE_SIZE_DP);
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) holder.innerCircleShape
|
||||
.getLayoutParams();
|
||||
layoutParams.width = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize);
|
||||
layoutParams.height = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize);
|
||||
holder.innerCircleShape.setLayoutParams(layoutParams);
|
||||
|
||||
layoutParams = (RelativeLayout.LayoutParams) holder.outerCircleShape.getLayoutParams();
|
||||
layoutParams.width = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize + 4);
|
||||
layoutParams.height = TUIMultimediaResourceUtils.dip2px(mContext, innerCircleShapeSize + 4);
|
||||
holder.outerCircleShape.setLayoutParams(layoutParams);
|
||||
|
||||
convertView.setOnClickListener(v -> {
|
||||
if (mSelectPosition != position) {
|
||||
mSelectPosition = position;
|
||||
mTuiDataSelectColor.set(COLOR_ARRAY[mSelectPosition]);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ImageView outerCircleShape;
|
||||
private final ImageView innerCircleShape;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
outerCircleShape = itemView.findViewById(R.id.outer_color_shape);
|
||||
innerCircleShape = itemView.findViewById(R.id.inner_color_shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class SubtitleInfo {
|
||||
|
||||
public List<String> textList = new LinkedList<>();
|
||||
public int textColor;
|
||||
public int textSize;
|
||||
|
||||
public String getText() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String textItem : textList) {
|
||||
text.append(textItem);
|
||||
}
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
public boolean equals(SubtitleInfo subtitleInfo) {
|
||||
if (subtitleInfo == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (textColor != subtitleInfo.textColor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (textSize != subtitleInfo.textSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return textList.equals(subtitleInfo.textList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIHorizontalScrollView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
|
||||
public class SubtitlePanelView extends RelativeLayout {
|
||||
|
||||
private final static int MAX_SHOW_LINES = 8;
|
||||
private final String TAG = SubtitlePanelView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaData<Integer> mTuiDataSelectedTextColor = new TUIMultimediaData<>(Color.WHITE);
|
||||
private EditText mInputEditText;
|
||||
private View mDecorView;
|
||||
private boolean mIsSoftKeypadShow = false;
|
||||
private SubtitleInputListener mSubtitleInputListener;
|
||||
private final OnGlobalLayoutListener onGlobalLayoutListener = this::softKeypadLayoutListener;
|
||||
|
||||
public SubtitlePanelView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void inputText(SubtitleInfo subtitleInfo, SubtitleInputListener subtitleInputListener) {
|
||||
mSubtitleInputListener = subtitleInputListener;
|
||||
|
||||
String text = subtitleInfo != null ? subtitleInfo.getText() : "";
|
||||
mInputEditText.setText(text);
|
||||
mInputEditText.setSelection(text.length());
|
||||
|
||||
if (subtitleInfo != null) {
|
||||
mInputEditText.setTextColor(subtitleInfo.textColor);
|
||||
mTuiDataSelectedTextColor.set(subtitleInfo.textColor);
|
||||
}
|
||||
|
||||
showInputView();
|
||||
}
|
||||
|
||||
public int getTextViewWidth() {
|
||||
if (mInputEditText != null) {
|
||||
return mInputEditText.getLayoutParams().width;
|
||||
}
|
||||
return 720;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mInputEditText.getLayoutParams();
|
||||
layoutParams.width = getWidth() * 2 / 3;
|
||||
layoutParams.height = LayoutParams.WRAP_CONTENT;
|
||||
layoutParams.topMargin = getHeight() / 6;
|
||||
layoutParams.leftMargin = getWidth() / 6;
|
||||
mInputEditText.setLayoutParams(layoutParams);
|
||||
mInputEditText.requestFocus();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
View root = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_edit_subtitle_input_view, this, true);
|
||||
mDecorView = ((Activity) mContext).getWindow().getDecorView();
|
||||
|
||||
mInputEditText = findViewById(R.id.subtitle_input_edit_text);
|
||||
mInputEditText.setVisibility(VISIBLE);
|
||||
mInputEditText.requestFocus();
|
||||
mInputEditText.setLines(MAX_SHOW_LINES);
|
||||
|
||||
TUIHorizontalScrollView colorSelectScrollItemView = findViewById(R.id.input_text_color_scroll_view);
|
||||
colorSelectScrollItemView.setAdapter(new SubtitleColorItemAdapter(mContext, mTuiDataSelectedTextColor));
|
||||
mTuiDataSelectedTextColor.observe(color -> {
|
||||
mInputEditText.setTextColor(color);
|
||||
colorSelectScrollItemView.setClicked(SubtitleColorItemAdapter.getColorIndex(color));
|
||||
});
|
||||
|
||||
root.setOnClickListener(v -> completeTextInput(true));
|
||||
findViewById(R.id.subtitle_input_confirm).setOnClickListener(v -> completeTextInput(true));
|
||||
findViewById(R.id.subtitle_input_cancel).setOnClickListener(v -> completeTextInput(false));
|
||||
}
|
||||
|
||||
private void softKeypadLayoutListener() {
|
||||
if (mDecorView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect r = new Rect();
|
||||
mDecorView.getWindowVisibleDisplayFrame(r);
|
||||
int screenHeight = mDecorView.getRootView().getHeight();
|
||||
int keypadHeight = screenHeight - r.bottom;
|
||||
if (keypadHeight < screenHeight * 0.15) {
|
||||
if (mIsSoftKeypadShow) {
|
||||
mIsSoftKeypadShow = false;
|
||||
completeTextInput(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
mIsSoftKeypadShow = true;
|
||||
softKeypadLayoutListener(keypadHeight);
|
||||
}
|
||||
|
||||
private void softKeypadLayoutListener(int keypadHeight) {
|
||||
LinearLayout colorLayout = findViewById(R.id.color_select_layout);
|
||||
if (colorLayout != null) {
|
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) colorLayout.getLayoutParams();
|
||||
layoutParams.bottomMargin = keypadHeight + 30;
|
||||
colorLayout.setLayoutParams(layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeTextInput(boolean isConfirm) {
|
||||
LiteavLog.i(TAG, (isConfirm ? "confirm" : "cancel") + " text input");
|
||||
SubtitleInfo subtitleInfo = null;
|
||||
if (isConfirm) {
|
||||
subtitleInfo = getSubtitleInfo();
|
||||
}
|
||||
hideInputView();
|
||||
if (mSubtitleInputListener != null) {
|
||||
mSubtitleInputListener.onSubtitleInputCompeted(subtitleInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private SubtitleInfo getSubtitleInfo() {
|
||||
SubtitleInfo subtitleInfo = new SubtitleInfo();
|
||||
String text = mInputEditText.getText().toString();
|
||||
int lineCount = mInputEditText.getLineCount();
|
||||
for (int i = 0; i < lineCount; i++) {
|
||||
int start = mInputEditText.getLayout().getLineStart(i);
|
||||
int end = mInputEditText.getLayout().getLineEnd(i);
|
||||
String lineText = text.substring(start, end);
|
||||
subtitleInfo.textList.add(lineText);
|
||||
}
|
||||
subtitleInfo.textColor = mTuiDataSelectedTextColor.get();
|
||||
subtitleInfo.textSize = (int) mInputEditText.getTextSize();
|
||||
return subtitleInfo;
|
||||
}
|
||||
|
||||
private void hideInputView() {
|
||||
mIsSoftKeypadShow = false;
|
||||
if (mDecorView != null) {
|
||||
mDecorView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
|
||||
}
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) mContext
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (inputMethodManager != null) {
|
||||
inputMethodManager.hideSoftInputFromWindow(getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void showInputView() {
|
||||
setVisibility(VISIBLE);
|
||||
if (mDecorView != null) {
|
||||
mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
|
||||
}
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) mContext
|
||||
.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (inputMethodManager != null) {
|
||||
inputMethodManager.showSoftInput(mInputEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
public interface SubtitleInputListener {
|
||||
|
||||
void onSubtitleInputCompeted(SubtitleInfo subtitleInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
|
||||
|
||||
import static com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPasterInfo.PasterType.SUBTITLE_PASTER;
|
||||
import static com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView.Status.STATUS_EDIT;
|
||||
import static com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView.Status.STATUS_INIT;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.floatlayer.FloatLayerViewGroup;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class SubtitlePasterFloatLayerView extends FloatLayerView {
|
||||
|
||||
private final String TAG = SubtitlePasterFloatLayerView.class + "_" + hashCode();
|
||||
private final SubtitlePanelView mSubtitlePanelView;
|
||||
private final FloatLayerViewGroup mFloatLayerViewGroup;
|
||||
|
||||
private SubtitleInfo mSubtitleTextInfo;
|
||||
|
||||
private final TUIMultimediaDataObserver<Status> mOnStatusChanged = status -> {
|
||||
switch (status) {
|
||||
case STATUS_EDIT:
|
||||
setVisibility(INVISIBLE);
|
||||
onEditText();
|
||||
break;
|
||||
case STATUS_DELETE:
|
||||
onDeleted();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
public SubtitlePasterFloatLayerView(Context context, FloatLayerViewGroup floatLayerViewGroup,
|
||||
SubtitleInfo tuiDataTextInfo, SubtitlePanelView tuiDataSubtitlePanelView) {
|
||||
super(context);
|
||||
mSubtitlePanelView = tuiDataSubtitlePanelView;
|
||||
mSubtitleTextInfo = tuiDataTextInfo;
|
||||
setPasterType(SUBTITLE_PASTER);
|
||||
mFloatLayerViewGroup = floatLayerViewGroup;
|
||||
mFloatLayerViewGroup.addFloatLayerView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
generatorSubtitlePaster(mSubtitleTextInfo);
|
||||
addObserve();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
removeObserve();
|
||||
}
|
||||
|
||||
private void onEditText() {
|
||||
mSubtitlePanelView.inputText(mSubtitleTextInfo, subtitleInfo -> {
|
||||
if (subtitleInfo != null) {
|
||||
mSubtitleTextInfo = subtitleInfo;
|
||||
generatorSubtitlePaster(subtitleInfo);
|
||||
}
|
||||
invalidate();
|
||||
setVisibility(VISIBLE);
|
||||
mTuiDataStatus.set(STATUS_INIT);
|
||||
});
|
||||
}
|
||||
|
||||
private void generatorSubtitlePaster(SubtitleInfo subtitleInfo) {
|
||||
LiteavLog.i(TAG, "set subtitle text");
|
||||
if (subtitleInfo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (subtitleInfo.getText().isEmpty()) {
|
||||
onDeleted();
|
||||
return;
|
||||
}
|
||||
LiteavLog.i(TAG, "set subtitle text = " + subtitleInfo.getText());
|
||||
SubtitlePasterGenerator pasterGenerator = new SubtitlePasterGenerator();
|
||||
pasterGenerator.setSubtitleInfo(subtitleInfo);
|
||||
Bitmap bitmap = pasterGenerator.createTextBitmap();
|
||||
setImageBitmap(bitmap);
|
||||
|
||||
int subtitleWidth = mSubtitlePanelView.getTextViewWidth();
|
||||
if (bitmap != null) {
|
||||
float scale = Math.max(bitmap.getHeight() * 1.0f / getSubtitleMaxHeight(),
|
||||
bitmap.getWidth() * 1.0f / subtitleWidth);
|
||||
|
||||
scale = Math.max(scale, pasterGenerator.getScale());
|
||||
if (mTuiDataStatus.get() != STATUS_EDIT) {
|
||||
setImageScale(1.0f / scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onDeleted() {
|
||||
mFloatLayerViewGroup.removeFloatLayerView(this);
|
||||
}
|
||||
|
||||
private void addObserve() {
|
||||
mTuiDataStatus.observe(mOnStatusChanged);
|
||||
}
|
||||
|
||||
private void removeObserve() {
|
||||
mTuiDataStatus.removeObserver(mOnStatusChanged);
|
||||
}
|
||||
|
||||
private int getSubtitleMaxHeight() {
|
||||
return (int) (TUIMultimediaResourceUtils.getScreenSize(TUIMultimediaPlugin.getAppContext()).y * 2 / 3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.subtitlePaster;
|
||||
|
||||
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 com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SubtitlePasterGenerator {
|
||||
|
||||
private static final float TEXT_BITMAP_SCALE = 4.0f;
|
||||
private static final int LINE_SPACING_DP = 2;
|
||||
private final String TAG = SubtitlePasterGenerator.class.getSimpleName() + "_" + hashCode();
|
||||
private Bitmap mPasterBitmap;
|
||||
private Paint mPaint;
|
||||
private SubtitleInfo mParams;
|
||||
|
||||
public void setSubtitleInfo(@NonNull SubtitleInfo params) {
|
||||
mParams = params;
|
||||
if (mPasterBitmap != null && mPasterBitmap.isRecycled()) {
|
||||
return;
|
||||
}
|
||||
initPaint();
|
||||
|
||||
if (mPasterBitmap == null && !mParams.getText().isEmpty()) {
|
||||
int lineTextHeight = (int) (getFontHeight() +
|
||||
TUIMultimediaResourceUtils.dip2px(TUIMultimediaPlugin.getAppContext(), LINE_SPACING_DP));
|
||||
int height = lineTextHeight * mParams.textList.size();
|
||||
int width = 0;
|
||||
for (String text : mParams.textList) {
|
||||
int lineWidth = (int) mPaint.measureText(text);
|
||||
if (lineWidth > width) {
|
||||
width = lineWidth;
|
||||
}
|
||||
}
|
||||
LiteavLog.i(TAG, "subtitle bitmap width = " + width + " height = " + height);
|
||||
mPasterBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return TEXT_BITMAP_SCALE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Bitmap createTextBitmap() {
|
||||
if (mPasterBitmap == null || mPasterBitmap.isRecycled()) {
|
||||
return null;
|
||||
}
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
mPasterBitmap.getWidth(), mPasterBitmap.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
canvas.drawBitmap(mPasterBitmap, 0, 0, mPaint);
|
||||
drawText(canvas);
|
||||
if (!mPasterBitmap.isRecycled()) {
|
||||
mPasterBitmap.recycle();
|
||||
mPasterBitmap = null;
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void drawText(@NonNull Canvas canvas) {
|
||||
List<TextParams> list = locateText();
|
||||
for (TextParams params : list) {
|
||||
canvas.drawText(params.text, params.x, params.y, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<TextParams> locateText() {
|
||||
List<TextParams> list = new ArrayList<>();
|
||||
List<String> text = mParams.textList;
|
||||
float baseX = 0;
|
||||
float baseY = getFontBaseHeight()
|
||||
- TUIMultimediaResourceUtils.dip2px(TUIMultimediaPlugin.getAppContext(), LINE_SPACING_DP) * 1.0f / 2;
|
||||
int lineTextHeight = (int) (getFontHeight() +
|
||||
TUIMultimediaResourceUtils.dip2px(TUIMultimediaPlugin.getAppContext(), LINE_SPACING_DP));
|
||||
for (int i = 0; i < text.size(); i++) {
|
||||
list.add(new TextParams(text.get(i), baseX, baseY + lineTextHeight * i));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void initPaint() {
|
||||
mPaint = new Paint();
|
||||
mPaint.setColor(mParams.textColor != 0 ? mParams.textColor : Color.WHITE);
|
||||
mPaint.setTextSize(mParams.textSize * TEXT_BITMAP_SCALE);
|
||||
mPaint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
private float getFontHeight() {
|
||||
Paint.FontMetrics metrics = new Paint.FontMetrics();
|
||||
mPaint.getFontMetrics(metrics);
|
||||
return metrics.bottom - metrics.top;
|
||||
}
|
||||
|
||||
private float getFontBaseHeight() {
|
||||
Paint.FontMetrics metrics = new Paint.FontMetrics();
|
||||
mPaint.getFontMetrics(metrics);
|
||||
return metrics.bottom - metrics.top - 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
|
||||
import com.tencent.qcloud.tuicore.TUICore;
|
||||
import com.tencent.qcloud.tuikit.tuichat.interfaces.AlbumPickerListener;
|
||||
import com.tencent.qcloud.tuikit.tuichat.interfaces.IAlbumPicker;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaPlugin;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaMediaProcessor;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker.AlbumPickerActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class TUIMultimediaAlbumPicker extends IAlbumPicker {
|
||||
private static final String TAG = "TUIMultimediaAlbumPicker";
|
||||
|
||||
@Override
|
||||
public void pickMedia(ActivityResultCaller activityResultCaller, AlbumPickerListener listener) {
|
||||
if (activityResultCaller == null || listener == null) {
|
||||
return;
|
||||
}
|
||||
TUICore.startActivityForResult(activityResultCaller, AlbumPickerActivity.class, null, result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
||||
ArrayList<Uri> uris = result.getData().getParcelableArrayListExtra("data");
|
||||
ArrayList<Uri> transcodeData = result.getData().getParcelableArrayListExtra("transcodeData");
|
||||
if ((uris == null || uris.isEmpty()) && (transcodeData == null || transcodeData.isEmpty())) {
|
||||
listener.onCancel();
|
||||
return;
|
||||
}
|
||||
if (transcodeData != null && !transcodeData.isEmpty()) {
|
||||
for (Uri uri : transcodeData) {
|
||||
listener.onOriginalMediaPicked(uri);
|
||||
}
|
||||
TUIMultimediaMediaProcessor.getInstance().transcodeMedia(transcodeData, new TUIMultimediaMediaProcessor.TUIMultimediaMediaTranscodeListener() {
|
||||
@Override
|
||||
public void onTranscodeFinished(TUIMultimediaMediaProcessor.TranscodeResult transcodeResult) {
|
||||
if (transcodeResult.errorCode == 0) {
|
||||
listener.onFinished(transcodeResult.originalUri, transcodeResult.transcodeMediaUri);
|
||||
} else {
|
||||
listener.onFinished(transcodeResult.originalUri, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTranscodeProgress(Uri originalUri, int transcodeProgress) {
|
||||
listener.onProgress(originalUri, transcodeProgress);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (uris != null && !uris.isEmpty()) {
|
||||
for (Uri uri : uris) {
|
||||
listener.onFinished(uri, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.permission;
|
||||
|
||||
import static android.Manifest.permission;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import com.tencent.imsdk.common.IMContext;
|
||||
import com.tencent.imsdk.common.SystemUtil;
|
||||
import com.tencent.qcloud.tuicore.ServiceInitializer;
|
||||
import com.tencent.qcloud.tuicore.TUIConstants;
|
||||
import com.tencent.qcloud.tuicore.TUICore;
|
||||
import com.tencent.qcloud.tuicore.permission.PermissionCallback;
|
||||
import com.tencent.qcloud.tuicore.permission.PermissionRequester;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
|
||||
public class ImageVideoPermissionRequester {
|
||||
private static final String TAG = "ImageVideoPermissionReq";
|
||||
|
||||
public static boolean checkPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (PermissionRequester.newInstance(permission.READ_MEDIA_IMAGES, permission.READ_MEDIA_VIDEO).has()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
if (PermissionRequester.newInstance("android.permission.READ_MEDIA_VISUAL_USER_SELECTED").has()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return PermissionRequester.newInstance(permission.READ_EXTERNAL_STORAGE).has();
|
||||
}
|
||||
|
||||
public static boolean checkVisualPermission() {
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
return PermissionRequester.newInstance("android.permission.READ_MEDIA_VISUAL_USER_SELECTED").has();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void requestPermissions(PermissionCallback callback) {
|
||||
String title = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_media_title, getAppName());
|
||||
String description = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_media_reason);
|
||||
String tip = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_media_tip, getAppName());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
PermissionRequester.newInstance("android.permission.READ_MEDIA_VISUAL_USER_SELECTED", permission.READ_MEDIA_IMAGES, permission.READ_MEDIA_VIDEO)
|
||||
.title(title)
|
||||
.description(description)
|
||||
.settingsTip(tip)
|
||||
.callback(callback)
|
||||
.request();
|
||||
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.TIRAMISU) {
|
||||
PermissionRequester.newInstance(permission.READ_MEDIA_IMAGES, permission.READ_MEDIA_VIDEO)
|
||||
.title(title)
|
||||
.description(description)
|
||||
.settingsTip(tip)
|
||||
.callback(callback)
|
||||
.request();
|
||||
} else {
|
||||
title = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_storage_title, getAppName());
|
||||
description = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_storage_reason);
|
||||
tip = ServiceInitializer.getAppContext().getString(R.string.multimedia_plugin_request_permission_storage_tip, getAppName());
|
||||
PermissionRequester.newInstance(permission.READ_EXTERNAL_STORAGE)
|
||||
.title(title)
|
||||
.description(description)
|
||||
.settingsTip(tip)
|
||||
.callback(callback)
|
||||
.request();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAppName() {
|
||||
String appName = "";
|
||||
Context context = ServiceInitializer.getAppContext();
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
|
||||
packageManager.getApplicationLabel(applicationInfo);
|
||||
CharSequence labelCharSequence = applicationInfo.loadLabel(packageManager);
|
||||
if (labelCharSequence != null && labelCharSequence.length() > 0) {
|
||||
appName = labelCharSequence.toString();
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "getAppName exception:" + e.getMessage());
|
||||
}
|
||||
|
||||
return appName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.tencent.qcloud.tuicore.interfaces.TUIValueCallback;
|
||||
import com.tencent.qcloud.tuicore.permission.PermissionCallback;
|
||||
import com.tencent.qcloud.tuikit.tuichat.util.TUIChatLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BaseBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BucketBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.VideoBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.AlbumClickListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.OnSelectedBucketChangedListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.ui.picker.AlbumGridView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.permission.ImageVideoPermissionRequester;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.previewer.AlbumPreviewFragment;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumPickerActivity extends AppCompatActivity {
|
||||
private static final String TAG = "AlbumSelectorActivity";
|
||||
private static final int DEFAULT_ANIM_DURATION = 200;
|
||||
|
||||
private View closeButton;
|
||||
|
||||
private AlbumGridView albumGridView;
|
||||
|
||||
private BucketListPop bucketListPop;
|
||||
private View bucketView;
|
||||
private TextView bucketNameTv;
|
||||
private ImageView bucketNameIv;
|
||||
private TextView fullImageTv;
|
||||
private ImageView fullImageCheckbox;
|
||||
private TextView previewButton;
|
||||
private TextView sendTv;
|
||||
|
||||
private boolean inAnimation = false;
|
||||
private boolean isFullImage = false;
|
||||
private BucketBean currentBucket;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
checkPermissionAndLoad();
|
||||
|
||||
setContentView(R.layout.multimedia_plugin_picker_selector_layout);
|
||||
|
||||
setNavigationBar();
|
||||
|
||||
closeButton = findViewById(R.id.close_button);
|
||||
albumGridView = findViewById(R.id.album_grid_view);
|
||||
bucketView = findViewById(R.id.bucket_name_layout);
|
||||
bucketNameTv = findViewById(R.id.bucket_name_tv);
|
||||
bucketNameIv = findViewById(R.id.bucket_name_iv);
|
||||
bucketListPop = findViewById(R.id.bucket_list_pop);
|
||||
fullImageTv = findViewById(R.id.full_image_text);
|
||||
fullImageCheckbox = findViewById(R.id.full_image_check_box);
|
||||
previewButton = findViewById(R.id.preview_button);
|
||||
sendTv = findViewById(R.id.send_button);
|
||||
|
||||
setOnClickListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
|
||||
private void checkPermissionAndLoad() {
|
||||
if (ImageVideoPermissionRequester.checkPermission()) {
|
||||
loadImages();
|
||||
} else {
|
||||
ImageVideoPermissionRequester.requestPermissions(new PermissionCallback() {
|
||||
@Override
|
||||
public void onGranted() {
|
||||
loadImages();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setNavigationBar() {
|
||||
int color = getResources().getColor(R.color.multimedia_plugin_picker_dark_color);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
getWindow().setStatusBarColor(color);
|
||||
getWindow().setNavigationBarColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
private void setOnClickListener() {
|
||||
albumGridView.setClickListener(new AlbumClickListener() {
|
||||
@Override
|
||||
public void onClick(BaseBean bean) {
|
||||
startPreview(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectChanged(BaseBean bean) {
|
||||
setFooterButtonState();
|
||||
}
|
||||
});
|
||||
|
||||
bucketListPop.setOnSelectedBucketChangedListener(new OnSelectedBucketChangedListener() {
|
||||
@Override
|
||||
public void onSelectedBucketChanged(BucketBean bucketBean) {
|
||||
currentBucket = bucketBean;
|
||||
bucketNameTv.setText(bucketBean.bucketName);
|
||||
albumGridView.setBucket(bucketBean);
|
||||
}
|
||||
});
|
||||
closeButton.setOnClickListener((v) -> {
|
||||
if (bucketListPop.isShowing()) {
|
||||
hideBucketList();
|
||||
return;
|
||||
}
|
||||
finish();
|
||||
});
|
||||
|
||||
bucketView.setOnClickListener((v) -> {
|
||||
if (inAnimation) {
|
||||
return;
|
||||
}
|
||||
if (bucketListPop.isShowing()) {
|
||||
hideBucketList();
|
||||
} else {
|
||||
showBucketList();
|
||||
}
|
||||
});
|
||||
previewButton.setOnClickListener((v) -> {
|
||||
if (albumGridView.getSelectedPhotoList().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
startPreview(null);
|
||||
});
|
||||
|
||||
fullImageTv.setOnClickListener((v) -> toggleFullImage());
|
||||
fullImageCheckbox.setOnClickListener((v) -> toggleFullImage());
|
||||
sendTv.setOnClickListener((v) -> setSelectionResult());
|
||||
}
|
||||
|
||||
private void setFooterButtonState() {
|
||||
List<BaseBean> selectedSet = albumGridView.getSelectedPhotoList();
|
||||
if (!selectedSet.isEmpty()) {
|
||||
previewButton.setTextColor(getResources().getColor(R.color.multimedia_plugin_picker_white_color));
|
||||
sendTv.setEnabled(true);
|
||||
sendTv.setText(getResources().getString(R.string.multimedia_plugin_picker_send) + "(" + selectedSet.size() + ")");
|
||||
previewButton.setText(getResources().getString(R.string.multimedia_plugin_picker_preview) + "(" + selectedSet.size() + ")");
|
||||
} else {
|
||||
previewButton.setTextColor(getResources().getColor(R.color.multimedia_plugin_picker_light_gray));
|
||||
sendTv.setEnabled(false);
|
||||
sendTv.setText(R.string.multimedia_plugin_picker_send);
|
||||
previewButton.setText(R.string.multimedia_plugin_picker_preview);
|
||||
}
|
||||
}
|
||||
|
||||
private void startPreview(BaseBean previewBean) {
|
||||
AlbumPreviewFragment fragment = new AlbumPreviewFragment();
|
||||
fragment.setAlbumGridView(albumGridView);
|
||||
fragment.setFullImage(this.isFullImage);
|
||||
fragment.setPreviewBean(previewBean);
|
||||
fragment.setBucketBean(currentBucket);
|
||||
fragment.setOnSelectClickListener((v) -> setFooterButtonState());
|
||||
fragment.setOnSendClickListener((v) -> setSelectionResult());
|
||||
fragment.setOnFullImageClickListener((v) -> toggleFullImage());
|
||||
fragment.show(getSupportFragmentManager(), "AlbumPreview");
|
||||
}
|
||||
|
||||
private void toggleFullImage() {
|
||||
this.isFullImage = !this.isFullImage;
|
||||
if (isFullImage) {
|
||||
fullImageCheckbox.setSelected(true);
|
||||
} else {
|
||||
fullImageCheckbox.setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void showBucketList() {
|
||||
bucketListPop.show();
|
||||
startAnim();
|
||||
}
|
||||
|
||||
private void hideBucketList() {
|
||||
bucketListPop.dismiss();
|
||||
startAnim();
|
||||
}
|
||||
|
||||
private void startAnim() {
|
||||
inAnimation = true;
|
||||
bucketNameIv.animate().rotationBy(180).setDuration(DEFAULT_ANIM_DURATION).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationPause(Animator animation) {
|
||||
inAnimation = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
inAnimation = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
inAnimation = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadImages() {
|
||||
|
||||
TUIMultimediaCore.getBucketBeanList(new TUIValueCallback<List<BucketBean>>() {
|
||||
@Override
|
||||
public void onSuccess(List<BucketBean> bucketBeanList) {
|
||||
if (bucketBeanList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
currentBucket = bucketBeanList.get(0);
|
||||
bucketListPop.setSelectedBucketBean(currentBucket);
|
||||
bucketListPop.setData(bucketBeanList);
|
||||
albumGridView.setBucket(currentBucket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int errorCode, String errorMessage) {
|
||||
TUIChatLog.e(TAG, "getBucketBeanList error: " + errorMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSelectionResult() {
|
||||
boolean isFullImage = this.isFullImage;
|
||||
List<BaseBean> selectedList = albumGridView.getSelectedPhotoList();
|
||||
if (isFullImage) {
|
||||
ArrayList<Uri> originalUris = new ArrayList<>(selectedList.size());
|
||||
for (BaseBean baseBean : selectedList) {
|
||||
originalUris.add(baseBean.getFinalUri());
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putParcelableArrayListExtra("data", originalUris);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
} else {
|
||||
ArrayList<Uri> transcodeList = new ArrayList<>();
|
||||
ArrayList<Uri> originalUris = new ArrayList<>();
|
||||
for (BaseBean baseBean : selectedList) {
|
||||
if (baseBean instanceof VideoBean) {
|
||||
if (baseBean.editedUri == null) {
|
||||
transcodeList.add(baseBean.uri);
|
||||
} else {
|
||||
originalUris.add(baseBean.editedUri);
|
||||
}
|
||||
} else {
|
||||
originalUris.add(baseBean.getFinalUri());
|
||||
}
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putParcelableArrayListExtra("data", originalUris);
|
||||
intent.putParcelableArrayListExtra("transcodeData", transcodeList);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.picker;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BaseBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BucketBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.OnSelectedBucketChangedListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BucketListPop extends FrameLayout {
|
||||
private static final int DEFAULT_ANIM_DURATION = 200;
|
||||
|
||||
private RecyclerView bucketList;
|
||||
private BucketAdapter adapter;
|
||||
private LinearLayoutManager layoutManager;
|
||||
|
||||
private List<BucketBean> data;
|
||||
private OnSelectedBucketChangedListener onSelectedBucketChangedListener;
|
||||
|
||||
private int listHeight = -1;
|
||||
private boolean isShowing = false;
|
||||
private BucketBean bucketBean;
|
||||
|
||||
public BucketListPop(@NonNull Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public BucketListPop(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public BucketListPop(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setSelectedBucketBean(BucketBean bucketBean) {
|
||||
this.bucketBean = bucketBean;
|
||||
}
|
||||
|
||||
public void setData(List<BucketBean> data) {
|
||||
this.data = data;
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setOnSelectedBucketChangedListener(OnSelectedBucketChangedListener listener) {
|
||||
this.onSelectedBucketChangedListener = listener;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
View view = LayoutInflater.from(getContext()).inflate(R.layout.multimedia_plugin_picker_bucket_pop_layout, this, true);
|
||||
bucketList = view.findViewById(R.id.bucket_list);
|
||||
SimpleItemAnimator animator = (SimpleItemAnimator) bucketList.getItemAnimator();
|
||||
if (animator != null) {
|
||||
animator.setSupportsChangeAnimations(false);
|
||||
}
|
||||
bucketList.setItemAnimator(null);
|
||||
adapter = new BucketAdapter();
|
||||
layoutManager = new LinearLayoutManager(getContext());
|
||||
bucketList.setAdapter(adapter);
|
||||
bucketList.setLayoutManager(layoutManager);
|
||||
view.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
setAlpha(0);
|
||||
}
|
||||
|
||||
public void show() {
|
||||
setVisibility(VISIBLE);
|
||||
post(() -> {
|
||||
if (listHeight < 0) {
|
||||
listHeight = bucketList.getHeight();
|
||||
}
|
||||
changeHeightWithAnim(0, listHeight, null);
|
||||
isShowing = true;
|
||||
this.animate().alpha(1).setDuration(DEFAULT_ANIM_DURATION).start();
|
||||
});
|
||||
}
|
||||
|
||||
public void dismiss() {
|
||||
changeHeightWithAnim(bucketList.getHeight(), 0, () -> {
|
||||
isShowing = false;
|
||||
setVisibility(GONE);
|
||||
});
|
||||
this.animate().alpha(0).setDuration(DEFAULT_ANIM_DURATION).start();
|
||||
}
|
||||
|
||||
private void changeHeightWithAnim(int from, int to, Runnable onFinished) {
|
||||
ValueAnimator animator = ValueAnimator.ofInt(from, to);
|
||||
animator.setDuration(DEFAULT_ANIM_DURATION);
|
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator animation) {
|
||||
ViewGroup.LayoutParams layoutParams = bucketList.getLayoutParams();
|
||||
layoutParams.height = (int) animation.getAnimatedValue();
|
||||
bucketList.setLayoutParams(layoutParams);
|
||||
}
|
||||
});
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
if (onFinished != null) {
|
||||
onFinished.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (onFinished != null) {
|
||||
onFinished.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
animator.start();
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return isShowing;
|
||||
}
|
||||
|
||||
class BucketAdapter extends RecyclerView.Adapter<BucketViewHolder> {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public BucketViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(getContext()).inflate(R.layout.multimedia_plugin_picker_bucket_pop_list_item_layout, parent, false);
|
||||
return new BucketViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull BucketViewHolder holder, int position) {
|
||||
BucketBean bucketBean = data.get(position);
|
||||
holder.bucketBean = bucketBean;
|
||||
String bucketName = bucketBean.bucketName;
|
||||
List<BaseBean> beanList = bucketBean.albumList;
|
||||
if (BucketListPop.this.bucketBean == bucketBean) {
|
||||
holder.selectedIcon.setVisibility(VISIBLE);
|
||||
} else {
|
||||
holder.selectedIcon.setVisibility(GONE);
|
||||
}
|
||||
int size;
|
||||
BaseBean bean = null;
|
||||
if (beanList == null || beanList.isEmpty()) {
|
||||
size = 0;
|
||||
} else {
|
||||
size = beanList.size();
|
||||
bean = beanList.get(0);
|
||||
Glide.with(holder.bucketIcon).asBitmap().load(bean).into(holder.bucketIcon);
|
||||
}
|
||||
holder.bucketNameTv.setText(bucketName + "");
|
||||
holder.bucketCountTv.setText("(" + size + ")");
|
||||
holder.itemView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
BucketListPop.this.bucketBean = bucketBean;
|
||||
if (onSelectedBucketChangedListener != null) {
|
||||
onSelectedBucketChangedListener.onSelectedBucketChanged(bucketBean);
|
||||
}
|
||||
notifyItemRangeChanged(0, getItemCount());
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (data == null) {
|
||||
return 0;
|
||||
}
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
|
||||
static class BucketViewHolder extends RecyclerView.ViewHolder {
|
||||
ImageView bucketIcon;
|
||||
TextView bucketNameTv;
|
||||
TextView bucketCountTv;
|
||||
ImageView selectedIcon;
|
||||
BucketBean bucketBean;
|
||||
|
||||
public BucketViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
bucketIcon = itemView.findViewById(R.id.bucket_icon);
|
||||
bucketNameTv = itemView.findViewById(R.id.bucket_name_tv);
|
||||
bucketCountTv = itemView.findViewById(R.id.bucket_count_tv);
|
||||
selectedIcon = itemView.findViewById(R.id.selected_icon);
|
||||
Drawable drawable = selectedIcon.getDrawable();
|
||||
if (drawable instanceof VectorDrawable) {
|
||||
int color = itemView.getContext().getResources().getColor(
|
||||
TUIThemeManager.getAttrResId(itemView.getContext(), com.tencent.qcloud.tuicore.R.attr.core_primary_color));
|
||||
ColorFilter filter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
drawable.setColorFilter(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.pick.ui.previewer;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.VideoView;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BucketBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.interfaces.AlbumClickListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.ui.picker.AlbumGridView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.ui.previewer.SelectedPhotosView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.utils.TUIMultimediaCoreUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaMediaProcessor;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.BaseBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.ImageBean;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.pick.beans.VideoBean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumPreviewFragment extends DialogFragment {
|
||||
private static final String TAG = "AlbumPreviewActivity";
|
||||
private static final int TYPE_IMAGE = 0;
|
||||
private static final int TYPE_VIDEO = 1;
|
||||
|
||||
private View rootView;
|
||||
private ImageView backButton;
|
||||
private TextView titleTv;
|
||||
private View selectButton;
|
||||
private ImageView selectCheckbox;
|
||||
private TextView editButton;
|
||||
private TextView sendButton;
|
||||
private ImageView fullImageCheckbox;
|
||||
private View fullImageButton;
|
||||
|
||||
private PreviewAdapter adapter;
|
||||
private ViewPager2 viewPager2;
|
||||
private SelectedPhotosView selectedPhotosView;
|
||||
private BaseBean currentBean;
|
||||
private AlbumGridView albumGridView;
|
||||
private View.OnClickListener onSendClickListener;
|
||||
private View.OnClickListener onFullImageClickListener;
|
||||
private View.OnClickListener onSelectClickListener;
|
||||
private List<BaseBean> previewList;
|
||||
private boolean isFullImage = false;
|
||||
private BaseBean previewBean;
|
||||
private BucketBean bucketBean;
|
||||
|
||||
public void setOnSendClickListener(View.OnClickListener onSendClickListener) {
|
||||
this.onSendClickListener = onSendClickListener;
|
||||
}
|
||||
|
||||
public void setOnFullImageClickListener(View.OnClickListener onFullImageClickListener) {
|
||||
this.onFullImageClickListener = onFullImageClickListener;
|
||||
}
|
||||
|
||||
public void setOnSelectClickListener(View.OnClickListener onSelectClickListener) {
|
||||
this.onSelectClickListener = onSelectClickListener;
|
||||
}
|
||||
|
||||
public void setBucketBean(BucketBean bucketBean) {
|
||||
this.bucketBean = bucketBean;
|
||||
}
|
||||
|
||||
public void setPreviewBean(BaseBean previewBean) {
|
||||
this.previewBean = previewBean;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
setRetainInstance(true);
|
||||
Dialog dialog = new AppCompatDialog(getContext(), R.style.AlbumTransparentPopStyle);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.setBackgroundDrawable(new ColorDrawable());
|
||||
window.getDecorView().setPadding(0, 0, 0, 0);
|
||||
int color = getResources().getColor(R.color.multimedia_plugin_picker_dark_color);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
window.setStatusBarColor(color);
|
||||
window.setNavigationBarColor(color);
|
||||
}
|
||||
WindowManager.LayoutParams lp = window.getAttributes();
|
||||
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
window.setAttributes(lp);
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
rootView = inflater.inflate(R.layout.multimedia_plugin_picker_preview_layout, container, false);
|
||||
backButton = rootView.findViewById(R.id.back_button);
|
||||
backButton.getDrawable().setAutoMirrored(true);
|
||||
titleTv = rootView.findViewById(R.id.preview_title);
|
||||
selectedPhotosView = rootView.findViewById(R.id.selected_list);
|
||||
selectButton = rootView.findViewById(R.id.select_button);
|
||||
selectCheckbox = rootView.findViewById(R.id.select_checkbox);
|
||||
sendButton = rootView.findViewById(R.id.send_button);
|
||||
fullImageCheckbox = rootView.findViewById(R.id.full_image_checkbox);
|
||||
fullImageButton = rootView.findViewById(R.id.full_image_button);
|
||||
editButton = rootView.findViewById(R.id.edit_button);
|
||||
setData();
|
||||
setOnClickListener();
|
||||
return rootView;
|
||||
}
|
||||
|
||||
public void setAlbumGridView(AlbumGridView albumGridView) {
|
||||
this.albumGridView = albumGridView;
|
||||
}
|
||||
|
||||
public void setFullImage(boolean fullImage) {
|
||||
this.isFullImage = fullImage;
|
||||
}
|
||||
|
||||
private void setData() {
|
||||
BaseBean previewBean = this.previewBean;
|
||||
currentBean = previewBean;
|
||||
if (previewBean == null) {
|
||||
previewList = albumGridView.getSelectedPhotoList();
|
||||
currentBean = previewList.get(0);
|
||||
} else {
|
||||
previewList = bucketBean.albumList;
|
||||
}
|
||||
adapter = new PreviewAdapter();
|
||||
adapter.data = previewList;
|
||||
viewPager2 = rootView.findViewById(R.id.view_pager);
|
||||
viewPager2.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
|
||||
viewPager2.setAdapter(adapter);
|
||||
|
||||
int index = previewList.indexOf(previewBean);
|
||||
titleTv.setText(index + 1 + "/" + previewList.size());
|
||||
viewPager2.setCurrentItem(index, false);
|
||||
viewPager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
titleTv.setText(position + 1 + "/" + previewList.size());
|
||||
currentBean = previewList.get(position);
|
||||
selectedPhotosView.switchPhoto(currentBean);
|
||||
setSelectCheckboxBg();
|
||||
}
|
||||
});
|
||||
selectedPhotosView.setPhotoList(albumGridView.getSelectedPhotoList());
|
||||
selectedPhotosView.switchPhoto(currentBean);
|
||||
selectedPhotosView.setAlbumClickListener(new AlbumClickListener() {
|
||||
@Override
|
||||
public void onClick(BaseBean bean) {
|
||||
switchToPage(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectChanged(BaseBean bean) {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
setFullImageCheckboxState();
|
||||
setSelectCheckboxBg();
|
||||
}
|
||||
|
||||
private void setFullImageCheckboxState() {
|
||||
if (this.isFullImage) {
|
||||
fullImageCheckbox.setSelected(true);
|
||||
} else {
|
||||
fullImageCheckbox.setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void switchToPage(BaseBean bean) {
|
||||
List<BaseBean> beanList = adapter.data;
|
||||
int index = beanList.indexOf(bean);
|
||||
if (index >= 0) {
|
||||
currentBean = bean;
|
||||
viewPager2.setCurrentItem(index, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setOnClickListener() {
|
||||
backButton.setOnClickListener((v) -> dismiss());
|
||||
selectButton.setOnClickListener((v) -> toggleSelected());
|
||||
fullImageButton.setOnClickListener((v) -> toggleFullImage());
|
||||
sendButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (albumGridView.getSelectedPhotoList().isEmpty()) {
|
||||
albumGridView.setSelected(currentBean, true);
|
||||
}
|
||||
if (onSendClickListener != null) {
|
||||
onSendClickListener.onClick(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
selectedPhotosView.setClickListener(new AlbumClickListener() {
|
||||
@Override
|
||||
public void onClick(BaseBean bean) {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
switchToPage(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectChanged(BaseBean bean) {
|
||||
|
||||
}
|
||||
});
|
||||
editButton.setOnClickListener((v) -> {
|
||||
Context context = getContext();
|
||||
if (!(context instanceof ActivityResultCaller) || !isAdded()) {
|
||||
return;
|
||||
}
|
||||
Uri editUri = currentBean.getFinalUri();
|
||||
TUIMultimediaMediaProcessor.getInstance().editMedia(context, editUri, uri -> {
|
||||
if (uri != null) {
|
||||
for (BaseBean baseBean : previewList) {
|
||||
if (baseBean.getFinalUri() == editUri) {
|
||||
baseBean.editedUri = uri;
|
||||
albumGridView.freshPhoto(baseBean);
|
||||
selectedPhotosView.freshPhoto(baseBean);
|
||||
adapter.freshPhoto(baseBean);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleFullImage() {
|
||||
this.isFullImage = !this.isFullImage;
|
||||
setFullImageCheckboxState();
|
||||
if (onFullImageClickListener != null) {
|
||||
onFullImageClickListener.onClick(fullImageButton);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSelected() {
|
||||
boolean valid = TUIMultimediaCoreUtil.checkSize(currentBean.getFinalUri());
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (currentBean.isSelected) {
|
||||
albumGridView.setSelected(currentBean, false);
|
||||
selectedPhotosView.removePhoto(currentBean);
|
||||
} else {
|
||||
albumGridView.setSelected(currentBean, true);
|
||||
selectedPhotosView.addPhoto(currentBean);
|
||||
}
|
||||
if (onSelectClickListener != null) {
|
||||
onSelectClickListener.onClick(selectButton);
|
||||
}
|
||||
setSelectCheckboxBg();
|
||||
}
|
||||
|
||||
private void setSelectCheckboxBg() {
|
||||
if (currentBean.isSelected) {
|
||||
selectCheckbox.setBackgroundResource(R.drawable.multimedia_plugin_picker_preview_selected_checkbox_bg);
|
||||
selectCheckbox.setImageResource(R.drawable.multimedia_plugin_picker_preview_selected_tick_icon);
|
||||
} else {
|
||||
selectCheckbox.setBackgroundResource(R.drawable.multimedia_plugin_picker_select_gray_ring_bg);
|
||||
selectCheckbox.setImageResource(0);
|
||||
}
|
||||
List<BaseBean> selectedSet = albumGridView.getSelectedPhotoList();
|
||||
if (selectedSet.isEmpty()) {
|
||||
sendButton.setText(getString(R.string.multimedia_plugin_picker_send));
|
||||
} else {
|
||||
sendButton.setText(getString(R.string.multimedia_plugin_picker_send) + "(" + selectedSet.size() + ")");
|
||||
}
|
||||
if (!selectedSet.isEmpty()) {
|
||||
selectedPhotosView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
selectedPhotosView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
static class PreviewAdapter extends RecyclerView.Adapter<PreviewViewHolder> {
|
||||
public List<BaseBean> data;
|
||||
|
||||
@Override
|
||||
public PreviewViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view;
|
||||
if (viewType == TYPE_VIDEO) {
|
||||
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.multimedia_plugin_picker_preview_video_item_layout, parent, false);
|
||||
return new PreviewVideoHolder(view);
|
||||
} else {
|
||||
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.multimedia_plugin_picker_preview_image_item_layout, parent, false);
|
||||
return new PreviewImageHolder(view);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreviewViewHolder holder, int position) {
|
||||
BaseBean bean = data.get(position);
|
||||
holder.bind(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
if (data == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return data.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
BaseBean baseBean = data.get(position);
|
||||
if (baseBean instanceof VideoBean) {
|
||||
return TYPE_VIDEO;
|
||||
} else {
|
||||
return TYPE_IMAGE;
|
||||
}
|
||||
}
|
||||
|
||||
public void freshPhoto(BaseBean bean) {
|
||||
if (data != null && bean != null) {
|
||||
int index = data.indexOf(bean);
|
||||
if (index != -1) {
|
||||
notifyItemChanged(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class PreviewViewHolder extends RecyclerView.ViewHolder {
|
||||
public PreviewViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public void bind(BaseBean bean) {}
|
||||
}
|
||||
|
||||
static class PreviewImageHolder extends PreviewViewHolder {
|
||||
ImageView imageView;
|
||||
public PreviewImageHolder(View itemView) {
|
||||
super(itemView);
|
||||
imageView = itemView.findViewById(R.id.image_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(BaseBean bean) {
|
||||
Glide.with(itemView.getContext())
|
||||
.load(bean.getFinalUri())
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.dontTransform()
|
||||
.dontAnimate()
|
||||
.into(imageView);
|
||||
}
|
||||
}
|
||||
|
||||
static class PreviewVideoHolder extends PreviewViewHolder {
|
||||
VideoView videoView;
|
||||
View playButton;
|
||||
ImageView previewImage;
|
||||
public PreviewVideoHolder(View itemView) {
|
||||
super(itemView);
|
||||
videoView = itemView.findViewById(R.id.video_view);
|
||||
playButton = itemView.findViewById(R.id.play_button);
|
||||
previewImage = itemView.findViewById(R.id.video_preview_image_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(BaseBean bean) {
|
||||
itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@NonNull View v) {}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@NonNull View v) {
|
||||
videoView.resume();
|
||||
playButton.setVisibility(View.VISIBLE);
|
||||
previewImage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
Uri uri = bean.getFinalUri();
|
||||
Glide.with(itemView.getContext()).load(uri).into(previewImage);
|
||||
videoView.setVideoURI(uri);
|
||||
playButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
playButton.setVisibility(View.GONE);
|
||||
previewImage.setVisibility(View.GONE);
|
||||
videoView.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFullScreen;
|
||||
|
||||
public class TUIMultimediaRecordActivity extends TUIMultimediaFullScreen {
|
||||
|
||||
private final String TAG = TUIMultimediaRecordActivity.class.getSimpleName() + "_" + hashCode();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
LiteavLog.i(TAG, "onCreate");
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.multimedia_plugin_record_activity);
|
||||
TUIMultimediaRecordFragment fragment = new TUIMultimediaRecordFragment(this);
|
||||
fragment.setArguments(getIntent().getExtras());
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.fl_record_fragment_container, fragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.liteav.beauty.TXBeautyManager;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants.VideoQuality;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
|
||||
import com.tencent.rtmp.TXLiveConstants;
|
||||
import com.tencent.rtmp.ui.TXCloudVideoView;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
import com.tencent.ugc.TXRecordCommon.ITXVideoRecordListener;
|
||||
import com.tencent.ugc.TXRecordCommon.TXRecordResult;
|
||||
import com.tencent.ugc.TXUGCRecord;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class TUIMultimediaRecordCore {
|
||||
|
||||
private final static int PROCESS_TIME_INTERVAL_MS = 40;
|
||||
private final static float DEFAULT_VOLUME = 0.5f;
|
||||
private final static int DEFAULT_VIDEO_GOP = 3;
|
||||
private final static int LOW_QUALITY_BITRATE = 1000;
|
||||
private final static int MEDIUM_QUALITY_BITRATE = 3000;
|
||||
private final static int HIGH_QUALITY_BITRATE = 5000;
|
||||
private final static int EDIT_BITRATE = 12000;
|
||||
private final static int LOW_QUALITY_FPS = 25;
|
||||
private final static int MEDIUM_QUALITY_FPS = 25;
|
||||
private final static int HIGH_QUALITY_FPS = 30;
|
||||
private final static int LOW_QUALITY_RESOLUTION = TXRecordCommon.VIDEO_RESOLUTION_720_1280;
|
||||
private final static int MEDIUM_QUALITY_RESOLUTION = TXRecordCommon.VIDEO_RESOLUTION_720_1280;
|
||||
private final static int HIGH_QUALITY_RESOLUTION = TXRecordCommon.VIDEO_RESOLUTION_1080_1920;
|
||||
private final static int DEFAULT_MAX_RECORD_DURATION_MS = 15000;
|
||||
private final static int DEFAULT_MIN_RECORD_DURATION_MS = 2000;
|
||||
|
||||
private final String TAG = TUIMultimediaRecordCore.class.getSimpleName() + "_" + hashCode();
|
||||
private final TXUGCRecord mRecordSDK;
|
||||
private final RecordInfo mRecordInfo;
|
||||
|
||||
private TXBeautyManager mBeautyManager;
|
||||
private Bitmap mFilterBitmap;
|
||||
private VideoQuality mVideoQuality = VideoQuality.LOW;
|
||||
private Boolean mIsNeedEdit = true;
|
||||
private int mMaxDuration = DEFAULT_MAX_RECORD_DURATION_MS;
|
||||
private int mMinDuration = DEFAULT_MIN_RECORD_DURATION_MS;
|
||||
|
||||
private Timer mRecordProcessTimer;
|
||||
private long mRecordProgress;
|
||||
|
||||
private final ITXVideoRecordListener mVideoRecordListener = new ITXVideoRecordListener() {
|
||||
@Override
|
||||
public void onRecordEvent(int eventCode, Bundle bundle) {
|
||||
LiteavLog.i(TAG, "onRecordEvent code = " + eventCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordProgress(long progress) {
|
||||
LiteavLog.i(TAG, "onRecordProgress progress = " + progress);
|
||||
if (progress > 0) {
|
||||
startProcessTimer(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordComplete(TXRecordResult txRecordResult) {
|
||||
LiteavLog.i(TAG, "onRecordComplete retCode = " + txRecordResult);
|
||||
stopProcessTimer();
|
||||
if (txRecordResult.retCode == TXRecordCommon.RECORD_RESULT_OK ||
|
||||
txRecordResult.retCode == TXRecordCommon.RECORD_RESULT_OK_REACHED_MAXDURATION) {
|
||||
mRecordInfo.recordResult.isSuccess = true;
|
||||
mRecordInfo.recordResult.type = TUIMultimediaConstants.RECORD_TYPE_VIDEO;
|
||||
mRecordInfo.recordResult.path = txRecordResult.videoPath;
|
||||
} else {
|
||||
mRecordInfo.recordResult.isSuccess = false;
|
||||
}
|
||||
mRecordInfo.recordResult.code = txRecordResult.retCode;
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.STOP);
|
||||
mRecordSDK.getPartsManager().deleteAllParts();
|
||||
toggleTorch(false);
|
||||
LiteavLog.i(TAG, "onRecordComplete finish");
|
||||
}
|
||||
};
|
||||
|
||||
public TUIMultimediaRecordCore(Context context, RecordInfo recordInfo) {
|
||||
mRecordSDK = TXUGCRecord.getInstance(context);
|
||||
mRecordInfo = recordInfo;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
LiteavLog.i(TAG, "release");
|
||||
mRecordSDK.setVideoRecordListener(null);
|
||||
mRecordSDK.getPartsManager().deleteAllParts();
|
||||
setFilterAndStrength(null, 0);
|
||||
setBeautyStyleAndLevel(0, 0);
|
||||
setWhitenessLevel(0);
|
||||
setRuddyLevel(0);
|
||||
mRecordSDK.release();
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.IDLE);
|
||||
stopProcessTimer();
|
||||
mBeautyManager = null;
|
||||
}
|
||||
|
||||
public void startCameraPreview(TXCloudVideoView videoView) {
|
||||
Log.i(TAG, "start camera preview. videoView " + videoView);
|
||||
TXRecordCommon.TXUGCCustomConfig customConfig = new TXRecordCommon.TXUGCCustomConfig();
|
||||
initRecordConfig(customConfig);
|
||||
|
||||
mRecordSDK.startCameraCustomPreview(customConfig, videoView);
|
||||
mRecordSDK.setRecordSpeed(TXRecordCommon.RECORD_SPEED_NORMAL);
|
||||
mRecordSDK.setHomeOrientation(TXLiveConstants.VIDEO_ANGLE_HOME_DOWN);
|
||||
mRecordSDK.setRenderRotation(TXLiveConstants.RENDER_ROTATION_PORTRAIT);
|
||||
mRecordSDK.setAspectRatio(TXRecordCommon.VIDEO_ASPECT_RATIO_9_16);
|
||||
mRecordSDK.setVideoRenderMode(TXRecordCommon.VIDEO_RENDER_MODE_ADJUST_RESOLUTION);
|
||||
mRecordSDK.setMicVolume(DEFAULT_VOLUME);
|
||||
}
|
||||
|
||||
public void stopCameraPreview() {
|
||||
LiteavLog.i(TAG, "stop camera preview");
|
||||
mRecordSDK.stopCameraPreview();
|
||||
}
|
||||
|
||||
public void startRecord(String videoFilePath) {
|
||||
if (mRecordInfo.tuiDataRecordStatus.get() == RecordStatus.RECORDING) {
|
||||
LiteavLog.e(TAG, "start record, but current status is recording.");
|
||||
return;
|
||||
}
|
||||
LiteavLog.i(TAG, "start record. vide file Path is " + videoFilePath);
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.RECORDING);
|
||||
mRecordSDK.setVideoRecordListener(mVideoRecordListener);
|
||||
mRecordSDK.startRecord(videoFilePath, null);
|
||||
}
|
||||
|
||||
public void stopRecord() {
|
||||
LiteavLog.i(TAG, "stop record.current stats is " + mRecordInfo.tuiDataRecordStatus.get());
|
||||
if (mRecordInfo.tuiDataRecordStatus.get() != RecordStatus.RECORDING) {
|
||||
return;
|
||||
}
|
||||
mRecordSDK.stopRecord();
|
||||
stopProcessTimer();
|
||||
}
|
||||
|
||||
public void setAspectRatio(int aspectRatio) {
|
||||
LiteavLog.i(TAG, "set aspect ration is " + aspectRatio);
|
||||
mRecordInfo.tuiDataAspectRatio.set(aspectRatio);
|
||||
mRecordSDK.setAspectRatio(aspectRatio);
|
||||
}
|
||||
|
||||
public void takePhoto(String photoFilePath) {
|
||||
LiteavLog.i(TAG, "take photo.current status is " + mRecordInfo.tuiDataRecordStatus.get());
|
||||
if (mRecordInfo.tuiDataRecordStatus.get() == RecordStatus.RECORDING) {
|
||||
return;
|
||||
}
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.TAKE_PHOTOING);
|
||||
mRecordSDK.snapshot(bitmap -> {
|
||||
LiteavLog.i(TAG, "onSnapshot. snap file Path is " + photoFilePath);
|
||||
toggleTorch(false);
|
||||
FileOutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = new FileOutputStream(photoFilePath);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
|
||||
mRecordInfo.recordResult.type = TUIMultimediaConstants.RECORD_TYPE_PHOTO;
|
||||
mRecordInfo.recordResult.path = photoFilePath;
|
||||
mRecordInfo.recordResult.isSuccess = true;
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.STOP);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void switchCamera(boolean isFront) {
|
||||
LiteavLog.i(TAG, "switch camera. is front " + isFront);
|
||||
if (isFront) {
|
||||
toggleTorch(false);
|
||||
}
|
||||
mRecordInfo.tuiDataIsFontCamera.set(isFront);
|
||||
mRecordSDK.switchCamera(isFront);
|
||||
}
|
||||
|
||||
public boolean toggleTorch(boolean enable) {
|
||||
LiteavLog.i(TAG, "toggleTorch enable is " + enable);
|
||||
mRecordInfo.tuiDataIsFlashOn.set(enable);
|
||||
return mRecordSDK.toggleTorch(enable);
|
||||
}
|
||||
|
||||
public int getMaxZoom() {
|
||||
return mRecordSDK.getMaxZoom();
|
||||
}
|
||||
|
||||
public boolean setZoom(int value) {
|
||||
LiteavLog.i(TAG, "set zoom value = " + value);
|
||||
return mRecordSDK.setZoom(value);
|
||||
}
|
||||
|
||||
public void setFocusPosition(float eventX, float eventY) {
|
||||
LiteavLog.i(TAG, "set focus position [" + eventX + "," + eventY + "]");
|
||||
mRecordSDK.setFocusPosition(eventX, eventY);
|
||||
}
|
||||
|
||||
public void setBeautyStyleAndLevel(int style, float level) {
|
||||
LiteavLog.i(TAG, "set beauty style:" + style + " level:" + level);
|
||||
TXBeautyManager beautyManager = getBeautyManager();
|
||||
if (beautyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (style >= 3 || style < 0) {
|
||||
return;
|
||||
}
|
||||
beautyManager.setBeautyStyle(style);
|
||||
beautyManager.setBeautyLevel(level);
|
||||
}
|
||||
|
||||
public void setWhitenessLevel(float whitenessLevel) {
|
||||
LiteavLog.i(TAG, "set whiteness level:" + whitenessLevel);
|
||||
TXBeautyManager beautyManager = getBeautyManager();
|
||||
if (beautyManager != null) {
|
||||
beautyManager.setWhitenessLevel(whitenessLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRuddyLevel(float ruddyLevel) {
|
||||
LiteavLog.i(TAG, "set ruddy level:" + ruddyLevel);
|
||||
TXBeautyManager beautyManager = getBeautyManager();
|
||||
if (beautyManager != null) {
|
||||
beautyManager.setRuddyLevel(ruddyLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFilterAndStrength(Bitmap bitmap, int strength) {
|
||||
LiteavLog.i(TAG, "set filter bitmap:" + bitmap + " strength:" + strength);
|
||||
TXBeautyManager beautyManager = getBeautyManager();
|
||||
if (beautyManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFilterBitmap != bitmap) {
|
||||
beautyManager.setFilter(bitmap);
|
||||
mFilterBitmap = bitmap;
|
||||
}
|
||||
|
||||
beautyManager.setFilterStrength(strength / 10.0f);
|
||||
}
|
||||
|
||||
public void setFilter(Bitmap leftBitmap, float leftIntensity, Bitmap rightBitmap,
|
||||
float rightIntensity, float leftRatio) {
|
||||
LiteavLog.i(TAG, "set filter. left intensity is " + leftIntensity
|
||||
+ " right intensity is " + " left ration is " + leftRatio);
|
||||
mRecordSDK.setFilter(leftBitmap, leftIntensity, rightBitmap, rightIntensity, leftRatio);
|
||||
mFilterBitmap = null;
|
||||
}
|
||||
|
||||
public void setMinDuration(int duration) {
|
||||
mMinDuration = duration;
|
||||
}
|
||||
|
||||
public int getMaxDuration() {
|
||||
return mMaxDuration;
|
||||
}
|
||||
|
||||
public void setMaxDuration(int duration) {
|
||||
mMaxDuration = duration;
|
||||
}
|
||||
|
||||
public void setVideoQuality(VideoQuality videoQuality) {
|
||||
LiteavLog.i(TAG, "set video quality " + videoQuality);
|
||||
mVideoQuality = videoQuality;
|
||||
}
|
||||
|
||||
public void setIsNeedEdit(Boolean isNeedEdit) {
|
||||
mIsNeedEdit = isNeedEdit;
|
||||
}
|
||||
|
||||
private void initRecordConfig(TXRecordCommon.TXUGCCustomConfig customConfig) {
|
||||
setBaseVideoEncodeParamWithQuality(mVideoQuality, customConfig);
|
||||
|
||||
customConfig.videoGop = DEFAULT_VIDEO_GOP;
|
||||
customConfig.profile = TXRecordCommon.RECORD_PROFILE_HIGH;
|
||||
|
||||
customConfig.maxDuration = mMaxDuration;
|
||||
customConfig.minDuration = mMinDuration;
|
||||
customConfig.touchFocus = false;
|
||||
customConfig.needEdit = false;
|
||||
customConfig.isFront = mRecordInfo.tuiDataIsFontCamera.get();
|
||||
}
|
||||
|
||||
private void setBaseVideoEncodeParamWithQuality(VideoQuality quality,
|
||||
TXRecordCommon.TXUGCCustomConfig customConfig) {
|
||||
switch (quality) {
|
||||
case HIGH:
|
||||
customConfig.videoFps = HIGH_QUALITY_FPS;
|
||||
customConfig.videoResolution = HIGH_QUALITY_RESOLUTION;
|
||||
customConfig.videoBitrate = mIsNeedEdit ? EDIT_BITRATE : HIGH_QUALITY_BITRATE;
|
||||
break;
|
||||
case MEDIUM:
|
||||
customConfig.videoFps = MEDIUM_QUALITY_FPS;
|
||||
customConfig.videoResolution = MEDIUM_QUALITY_RESOLUTION;
|
||||
customConfig.videoBitrate = mIsNeedEdit ? EDIT_BITRATE : MEDIUM_QUALITY_BITRATE;
|
||||
break;
|
||||
case LOW:
|
||||
default:
|
||||
customConfig.videoFps = LOW_QUALITY_FPS;
|
||||
customConfig.videoResolution = LOW_QUALITY_RESOLUTION;
|
||||
customConfig.videoBitrate = mIsNeedEdit ? EDIT_BITRATE : LOW_QUALITY_BITRATE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startProcessTimer(long progress) {
|
||||
if (mRecordProcessTimer != null) {
|
||||
return;
|
||||
}
|
||||
mRecordProgress = progress;
|
||||
mRecordProcessTimer = new Timer();
|
||||
mRecordProcessTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
mRecordProgress += PROCESS_TIME_INTERVAL_MS;
|
||||
mRecordInfo.tuiDataRecordProcess.set(mRecordProgress * 1.0f / mMaxDuration);
|
||||
}
|
||||
}, PROCESS_TIME_INTERVAL_MS, PROCESS_TIME_INTERVAL_MS);
|
||||
}
|
||||
|
||||
private void stopProcessTimer() {
|
||||
if (mRecordProcessTimer != null) {
|
||||
mRecordProcessTimer.cancel();
|
||||
mRecordProcessTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private TXBeautyManager getBeautyManager() {
|
||||
if (mBeautyManager != null) {
|
||||
return mBeautyManager;
|
||||
}
|
||||
|
||||
if (mRecordSDK == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mBeautyManager = mRecordSDK.getBeautyManager();
|
||||
if (mBeautyManager != null) {
|
||||
mBeautyManager.setBeautyStyle(TXBeautyManager.TXBeautyStyleSmooth);
|
||||
}
|
||||
return mBeautyManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaPictureEditFragment;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.edit.TUIMultimediaVideoEditFragment;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordResult;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.RecordFunctionView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.RecordVideoView;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
|
||||
public class TUIMultimediaRecordFragment extends Fragment {
|
||||
|
||||
private final String TAG = TUIMultimediaRecordFragment.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mTUIMultimediaRecordCore;
|
||||
private final RecordInfo mRecordInfo;
|
||||
|
||||
private RecordVideoView mRecordVideoView;
|
||||
private RecordFunctionView mRecordFunctionView;
|
||||
private RelativeLayout mVideoViewContainer;
|
||||
private RelativeLayout mRecordFunctionContainer;
|
||||
private boolean isFlashOnWhenPause;
|
||||
|
||||
private final TUIMultimediaDataObserver<RecordStatus> mRecordStatusObserver = new TUIMultimediaDataObserver<RecordStatus>() {
|
||||
@Override
|
||||
public void onChanged(RecordStatus recordStatus) {
|
||||
LiteavLog.i(TAG, "record status onChanged. current status is " + recordStatus
|
||||
+ " record result : " + mRecordInfo.recordResult.isSuccess);
|
||||
if (recordStatus == RecordStatus.STOP && mRecordInfo.recordResult.isSuccess) {
|
||||
editRecordFile(mRecordInfo.recordResult);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public TUIMultimediaRecordFragment(Context context) {
|
||||
mContext = context;
|
||||
mRecordInfo = new RecordInfo();
|
||||
mTUIMultimediaRecordCore = new TUIMultimediaRecordCore(context, mRecordInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initParameters();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View contentView = inflater.inflate(R.layout.multimedia_plugin_record_fragment, container, false);
|
||||
mVideoViewContainer = contentView.findViewById(R.id.rl_video_view_container);
|
||||
mRecordFunctionContainer = contentView.findViewById(R.id.rl_function_view_container);
|
||||
addObserver();
|
||||
addChildView();
|
||||
return contentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
LiteavLog.i(TAG, "onDestroyView");
|
||||
super.onDestroyView();
|
||||
removeChildView();
|
||||
removeObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
LiteavLog.i(TAG, "onDestroy");
|
||||
super.onDestroy();
|
||||
mTUIMultimediaRecordCore.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
mTUIMultimediaRecordCore.toggleTorch(isFlashOnWhenPause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
isFlashOnWhenPause = mRecordInfo.tuiDataIsFlashOn.get();
|
||||
mTUIMultimediaRecordCore.toggleTorch(false);
|
||||
}
|
||||
|
||||
private void addObserver() {
|
||||
mRecordInfo.tuiDataRecordStatus.observe(mRecordStatusObserver);
|
||||
}
|
||||
|
||||
private void removeObserver() {
|
||||
mRecordInfo.tuiDataRecordStatus.removeObserver(mRecordStatusObserver);
|
||||
}
|
||||
|
||||
private void initParameters() {
|
||||
Bundle arguments = getArguments();
|
||||
if (arguments != null) {
|
||||
mRecordInfo.recodeType = arguments.getInt(TUIMultimediaConstants.PARAM_NAME_RECORD_TYPE,
|
||||
TUIMultimediaConstants.RECORD_TYPE_VIDEO);
|
||||
}
|
||||
mTUIMultimediaRecordCore.setMaxDuration(TUIMultimediaIConfig.getInstance().getMaxRecordDurationMs());
|
||||
mTUIMultimediaRecordCore.setMinDuration(TUIMultimediaIConfig.getInstance().getMinRecordDurationMs());
|
||||
mTUIMultimediaRecordCore.setVideoQuality(TUIMultimediaIConfig.getInstance().getVideoQuality());
|
||||
mTUIMultimediaRecordCore.setIsNeedEdit(true);
|
||||
}
|
||||
|
||||
private void removeChildView() {
|
||||
mVideoViewContainer.removeAllViews();
|
||||
mRecordFunctionContainer.removeAllViews();
|
||||
}
|
||||
|
||||
private void addChildView() {
|
||||
mVideoViewContainer.removeAllViews();
|
||||
if (mRecordVideoView == null) {
|
||||
mRecordVideoView = new RecordVideoView(mContext, mTUIMultimediaRecordCore, mRecordInfo);
|
||||
}
|
||||
mVideoViewContainer
|
||||
.addView(mRecordVideoView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
|
||||
mRecordFunctionContainer.removeAllViews();
|
||||
if (mRecordFunctionView == null) {
|
||||
mRecordFunctionView = new RecordFunctionView(mContext, mTUIMultimediaRecordCore, mRecordInfo);
|
||||
}
|
||||
mRecordFunctionContainer
|
||||
.addView(mRecordFunctionView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
}
|
||||
|
||||
private void editRecordFile(RecordResult recordResult) {
|
||||
LiteavLog.i(TAG,"editRecordFile file path: " + recordResult.path);
|
||||
Fragment fragment;
|
||||
if (recordResult.type == TUIMultimediaConstants.RECORD_TYPE_PHOTO) {
|
||||
fragment = new TUIMultimediaPictureEditFragment(mContext);
|
||||
} else {
|
||||
fragment = new TUIMultimediaVideoEditFragment(mContext);
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_PATH, recordResult.path);
|
||||
bundle.putFloat(TUIMultimediaConstants.PARAM_NAME_EDIT_FILE_RATIO,
|
||||
convertAspectRation(mRecordInfo.tuiDataAspectRatio.get()));
|
||||
bundle.putBoolean(TUIMultimediaConstants.PARAM_NAME_IS_RECODE_FILE, true);
|
||||
|
||||
fragment.setArguments(bundle);
|
||||
((AppCompatActivity) mContext).getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.fl_record_fragment_container, fragment)
|
||||
.addToBackStack(null).commit();
|
||||
}
|
||||
|
||||
private float convertAspectRation(int aspectRatio) {
|
||||
switch (aspectRatio) {
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_16_9:
|
||||
return 16.0f / 9.0f;
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_3_4:
|
||||
return 3.0f / 4.0f;
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_4_3:
|
||||
return 4.0f / 3.0f;
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_1_1:
|
||||
return 1.0f;
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_9_16:
|
||||
default:
|
||||
return 9.0f / 16.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import com.tencent.qcloud.tuicore.TUICore;
|
||||
import com.tencent.qcloud.tuikit.timcommon.util.FileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuichat.interfaces.IMultimediaRecorder;
|
||||
import com.tencent.qcloud.tuikit.tuichat.interfaces.MultimediaRecorderListener;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
|
||||
public class TUIMultimediaRecorder extends IMultimediaRecorder {
|
||||
|
||||
@Override
|
||||
public void openRecorder(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
|
||||
Bundle bundle = new Bundle();
|
||||
openMultimediaRecord(activityResultCaller, bundle, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openCamera(ActivityResultCaller activityResultCaller, MultimediaRecorderListener listener) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(TUIMultimediaConstants.PARAM_NAME_RECORD_TYPE, TUIMultimediaConstants.RECORD_TYPE_PHOTO);
|
||||
openMultimediaRecord(activityResultCaller, bundle, listener);
|
||||
}
|
||||
|
||||
private void openMultimediaRecord(ActivityResultCaller activityResultCaller, Bundle bundle,MultimediaRecorderListener listener) {
|
||||
if (activityResultCaller == null || listener == null) {
|
||||
return;
|
||||
}
|
||||
TUICore.startActivityForResult(activityResultCaller, TUIMultimediaRecordActivity.class, bundle, result -> {
|
||||
Bundle resultBundle = null;
|
||||
if (result.getData() != null) {
|
||||
resultBundle = result.getData().getExtras();
|
||||
}
|
||||
|
||||
String recordPath = null;
|
||||
if (resultBundle != null) {
|
||||
recordPath = resultBundle.getString(TUIMultimediaConstants.PARAM_NAME_EDITED_FILE_PATH);
|
||||
}
|
||||
|
||||
Uri recordUri = null;
|
||||
if (recordPath != null) {
|
||||
recordUri = FileUtil.getUriFromPath(recordPath);
|
||||
}
|
||||
|
||||
if (recordUri != null) {
|
||||
listener.onSuccess(recordUri);
|
||||
} else {
|
||||
listener.onFailed(-1, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import java.util.List;
|
||||
|
||||
public class BeautyInfo {
|
||||
|
||||
private static final String DEFAULT_BEAUTY_DATA = "file:///asset/config/beauty_info_default_data.json";
|
||||
public final TUIMultimediaData<BeautyItem> tuiDataSelectedBeautyItem = new TUIMultimediaData<>(null);
|
||||
private final String TAG = BeautyInfo.class.getSimpleName() + "_" + hashCode();
|
||||
@SerializedName("beauty_type_list")
|
||||
private List<BeautyType> mBeautyTypeList;
|
||||
|
||||
public static BeautyInfo CreateDefaultBeautyInfo() {
|
||||
LiteavLog.i("BeautyInfo", "create default beautyInfo");
|
||||
String json = TUIMultimediaFileUtil.readTextFromFile(DEFAULT_BEAUTY_DATA);
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(json, BeautyInfo.class);
|
||||
}
|
||||
|
||||
public BeautyType getBeautyType(int typeIndex) {
|
||||
if (mBeautyTypeList == null || typeIndex >= mBeautyTypeList.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mBeautyTypeList.get(typeIndex);
|
||||
}
|
||||
|
||||
public void setSelectedItemIndex(int typeIndex, int itemIndex) {
|
||||
LiteavLog.i(TAG, "set selected item index.type index = " + typeIndex + " item index = " + itemIndex);
|
||||
BeautyType BeautyType = getBeautyType(typeIndex);
|
||||
if (BeautyType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeautyType.setSelectedItemIndex(itemIndex);
|
||||
BeautyItem beautyItem = BeautyType.getSelectBeautyItem();
|
||||
if (beautyItem != tuiDataSelectedBeautyItem.get()) {
|
||||
tuiDataSelectedBeautyItem.set(beautyItem);
|
||||
}
|
||||
}
|
||||
|
||||
public int getItemTypeIndex(BeautyItem beautyItem) {
|
||||
for (int i = 0; i < mBeautyTypeList.size(); i++) {
|
||||
if (mBeautyTypeList.get(i).isContainItem(beautyItem)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getBeautyTypeSize() {
|
||||
return mBeautyTypeList != null ? mBeautyTypeList.size() : 0;
|
||||
}
|
||||
|
||||
public int getItemSize(int typeIndex) {
|
||||
BeautyType beautyType = getBeautyType(typeIndex);
|
||||
return beautyType != null ? beautyType.getItemSize() : 0;
|
||||
}
|
||||
|
||||
public int getTypeIndexWithType(BeautyInnerType beautyType) {
|
||||
if (beautyType == BeautyInnerType.BEAUTY_FILTER) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
|
||||
|
||||
public enum BeautyInnerType {
|
||||
BEAUTY_NONE(0),
|
||||
BEAUTY_SMOOTH(10),
|
||||
BEAUTY_NATURAL(11),
|
||||
BEAUTY_PITU(12),
|
||||
BEAUTY_WHITE(13),
|
||||
BEAUTY_RUDDY(14),
|
||||
BEAUTY_FILTER(20);
|
||||
|
||||
private final int mValue;
|
||||
|
||||
BeautyInnerType(int value) {
|
||||
this.mValue = value;
|
||||
}
|
||||
|
||||
public static BeautyInnerType fromInteger(int x) {
|
||||
for (BeautyInnerType type : BeautyInnerType.values()) {
|
||||
if (type.mValue == x) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return BEAUTY_NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.util.TypedValue;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
|
||||
|
||||
public class BeautyItem {
|
||||
|
||||
@SerializedName("item_type")
|
||||
private int mItemType;
|
||||
@SerializedName("item_level")
|
||||
private int mItemLevel = 5;
|
||||
@SerializedName("item_name")
|
||||
private String mItemName;
|
||||
@SerializedName("item_icon_normal")
|
||||
private String mItemIconResName;
|
||||
@SerializedName("item_filter_bitmap")
|
||||
private String mFilterBitmapResource;
|
||||
|
||||
private Bitmap mFilterBitmap;
|
||||
|
||||
public Bitmap getFilterBitmap() {
|
||||
if (mFilterBitmapResource == null || mFilterBitmapResource.isEmpty() || !mFilterBitmapResource
|
||||
.startsWith("@")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (mFilterBitmap != null) {
|
||||
return mFilterBitmap;
|
||||
}
|
||||
|
||||
TypedValue value = new TypedValue();
|
||||
int resId = TUIMultimediaResourceUtils.getDrawableId(mFilterBitmapResource);
|
||||
TUIMultimediaResourceUtils.getResources().openRawResource(resId, value);
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inTargetDensity = value.density;
|
||||
mFilterBitmap = BitmapFactory.decodeResource(TUIMultimediaResourceUtils.getResources(), resId, opts);
|
||||
return mFilterBitmap;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return mItemLevel;
|
||||
}
|
||||
|
||||
public void setLevel(int level) {
|
||||
mItemLevel = level;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (mItemName.startsWith(TUIMultimediaResourceUtils.STRING_RESOURCE_PREFIX)) {
|
||||
return TUIMultimediaResourceUtils.getString(mItemName);
|
||||
}
|
||||
return mItemName;
|
||||
}
|
||||
|
||||
public BeautyInnerType getInnerType() {
|
||||
return BeautyInnerType.fromInteger(mItemType);
|
||||
}
|
||||
|
||||
public String getIconResName() {
|
||||
return mItemIconResName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class BeautyType {
|
||||
|
||||
@SerializedName("beauty_type_name")
|
||||
private String mTabName;
|
||||
@SerializedName("beauty_item_list")
|
||||
private List<BeautyItem> mBeautyItemList = new LinkedList<>();
|
||||
|
||||
private int mSelectedItemIndex = 1;
|
||||
|
||||
public BeautyItem getSelectBeautyItem() {
|
||||
if (mBeautyItemList == null || mSelectedItemIndex >= mBeautyItemList.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mBeautyItemList.get(mSelectedItemIndex);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return TUIMultimediaResourceUtils.getString(mTabName);
|
||||
}
|
||||
|
||||
public int getItemSize() {
|
||||
return mBeautyItemList != null ? mBeautyItemList.size() : 0;
|
||||
}
|
||||
|
||||
public BeautyItem getItem(int index) {
|
||||
if (mBeautyItemList == null || index >= mBeautyItemList.size()) {
|
||||
return null;
|
||||
}
|
||||
return mBeautyItemList.get(index);
|
||||
}
|
||||
|
||||
public int getSelectedItemIndex() {
|
||||
return mSelectedItemIndex;
|
||||
}
|
||||
|
||||
public void setSelectedItemIndex(int index) {
|
||||
mSelectedItemIndex = index;
|
||||
}
|
||||
|
||||
public BeautyItem getSelectedItem() {
|
||||
return getItem(mSelectedItemIndex);
|
||||
}
|
||||
|
||||
public boolean isContainItem(BeautyItem item) {
|
||||
return mBeautyItemList != null && mBeautyItemList.contains(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data;
|
||||
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
|
||||
public class RecordInfo {
|
||||
|
||||
public TUIMultimediaData<RecordStatus> tuiDataRecordStatus = new TUIMultimediaData<>(RecordStatus.IDLE);
|
||||
public TUIMultimediaData<Float> tuiDataRecordProcess = new TUIMultimediaData<>(0.0f);
|
||||
public TUIMultimediaData<Integer> tuiDataAspectRatio = new TUIMultimediaData<>(TXRecordCommon.VIDEO_ASPECT_RATIO_9_16);
|
||||
public TUIMultimediaData<Boolean> tuiDataIsFontCamera = new TUIMultimediaData<>(false);
|
||||
public TUIMultimediaData<Boolean> tuiDataIsFlashOn = new TUIMultimediaData<>(false);
|
||||
public TUIMultimediaData<Boolean> tuiDataShowBeautyView = new TUIMultimediaData<>(false);
|
||||
public RecordResult recordResult = new RecordResult();
|
||||
public BeautyInfo beautyInfo;
|
||||
public int recodeType = TUIMultimediaConstants.RECORD_TYPE_VIDEO;
|
||||
|
||||
public enum RecordStatus {
|
||||
IDLE, STOP, RECORDING,TAKE_PHOTOING
|
||||
}
|
||||
|
||||
static public class RecordResult {
|
||||
|
||||
public String path;
|
||||
public int type;
|
||||
public boolean isSuccess;
|
||||
public int code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import androidx.annotation.ColorInt;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ProgressRingView extends View {
|
||||
|
||||
private static final int PAINT_WIDTH = 10;
|
||||
private static final int ZERO_PROGRESS_PAINT_WIDTH = 4;
|
||||
private static final int TOTAL_RING_ANGEL = 360;
|
||||
private static final int RING_START_ANGEL = -90;
|
||||
|
||||
private float mProgress = 0;
|
||||
private RectF mRectF;
|
||||
private Paint mProgressPaint;
|
||||
private Paint mBackgroundPaint;
|
||||
private int mProgressColor = Color.BLUE;
|
||||
|
||||
public ProgressRingView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public ProgressRingView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
mProgressPaint = new Paint();
|
||||
mProgressPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
|
||||
mProgressPaint.setStyle(Paint.Style.STROKE);
|
||||
mProgressPaint.setAntiAlias(true);
|
||||
mProgressPaint.setColor(TUIMultimediaIConfig.getInstance().getThemeColor());
|
||||
|
||||
mBackgroundPaint = new Paint();
|
||||
mBackgroundPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
|
||||
mBackgroundPaint.setStyle(Paint.Style.STROKE);
|
||||
mBackgroundPaint.setAntiAlias(true);
|
||||
|
||||
mRectF = new RectF();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (mProgress > 0) {
|
||||
mProgressPaint.setStrokeWidth(PAINT_WIDTH);
|
||||
mBackgroundPaint.setStrokeWidth(PAINT_WIDTH);
|
||||
mBackgroundPaint.setColor(Color.GRAY);
|
||||
} else {
|
||||
mProgressPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
|
||||
mBackgroundPaint.setStrokeWidth(ZERO_PROGRESS_PAINT_WIDTH);
|
||||
mBackgroundPaint.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
int centerX = getWidth() / 2;
|
||||
int centerY = getHeight() / 2;
|
||||
int radius = Math.min(centerX, centerY) - PAINT_WIDTH / 2;
|
||||
mRectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
|
||||
canvas.drawOval(mRectF, mBackgroundPaint);
|
||||
canvas.drawArc(mRectF, RING_START_ANGEL, mProgress * TOTAL_RING_ANGEL, false, mProgressPaint);
|
||||
}
|
||||
|
||||
public void setProgress(float progress) {
|
||||
mProgress = progress;
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class RecordAspectView extends RelativeLayout {
|
||||
|
||||
private final String TAG = RecordAspectView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
private final RecordInfo mRecordInfo;
|
||||
|
||||
private ImageView mImageAspectCurr;
|
||||
private int mFirstAspect;
|
||||
|
||||
|
||||
public RecordAspectView(Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mRecordCore = recordCore;
|
||||
mRecordInfo = recordInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
View root = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_aspect_view, this, true);
|
||||
mImageAspectCurr = findViewById(R.id.iv_aspect);
|
||||
selectAnotherAspect(mRecordInfo.tuiDataAspectRatio.get());
|
||||
root.setOnClickListener(v -> selectAnotherAspect(mFirstAspect));
|
||||
}
|
||||
|
||||
private void selectAnotherAspect(int targetScale) {
|
||||
mRecordCore.setAspectRatio(targetScale);
|
||||
switch (targetScale) {
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_9_16:
|
||||
mImageAspectCurr.setImageResource(R.drawable.multimedia_plugin_record_aspect_916);
|
||||
mFirstAspect = TXRecordCommon.VIDEO_ASPECT_RATIO_3_4;
|
||||
break;
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_3_4:
|
||||
mImageAspectCurr.setImageResource(R.drawable.multimedia_plugin_record_aspect_34);
|
||||
mFirstAspect = TXRecordCommon.VIDEO_ASPECT_RATIO_9_16;
|
||||
break;
|
||||
// case TXRecordCommon.VIDEO_ASPECT_RATIO_FULL_SRCREEN:
|
||||
// mImageAspectCurr.setImageResource(R.drawable.multimedia_plugin_ic_aspect_11);
|
||||
// mFirstAspect = TXRecordCommon.VIDEO_ASPECT_RATIO_9_16;
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
|
||||
public class RecordButtonView extends RelativeLayout implements View.OnTouchListener {
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private View mButtonInnerView;
|
||||
private ProgressRingView mButtonOuterProgressRingView;
|
||||
private OnTouchListener mOnTouchListener;
|
||||
private boolean mIsOnlySupportTakePhoto = false;
|
||||
|
||||
public RecordButtonView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
initViews();
|
||||
}
|
||||
|
||||
public RecordButtonView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
initViews();
|
||||
}
|
||||
|
||||
public RecordButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
initViews();
|
||||
}
|
||||
|
||||
public void setIsOnlySupportTakePhoto(boolean isOnlySupportTakePhoto) {
|
||||
mIsOnlySupportTakePhoto = isOnlySupportTakePhoto;
|
||||
}
|
||||
|
||||
public void setProcess(float process) {
|
||||
mButtonOuterProgressRingView.setProgress(process);
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
View rootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_start_record_button,
|
||||
this, true);
|
||||
mButtonOuterProgressRingView = rootView.findViewById(R.id.record_button_ring_progress_bar);
|
||||
mButtonOuterProgressRingView.setVisibility(VISIBLE);
|
||||
|
||||
super.setOnTouchListener(this);
|
||||
|
||||
mButtonInnerView = findViewById(R.id.record_button_inner_circle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnTouchListener(OnTouchListener listener) {
|
||||
mOnTouchListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performClick() {
|
||||
return super.performClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View view, @NonNull MotionEvent motionEvent) {
|
||||
int action = motionEvent.getAction();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
startTakePhotoAnim();
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
endTakePhotoAnim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mOnTouchListener != null) {
|
||||
return mOnTouchListener.onTouch(view, motionEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void startTakePhotoAnim() {
|
||||
ViewGroup rootLayout = findViewById(R.id.layout_compose_record_btn);
|
||||
|
||||
float scale = 1.0f;
|
||||
if (!mIsOnlySupportTakePhoto) {
|
||||
scale = (Math.min(rootLayout.getWidth(), rootLayout.getHeight()) - 5) * 1.0f /
|
||||
Math.min(mButtonOuterProgressRingView.getWidth(), mButtonOuterProgressRingView.getHeight());
|
||||
}
|
||||
|
||||
ObjectAnimator btnBkgZoomOutXAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleX", scale);
|
||||
ObjectAnimator btnBkgZoomOutYAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleY", scale);
|
||||
|
||||
ObjectAnimator btnZoomInXAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleX", 0.5f);
|
||||
ObjectAnimator btnZoomInYAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleY", 0.5f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.setDuration(80);
|
||||
animatorSet.setInterpolator(new LinearInterpolator());
|
||||
animatorSet.play(btnBkgZoomOutXAn)
|
||||
.with(btnBkgZoomOutYAn)
|
||||
.with(btnZoomInXAn)
|
||||
.with(btnZoomInYAn);
|
||||
animatorSet.start();
|
||||
}
|
||||
|
||||
public void endTakePhotoAnim() {
|
||||
ObjectAnimator btnBkgZoomInXAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleX", 1f);
|
||||
ObjectAnimator btnBkgZoomIntYAn = ObjectAnimator.ofFloat(mButtonOuterProgressRingView, "scaleY", 1f);
|
||||
|
||||
ObjectAnimator btnZoomInXAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleX", 1f);
|
||||
ObjectAnimator btnZoomInYAn = ObjectAnimator.ofFloat(mButtonInnerView, "scaleY", 1f);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.setDuration(80);
|
||||
animatorSet.setInterpolator(new LinearInterpolator());
|
||||
animatorSet.play(btnBkgZoomInXAn)
|
||||
.with(btnBkgZoomIntYAn)
|
||||
.with(btnZoomInXAn)
|
||||
.with(btnZoomInYAn);
|
||||
animatorSet.start();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class RecordFunctionView extends LinearLayout {
|
||||
|
||||
private final static int DEFAULT_OPERATION_VIEW_HEIGHT_DP = 215;
|
||||
private final static int MIN_OPERATION_VIEW_HEIGHT_DP = 155;
|
||||
private final static int DEFAULT_OPERATION_TIPS_VIEW_HEIGHT_DP = 40;
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
private final RecordInfo mRecordInfo;
|
||||
|
||||
private RecordSettingView mRecordSettingView;
|
||||
private RecordOperationView mRecordOperationView;
|
||||
|
||||
private RelativeLayout mOperationViewContainer;
|
||||
private final TUIMultimediaDataObserver<Boolean> mIsShowBeautyView = new TUIMultimediaDataObserver<Boolean>() {
|
||||
@Override
|
||||
public void onChanged(Boolean isShowBeautyView) {
|
||||
if (mOperationViewContainer != null) {
|
||||
mOperationViewContainer.setVisibility(isShowBeautyView ? GONE : VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
private RelativeLayout mSettingViewContainer;
|
||||
private final TUIMultimediaDataObserver<RecordStatus> mRecodeStatusOnChanged = new TUIMultimediaDataObserver<RecordStatus>() {
|
||||
@Override
|
||||
public void onChanged(RecordStatus recordStatus) {
|
||||
if (mSettingViewContainer != null) {
|
||||
mSettingViewContainer.setVisibility((recordStatus == RecordStatus.RECORDING
|
||||
|| recordStatus == RecordStatus.TAKE_PHOTOING) ? GONE : VISIBLE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public RecordFunctionView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mRecordCore = recordCore;
|
||||
mRecordInfo = recordInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
addObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
removeObserver();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllViews() {
|
||||
if (mOperationViewContainer != null) {
|
||||
mOperationViewContainer.removeAllViews();
|
||||
}
|
||||
|
||||
if (mSettingViewContainer != null) {
|
||||
mSettingViewContainer.removeAllViews();
|
||||
}
|
||||
super.removeAllViews();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_function_view, this, true);
|
||||
|
||||
initRecordOperationView();
|
||||
|
||||
mSettingViewContainer = findViewById(R.id.rl_setting_view_container);
|
||||
mSettingViewContainer.removeAllViews();
|
||||
if (mRecordSettingView == null) {
|
||||
mRecordSettingView = new RecordSettingView(mContext, mRecordCore, mRecordInfo);
|
||||
}
|
||||
mSettingViewContainer.addView(mRecordSettingView, new RelativeLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
}
|
||||
|
||||
private void initRecordOperationView() {
|
||||
mOperationViewContainer = findViewById(R.id.rl_operation_view_container);
|
||||
mOperationViewContainer.removeAllViews();
|
||||
if (mRecordOperationView == null) {
|
||||
mRecordOperationView = new RecordOperationView(mContext, mRecordCore, mRecordInfo);
|
||||
}
|
||||
adjustRecordOperationViewPosition(mRecordInfo.tuiDataAspectRatio.get());
|
||||
}
|
||||
|
||||
private void adjustRecordOperationViewPosition(int aspectRation) {
|
||||
if (mOperationViewContainer == null || mRecordOperationView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mOperationViewContainer.removeAllViews();
|
||||
Point screenSize = TUIMultimediaResourceUtils.getScreenSize(mContext);
|
||||
int viewHeight = TUIMultimediaResourceUtils.dip2px(mContext, DEFAULT_OPERATION_VIEW_HEIGHT_DP);
|
||||
if (aspectRation == TXRecordCommon.VIDEO_ASPECT_RATIO_9_16) {
|
||||
viewHeight = screenSize.y - screenSize.x * 16 / 9 + TUIMultimediaResourceUtils
|
||||
.dip2px(mContext, DEFAULT_OPERATION_TIPS_VIEW_HEIGHT_DP);
|
||||
} else if (aspectRation == TXRecordCommon.VIDEO_ASPECT_RATIO_3_4) {
|
||||
viewHeight = screenSize.y / 2 - (screenSize.x * 4 / 3) / 2 + TUIMultimediaResourceUtils
|
||||
.dip2px(mContext, DEFAULT_OPERATION_TIPS_VIEW_HEIGHT_DP);
|
||||
}
|
||||
int minViewHeight = TUIMultimediaResourceUtils.dip2px(mContext, MIN_OPERATION_VIEW_HEIGHT_DP);
|
||||
viewHeight = Math.max(viewHeight, minViewHeight);
|
||||
LayoutParams linearLayoutCompat = new LayoutParams(MATCH_PARENT, viewHeight);
|
||||
mOperationViewContainer.addView(mRecordOperationView, linearLayoutCompat);
|
||||
}
|
||||
|
||||
private void addObserver() {
|
||||
mRecordInfo.tuiDataShowBeautyView.observe(mIsShowBeautyView);
|
||||
mRecordInfo.tuiDataRecordStatus.observe(mRecodeStatusOnChanged);
|
||||
mRecordInfo.tuiDataAspectRatio.observe(this::adjustRecordOperationViewPosition);
|
||||
}
|
||||
|
||||
private void removeObserver() {
|
||||
mRecordInfo.tuiDataShowBeautyView.removeObserver(mIsShowBeautyView);
|
||||
mRecordInfo.tuiDataRecordStatus.removeObserver(mRecodeStatusOnChanged);
|
||||
mRecordInfo.tuiDataAspectRatio.removeObserver(this::adjustRecordOperationViewPosition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.imsdk.BuildConfig;
|
||||
import com.tencent.imsdk.base.ThreadUtils;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediacore.TUIMultimediaSignatureChecker;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaConstants;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaAuthorizationPrompter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaFileUtil.MultimediaPluginFileType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo.RecordStatus;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class RecordOperationView extends LinearLayout {
|
||||
|
||||
private final String TAG = RecordOperationView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
private final RecordInfo mRecordInfo;
|
||||
|
||||
private RecordButtonView mRecordButtonView;
|
||||
private TextView mRecordOperationTipsView;
|
||||
private TextView mRecordTimeTextView;
|
||||
private Float mLastRecordProcess = 0.0f;
|
||||
private String mLastRecordTime;
|
||||
|
||||
private final TUIMultimediaDataObserver<RecordStatus> mRecordStatusObserver = new TUIMultimediaDataObserver<RecordStatus>() {
|
||||
@Override
|
||||
public void onChanged(RecordStatus recordStatus) {
|
||||
if (recordStatus == RecordStatus.RECORDING || recordStatus == RecordStatus.TAKE_PHOTOING) {
|
||||
findViewById(R.id.cancel_record_button).setVisibility(INVISIBLE);
|
||||
findViewById(R.id.record_switch_camera).setVisibility(INVISIBLE);
|
||||
hideRecordOperationTips();
|
||||
} else {
|
||||
mRecordButtonView.setProcess(0);
|
||||
mLastRecordProcess = 0.0f;
|
||||
mLastRecordTime = null;
|
||||
if (recordStatus == RecordStatus.STOP && !mRecordInfo.recordResult.isSuccess
|
||||
&& mRecordInfo.recordResult.code == TXRecordCommon.RECORD_RESULT_OK_LESS_THAN_MINDURATION) {
|
||||
mRecordCore.takePhoto(
|
||||
TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE));
|
||||
} else {
|
||||
findViewById(R.id.cancel_record_button).setVisibility(VISIBLE);
|
||||
findViewById(R.id.record_switch_camera).setVisibility(VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
private final TUIMultimediaDataObserver<Float> mRecordProcessObserver = new TUIMultimediaDataObserver<Float>() {
|
||||
@Override
|
||||
public void onChanged(Float process) {
|
||||
LiteavLog.i(TAG, "record process is " + process);
|
||||
if (process > mLastRecordProcess) {
|
||||
mLastRecordProcess = process;
|
||||
mRecordButtonView.setProcess(process);
|
||||
setRecordTime(process);
|
||||
}
|
||||
}
|
||||
};
|
||||
private boolean isNeedShowOperationTipsView = true;
|
||||
|
||||
public RecordOperationView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mRecordCore = recordCore;
|
||||
mRecordInfo = recordInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
addObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
removeObserver();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_operation_view, this, true);
|
||||
|
||||
findViewById(R.id.record_switch_camera).setOnClickListener(v -> {
|
||||
boolean is_front_camera = mRecordInfo.tuiDataIsFontCamera.get();
|
||||
mRecordCore.switchCamera(!is_front_camera);
|
||||
});
|
||||
|
||||
findViewById(R.id.cancel_record_button).setOnClickListener(v -> {
|
||||
((Activity) mContext).setResult(Activity.RESULT_CANCELED, null);
|
||||
((Activity) mContext).finish();
|
||||
});
|
||||
|
||||
mRecordTimeTextView = findViewById(R.id.record_time);
|
||||
mRecordOperationTipsView = findViewById(R.id.record_operation_tips);
|
||||
mRecordOperationTipsView.setVisibility(isNeedShowOperationTipsView ? VISIBLE : INVISIBLE);
|
||||
mRecordButtonView = findViewById(R.id.start_record_button);
|
||||
if (mRecordInfo.recodeType == TUIMultimediaConstants.RECORD_TYPE_VIDEO) {
|
||||
initRecordButtonForVideoRecode();
|
||||
} else {
|
||||
initRecordButtonForTakePhoto();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initRecordButtonForVideoRecode() {
|
||||
mRecordButtonView.setOnLongClickListener(v -> {
|
||||
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mRecordCore.startRecord(TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.RECODE_FILE));
|
||||
return false;
|
||||
});
|
||||
|
||||
mRecordButtonView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
mRecordCore.stopRecord();
|
||||
mRecordTimeTextView.setVisibility(INVISIBLE);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
mRecordButtonView.setOnClickListener(
|
||||
v -> {
|
||||
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mRecordCore.takePhoto(
|
||||
TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE));
|
||||
});
|
||||
mRecordOperationTipsView.setText(R.string.multimedia_plugin_record_operation_tips);
|
||||
mRecordButtonView.setIsOnlySupportTakePhoto(false);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initRecordButtonForTakePhoto() {
|
||||
mRecordButtonView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.TAKE_PHOTOING);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
mRecordButtonView.setOnClickListener(
|
||||
v -> {
|
||||
if (!TUIMultimediaAuthorizationPrompter.verifyPermissionGranted(mContext)) {
|
||||
mRecordInfo.tuiDataRecordStatus.set(RecordStatus.IDLE);
|
||||
return;
|
||||
}
|
||||
mRecordCore.takePhoto(
|
||||
TUIMultimediaFileUtil.generateFilePath(MultimediaPluginFileType.PICTURE_FILE));
|
||||
});
|
||||
|
||||
mRecordOperationTipsView.setText(R.string.multimedia_plugin_record_take_phone_operation_tips);
|
||||
mRecordButtonView.setIsOnlySupportTakePhoto(true);
|
||||
}
|
||||
|
||||
private void setRecordTime(float process) {
|
||||
int maxDuration = mRecordCore.getMaxDuration();
|
||||
String recordTime = TUIMultimediaResourceUtils.secondsToTimeString((long) (process * maxDuration));
|
||||
if (recordTime.equals(mLastRecordTime)) {
|
||||
return;
|
||||
}
|
||||
mLastRecordTime = recordTime;
|
||||
ThreadUtils.getUiThreadHandler().post(() -> {
|
||||
mRecordTimeTextView.setText(mLastRecordTime);
|
||||
});
|
||||
}
|
||||
|
||||
private void addObserver() {
|
||||
mRecordInfo.tuiDataRecordStatus.observe(mRecordStatusObserver);
|
||||
mRecordInfo.tuiDataRecordProcess.observe(mRecordProcessObserver);
|
||||
}
|
||||
|
||||
private void removeObserver() {
|
||||
mRecordInfo.tuiDataRecordStatus.removeObserver(mRecordStatusObserver);
|
||||
mRecordInfo.tuiDataRecordProcess.removeObserver(mRecordProcessObserver);
|
||||
}
|
||||
|
||||
private void hideRecordOperationTips() {
|
||||
if (mRecordOperationTipsView != null) {
|
||||
mRecordOperationTipsView.setVisibility(INVISIBLE);
|
||||
}
|
||||
isNeedShowOperationTipsView = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty.BeautyFilterScrollView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty.BeautyPanelView;
|
||||
|
||||
public class RecordSettingView extends RelativeLayout {
|
||||
|
||||
private static final int SCROLL_FILTER_RIGHT_LEFT_MARGIN_SP = 15;
|
||||
private static final int SETTING_ITEM_VIEW_BOTTOM_MARGIN_SP = 24;
|
||||
private final String TAG = RecordSettingView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
|
||||
private RecordInfo mRecordInfo;
|
||||
private BeautyPanelView mBeautyPanelView;
|
||||
private BeautyFilterScrollView mBeautyFilterScrollView;
|
||||
private SettingItemViewHolder mSettingItemTorch;
|
||||
private RecordAspectView mAspectView;
|
||||
private LinearLayout mLayoutSetting;
|
||||
private RelativeLayout mBeautyPanelViewContainer;
|
||||
private RelativeLayout mScrollFilterViewContainer;
|
||||
|
||||
private final TUIMultimediaDataObserver<Boolean> mFlashStatusObserver = isOnAndFront -> {
|
||||
if (mSettingItemTorch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LiteavLog.i(TAG,"is front camera:" + mRecordInfo.tuiDataIsFontCamera.get()
|
||||
+ " is flash on: " + mRecordInfo.tuiDataIsFlashOn.get());
|
||||
|
||||
if (mRecordInfo.tuiDataIsFontCamera.get()) {
|
||||
mSettingItemTorch.setIconRes(R.drawable.multimedia_plugin_record_torch_close_disable);
|
||||
mSettingItemTorch.setClickable(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int resId = mRecordInfo.tuiDataIsFlashOn.get() ? R.drawable.multimedia_plugin_record_torch_open : R.drawable.multimedia_plugin_record_torch_close;
|
||||
mSettingItemTorch.setIconRes(resId);
|
||||
mSettingItemTorch.setClickable(true);
|
||||
};
|
||||
|
||||
public RecordSettingView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mRecordCore = recordCore;
|
||||
mRecordInfo = recordInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
addObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
removeAllViews();
|
||||
removeObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllViews() {
|
||||
if (mBeautyPanelViewContainer != null) {
|
||||
mBeautyPanelViewContainer.removeAllViews();
|
||||
}
|
||||
|
||||
if (mScrollFilterViewContainer != null) {
|
||||
mScrollFilterViewContainer.removeAllViews();
|
||||
}
|
||||
|
||||
mLayoutSetting.removeAllViews();
|
||||
super.removeAllViews();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
View rootView = LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_setting_view, this, true);
|
||||
rootView.setOnClickListener(v -> {
|
||||
if (mBeautyPanelView != null) {
|
||||
mBeautyPanelView.setVisibility(GONE);
|
||||
}
|
||||
});
|
||||
mLayoutSetting = findViewById(R.id.layout_setting_item);
|
||||
|
||||
initTorchView();
|
||||
initBeautyView();
|
||||
initScrollBeautyFilterView();
|
||||
initAspectView();
|
||||
}
|
||||
|
||||
private void initTorchView() {
|
||||
if (!TUIMultimediaIConfig.getInstance().isSupportRecordTorch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSettingItemTorch = addSettingItem(R.drawable.multimedia_plugin_record_torch_close, R.string.multimedia_plugin_torch);
|
||||
mSettingItemTorch.setOnClickListener(v -> {
|
||||
boolean isFlashOn = mRecordInfo.tuiDataIsFlashOn.get();
|
||||
mRecordCore.toggleTorch(!isFlashOn);
|
||||
});
|
||||
}
|
||||
|
||||
private void initBeautyView() {
|
||||
if (!TUIMultimediaIConfig.getInstance().isSupportRecordBeauty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SettingItemViewHolder mBeautyItemTorch = addSettingItem(R.drawable.multimedia_plugin_record_beauty, R.string.multimedia_plugin_beauty);
|
||||
mBeautyItemTorch.setOnClickListener(v ->
|
||||
mBeautyPanelView.setVisibility(!mRecordInfo.tuiDataShowBeautyView.get() ? VISIBLE : INVISIBLE));
|
||||
|
||||
if (mRecordInfo.beautyInfo == null) {
|
||||
mRecordInfo.beautyInfo = BeautyInfo.CreateDefaultBeautyInfo();
|
||||
}
|
||||
|
||||
if (mBeautyPanelView == null) {
|
||||
mBeautyPanelView = new BeautyPanelView(getContext(), mRecordCore, mRecordInfo);
|
||||
mBeautyPanelView.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
mBeautyPanelViewContainer = findViewById(R.id.rl_beauty_panel_view_container);
|
||||
mBeautyPanelViewContainer.removeAllViews();
|
||||
mBeautyPanelViewContainer
|
||||
.addView(mBeautyPanelView, new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
|
||||
}
|
||||
|
||||
private void initScrollBeautyFilterView() {
|
||||
if (!TUIMultimediaIConfig.getInstance().isSupportRecordScrollFilter()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRecordInfo.beautyInfo == null) {
|
||||
mRecordInfo.beautyInfo = BeautyInfo.CreateDefaultBeautyInfo();
|
||||
}
|
||||
|
||||
if (mBeautyFilterScrollView == null) {
|
||||
mBeautyFilterScrollView = new BeautyFilterScrollView(mContext, mRecordCore, mRecordInfo.beautyInfo);
|
||||
}
|
||||
|
||||
mScrollFilterViewContainer = findViewById(R.id.rl_beauty_filter_scroll_view_container);
|
||||
mScrollFilterViewContainer.removeAllViews();
|
||||
RelativeLayout.LayoutParams layoutParams =
|
||||
new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
layoutParams.leftMargin = (int) TUIMultimediaResourceUtils.spToPx(mContext, SCROLL_FILTER_RIGHT_LEFT_MARGIN_SP);
|
||||
layoutParams.rightMargin = (int) TUIMultimediaResourceUtils.spToPx(mContext, SCROLL_FILTER_RIGHT_LEFT_MARGIN_SP);
|
||||
mScrollFilterViewContainer.addView(mBeautyFilterScrollView, layoutParams);
|
||||
mBeautyFilterScrollView.setOnClickListener(v -> {
|
||||
if (mBeautyPanelView != null) {
|
||||
mBeautyPanelView.setVisibility(GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initAspectView() {
|
||||
if (!TUIMultimediaIConfig.getInstance().isSupportRecordAspect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAspectView == null) {
|
||||
mAspectView = new RecordAspectView(getContext(), mRecordCore, mRecordInfo);
|
||||
}
|
||||
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
|
||||
layoutParams.bottomMargin = TUIMultimediaResourceUtils.dip2px(mContext, SETTING_ITEM_VIEW_BOTTOM_MARGIN_SP);
|
||||
layoutParams.gravity = Gravity.CENTER;
|
||||
mLayoutSetting.addView(mAspectView,layoutParams);
|
||||
}
|
||||
|
||||
private SettingItemViewHolder addSettingItem(int iconResId, int titleResId) {
|
||||
View view = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.multimedia_plugin_record_setting_item_view, this, false);
|
||||
SettingItemViewHolder settingItemViewHolder = new SettingItemViewHolder(view);
|
||||
settingItemViewHolder.setIconRes(iconResId);
|
||||
settingItemViewHolder.setTitle(titleResId);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
|
||||
layoutParams.bottomMargin = TUIMultimediaResourceUtils.dip2px(mContext, SETTING_ITEM_VIEW_BOTTOM_MARGIN_SP);
|
||||
layoutParams.gravity = Gravity.CENTER;
|
||||
mLayoutSetting.addView(view,layoutParams);
|
||||
return settingItemViewHolder;
|
||||
}
|
||||
|
||||
private void addObserver() {
|
||||
mRecordInfo.tuiDataIsFontCamera.observe(mFlashStatusObserver);
|
||||
mRecordInfo.tuiDataIsFlashOn.observe(mFlashStatusObserver);
|
||||
}
|
||||
|
||||
private void removeObserver() {
|
||||
mRecordInfo.tuiDataIsFontCamera.removeObserver(mFlashStatusObserver);
|
||||
mRecordInfo.tuiDataIsFlashOn.removeObserver(mFlashStatusObserver);
|
||||
}
|
||||
|
||||
static class SettingItemViewHolder{
|
||||
private final View mRootView;
|
||||
private final ImageView mIcon;
|
||||
private final TextView mTitle;
|
||||
|
||||
public SettingItemViewHolder(View rootView) {
|
||||
mRootView = rootView;
|
||||
mIcon = mRootView.findViewById(R.id.icon);
|
||||
mTitle = mRootView.findViewById(R.id.title);
|
||||
}
|
||||
|
||||
public void setIconRes(int iconResId) {
|
||||
mIcon.setBackgroundResource(iconResId);
|
||||
}
|
||||
|
||||
public void setTitle(int titleResId) {
|
||||
mTitle.setText(TUIMultimediaResourceUtils.getString(titleResId));
|
||||
}
|
||||
|
||||
public void setOnClickListener(OnClickListener onClickListener) {
|
||||
mRootView.setOnClickListener(onClickListener);
|
||||
}
|
||||
|
||||
public void setClickable(boolean clickable) {
|
||||
mRootView.setClickable(clickable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
import com.tencent.rtmp.ui.TXCloudVideoView;
|
||||
import com.tencent.ugc.TXRecordCommon;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class RecordVideoView extends FrameLayout {
|
||||
|
||||
private final String TAG = RecordVideoView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
private final RecordInfo mRecordInfo;
|
||||
|
||||
private TXCloudVideoView mVideoView;
|
||||
|
||||
private final TUIMultimediaDataObserver<Integer> mAspectRatioObserver = this::setVideoViewSize;
|
||||
|
||||
public RecordVideoView(@NonNull Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mRecordCore = recordCore;
|
||||
mRecordInfo = recordInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initView();
|
||||
addObserver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
mRecordCore.stopCameraPreview();
|
||||
removeObserver();
|
||||
removeAllViews();
|
||||
}
|
||||
|
||||
public void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_video_play_view, this, true);
|
||||
mVideoView = findViewById(R.id.record_video_view);
|
||||
setVideoViewSize(mRecordInfo.tuiDataAspectRatio.get());
|
||||
mRecordCore.startCameraPreview(mVideoView);
|
||||
}
|
||||
|
||||
public void addObserver() {
|
||||
mRecordInfo.tuiDataAspectRatio.observe(mAspectRatioObserver);
|
||||
}
|
||||
|
||||
public void removeObserver() {
|
||||
mRecordInfo.tuiDataAspectRatio.removeObserver(mAspectRatioObserver);
|
||||
}
|
||||
|
||||
private void setVideoViewSize(int aspectRatio) {
|
||||
Point screenSize = TUIMultimediaResourceUtils.getScreenSize(mContext);
|
||||
LiteavLog.i(TAG, "screenSize = " + screenSize);
|
||||
switch (aspectRatio) {
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_9_16:
|
||||
mVideoView.setLayoutParams(new FrameLayout.LayoutParams(screenSize.x, screenSize.x * 16 / 9));
|
||||
break;
|
||||
case TXRecordCommon.VIDEO_ASPECT_RATIO_3_4:
|
||||
FrameLayout.LayoutParams layoutParams = (LayoutParams) mVideoView.getLayoutParams();
|
||||
layoutParams.width = screenSize.x;
|
||||
layoutParams.height = screenSize.x * 4 / 3;
|
||||
layoutParams.topMargin = (screenSize.y - layoutParams.height) / 2;
|
||||
mVideoView.setLayoutParams(layoutParams);
|
||||
break;
|
||||
// case TXRecordCommon.VIDEO_ASPECT_RATIO_FULL_SRCREEN:
|
||||
// mVideoView.setLayoutParams(new FrameLayout.LayoutParams(screenSize.x, screenSize.y));
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.GestureDetector.OnGestureListener;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.ScaleGestureDetector.OnScaleGestureListener;
|
||||
import android.view.View;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.LinearInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInnerType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyType;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class BeautyFilterScrollView extends RelativeLayout implements View.OnTouchListener {
|
||||
|
||||
private final String TAG = BeautyFilterScrollView.class + "_" + hashCode();
|
||||
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
private final BeautyInfo mBeautyInfo;
|
||||
private final int mFilterIndex;
|
||||
private final BeautyType mBeautyFilterType;
|
||||
|
||||
private TextView mFilterNameTextView;
|
||||
private FrameLayout mMaskLayout;
|
||||
private GestureDetector mGestureDetector;
|
||||
private ScaleGestureDetector mScaleGestureDetector;
|
||||
|
||||
private int mLeftIndex = 0;
|
||||
private int mRightIndex = 1;
|
||||
private int mLastLeftIndex = -1;
|
||||
private int mLastRightIndex = -1;
|
||||
private float mLeftBitmapRatio;
|
||||
private float mMoveRatio;
|
||||
private boolean mStartScroll;
|
||||
private boolean mMoveRight;
|
||||
private boolean mIsNeedChange;
|
||||
private boolean mIsDoingAnimator;
|
||||
private float mLastScaleFactor;
|
||||
private float mScaleFactor;
|
||||
private final OnScaleGestureListener mOnScaleGestureListener = new OnScaleGestureListener() {
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
return BeautyFilterScrollView.this.onScale(detector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||
mLastScaleFactor = detector.getScaleFactor();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
}
|
||||
};
|
||||
private Bitmap mLeftBitmap;
|
||||
private Bitmap mRightBitmap;
|
||||
private final OnGestureListener mOnGestureListener = new OnGestureListener() {
|
||||
@Override
|
||||
public boolean onDown(MotionEvent e) {
|
||||
mStartScroll = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
BeautyFilterScrollView.this.performClick();
|
||||
mRecordCore.setFocusPosition(e.getX(), e.getY());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
|
||||
return BeautyFilterScrollView.this.onScroll(e1, e2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowPress(MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@NonNull
|
||||
private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
|
||||
new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
|
||||
mIsDoingAnimator = true;
|
||||
|
||||
float leftRatio = (float) valueAnimator.getAnimatedValue();
|
||||
mFilterNameTextView.setVisibility(View.INVISIBLE);
|
||||
|
||||
BeautyItem leftFilterItem = mBeautyFilterType.getItem(mLeftIndex);
|
||||
BeautyItem rightFilterItem = mBeautyFilterType.getItem(mRightIndex);
|
||||
float leftSpecialRatio = (leftFilterItem != null ? leftFilterItem.getLevel() : 0) / 10.f;
|
||||
float rightSpecialRatio = (rightFilterItem != null ? rightFilterItem.getLevel() : 0) / 10.f;
|
||||
mRecordCore.setFilter(mLeftBitmap, leftSpecialRatio, mRightBitmap, rightSpecialRatio, leftRatio);
|
||||
|
||||
if (leftRatio == 0 || leftRatio == 1) {
|
||||
mLeftBitmapRatio = leftRatio;
|
||||
mIsDoingAnimator = false;
|
||||
if (mIsNeedChange) {
|
||||
mIsNeedChange = false;
|
||||
mBeautyInfo.setSelectedItemIndex(mFilterIndex, (leftRatio == 0 ? mRightIndex : mLeftIndex));
|
||||
doTextAnimator();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public BeautyFilterScrollView(@NonNull Context context, TUIMultimediaRecordCore recordCore, BeautyInfo beautyInfo) {
|
||||
super(context);
|
||||
mRecordCore = recordCore;
|
||||
mBeautyInfo = beautyInfo;
|
||||
mFilterIndex = mBeautyInfo.getTypeIndexWithType(BeautyInnerType.BEAUTY_FILTER);
|
||||
mBeautyFilterType = mBeautyInfo.getBeautyType(mFilterIndex);
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initViews() {
|
||||
inflate(getContext(), R.layout.multimedia_plugin_record_scroll_filter_view, this);
|
||||
|
||||
mMaskLayout = findViewById(R.id.fl_filter_mask);
|
||||
mMaskLayout.setOnTouchListener(this);
|
||||
mFilterNameTextView = findViewById(R.id.tv_filter_name);
|
||||
mFilterNameTextView.setVisibility(INVISIBLE);
|
||||
mGestureDetector = new GestureDetector(getContext(), mOnGestureListener);
|
||||
mScaleGestureDetector = new ScaleGestureDetector(getContext(), mOnScaleGestureListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
initViews();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, @NonNull MotionEvent event) {
|
||||
if (v != mMaskLayout) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int pointerCount = event.getPointerCount();
|
||||
if (pointerCount >= 2) {
|
||||
mScaleGestureDetector.onTouchEvent(event);
|
||||
return true;
|
||||
} else if (pointerCount == 1) {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
if (mStartScroll && event.getAction() == MotionEvent.ACTION_UP) {
|
||||
doFilterAnimator();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doFilterAnimator() {
|
||||
LiteavLog.i(TAG, "doFilterAnimator");
|
||||
int filterSelectIndex = mBeautyFilterType.getSelectedItemIndex();
|
||||
ValueAnimator mFilterAnimator;
|
||||
if (mMoveRatio >= 0.2f) {
|
||||
mIsNeedChange = true;
|
||||
if (mMoveRight) {
|
||||
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 1);
|
||||
} else {
|
||||
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 0);
|
||||
}
|
||||
} else {
|
||||
if (filterSelectIndex == mLeftIndex) {
|
||||
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 1);
|
||||
} else {
|
||||
mFilterAnimator = generateValueAnimator(mLeftBitmapRatio, 0);
|
||||
}
|
||||
}
|
||||
mFilterAnimator.addUpdateListener(mAnimatorUpdateListener);
|
||||
mFilterAnimator.start();
|
||||
}
|
||||
|
||||
private ValueAnimator generateValueAnimator(float start, float end) {
|
||||
ValueAnimator animator = ValueAnimator.ofFloat(start, end);
|
||||
animator.setInterpolator(new LinearInterpolator());
|
||||
animator.setDuration(400);
|
||||
return animator;
|
||||
}
|
||||
|
||||
public void doTextAnimator() {
|
||||
LiteavLog.i(TAG, "doTextAnimator");
|
||||
BeautyItem beautyItem = mBeautyFilterType.getSelectedItem();
|
||||
if (beautyItem == null) {
|
||||
return;
|
||||
}
|
||||
LiteavLog.i(TAG, "doTextAnimator text = " + beautyItem.getName());
|
||||
mFilterNameTextView.setText(beautyItem.getName());
|
||||
AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0.3f);
|
||||
alphaAnimation.setDuration(1500);
|
||||
alphaAnimation.setInterpolator(new LinearInterpolator());
|
||||
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
mFilterNameTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
mFilterNameTextView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
});
|
||||
mFilterNameTextView.startAnimation(alphaAnimation);
|
||||
}
|
||||
|
||||
public boolean onScroll(@NonNull MotionEvent downEvent, @NonNull MotionEvent moveEvent) {
|
||||
if (mIsDoingAnimator) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int filterSelectIndex = mBeautyFilterType.getSelectedItemIndex();
|
||||
boolean moveRight = moveEvent.getX() > downEvent.getX();
|
||||
if (moveRight && filterSelectIndex == 0) {
|
||||
return true;
|
||||
} else if (!moveRight && filterSelectIndex == mBeautyInfo.getItemSize(mFilterIndex) - 1) {
|
||||
return true;
|
||||
} else {
|
||||
mStartScroll = true;
|
||||
if (moveRight) {
|
||||
mLeftIndex = filterSelectIndex - 1;
|
||||
mRightIndex = filterSelectIndex;
|
||||
} else {
|
||||
mLeftIndex = filterSelectIndex;
|
||||
mRightIndex = filterSelectIndex + 1;
|
||||
}
|
||||
|
||||
if (mLastLeftIndex != mLeftIndex) {
|
||||
BeautyItem beautyItem = mBeautyFilterType.getItem(mLeftIndex);
|
||||
mLeftBitmap = beautyItem != null ? beautyItem.getFilterBitmap() : null;
|
||||
mLastLeftIndex = mLeftIndex;
|
||||
}
|
||||
|
||||
if (mLastRightIndex != mRightIndex) {
|
||||
BeautyItem beautyItem = mBeautyFilterType.getItem(mRightIndex);
|
||||
mRightBitmap = beautyItem != null ? beautyItem.getFilterBitmap() : null;
|
||||
mLastRightIndex = mRightIndex;
|
||||
}
|
||||
|
||||
int width = mMaskLayout.getWidth();
|
||||
float dis = moveEvent.getX() - downEvent.getX();
|
||||
float leftBitmapRatio = Math.abs(dis) / (width * 1.0f);
|
||||
mMoveRatio = leftBitmapRatio;
|
||||
if (!moveRight) {
|
||||
leftBitmapRatio = 1 - leftBitmapRatio;
|
||||
}
|
||||
mMoveRight = moveRight;
|
||||
mLeftBitmapRatio = leftBitmapRatio;
|
||||
|
||||
BeautyItem leftFilterItem = mBeautyFilterType.getItem(mLeftIndex);
|
||||
BeautyItem rightFilterItem = mBeautyFilterType.getItem(mRightIndex);
|
||||
float leftSpecialRatio = (leftFilterItem != null ? leftFilterItem.getLevel() : 0) / 10.f;
|
||||
float rightSpecialRatio = (rightFilterItem != null ? rightFilterItem.getLevel() : 0) / 10.f;
|
||||
mRecordCore.setFilter(mLeftBitmap, leftSpecialRatio, mRightBitmap, rightSpecialRatio, leftBitmapRatio);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean onScale(@NonNull ScaleGestureDetector detector) {
|
||||
LiteavLog.i(TAG, "onScale");
|
||||
int maxZoom = mRecordCore.getMaxZoom();
|
||||
if (maxZoom == 0) {
|
||||
Log.i(TAG, "camera not support zoom");
|
||||
return false;
|
||||
}
|
||||
|
||||
float factorOffset = detector.getScaleFactor() - mLastScaleFactor;
|
||||
|
||||
mScaleFactor += factorOffset;
|
||||
mLastScaleFactor = detector.getScaleFactor();
|
||||
if (mScaleFactor < 0) {
|
||||
mScaleFactor = 0;
|
||||
}
|
||||
if (mScaleFactor > 1) {
|
||||
mScaleFactor = 1;
|
||||
}
|
||||
|
||||
int zoomValue = Math.round(mScaleFactor * maxZoom);
|
||||
mRecordCore.setZoom(zoomValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.TUIMultimediaIConfig;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaScrollViewAdapter;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyType;
|
||||
|
||||
public class BeautyItemAdapter extends TUIMultimediaScrollViewAdapter {
|
||||
|
||||
private final Context mContext;
|
||||
private final BeautyInfo mBeautyInfo;
|
||||
private final int mTypeIndex;
|
||||
private ViewHolder mLastSelectedView;
|
||||
|
||||
public BeautyItemAdapter(Context context, BeautyInfo beautyInfo, int typeIndex) {
|
||||
mContext = context;
|
||||
mBeautyInfo = beautyInfo;
|
||||
mTypeIndex = typeIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
BeautyType beautyType = mBeautyInfo.getBeautyType(mTypeIndex);
|
||||
return beautyType != null ? beautyType.getItemSize() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeautyItem getItem(int position) {
|
||||
BeautyType beautyType = mBeautyInfo.getBeautyType(mTypeIndex);
|
||||
return beautyType != null ? beautyType.getItem(position) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||
final ViewHolder holder;
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(mContext)
|
||||
.inflate(getViewResourceId(), parent, false);
|
||||
holder = new ViewHolder(convertView, mContext);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
BeautyType beautyType = mBeautyInfo.getBeautyType(mTypeIndex);
|
||||
BeautyItem beautyItem = beautyType != null ? beautyType.getItem(position) : null;
|
||||
if (beautyItem != null) {
|
||||
holder.title.setText(beautyItem.getName());
|
||||
TUIMultimediaResourceUtils.setImageResource(holder.icon, beautyItem.getIconResName());
|
||||
}
|
||||
|
||||
holder.setSelected(mSelectPosition == position && mSelectPosition != 0);
|
||||
if (mSelectPosition == position) {
|
||||
mLastSelectedView = holder;
|
||||
}
|
||||
|
||||
convertView.setOnClickListener(v -> {
|
||||
if (mSelectPosition != position) {
|
||||
mSelectPosition = position;
|
||||
holder.setSelected(mSelectPosition != 0);
|
||||
mBeautyInfo.setSelectedItemIndex(mTypeIndex, position);
|
||||
if (mLastSelectedView != null) {
|
||||
mLastSelectedView.setSelected(false);
|
||||
}
|
||||
mLastSelectedView = holder;
|
||||
}
|
||||
});
|
||||
return convertView;
|
||||
}
|
||||
|
||||
private int getViewResourceId() {
|
||||
String typeName = mBeautyInfo.getBeautyType(mTypeIndex).getName();
|
||||
if (typeName.equals(TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_setting_panel_filter))) {
|
||||
return R.layout.multimedia_plugin_record_beauty_filter_item_view;
|
||||
} else {
|
||||
return R.layout.multimedia_plugin_record_beauty_item_view;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final View root;
|
||||
private Context context;
|
||||
private final ImageView icon;
|
||||
private final TextView title;
|
||||
private final ImageView background;
|
||||
|
||||
public ViewHolder(View itemView, Context context) {
|
||||
super(itemView);
|
||||
this.context = context;
|
||||
root = itemView;
|
||||
icon = itemView.findViewById(R.id.beauty_iv_icon);
|
||||
title = itemView.findViewById(R.id.beauty_tv_title);
|
||||
background = itemView.findViewById(R.id.select_background);
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
if (selected) {
|
||||
background.setBackground(
|
||||
TUIMultimediaResourceUtils.getDrawable(context, R.drawable.multimedia_plugin_beauty_backgroud_selected,
|
||||
TUIMultimediaIConfig.getInstance().getThemeColor()));
|
||||
} else {
|
||||
background.setBackground(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
package com.tencent.qcloud.tuikit.tuimultimediaplugin.record.view.beauty;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener;
|
||||
import com.google.android.material.tabs.TabLayout.Tab;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import com.tencent.liteav.base.util.LiteavLog;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.R;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaResourceUtils;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.common.TUIMultimediaData.TUIMultimediaDataObserver;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.TUIMultimediaRecordCore;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInfo;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyInnerType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyItem;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.BeautyType;
|
||||
import com.tencent.qcloud.tuikit.tuimultimediaplugin.record.data.RecordInfo;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
public class BeautyPanelView extends RelativeLayout {
|
||||
|
||||
private final String TAG = BeautyPanelView.class.getSimpleName() + "_" + hashCode();
|
||||
private final Context mContext;
|
||||
private final TUIMultimediaRecordCore mRecordCore;
|
||||
private final BeautyInfo mBeautyInfo;
|
||||
private final RecordInfo mRecordInfo;
|
||||
private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
int beautyLevel = progress * 10 / seekBar.getMax();
|
||||
BeautyItem beautyItem = mBeautyInfo.tuiDataSelectedBeautyItem.get();
|
||||
if (beautyItem != null) {
|
||||
beautyItem.setLevel(beautyLevel);
|
||||
setBeauty(beautyItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
}
|
||||
};
|
||||
private final OnTabSelectedListener onTabSelectedListener = new OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(Tab tab) {
|
||||
changeTabSelectedStatus(tab, true);
|
||||
BeautyType beautyType = mBeautyInfo.getBeautyType(tab.getPosition());
|
||||
if (beautyType == null) {
|
||||
return;
|
||||
}
|
||||
mBeautyInfo.setSelectedItemIndex(tab.getPosition(), beautyType.getSelectedItemIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(Tab tab) {
|
||||
changeTabSelectedStatus(tab, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(Tab tab) {
|
||||
}
|
||||
};
|
||||
private TabLayout mBeautyTypeTabView;
|
||||
private SeekBar mStrengthSettingSeekBar;
|
||||
private View mLayoutStrengthSetting;
|
||||
TUIMultimediaDataObserver<BeautyItem> mOnSelectedBeautyItemChanged = beautyItem -> {
|
||||
updateBeautyTab(beautyItem);
|
||||
updateSeekBar(beautyItem);
|
||||
setBeauty(beautyItem);
|
||||
};
|
||||
|
||||
public BeautyPanelView(Context context, TUIMultimediaRecordCore recordCore, RecordInfo recordInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mRecordCore = recordCore;
|
||||
mRecordInfo = recordInfo;
|
||||
mBeautyInfo = recordInfo.beautyInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
super.setVisibility(visibility);
|
||||
mRecordInfo.tuiDataShowBeautyView.set(visibility == VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
LiteavLog.i(TAG, "onAttachedToWindow");
|
||||
super.onAttachedToWindow();
|
||||
mBeautyInfo.tuiDataSelectedBeautyItem.observe(mOnSelectedBeautyItemChanged);
|
||||
initView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
LiteavLog.i(TAG, "onDetachedFromWindow");
|
||||
mStrengthSettingSeekBar.setOnSeekBarChangeListener(null);
|
||||
mBeautyTypeTabView.removeOnTabSelectedListener(onTabSelectedListener);
|
||||
mBeautyInfo.tuiDataSelectedBeautyItem.removeObserver(mOnSelectedBeautyItemChanged);
|
||||
removeAllViews();
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
private void initView() {
|
||||
LayoutInflater.from(mContext).inflate(R.layout.multimedia_plugin_record_beauty_panel_view, this);
|
||||
initBeautySettingView();
|
||||
}
|
||||
|
||||
private void initBeautySettingView() {
|
||||
mStrengthSettingSeekBar = findViewById(R.id.beauty_strength_seek_bar);
|
||||
mStrengthSettingSeekBar.setOnSeekBarChangeListener(mSeekBarChangeListener);
|
||||
mLayoutStrengthSetting = findViewById(R.id.rl_beauty_strength_setting);
|
||||
|
||||
ViewPager2 beautyPager = findViewById(R.id.record_view_pager);
|
||||
BeautyTypeAdapter adapter = new BeautyTypeAdapter(mContext, mBeautyInfo);
|
||||
beautyPager.setAdapter(adapter);
|
||||
beautyPager.setUserInputEnabled(false);
|
||||
|
||||
mBeautyTypeTabView = findViewById(R.id.beauty_type_tab_view);
|
||||
mBeautyTypeTabView.addOnTabSelectedListener(onTabSelectedListener);
|
||||
mBeautyTypeTabView.setTabMode(TabLayout.MODE_SCROLLABLE);
|
||||
new TabLayoutMediator(mBeautyTypeTabView, beautyPager, this::setTabText).attach();
|
||||
}
|
||||
|
||||
private void setTabText(@NonNull Tab tab, int position) {
|
||||
BeautyType beautyType = mBeautyInfo.getBeautyType(position);
|
||||
TextView customTextView = new TextView(getContext());
|
||||
customTextView.setGravity(Gravity.CENTER);
|
||||
String text = beautyType != null ? beautyType.getName() + " " : "";
|
||||
customTextView.setText(text);
|
||||
customTextView.setMaxLines(1);
|
||||
customTextView.setTextColor(
|
||||
TUIMultimediaResourceUtils.getResources().getColor(R.color.multimedia_plugin_record_beauty_type_text_color_normal));
|
||||
|
||||
float textSize = TUIMultimediaResourceUtils.getResources().getDimension(R.dimen.multimedia_plugin_common_tab_text_size);
|
||||
customTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
|
||||
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
|
||||
customTextView.setTypeface(Typeface.create(fontName, Typeface.NORMAL));
|
||||
tab.setCustomView(customTextView);
|
||||
}
|
||||
|
||||
private void changeTabSelectedStatus(Tab tab, boolean isSelected) {
|
||||
TextView tabTextView = (TextView) tab.getCustomView();
|
||||
if (tabTextView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_normal;
|
||||
int textColorFontStyle = Typeface.NORMAL;
|
||||
if (isSelected) {
|
||||
textColorResourceId = R.color.multimedia_plugin_record_beauty_type_text_color_selected;
|
||||
textColorFontStyle = Typeface.BOLD;
|
||||
}
|
||||
|
||||
tabTextView.setTextColor(
|
||||
TUIMultimediaResourceUtils.getResources().getColor(textColorResourceId));
|
||||
String fontName = TUIMultimediaResourceUtils.getString(R.string.multimedia_plugin_common_font);
|
||||
tabTextView.setTypeface(Typeface.create(fontName, textColorFontStyle));
|
||||
}
|
||||
|
||||
private void updateBeautyTab(BeautyItem beautyItem) {
|
||||
if (beautyItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int beautyTypeIndex = mBeautyInfo.getItemTypeIndex(beautyItem);
|
||||
if (mBeautyTypeTabView.getSelectedTabPosition() == beautyTypeIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tab tab = mBeautyTypeTabView.getTabAt(beautyTypeIndex);
|
||||
if (tab != null) {
|
||||
tab.select();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSeekBar(BeautyItem beautyItem) {
|
||||
if (beautyItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int level = beautyItem.getLevel();
|
||||
if (level < 0) {
|
||||
mLayoutStrengthSetting.setVisibility(INVISIBLE);
|
||||
} else {
|
||||
mLayoutStrengthSetting.setVisibility(VISIBLE);
|
||||
mStrengthSettingSeekBar.setProgress(level * mStrengthSettingSeekBar.getMax() / 10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setBeauty(BeautyItem beautyItem) {
|
||||
if (beautyItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeautyInnerType beautyType = beautyItem.getInnerType();
|
||||
int level = beautyItem.getLevel();
|
||||
LiteavLog.i(TAG, "set Beauty. beautyType " + beautyType
|
||||
+ " beautyName " + beautyItem.getName() + " level " + level);
|
||||
switch (beautyType) {
|
||||
case BEAUTY_SMOOTH:
|
||||
mRecordCore.setBeautyStyleAndLevel(0, level);
|
||||
break;
|
||||
case BEAUTY_NATURAL:
|
||||
mRecordCore.setBeautyStyleAndLevel(1, level);
|
||||
break;
|
||||
case BEAUTY_PITU:
|
||||
mRecordCore.setBeautyStyleAndLevel(2, level);
|
||||
break;
|
||||
case BEAUTY_WHITE:
|
||||
mRecordCore.setWhitenessLevel(level);
|
||||
break;
|
||||
case BEAUTY_RUDDY:
|
||||
mRecordCore.setRuddyLevel(level);
|
||||
break;
|
||||
case BEAUTY_FILTER:
|
||||
mRecordCore.setFilterAndStrength(beautyItem.getFilterBitmap(), level);
|
||||
break;
|
||||
case BEAUTY_NONE:
|
||||
mRecordCore.setWhitenessLevel(0);
|
||||
mRecordCore.setRuddyLevel(0);
|
||||
mRecordCore.setBeautyStyleAndLevel(1, 0);
|
||||
mRecordCore.setBeautyStyleAndLevel(2, 0);
|
||||
mRecordCore.setBeautyStyleAndLevel(3, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||