1、个人信息完善
2、集成聊天功能 3、完成登录
This commit is contained in:
56
timcommon/build.gradle
Normal file
56
timcommon/build.gradle
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
plugins {
|
||||||
|
id 'com.android.library'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
namespace "com.tencent.qcloud.tuikit.timcommon"
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 19
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
res.srcDirs += "src/main/res-light"
|
||||||
|
res.srcDirs += "src/main/res-lively"
|
||||||
|
res.srcDirs += "src/main/res-serious"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
/*plugin-build-Begin
|
||||||
|
|
||||||
|
compileOnly fileTree(include: ['*.jar','*.aar'], dir: '../../../../tuikit/android/libs')
|
||||||
|
|
||||||
|
plugin-build-End*/
|
||||||
|
|
||||||
|
def projects = this.rootProject.getAllprojects().stream().map { project -> project.name }.collect()
|
||||||
|
api projects.contains("tuicore") ? project(':tuicore') : "com.tencent.liteav.tuikit:tuicore:8.5.6864"
|
||||||
|
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'com.google.code.gson:gson:2.9.1'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
|
implementation 'androidx.viewpager2:viewpager2:1.0.0'
|
||||||
|
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
38
timcommon/src/main/AndroidManifest.xml
Normal file
38
timcommon/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application >
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.SelectionActivity"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
<activity
|
||||||
|
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.ImageSelectActivity"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.SelectionMinimalistActivity"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
<activity
|
||||||
|
android:name="com.tencent.qcloud.tuikit.timcommon.component.activities.ImageSelectMinimalistActivity"
|
||||||
|
android:screenOrientation="portrait" />
|
||||||
|
|
||||||
|
<activity android:name="com.tencent.qcloud.tuikit.timcommon.util.ActivityResultResolver$ActivityResultProxyActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
|
android:multiprocess="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/CoreActivityTranslucent"
|
||||||
|
android:windowSoftInputMode="stateHidden|stateAlwaysHidden"/>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="com.tencent.qcloud.tuikit.timcommon.util.FileProvider"
|
||||||
|
android:authorities="${applicationId}.timcommon.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths_public"/>
|
||||||
|
</provider>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon;
|
||||||
|
|
||||||
|
public class TIMCommonConfig {
|
||||||
|
private static boolean enableGroupGridAvatar = true;
|
||||||
|
private static int defaultAvatarImage;
|
||||||
|
private static int defaultGroupAvatarImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets whether to display the avatar in the nine-square grid style in the group conversation, the default is true
|
||||||
|
*/
|
||||||
|
public static boolean isEnableGroupGridAvatar() {
|
||||||
|
return enableGroupGridAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to display the avatar in the nine-square grid style in group conversations
|
||||||
|
*/
|
||||||
|
public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
|
||||||
|
TIMCommonConfig.enableGroupGridAvatar = enableGroupGridAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the default avatar for c2c conversation
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getDefaultAvatarImage() {
|
||||||
|
return defaultAvatarImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*Set the default avatar for c2c conversation
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static void setDefaultAvatarImage(int defaultAvatarImage) {
|
||||||
|
TIMCommonConfig.defaultAvatarImage = defaultAvatarImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the default avatar for group conversation
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getDefaultGroupAvatarImage() {
|
||||||
|
return defaultGroupAvatarImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*Set the default avatar for group conversation
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static void setDefaultGroupAvatarImage(int defaultGroupAvatarImage) {
|
||||||
|
TIMCommonConfig.defaultGroupAvatarImage = defaultGroupAvatarImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon;
|
||||||
|
|
||||||
|
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.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuicore.annotations.TUIInitializerID;
|
||||||
|
import com.tencent.qcloud.tuicore.interfaces.TUIInitializer;
|
||||||
|
|
||||||
|
@AutoService(TUIInitializer.class)
|
||||||
|
@TUIInitializerID("TIMCommon")
|
||||||
|
public class TIMCommonService implements TUIInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Context context) {
|
||||||
|
TUIThemeManager.addLightTheme(R.style.TIMCommonLightTheme);
|
||||||
|
TUIThemeManager.addLivelyTheme(R.style.TIMCommonLivelyTheme);
|
||||||
|
TUIThemeManager.addSeriousTheme(R.style.TIMCommonSeriousTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context getAppContext() {
|
||||||
|
return ServiceInitializer.getAppContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class ChatFace implements Serializable {
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
protected String faceUrl;
|
||||||
|
private FaceGroup<? extends ChatFace> faceGroup;
|
||||||
|
private String faceKey;
|
||||||
|
private String faceName;
|
||||||
|
private boolean autoMirrored = false;
|
||||||
|
|
||||||
|
public void setFaceKey(String faceKey) {
|
||||||
|
this.faceKey = faceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFaceKey() {
|
||||||
|
return faceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceName(String faceName) {
|
||||||
|
this.faceName = faceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFaceName() {
|
||||||
|
return faceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceGroup(FaceGroup<? extends ChatFace> faceGroup) {
|
||||||
|
this.faceGroup = faceGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FaceGroup<? extends ChatFace> getFaceGroup() {
|
||||||
|
return faceGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidth(int width) {
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeight(int height) {
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceUrl(String faceUrl) {
|
||||||
|
this.faceUrl = faceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFaceUrl() {
|
||||||
|
return faceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoMirrored(boolean autoMirrored) {
|
||||||
|
this.autoMirrored = autoMirrored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoMirrored() {
|
||||||
|
return autoMirrored;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Custom expression attribute class
|
||||||
|
*/
|
||||||
|
public class CustomFace extends ChatFace {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param assetPath
|
||||||
|
*/
|
||||||
|
public void setAssetPath(String assetPath) {
|
||||||
|
this.faceUrl = "file:///android_asset/" + assetPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
public class Emoji extends ChatFace {
|
||||||
|
private Bitmap icon;
|
||||||
|
|
||||||
|
public Bitmap getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIcon(Bitmap icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class FaceGroup<T extends ChatFace> {
|
||||||
|
private int groupID;
|
||||||
|
private String groupName;
|
||||||
|
private String desc;
|
||||||
|
private Object faceGroupIconUrl;
|
||||||
|
private int pageRowCount;
|
||||||
|
private int pageColumnCount;
|
||||||
|
private boolean isEmoji = false;
|
||||||
|
private final Map<String, T> faces = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
public int getGroupID() {
|
||||||
|
return groupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupID(int groupID) {
|
||||||
|
this.groupID = groupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupName(String groupName) {
|
||||||
|
this.groupName = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupName() {
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmojiGroup() {
|
||||||
|
return isEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDesc() {
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDesc(String desc) {
|
||||||
|
this.desc = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceGroupIconUrl(Object faceGroupIconUrl) {
|
||||||
|
this.faceGroupIconUrl = faceGroupIconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getFaceGroupIconUrl() {
|
||||||
|
return faceGroupIconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageRowCount() {
|
||||||
|
return pageRowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageRowCount(int pageRowCount) {
|
||||||
|
this.pageRowCount = pageRowCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageColumnCount() {
|
||||||
|
return pageColumnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPageColumnCount(int pageColumnCount) {
|
||||||
|
this.pageColumnCount = pageColumnCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<T> getFaces() {
|
||||||
|
return new ArrayList<>(faces.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFace(String faceKey, T face) {
|
||||||
|
if (face instanceof Emoji) {
|
||||||
|
isEmoji = true;
|
||||||
|
}
|
||||||
|
face.setFaceGroup(this);
|
||||||
|
faces.put(faceKey, face);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getFace(String faceKey) {
|
||||||
|
if (TextUtils.isEmpty(faceKey)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
T face = faces.get(faceKey);
|
||||||
|
if (face == null) {
|
||||||
|
int index = faceKey.lastIndexOf("@2x");
|
||||||
|
if (index == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String oldFaceKey = faceKey.substring(0, index);
|
||||||
|
face = faces.get(oldFaceKey);
|
||||||
|
}
|
||||||
|
return face;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class FriendProfileBean extends UserBean implements Serializable {
|
||||||
|
private int allowType;
|
||||||
|
|
||||||
|
public int getAllowType() {
|
||||||
|
return allowType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowType(int allowType) {
|
||||||
|
this.allowType = allowType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import com.tencent.imsdk.group.GroupMemberInfo;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class GroupProfileBean implements Serializable {
|
||||||
|
private String groupName;
|
||||||
|
private String groupID;
|
||||||
|
private String groupType;
|
||||||
|
private String notification;
|
||||||
|
private String groupIntroduction;
|
||||||
|
private String groupFaceUrl;
|
||||||
|
private int memberCount;
|
||||||
|
private int roleInGroup;
|
||||||
|
private int recvOpt;
|
||||||
|
private int approveOpt;
|
||||||
|
private int addOpt;
|
||||||
|
private boolean isAllMuted = false;
|
||||||
|
|
||||||
|
public String getGroupName() {
|
||||||
|
return groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupName(String groupName) {
|
||||||
|
this.groupName = groupName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupID() {
|
||||||
|
return groupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupID(String groupID) {
|
||||||
|
this.groupID = groupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupType() {
|
||||||
|
return groupType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupType(String groupType) {
|
||||||
|
this.groupType = groupType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotification() {
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotification(String notification) {
|
||||||
|
this.notification = notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupIntroduction() {
|
||||||
|
return groupIntroduction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupIntroduction(String groupIntroduction) {
|
||||||
|
this.groupIntroduction = groupIntroduction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupFaceUrl() {
|
||||||
|
return groupFaceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupFaceUrl(String groupFaceUrl) {
|
||||||
|
this.groupFaceUrl = groupFaceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMemberCount() {
|
||||||
|
return memberCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMemberCount(int memberCount) {
|
||||||
|
this.memberCount = memberCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRoleInGroup() {
|
||||||
|
return roleInGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleInGroup(int roleInGroup) {
|
||||||
|
this.roleInGroup = roleInGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canManage() {
|
||||||
|
return roleInGroup == GroupMemberInfo.MEMBER_ROLE_OWNER || roleInGroup == GroupMemberInfo.MEMBER_ROLE_ADMINISTRATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOwner() {
|
||||||
|
return roleInGroup == GroupMemberInfo.MEMBER_ROLE_OWNER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAdmin() {
|
||||||
|
return roleInGroup == GroupMemberInfo.MEMBER_ROLE_ADMINISTRATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllMuted(boolean allMuted) {
|
||||||
|
isAllMuted = allMuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllMuted() {
|
||||||
|
return isAllMuted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApproveOpt(int approveOpt) {
|
||||||
|
this.approveOpt = approveOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getApproveOpt() {
|
||||||
|
return approveOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAddOpt() {
|
||||||
|
return addOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddOpt(int addOpt) {
|
||||||
|
this.addOpt = addOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecvOpt(int recvOpt) {
|
||||||
|
this.recvOpt = recvOpt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRecvOpt() {
|
||||||
|
return recvOpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Carrying function macros through messages,Mainly used to be compatible with old and new versions,Use the cloudCustomData field.
|
||||||
|
* Such as Typing function.
|
||||||
|
*/
|
||||||
|
public class MessageFeature implements Serializable {
|
||||||
|
public static final int VERSION = 1;
|
||||||
|
|
||||||
|
private int needTyping = 1; // message typing feature ...
|
||||||
|
private int version = VERSION;
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(int version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNeedTyping() {
|
||||||
|
return needTyping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedTyping(int needTyping) {
|
||||||
|
this.needTyping = needTyping;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import com.tencent.imsdk.v2.V2TIMMessageReceipt;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class MessageReceiptInfo implements Serializable {
|
||||||
|
private V2TIMMessageReceipt messageReceipt;
|
||||||
|
|
||||||
|
public void setMessageReceipt(V2TIMMessageReceipt messageReceipt) {
|
||||||
|
this.messageReceipt = messageReceipt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserID() {
|
||||||
|
if (messageReceipt != null) {
|
||||||
|
return messageReceipt.getUserID();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPeerRead() {
|
||||||
|
if (messageReceipt != null) {
|
||||||
|
return messageReceipt.isPeerRead();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupID() {
|
||||||
|
if (messageReceipt != null) {
|
||||||
|
return messageReceipt.getGroupID();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getReadCount() {
|
||||||
|
if (messageReceipt != null) {
|
||||||
|
return messageReceipt.getReadCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUnreadCount() {
|
||||||
|
if (messageReceipt != null) {
|
||||||
|
return messageReceipt.getUnreadCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMsgID() {
|
||||||
|
if (messageReceipt != null) {
|
||||||
|
return messageReceipt.getMsgID();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MessageRepliesBean implements Serializable {
|
||||||
|
public static final int VERSION = 1;
|
||||||
|
private List<ReplyBean> replies;
|
||||||
|
private int version = VERSION;
|
||||||
|
|
||||||
|
public void addReplyMessage(String messageId, String messageAbstract, String sender) {
|
||||||
|
if (replies == null) {
|
||||||
|
replies = new ArrayList<>();
|
||||||
|
}
|
||||||
|
for (ReplyBean replyBean : replies) {
|
||||||
|
if (TextUtils.equals(replyBean.messageID, messageId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplyBean replyBean = new ReplyBean();
|
||||||
|
replyBean.messageID = messageId;
|
||||||
|
replyBean.messageAbstract = messageAbstract;
|
||||||
|
replyBean.messageSender = sender;
|
||||||
|
replies.add(replyBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeReplyMessage(String messageID) {
|
||||||
|
if (replies == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ReplyBean replyBean : replies) {
|
||||||
|
if (TextUtils.equals(replyBean.messageID, messageID)) {
|
||||||
|
replies.remove(replyBean);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVersion(int version) {
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ReplyBean> getReplies() {
|
||||||
|
return replies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplies(List<ReplyBean> replies) {
|
||||||
|
this.replies = replies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRepliesSize() {
|
||||||
|
if (replies != null) {
|
||||||
|
return replies.size();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class ReplyBean implements Serializable {
|
||||||
|
private String messageID;
|
||||||
|
private String messageAbstract;
|
||||||
|
private String messageSender;
|
||||||
|
private transient String senderFaceUrl;
|
||||||
|
private transient String senderShowName;
|
||||||
|
|
||||||
|
public String getMessageID() {
|
||||||
|
return messageID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageID(String messageID) {
|
||||||
|
this.messageID = messageID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageAbstract() {
|
||||||
|
return messageAbstract;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageAbstract(String messageAbstract) {
|
||||||
|
this.messageAbstract = messageAbstract;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageSender() {
|
||||||
|
return messageSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageSender(String messageSender) {
|
||||||
|
this.messageSender = messageSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderFaceUrl(String senderFaceUrl) {
|
||||||
|
this.senderFaceUrl = senderFaceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSenderFaceUrl() {
|
||||||
|
return senderFaceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderShowName(String senderShowName) {
|
||||||
|
this.senderShowName = senderShowName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSenderShowName() {
|
||||||
|
if (TextUtils.isEmpty(senderShowName)) {
|
||||||
|
return messageSender;
|
||||||
|
}
|
||||||
|
return senderShowName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,472 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMMessage;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.MessageBuilder;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.MessageParser;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonConstants;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public abstract class TUIMessageBean implements Serializable {
|
||||||
|
|
||||||
|
public static final int MSG_STATUS_SENDING = V2TIMMessage.V2TIM_MSG_STATUS_SENDING;
|
||||||
|
public static final int MSG_STATUS_SEND_SUCCESS = V2TIMMessage.V2TIM_MSG_STATUS_SEND_SUCC;
|
||||||
|
public static final int MSG_STATUS_SEND_FAIL = V2TIMMessage.V2TIM_MSG_STATUS_SEND_FAIL;
|
||||||
|
public static final int MSG_STATUS_REVOKE = V2TIMMessage.V2TIM_MSG_STATUS_LOCAL_REVOKED;
|
||||||
|
|
||||||
|
public static final int MSG_SOURCE_UNKNOWN = 0;
|
||||||
|
|
||||||
|
public static final int MSG_SOURCE_ONLINE_PUSH = 1;
|
||||||
|
|
||||||
|
public static final int MSG_SOURCE_GET_HISTORY = 2;
|
||||||
|
|
||||||
|
private V2TIMMessage v2TIMMessage;
|
||||||
|
private long msgTime;
|
||||||
|
private String extra;
|
||||||
|
private String id;
|
||||||
|
private boolean isGroup;
|
||||||
|
private int status;
|
||||||
|
private String selectText;
|
||||||
|
private boolean excludeFromHistory;
|
||||||
|
private boolean isUseMsgReceiverAvatar = false;
|
||||||
|
private boolean isEnableForward = true;
|
||||||
|
private UserBean revoker;
|
||||||
|
private boolean hasRiskContent = false;
|
||||||
|
private int messageSource = 0;
|
||||||
|
private MessageReceiptInfo messageReceiptInfo;
|
||||||
|
private MessageRepliesBean messageRepliesBean;
|
||||||
|
private boolean hasReaction = false;
|
||||||
|
private Map<String, UserBean> userBeanMap = new LinkedHashMap<>();
|
||||||
|
private boolean isSending = false;
|
||||||
|
private boolean isProcessing = false;
|
||||||
|
private Object processingThumbnail;
|
||||||
|
private String userId = "";
|
||||||
|
private String groupId = "";
|
||||||
|
|
||||||
|
public void setExcludeFromHistory(boolean excludeFromHistory) {
|
||||||
|
this.excludeFromHistory = excludeFromHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExcludeFromHistory() {
|
||||||
|
return excludeFromHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseMsgReceiverAvatar(boolean useMsgReceiverAvatar) {
|
||||||
|
isUseMsgReceiverAvatar = useMsgReceiverAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUseMsgReceiverAvatar() {
|
||||||
|
return isUseMsgReceiverAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableForward() {
|
||||||
|
return isEnableForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableForward(boolean enableForward) {
|
||||||
|
isEnableForward = enableForward;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageRepliesBean getMessageRepliesBean() {
|
||||||
|
return messageRepliesBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageRepliesBean(MessageRepliesBean messageRepliesBean) {
|
||||||
|
this.messageRepliesBean = messageRepliesBean;
|
||||||
|
MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_REPLIES_KEY, messageRepliesBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageReceiptInfo(MessageReceiptInfo messageReceiptInfo) {
|
||||||
|
this.messageReceiptInfo = messageReceiptInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getReadCount() {
|
||||||
|
if (messageReceiptInfo != null) {
|
||||||
|
return messageReceiptInfo.getReadCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUnreadCount() {
|
||||||
|
if (messageReceiptInfo != null) {
|
||||||
|
return messageReceiptInfo.getUnreadCount();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCommonAttribute(V2TIMMessage v2TIMMessage) {
|
||||||
|
msgTime = System.currentTimeMillis() / 1000;
|
||||||
|
this.v2TIMMessage = v2TIMMessage;
|
||||||
|
|
||||||
|
if (v2TIMMessage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = v2TIMMessage.getMsgID();
|
||||||
|
isGroup = !TextUtils.isEmpty(v2TIMMessage.getGroupID());
|
||||||
|
hasRiskContent = v2TIMMessage.hasRiskContent();
|
||||||
|
if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_LOCAL_REVOKED) {
|
||||||
|
status = MSG_STATUS_REVOKE;
|
||||||
|
if (isSelf()) {
|
||||||
|
extra = TIMCommonService.getAppContext().getString(R.string.revoke_tips_you);
|
||||||
|
} else if (isGroup) {
|
||||||
|
extra = "\"" + getSender() + "\"" + TIMCommonService.getAppContext().getString(R.string.revoke_tips);
|
||||||
|
} else {
|
||||||
|
extra = TIMCommonService.getAppContext().getString(R.string.revoke_tips_other);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isSelf()) {
|
||||||
|
if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SEND_FAIL) {
|
||||||
|
status = MSG_STATUS_SEND_FAIL;
|
||||||
|
} else if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SEND_SUCC) {
|
||||||
|
status = MSG_STATUS_SEND_SUCCESS;
|
||||||
|
} else if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SENDING) {
|
||||||
|
status = MSG_STATUS_SENDING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messageRepliesBean = MessageParser.parseMessageReplies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPeerRead() {
|
||||||
|
if (messageReceiptInfo != null) {
|
||||||
|
return messageReceiptInfo.isPeerRead();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRiskContent() {
|
||||||
|
return hasRiskContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllRead() {
|
||||||
|
return getUnreadCount() == 0 && getReadCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUnread() {
|
||||||
|
return getReadCount() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get a summary of messages to display in the conversation list
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String onGetDisplayString() {
|
||||||
|
return getExtra();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onProcessMessage(V2TIMMessage v2TIMMessage);
|
||||||
|
|
||||||
|
public final long getMessageTime() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
long timestamp = v2TIMMessage.getTimestamp();
|
||||||
|
if (timestamp != 0) {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msgTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMsgSeq() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getSeq();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getUserID();
|
||||||
|
}
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupId(String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelf() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.isSelf();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSender() {
|
||||||
|
String sender = null;
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
sender = v2TIMMessage.getSender();
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(sender)) {
|
||||||
|
sender = V2TIMManager.getInstance().getLoginUser();
|
||||||
|
}
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public V2TIMMessage getV2TIMMessage() {
|
||||||
|
return v2TIMMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGroup() {
|
||||||
|
return isGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroup(boolean group) {
|
||||||
|
isGroup = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupId() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getGroupID();
|
||||||
|
}
|
||||||
|
return groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameCard() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getNameCard();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNickName() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getNickName();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFriendRemark() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getFriendRemark();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserDisplayName() {
|
||||||
|
String displayName;
|
||||||
|
if (!TextUtils.isEmpty(getNameCard())) {
|
||||||
|
displayName = getNameCard();
|
||||||
|
} else if (!TextUtils.isEmpty(getFriendRemark())) {
|
||||||
|
displayName = getFriendRemark();
|
||||||
|
} else if (!TextUtils.isEmpty(getNickName())) {
|
||||||
|
displayName = getNickName();
|
||||||
|
} else {
|
||||||
|
displayName = getSender();
|
||||||
|
}
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFaceUrl() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getFaceUrl();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(int status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtra(String extra) {
|
||||||
|
this.extra = extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExtra() {
|
||||||
|
return extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMsgType() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getElemType();
|
||||||
|
} else {
|
||||||
|
return V2TIMMessage.V2TIM_ELEM_TYPE_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedReadReceipt() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.isNeedReadReceipt();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedReadReceipt(boolean isNeedReceipt) {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
v2TIMMessage.setNeedReadReceipt(isNeedReceipt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setV2TIMMessage(V2TIMMessage v2TIMMessage) {
|
||||||
|
this.v2TIMMessage = v2TIMMessage;
|
||||||
|
setCommonAttribute(v2TIMMessage);
|
||||||
|
onProcessMessage(v2TIMMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(TUIMessageBean messageBean) {
|
||||||
|
setV2TIMMessage(messageBean.getV2TIMMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectText() {
|
||||||
|
return selectText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectText(String text) {
|
||||||
|
this.selectText = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageFeature isSupportTyping() {
|
||||||
|
return MessageParser.isSupportTyping(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageTypingFeature(MessageFeature messageFeature) {
|
||||||
|
MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_FEATURE_KEY, messageFeature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserBean getRevoker() {
|
||||||
|
if (revoker != null) {
|
||||||
|
return revoker;
|
||||||
|
}
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
V2TIMUserFullInfo fullInfo = v2TIMMessage.getRevokerInfo();
|
||||||
|
if (fullInfo != null) {
|
||||||
|
revoker = new UserBean();
|
||||||
|
revoker.setUserId(fullInfo.getUserID());
|
||||||
|
revoker.setNickName(fullInfo.getNickName());
|
||||||
|
revoker.setFaceUrl(fullInfo.getFaceUrl());
|
||||||
|
return revoker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRevoker(UserBean revoker) {
|
||||||
|
this.revoker = revoker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRevokeReason() {
|
||||||
|
if (v2TIMMessage != null) {
|
||||||
|
return v2TIMMessage.getRevokeReason();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasRiskContent(boolean hasRiskContent) {
|
||||||
|
this.hasRiskContent = hasRiskContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageSource() {
|
||||||
|
return messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageSource(int messageSource) {
|
||||||
|
this.messageSource = messageSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean customReloadWithNewMsg(V2TIMMessage v2TIMMessage) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHasReaction() {
|
||||||
|
return hasReaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasReaction(boolean hasReaction) {
|
||||||
|
this.hasReaction = hasReaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRevoked() {
|
||||||
|
return getStatus() == TUIMessageBean.MSG_STATUS_REVOKE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserBean(String userID, UserBean userBean) {
|
||||||
|
userBeanMap.put(userID, userBean);
|
||||||
|
if (messageRepliesBean != null) {
|
||||||
|
List<MessageRepliesBean.ReplyBean> replyBeanList = messageRepliesBean.getReplies();
|
||||||
|
if (replyBeanList != null && !replyBeanList.isEmpty()) {
|
||||||
|
for (MessageRepliesBean.ReplyBean replyBean : replyBeanList) {
|
||||||
|
if (userBean != null && TextUtils.equals(replyBean.getMessageSender(), userID)) {
|
||||||
|
replyBean.setSenderFaceUrl(userBean.getFaceUrl());
|
||||||
|
replyBean.setSenderShowName(userBean.getDisplayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserBean getUserBean(String userID) {
|
||||||
|
return userBeanMap.get(userID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSending() {
|
||||||
|
return isSending;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSending(boolean sending) {
|
||||||
|
this.isSending = sending;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getAdditionalUserIDList() {
|
||||||
|
Set<String> userIdSet = new HashSet<>();
|
||||||
|
MessageRepliesBean messageRepliesBean = getMessageRepliesBean();
|
||||||
|
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
|
||||||
|
List<MessageRepliesBean.ReplyBean> replyBeanList = messageRepliesBean.getReplies();
|
||||||
|
for (MessageRepliesBean.ReplyBean replyBean : replyBeanList) {
|
||||||
|
userIdSet.add(replyBean.getMessageSender());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userIdSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProcessing(boolean processing) {
|
||||||
|
isProcessing = processing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProcessing() {
|
||||||
|
return isProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getProcessingThumbnail() {
|
||||||
|
return processingThumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProcessingThumbnail(Object processingThumbnail) {
|
||||||
|
this.processingThumbnail = processingThumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean needAsyncGetDisplayString() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends TUIReplyQuoteBean> getReplyQuoteBeanClass() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public abstract class TUIReplyQuoteBean<T extends TUIMessageBean> implements Serializable {
|
||||||
|
private T messageBean;
|
||||||
|
protected String defaultAbstract;
|
||||||
|
protected int messageType;
|
||||||
|
|
||||||
|
public abstract void onProcessReplyQuoteBean(T messageBean);
|
||||||
|
|
||||||
|
public void setMessageBean(T messageBean) {
|
||||||
|
this.messageBean = messageBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultAbstract(String defaultAbstract) {
|
||||||
|
this.defaultAbstract = defaultAbstract;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageType(int messageType) {
|
||||||
|
this.messageType = messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMessageType() {
|
||||||
|
return messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getMessageBean() {
|
||||||
|
return messageBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasRiskContent() {
|
||||||
|
if (messageBean != null) {
|
||||||
|
return messageBean.hasRiskContent();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultAbstract() {
|
||||||
|
return defaultAbstract;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.bean;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class UserBean implements Serializable {
|
||||||
|
protected String userId;
|
||||||
|
protected String nickName;
|
||||||
|
protected String nameCard;
|
||||||
|
protected String friendRemark;
|
||||||
|
protected String faceUrl;
|
||||||
|
protected String signature;
|
||||||
|
protected long birthday;
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNickName() {
|
||||||
|
return nickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNickName(String nickName) {
|
||||||
|
this.nickName = nickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFriendRemark() {
|
||||||
|
return friendRemark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFriendRemark(String friendRemark) {
|
||||||
|
this.friendRemark = friendRemark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameCard(String nameCard) {
|
||||||
|
this.nameCard = nameCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNameCard() {
|
||||||
|
return nameCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
if (!TextUtils.isEmpty(nameCard)) {
|
||||||
|
return nameCard;
|
||||||
|
} else if (!TextUtils.isEmpty(friendRemark)) {
|
||||||
|
return friendRemark;
|
||||||
|
} else if (!TextUtils.isEmpty(nickName)) {
|
||||||
|
return nickName;
|
||||||
|
} else {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFaceUrl() {
|
||||||
|
return faceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFaceUrl(String faceUrl) {
|
||||||
|
this.faceUrl = faceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBirthday() {
|
||||||
|
return birthday;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignature(String signature) {
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBirthday(long birthday) {
|
||||||
|
this.birthday = birthday;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
|
||||||
|
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public abstract class MessageBaseHolder<T extends TUIMessageBean> extends RecyclerView.ViewHolder {
|
||||||
|
public ICommonMessageAdapter mAdapter;
|
||||||
|
protected OnItemClickListener onItemClickListener;
|
||||||
|
|
||||||
|
public TextView chatTimeText;
|
||||||
|
public FrameLayout msgContentFrame;
|
||||||
|
public LinearLayout msgReplyDetailLayout;
|
||||||
|
public LinearLayout msgArea;
|
||||||
|
public LinearLayout msgAreaAndReply;
|
||||||
|
public FrameLayout reactionArea;
|
||||||
|
public CheckBox mMutiSelectCheckBox;
|
||||||
|
public RelativeLayout rightGroupLayout;
|
||||||
|
public RelativeLayout mContentLayout;
|
||||||
|
private HighlightListener highlightListener;
|
||||||
|
protected T currentMessageBean;
|
||||||
|
|
||||||
|
public MessageBaseHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
chatTimeText = itemView.findViewById(R.id.message_top_time_tv);
|
||||||
|
msgContentFrame = itemView.findViewById(R.id.msg_content_fl);
|
||||||
|
msgReplyDetailLayout = itemView.findViewById(R.id.msg_reply_detail_fl);
|
||||||
|
reactionArea = itemView.findViewById(R.id.message_reaction_area);
|
||||||
|
msgArea = itemView.findViewById(R.id.msg_area);
|
||||||
|
msgAreaAndReply = itemView.findViewById(R.id.msg_area_and_reply);
|
||||||
|
mMutiSelectCheckBox = itemView.findViewById(R.id.select_checkbox);
|
||||||
|
rightGroupLayout = itemView.findViewById(R.id.right_group_layout);
|
||||||
|
mContentLayout = itemView.findViewById(R.id.message_content_layout);
|
||||||
|
initVariableLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getVariableLayout();
|
||||||
|
|
||||||
|
private void setVariableLayout(int resId) {
|
||||||
|
if (msgContentFrame.getChildCount() == 0) {
|
||||||
|
View.inflate(itemView.getContext(), resId, msgContentFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initVariableLayout() {
|
||||||
|
if (getVariableLayout() != 0) {
|
||||||
|
setVariableLayout(getVariableLayout());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdapter(ICommonMessageAdapter adapter) {
|
||||||
|
mAdapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||||
|
this.onItemClickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnItemClickListener getOnItemClickListener() {
|
||||||
|
return this.onItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void layoutViews(final T msg, final int position) {
|
||||||
|
currentMessageBean = msg;
|
||||||
|
registerHighlightListener(msg.getId());
|
||||||
|
setChatTimeStyle();
|
||||||
|
|
||||||
|
if (position > 1) {
|
||||||
|
TUIMessageBean last = mAdapter.getItem(position - 1);
|
||||||
|
if (last != null) {
|
||||||
|
if (msg.getMessageTime() - last.getMessageTime() >= 5 * 60) {
|
||||||
|
chatTimeText.setVisibility(View.VISIBLE);
|
||||||
|
chatTimeText.setText(DateTimeUtil.getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
|
||||||
|
} else {
|
||||||
|
chatTimeText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chatTimeText.setVisibility(View.VISIBLE);
|
||||||
|
chatTimeText.setText(DateTimeUtil.getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChatTimeStyle() {
|
||||||
|
Drawable chatTimeBubble = TUIConfigClassic.getChatTimeBubble();
|
||||||
|
if (chatTimeBubble != null) {
|
||||||
|
chatTimeText.setBackground(chatTimeBubble);
|
||||||
|
}
|
||||||
|
int chatTimeFontColor = TUIConfigClassic.getChatTimeFontColor();
|
||||||
|
if (chatTimeFontColor != TUIConfigClassic.UNDEFINED) {
|
||||||
|
chatTimeText.setTextColor(chatTimeFontColor);
|
||||||
|
}
|
||||||
|
int chatTimeFontSize = TUIConfigClassic.getChatTimeFontSize();
|
||||||
|
if (chatTimeFontSize != TUIConfigClassic.UNDEFINED) {
|
||||||
|
chatTimeText.setTextSize(chatTimeFontSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerHighlightListener(String msgID) {
|
||||||
|
if (highlightListener == null) {
|
||||||
|
highlightListener = new HighlightListener() {
|
||||||
|
@Override
|
||||||
|
public void onHighlightStart() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHighlightEnd() {
|
||||||
|
clearHighLightBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHighlightUpdate(int color) {
|
||||||
|
setHighLightBackground(color);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
HighlightPresenter.registerHighlightListener(msgID, highlightListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRecycled() {
|
||||||
|
if (currentMessageBean != null) {
|
||||||
|
HighlightPresenter.unregisterHighlightListener(currentMessageBean.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleZeroPadding() {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgArea.setPaddingRelative(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleBackground(int resID) {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgArea.setBackgroundResource(resID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleBackground(Drawable drawable) {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgArea.setBackground(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getMessageBubbleBackground() {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return msgArea.getBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHighLightBackground(int color) {
|
||||||
|
Drawable drawable = getMessageBubbleBackground();
|
||||||
|
if (drawable != null) {
|
||||||
|
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearHighLightBackground() {
|
||||||
|
Drawable drawable = getMessageBubbleBackground();
|
||||||
|
if (drawable != null) {
|
||||||
|
drawable.setColorFilter(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,692 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.RequestBuilder;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMMessage;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMValueCallback;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConstants;
|
||||||
|
import com.tencent.qcloud.tuicore.TUICore;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.config.classicui.TUIConfigClassic;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class MessageContentHolder<T extends TUIMessageBean> extends MessageBaseHolder<T> {
|
||||||
|
|
||||||
|
public ImageView leftUserIcon;
|
||||||
|
public ImageView rightUserIcon;
|
||||||
|
public TextView leftUserNameText;
|
||||||
|
public LinearLayout msgContentLinear;
|
||||||
|
public View riskContentLine;
|
||||||
|
public TextView riskContentText;
|
||||||
|
public ProgressBar sendingProgress;
|
||||||
|
public ImageView statusImage;
|
||||||
|
public TextView isReadText;
|
||||||
|
public TextView unreadAudioText;
|
||||||
|
public TextView messageDetailsTimeTv;
|
||||||
|
private FrameLayout bottomContentFrameLayout;
|
||||||
|
private View bottomFailedIv;
|
||||||
|
|
||||||
|
public boolean isForwardMode = false;
|
||||||
|
public boolean isReplyDetailMode = false;
|
||||||
|
public boolean isMultiSelectMode = false;
|
||||||
|
|
||||||
|
private List<TUIMessageBean> mForwardDataSource = new ArrayList<>();
|
||||||
|
protected SelectionHelper selectionHelper;
|
||||||
|
|
||||||
|
// Whether to display the bottom content. The merged-forwarded message details activity does not display the bottom content.
|
||||||
|
protected boolean isNeedShowBottomLayout = true;
|
||||||
|
protected boolean isShowRead = false;
|
||||||
|
private Fragment fragment;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
protected boolean hasRiskContent = false;
|
||||||
|
protected boolean isLayoutOnStart = true;
|
||||||
|
|
||||||
|
public MessageContentHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
leftUserIcon = itemView.findViewById(R.id.left_user_icon_view);
|
||||||
|
rightUserIcon = itemView.findViewById(R.id.right_user_icon_view);
|
||||||
|
leftUserNameText = itemView.findViewById(R.id.left_user_name_tv);
|
||||||
|
msgContentLinear = itemView.findViewById(R.id.msg_content_ll);
|
||||||
|
riskContentLine = itemView.findViewById(R.id.risk_content_line);
|
||||||
|
riskContentText = itemView.findViewById(R.id.risk_content_text);
|
||||||
|
statusImage = itemView.findViewById(R.id.message_status_iv);
|
||||||
|
sendingProgress = itemView.findViewById(R.id.message_sending_pb);
|
||||||
|
sendingProgress.getIndeterminateDrawable().mutate();
|
||||||
|
isReadText = itemView.findViewById(R.id.is_read_tv);
|
||||||
|
unreadAudioText = itemView.findViewById(R.id.audio_unread);
|
||||||
|
messageDetailsTimeTv = itemView.findViewById(R.id.msg_detail_time_tv);
|
||||||
|
bottomContentFrameLayout = itemView.findViewById(R.id.bottom_content_fl);
|
||||||
|
bottomFailedIv = itemView.findViewById(R.id.bottom_failed_iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFragment(Fragment fragment) {
|
||||||
|
this.fragment = fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecyclerView(RecyclerView recyclerView) {
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerView getRecyclerView() {
|
||||||
|
return this.recyclerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setForwardDataSource(List<TUIMessageBean> dataSource) {
|
||||||
|
if (dataSource == null || dataSource.isEmpty()) {
|
||||||
|
mForwardDataSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TUIMessageBean> mediaSource = new ArrayList<>();
|
||||||
|
for (TUIMessageBean messageBean : dataSource) {
|
||||||
|
int type = messageBean.getMsgType();
|
||||||
|
if (type == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE || type == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) {
|
||||||
|
mediaSource.add(messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mForwardDataSource = mediaSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TUIMessageBean> getForwardDataSource() {
|
||||||
|
return mForwardDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layoutViews(final T msg, final int position) {
|
||||||
|
Context context = itemView.getContext();
|
||||||
|
if (TUIUtil.isActivityDestroyed(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRiskContent = msg.hasRiskContent();
|
||||||
|
super.layoutViews(msg, position);
|
||||||
|
setLayoutAlignment(msg);
|
||||||
|
setUserIcon(msg);
|
||||||
|
setUserName(msg);
|
||||||
|
loadAvatar(msg);
|
||||||
|
setSendingProgress(msg);
|
||||||
|
setStatusImage(msg);
|
||||||
|
setMessageBubbleBackground();
|
||||||
|
setOnClickListener(msg, position);
|
||||||
|
|
||||||
|
if (rightGroupLayout != null) {
|
||||||
|
rightGroupLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
msgContentLinear.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
setReadStatus(msg);
|
||||||
|
|
||||||
|
if (isReplyDetailMode) {
|
||||||
|
chatTimeText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
setReplyContent(msg);
|
||||||
|
setReactContent(msg);
|
||||||
|
if (isNeedShowBottomLayout) {
|
||||||
|
setBottomContent(msg);
|
||||||
|
}
|
||||||
|
bottomFailedIv.setVisibility(View.GONE);
|
||||||
|
if (hasRiskContent) {
|
||||||
|
bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_risk_bg);
|
||||||
|
if (bottomContentFrameLayout.getVisibility() == View.VISIBLE) {
|
||||||
|
bottomFailedIv.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
riskContentLine.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
riskContentLine.setVisibility(View.GONE);
|
||||||
|
bottomContentFrameLayout.setBackgroundResource(R.drawable.chat_message_bottom_area_bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMessageBubbleDefaultPadding();
|
||||||
|
layoutVariableViews(msg, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReadStatus(T msg) {
|
||||||
|
// clear isReadText status
|
||||||
|
isReadText.setTextColor(isReadText.getResources().getColor(R.color.text_gray1));
|
||||||
|
isReadText.setOnClickListener(null);
|
||||||
|
|
||||||
|
if (isForwardMode || isReplyDetailMode) {
|
||||||
|
isReadText.setVisibility(View.GONE);
|
||||||
|
unreadAudioText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
if (isShowRead) {
|
||||||
|
if (msg.isSelf() && TUIMessageBean.MSG_STATUS_SEND_SUCCESS == msg.getStatus()) {
|
||||||
|
if (!msg.isNeedReadReceipt()) {
|
||||||
|
isReadText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
showReadText(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isReadText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreadAudioText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLayoutAlignment(TUIMessageBean msg) {
|
||||||
|
if (isForwardMode || isReplyDetailMode) {
|
||||||
|
isLayoutOnStart = true;
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
isLayoutOnStart = false;
|
||||||
|
} else {
|
||||||
|
isLayoutOnStart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isForwardMode || isReplyDetailMode) {
|
||||||
|
msgContentLinear.removeView(msgAreaAndReply);
|
||||||
|
msgContentLinear.addView(msgAreaAndReply);
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
msgContentLinear.removeView(msgAreaAndReply);
|
||||||
|
msgContentLinear.addView(msgAreaAndReply);
|
||||||
|
} else {
|
||||||
|
msgContentLinear.removeView(msgAreaAndReply);
|
||||||
|
msgContentLinear.addView(msgAreaAndReply, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setGravity(isLayoutOnStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMessageBubbleBackground() {
|
||||||
|
if (!TUIConfigClassic.isEnableMessageBubbleStyle()) {
|
||||||
|
setMessageBubbleBackground(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Drawable sendBubble = TUIConfigClassic.getSendBubbleBackground();
|
||||||
|
Drawable receiveBubble = TUIConfigClassic.getReceiveBubbleBackground();
|
||||||
|
Drawable sendErrorBubble = TUIConfigClassic.getSendErrorBubbleBackground();
|
||||||
|
Drawable receiveErrorBubble = TUIConfigClassic.getReceiveErrorBubbleBackground();
|
||||||
|
|
||||||
|
if (hasRiskContent) {
|
||||||
|
if (!isLayoutOnStart) {
|
||||||
|
if (sendErrorBubble != null) {
|
||||||
|
setMessageBubbleBackground(sendErrorBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_right);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (receiveErrorBubble != null) {
|
||||||
|
setMessageBubbleBackground(receiveErrorBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(R.drawable.chat_message_popup_risk_content_border_left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setRiskContent(null);
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
if (receiveBubble != null) {
|
||||||
|
setMessageBubbleBackground(receiveBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sendBubble != null) {
|
||||||
|
setMessageBubbleBackground(sendBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_self_bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setStatusImage(T msg) {
|
||||||
|
statusImage.setVisibility(View.GONE);
|
||||||
|
if (hasRiskContent) {
|
||||||
|
statusImage.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
|
||||||
|
statusImage.setVisibility(View.VISIBLE);
|
||||||
|
statusImage.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onSendFailBtnClick(statusImage, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setRiskContent(String riskContent) {
|
||||||
|
if (TextUtils.isEmpty(riskContent)) {
|
||||||
|
riskContentLine.setVisibility(View.GONE);
|
||||||
|
riskContentText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
riskContentLine.setVisibility(View.VISIBLE);
|
||||||
|
riskContentText.setVisibility(View.VISIBLE);
|
||||||
|
riskContentText.setText(riskContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOnClickListener(T msg, int position) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
onItemClickListener.onMessageLongClick(v, msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
msgArea.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
leftUserIcon.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
onItemClickListener.onUserIconClick(view, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
leftUserIcon.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
onItemClickListener.onUserIconLongClick(view, msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rightUserIcon.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
onItemClickListener.onUserIconClick(view, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
|
||||||
|
msgContentFrame.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgContentFrame, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
msgContentFrame.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageClick(msgContentFrame, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSendingProgress(T msg) {
|
||||||
|
if (isForwardMode || isReplyDetailMode) {
|
||||||
|
hideSendingProgress();
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
if (msg.isSending()) {
|
||||||
|
showSendingProgress();
|
||||||
|
} else {
|
||||||
|
hideSendingProgress();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hideSendingProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showSendingProgress() {
|
||||||
|
sendingProgress.setVisibility(View.VISIBLE);
|
||||||
|
Drawable drawable = sendingProgress.getIndeterminateDrawable();
|
||||||
|
if (drawable instanceof Animatable) {
|
||||||
|
((Animatable) drawable).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void hideSendingProgress() {
|
||||||
|
sendingProgress.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
|
private void setUserName(T msg) {
|
||||||
|
if (isForwardMode || isReplyDetailMode) {
|
||||||
|
leftUserNameText.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
if (TUIConfigClassic.getReceiveNickNameVisibility() != TUIConfigClassic.UNDEFINED) {
|
||||||
|
leftUserNameText.setVisibility(TUIConfigClassic.getReceiveNickNameVisibility());
|
||||||
|
} else {
|
||||||
|
if (msg.isGroup()) {
|
||||||
|
leftUserNameText.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
leftUserNameText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
leftUserNameText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TUIConfigClassic.getReceiveNickNameColor() != TUIConfigClassic.UNDEFINED) {
|
||||||
|
leftUserNameText.setTextColor(TUIConfigClassic.getReceiveNickNameColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TUIConfigClassic.getReceiveNickNameFontSize() != TUIConfigClassic.UNDEFINED) {
|
||||||
|
leftUserNameText.setTextSize(TUIConfigClassic.getReceiveNickNameFontSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
leftUserNameText.setText(msg.getUserDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUserIcon(T msg) {
|
||||||
|
if (isForwardMode || isReplyDetailMode) {
|
||||||
|
leftUserIcon.setVisibility(View.VISIBLE);
|
||||||
|
rightUserIcon.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
leftUserIcon.setVisibility(View.GONE);
|
||||||
|
rightUserIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
leftUserIcon.setVisibility(View.VISIBLE);
|
||||||
|
rightUserIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBottomContent(TUIMessageBean msg) {
|
||||||
|
HashMap<String, Object> param = new HashMap<>();
|
||||||
|
param.put(TUIConstants.TUIChat.MESSAGE_BEAN, msg);
|
||||||
|
param.put(TUIConstants.TUIChat.CHAT_RECYCLER_VIEW, recyclerView);
|
||||||
|
param.put(TUIConstants.TUIChat.FRAGMENT, fragment);
|
||||||
|
|
||||||
|
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageBottom.CLASSIC_EXTENSION_ID, bottomContentFrameLayout, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAvatar(TUIMessageBean msg) {
|
||||||
|
Drawable drawable = TUIConfigClassic.getDefaultAvatarImage();
|
||||||
|
if (drawable != null) {
|
||||||
|
setupAvatar(drawable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.isUseMsgReceiverAvatar() && mAdapter != null) {
|
||||||
|
String cachedFaceUrl = mAdapter.getUserFaceUrlCache().getCachedFaceUrl(msg.getSender());
|
||||||
|
if (cachedFaceUrl == null) {
|
||||||
|
List<String> idList = new ArrayList<>();
|
||||||
|
idList.add(msg.getSender());
|
||||||
|
V2TIMManager.getInstance().getUsersInfo(idList, new V2TIMValueCallback<List<V2TIMUserFullInfo>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<V2TIMUserFullInfo> v2TIMUserFullInfos) {
|
||||||
|
if (v2TIMUserFullInfos == null || v2TIMUserFullInfos.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V2TIMUserFullInfo userInfo = v2TIMUserFullInfos.get(0);
|
||||||
|
String faceUrl = userInfo.getFaceUrl();
|
||||||
|
if (TextUtils.isEmpty(userInfo.getFaceUrl())) {
|
||||||
|
faceUrl = "";
|
||||||
|
}
|
||||||
|
mAdapter.getUserFaceUrlCache().pushFaceUrl(userInfo.getUserID(), faceUrl);
|
||||||
|
mAdapter.onItemRefresh(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(int code, String desc) {
|
||||||
|
setupAvatar("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setupAvatar(cachedFaceUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setupAvatar(msg.getFaceUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAvatar(Object faceUrl) {
|
||||||
|
int avatarSize = TUIConfigClassic.getMessageListAvatarSize();
|
||||||
|
if (avatarSize == TUIConfigClassic.UNDEFINED) {
|
||||||
|
avatarSize = ScreenUtil.dip2px(41);
|
||||||
|
}
|
||||||
|
ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
|
||||||
|
params.width = avatarSize;
|
||||||
|
if (leftUserIcon.getVisibility() == View.INVISIBLE) {
|
||||||
|
params.height = 1;
|
||||||
|
} else {
|
||||||
|
params.height = avatarSize;
|
||||||
|
}
|
||||||
|
leftUserIcon.setLayoutParams(params);
|
||||||
|
|
||||||
|
params = rightUserIcon.getLayoutParams();
|
||||||
|
params.width = avatarSize;
|
||||||
|
if (rightUserIcon.getVisibility() == View.INVISIBLE) {
|
||||||
|
params.height = 1;
|
||||||
|
} else {
|
||||||
|
params.height = avatarSize;
|
||||||
|
}
|
||||||
|
rightUserIcon.setLayoutParams(params);
|
||||||
|
|
||||||
|
int radius = ScreenUtil.dip2px(4);
|
||||||
|
if (TUIConfigClassic.getMessageListAvatarRadius() != TUIConfigClassic.UNDEFINED) {
|
||||||
|
radius = TUIConfigClassic.getMessageListAvatarRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView renderedView;
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
renderedView = leftUserIcon;
|
||||||
|
} else {
|
||||||
|
renderedView = rightUserIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestBuilder<Drawable> errorRequestBuilder =
|
||||||
|
Glide.with(itemView.getContext())
|
||||||
|
.load(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
|
||||||
|
.placeholder(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
|
||||||
|
.transform(new RoundedCorners(radius));
|
||||||
|
|
||||||
|
Glide.with(itemView.getContext()).load(faceUrl).transform(new RoundedCorners(radius)).error(errorRequestBuilder).into(renderedView);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setMessageBubbleDefaultPadding() {
|
||||||
|
// after setting background, the padding will be reset
|
||||||
|
int paddingHorizontal = itemView.getResources().getDimensionPixelSize(R.dimen.chat_message_area_padding_left_right);
|
||||||
|
int paddingVertical = itemView.getResources().getDimensionPixelSize(R.dimen.chat_message_area_padding_top_bottom);
|
||||||
|
msgArea.setPaddingRelative(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setGravity(boolean isStart) {
|
||||||
|
int gravity = isStart ? Gravity.START : Gravity.END;
|
||||||
|
msgAreaAndReply.setGravity(gravity);
|
||||||
|
ViewGroup.LayoutParams layoutParams = msgContentFrame.getLayoutParams();
|
||||||
|
if (layoutParams instanceof FrameLayout.LayoutParams) {
|
||||||
|
((FrameLayout.LayoutParams) layoutParams).gravity = gravity;
|
||||||
|
} else if (layoutParams instanceof LinearLayout.LayoutParams) {
|
||||||
|
((LinearLayout.LayoutParams) layoutParams).gravity = gravity;
|
||||||
|
}
|
||||||
|
msgArea.setGravity(gravity);
|
||||||
|
msgContentFrame.setLayoutParams(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReplyContent(TUIMessageBean messageBean) {
|
||||||
|
MessageRepliesBean messageRepliesBean = messageBean.getMessageRepliesBean();
|
||||||
|
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
|
||||||
|
TextView replyNumText = msgReplyDetailLayout.findViewById(R.id.reply_num);
|
||||||
|
replyNumText.setText(String.format(Locale.US, replyNumText.getResources().getString(R.string.chat_reply_num), messageRepliesBean.getRepliesSize()));
|
||||||
|
msgReplyDetailLayout.setVisibility(View.VISIBLE);
|
||||||
|
msgReplyDetailLayout.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onReplyDetailClick(messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
msgReplyDetailLayout.setVisibility(View.GONE);
|
||||||
|
msgReplyDetailLayout.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
if (!isReplyDetailMode) {
|
||||||
|
messageDetailsTimeTv.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
messageDetailsTimeTv.setText(DateTimeUtil.getTimeFormatText(new Date(messageBean.getMessageTime() * 1000)));
|
||||||
|
messageDetailsTimeTv.setVisibility(View.VISIBLE);
|
||||||
|
msgReplyDetailLayout.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReactContent(TUIMessageBean messageBean) {
|
||||||
|
Map<String, Object> param = new HashMap<>();
|
||||||
|
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.MESSAGE, messageBean);
|
||||||
|
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE,
|
||||||
|
TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_CLASSIC);
|
||||||
|
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID, reactionArea, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showReadText(TUIMessageBean msg) {
|
||||||
|
if (hasRiskContent) {
|
||||||
|
isReadText.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (msg.isGroup()) {
|
||||||
|
isReadText.setVisibility(View.VISIBLE);
|
||||||
|
if (msg.isAllRead()) {
|
||||||
|
isReadText.setText(R.string.has_all_read);
|
||||||
|
} else if (msg.isUnread()) {
|
||||||
|
isReadText.setTextColor(
|
||||||
|
isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color)));
|
||||||
|
isReadText.setText(R.string.unread);
|
||||||
|
isReadText.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onReadStatusClick(v, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
long readCount = msg.getReadCount();
|
||||||
|
if (readCount > 0) {
|
||||||
|
isReadText.setText(String.format(Locale.US, isReadText.getResources().getString(R.string.someone_has_read), readCount));
|
||||||
|
isReadText.setTextColor(
|
||||||
|
isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color)));
|
||||||
|
isReadText.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onReadStatusClick(v, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isReadText.setVisibility(View.VISIBLE);
|
||||||
|
if (msg.isPeerRead()) {
|
||||||
|
isReadText.setText(R.string.has_read);
|
||||||
|
} else {
|
||||||
|
isReadText.setText(R.string.unread);
|
||||||
|
isReadText.setTextColor(
|
||||||
|
isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color)));
|
||||||
|
isReadText.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onReadStatusClick(v, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void layoutVariableViews(final T msg, final int position);
|
||||||
|
|
||||||
|
public void onRecycled() {
|
||||||
|
super.onRecycled();
|
||||||
|
if (selectionHelper != null) {
|
||||||
|
selectionHelper.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReadStatusClick(View view, TUIMessageBean messageBean) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageReadStatusClick(view, messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setSelectionHelper(TUIMessageBean msg, TextView textView, int position) {
|
||||||
|
if (selectionHelper == null) {
|
||||||
|
selectionHelper = new SelectionHelper();
|
||||||
|
}
|
||||||
|
selectionHelper.setTextView(textView);
|
||||||
|
if (isMultiSelectMode || isForwardMode) {
|
||||||
|
selectionHelper.setFrozen(true);
|
||||||
|
} else {
|
||||||
|
selectionHelper.setFrozen(false);
|
||||||
|
}
|
||||||
|
selectionHelper.setSelectListener(new SelectionHelper.OnSelectListener() {
|
||||||
|
@Override
|
||||||
|
public void onTextSelected(CharSequence content) {
|
||||||
|
String selectedText = "";
|
||||||
|
if (!TextUtils.isEmpty(content)) {
|
||||||
|
selectedText = content.toString();
|
||||||
|
msg.setSelectText(selectedText);
|
||||||
|
SelectionHelper.setSelected(selectionHelper);
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onTextSelected(msgArea, position, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismiss() {
|
||||||
|
msg.setSelectText(msg.getExtra());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClickUrl(String url) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShowPop() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismissPop() {}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedShowBottomLayout(boolean needShowBottomLayout) {
|
||||||
|
isNeedShowBottomLayout = needShowBottomLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowRead(boolean showRead) {
|
||||||
|
isShowRead = showRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,512 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.Region;
|
||||||
|
import android.text.Layout;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.face.CenterImageSpan;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TextUtil;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SelectionHelper {
|
||||||
|
private static final String TAG = "SelectionHelper";
|
||||||
|
|
||||||
|
private SelectionHandle startHandle;
|
||||||
|
private SelectionHandle endHandle;
|
||||||
|
private final SelectionInfo mSelectionInfo = new SelectionInfo();
|
||||||
|
private OnSelectListener mSelectListener;
|
||||||
|
|
||||||
|
private TextView textView;
|
||||||
|
private Spannable spannable;
|
||||||
|
|
||||||
|
private int mTextViewMarginStart = 0;
|
||||||
|
private GestureDetector gestureDetector;
|
||||||
|
private GestureDetector.SimpleOnGestureListener gestureListener;
|
||||||
|
private int selectionColor;
|
||||||
|
private int handleColor;
|
||||||
|
private int handleSize;
|
||||||
|
private BackgroundColorSpan bgSpan;
|
||||||
|
private boolean frozen = false;
|
||||||
|
|
||||||
|
private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;
|
||||||
|
private View.OnAttachStateChangeListener onAttachStateChangeListener;
|
||||||
|
private static WeakReference<SelectionHelper> selectedReference;
|
||||||
|
|
||||||
|
public static void setSelected(SelectionHelper selected) {
|
||||||
|
SelectionHelper oldSelected = getSelected();
|
||||||
|
if (oldSelected != null && selected != oldSelected) {
|
||||||
|
oldSelected.clearSelection();
|
||||||
|
}
|
||||||
|
selectedReference = new WeakReference<>(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetSelected() {
|
||||||
|
SelectionHelper selectionHelper = getSelected();
|
||||||
|
if (selectionHelper != null) {
|
||||||
|
selectionHelper.clearSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SelectionHelper getSelected() {
|
||||||
|
if (selectedReference != null) {
|
||||||
|
return selectedReference.get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrozen(boolean frozen) {
|
||||||
|
this.frozen = frozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnSelectListener {
|
||||||
|
void onTextSelected(CharSequence content);
|
||||||
|
|
||||||
|
void onDismiss();
|
||||||
|
|
||||||
|
void onClickUrl(String url);
|
||||||
|
|
||||||
|
void onShowPop();
|
||||||
|
|
||||||
|
void onDismissPop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SelectionHelper() {
|
||||||
|
selectionColor = 0x3f1470ff;
|
||||||
|
handleColor = 0xff1470ff;
|
||||||
|
handleSize = ScreenUtil.dip2px(16);
|
||||||
|
gestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public void onShowPress(MotionEvent e) {
|
||||||
|
if (frozen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initHandler();
|
||||||
|
ClickableSpan[] spans = TextUtil.findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
|
||||||
|
if (spans != null && spans.length > 0) {
|
||||||
|
ClickableSpan span = spans[0];
|
||||||
|
int spanStart = spannable.getSpanStart(span);
|
||||||
|
int spanEnd = spannable.getSpanEnd(span);
|
||||||
|
setSelection(spanStart, spanEnd);
|
||||||
|
} else {
|
||||||
|
selectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
|
if (frozen) {
|
||||||
|
return super.onSingleTapUp(e);
|
||||||
|
}
|
||||||
|
ClickableSpan[] spans = TextUtil.findSpansByLocation(textView, Math.round(e.getX()), Math.round(e.getY()));
|
||||||
|
if (spans != null && spans.length > 0) {
|
||||||
|
ClickableSpan span = spans[0];
|
||||||
|
span.onClick(textView);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gestureDetector = new GestureDetector(gestureListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectListener(OnSelectListener selectListener) {
|
||||||
|
mSelectListener = selectListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void destroy() {
|
||||||
|
if (textView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
textView.removeOnAttachStateChangeListener(onAttachStateChangeListener);
|
||||||
|
textView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
|
||||||
|
clearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectAll() {
|
||||||
|
initHandler();
|
||||||
|
if (textView.getText() instanceof Spannable) {
|
||||||
|
spannable = (Spannable) textView.getText();
|
||||||
|
}
|
||||||
|
if (spannable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSelection(0, textView.getText().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextView(TextView textView) {
|
||||||
|
this.textView = textView;
|
||||||
|
if (textView.getText() instanceof Spannable) {
|
||||||
|
this.spannable = (Spannable) textView.getText();
|
||||||
|
}
|
||||||
|
textView.removeOnAttachStateChangeListener(onAttachStateChangeListener);
|
||||||
|
onAttachStateChangeListener = new View.OnAttachStateChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onViewAttachedToWindow(View v) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewDetachedFromWindow(View v) {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
textView.addOnAttachStateChangeListener(onAttachStateChangeListener);
|
||||||
|
textView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
|
||||||
|
mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw() {
|
||||||
|
int[] location = new int[2];
|
||||||
|
textView.getLocationInWindow(location);
|
||||||
|
mTextViewMarginStart = location[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
textView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
|
||||||
|
textView.setMovementMethod(new LinkMovementMethodInterceptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHandler() {
|
||||||
|
if (startHandle == null) {
|
||||||
|
startHandle = new SelectionHandle(true);
|
||||||
|
}
|
||||||
|
if (endHandle == null) {
|
||||||
|
endHandle = new SelectionHandle(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSelectionHandle(SelectionHandle selectionHandle) {
|
||||||
|
Layout layout = textView.getLayout();
|
||||||
|
if (layout == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int offset = selectionHandle.isLeft ? mSelectionInfo.start : mSelectionInfo.end;
|
||||||
|
selectionHandle.show((int) layout.getPrimaryHorizontal(offset), layout.getLineBottom(layout.getLineForOffset(offset)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelection(int startPos, int endPos) {
|
||||||
|
initHandler();
|
||||||
|
|
||||||
|
if (startPos != -1) {
|
||||||
|
mSelectionInfo.start = startPos;
|
||||||
|
}
|
||||||
|
if (endPos != -1) {
|
||||||
|
mSelectionInfo.end = endPos;
|
||||||
|
}
|
||||||
|
if (mSelectionInfo.start > mSelectionInfo.end) {
|
||||||
|
int temp = mSelectionInfo.start;
|
||||||
|
mSelectionInfo.start = mSelectionInfo.end;
|
||||||
|
mSelectionInfo.end = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSelectionInfo.selectionContent = spannable.subSequence(mSelectionInfo.start, mSelectionInfo.end).toString();
|
||||||
|
setSelectionBg(spannable, mSelectionInfo.start, mSelectionInfo.end);
|
||||||
|
showSelectionHandle(startHandle);
|
||||||
|
showSelectionHandle(endHandle);
|
||||||
|
if (mSelectListener != null) {
|
||||||
|
mSelectListener.onTextSelected(mSelectionInfo.selectionContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelectionBg(Spannable text, int start, int end) {
|
||||||
|
if (bgSpan == null) {
|
||||||
|
bgSpan = new BackgroundColorSpan(selectionColor);
|
||||||
|
}
|
||||||
|
text.setSpan(bgSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
CenterImageSpan[] allImageSpans = text.getSpans(0, text.length(), CenterImageSpan.class);
|
||||||
|
CenterImageSpan[] imageSpans = text.getSpans(start, end, CenterImageSpan.class);
|
||||||
|
if (allImageSpans != null) {
|
||||||
|
for (CenterImageSpan imageSpan : allImageSpans) {
|
||||||
|
imageSpan.setBgColor(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (imageSpans != null) {
|
||||||
|
for (CenterImageSpan imageSpan : imageSpans) {
|
||||||
|
imageSpan.setBgColor(selectionColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearSelection() {
|
||||||
|
mSelectionInfo.selectionContent = null;
|
||||||
|
clearSelectionBg();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearSelectionBg() {
|
||||||
|
if (spannable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bgSpan != null) {
|
||||||
|
spannable.removeSpan(bgSpan);
|
||||||
|
}
|
||||||
|
CenterImageSpan[] imageSpans = spannable.getSpans(0, spannable.length(), CenterImageSpan.class);
|
||||||
|
if (imageSpans != null) {
|
||||||
|
for (CenterImageSpan imageSpan : imageSpans) {
|
||||||
|
imageSpan.setBgColor(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startHandle != null) {
|
||||||
|
startHandle.dismiss();
|
||||||
|
}
|
||||||
|
if (endHandle != null) {
|
||||||
|
endHandle.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SelectionHandle extends View {
|
||||||
|
private PopupWindow mPopupWindow;
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
private int mCircleRadius = handleSize / 2;
|
||||||
|
private int mWidth = handleSize;
|
||||||
|
private int mHeight = handleSize;
|
||||||
|
private int mPadding = 32;
|
||||||
|
private boolean isLeft;
|
||||||
|
|
||||||
|
public SelectionHandle(boolean isLeft) {
|
||||||
|
super(textView.getContext());
|
||||||
|
this.isLeft = isLeft;
|
||||||
|
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
mPaint.setColor(handleColor);
|
||||||
|
|
||||||
|
mPopupWindow = new PopupWindow(this);
|
||||||
|
mPopupWindow.setClippingEnabled(false);
|
||||||
|
mPopupWindow.setWidth(mWidth + mPadding * 2);
|
||||||
|
mPopupWindow.setHeight(mHeight + mPadding / 2);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
canvas.drawCircle(mCircleRadius + mPadding, mCircleRadius, mCircleRadius, mPaint);
|
||||||
|
if (isLeft) {
|
||||||
|
canvas.drawRect(mCircleRadius + mPadding, 0, mCircleRadius * 2 + mPadding, mCircleRadius, mPaint);
|
||||||
|
} else {
|
||||||
|
canvas.drawRect(mPadding, 0, mCircleRadius + mPadding, mCircleRadius, mPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int mAdjustX;
|
||||||
|
private int mAdjustY;
|
||||||
|
|
||||||
|
private int mBeforeDragStart;
|
||||||
|
private int mBeforeDragEnd;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
mBeforeDragStart = mSelectionInfo.start;
|
||||||
|
mBeforeDragEnd = mSelectionInfo.end;
|
||||||
|
mAdjustX = (int) event.getX();
|
||||||
|
mAdjustY = (int) event.getY();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
if (null != mSelectListener) {
|
||||||
|
mSelectListener.onDismissPop();
|
||||||
|
}
|
||||||
|
int rawX = (int) event.getRawX();
|
||||||
|
int rawY = (int) event.getRawY();
|
||||||
|
|
||||||
|
update(rawX + mAdjustX - mWidth - mTextViewMarginStart, rawY + mAdjustY - mHeight - (int) textView.getTextSize());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeDirection() {
|
||||||
|
isLeft = !isLeft;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dismiss() {
|
||||||
|
Log.e(TAG, "handler dismiss");
|
||||||
|
mPopupWindow.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] mTempCoors = new int[2];
|
||||||
|
|
||||||
|
public void update(int x, int y) {
|
||||||
|
textView.getLocationInWindow(mTempCoors);
|
||||||
|
int oldOffset;
|
||||||
|
if (isLeft) {
|
||||||
|
oldOffset = mSelectionInfo.start;
|
||||||
|
} else {
|
||||||
|
oldOffset = mSelectionInfo.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
y -= mTempCoors[1];
|
||||||
|
|
||||||
|
int offset = getHysteresisOffset(textView, x, y, oldOffset);
|
||||||
|
|
||||||
|
if (offset != oldOffset) {
|
||||||
|
mSelectionInfo.selectionContent = null;
|
||||||
|
if (isLeft) {
|
||||||
|
if (offset > mBeforeDragEnd) {
|
||||||
|
SelectionHandle handle = getSelectionHandle(false);
|
||||||
|
changeDirection();
|
||||||
|
handle.changeDirection();
|
||||||
|
mBeforeDragStart = mBeforeDragEnd;
|
||||||
|
setSelection(mBeforeDragEnd, offset);
|
||||||
|
handle.updateSelectionHandle();
|
||||||
|
} else {
|
||||||
|
setSelection(offset, -1);
|
||||||
|
}
|
||||||
|
updateSelectionHandle();
|
||||||
|
} else {
|
||||||
|
if (offset < mBeforeDragStart) {
|
||||||
|
SelectionHandle handle = getSelectionHandle(true);
|
||||||
|
handle.changeDirection();
|
||||||
|
changeDirection();
|
||||||
|
mBeforeDragEnd = mBeforeDragStart;
|
||||||
|
setSelection(offset, mBeforeDragStart);
|
||||||
|
handle.updateSelectionHandle();
|
||||||
|
} else {
|
||||||
|
setSelection(mBeforeDragStart, offset);
|
||||||
|
}
|
||||||
|
updateSelectionHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSelectionHandle() {
|
||||||
|
textView.getLocationInWindow(mTempCoors);
|
||||||
|
Layout layout = textView.getLayout();
|
||||||
|
if (isLeft) {
|
||||||
|
mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.start) - mWidth + getExtraX(),
|
||||||
|
layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.start)) + getExtraY(), -1, -1);
|
||||||
|
} else {
|
||||||
|
mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.end) + getExtraX(),
|
||||||
|
layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.end)) + getExtraY(), -1, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(int x, int y) {
|
||||||
|
textView.getLocationInWindow(mTempCoors);
|
||||||
|
int offset = isLeft ? mWidth : 0;
|
||||||
|
mPopupWindow.showAtLocation(textView, Gravity.NO_GRAVITY, x - offset + getExtraX(), y + getExtraY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExtraX() {
|
||||||
|
return mTempCoors[0] - mPadding + textView.getPaddingLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExtraY() {
|
||||||
|
return mTempCoors[1] + textView.getPaddingTop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SelectionHandle getSelectionHandle(boolean isLeft) {
|
||||||
|
if (startHandle.isLeft == isLeft) {
|
||||||
|
return startHandle;
|
||||||
|
} else {
|
||||||
|
return endHandle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SelectionInfo {
|
||||||
|
public int start;
|
||||||
|
public int end;
|
||||||
|
public String selectionContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LinkMovementMethodInterceptor extends LinkMovementMethod {
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
||||||
|
return gestureDetector.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getHysteresisOffset(TextView textView, int x, int y, int previousOffset) {
|
||||||
|
final Layout layout = textView.getLayout();
|
||||||
|
if (layout == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int line = layout.getLineForVertical(y);
|
||||||
|
|
||||||
|
// The "HACK BLOCK"S in this function is required because of how Android Layout for
|
||||||
|
// TextView works - if 'offset' equals to the last character of a line, then
|
||||||
|
//
|
||||||
|
// * getLineForOffset(offset) will result the NEXT line
|
||||||
|
// * getPrimaryHorizontal(offset) will return 0 because the next insertion point is on the next line
|
||||||
|
// * getOffsetForHorizontal(line, x) will not return the last offset of a line no matter where x is
|
||||||
|
// These are highly undesired and is worked around with the HACK BLOCK
|
||||||
|
//
|
||||||
|
// @see Moon+ Reader/Color Note - see how it can't select the last character of a line unless you move
|
||||||
|
// the cursor to the beginning of the next line.
|
||||||
|
//
|
||||||
|
////////////////////HACK BLOCK////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
if (isEndOfLineOffset(layout, previousOffset)) {
|
||||||
|
// we have to minus one from the offset so that the code below to find
|
||||||
|
// the previous line can work correctly.
|
||||||
|
int left = (int) layout.getPrimaryHorizontal(previousOffset - 1);
|
||||||
|
int right = (int) layout.getLineRight(line);
|
||||||
|
int threshold = (right - left) / 2; // half the width of the last character
|
||||||
|
if (x > right - threshold) {
|
||||||
|
previousOffset -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
final int previousLine = layout.getLineForOffset(previousOffset);
|
||||||
|
final int previousLineTop = layout.getLineTop(previousLine);
|
||||||
|
final int previousLineBottom = layout.getLineBottom(previousLine);
|
||||||
|
final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
|
||||||
|
|
||||||
|
// If new line is just before or after previous line and y position is less than
|
||||||
|
// hysteresisThreshold away from previous line, keep cursor on previous line.
|
||||||
|
if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold))
|
||||||
|
|| ((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) {
|
||||||
|
line = previousLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = layout.getOffsetForHorizontal(line, x);
|
||||||
|
|
||||||
|
// This allow the user to select the last character of a line without moving the
|
||||||
|
// cursor to the next line. (As Layout.getOffsetForHorizontal does not return the
|
||||||
|
// offset of the last character of the specified line)
|
||||||
|
//
|
||||||
|
// But this function will probably get called again immediately, must decrement the offset
|
||||||
|
// by 1 to compensate for the change made below. (see previous HACK BLOCK)
|
||||||
|
/////////////////////HACK BLOCK///////////////////////////////////////////////////
|
||||||
|
if (offset < textView.getText().length() - 1) {
|
||||||
|
if (isEndOfLineOffset(layout, offset + 1)) {
|
||||||
|
int left = (int) layout.getPrimaryHorizontal(offset);
|
||||||
|
int right = (int) layout.getLineRight(line);
|
||||||
|
int threshold = (right - left) / 2; // half the width of the last character
|
||||||
|
if (x > right - threshold) {
|
||||||
|
offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isEndOfLineOffset(Layout layout, int offset) {
|
||||||
|
return offset > 0 && layout.getLineForOffset(offset) == layout.getLineForOffset(offset - 1) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIReplyQuoteBean;
|
||||||
|
|
||||||
|
public abstract class TUIReplyQuoteView<T extends TUIReplyQuoteBean<?>> extends FrameLayout {
|
||||||
|
|
||||||
|
public abstract int getLayoutResourceId();
|
||||||
|
|
||||||
|
public TUIReplyQuoteView(Context context) {
|
||||||
|
super(context);
|
||||||
|
int resId = getLayoutResourceId();
|
||||||
|
if (resId != 0) {
|
||||||
|
LayoutInflater.from(context).inflate(resId, this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void onDrawReplyQuote(T quoteBean);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Whether the original message sender is himself, used for different UI displays
|
||||||
|
*
|
||||||
|
* @param isSelf
|
||||||
|
*/
|
||||||
|
public void setSelf(boolean isSelf) {}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BottomSelectSheet {
|
||||||
|
private List<String> selectList = new ArrayList<>();
|
||||||
|
private Dialog dialog;
|
||||||
|
private ArrayAdapter<String> listAdapter;
|
||||||
|
private BottomSelectSheetOnClickListener onClickListener;
|
||||||
|
|
||||||
|
public BottomSelectSheet(Context context) {
|
||||||
|
View view = View.inflate(context, R.layout.common_bottom_select_sheet, null);
|
||||||
|
dialog = new Dialog(context, R.style.BottomSelectSheet);
|
||||||
|
dialog.setContentView(view);
|
||||||
|
dialog.setCancelable(true);
|
||||||
|
dialog.setCanceledOnTouchOutside(true);
|
||||||
|
Window window = dialog.getWindow();
|
||||||
|
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
WindowManager m = window.getWindowManager();
|
||||||
|
Display d = m.getDefaultDisplay();
|
||||||
|
WindowManager.LayoutParams p = window.getAttributes();
|
||||||
|
p.width = d.getWidth();
|
||||||
|
p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
|
window.setAttributes(p);
|
||||||
|
window.setGravity(Gravity.BOTTOM);
|
||||||
|
window.setWindowAnimations(R.style.BottomSelectSheet_Anim);
|
||||||
|
|
||||||
|
final ListView listView = view.findViewById(R.id.item_list);
|
||||||
|
listAdapter = new ArrayAdapter<>(context, R.layout.common_bottom_sheet_item, R.id.sheet_item, selectList);
|
||||||
|
listView.setAdapter(listAdapter);
|
||||||
|
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
dismiss();
|
||||||
|
if (onClickListener != null) {
|
||||||
|
onClickListener.onSheetClick(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TextView cancelButton = view.findViewById(R.id.cancel_button);
|
||||||
|
cancelButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dismiss() {
|
||||||
|
if (dialog != null && dialog.isShowing()) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
if (dialog != null && !dialog.isShowing()) {
|
||||||
|
listAdapter.notifyDataSetChanged();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectList(List<String> selectList) {
|
||||||
|
this.selectList.clear();
|
||||||
|
this.selectList.addAll(selectList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnClickListener(BottomSelectSheetOnClickListener onClickListener) {
|
||||||
|
this.onClickListener = onClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BottomSelectSheetOnClickListener {
|
||||||
|
void onSheetClick(int index);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/questions/30458640/recyclerview-java-lang-indexoutofboundsexception-inconsistency-detected-inval
|
||||||
|
*/
|
||||||
|
public class CustomLinearLayoutManager extends LinearLayoutManager {
|
||||||
|
public CustomLinearLayoutManager(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
|
||||||
|
super(context, orientation, reverseLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
|
||||||
|
try {
|
||||||
|
super.onLayoutChildren(recycler, state);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.w("CustomLinearLayoutManager", "" + e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class IndicatorView extends LinearLayout {
|
||||||
|
private Context mContext;
|
||||||
|
private ArrayList<ImageView> mImageViews;
|
||||||
|
private Bitmap bmpSelect;
|
||||||
|
private Bitmap bmpNormal;
|
||||||
|
private int mHeight = 6;
|
||||||
|
private int mMaxHeight;
|
||||||
|
private AnimatorSet mPlayByInAnimatorSet;
|
||||||
|
private AnimatorSet mPlayByOutAnimatorSet;
|
||||||
|
|
||||||
|
public IndicatorView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
this.mContext = context;
|
||||||
|
this.setOrientation(HORIZONTAL);
|
||||||
|
mMaxHeight = ScreenUtil.dip2px(mHeight);
|
||||||
|
bmpSelect = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_select);
|
||||||
|
bmpNormal = BitmapFactory.decodeResource(getResources(), R.drawable.indicator_point_nomal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndicatorView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init(int count) {
|
||||||
|
mImageViews = new ArrayList<>();
|
||||||
|
this.removeAllViews();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
LayoutParams params = new LinearLayout.LayoutParams(mMaxHeight, mMaxHeight);
|
||||||
|
ImageView imageView = new ImageView(mContext);
|
||||||
|
params.setMarginStart(12);
|
||||||
|
params.setMarginEnd(12);
|
||||||
|
if (i == 0) {
|
||||||
|
imageView.setImageBitmap(bmpSelect);
|
||||||
|
} else {
|
||||||
|
imageView.setImageBitmap(bmpNormal);
|
||||||
|
}
|
||||||
|
this.addView(imageView, params);
|
||||||
|
mImageViews.add(imageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void playBy(int startPosition, int nextPosition) {
|
||||||
|
final boolean isShowInAnimOnly = false;
|
||||||
|
if (startPosition < 0 || nextPosition < 0 || nextPosition == startPosition) {
|
||||||
|
startPosition = nextPosition = 0;
|
||||||
|
}
|
||||||
|
if (mImageViews == null || mImageViews.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (startPosition >= mImageViews.size() || nextPosition >= mImageViews.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImageView imageViewStrat = mImageViews.get(startPosition);
|
||||||
|
final ImageView imageViewNext = mImageViews.get(nextPosition);
|
||||||
|
|
||||||
|
ObjectAnimator anim1 = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f, 0.25f);
|
||||||
|
ObjectAnimator anim2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f, 0.25f);
|
||||||
|
|
||||||
|
if (mPlayByOutAnimatorSet != null && mPlayByOutAnimatorSet.isRunning()) {
|
||||||
|
mPlayByOutAnimatorSet.cancel();
|
||||||
|
mPlayByOutAnimatorSet = null;
|
||||||
|
}
|
||||||
|
mPlayByOutAnimatorSet = new AnimatorSet();
|
||||||
|
mPlayByOutAnimatorSet.play(anim1).with(anim2);
|
||||||
|
mPlayByOutAnimatorSet.setDuration(100);
|
||||||
|
|
||||||
|
ObjectAnimator animIn1 = ObjectAnimator.ofFloat(imageViewNext, "scaleX", 0.25f, 1.0f);
|
||||||
|
ObjectAnimator animIn2 = ObjectAnimator.ofFloat(imageViewNext, "scaleY", 0.25f, 1.0f);
|
||||||
|
|
||||||
|
if (mPlayByInAnimatorSet != null && mPlayByInAnimatorSet.isRunning()) {
|
||||||
|
mPlayByInAnimatorSet.cancel();
|
||||||
|
mPlayByInAnimatorSet = null;
|
||||||
|
}
|
||||||
|
mPlayByInAnimatorSet = new AnimatorSet();
|
||||||
|
mPlayByInAnimatorSet.play(animIn1).with(animIn2);
|
||||||
|
mPlayByInAnimatorSet.setDuration(100);
|
||||||
|
|
||||||
|
if (isShowInAnimOnly) {
|
||||||
|
mPlayByInAnimatorSet.start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
anim1.addListener(new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
imageViewStrat.setImageBitmap(bmpNormal);
|
||||||
|
ObjectAnimator animFil1l = ObjectAnimator.ofFloat(imageViewStrat, "scaleX", 1.0f);
|
||||||
|
ObjectAnimator animFill2 = ObjectAnimator.ofFloat(imageViewStrat, "scaleY", 1.0f);
|
||||||
|
AnimatorSet mFillAnimatorSet = new AnimatorSet();
|
||||||
|
mFillAnimatorSet.play(animFil1l).with(animFill2);
|
||||||
|
mFillAnimatorSet.start();
|
||||||
|
imageViewNext.setImageBitmap(bmpSelect);
|
||||||
|
mPlayByInAnimatorSet.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animation) {}
|
||||||
|
});
|
||||||
|
mPlayByOutAnimatorSet.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom LineControllerView
|
||||||
|
*/
|
||||||
|
public class LineControllerView extends RelativeLayout {
|
||||||
|
private String mName;
|
||||||
|
private boolean mIsBottom;
|
||||||
|
private boolean mIsTop;
|
||||||
|
private String mContent;
|
||||||
|
private boolean mIsJump;
|
||||||
|
private boolean mIsSwitch;
|
||||||
|
|
||||||
|
protected TextView mNameText;
|
||||||
|
protected TextView mContentText;
|
||||||
|
private ImageView mNavArrowView;
|
||||||
|
protected Switch mSwitchView;
|
||||||
|
protected View bottomLine;
|
||||||
|
private View mMask;
|
||||||
|
private View container;
|
||||||
|
|
||||||
|
public LineControllerView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.timcommon_line_controller_view, this);
|
||||||
|
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineControllerView, 0, 0);
|
||||||
|
try {
|
||||||
|
mName = ta.getString(R.styleable.LineControllerView_name);
|
||||||
|
mContent = ta.getString(R.styleable.LineControllerView_subject);
|
||||||
|
mIsBottom = ta.getBoolean(R.styleable.LineControllerView_isBottom, false);
|
||||||
|
mIsTop = ta.getBoolean(R.styleable.LineControllerView_isTop, false);
|
||||||
|
mIsJump = ta.getBoolean(R.styleable.LineControllerView_canNav, false);
|
||||||
|
mIsSwitch = ta.getBoolean(R.styleable.LineControllerView_isSwitch, false);
|
||||||
|
setUpView();
|
||||||
|
} finally {
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpView() {
|
||||||
|
mNameText = findViewById(R.id.name);
|
||||||
|
mNameText.setText(mName);
|
||||||
|
mContentText = findViewById(R.id.content);
|
||||||
|
mContentText.setText(mContent);
|
||||||
|
bottomLine = findViewById(R.id.bottom_line);
|
||||||
|
View topLine = findViewById(R.id.top_line);
|
||||||
|
bottomLine.setVisibility(mIsBottom ? VISIBLE : GONE);
|
||||||
|
topLine.setVisibility(mIsTop ? VISIBLE : GONE);
|
||||||
|
mNavArrowView = findViewById(R.id.rightArrow);
|
||||||
|
Drawable arrowDrawable = mNavArrowView.getDrawable();
|
||||||
|
if (arrowDrawable != null) {
|
||||||
|
DrawableCompat.setAutoMirrored(arrowDrawable, true);
|
||||||
|
}
|
||||||
|
mNavArrowView.setVisibility(mIsJump ? VISIBLE : GONE);
|
||||||
|
ViewGroup contentLayout = findViewById(R.id.content_view);
|
||||||
|
contentLayout.setVisibility(mIsSwitch ? GONE : VISIBLE);
|
||||||
|
mSwitchView = findViewById(R.id.btnSwitch);
|
||||||
|
mSwitchView.setVisibility(mIsSwitch ? VISIBLE : GONE);
|
||||||
|
mMask = findViewById(R.id.disable_mask);
|
||||||
|
container = findViewById(R.id.view_container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackground(Drawable drawable) {
|
||||||
|
super.setBackground(drawable);
|
||||||
|
container.setBackground(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return mContentText.getText().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.mContent = content;
|
||||||
|
mContentText.setText(content);
|
||||||
|
mContentText.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
mNameText.setText(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleLine(boolean singleLine) {
|
||||||
|
mContentText.setSingleLine(singleLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to jump
|
||||||
|
*
|
||||||
|
* @param canNav
|
||||||
|
*/
|
||||||
|
public void setCanNav(boolean canNav) {
|
||||||
|
this.mIsJump = canNav;
|
||||||
|
mNavArrowView.setVisibility(canNav ? VISIBLE : GONE);
|
||||||
|
if (canNav) {
|
||||||
|
mContentText.setTextIsSelectable(false);
|
||||||
|
} else {
|
||||||
|
mContentText.setTextIsSelectable(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChecked() {
|
||||||
|
return mSwitchView.isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChecked(boolean on) {
|
||||||
|
mSwitchView.setChecked(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckListener(CompoundButton.OnCheckedChangeListener listener) {
|
||||||
|
mSwitchView.setOnCheckedChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMask(boolean enableMask) {
|
||||||
|
if (enableMask) {
|
||||||
|
mNameText.setEnabled(false);
|
||||||
|
mContentText.setEnabled(false);
|
||||||
|
mNameText.setTextColor(getResources().getColor(R.color.text_color_gray));
|
||||||
|
mContentText.setTextColor(getResources().getColor(R.color.text_color_gray));
|
||||||
|
mSwitchView.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
mNameText.setEnabled(true);
|
||||||
|
mContentText.setEnabled(true);
|
||||||
|
mNameText.setTextColor(getResources().getColor(R.color.core_line_controller_title_color));
|
||||||
|
mContentText.setTextColor(getResources().getColor(R.color.core_line_controller_content_color));
|
||||||
|
mSwitchView.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class MaxWidthFrameLayout extends FrameLayout {
|
||||||
|
int maxWidthPx;
|
||||||
|
|
||||||
|
public MaxWidthFrameLayout(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attributeSet) {
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.max_width_style);
|
||||||
|
maxWidthPx = array.getDimensionPixelSize(R.styleable.max_width_style_maxWidth, 0);
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
if (maxWidthPx > 0 && maxWidthPx < measuredWidth) {
|
||||||
|
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidthPx, MeasureSpec.AT_MOST);
|
||||||
|
}
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class MaxWidthLinearLayout extends LinearLayout {
|
||||||
|
int maxWidthPx;
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxWidthLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attributeSet) {
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.max_width_style);
|
||||||
|
maxWidthPx = array.getDimensionPixelSize(R.styleable.max_width_style_maxWidth, 0);
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||||
|
if (maxWidthPx > 0 && maxWidthPx < measuredWidth) {
|
||||||
|
widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidthPx, MeasureSpec.AT_MOST);
|
||||||
|
}
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.SwitchCompat;
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom LineControllerView
|
||||||
|
*/
|
||||||
|
public class MinimalistLineControllerView extends RelativeLayout {
|
||||||
|
private String mName;
|
||||||
|
private boolean mIsBottom;
|
||||||
|
private boolean mIsTop;
|
||||||
|
private String mContent;
|
||||||
|
private boolean mIsJump;
|
||||||
|
private boolean mIsSwitch;
|
||||||
|
|
||||||
|
protected TextView mNameText;
|
||||||
|
protected TextView mContentText;
|
||||||
|
private ImageView mNavArrowView;
|
||||||
|
protected SwitchCompat mSwitchView;
|
||||||
|
protected View bottomLine;
|
||||||
|
private View mMask;
|
||||||
|
private View container;
|
||||||
|
|
||||||
|
public MinimalistLineControllerView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.minimalist_line_controller_view, this);
|
||||||
|
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineControllerView, 0, 0);
|
||||||
|
try {
|
||||||
|
mName = ta.getString(R.styleable.LineControllerView_name);
|
||||||
|
mContent = ta.getString(R.styleable.LineControllerView_subject);
|
||||||
|
mIsBottom = ta.getBoolean(R.styleable.LineControllerView_isBottom, false);
|
||||||
|
mIsTop = ta.getBoolean(R.styleable.LineControllerView_isTop, false);
|
||||||
|
mIsJump = ta.getBoolean(R.styleable.LineControllerView_canNav, false);
|
||||||
|
mIsSwitch = ta.getBoolean(R.styleable.LineControllerView_isSwitch, false);
|
||||||
|
setUpView();
|
||||||
|
} finally {
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUpView() {
|
||||||
|
mNameText = findViewById(R.id.name);
|
||||||
|
mNameText.setText(mName);
|
||||||
|
mContentText = findViewById(R.id.content);
|
||||||
|
mContentText.setText(mContent);
|
||||||
|
mContentText.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
MinimalistLineControllerView.this.performClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bottomLine = findViewById(R.id.bottom_line);
|
||||||
|
View topLine = findViewById(R.id.top_line);
|
||||||
|
bottomLine.setVisibility(mIsBottom ? VISIBLE : GONE);
|
||||||
|
topLine.setVisibility(mIsTop ? VISIBLE : GONE);
|
||||||
|
mNavArrowView = findViewById(R.id.rightArrow);
|
||||||
|
Drawable arrowDrawable = mNavArrowView.getDrawable();
|
||||||
|
if (arrowDrawable != null) {
|
||||||
|
DrawableCompat.setAutoMirrored(arrowDrawable, true);
|
||||||
|
}
|
||||||
|
mNavArrowView.setVisibility(mIsJump ? VISIBLE : GONE);
|
||||||
|
ViewGroup contentLayout = findViewById(R.id.content_view);
|
||||||
|
contentLayout.setVisibility(mIsSwitch ? GONE : VISIBLE);
|
||||||
|
mSwitchView = findViewById(R.id.btnSwitch);
|
||||||
|
mSwitchView.setVisibility(mIsSwitch ? VISIBLE : GONE);
|
||||||
|
mMask = findViewById(R.id.disable_mask);
|
||||||
|
container = findViewById(R.id.view_container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackground(Drawable drawable) {
|
||||||
|
super.setBackground(drawable);
|
||||||
|
if (container != null) {
|
||||||
|
container.setBackground(drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundColor(int color) {
|
||||||
|
super.setBackgroundColor(color);
|
||||||
|
if (container != null) {
|
||||||
|
container.setBackgroundColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return mContentText.getText().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.mContent = content;
|
||||||
|
mContentText.setText(content);
|
||||||
|
mContentText.requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleLine(boolean singleLine) {
|
||||||
|
mContentText.setSingleLine(singleLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to jump
|
||||||
|
*
|
||||||
|
* @param canNav
|
||||||
|
*/
|
||||||
|
public void setCanNav(boolean canNav) {
|
||||||
|
this.mIsJump = canNav;
|
||||||
|
mNavArrowView.setVisibility(canNav ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isChecked() {
|
||||||
|
return mSwitchView.isChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChecked(boolean on) {
|
||||||
|
mSwitchView.setChecked(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCheckListener(CompoundButton.OnCheckedChangeListener listener) {
|
||||||
|
mSwitchView.setOnCheckedChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMask(boolean enableMask) {
|
||||||
|
if (enableMask) {
|
||||||
|
mNameText.setEnabled(false);
|
||||||
|
mNameText.setTextColor(getResources().getColor(R.color.text_color_gray));
|
||||||
|
mContentText.setEnabled(false);
|
||||||
|
mContentText.setTextColor(getResources().getColor(R.color.text_color_gray));
|
||||||
|
mSwitchView.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
mNameText.setEnabled(true);
|
||||||
|
mNameText.setTextColor(getResources().getColor(R.color.core_line_controller_title_color));
|
||||||
|
mContentText.setEnabled(true);
|
||||||
|
mContentText.setTextColor(getResources().getColor(R.color.core_line_controller_content_color));
|
||||||
|
mSwitchView.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNameColor(int color) {
|
||||||
|
mNameText.setTextColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.mName = name;
|
||||||
|
mNameText.setText(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class MinimalistTitleBar extends TitleBarLayout {
|
||||||
|
public MinimalistTitleBar(Context context) {
|
||||||
|
super(context);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinimalistTitleBar(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinimalistTitleBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView(Context context) {
|
||||||
|
setLeftReturnListener(context);
|
||||||
|
setBackgroundColor(Color.WHITE);
|
||||||
|
getLeftIcon().setBackgroundResource(R.drawable.core_minimalist_back_icon);
|
||||||
|
Drawable leftIconDrawable = getLeftIcon().getBackground();
|
||||||
|
if (leftIconDrawable != null) {
|
||||||
|
leftIconDrawable.setAutoMirrored(true);
|
||||||
|
}
|
||||||
|
getLeftTitle().setTextColor(0xFF0365F9);
|
||||||
|
getRightTitle().setTextColor(0xFF0365F9);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.InputFilter;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.LinearInterpolator;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.tencent.qcloud.tuicore.util.ToastUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.SoftKeyBoardUtil;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class PopupInputCard {
|
||||||
|
private PopupWindow popupWindow;
|
||||||
|
|
||||||
|
private TextView titleTv;
|
||||||
|
private EditText editText;
|
||||||
|
private TextView descriptionTv;
|
||||||
|
private Button positiveBtn;
|
||||||
|
private View closeBtn;
|
||||||
|
private OnClickListener positiveOnClickListener;
|
||||||
|
private OnTextExceedListener textExceedListener;
|
||||||
|
|
||||||
|
private int minLimit = 0;
|
||||||
|
private int maxLimit = Integer.MAX_VALUE;
|
||||||
|
private String rule;
|
||||||
|
private String notMachRuleTip;
|
||||||
|
private ByteLengthFilter lengthFilter = new ByteLengthFilter();
|
||||||
|
|
||||||
|
public PopupInputCard(Activity activity) {
|
||||||
|
View popupView = LayoutInflater.from(activity).inflate(R.layout.timcommon_layout_popup_card, null);
|
||||||
|
titleTv = popupView.findViewById(R.id.popup_card_title);
|
||||||
|
editText = popupView.findViewById(R.id.popup_card_edit);
|
||||||
|
descriptionTv = popupView.findViewById(R.id.popup_card_description);
|
||||||
|
positiveBtn = popupView.findViewById(R.id.popup_card_positive_btn);
|
||||||
|
closeBtn = popupView.findViewById(R.id.close_btn);
|
||||||
|
|
||||||
|
popupWindow = new PopupWindow(popupView, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, true) {
|
||||||
|
@Override
|
||||||
|
public void showAtLocation(View anchor, int gravity, int x, int y) {
|
||||||
|
if (activity != null && !activity.isFinishing()) {
|
||||||
|
Window dialogWindow = activity.getWindow();
|
||||||
|
startAnimation(dialogWindow, true);
|
||||||
|
}
|
||||||
|
editText.requestFocus();
|
||||||
|
if (activity.getWindow() != null) {
|
||||||
|
SoftKeyBoardUtil.showKeyBoard(activity.getWindow());
|
||||||
|
}
|
||||||
|
super.showAtLocation(anchor, gravity, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dismiss() {
|
||||||
|
if (activity != null && !activity.isFinishing()) {
|
||||||
|
Window dialogWindow = activity.getWindow();
|
||||||
|
startAnimation(dialogWindow, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
popupWindow.setBackgroundDrawable(new ColorDrawable());
|
||||||
|
popupWindow.setTouchable(true);
|
||||||
|
popupWindow.setOutsideTouchable(false);
|
||||||
|
popupWindow.setAnimationStyle(R.style.PopupInputCardAnim);
|
||||||
|
popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
|
||||||
|
popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||||
|
popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss() {
|
||||||
|
if (activity.getWindow() != null) {
|
||||||
|
SoftKeyBoardUtil.hideKeyBoard(activity.getWindow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
positiveBtn.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
String result = editText.getText().toString();
|
||||||
|
|
||||||
|
if (result.length() < minLimit || result.length() > maxLimit) {
|
||||||
|
ToastUtil.toastShortMessage(notMachRuleTip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(rule) && !Pattern.matches(rule, result)) {
|
||||||
|
ToastUtil.toastShortMessage(notMachRuleTip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positiveOnClickListener != null) {
|
||||||
|
positiveOnClickListener.onClick(editText.getText().toString());
|
||||||
|
}
|
||||||
|
popupWindow.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
closeBtn.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
popupWindow.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
editText.setFilters(new InputFilter[] {lengthFilter});
|
||||||
|
editText.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
if (!TextUtils.isEmpty(rule)) {
|
||||||
|
if (!Pattern.matches(rule, s.toString())) {
|
||||||
|
positiveBtn.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
positiveBtn.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startAnimation(Window window, boolean isShow) {
|
||||||
|
ValueAnimator animator;
|
||||||
|
if (isShow) {
|
||||||
|
animator = ValueAnimator.ofFloat(1.0f, 0.5f);
|
||||||
|
} else {
|
||||||
|
animator = ValueAnimator.ofFloat(0.5f, 1.0f);
|
||||||
|
}
|
||||||
|
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
WindowManager.LayoutParams lp = window.getAttributes();
|
||||||
|
lp.alpha = (float) animation.getAnimatedValue();
|
||||||
|
window.setAttributes(lp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
LinearInterpolator interpolator = new LinearInterpolator();
|
||||||
|
animator.setDuration(200);
|
||||||
|
animator.setInterpolator(interpolator);
|
||||||
|
animator.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show(View rootView, int gravity) {
|
||||||
|
if (popupWindow != null) {
|
||||||
|
popupWindow.showAtLocation(rootView, gravity, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String title) {
|
||||||
|
titleTv.setText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
if (!TextUtils.isEmpty(description)) {
|
||||||
|
descriptionTv.setVisibility(View.VISIBLE);
|
||||||
|
descriptionTv.setText(description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
editText.setText(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPositive(OnClickListener clickListener) {
|
||||||
|
positiveOnClickListener = clickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextExceedListener(OnTextExceedListener textExceedListener) {
|
||||||
|
this.textExceedListener = textExceedListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSingleLine(boolean isSingleLine) {
|
||||||
|
editText.setSingleLine(isSingleLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxLimit(int maxLimit) {
|
||||||
|
this.maxLimit = maxLimit;
|
||||||
|
lengthFilter.setLength(maxLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinLimit(int minLimit) {
|
||||||
|
this.minLimit = minLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRule(String rule) {
|
||||||
|
if (TextUtils.isEmpty(rule)) {
|
||||||
|
this.rule = "";
|
||||||
|
} else {
|
||||||
|
this.rule = rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotMachRuleTip(String notMachRuleTip) {
|
||||||
|
this.notMachRuleTip = notMachRuleTip;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ByteLengthFilter implements InputFilter {
|
||||||
|
private int length = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
public ByteLengthFilter() {}
|
||||||
|
|
||||||
|
public void setLength(int length) {
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||||
|
int destLength = 0;
|
||||||
|
int destReplaceLength = 0;
|
||||||
|
int sourceLength = 0;
|
||||||
|
if (!TextUtils.isEmpty(dest)) {
|
||||||
|
destLength = dest.toString().getBytes().length;
|
||||||
|
destReplaceLength = dest.subSequence(dstart, dend).toString().getBytes().length;
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(source)) {
|
||||||
|
sourceLength = source.subSequence(start, end).toString().getBytes().length;
|
||||||
|
}
|
||||||
|
int keepBytesLength = length - (destLength - destReplaceLength);
|
||||||
|
if (keepBytesLength <= 0) {
|
||||||
|
if (textExceedListener != null) {
|
||||||
|
textExceedListener.onTextExceedMax();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
} else if (keepBytesLength >= sourceLength) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (textExceedListener != null) {
|
||||||
|
textExceedListener.onTextExceedMax();
|
||||||
|
}
|
||||||
|
return getSource(source, start, keepBytesLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence getSource(CharSequence sequence, int start, int keepLength) {
|
||||||
|
int sequenceLength = sequence.length();
|
||||||
|
int end = 0;
|
||||||
|
for (int i = 1; i <= sequenceLength; i++) {
|
||||||
|
if (sequence.subSequence(0, i).toString().getBytes().length <= keepLength) {
|
||||||
|
end = i;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (end > 0 && Character.isHighSurrogate(sequence.charAt(end - 1))) {
|
||||||
|
--end;
|
||||||
|
if (end == start) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sequence.subSequence(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface OnClickListener {
|
||||||
|
void onClick(String result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnTextExceedListener {
|
||||||
|
void onTextExceedMax();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PaintFlagsDrawFilter;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class RoundCornerImageView extends AppCompatImageView {
|
||||||
|
private final Path path = new Path();
|
||||||
|
private final RectF rectF = new RectF();
|
||||||
|
private final PaintFlagsDrawFilter aliasFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
private int radius;
|
||||||
|
private int leftTopRadius;
|
||||||
|
private int rightTopRadius;
|
||||||
|
private int rightBottomRadius;
|
||||||
|
private int leftBottomRadius;
|
||||||
|
|
||||||
|
public RoundCornerImageView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundCornerImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundCornerImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs) {
|
||||||
|
setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||||
|
int defaultRadius = 0;
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundCornerImageView);
|
||||||
|
radius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_corner_radius, defaultRadius);
|
||||||
|
leftTopRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_left_top_corner_radius, defaultRadius);
|
||||||
|
rightTopRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_right_top_corner_radius, defaultRadius);
|
||||||
|
rightBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_right_bottom_corner_radius, defaultRadius);
|
||||||
|
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_left_bottom_corner_radius, defaultRadius);
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultRadius == leftTopRadius) {
|
||||||
|
leftTopRadius = radius;
|
||||||
|
}
|
||||||
|
if (defaultRadius == rightTopRadius) {
|
||||||
|
rightTopRadius = radius;
|
||||||
|
}
|
||||||
|
if (defaultRadius == rightBottomRadius) {
|
||||||
|
rightBottomRadius = radius;
|
||||||
|
}
|
||||||
|
if (defaultRadius == leftBottomRadius) {
|
||||||
|
leftBottomRadius = radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftBottomRadius(int leftBottomRadius) {
|
||||||
|
this.leftBottomRadius = leftBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftTopRadius(int leftTopRadius) {
|
||||||
|
this.leftTopRadius = leftTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRadius(int radius) {
|
||||||
|
this.radius = radius;
|
||||||
|
leftBottomRadius = radius;
|
||||||
|
rightBottomRadius = radius;
|
||||||
|
rightTopRadius = radius;
|
||||||
|
leftTopRadius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightBottomRadius(int rightBottomRadius) {
|
||||||
|
this.rightBottomRadius = rightBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightTopRadius(int rightTopRadius) {
|
||||||
|
this.rightTopRadius = rightTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLeftBottomRadius() {
|
||||||
|
return leftBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLeftTopRadius() {
|
||||||
|
return leftTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRadius() {
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRightBottomRadius() {
|
||||||
|
return rightBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRightTopRadius() {
|
||||||
|
return rightTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
path.reset();
|
||||||
|
canvas.setDrawFilter(aliasFilter);
|
||||||
|
rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
||||||
|
// left-top -> right-top -> right-bottom -> left-bottom
|
||||||
|
float[] radius = {
|
||||||
|
leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius};
|
||||||
|
path.addRoundRect(rectF, radius, Path.Direction.CW);
|
||||||
|
canvas.clipPath(path);
|
||||||
|
super.onDraw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PaintFlagsDrawFilter;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class RoundFrameLayout extends FrameLayout {
|
||||||
|
private final Path path = new Path();
|
||||||
|
private final RectF rectF = new RectF();
|
||||||
|
private final PaintFlagsDrawFilter aliasFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
private int radius;
|
||||||
|
private int leftTopRadius;
|
||||||
|
private int rightTopRadius;
|
||||||
|
private int rightBottomRadius;
|
||||||
|
private int leftBottomRadius;
|
||||||
|
|
||||||
|
public RoundFrameLayout(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoundFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs) {
|
||||||
|
setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||||
|
int defaultRadius = 0;
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundFrameLayout);
|
||||||
|
radius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_corner_radius, defaultRadius);
|
||||||
|
leftTopRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_left_top_corner_radius, defaultRadius);
|
||||||
|
rightTopRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_right_top_corner_radius, defaultRadius);
|
||||||
|
rightBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_right_bottom_corner_radius, defaultRadius);
|
||||||
|
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_left_bottom_corner_radius, defaultRadius);
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultRadius == leftTopRadius) {
|
||||||
|
leftTopRadius = radius;
|
||||||
|
}
|
||||||
|
if (defaultRadius == rightTopRadius) {
|
||||||
|
rightTopRadius = radius;
|
||||||
|
}
|
||||||
|
if (defaultRadius == rightBottomRadius) {
|
||||||
|
rightBottomRadius = radius;
|
||||||
|
}
|
||||||
|
if (defaultRadius == leftBottomRadius) {
|
||||||
|
leftBottomRadius = radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setLeftBottomRadius(int leftBottomRadius) {
|
||||||
|
this.leftBottomRadius = leftBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftTopRadius(int leftTopRadius) {
|
||||||
|
this.leftTopRadius = leftTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRadius(int radius) {
|
||||||
|
this.radius = radius;
|
||||||
|
leftBottomRadius = radius;
|
||||||
|
rightBottomRadius = radius;
|
||||||
|
rightTopRadius = radius;
|
||||||
|
leftTopRadius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightBottomRadius(int rightBottomRadius) {
|
||||||
|
this.rightBottomRadius = rightBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightTopRadius(int rightTopRadius) {
|
||||||
|
this.rightTopRadius = rightTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLeftBottomRadius() {
|
||||||
|
return leftBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLeftTopRadius() {
|
||||||
|
return leftTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRadius() {
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRightBottomRadius() {
|
||||||
|
return rightBottomRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRightTopRadius() {
|
||||||
|
return rightTopRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void dispatchDraw(Canvas canvas) {
|
||||||
|
path.reset();
|
||||||
|
canvas.setDrawFilter(aliasFilter);
|
||||||
|
rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
||||||
|
// left-top -> right-top -> right-bottom -> left-bottom
|
||||||
|
float[] radius = {
|
||||||
|
leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius};
|
||||||
|
path.addRoundRect(rectF, radius, Path.Direction.CW);
|
||||||
|
canvas.clipPath(path);
|
||||||
|
super.dispatchDraw(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.SwitchCompat;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
public class SwitchCustomWidth extends SwitchCompat {
|
||||||
|
private static final String TAG = "SwitchCustomWidth";
|
||||||
|
|
||||||
|
private int customSwitchWidth;
|
||||||
|
|
||||||
|
public SwitchCustomWidth(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
initCustomAttr(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwitchCustomWidth(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initCustomAttr(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SwitchCustomWidth(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initCustomAttr(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initCustomAttr(Context context, AttributeSet attributeSet) {
|
||||||
|
if (attributeSet != null) {
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.SwitchCustomWidth);
|
||||||
|
customSwitchWidth = array.getDimensionPixelSize(R.styleable.SwitchCustomWidth_custom_width, 0);
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
try {
|
||||||
|
if (customSwitchWidth == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Class<SwitchCompat> clazz = SwitchCompat.class;
|
||||||
|
Field mSwitchWidthFiled = clazz.getDeclaredField("mSwitchWidth");
|
||||||
|
mSwitchWidthFiled.setAccessible(true);
|
||||||
|
mSwitchWidthFiled.set(this, customSwitchWidth);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.w(TAG, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
|
||||||
|
public class TitleBarLayout extends LinearLayout implements ITitleBarLayout {
|
||||||
|
private LinearLayout mLeftGroup;
|
||||||
|
private LinearLayout mRightGroup;
|
||||||
|
private TextView mLeftTitle;
|
||||||
|
private TextView mCenterTitle;
|
||||||
|
private TextView mRightTitle;
|
||||||
|
private ImageView mLeftIcon;
|
||||||
|
private ImageView mRightIcon;
|
||||||
|
private RelativeLayout mTitleLayout;
|
||||||
|
private ImageView mLeftUserAvatar;
|
||||||
|
private TextView mUserNickName;
|
||||||
|
private UnreadCountTextView unreadCountTextView;
|
||||||
|
|
||||||
|
public TitleBarLayout(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleBarLayout(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleBarLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("WrongViewCast")
|
||||||
|
private void init(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
String middleTitle = null;
|
||||||
|
boolean canReturn = false;
|
||||||
|
if (attrs != null) {
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TitleBarLayout);
|
||||||
|
middleTitle = array.getString(R.styleable.TitleBarLayout_title_bar_middle_title);
|
||||||
|
canReturn = array.getBoolean(R.styleable.TitleBarLayout_title_bar_can_return, false);
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
inflate(context, R.layout.timcommon_title_bar_layout, this);
|
||||||
|
mTitleLayout = findViewById(R.id.page_title_layout);
|
||||||
|
mLeftGroup = findViewById(R.id.page_title_left_group);
|
||||||
|
mRightGroup = findViewById(R.id.page_title_right_group);
|
||||||
|
mLeftTitle = findViewById(R.id.page_title_left_text);
|
||||||
|
mRightTitle = findViewById(R.id.page_title_right_text);
|
||||||
|
mCenterTitle = findViewById(R.id.page_title);
|
||||||
|
mLeftIcon = findViewById(R.id.page_title_left_icon);
|
||||||
|
Drawable leftIconDrawable = mLeftIcon.getBackground();
|
||||||
|
if (leftIconDrawable != null) {
|
||||||
|
leftIconDrawable.setAutoMirrored(true);
|
||||||
|
}
|
||||||
|
mRightIcon = findViewById(R.id.page_title_right_icon);
|
||||||
|
unreadCountTextView = findViewById(R.id.new_message_total_unread);
|
||||||
|
|
||||||
|
mLeftUserAvatar = findViewById(R.id.tab_icon);
|
||||||
|
mUserNickName = findViewById(R.id.tab_nickname);
|
||||||
|
|
||||||
|
LayoutParams params = (LayoutParams) mTitleLayout.getLayoutParams();
|
||||||
|
params.height = ScreenUtil.getPxByDp(50);
|
||||||
|
mTitleLayout.setLayoutParams(params);
|
||||||
|
setBackgroundResource(TUIThemeManager.getAttrResId(getContext(), R.attr.core_title_bar_bg));
|
||||||
|
|
||||||
|
int iconSize = ScreenUtil.dip2px(20);
|
||||||
|
ViewGroup.LayoutParams iconParams = mLeftIcon.getLayoutParams();
|
||||||
|
iconParams.width = iconSize;
|
||||||
|
iconParams.height = iconSize;
|
||||||
|
mLeftIcon.setLayoutParams(iconParams);
|
||||||
|
iconParams = mRightIcon.getLayoutParams();
|
||||||
|
iconParams.width = iconSize;
|
||||||
|
iconParams.height = iconSize;
|
||||||
|
|
||||||
|
mRightIcon.setLayoutParams(iconParams);
|
||||||
|
|
||||||
|
if (canReturn) {
|
||||||
|
setLeftReturnListener(context);
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(middleTitle)) {
|
||||||
|
mCenterTitle.setText(middleTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftReturnListener(Context context) {
|
||||||
|
mLeftGroup.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (context instanceof Activity) {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(TitleBarLayout.this.getWindowToken(), 0);
|
||||||
|
((Activity) context).finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnLeftClickListener(OnClickListener listener) {
|
||||||
|
mLeftGroup.setOnClickListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnRightClickListener(OnClickListener listener) {
|
||||||
|
mRightGroup.setOnClickListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTitle(String title, Position position) {
|
||||||
|
switch (position) {
|
||||||
|
case LEFT:
|
||||||
|
mLeftTitle.setText(title);
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
mRightTitle.setText(title);
|
||||||
|
break;
|
||||||
|
case MIDDLE:
|
||||||
|
mCenterTitle.setText(title);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinearLayout getLeftGroup() {
|
||||||
|
return mLeftGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinearLayout getRightGroup() {
|
||||||
|
return mRightGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageView getLeftIcon() {
|
||||||
|
return mLeftIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLeftIcon(int resId) {
|
||||||
|
mLeftIcon.setBackgroundResource(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageView getRightIcon() {
|
||||||
|
return mRightIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRightIcon(int resId) {
|
||||||
|
mRightIcon.setBackgroundResource(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextView getLeftTitle() {
|
||||||
|
return mLeftTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextView getmUserNickName() {
|
||||||
|
return mUserNickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setmUserNickName(TextView mUserNickName) {
|
||||||
|
this.mUserNickName = mUserNickName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageView getmLeftUserAvatar() {
|
||||||
|
return mLeftUserAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setmLeftUserAvatar(ImageView mLeftUserAvatar) {
|
||||||
|
this.mLeftUserAvatar = mLeftUserAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextView getMiddleTitle() {
|
||||||
|
return mCenterTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextView getRightTitle() {
|
||||||
|
return mRightTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnreadCountTextView getUnreadCountTextView() {
|
||||||
|
return unreadCountTextView;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class UnreadCountTextView extends AppCompatTextView {
|
||||||
|
private int mNormalSize;
|
||||||
|
private Paint mPaint;
|
||||||
|
|
||||||
|
public UnreadCountTextView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnreadCountTextView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnreadCountTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs) {
|
||||||
|
setTextDirection(View.TEXT_DIRECTION_LTR);
|
||||||
|
mNormalSize = dp2px(18.4f);
|
||||||
|
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UnreadCountTextView);
|
||||||
|
int paintColor = typedArray.getColor(R.styleable.UnreadCountTextView_paint_color, getResources().getColor(R.color.read_dot_bg));
|
||||||
|
typedArray.recycle();
|
||||||
|
|
||||||
|
mPaint = new Paint();
|
||||||
|
mPaint.setColor(paintColor);
|
||||||
|
mPaint.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPaintColor(int color) {
|
||||||
|
if (mPaint != null) {
|
||||||
|
mPaint.setColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
if (getText().length() == 0) {
|
||||||
|
int l = (getMeasuredWidth() - dp2px(6)) / 2;
|
||||||
|
int t = l;
|
||||||
|
int r = getMeasuredWidth() - l;
|
||||||
|
int b = r;
|
||||||
|
canvas.drawOval(new RectF(l, t, r, b), mPaint);
|
||||||
|
} else if (getText().length() == 1) {
|
||||||
|
canvas.drawOval(new RectF(0, 0, mNormalSize, mNormalSize), mPaint);
|
||||||
|
} else if (getText().length() > 1) {
|
||||||
|
canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), getMeasuredHeight() / 2, getMeasuredHeight() / 2, mPaint);
|
||||||
|
}
|
||||||
|
super.onDraw(canvas);
|
||||||
|
|
||||||
|
// 获取文本的宽度
|
||||||
|
// float textWidth = getPaint().measureText(getText().toString());
|
||||||
|
//
|
||||||
|
// float unreadMarkerLeft = textWidth + dp2px(4);
|
||||||
|
// float unreadMarkerTop = (getMeasuredHeight() - mNormalSize) / 2;
|
||||||
|
// float unreadMarkerRight = unreadMarkerLeft + mNormalSize;
|
||||||
|
// float unreadMarkerBottom = unreadMarkerTop + mNormalSize;
|
||||||
|
//
|
||||||
|
// if (getText().length() == 0) {
|
||||||
|
// int l = (getMeasuredWidth() - dp2px(6)) / 2;
|
||||||
|
// int t = l;
|
||||||
|
// int r = getMeasuredWidth() - l;
|
||||||
|
// int b = r;
|
||||||
|
// canvas.drawOval(new RectF(l, t, r, b), mPaint);
|
||||||
|
// } else if (getText().length() == 1) {
|
||||||
|
// canvas.drawOval(new RectF(unreadMarkerLeft, unreadMarkerTop, unreadMarkerRight, unreadMarkerBottom), mPaint);
|
||||||
|
// } else if (getText().length() > 1) {
|
||||||
|
// canvas.drawRoundRect(
|
||||||
|
// new RectF(unreadMarkerLeft, unreadMarkerTop, unreadMarkerRight + dp2px((getText().length() - 1) * 10), unreadMarkerBottom),
|
||||||
|
// getMeasuredHeight() / 2,
|
||||||
|
// getMeasuredHeight() / 2,
|
||||||
|
// mPaint
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// super.onDraw(canvas); // ✅ 必须放在最后才能显示文本
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int width = mNormalSize;
|
||||||
|
int height = mNormalSize;
|
||||||
|
if (getText().length() > 1) {
|
||||||
|
width = mNormalSize + dp2px((getText().length() - 1) * 10);
|
||||||
|
}
|
||||||
|
setMeasuredDimension(width, height);
|
||||||
|
|
||||||
|
// 固定高度为 mNormalSize
|
||||||
|
// int height = mNormalSize;
|
||||||
|
//
|
||||||
|
// // 计算文本宽度 + 未读标记宽度(包含间距)
|
||||||
|
// float textWidth = getPaint().measureText(getText().toString());
|
||||||
|
// float unreadMarkerWidth = mNormalSize + dp2px(4); // 未读标记 + 间距
|
||||||
|
//
|
||||||
|
// int width = (int) (textWidth + unreadMarkerWidth);
|
||||||
|
//
|
||||||
|
// // 如果是多个字符的计数(如 "99+"),增加额外宽度
|
||||||
|
// if (getText().length() > 1) {
|
||||||
|
// width += dp2px((getText().length() - 1) * 10);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// setMeasuredDimension(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dp2px(float dp) {
|
||||||
|
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
|
||||||
|
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.action;
|
||||||
|
|
||||||
|
public interface PopActionClickListener {
|
||||||
|
void onActionClick(int index, Object data);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.action;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PopDialogAdapter extends BaseAdapter {
|
||||||
|
private List<PopMenuAction> dataSource = new ArrayList<>();
|
||||||
|
|
||||||
|
public void setDataSource(final List datas) {
|
||||||
|
dataSource = datas;
|
||||||
|
ThreadUtils.postOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return dataSource.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return dataSource.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||||
|
ViewHolder holder;
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(TUIConfig.getAppContext()).inflate(R.layout.pop_dialog_adapter, parent, false);
|
||||||
|
holder = new ViewHolder();
|
||||||
|
holder.text = convertView.findViewById(R.id.pop_dialog_text);
|
||||||
|
convertView.setTag(holder);
|
||||||
|
} else {
|
||||||
|
holder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
|
PopMenuAction action = (PopMenuAction) getItem(position);
|
||||||
|
holder.text.setText(action.getActionName());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder {
|
||||||
|
TextView text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.action;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
public class PopMenuAction {
|
||||||
|
private String actionName;
|
||||||
|
private Bitmap icon;
|
||||||
|
private int iconResId;
|
||||||
|
private PopActionClickListener actionClickListener;
|
||||||
|
private int weight;
|
||||||
|
|
||||||
|
public String getActionName() {
|
||||||
|
return actionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionName(String actionName) {
|
||||||
|
this.actionName = actionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIcon(Bitmap mIcon) {
|
||||||
|
this.icon = mIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIconResId() {
|
||||||
|
return iconResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconResId(int iconResId) {
|
||||||
|
this.iconResId = iconResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PopActionClickListener getActionClickListener() {
|
||||||
|
return actionClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActionClickListener(PopActionClickListener actionClickListener) {
|
||||||
|
this.actionClickListener = actionClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWeight(int weight) {
|
||||||
|
this.weight = weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.action;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.BaseAdapter;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PopMenuAdapter extends BaseAdapter {
|
||||||
|
private List<PopMenuAction> dataSource = new ArrayList<>();
|
||||||
|
|
||||||
|
public PopMenuAdapter() {}
|
||||||
|
|
||||||
|
public void setDataSource(final List datas) {
|
||||||
|
dataSource = datas;
|
||||||
|
ThreadUtils.postOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
return dataSource.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getItem(int position) {
|
||||||
|
return dataSource.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getItemId(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(final int position, View convertView, ViewGroup parent) {
|
||||||
|
ViewHolder holder;
|
||||||
|
if (convertView == null) {
|
||||||
|
convertView = LayoutInflater.from(TUIConfig.getAppContext()).inflate(R.layout.pop_menu_adapter, parent, false);
|
||||||
|
holder = new ViewHolder();
|
||||||
|
holder.menuIcon = convertView.findViewById(R.id.pop_menu_icon);
|
||||||
|
|
||||||
|
int iconSize = convertView.getResources().getDimensionPixelSize(R.dimen.core_pop_menu_icon_size);
|
||||||
|
ViewGroup.LayoutParams params = holder.menuIcon.getLayoutParams();
|
||||||
|
params.width = iconSize;
|
||||||
|
params.height = iconSize;
|
||||||
|
holder.menuIcon.setLayoutParams(params);
|
||||||
|
|
||||||
|
holder.menuLable = convertView.findViewById(R.id.pop_menu_label);
|
||||||
|
convertView.setTag(holder);
|
||||||
|
} else {
|
||||||
|
holder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
|
PopMenuAction action = (PopMenuAction) getItem(position);
|
||||||
|
holder.menuIcon.setVisibility(View.VISIBLE);
|
||||||
|
if (action.getIcon() != null) {
|
||||||
|
holder.menuIcon.setImageBitmap(action.getIcon());
|
||||||
|
} else if (action.getIconResId() > 0) {
|
||||||
|
holder.menuIcon.setImageResource(action.getIconResId());
|
||||||
|
} else {
|
||||||
|
holder.menuIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
holder.menuLable.setText(action.getActionName());
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder {
|
||||||
|
TextView menuLable;
|
||||||
|
ImageView menuIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class BaseLightActivity extends AppCompatActivity {
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
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(
|
||||||
|
getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_header_start_color)));
|
||||||
|
getWindow().setNavigationBarColor(getResources().getColor(R.color.navigation_bar_color));
|
||||||
|
int vis = getWindow().getDecorView().getSystemUiVisibility();
|
||||||
|
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||||
|
vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
hideSoftInput();
|
||||||
|
super.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideSoftInput() {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
Window window = getWindow();
|
||||||
|
if (window != null) {
|
||||||
|
imm.hideSoftInputFromWindow(window.getDecorView().getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
public class BaseMinimalistLightActivity extends AppCompatActivity {
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
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(0xFFFFFFFF);
|
||||||
|
getWindow().setNavigationBarColor(0xFFFFFFFF);
|
||||||
|
int vis = getWindow().getDecorView().getSystemUiVisibility();
|
||||||
|
vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
|
||||||
|
vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
|
||||||
|
getWindow().getDecorView().setSystemUiVisibility(vis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void finish() {
|
||||||
|
hideSoftInput();
|
||||||
|
super.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideSoftInput() {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
Window window = getWindow();
|
||||||
|
if (window != null) {
|
||||||
|
imm.hideSoftInputFromWindow(window.getDecorView().getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.activities;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConstants;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuicore.util.ToastUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.SynthesizedImageView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ImageSelectActivity extends BaseLightActivity {
|
||||||
|
private static final String TAG = ImageSelectActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL = "chat/conversation/background/default/url";
|
||||||
|
public static final int RESULT_CODE_ERROR = -1;
|
||||||
|
public static final int RESULT_CODE_SUCCESS = 0;
|
||||||
|
public static final String TITLE = "title";
|
||||||
|
public static final String SPAN_COUNT = "spanCount";
|
||||||
|
public static final String DATA = "data";
|
||||||
|
public static final String ITEM_HEIGHT = "itemHeight";
|
||||||
|
public static final String ITEM_WIDTH = "itemWidth";
|
||||||
|
public static final String SELECTED = "selected";
|
||||||
|
public static final String PLACEHOLDER = "placeholder";
|
||||||
|
public static final String NEED_DOWNLOAD_LOCAL = "needDownload";
|
||||||
|
|
||||||
|
private int defaultSpacing;
|
||||||
|
|
||||||
|
private List<ImageBean> data;
|
||||||
|
private ImageBean selected;
|
||||||
|
private int placeHolder;
|
||||||
|
private int columnNum;
|
||||||
|
private RecyclerView imageGrid;
|
||||||
|
private GridLayoutManager gridLayoutManager;
|
||||||
|
private ImageGridAdapter gridAdapter;
|
||||||
|
private TitleBarLayout titleBarLayout;
|
||||||
|
private int itemHeight;
|
||||||
|
private int itemWidth;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
defaultSpacing = ScreenUtil.dip2px(12);
|
||||||
|
setContentView(R.layout.core_activity_image_select_layout);
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String title = intent.getStringExtra(TITLE);
|
||||||
|
titleBarLayout = findViewById(R.id.image_select_title);
|
||||||
|
titleBarLayout.setTitle(title, ITitleBarLayout.Position.MIDDLE);
|
||||||
|
titleBarLayout.setTitle(getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT);
|
||||||
|
titleBarLayout.getRightIcon().setVisibility(View.GONE);
|
||||||
|
titleBarLayout.getRightTitle().setTextColor(0xFF006EFF);
|
||||||
|
titleBarLayout.setOnLeftClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
setResult(RESULT_CODE_ERROR);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
boolean needDownload = intent.getBooleanExtra(NEED_DOWNLOAD_LOCAL, false);
|
||||||
|
titleBarLayout.setOnRightClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (selected == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (needDownload) {
|
||||||
|
downloadUrl();
|
||||||
|
} else {
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra(DATA, (Serializable) selected);
|
||||||
|
setResult(RESULT_CODE_SUCCESS, resultIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
data = (List<ImageBean>) intent.getSerializableExtra(DATA);
|
||||||
|
selected = (ImageBean) intent.getSerializableExtra(SELECTED);
|
||||||
|
placeHolder = intent.getIntExtra(PLACEHOLDER, 0);
|
||||||
|
itemHeight = intent.getIntExtra(ITEM_HEIGHT, 0);
|
||||||
|
itemWidth = intent.getIntExtra(ITEM_WIDTH, 0);
|
||||||
|
columnNum = intent.getIntExtra(SPAN_COUNT, 2);
|
||||||
|
gridLayoutManager = new GridLayoutManager(this, columnNum);
|
||||||
|
imageGrid = findViewById(R.id.image_select_grid);
|
||||||
|
imageGrid.addItemDecoration(new GridDecoration(columnNum, defaultSpacing, defaultSpacing));
|
||||||
|
imageGrid.setLayoutManager(gridLayoutManager);
|
||||||
|
imageGrid.setItemAnimator(null);
|
||||||
|
gridAdapter = new ImageGridAdapter();
|
||||||
|
gridAdapter.setPlaceHolder(placeHolder);
|
||||||
|
gridAdapter.setSelected(selected);
|
||||||
|
gridAdapter.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(ImageBean obj) {
|
||||||
|
selected = obj;
|
||||||
|
setSelectedStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridAdapter.setItemWidth(itemWidth);
|
||||||
|
gridAdapter.setItemHeight(itemHeight);
|
||||||
|
imageGrid.setAdapter(gridAdapter);
|
||||||
|
gridAdapter.setData(data);
|
||||||
|
setSelectedStatus();
|
||||||
|
gridAdapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadUrl() {
|
||||||
|
if (selected == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.isDefault()) {
|
||||||
|
selected.setLocalPath(CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL);
|
||||||
|
setResult(selected);
|
||||||
|
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = selected.getImageUri();
|
||||||
|
if (TextUtils.isEmpty(url)) {
|
||||||
|
Log.d(TAG, "DownloadUrl is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgressDialog dialog = new ProgressDialog(this);
|
||||||
|
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
dialog.setCancelable(false);
|
||||||
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
|
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.setMessage(getResources().getString(R.string.setting));
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
ImageBean finalBean = selected;
|
||||||
|
Glide.with(this)
|
||||||
|
.downloadOnly()
|
||||||
|
.load(url)
|
||||||
|
.listener(new RequestListener<File>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
||||||
|
dialog.cancel();
|
||||||
|
Log.e(TAG, "DownloadUrl onLoadFailed e = " + e);
|
||||||
|
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_fail));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
dialog.cancel();
|
||||||
|
String path = resource.getAbsolutePath();
|
||||||
|
Log.e(TAG, "DownloadUrl resource path = " + path);
|
||||||
|
finalBean.setLocalPath(path);
|
||||||
|
setResult(finalBean);
|
||||||
|
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.preload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setResult(ImageBean bean) {
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra(DATA, (Serializable) bean);
|
||||||
|
setResult(RESULT_CODE_SUCCESS, resultIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelectedStatus() {
|
||||||
|
if (selected != null && data != null && data.contains(selected)) {
|
||||||
|
titleBarLayout.getRightTitle().setEnabled(true);
|
||||||
|
titleBarLayout.getRightTitle().setTextColor(
|
||||||
|
getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_primary_color)));
|
||||||
|
} else {
|
||||||
|
titleBarLayout.getRightTitle().setEnabled(false);
|
||||||
|
titleBarLayout.getRightTitle().setTextColor(0xFF666666);
|
||||||
|
}
|
||||||
|
gridAdapter.setSelected(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageGridAdapter extends RecyclerView.Adapter<ImageGridAdapter.ImageViewHolder> {
|
||||||
|
private int itemWidth;
|
||||||
|
private int itemHeight;
|
||||||
|
|
||||||
|
private List<ImageBean> data;
|
||||||
|
private ImageBean selected;
|
||||||
|
private int placeHolder;
|
||||||
|
private OnItemClickListener onItemClickListener;
|
||||||
|
|
||||||
|
public void setData(List<ImageBean> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelected(ImageBean selected) {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
this.selected = selected;
|
||||||
|
} else {
|
||||||
|
this.selected = selected;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaceHolder(int placeHolder) {
|
||||||
|
this.placeHolder = placeHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemHeight(int itemHeight) {
|
||||||
|
this.itemHeight = itemHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemWidth(int itemWidth) {
|
||||||
|
this.itemWidth = itemWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
|
||||||
|
this.onItemClickListener = onItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.core_select_image_item_layout, parent, false);
|
||||||
|
return new ImageViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {
|
||||||
|
ImageView imageView = holder.imageView;
|
||||||
|
setItemLayoutParams(holder);
|
||||||
|
ImageBean imageBean = data.get(position);
|
||||||
|
if (selected != null && imageBean != null && TextUtils.equals(selected.getThumbnailUri(), imageBean.getThumbnailUri())) {
|
||||||
|
holder.selectBorderLayout.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.selectBorderLayout.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageBean.getGroupGridAvatar() != null) {
|
||||||
|
holder.defaultLayout.setVisibility(View.GONE);
|
||||||
|
if (imageView instanceof SynthesizedImageView) {
|
||||||
|
SynthesizedImageView synthesizedImageView = ((SynthesizedImageView) (imageView));
|
||||||
|
String imageId = imageBean.getImageId();
|
||||||
|
synthesizedImageView.setImageId(imageId);
|
||||||
|
synthesizedImageView.displayImage(imageBean.getGroupGridAvatar()).load(imageId);
|
||||||
|
}
|
||||||
|
} else if (imageBean.isDefault()) {
|
||||||
|
holder.defaultLayout.setVisibility(View.VISIBLE);
|
||||||
|
imageView.setImageResource(android.R.color.transparent);
|
||||||
|
} else {
|
||||||
|
holder.defaultLayout.setVisibility(View.GONE);
|
||||||
|
Glide.with(holder.itemView.getContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(imageBean.getThumbnailUri())
|
||||||
|
.placeholder(placeHolder)
|
||||||
|
.apply(new RequestOptions().error(placeHolder))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onClick(imageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setItemLayoutParams(ImageViewHolder holder) {
|
||||||
|
if (itemHeight > 0 && itemWidth > 0) {
|
||||||
|
ViewGroup.LayoutParams itemViewLayoutParams = holder.itemView.getLayoutParams();
|
||||||
|
itemViewLayoutParams.width = itemWidth;
|
||||||
|
itemViewLayoutParams.height = itemHeight;
|
||||||
|
holder.itemView.setLayoutParams(itemViewLayoutParams);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams params = holder.imageView.getLayoutParams();
|
||||||
|
params.width = itemWidth;
|
||||||
|
params.height = itemHeight;
|
||||||
|
holder.imageView.setLayoutParams(params);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams borderLayoutParams = holder.selectBorderLayout.getLayoutParams();
|
||||||
|
borderLayoutParams.width = itemWidth;
|
||||||
|
borderLayoutParams.height = itemHeight;
|
||||||
|
holder.selectBorderLayout.setLayoutParams(borderLayoutParams);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams borderParams = holder.selectedBorder.getLayoutParams();
|
||||||
|
borderParams.width = itemWidth;
|
||||||
|
borderParams.height = itemHeight;
|
||||||
|
holder.selectedBorder.setLayoutParams(borderParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ImageView imageView;
|
||||||
|
private final ImageView selectedBorder;
|
||||||
|
private final RelativeLayout selectBorderLayout;
|
||||||
|
private final Button defaultLayout;
|
||||||
|
|
||||||
|
public ImageViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
imageView = itemView.findViewById(R.id.content_image);
|
||||||
|
selectedBorder = itemView.findViewById(R.id.select_border);
|
||||||
|
selectBorderLayout = itemView.findViewById(R.id.selected_border_area);
|
||||||
|
defaultLayout = itemView.findViewById(R.id.default_image_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add spacing
|
||||||
|
*/
|
||||||
|
public static class GridDecoration extends RecyclerView.ItemDecoration {
|
||||||
|
private final int columnNum; // span count
|
||||||
|
private final int leftRightSpace; // vertical spacing
|
||||||
|
private final int topBottomSpace; // horizontal spacing
|
||||||
|
|
||||||
|
public GridDecoration(int columnNum, int leftRightSpace, int topBottomSpace) {
|
||||||
|
this.columnNum = columnNum;
|
||||||
|
this.leftRightSpace = leftRightSpace;
|
||||||
|
this.topBottomSpace = topBottomSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
int position = parent.getChildAdapterPosition(view);
|
||||||
|
int column = position % columnNum;
|
||||||
|
|
||||||
|
int left = column * leftRightSpace / columnNum;
|
||||||
|
int right = leftRightSpace * (columnNum - 1 - column) / columnNum;
|
||||||
|
if (LayoutUtil.isRTL()) {
|
||||||
|
outRect.left = right;
|
||||||
|
outRect.right = left;
|
||||||
|
} else {
|
||||||
|
outRect.left = left;
|
||||||
|
outRect.right = right;
|
||||||
|
}
|
||||||
|
// add top spacing
|
||||||
|
if (position >= columnNum) {
|
||||||
|
outRect.top = topBottomSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
void onClick(ImageBean obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageBean implements Serializable {
|
||||||
|
String thumbnailUri; // for display
|
||||||
|
String imageUri; // for download
|
||||||
|
String localPath; // for local path
|
||||||
|
boolean isDefault = false; // for default display
|
||||||
|
List<Object> groupGridAvatar = null; // for group grid avatar
|
||||||
|
String imageId;
|
||||||
|
|
||||||
|
public ImageBean() {}
|
||||||
|
|
||||||
|
public ImageBean(String thumbnailUri, String imageUri, boolean isDefault) {
|
||||||
|
this.thumbnailUri = thumbnailUri;
|
||||||
|
this.imageUri = imageUri;
|
||||||
|
this.isDefault = isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageUri() {
|
||||||
|
return imageUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUri() {
|
||||||
|
return thumbnailUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUri(String imageUri) {
|
||||||
|
this.imageUri = imageUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThumbnailUri(String thumbnailUri) {
|
||||||
|
this.thumbnailUri = thumbnailUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalPath() {
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalPath(String localPath) {
|
||||||
|
this.localPath = localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefault() {
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefault(boolean aDefault) {
|
||||||
|
isDefault = aDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getGroupGridAvatar() {
|
||||||
|
return groupGridAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupGridAvatar(List<Object> groupGridAvatar) {
|
||||||
|
this.groupGridAvatar = groupGridAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageId() {
|
||||||
|
return imageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageId(String imageId) {
|
||||||
|
this.imageId = imageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.activities;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConstants;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuicore.util.ToastUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.SynthesizedImageView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ImageSelectMinimalistActivity extends BaseMinimalistLightActivity {
|
||||||
|
private static final String TAG = ImageSelectMinimalistActivity.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final int RESULT_CODE_ERROR = -1;
|
||||||
|
public static final int RESULT_CODE_SUCCESS = 0;
|
||||||
|
public static final String TITLE = "title";
|
||||||
|
public static final String SPAN_COUNT = "spanCount";
|
||||||
|
public static final String DATA = "data";
|
||||||
|
public static final String ITEM_HEIGHT = "itemHeight";
|
||||||
|
public static final String ITEM_WIDTH = "itemWidth";
|
||||||
|
public static final String SELECTED = "selected";
|
||||||
|
public static final String PLACEHOLDER = "placeholder";
|
||||||
|
public static final String NEED_DOWLOAD_LOCAL = "needdowmload";
|
||||||
|
public static final String CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL = "chat/conversation/background/default/url";
|
||||||
|
|
||||||
|
private int defaultSpacing;
|
||||||
|
|
||||||
|
private List<ImageBean> data;
|
||||||
|
private ImageBean selected;
|
||||||
|
private int placeHolder;
|
||||||
|
private int columnNum;
|
||||||
|
private RecyclerView imageGrid;
|
||||||
|
private GridLayoutManager gridLayoutManager;
|
||||||
|
private ImageGridAdapter gridAdapter;
|
||||||
|
private TitleBarLayout titleBarLayout;
|
||||||
|
private int itemHeight;
|
||||||
|
private int itemWidth;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
defaultSpacing = ScreenUtil.dip2px(12);
|
||||||
|
setContentView(R.layout.core_minimalist_activity_image_select_layout);
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String title = intent.getStringExtra(TITLE);
|
||||||
|
titleBarLayout = findViewById(R.id.image_select_title);
|
||||||
|
titleBarLayout.setTitle(title, ITitleBarLayout.Position.MIDDLE);
|
||||||
|
titleBarLayout.setTitle(getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT);
|
||||||
|
titleBarLayout.getRightIcon().setVisibility(View.GONE);
|
||||||
|
titleBarLayout.getRightTitle().setTextColor(0xFF006EFF);
|
||||||
|
titleBarLayout.setOnLeftClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
setResult(RESULT_CODE_ERROR);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
boolean needDownload = intent.getBooleanExtra(NEED_DOWLOAD_LOCAL, false);
|
||||||
|
titleBarLayout.setOnRightClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (selected == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (needDownload) {
|
||||||
|
downloadUrl();
|
||||||
|
} else {
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra(DATA, (Serializable) selected);
|
||||||
|
setResult(RESULT_CODE_SUCCESS, resultIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
data = (List<ImageBean>) intent.getSerializableExtra(DATA);
|
||||||
|
selected = (ImageBean) intent.getSerializableExtra(SELECTED);
|
||||||
|
placeHolder = intent.getIntExtra(PLACEHOLDER, 0);
|
||||||
|
itemHeight = intent.getIntExtra(ITEM_HEIGHT, 0);
|
||||||
|
itemWidth = intent.getIntExtra(ITEM_WIDTH, 0);
|
||||||
|
columnNum = intent.getIntExtra(SPAN_COUNT, 2);
|
||||||
|
gridLayoutManager = new GridLayoutManager(this, columnNum);
|
||||||
|
imageGrid = findViewById(R.id.image_select_grid);
|
||||||
|
imageGrid.addItemDecoration(new GridDecoration(columnNum, defaultSpacing, defaultSpacing));
|
||||||
|
imageGrid.setLayoutManager(gridLayoutManager);
|
||||||
|
imageGrid.setItemAnimator(null);
|
||||||
|
gridAdapter = new ImageGridAdapter();
|
||||||
|
gridAdapter.setPlaceHolder(placeHolder);
|
||||||
|
gridAdapter.setSelected(selected);
|
||||||
|
gridAdapter.setOnItemClickListener(new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(ImageBean obj) {
|
||||||
|
selected = obj;
|
||||||
|
setSelectedStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gridAdapter.setItemWidth(itemWidth);
|
||||||
|
gridAdapter.setItemHeight(itemHeight);
|
||||||
|
gridAdapter.setData(data);
|
||||||
|
imageGrid.setAdapter(gridAdapter);
|
||||||
|
setSelectedStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadUrl() {
|
||||||
|
if (selected == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.isDefault()) {
|
||||||
|
selected.setLocalPath(CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL);
|
||||||
|
setResult(selected);
|
||||||
|
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = selected.getImageUri();
|
||||||
|
if (TextUtils.isEmpty(url)) {
|
||||||
|
Log.d(TAG, "DownloadUrl is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProgressDialog dialog = new ProgressDialog(this);
|
||||||
|
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||||
|
dialog.setCancelable(false);
|
||||||
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
|
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.setMessage(getResources().getString(R.string.setting));
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
ImageBean finalBean = selected;
|
||||||
|
Glide.with(this)
|
||||||
|
.downloadOnly()
|
||||||
|
.load(url)
|
||||||
|
.listener(new RequestListener<File>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
||||||
|
dialog.cancel();
|
||||||
|
Log.e(TAG, "DownloadUrl onLoadFailed e = " + e);
|
||||||
|
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_fail));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(File resource, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
dialog.cancel();
|
||||||
|
String path = resource.getAbsolutePath();
|
||||||
|
Log.e(TAG, "DownloadUrl resource path = " + path);
|
||||||
|
finalBean.setLocalPath(path);
|
||||||
|
setResult(finalBean);
|
||||||
|
ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.preload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setResult(ImageBean bean) {
|
||||||
|
Intent resultIntent = new Intent();
|
||||||
|
resultIntent.putExtra(DATA, (Serializable) bean);
|
||||||
|
setResult(RESULT_CODE_SUCCESS, resultIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSelectedStatus() {
|
||||||
|
if (selected != null && data != null && data.contains(selected)) {
|
||||||
|
titleBarLayout.getRightTitle().setEnabled(true);
|
||||||
|
titleBarLayout.getRightTitle().setTextColor(
|
||||||
|
getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_primary_color)));
|
||||||
|
} else {
|
||||||
|
titleBarLayout.getRightTitle().setEnabled(false);
|
||||||
|
titleBarLayout.getRightTitle().setTextColor(0xFF666666);
|
||||||
|
}
|
||||||
|
gridAdapter.setSelected(selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageGridAdapter extends RecyclerView.Adapter<ImageGridAdapter.ImageViewHolder> {
|
||||||
|
private int itemWidth;
|
||||||
|
private int itemHeight;
|
||||||
|
|
||||||
|
private List<ImageBean> data;
|
||||||
|
private ImageBean selected;
|
||||||
|
private int placeHolder;
|
||||||
|
private OnItemClickListener onItemClickListener;
|
||||||
|
|
||||||
|
public void setData(List<ImageBean> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageBean getDataByPosition(int position) {
|
||||||
|
if (data != null) {
|
||||||
|
return data.get(position);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelected(ImageBean selected) {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
this.selected = selected;
|
||||||
|
} else {
|
||||||
|
int index = indexOf(selected);
|
||||||
|
int index2 = indexOf(this.selected);
|
||||||
|
this.selected = selected;
|
||||||
|
notifyItemChanged(index, this.selected);
|
||||||
|
notifyItemChanged(index2, this.selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int indexOf(ImageBean imageBean) {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
int originIndex = -1;
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
ImageBean item = data.get(i);
|
||||||
|
if (TextUtils.equals(item.thumbnailUri, imageBean.thumbnailUri)) {
|
||||||
|
originIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaceHolder(int placeHolder) {
|
||||||
|
this.placeHolder = placeHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemHeight(int itemHeight) {
|
||||||
|
this.itemHeight = itemHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItemWidth(int itemWidth) {
|
||||||
|
this.itemWidth = itemWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
|
||||||
|
this.onItemClickListener = onItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.core_select_image_item_layout, parent, false);
|
||||||
|
return new ImageViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ImageViewHolder holder, int position, @NonNull List<Object> payload) {
|
||||||
|
ImageView imageView = holder.imageView;
|
||||||
|
setItemLayoutParams(holder);
|
||||||
|
ImageBean imageBean = data.get(position);
|
||||||
|
if (selected != null && imageBean != null && TextUtils.equals(selected.getThumbnailUri(), imageBean.getThumbnailUri())) {
|
||||||
|
holder.selectBorderLayout.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.selectBorderLayout.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageBean.getGroupGridAvatar() != null) {
|
||||||
|
holder.defaultLayout.setVisibility(View.GONE);
|
||||||
|
if (!payload.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (imageView instanceof SynthesizedImageView) {
|
||||||
|
SynthesizedImageView synthesizedImageView = ((SynthesizedImageView) (imageView));
|
||||||
|
String imageId = imageBean.getImageId();
|
||||||
|
synthesizedImageView.setImageId(imageId);
|
||||||
|
synthesizedImageView.displayImage(imageBean.getGroupGridAvatar()).load(imageId);
|
||||||
|
}
|
||||||
|
} else if (imageBean.isDefault()) {
|
||||||
|
holder.defaultLayout.setVisibility(View.VISIBLE);
|
||||||
|
imageView.setImageResource(android.R.color.transparent);
|
||||||
|
} else {
|
||||||
|
holder.defaultLayout.setVisibility(View.GONE);
|
||||||
|
Glide.with(holder.itemView.getContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(imageBean.getThumbnailUri())
|
||||||
|
.placeholder(placeHolder)
|
||||||
|
.apply(new RequestOptions().error(placeHolder))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onClick(imageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setItemLayoutParams(ImageViewHolder holder) {
|
||||||
|
if (itemHeight > 0 && itemWidth > 0) {
|
||||||
|
ViewGroup.LayoutParams itemViewLayoutParams = holder.itemView.getLayoutParams();
|
||||||
|
itemViewLayoutParams.width = itemWidth;
|
||||||
|
itemViewLayoutParams.height = itemHeight;
|
||||||
|
holder.itemView.setLayoutParams(itemViewLayoutParams);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams params = holder.imageView.getLayoutParams();
|
||||||
|
params.width = itemWidth;
|
||||||
|
params.height = itemHeight;
|
||||||
|
holder.imageView.setLayoutParams(params);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams borderLayoutParams = holder.selectBorderLayout.getLayoutParams();
|
||||||
|
borderLayoutParams.width = itemWidth;
|
||||||
|
borderLayoutParams.height = itemHeight;
|
||||||
|
holder.selectBorderLayout.setLayoutParams(borderLayoutParams);
|
||||||
|
|
||||||
|
ViewGroup.LayoutParams borderParams = holder.selectedBorder.getLayoutParams();
|
||||||
|
borderParams.width = itemWidth;
|
||||||
|
borderParams.height = itemHeight;
|
||||||
|
holder.selectedBorder.setLayoutParams(borderParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
if (data == null || data.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ImageView imageView;
|
||||||
|
private final ImageView selectedBorder;
|
||||||
|
private final RelativeLayout selectBorderLayout;
|
||||||
|
private final Button defaultLayout;
|
||||||
|
|
||||||
|
public ImageViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
imageView = itemView.findViewById(R.id.content_image);
|
||||||
|
selectedBorder = itemView.findViewById(R.id.select_border);
|
||||||
|
selectBorderLayout = itemView.findViewById(R.id.selected_border_area);
|
||||||
|
defaultLayout = itemView.findViewById(R.id.default_image_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add spacing
|
||||||
|
*/
|
||||||
|
public static class GridDecoration extends RecyclerView.ItemDecoration {
|
||||||
|
private final int columnNum; // span count
|
||||||
|
private final int leftRightSpace; // vertical spacing
|
||||||
|
private final int topBottomSpace; // horizontal spacing
|
||||||
|
|
||||||
|
public GridDecoration(int columnNum, int leftRightSpace, int topBottomSpace) {
|
||||||
|
this.columnNum = columnNum;
|
||||||
|
this.leftRightSpace = leftRightSpace;
|
||||||
|
this.topBottomSpace = topBottomSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
int position = parent.getChildAdapterPosition(view);
|
||||||
|
int column = position % columnNum;
|
||||||
|
|
||||||
|
int left = column * leftRightSpace / columnNum;
|
||||||
|
int right = leftRightSpace * (columnNum - 1 - column) / columnNum;
|
||||||
|
if (LayoutUtil.isRTL()) {
|
||||||
|
outRect.left = right;
|
||||||
|
outRect.right = left;
|
||||||
|
} else {
|
||||||
|
outRect.left = left;
|
||||||
|
outRect.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add top spacing
|
||||||
|
if (position >= columnNum) {
|
||||||
|
outRect.top = topBottomSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
void onClick(ImageBean obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageBean implements Serializable {
|
||||||
|
String thumbnailUri; // for display
|
||||||
|
String imageUri; // for download
|
||||||
|
String localPath; // for local path
|
||||||
|
boolean isDefault = false; // for default display
|
||||||
|
List<Object> groupGridAvatar = null; // for group grid avatar
|
||||||
|
String imageId;
|
||||||
|
|
||||||
|
public ImageBean() {}
|
||||||
|
|
||||||
|
public ImageBean(String thumbnailUri, String imageUri, boolean isDefault) {
|
||||||
|
this.thumbnailUri = thumbnailUri;
|
||||||
|
this.imageUri = imageUri;
|
||||||
|
this.isDefault = isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageUri() {
|
||||||
|
return imageUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUri() {
|
||||||
|
return thumbnailUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUri(String imageUri) {
|
||||||
|
this.imageUri = imageUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setThumbnailUri(String thumbnailUri) {
|
||||||
|
this.thumbnailUri = thumbnailUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalPath() {
|
||||||
|
return localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalPath(String localPath) {
|
||||||
|
this.localPath = localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDefault() {
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefault(boolean aDefault) {
|
||||||
|
isDefault = aDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getGroupGridAvatar() {
|
||||||
|
return groupGridAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGroupGridAvatar(List<Object> groupGridAvatar) {
|
||||||
|
this.groupGridAvatar = groupGridAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageId() {
|
||||||
|
return imageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageId(String imageId) {
|
||||||
|
this.imageId = imageId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.InputFilter;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class SelectionActivity extends BaseLightActivity {
|
||||||
|
private static OnResultReturnListener sOnResultReturnListener;
|
||||||
|
|
||||||
|
private RecyclerView selectListView;
|
||||||
|
private SelectAdapter selectListAdapter;
|
||||||
|
private EditText input;
|
||||||
|
private int mSelectionType;
|
||||||
|
private ArrayList<String> selectList = new ArrayList<>();
|
||||||
|
private int selectedItem = -1;
|
||||||
|
private OnItemClickListener onItemClickListener;
|
||||||
|
private boolean needConfirm = true;
|
||||||
|
private boolean returnNow = true;
|
||||||
|
|
||||||
|
public static void startTextSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
|
||||||
|
bundle.putInt(Selection.TYPE, Selection.TYPE_TEXT);
|
||||||
|
startSelection(context, bundle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startListSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
|
||||||
|
bundle.putInt(Selection.TYPE, Selection.TYPE_LIST);
|
||||||
|
startSelection(context, bundle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
|
||||||
|
Intent intent = new Intent(context, SelectionActivity.class);
|
||||||
|
intent.putExtra(Selection.CONTENT, bundle);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
sOnResultReturnListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.tuicore_selection_activity);
|
||||||
|
final TitleBarLayout titleBar = findViewById(R.id.edit_title_bar);
|
||||||
|
selectListView = findViewById(R.id.select_list);
|
||||||
|
selectListAdapter = new SelectAdapter();
|
||||||
|
selectListView.setAdapter(selectListAdapter);
|
||||||
|
selectListView.setLayoutManager(new CustomLinearLayoutManager(this));
|
||||||
|
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
|
||||||
|
dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.core_list_divider));
|
||||||
|
selectListView.addItemDecoration(dividerItemDecoration);
|
||||||
|
onItemClickListener = new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(int position) {
|
||||||
|
selectedItem = position;
|
||||||
|
selectListAdapter.setSelectedItem(position);
|
||||||
|
selectListAdapter.notifyDataSetChanged();
|
||||||
|
if (!needConfirm) {
|
||||||
|
echoClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
input = findViewById(R.id.edit_content_et);
|
||||||
|
|
||||||
|
Bundle bundle = getIntent().getBundleExtra(Selection.CONTENT);
|
||||||
|
switch (bundle.getInt(Selection.TYPE)) {
|
||||||
|
case Selection.TYPE_TEXT:
|
||||||
|
selectListView.setVisibility(View.GONE);
|
||||||
|
String defaultString = bundle.getString(Selection.INIT_CONTENT);
|
||||||
|
int limit = bundle.getInt(Selection.LIMIT);
|
||||||
|
if (!TextUtils.isEmpty(defaultString)) {
|
||||||
|
input.setText(defaultString);
|
||||||
|
input.setSelection(defaultString.length());
|
||||||
|
}
|
||||||
|
if (limit > 0) {
|
||||||
|
input.setFilters(new InputFilter[] {new InputFilter.LengthFilter(limit)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Selection.TYPE_LIST:
|
||||||
|
input.setVisibility(View.GONE);
|
||||||
|
ArrayList<String> list = bundle.getStringArrayList(Selection.LIST);
|
||||||
|
selectedItem = bundle.getInt(Selection.DEFAULT_SELECT_ITEM_INDEX);
|
||||||
|
if (list == null || list.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectList.clear();
|
||||||
|
selectList.addAll(list);
|
||||||
|
selectListAdapter.setSelectedItem(selectedItem);
|
||||||
|
selectListAdapter.setData(selectList);
|
||||||
|
selectListAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSelectionType = bundle.getInt(Selection.TYPE);
|
||||||
|
|
||||||
|
final String title = bundle.getString(Selection.TITLE);
|
||||||
|
|
||||||
|
needConfirm = bundle.getBoolean(Selection.NEED_CONFIRM, true);
|
||||||
|
returnNow = bundle.getBoolean(Selection.RETURN_NOW, true);
|
||||||
|
|
||||||
|
titleBar.setTitle(title, ITitleBarLayout.Position.MIDDLE);
|
||||||
|
titleBar.setOnLeftClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
titleBar.getRightIcon().setVisibility(View.GONE);
|
||||||
|
if (needConfirm) {
|
||||||
|
titleBar.getRightTitle().setText(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure));
|
||||||
|
titleBar.setOnRightClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
echoClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
titleBar.getRightGroup().setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void echoClick() {
|
||||||
|
switch (mSelectionType) {
|
||||||
|
case Selection.TYPE_TEXT:
|
||||||
|
if (sOnResultReturnListener != null) {
|
||||||
|
sOnResultReturnListener.onReturn(input.getText().toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Selection.TYPE_LIST:
|
||||||
|
if (sOnResultReturnListener != null) {
|
||||||
|
sOnResultReturnListener.onReturn(selectedItem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (returnNow) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
sOnResultReturnListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectAdapter extends RecyclerView.Adapter<SelectAdapter.SelectViewHolder> {
|
||||||
|
int selectedItem = -1;
|
||||||
|
ArrayList<String> data = new ArrayList<>();
|
||||||
|
|
||||||
|
public void setData(ArrayList<String> data) {
|
||||||
|
this.data.clear();
|
||||||
|
this.data.addAll(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedItem(int selectedItem) {
|
||||||
|
this.selectedItem = selectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(SelectionActivity.this).inflate(R.layout.core_select_item_layout, parent, false);
|
||||||
|
return new SelectViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SelectViewHolder holder, int position) {
|
||||||
|
String nameStr = data.get(position);
|
||||||
|
holder.name.setText(nameStr);
|
||||||
|
if (selectedItem == position) {
|
||||||
|
holder.selectedIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.selectedIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onItemClickListener.onClick(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
TextView name;
|
||||||
|
ImageView selectedIcon;
|
||||||
|
|
||||||
|
public SelectViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
name = itemView.findViewById(R.id.name);
|
||||||
|
selectedIcon = itemView.findViewById(R.id.selected_icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnResultReturnListener {
|
||||||
|
void onReturn(Object res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
void onClick(int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Selection {
|
||||||
|
public static final String SELECT_ALL = "select_all";
|
||||||
|
public static final String CONTENT = "content";
|
||||||
|
public static final String TYPE = "type";
|
||||||
|
public static final String TITLE = "title";
|
||||||
|
public static final String INIT_CONTENT = "init_content";
|
||||||
|
public static final String DEFAULT_SELECT_ITEM_INDEX = "default_select_item_index";
|
||||||
|
public static final String LIST = "list";
|
||||||
|
public static final String LIMIT = "limit";
|
||||||
|
public static final String NEED_CONFIRM = "needConfirm";
|
||||||
|
public static final String RETURN_NOW = "returnNow";
|
||||||
|
public static final int TYPE_TEXT = 1;
|
||||||
|
public static final int TYPE_LIST = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.InputFilter;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class SelectionMinimalistActivity extends BaseMinimalistLightActivity {
|
||||||
|
private static OnResultReturnListener sOnResultReturnListener;
|
||||||
|
|
||||||
|
private RecyclerView selectListView;
|
||||||
|
private SelectAdapter selectListAdapter;
|
||||||
|
private EditText input;
|
||||||
|
private int mSelectionType;
|
||||||
|
private ArrayList<String> selectList = new ArrayList<>();
|
||||||
|
private int selectedItem = -1;
|
||||||
|
private OnItemClickListener onItemClickListener;
|
||||||
|
private boolean needConfirm = true;
|
||||||
|
private boolean returnNow = true;
|
||||||
|
|
||||||
|
public static void startTextSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
|
||||||
|
bundle.putInt(Selection.TYPE, Selection.TYPE_TEXT);
|
||||||
|
startSelection(context, bundle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startListSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
|
||||||
|
bundle.putInt(Selection.TYPE, Selection.TYPE_LIST);
|
||||||
|
startSelection(context, bundle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startSelection(Context context, Bundle bundle, OnResultReturnListener listener) {
|
||||||
|
Intent intent = new Intent(context, SelectionMinimalistActivity.class);
|
||||||
|
intent.putExtra(Selection.CONTENT, bundle);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
context.startActivity(intent);
|
||||||
|
sOnResultReturnListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.core_minimalist_selection_activity);
|
||||||
|
final TitleBarLayout titleBar = findViewById(R.id.edit_title_bar);
|
||||||
|
selectListView = findViewById(R.id.select_list);
|
||||||
|
selectListAdapter = new SelectAdapter();
|
||||||
|
selectListView.setAdapter(selectListAdapter);
|
||||||
|
selectListView.setLayoutManager(new CustomLinearLayoutManager(this));
|
||||||
|
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
|
||||||
|
dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.core_list_divider));
|
||||||
|
selectListView.addItemDecoration(dividerItemDecoration);
|
||||||
|
onItemClickListener = new OnItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(int position) {
|
||||||
|
selectedItem = position;
|
||||||
|
selectListAdapter.setSelectedItem(position);
|
||||||
|
selectListAdapter.notifyDataSetChanged();
|
||||||
|
if (!needConfirm) {
|
||||||
|
echoClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
input = findViewById(R.id.edit_content_et);
|
||||||
|
|
||||||
|
Bundle bundle = getIntent().getBundleExtra(Selection.CONTENT);
|
||||||
|
switch (bundle.getInt(Selection.TYPE)) {
|
||||||
|
case Selection.TYPE_TEXT:
|
||||||
|
selectListView.setVisibility(View.GONE);
|
||||||
|
String defaultString = bundle.getString(Selection.INIT_CONTENT);
|
||||||
|
int limit = bundle.getInt(Selection.LIMIT);
|
||||||
|
if (!TextUtils.isEmpty(defaultString)) {
|
||||||
|
input.setText(defaultString);
|
||||||
|
input.setSelection(defaultString.length());
|
||||||
|
}
|
||||||
|
if (limit > 0) {
|
||||||
|
input.setFilters(new InputFilter[] {new InputFilter.LengthFilter(limit)});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Selection.TYPE_LIST:
|
||||||
|
input.setVisibility(View.GONE);
|
||||||
|
ArrayList<String> list = bundle.getStringArrayList(Selection.LIST);
|
||||||
|
selectedItem = bundle.getInt(Selection.DEFAULT_SELECT_ITEM_INDEX);
|
||||||
|
if (list == null || list.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectList.clear();
|
||||||
|
selectList.addAll(list);
|
||||||
|
selectListAdapter.setSelectedItem(selectedItem);
|
||||||
|
selectListAdapter.setData(selectList);
|
||||||
|
selectListAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mSelectionType = bundle.getInt(Selection.TYPE);
|
||||||
|
|
||||||
|
final String title = bundle.getString(Selection.TITLE);
|
||||||
|
|
||||||
|
needConfirm = bundle.getBoolean(Selection.NEED_CONFIRM, true);
|
||||||
|
returnNow = bundle.getBoolean(Selection.RETURN_NOW, true);
|
||||||
|
|
||||||
|
titleBar.setTitle(title, ITitleBarLayout.Position.MIDDLE);
|
||||||
|
titleBar.setOnLeftClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
titleBar.getRightIcon().setVisibility(View.GONE);
|
||||||
|
if (needConfirm) {
|
||||||
|
titleBar.getRightTitle().setText(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure));
|
||||||
|
titleBar.setOnRightClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
echoClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
titleBar.getRightGroup().setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void echoClick() {
|
||||||
|
switch (mSelectionType) {
|
||||||
|
case Selection.TYPE_TEXT:
|
||||||
|
if (sOnResultReturnListener != null) {
|
||||||
|
sOnResultReturnListener.onReturn(input.getText().toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Selection.TYPE_LIST:
|
||||||
|
if (sOnResultReturnListener != null) {
|
||||||
|
sOnResultReturnListener.onReturn(selectedItem);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (returnNow) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
sOnResultReturnListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectAdapter extends RecyclerView.Adapter<SelectAdapter.SelectViewHolder> {
|
||||||
|
int selectedItem = -1;
|
||||||
|
ArrayList<String> data = new ArrayList<>();
|
||||||
|
|
||||||
|
public void setData(ArrayList<String> data) {
|
||||||
|
this.data.clear();
|
||||||
|
this.data.addAll(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedItem(int selectedItem) {
|
||||||
|
this.selectedItem = selectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(SelectionMinimalistActivity.this).inflate(R.layout.core_select_item_layout, parent, false);
|
||||||
|
return new SelectViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SelectViewHolder holder, int position) {
|
||||||
|
String nameStr = data.get(position);
|
||||||
|
holder.name.setText(nameStr);
|
||||||
|
if (selectedItem == position) {
|
||||||
|
holder.selectedIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.selectedIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onItemClickListener.onClick(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return data.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
TextView name;
|
||||||
|
ImageView selectedIcon;
|
||||||
|
|
||||||
|
public SelectViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
name = itemView.findViewById(R.id.name);
|
||||||
|
selectedIcon = itemView.findViewById(R.id.selected_icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnResultReturnListener {
|
||||||
|
void onReturn(Object res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
void onClick(int position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Selection {
|
||||||
|
public static final String SELECT_ALL = "select_all";
|
||||||
|
public static final String CONTENT = "content";
|
||||||
|
public static final String TYPE = "type";
|
||||||
|
public static final String TITLE = "title";
|
||||||
|
public static final String INIT_CONTENT = "init_content";
|
||||||
|
public static final String DEFAULT_SELECT_ITEM_INDEX = "default_select_item_index";
|
||||||
|
public static final String LIST = "list";
|
||||||
|
public static final String LIMIT = "limit";
|
||||||
|
public static final String NEED_CONFIRM = "needConfirm";
|
||||||
|
public static final String RETURN_NOW = "returnNow";
|
||||||
|
public static final int TYPE_TEXT = 1;
|
||||||
|
public static final int TYPE_LIST = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,329 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.dialog;
|
||||||
|
|
||||||
|
import static com.tencent.qcloud.tuicore.TUIConfig.TUICORE_SETTINGS_SP_NAME;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.text.method.MovementMethod;
|
||||||
|
import android.view.Display;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuicore.util.SPUtils;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.BuildConfig;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
public class TUIKitDialog {
|
||||||
|
private Context mContext;
|
||||||
|
protected Dialog dialog;
|
||||||
|
private LinearLayout mBackgroundLayout;
|
||||||
|
private LinearLayout mMainLayout;
|
||||||
|
protected TextView mTitleTv;
|
||||||
|
private Button mCancelButton;
|
||||||
|
private Button mSureButton;
|
||||||
|
private ImageView mLineImg;
|
||||||
|
private Display mDisplay;
|
||||||
|
|
||||||
|
private boolean showTitle = false;
|
||||||
|
private boolean showPosBtn = false;
|
||||||
|
private boolean showNegBtn = false;
|
||||||
|
|
||||||
|
private float dialogWidth = 0.7f;
|
||||||
|
|
||||||
|
public TUIKitDialog(Context context) {
|
||||||
|
this.mContext = context;
|
||||||
|
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
||||||
|
mDisplay = windowManager.getDefaultDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIKitDialog builder() {
|
||||||
|
View view = LayoutInflater.from(mContext).inflate(R.layout.common_dialog_view_layout, null);
|
||||||
|
mBackgroundLayout = view.findViewById(R.id.ll_background);
|
||||||
|
mMainLayout = view.findViewById(R.id.ll_alert);
|
||||||
|
mMainLayout.setVerticalGravity(View.GONE);
|
||||||
|
mTitleTv = view.findViewById(R.id.tv_title);
|
||||||
|
mTitleTv.setVisibility(View.GONE);
|
||||||
|
mCancelButton = view.findViewById(R.id.btn_neg);
|
||||||
|
mCancelButton.setVisibility(View.GONE);
|
||||||
|
mSureButton = view.findViewById(R.id.btn_pos);
|
||||||
|
mSureButton.setVisibility(View.GONE);
|
||||||
|
mLineImg = view.findViewById(R.id.img_line);
|
||||||
|
mLineImg.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
dialog = new Dialog(mContext, R.style.TUIKit_AlertDialogStyle);
|
||||||
|
dialog.setContentView(view);
|
||||||
|
|
||||||
|
mBackgroundLayout.setLayoutParams(new FrameLayout.LayoutParams((int) (mDisplay.getWidth() * dialogWidth), LayoutParams.WRAP_CONTENT));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIKitDialog setTitle(@NonNull CharSequence title) {
|
||||||
|
showTitle = true;
|
||||||
|
mTitleTv.setText(title);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Whether to click back to cancel
|
||||||
|
* @param cancel
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public TUIKitDialog setCancelable(boolean cancel) {
|
||||||
|
dialog.setCancelable(cancel);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the setting can be canceled
|
||||||
|
*
|
||||||
|
* @param isCancelOutside
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public TUIKitDialog setCancelOutside(boolean isCancelOutside) {
|
||||||
|
dialog.setCanceledOnTouchOutside(isCancelOutside);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIKitDialog setPositiveButton(final OnClickListener listener) {
|
||||||
|
setPositiveButton(TUIConfig.getAppContext().getString(com.tencent.qcloud.tuicore.R.string.sure), listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIKitDialog setPositiveButton(CharSequence text, final OnClickListener listener) {
|
||||||
|
showPosBtn = true;
|
||||||
|
mSureButton.setText(text);
|
||||||
|
mSureButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onClick(v);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitleGravity(int gravity) {
|
||||||
|
mTitleTv.setGravity(gravity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIKitDialog setNegativeButton(CharSequence text, final OnClickListener listener) {
|
||||||
|
showNegBtn = true;
|
||||||
|
mCancelButton.setText(text);
|
||||||
|
mCancelButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
listener.onClick(v);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIKitDialog setNegativeButton(final OnClickListener listener) {
|
||||||
|
setNegativeButton(TUIConfig.getAppContext().getString(com.tencent.qcloud.tuicore.R.string.cancel), listener);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLayout() {
|
||||||
|
if (!showTitle) {
|
||||||
|
mTitleTv.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showTitle) {
|
||||||
|
mTitleTv.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showPosBtn && !showNegBtn) {
|
||||||
|
mSureButton.setVisibility(View.GONE);
|
||||||
|
mSureButton.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPosBtn && showNegBtn) {
|
||||||
|
mSureButton.setVisibility(View.VISIBLE);
|
||||||
|
mCancelButton.setVisibility(View.VISIBLE);
|
||||||
|
mLineImg.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPosBtn && !showNegBtn) {
|
||||||
|
mSureButton.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showPosBtn && showNegBtn) {
|
||||||
|
mCancelButton.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
setLayout();
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dismiss() {
|
||||||
|
if (dialog != null && dialog.isShowing()) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowing() {
|
||||||
|
return dialog != null && dialog.isShowing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param dialogWidth
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public TUIKitDialog setDialogWidth(float dialogWidth) {
|
||||||
|
if (mBackgroundLayout != null) {
|
||||||
|
mBackgroundLayout.setLayoutParams(new FrameLayout.LayoutParams((int) (mDisplay.getWidth() * dialogWidth), LayoutParams.WRAP_CONTENT));
|
||||||
|
}
|
||||||
|
this.dialogWidth = dialogWidth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TUIIMUpdateDialog {
|
||||||
|
private static final class TUIIMUpdateDialogHolder {
|
||||||
|
private static final TUIIMUpdateDialog instance = new TUIIMUpdateDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String KEY_NEVER_SHOW = "neverShow";
|
||||||
|
|
||||||
|
private boolean isNeverShow;
|
||||||
|
private boolean isShowOnlyDebug = false;
|
||||||
|
private String dialogFeatureName;
|
||||||
|
|
||||||
|
private WeakReference<TUIKitDialog> tuiKitDialog;
|
||||||
|
|
||||||
|
public static TUIIMUpdateDialog getInstance() {
|
||||||
|
return TUIIMUpdateDialogHolder.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TUIIMUpdateDialog() {
|
||||||
|
isNeverShow = SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).getBoolean(getDialogFeatureName(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog createDialog(Context context) {
|
||||||
|
tuiKitDialog = new WeakReference<>(new TUIKitDialog(context));
|
||||||
|
tuiKitDialog.get().builder();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeverShow(boolean neverShowAlert) {
|
||||||
|
this.isNeverShow = neverShowAlert;
|
||||||
|
SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).put(getDialogFeatureName(), neverShowAlert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setShowOnlyDebug(boolean isShowOnlyDebug) {
|
||||||
|
this.isShowOnlyDebug = isShowOnlyDebug;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setMovementMethod(MovementMethod movementMethod) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().mTitleTv.setMovementMethod(movementMethod);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setHighlightColor(int color) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().mTitleTv.setHighlightColor(color);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setCancelable(boolean cancelable) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().setCancelable(cancelable);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setCancelOutside(boolean cancelOutside) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().setCancelOutside(cancelOutside);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setTitle(CharSequence charSequence) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().setTitle(charSequence);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setDialogWidth(float dialogWidth) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().setDialogWidth(dialogWidth);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setPositiveButton(CharSequence text, OnClickListener clickListener) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().setPositiveButton(text, clickListener);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setNegativeButton(CharSequence text, OnClickListener clickListener) {
|
||||||
|
if (tuiKitDialog != null && tuiKitDialog.get() != null) {
|
||||||
|
tuiKitDialog.get().setNegativeButton(text, clickListener);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TUIIMUpdateDialog setDialogFeatureName(String featureName) {
|
||||||
|
this.dialogFeatureName = featureName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDialogFeatureName() {
|
||||||
|
return dialogFeatureName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show() {
|
||||||
|
if (tuiKitDialog == null || tuiKitDialog.get() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isNeverShow = SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).getBoolean(getDialogFeatureName(), false);
|
||||||
|
Dialog dialog = tuiKitDialog.get().dialog;
|
||||||
|
if (dialog == null || dialog.isShowing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNeverShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isShowOnlyDebug && !BuildConfig.DEBUG) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tuiKitDialog.get().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dismiss() {
|
||||||
|
if (tuiKitDialog == null || tuiKitDialog.get() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tuiKitDialog.get().dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.face;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.style.ImageSpan;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class CenterImageSpan extends ImageSpan {
|
||||||
|
|
||||||
|
private int bgColor = -1;
|
||||||
|
|
||||||
|
public CenterImageSpan(@NonNull Drawable drawable) {
|
||||||
|
super(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBgColor(int bgColor) {
|
||||||
|
this.bgColor = bgColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||||
|
Drawable drawable = getDrawable();
|
||||||
|
Rect rect = drawable.getBounds();
|
||||||
|
Paint.FontMetricsInt paintFm = paint.getFontMetricsInt();
|
||||||
|
int center = (paintFm.top + paintFm.bottom) / 2;
|
||||||
|
if (fm != null) {
|
||||||
|
fm.ascent = center - (rect.height() / 2);
|
||||||
|
fm.descent = center + (rect.height() / 2);
|
||||||
|
fm.top = fm.ascent;
|
||||||
|
fm.bottom = fm.descent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rect.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
|
||||||
|
if (bgColor == -1) {
|
||||||
|
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
|
||||||
|
} else {
|
||||||
|
canvas.save();
|
||||||
|
paint.setColor(bgColor);
|
||||||
|
canvas.drawRect(x, top, getDrawable().getBounds().right + x, bottom, paint);
|
||||||
|
canvas.restore();
|
||||||
|
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,546 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.face;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.text.style.ImageSpan;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.FaceGroup;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonLog;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class FaceManager {
|
||||||
|
private static final String TAG = "FaceManager";
|
||||||
|
|
||||||
|
private static final class FaceManagerHolder {
|
||||||
|
@SuppressLint("StaticFieldLeak") private static final FaceManager instance = new FaceManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int EMOJI_GROUP_ID = 0;
|
||||||
|
public static final int EMOJI_COLUMN_COUNT = 8;
|
||||||
|
public static final int EMOJI_ROW_COUNT = 3;
|
||||||
|
|
||||||
|
private final Map<String, Emoji> emojiMap = new LinkedHashMap<>();
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final Map<Integer, FaceGroup<?>> faceGroupMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private FaceManager() {
|
||||||
|
context = TIMCommonService.getAppContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FaceManager getInstance() {
|
||||||
|
return FaceManagerHolder.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayList<Emoji> getEmojiList() {
|
||||||
|
return new ArrayList<>(getInstance().emojiMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Emoji> getEmojiMap() {
|
||||||
|
return Collections.unmodifiableMap(getInstance().emojiMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getEmojiCount() {
|
||||||
|
return getInstance().emojiMap.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add a new faceGroup
|
||||||
|
* @param groupID must >= 1
|
||||||
|
* @param faceGroup the faceGroup be added
|
||||||
|
*/
|
||||||
|
public static synchronized <T extends ChatFace> void addFaceGroup(int groupID, FaceGroup<T> faceGroup) {
|
||||||
|
faceGroup.setGroupID(groupID);
|
||||||
|
getInstance().faceGroupMap.put(groupID, faceGroup);
|
||||||
|
if (faceGroup.isEmojiGroup()) {
|
||||||
|
List<T> faces = faceGroup.getFaces();
|
||||||
|
for (T face : faces) {
|
||||||
|
getInstance().emojiMap.put(face.getFaceKey(), (Emoji) face);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<FaceGroup> getFaceGroupList() {
|
||||||
|
return new ArrayList<>(getInstance().faceGroupMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Emoji loadAssetEmoji(String emojiKey, String assetFilePath, int size) {
|
||||||
|
String realPath = "file:///android_asset/" + assetFilePath;
|
||||||
|
Bitmap bitmap = loadBitmap(realPath, size, size);
|
||||||
|
if (bitmap == null) {
|
||||||
|
TIMCommonLog.e(TAG, "load bitmap failed : " + realPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Emoji emoji = new Emoji();
|
||||||
|
emoji.setIcon(bitmap);
|
||||||
|
emoji.setFaceKey(emojiKey);
|
||||||
|
return emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap loadBitmap(String resUrl, int width, int height) {
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
try {
|
||||||
|
bitmap = Glide.with(TIMCommonService.getAppContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(resUrl)
|
||||||
|
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
|
||||||
|
.submit(width, height)
|
||||||
|
.get();
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
TIMCommonLog.e(TAG, "load bitmap failed : " + e.getMessage());
|
||||||
|
}
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadFace(ChatFace chatFace, ImageView imageView) {
|
||||||
|
getInstance().internalLoadFace(chatFace, imageView, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadFace(int faceGroupID, String faceKey, ImageView view) {
|
||||||
|
getInstance().internalLoadFace(faceGroupID, faceKey, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalLoadFace(int faceGroupID, String faceKey, ImageView imageView) {
|
||||||
|
if (imageView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(faceKey)) {
|
||||||
|
Glide.with(TIMCommonService.getAppContext()).load(android.R.drawable.ic_menu_report_image).centerInside().into(imageView);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String faceUrl = "";
|
||||||
|
FaceGroup faceGroup = faceGroupMap.get(faceGroupID);
|
||||||
|
ChatFace face = null;
|
||||||
|
if (faceGroup != null) {
|
||||||
|
face = faceGroup.getFace(faceKey);
|
||||||
|
if (face != null) {
|
||||||
|
faceUrl = face.getFaceUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ChatFace finalFace = face;
|
||||||
|
Glide.with(TIMCommonService.getAppContext())
|
||||||
|
.load(faceUrl)
|
||||||
|
.centerInside()
|
||||||
|
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
|
||||||
|
.addListener(new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
if (finalFace != null && finalFace.isAutoMirrored()) {
|
||||||
|
resource.setAutoMirrored(true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalLoadFace(ChatFace chatFace, ImageView imageView, boolean isBitMap) {
|
||||||
|
if (imageView == null || chatFace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chatFace instanceof Emoji) {
|
||||||
|
Glide.with(TIMCommonService.getAppContext())
|
||||||
|
.load(((Emoji) chatFace).getIcon())
|
||||||
|
.centerInside()
|
||||||
|
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
|
||||||
|
.addListener(new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
if (chatFace.isAutoMirrored()) {
|
||||||
|
resource.setAutoMirrored(true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(imageView);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String faceUrl = "";
|
||||||
|
FaceGroup faceGroup = chatFace.getFaceGroup();
|
||||||
|
ChatFace face = null;
|
||||||
|
if (faceGroup != null) {
|
||||||
|
face = faceGroup.getFace(chatFace.getFaceKey());
|
||||||
|
if (face != null) {
|
||||||
|
faceUrl = face.getFaceUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ChatFace finalFace = face;
|
||||||
|
if (isBitMap) {
|
||||||
|
Glide.with(TIMCommonService.getAppContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(faceUrl)
|
||||||
|
.centerInside()
|
||||||
|
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
|
||||||
|
.addListener(new RequestListener<Bitmap>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
if (finalFace.isAutoMirrored()) {
|
||||||
|
imageView.setImageBitmap(resource);
|
||||||
|
imageView.getDrawable().setAutoMirrored(true);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(imageView);
|
||||||
|
} else {
|
||||||
|
Glide.with(TIMCommonService.getAppContext())
|
||||||
|
.load(faceUrl)
|
||||||
|
.centerInside()
|
||||||
|
.apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image))
|
||||||
|
.addListener(new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
if (finalFace != null && finalFace.isAutoMirrored()) {
|
||||||
|
resource.setAutoMirrored(true);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFaceChar(String faceChar) {
|
||||||
|
return getEmojiMap().get(faceChar) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean handlerEmojiText(TextView comment, CharSequence content, boolean typing) {
|
||||||
|
if (comment == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (content == null) {
|
||||||
|
comment.setText(null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Spannable spannable;
|
||||||
|
if (comment instanceof EditText && content instanceof Editable) {
|
||||||
|
spannable = (Editable) content;
|
||||||
|
ImageSpan[] imageSpans = ((Editable) content).getSpans(0, content.length(), ImageSpan.class);
|
||||||
|
for (ImageSpan span : imageSpans) {
|
||||||
|
((Editable) content).removeSpan(span);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spannable = new SpannableStringBuilder(content);
|
||||||
|
}
|
||||||
|
String regex = "\\[(\\S+?)\\]";
|
||||||
|
Pattern p = Pattern.compile(regex);
|
||||||
|
Matcher m = p.matcher(content);
|
||||||
|
boolean imageFound = false;
|
||||||
|
while (m.find()) {
|
||||||
|
String emojiName = m.group();
|
||||||
|
Emoji emoji = getEmojiMap().get(emojiName);
|
||||||
|
if (emoji != null) {
|
||||||
|
Bitmap bitmap = emoji.getIcon();
|
||||||
|
if (bitmap != null) {
|
||||||
|
imageFound = true;
|
||||||
|
|
||||||
|
BitmapDrawable bitmapDrawable = new BitmapDrawable(getInstance().context.getResources(), bitmap);
|
||||||
|
int size = getInstance().context.getResources().getDimensionPixelSize(R.dimen.common_default_emoji_size);
|
||||||
|
bitmapDrawable.setBounds(0, 0, size, size);
|
||||||
|
ImageSpan imageSpan = new CenterImageSpan(bitmapDrawable);
|
||||||
|
spannable.setSpan(imageSpan, m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no emoticon picture is found, and it is currently in the input state, the input box will not be reset.
|
||||||
|
if (!imageFound && typing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int selection = comment.getSelectionStart();
|
||||||
|
if (!(comment instanceof EditText)) {
|
||||||
|
comment.setText(spannable);
|
||||||
|
}
|
||||||
|
if (comment instanceof EditText) {
|
||||||
|
((EditText) comment).setSelection(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap getEmoji(String name) {
|
||||||
|
Emoji emoji = getEmojiMap().get(name);
|
||||||
|
if (emoji != null) {
|
||||||
|
return emoji.getIcon();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CharSequence emojiJudge(CharSequence text) {
|
||||||
|
if (TextUtils.isEmpty(text)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getEmojiCount() == 0) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||||
|
String regex = "\\[(\\S+?)\\]";
|
||||||
|
Pattern p = Pattern.compile(regex);
|
||||||
|
Matcher m = p.matcher(text);
|
||||||
|
ArrayList<EmojiData> emojiDataArrayList = new ArrayList<>();
|
||||||
|
|
||||||
|
// Traverse to find matching characters and store
|
||||||
|
while (m.find()) {
|
||||||
|
String emojiKey = m.group();
|
||||||
|
int start = m.start();
|
||||||
|
int end = m.end();
|
||||||
|
|
||||||
|
Emoji emoji = getEmojiMap().get(emojiKey);
|
||||||
|
if (emoji == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EmojiData emojiData = new EmojiData();
|
||||||
|
emojiData.setStart(start);
|
||||||
|
emojiData.setEnd(end);
|
||||||
|
emojiData.setEmojiText(emoji.getFaceName());
|
||||||
|
|
||||||
|
emojiDataArrayList.add(emojiData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flashback replacement
|
||||||
|
if (emojiDataArrayList.isEmpty()) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
for (int i = emojiDataArrayList.size() - 1; i >= 0; i--) {
|
||||||
|
EmojiData emojiData = emojiDataArrayList.get(i);
|
||||||
|
String emojiName = emojiData.getEmojiText();
|
||||||
|
int start = emojiData.getStart();
|
||||||
|
int end = emojiData.getEnd();
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(emojiName) && start != -1 && end != -1) {
|
||||||
|
sb.replace(start, end, emojiName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> splitEmojiText(String text) {
|
||||||
|
String regex = "\\[(\\S+?)\\]";
|
||||||
|
Pattern p = Pattern.compile(regex);
|
||||||
|
Matcher m = p.matcher(text);
|
||||||
|
ArrayList<EmojiData> emojiDataList = new ArrayList<>();
|
||||||
|
int lastMentionIndex = -1;
|
||||||
|
while (m.find()) {
|
||||||
|
String emojiKey = m.group();
|
||||||
|
int start;
|
||||||
|
if (lastMentionIndex != -1) {
|
||||||
|
start = text.indexOf(emojiKey, lastMentionIndex);
|
||||||
|
} else {
|
||||||
|
start = text.indexOf(emojiKey);
|
||||||
|
}
|
||||||
|
int end = start + emojiKey.length();
|
||||||
|
lastMentionIndex = end;
|
||||||
|
|
||||||
|
Emoji emoji = getEmojiMap().get(emojiKey);
|
||||||
|
if (emoji == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EmojiData emojiData = new EmojiData();
|
||||||
|
emojiData.setStart(start);
|
||||||
|
emojiData.setEnd(end);
|
||||||
|
emojiDataList.add(emojiData);
|
||||||
|
}
|
||||||
|
List<String> stringList = new ArrayList<>();
|
||||||
|
int offset = 0;
|
||||||
|
for (EmojiData emojiData : emojiDataList) {
|
||||||
|
int start = emojiData.getStart() - offset;
|
||||||
|
int end = emojiData.getEnd() - offset;
|
||||||
|
String startStr = text.substring(0, start);
|
||||||
|
String middleStr = text.substring(start, end);
|
||||||
|
text = text.substring(end);
|
||||||
|
if (!TextUtils.isEmpty(startStr)) {
|
||||||
|
stringList.add(startStr);
|
||||||
|
}
|
||||||
|
stringList.add(middleStr);
|
||||||
|
offset += startStr.length() + middleStr.length();
|
||||||
|
}
|
||||||
|
if (!TextUtils.isEmpty(text)) {
|
||||||
|
stringList.add(text);
|
||||||
|
}
|
||||||
|
return stringList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> findEmojiKeyListFromText(String text) {
|
||||||
|
if (TextUtils.isEmpty(text)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
List<String> emojiKeyList = new ArrayList<>();
|
||||||
|
// TUIKit custom emoji.
|
||||||
|
String regexOfCustomEmoji = "\\[(\\S+?)\\]";
|
||||||
|
Pattern patternOfCustomEmoji = Pattern.compile(regexOfCustomEmoji);
|
||||||
|
Matcher matcherOfCustomEmoji = patternOfCustomEmoji.matcher(text);
|
||||||
|
while (matcherOfCustomEmoji.find()) {
|
||||||
|
String emojiName = matcherOfCustomEmoji.group();
|
||||||
|
Emoji emoji = getEmojiMap().get(emojiName);
|
||||||
|
if (emoji != null) {
|
||||||
|
Bitmap bitmap = emoji.getIcon();
|
||||||
|
if (bitmap != null) {
|
||||||
|
emojiKeyList.add(emojiName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Universal standard emoji.
|
||||||
|
String regexOfUniversalEmoji = getRegexOfUniversalEmoji();
|
||||||
|
Pattern patternOfUniversalEmoji = Pattern.compile(regexOfUniversalEmoji);
|
||||||
|
Matcher matcherOfUniversalEmoji = patternOfUniversalEmoji.matcher(text);
|
||||||
|
while (matcherOfUniversalEmoji.find()) {
|
||||||
|
String emojiKey = matcherOfUniversalEmoji.group();
|
||||||
|
if (!TextUtils.isEmpty(emojiKey)) {
|
||||||
|
emojiKeyList.add(matcherOfUniversalEmoji.group());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emojiKeyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class EmojiData {
|
||||||
|
private int start;
|
||||||
|
private int end;
|
||||||
|
private String emojiText;
|
||||||
|
|
||||||
|
public int getEnd() {
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnd(int end) {
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStart(int start) {
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmojiText() {
|
||||||
|
return emojiText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmojiText(String emojiText) {
|
||||||
|
this.emojiText = emojiText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex of universal emoji, refer to https://unicode.org/reports/tr51/#EBNF_and_Regex
|
||||||
|
private static String getRegexOfUniversalEmoji() {
|
||||||
|
String ri = "[\\U0001F1E6-\\U0001F1FF]";
|
||||||
|
// \u0023(#), \u002A(*), \u0030(keycap 0), \u0039(keycap 9), \u00A9(©), \u00AE(®) couldn't be added to NSString directly, need to transform a little
|
||||||
|
// bit.
|
||||||
|
String support = "\\U000000A9|\\U000000AE|\\u203C|\\u2049|\\u2122|\\u2139|[\\u2194-\\u2199]|[\\u21A9-\\u21AA]"
|
||||||
|
+ "|[\\u231A-\\u231B]|\\u2328|\\u23CF|[\\u23E9-\\u23EF]|[\\u23F0-\\u23F3]|[\\u23F8-\\u23FA]|\\u24C2"
|
||||||
|
+ "|[\\u25AA-\\u25AB]|\\u25B6|\\u25C0|[\\u25FB-\\u25FE]|[\\u2600-\\u2604]|\\u260E|\\u2611|[\\u2614-\\u2615]"
|
||||||
|
+ "|\\u2618|\\u261D|\\u2620|[\\u2622-\\u2623]|\\u2626|\\u262A|[\\u262E-\\u262F]|[\\u2638-\\u263A]|\\u2640"
|
||||||
|
+ "|\\u2642|[\\u2648-\\u264F]|[\\u2650-\\u2653]|\\u265F|\\u2660|\\u2663|[\\u2665-\\u2666]|\\u2668|\\u267B"
|
||||||
|
+ "|[\\u267E-\\u267F]|[\\u2692-\\u2697]|\\u2699|[\\u269B-\\u269C]|[\\u26A0-\\u26A1]|\\u26A7|[\\u26AA-\\u26AB]"
|
||||||
|
+ "|[\\u26B0-\\u26B1]|[\\u26BD-\\u26BE]|[\\u26C4-\\u26C5]|\\u26C8|[\\u26CE-\\u26CF]|\\u26D1|[\\u26D3-\\u26D4]"
|
||||||
|
+ "|[\\u26E9-\\u26EA]|[\\u26F0-\\u26F5]|[\\u26F7-\\u26FA]|\\u26FD|\\u2702|\\u2705|[\\u2708-\\u270D]|\\u270F|\\u2712"
|
||||||
|
+ "|\\u2714|\\u2716|\\u271D|\\u2721|\\u2728|[\\u2733-\\u2734]|\\u2744|\\u2747|\\u274C|\\u274E|[\\u2753-\\u2755]"
|
||||||
|
+ "|\\u2757|[\\u2763-\\u2764]|[\\u2795-\\u2797]|\\u27A1|\\u27B0|\\u27BF|[\\u2934-\\u2935]|[\\u2B05-\\u2B07]"
|
||||||
|
+ "|[\\u2B1B-\\u2B1C]|\\u2B50|\\u2B55|\\u3030|\\u303D|\\u3297|\\u3299|\\U0001F004|\\U0001F0CF|[\\U0001F170-\\U0001F171]"
|
||||||
|
+ "|[\\U0001F17E-\\U0001F17F]|\\U0001F18E|[\\U0001F191-\\U0001F19A]|[\\U0001F1E6-\\U0001F1FF]|[\\U0001F201-\\U0001F202]"
|
||||||
|
+ "|\\U0001F21A|\\U0001F22F|[\\U0001F232-\\U0001F23A]|[\\U0001F250-\\U0001F251]|[\\U0001F300-\\U0001F30F]"
|
||||||
|
+ "|[\\U0001F310-\\U0001F31F]|[\\U0001F320-\\U0001F321]|[\\U0001F324-\\U0001F32F]|[\\U0001F330-\\U0001F33F]"
|
||||||
|
+ "|[\\U0001F340-\\U0001F34F]|[\\U0001F350-\\U0001F35F]|[\\U0001F360-\\U0001F36F]|[\\U0001F370-\\U0001F37F]"
|
||||||
|
+ "|[\\U0001F380-\\U0001F38F]|[\\U0001F390-\\U0001F393]|[\\U0001F396-\\U0001F397]|[\\U0001F399-\\U0001F39B]"
|
||||||
|
+ "|[\\U0001F39E-\\U0001F39F]|[\\U0001F3A0-\\U0001F3AF]|[\\U0001F3B0-\\U0001F3BF]|[\\U0001F3C0-\\U0001F3CF]"
|
||||||
|
+ "|[\\U0001F3D0-\\U0001F3DF]|[\\U0001F3E0-\\U0001F3EF]|\\U0001F3F0|[\\U0001F3F3-\\U0001F3F5]|[\\U0001F3F7-\\U0001F3FF]"
|
||||||
|
+ "|[\\U0001F400-\\U0001F40F]|[\\U0001F410-\\U0001F41F]|[\\U0001F420-\\U0001F42F]|[\\U0001F430-\\U0001F43F]"
|
||||||
|
+ "|[\\U0001F440-\\U0001F44F]|[\\U0001F450-\\U0001F45F]|[\\U0001F460-\\U0001F46F]|[\\U0001F470-\\U0001F47F]"
|
||||||
|
+ "|[\\U0001F480-\\U0001F48F]|[\\U0001F490-\\U0001F49F]|[\\U0001F4A0-\\U0001F4AF]|[\\U0001F4B0-\\U0001F4BF]"
|
||||||
|
+ "|[\\U0001F4C0-\\U0001F4CF]|[\\U0001F4D0-\\U0001F4DF]|[\\U0001F4E0-\\U0001F4EF]|[\\U0001F4F0-\\U0001F4FF]"
|
||||||
|
+ "|[\\U0001F500-\\U0001F50F]|[\\U0001F510-\\U0001F51F]|[\\U0001F520-\\U0001F52F]|[\\U0001F530-\\U0001F53D]"
|
||||||
|
+ "|[\\U0001F549-\\U0001F54E]|[\\U0001F550-\\U0001F55F]|[\\U0001F560-\\U0001F567]|\\U0001F56F|\\U0001F570"
|
||||||
|
+ "|[\\U0001F573-\\U0001F57A]|\\U0001F587|[\\U0001F58A-\\U0001F58D]|\\U0001F590|[\\U0001F595-\\U0001F596]"
|
||||||
|
+ "|[\\U0001F5A4-\\U0001F5A5]|\\U0001F5A8|[\\U0001F5B1-\\U0001F5B2]|\\U0001F5BC|[\\U0001F5C2-\\U0001F5C4]"
|
||||||
|
+ "|[\\U0001F5D1-\\U0001F5D3]|[\\U0001F5DC-\\U0001F5DE]|\\U0001F5E1|\\U0001F5E3|\\U0001F5E8|\\U0001F5EF|\\U0001F5F3"
|
||||||
|
+ "|[\\U0001F5FA-\\U0001F5FF]|[\\U0001F600-\\U0001F60F]|[\\U0001F610-\\U0001F61F]|[\\U0001F620-\\U0001F62F]"
|
||||||
|
+ "|[\\U0001F630-\\U0001F63F]|[\\U0001F640-\\U0001F64F]|[\\U0001F650-\\U0001F65F]|[\\U0001F660-\\U0001F66F]"
|
||||||
|
+ "|[\\U0001F670-\\U0001F67F]|[\\U0001F680-\\U0001F68F]|[\\U0001F690-\\U0001F69F]|[\\U0001F6A0-\\U0001F6AF]"
|
||||||
|
+ "|[\\U0001F6B0-\\U0001F6BF]|[\\U0001F6C0-\\U0001F6C5]|[\\U0001F6CB-\\U0001F6CF]|[\\U0001F6D0-\\U0001F6D2]"
|
||||||
|
+ "|[\\U0001F6D5-\\U0001F6D7]|[\\U0001F6DD-\\U0001F6DF]|[\\U0001F6E0-\\U0001F6E5]|\\U0001F6E9|[\\U0001F6EB-\\U0001F6EC]"
|
||||||
|
+ "|\\U0001F6F0|[\\U0001F6F3-\\U0001F6FC]|[\\U0001F7E0-\\U0001F7EB]|\\U0001F7F0|[\\U0001F90C-\\U0001F90F]"
|
||||||
|
+ "|[\\U0001F910-\\U0001F91F]|[\\U0001F920-\\U0001F92F]|[\\U0001F930-\\U0001F93A]|[\\U0001F93C-\\U0001F93F]"
|
||||||
|
+ "|[\\U0001F940-\\U0001F945]|[\\U0001F947-\\U0001F94C]|[\\U0001F94D-\\U0001F94F]|[\\U0001F950-\\U0001F95F]"
|
||||||
|
+ "|[\\U0001F960-\\U0001F96F]|[\\U0001F970-\\U0001F97F]|[\\U0001F980-\\U0001F98F]|[\\U0001F990-\\U0001F99F]"
|
||||||
|
+ "|[\\U0001F9A0-\\U0001F9AF]|[\\U0001F9B0-\\U0001F9BF]|[\\U0001F9C0-\\U0001F9CF]|[\\U0001F9D0-\\U0001F9DF]"
|
||||||
|
+ "|[\\U0001F9E0-\\U0001F9EF]|[\\U0001F9F0-\\U0001F9FF]|[\\U0001FA70-\\U0001FA74]|[\\U0001FA78-\\U0001FA7C]"
|
||||||
|
+ "|[\\U0001FA80-\\U0001FA86]|[\\U0001FA90-\\U0001FA9F]|[\\U0001FAA0-\\U0001FAAC]|[\\U0001FAB0-\\U0001FABA]"
|
||||||
|
+ "|[\\U0001FAC0-\\U0001FAC5]|[\\U0001FAD0-\\U0001FAD9]|[\\U0001FAE0-\\U0001FAE7]|[\\U0001FAF0-\\U0001FAF6]";
|
||||||
|
String unsupport = "\\u0023|\\u002A|[\\u0030-\\u0039]|";
|
||||||
|
String emoji = unsupport + support;
|
||||||
|
|
||||||
|
// Construct regex of emoji by the rules above.
|
||||||
|
String eMod = "[\\U0001F3FB-\\U0001F3FF]";
|
||||||
|
|
||||||
|
String variationSelector = "\\uFE0F";
|
||||||
|
String keycap = "\\u20E3";
|
||||||
|
String tags = "[\\U000E0020-\\U000E007E]";
|
||||||
|
String termTag = "\\U000E007F";
|
||||||
|
String zwj = "\\u200D";
|
||||||
|
|
||||||
|
String risequence = "[" + ri + "]"
|
||||||
|
+ "[" + ri + "]";
|
||||||
|
String element = "[" + emoji + "]"
|
||||||
|
+ "("
|
||||||
|
+ "[" + eMod + "]|" + variationSelector + keycap + "?|[" + tags + "]+" + termTag + "?)?";
|
||||||
|
String regexEmoji = risequence + "|" + element + "(" + zwj + "(" + risequence + "|" + element + "))*";
|
||||||
|
|
||||||
|
return regexEmoji;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.face;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Base64;
|
||||||
|
import com.tencent.qcloud.tuicore.util.SPUtils;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.Emoji;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RecentEmojiManager {
|
||||||
|
public static final String PREFERENCE_NAME = "recentFace";
|
||||||
|
public static final int DEFAULT_RECENT_NUM = 10;
|
||||||
|
private static final String DEFAULT_RECENT_EMOJI_KEY = "recentEmoji";
|
||||||
|
|
||||||
|
private static final RecentEmojiManager instance = new RecentEmojiManager();
|
||||||
|
|
||||||
|
private RecentEmojiManager() {}
|
||||||
|
|
||||||
|
public static RecentEmojiManager getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String key) {
|
||||||
|
return SPUtils.getInstance(PREFERENCE_NAME).getString(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecentEmojiManager putString(String key, String value) {
|
||||||
|
SPUtils.getInstance(PREFERENCE_NAME).put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putCollection(List<String> emojiList) {
|
||||||
|
getInstance().putCollection(DEFAULT_RECENT_EMOJI_KEY, emojiList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecentEmojiManager putCollection(String key, List<String> emojiList) {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
|
||||||
|
objectOutputStream.writeObject(emojiList);
|
||||||
|
String collectionString = new String(Base64.encode(byteArrayOutputStream.toByteArray(), Base64.DEFAULT));
|
||||||
|
objectOutputStream.close();
|
||||||
|
return putString(key, collectionString);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCollection(String key) {
|
||||||
|
try {
|
||||||
|
String collectionString = getString(key);
|
||||||
|
if (TextUtils.isEmpty(collectionString) || TextUtils.isEmpty(collectionString.trim())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
byte[] mobileBytes = Base64.decode(collectionString.getBytes(), Base64.DEFAULT);
|
||||||
|
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(mobileBytes);
|
||||||
|
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
|
||||||
|
Object collectionObj = objectInputStream.readObject();
|
||||||
|
List<String> collection = null;
|
||||||
|
if (collectionObj instanceof List) {
|
||||||
|
collection = (List<String>) collectionObj;
|
||||||
|
}
|
||||||
|
return collection;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getCollection() {
|
||||||
|
return getInstance().getCollection(DEFAULT_RECENT_EMOJI_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateRecentUseEmoji(String emojiKey) {
|
||||||
|
List<String> recentList = getCollection();
|
||||||
|
recentList.remove(emojiKey);
|
||||||
|
recentList.add(0, emojiKey);
|
||||||
|
if (recentList.size() > DEFAULT_RECENT_NUM) {
|
||||||
|
recentList.remove(recentList.size() - 1);
|
||||||
|
}
|
||||||
|
putCollection(recentList);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiple image data
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MultiImageData implements Cloneable {
|
||||||
|
static final int maxSize = 9;
|
||||||
|
List<Object> imageUrls;
|
||||||
|
int defaultImageResId;
|
||||||
|
Map<Integer, Bitmap> bitmapMap;
|
||||||
|
int bgColor = Color.parseColor("#cfd3d8");
|
||||||
|
|
||||||
|
int targetImageSize;
|
||||||
|
int maxWidth;
|
||||||
|
int maxHeight;
|
||||||
|
int rowCount;
|
||||||
|
int columnCount;
|
||||||
|
int gap = 6;
|
||||||
|
|
||||||
|
public MultiImageData() {}
|
||||||
|
|
||||||
|
public MultiImageData(int defaultImageResId) {
|
||||||
|
this.defaultImageResId = defaultImageResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiImageData(List<Object> imageUrls, int defaultImageResId) {
|
||||||
|
this.imageUrls = imageUrls;
|
||||||
|
this.defaultImageResId = defaultImageResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultImageResId() {
|
||||||
|
return defaultImageResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultImageResId(int defaultImageResId) {
|
||||||
|
this.defaultImageResId = defaultImageResId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Object> getImageUrls() {
|
||||||
|
return imageUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUrls(List<Object> imageUrls) {
|
||||||
|
this.imageUrls = imageUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putBitmap(Bitmap bitmap, int position) {
|
||||||
|
if (null != bitmapMap) {
|
||||||
|
synchronized (bitmapMap) {
|
||||||
|
bitmapMap.put(position, bitmap);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bitmapMap = new HashMap<>();
|
||||||
|
synchronized (bitmapMap) {
|
||||||
|
bitmapMap.put(position, bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getBitmap(int position) {
|
||||||
|
if (null != bitmapMap) {
|
||||||
|
synchronized (bitmapMap) {
|
||||||
|
return bitmapMap.get(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
if (null != imageUrls) {
|
||||||
|
return imageUrls.size() > maxSize ? maxSize : imageUrls.size();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MultiImageData clone() throws CloneNotSupportedException {
|
||||||
|
MultiImageData multiImageData = (MultiImageData) super.clone();
|
||||||
|
if (imageUrls != null) {
|
||||||
|
multiImageData.imageUrls = new ArrayList<>(imageUrls.size());
|
||||||
|
multiImageData.imageUrls.addAll(imageUrls);
|
||||||
|
}
|
||||||
|
if (bitmapMap != null) {
|
||||||
|
multiImageData.bitmapMap = new HashMap<>();
|
||||||
|
multiImageData.bitmapMap.putAll(bitmapMap);
|
||||||
|
}
|
||||||
|
return multiImageData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffXfermode;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
|
||||||
|
@SuppressLint("AppCompatCustomView")
|
||||||
|
public class ShadeImageView extends ImageView {
|
||||||
|
private static SparseArray<Bitmap> sRoundBitmapArray = new SparseArray();
|
||||||
|
private Paint mShadePaint = new Paint();
|
||||||
|
private Bitmap mRoundBitmap;
|
||||||
|
private int radius;
|
||||||
|
|
||||||
|
public ShadeImageView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShadeImageView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShadeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs) {
|
||||||
|
radius = (int) ScreenUtil.dp2px(4.0f, getResources().getDisplayMetrics());
|
||||||
|
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.core_round_rect_image_style);
|
||||||
|
radius = array.getDimensionPixelSize(R.styleable.core_round_rect_image_style_round_radius, radius);
|
||||||
|
array.recycle();
|
||||||
|
setLayerType(LAYER_TYPE_HARDWARE, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
mShadePaint.setColor(Color.RED);
|
||||||
|
mShadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
|
||||||
|
mRoundBitmap = sRoundBitmapArray.get(getMeasuredWidth() + radius);
|
||||||
|
if (mRoundBitmap == null) {
|
||||||
|
mRoundBitmap = getRoundBitmap();
|
||||||
|
sRoundBitmapArray.put(getMeasuredWidth() + radius, mRoundBitmap);
|
||||||
|
}
|
||||||
|
canvas.drawBitmap(mRoundBitmap, 0, 0, mShadePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get rounded rectangle
|
||||||
|
*
|
||||||
|
* @return Bitmap
|
||||||
|
*/
|
||||||
|
private Bitmap getRoundBitmap() {
|
||||||
|
Bitmap output = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(output);
|
||||||
|
final int color = Color.parseColor("#cfd3d8");
|
||||||
|
final Rect rect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
||||||
|
final RectF rectF = new RectF(rect);
|
||||||
|
Paint paint = new Paint();
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
canvas.drawARGB(0, 0, 0, 0);
|
||||||
|
paint.setColor(color);
|
||||||
|
canvas.drawRoundRect(rectF, radius, radius, paint);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRadius() {
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRadius(int radius) {
|
||||||
|
this.radius = radius;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SynthesizedImageView extends ShadeImageView {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Group Chat Avatar Synthesizer
|
||||||
|
*/
|
||||||
|
TeamHeadSynthesizer teamHeadSynthesizer;
|
||||||
|
int imageSize = 100;
|
||||||
|
int synthesizedBg = Color.parseColor("#cfd3d8");
|
||||||
|
int defaultImageResId = 0;
|
||||||
|
int imageGap = 6;
|
||||||
|
|
||||||
|
public SynthesizedImageView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynthesizedImageView(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initAttrs(attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynthesizedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initAttrs(attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAttrs(AttributeSet attributeSet) {
|
||||||
|
TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.SynthesizedImageView);
|
||||||
|
if (null != ta) {
|
||||||
|
synthesizedBg = ta.getColor(R.styleable.SynthesizedImageView_synthesized_image_bg, synthesizedBg);
|
||||||
|
defaultImageResId = ta.getResourceId(R.styleable.SynthesizedImageView_synthesized_default_image, defaultImageResId);
|
||||||
|
imageSize = ta.getDimensionPixelSize(R.styleable.SynthesizedImageView_synthesized_image_size, imageSize);
|
||||||
|
imageGap = ta.getDimensionPixelSize(R.styleable.SynthesizedImageView_synthesized_image_gap, imageGap);
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context) {
|
||||||
|
teamHeadSynthesizer = new TeamHeadSynthesizer(context, this);
|
||||||
|
teamHeadSynthesizer.setMaxWidthHeight(imageSize, imageSize);
|
||||||
|
teamHeadSynthesizer.setDefaultImage(defaultImageResId);
|
||||||
|
teamHeadSynthesizer.setBgColor(synthesizedBg);
|
||||||
|
teamHeadSynthesizer.setGap(imageGap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynthesizedImageView displayImage(List<Object> imageUrls) {
|
||||||
|
teamHeadSynthesizer.getMultiImageData().setImageUrls(imageUrls);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynthesizedImageView defaultImage(int defaultImage) {
|
||||||
|
teamHeadSynthesizer.setDefaultImage(defaultImage);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SynthesizedImageView synthesizedWidthHeight(int maxWidth, int maxHeight) {
|
||||||
|
teamHeadSynthesizer.setMaxWidthHeight(maxWidth, maxHeight);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageId(String id) {
|
||||||
|
teamHeadSynthesizer.setImageId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(String imageId) {
|
||||||
|
teamHeadSynthesizer.load(imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
teamHeadSynthesizer.clearImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
|
||||||
|
public interface Synthesizer {
|
||||||
|
Bitmap synthesizeImageList(MultiImageData imageData);
|
||||||
|
|
||||||
|
boolean asyncLoadImageList(MultiImageData imageData);
|
||||||
|
|
||||||
|
void drawDrawable(Canvas canvas, MultiImageData imageData);
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.TIMCommonConfig;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.impl.GlideEngine;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ImageUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
public class TeamHeadSynthesizer implements Synthesizer {
|
||||||
|
MultiImageData multiImageData;
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
ImageView imageView;
|
||||||
|
|
||||||
|
// It is safe to set and get only in the main thread
|
||||||
|
private String currentImageId = "";
|
||||||
|
Callback callback = new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onCall(Bitmap bitmap, String targetID) {
|
||||||
|
if (!TextUtils.equals(getImageId(), targetID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GlideEngine.loadUserIcon(imageView, bitmap);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public TeamHeadSynthesizer(Context mContext, ImageView imageView) {
|
||||||
|
this.mContext = mContext;
|
||||||
|
this.imageView = imageView;
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
multiImageData = new MultiImageData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxWidthHeight(int maxWidth, int maxHeight) {
|
||||||
|
multiImageData.maxWidth = maxWidth;
|
||||||
|
multiImageData.maxHeight = maxHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiImageData getMultiImageData() {
|
||||||
|
return multiImageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDefaultImage() {
|
||||||
|
return multiImageData.getDefaultImageResId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultImage(int defaultImageResId) {
|
||||||
|
multiImageData.setDefaultImageResId(defaultImageResId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBgColor(int bgColor) {
|
||||||
|
multiImageData.bgColor = bgColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGap(int gap) {
|
||||||
|
multiImageData.gap = gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Grid params
|
||||||
|
*
|
||||||
|
* @param imagesSize Number of pictures
|
||||||
|
* @return gridParam[0] Rows gridParam[1] columns
|
||||||
|
*/
|
||||||
|
protected int[] calculateGridParam(int imagesSize) {
|
||||||
|
int[] gridParam = new int[2];
|
||||||
|
if (imagesSize < 3) {
|
||||||
|
gridParam[0] = 1;
|
||||||
|
gridParam[1] = imagesSize;
|
||||||
|
} else if (imagesSize <= 4) {
|
||||||
|
gridParam[0] = 2;
|
||||||
|
gridParam[1] = 2;
|
||||||
|
} else {
|
||||||
|
gridParam[0] = imagesSize / 3 + (imagesSize % 3 == 0 ? 0 : 1);
|
||||||
|
gridParam[1] = 3;
|
||||||
|
}
|
||||||
|
return gridParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bitmap synthesizeImageList(MultiImageData imageData) {
|
||||||
|
Bitmap mergeBitmap = Bitmap.createBitmap(imageData.maxWidth, imageData.maxHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(mergeBitmap);
|
||||||
|
drawDrawable(canvas, imageData);
|
||||||
|
canvas.save();
|
||||||
|
canvas.restore();
|
||||||
|
return mergeBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean asyncLoadImageList(MultiImageData imageData) {
|
||||||
|
boolean loadSuccess = true;
|
||||||
|
List<Object> imageUrls = imageData.getImageUrls();
|
||||||
|
for (int i = 0; i < imageUrls.size(); i++) {
|
||||||
|
Bitmap defaultIcon = BitmapFactory.decodeResource(mContext.getResources(), TIMCommonConfig.getDefaultAvatarImage());
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = asyncLoadImage(imageUrls.get(i), imageData.targetImageSize);
|
||||||
|
imageData.putBitmap(bitmap, i);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
imageData.putBitmap(defaultIcon, i);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
imageData.putBitmap(defaultIcon, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loadSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawDrawable(Canvas canvas, MultiImageData imageData) {
|
||||||
|
canvas.drawColor(imageData.bgColor);
|
||||||
|
int size = imageData.size();
|
||||||
|
int tCenter = (imageData.maxHeight + imageData.gap) / 2;
|
||||||
|
int bCenter = (imageData.maxHeight - imageData.gap) / 2;
|
||||||
|
int lCenter = (imageData.maxWidth + imageData.gap) / 2;
|
||||||
|
int rCenter = (imageData.maxWidth - imageData.gap) / 2;
|
||||||
|
int center = (imageData.maxHeight - imageData.targetImageSize) / 2;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int rowNum = i / imageData.columnCount;
|
||||||
|
int columnNum = i % imageData.columnCount;
|
||||||
|
|
||||||
|
int left = ((int) (imageData.targetImageSize * (imageData.columnCount == 1 ? columnNum + 0.5 : columnNum) + imageData.gap * (columnNum + 1)));
|
||||||
|
int top = ((int) (imageData.targetImageSize * (imageData.columnCount == 1 ? rowNum + 0.5 : rowNum) + imageData.gap * (rowNum + 1)));
|
||||||
|
int right = left + imageData.targetImageSize;
|
||||||
|
int bottom = top + imageData.targetImageSize;
|
||||||
|
|
||||||
|
Bitmap bitmap = imageData.getBitmap(i);
|
||||||
|
if (size == 1) {
|
||||||
|
drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap);
|
||||||
|
} else if (size == 2) {
|
||||||
|
drawBitmapAtPosition(canvas, left, center, right, center + imageData.targetImageSize, bitmap);
|
||||||
|
} else if (size == 3) {
|
||||||
|
if (i == 0) {
|
||||||
|
drawBitmapAtPosition(canvas, center, top, center + imageData.targetImageSize, bottom, bitmap);
|
||||||
|
} else {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * i + imageData.targetImageSize * (i - 1), tCenter,
|
||||||
|
imageData.gap * i + imageData.targetImageSize * i, tCenter + imageData.targetImageSize, bitmap);
|
||||||
|
}
|
||||||
|
} else if (size == 4) {
|
||||||
|
drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap);
|
||||||
|
} else if (size == 5) {
|
||||||
|
if (i == 0) {
|
||||||
|
drawBitmapAtPosition(canvas, rCenter - imageData.targetImageSize, rCenter - imageData.targetImageSize, rCenter, rCenter, bitmap);
|
||||||
|
} else if (i == 1) {
|
||||||
|
drawBitmapAtPosition(canvas, lCenter, rCenter - imageData.targetImageSize, lCenter + imageData.targetImageSize, rCenter, bitmap);
|
||||||
|
} else {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 2), tCenter,
|
||||||
|
imageData.gap * (i - 1) + imageData.targetImageSize * (i - 1), tCenter + imageData.targetImageSize, bitmap);
|
||||||
|
}
|
||||||
|
} else if (size == 6) {
|
||||||
|
if (i < 3) {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * (i + 1) + imageData.targetImageSize * i, bCenter - imageData.targetImageSize,
|
||||||
|
imageData.gap * (i + 1) + imageData.targetImageSize * (i + 1), bCenter, bitmap);
|
||||||
|
} else {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * (i - 2) + imageData.targetImageSize * (i - 3), tCenter,
|
||||||
|
imageData.gap * (i - 2) + imageData.targetImageSize * (i - 2), tCenter + imageData.targetImageSize, bitmap);
|
||||||
|
}
|
||||||
|
} else if (size == 7) {
|
||||||
|
if (i == 0) {
|
||||||
|
drawBitmapAtPosition(canvas, center, imageData.gap, center + imageData.targetImageSize, imageData.gap + imageData.targetImageSize, bitmap);
|
||||||
|
} else if (i > 0 && i < 4) {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * i + imageData.targetImageSize * (i - 1), center,
|
||||||
|
imageData.gap * i + imageData.targetImageSize * i, center + imageData.targetImageSize, bitmap);
|
||||||
|
} else {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * (i - 3) + imageData.targetImageSize * (i - 4), tCenter + imageData.targetImageSize / 2,
|
||||||
|
imageData.gap * (i - 3) + imageData.targetImageSize * (i - 3), tCenter + imageData.targetImageSize / 2 + imageData.targetImageSize,
|
||||||
|
bitmap);
|
||||||
|
}
|
||||||
|
} else if (size == 8) {
|
||||||
|
if (i == 0) {
|
||||||
|
drawBitmapAtPosition(
|
||||||
|
canvas, rCenter - imageData.targetImageSize, imageData.gap, rCenter, imageData.gap + imageData.targetImageSize, bitmap);
|
||||||
|
} else if (i == 1) {
|
||||||
|
drawBitmapAtPosition(
|
||||||
|
canvas, lCenter, imageData.gap, lCenter + imageData.targetImageSize, imageData.gap + imageData.targetImageSize, bitmap);
|
||||||
|
} else if (i > 1 && i < 5) {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 2), center,
|
||||||
|
imageData.gap * (i - 1) + imageData.targetImageSize * (i - 1), center + imageData.targetImageSize, bitmap);
|
||||||
|
} else {
|
||||||
|
drawBitmapAtPosition(canvas, imageData.gap * (i - 4) + imageData.targetImageSize * (i - 5), tCenter + imageData.targetImageSize / 2,
|
||||||
|
imageData.gap * (i - 4) + imageData.targetImageSize * (i - 4), tCenter + imageData.targetImageSize / 2 + imageData.targetImageSize,
|
||||||
|
bitmap);
|
||||||
|
}
|
||||||
|
} else if (size == 9) {
|
||||||
|
drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DrawBitmap
|
||||||
|
*
|
||||||
|
* @param canvas
|
||||||
|
* @param left
|
||||||
|
* @param top
|
||||||
|
* @param right
|
||||||
|
* @param bottom
|
||||||
|
* @param bitmap
|
||||||
|
*/
|
||||||
|
public void drawBitmapAtPosition(Canvas canvas, int left, int top, int right, int bottom, Bitmap bitmap) {
|
||||||
|
if (null == bitmap) {
|
||||||
|
if (multiImageData.getDefaultImageResId() > 0) {
|
||||||
|
bitmap = BitmapFactory.decodeResource(mContext.getResources(), multiImageData.getDefaultImageResId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (null != bitmap) {
|
||||||
|
Rect rect = new Rect(left, top, right, bottom);
|
||||||
|
canvas.drawBitmap(bitmap, null, rect, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap asyncLoadImage(Object imageUrl, int targetImageSize) throws ExecutionException, InterruptedException {
|
||||||
|
return GlideEngine.loadBitmap(imageUrl, targetImageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageId(String id) {
|
||||||
|
currentImageId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageId() {
|
||||||
|
return currentImageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(String imageId) {
|
||||||
|
if (multiImageData.size() == 0) {
|
||||||
|
|
||||||
|
// The image id when the request is initiated is inconsistent with the current image id,
|
||||||
|
// indicating that multiplexing has occurred, and the image should not be set at this time.
|
||||||
|
if (imageId != null && !TextUtils.equals(imageId, currentImageId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GlideEngine.loadUserIcon(imageView, getDefaultImage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (multiImageData.size() == 1) {
|
||||||
|
|
||||||
|
// The image id when the request is initiated is inconsistent with the current image id,
|
||||||
|
// indicating that multiplexing has occurred, and the image should not be set at this time.
|
||||||
|
if (imageId != null && !TextUtils.equals(imageId, currentImageId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GlideEngine.loadUserIcon(imageView, multiImageData.getImageUrls().get(0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Clear the content before loading images asynchronously to avoid flickering
|
||||||
|
clearImage();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Initialize the image information. Since it is asynchronous loading and synthesizing the avatar,
|
||||||
|
// a local object needs to be passed to the synthesis thread, which is only used in the asynchronous
|
||||||
|
// loading thread, so that when the image is reused, the external thread will not overwrite the local
|
||||||
|
// object by setting the url again.
|
||||||
|
MultiImageData copyMultiImageData;
|
||||||
|
try {
|
||||||
|
copyMultiImageData = multiImageData.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
List urlList = new ArrayList();
|
||||||
|
if (multiImageData.imageUrls != null) {
|
||||||
|
urlList.addAll(multiImageData.imageUrls);
|
||||||
|
}
|
||||||
|
copyMultiImageData = new MultiImageData(urlList, multiImageData.defaultImageResId);
|
||||||
|
}
|
||||||
|
int[] gridParam = calculateGridParam(multiImageData.size());
|
||||||
|
copyMultiImageData.rowCount = gridParam[0];
|
||||||
|
copyMultiImageData.columnCount = gridParam[1];
|
||||||
|
copyMultiImageData.targetImageSize = (copyMultiImageData.maxWidth - (copyMultiImageData.columnCount + 1) * copyMultiImageData.gap)
|
||||||
|
/ (copyMultiImageData.columnCount == 1 ? 2 : copyMultiImageData.columnCount);
|
||||||
|
final String finalImageId = imageId;
|
||||||
|
final MultiImageData finalCopyMultiImageData = copyMultiImageData;
|
||||||
|
ThreadUtils.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final File file = new File(TUIConfig.getImageBaseDir() + finalImageId);
|
||||||
|
boolean cacheBitmapExists = false;
|
||||||
|
Bitmap existsBitmap = null;
|
||||||
|
if (file.exists() && file.isFile()) {
|
||||||
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
existsBitmap = BitmapFactory.decodeFile(file.getPath(), options);
|
||||||
|
if (options.outWidth > 0 && options.outHeight > 0) {
|
||||||
|
cacheBitmapExists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cacheBitmapExists) {
|
||||||
|
asyncLoadImageList(finalCopyMultiImageData);
|
||||||
|
final Bitmap bitmap = synthesizeImageList(finalCopyMultiImageData);
|
||||||
|
ImageUtil.storeBitmap(file, bitmap);
|
||||||
|
ImageUtil.setGroupConversationAvatar(finalImageId, file.getAbsolutePath());
|
||||||
|
ThreadUtils.postOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
callback.onCall(bitmap, finalImageId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
final Bitmap finalExistsBitmap = existsBitmap;
|
||||||
|
ThreadUtils.postOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
callback.onCall(finalExistsBitmap, finalImageId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearImage() {
|
||||||
|
GlideEngine.clear(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
void onCall(Bitmap bitmap, String targetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.gatherimage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class UserIconView extends RelativeLayout {
|
||||||
|
private SynthesizedImageView mIconView;
|
||||||
|
private int mDefaultImageResId;
|
||||||
|
private int mIconRadius;
|
||||||
|
|
||||||
|
public UserIconView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserIconView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserIconView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(AttributeSet attributeSet) {
|
||||||
|
inflate(getContext(), R.layout.common_profile_icon_view, this);
|
||||||
|
if (attributeSet != null) {
|
||||||
|
TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.UserIconView);
|
||||||
|
if (null != ta) {
|
||||||
|
mDefaultImageResId = ta.getResourceId(R.styleable.UserIconView_default_image, mDefaultImageResId);
|
||||||
|
mIconRadius = ta.getDimensionPixelSize(R.styleable.UserIconView_image_radius, mIconRadius);
|
||||||
|
ta.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mIconView = findViewById(R.id.profile_icon);
|
||||||
|
if (mDefaultImageResId > 0) {
|
||||||
|
mIconView.defaultImage(mDefaultImageResId);
|
||||||
|
}
|
||||||
|
if (mIconRadius > 0) {
|
||||||
|
mIconView.setRadius(mIconRadius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultImageResId(int resId) {
|
||||||
|
mDefaultImageResId = resId;
|
||||||
|
mIconView.defaultImage(resId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRadius(int radius) {
|
||||||
|
mIconRadius = radius;
|
||||||
|
mIconView.setRadius(mIconRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconUrls(List<Object> iconUrls) {
|
||||||
|
mIconView.displayImage(iconUrls).load(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.highlight;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ArgbEvaluator;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.TIMCommonService;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HighlightPresenter {
|
||||||
|
public static final int DEFAULT_DURATION = 250;
|
||||||
|
public static final int DEFAULT_REPEAT_COUNT = 3;
|
||||||
|
|
||||||
|
private static final class HighlightPresenterHolder {
|
||||||
|
private static final HighlightPresenter INSTANCE = new HighlightPresenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HighlightPresenter getInstance() {
|
||||||
|
return HighlightPresenterHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, WeakReference<HighlightListener>> highlightListenerMap = new HashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, ValueAnimator> highlightMap = new HashMap<>();
|
||||||
|
|
||||||
|
private int highLightDarkColor = -1;
|
||||||
|
private int highLightLightColor = -1;
|
||||||
|
|
||||||
|
private HighlightPresenter() {}
|
||||||
|
|
||||||
|
public static void registerHighlightListener(String highlightID, HighlightListener listener) {
|
||||||
|
if (listener == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getInstance().highlightListenerMap.put(highlightID, new WeakReference<>(listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void unregisterHighlightListener(String highlightID) {
|
||||||
|
getInstance().highlightListenerMap.remove(highlightID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startHighlight(String highlightID) {
|
||||||
|
getInstance().internalStartHighlight(highlightID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void stopHighlight(String highlightID) {
|
||||||
|
getInstance().internalStopHighlight(highlightID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHighlightDarkColor(int color) {
|
||||||
|
getInstance().highLightDarkColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHighlightLightColor(int color) {
|
||||||
|
getInstance().highLightLightColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalStartHighlight(String highlightID) {
|
||||||
|
ValueAnimator highlightAnimator = new ValueAnimator();
|
||||||
|
if (highLightDarkColor == highLightLightColor && highLightLightColor == -1) {
|
||||||
|
highLightDarkColor = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_dark_color);
|
||||||
|
highLightLightColor = TIMCommonService.getAppContext().getResources().getColor(R.color.chat_message_bubble_high_light_light_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
highlightAnimator.setIntValues(highLightDarkColor, highLightLightColor);
|
||||||
|
highlightAnimator.setEvaluator(new ArgbEvaluator());
|
||||||
|
highlightAnimator.setRepeatCount(DEFAULT_REPEAT_COUNT);
|
||||||
|
highlightAnimator.setDuration(DEFAULT_DURATION);
|
||||||
|
highlightAnimator.setRepeatMode(ValueAnimator.REVERSE);
|
||||||
|
highlightAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
onHighlightStart(highlightID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
onHighlightEnd(highlightID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
onHighlightEnd(highlightID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
highlightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationUpdate(ValueAnimator animation) {
|
||||||
|
onHighlightUpdate(highlightID, (Integer) animation.getAnimatedValue());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
highlightAnimator.start();
|
||||||
|
highlightMap.put(highlightID, highlightAnimator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void internalStopHighlight(String highlightID) {
|
||||||
|
ValueAnimator highlightAnimator = highlightMap.get(highlightID);
|
||||||
|
if (highlightAnimator != null) {
|
||||||
|
highlightAnimator.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHighlightStart(String highlightID) {
|
||||||
|
HighlightListener lightListener = getInstance().getHighlightListener(highlightID);
|
||||||
|
if (lightListener != null) {
|
||||||
|
lightListener.onHighlightStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHighlightEnd(String highlightID) {
|
||||||
|
highlightMap.remove(highlightID);
|
||||||
|
HighlightListener lightListener = getInstance().getHighlightListener(highlightID);
|
||||||
|
if (lightListener != null) {
|
||||||
|
lightListener.onHighlightEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onHighlightUpdate(String highlightID, int color) {
|
||||||
|
HighlightListener lightListener = getHighlightListener(highlightID);
|
||||||
|
if (lightListener != null) {
|
||||||
|
lightListener.onHighlightUpdate(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HighlightListener getHighlightListener(String highlightID) {
|
||||||
|
WeakReference<HighlightListener> listener = highlightListenerMap.get(highlightID);
|
||||||
|
if (listener != null) {
|
||||||
|
return listener.get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.impl;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.Priority;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import com.tencent.qcloud.tuicore.TUILogin;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
public class GlideEngine {
|
||||||
|
public static void loadCornerImageWithoutPlaceHolder(ImageView imageView, Object uri, RequestListener listener, float radius) {
|
||||||
|
RoundedCorners transform = null;
|
||||||
|
if ((int) radius > 0) {
|
||||||
|
transform = new RoundedCorners((int) radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestOptions options = new RequestOptions().centerCrop();
|
||||||
|
if (transform != null) {
|
||||||
|
options = options.transform(transform);
|
||||||
|
}
|
||||||
|
Glide.with(TUILogin.getAppContext()).load(uri).apply(options).listener(listener).into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear(ImageView imageView) {
|
||||||
|
Glide.with(TUILogin.getAppContext()).clear(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadImage(ImageView imageView, String filePath, RequestListener listener) {
|
||||||
|
Glide.with(TUILogin.getAppContext())
|
||||||
|
.load(filePath)
|
||||||
|
.listener(listener)
|
||||||
|
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadImage(ImageView imageView, String filePath) {
|
||||||
|
Glide.with(TUILogin.getAppContext())
|
||||||
|
.load(filePath)
|
||||||
|
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadImage(ImageView imageView, Uri uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(TUILogin.getAppContext())
|
||||||
|
.load(uri)
|
||||||
|
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadImage(String filePath, String url) {
|
||||||
|
try {
|
||||||
|
File file = Glide.with(TUILogin.getAppContext()).asFile().load(url).submit().get();
|
||||||
|
File destFile = new File(filePath);
|
||||||
|
file.renameTo(destFile);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) {
|
||||||
|
Glide.with(context).load(uri).apply(new RequestOptions().override(resizeX, resizeY).priority(Priority.HIGH).fitCenter()).into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadImage(ImageView imageView, Object uri) {
|
||||||
|
if (uri == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Glide.with(TUILogin.getAppContext())
|
||||||
|
.load(uri)
|
||||||
|
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadUserIcon(ImageView imageView, Object uri) {
|
||||||
|
loadUserIcon(imageView, uri, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadUserIcon(ImageView imageView, Object uri, int radius) {
|
||||||
|
Glide.with(TUILogin.getAppContext())
|
||||||
|
.load(uri)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||||
|
.placeholder(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))
|
||||||
|
.apply(new RequestOptions().centerCrop().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadUserIcon(ImageView imageView, Object uri, int defaultResId, int radius) {
|
||||||
|
Glide.with(TUILogin.getAppContext()).load(uri).placeholder(defaultResId).apply(new RequestOptions().centerCrop().error(defaultResId)).into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap loadBitmap(Object imageUrl, int targetImageSize) throws InterruptedException, ExecutionException {
|
||||||
|
if (imageUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Glide.with(TUILogin.getAppContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(imageUrl)
|
||||||
|
.apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)))
|
||||||
|
.into(targetImageSize, targetImageSize)
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadImageSetDefault(ImageView imageView, Object uri, int defaultResId) {
|
||||||
|
Glide.with(TUILogin.getAppContext()).load(uri).placeholder(defaultResId).apply(new RequestOptions().centerCrop().error(defaultResId)).into(imageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.interfaces;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout;
|
||||||
|
|
||||||
|
public interface ILayout {
|
||||||
|
/**
|
||||||
|
* get title bar
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
TitleBarLayout getTitleBar();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the parent container of this Layout
|
||||||
|
*
|
||||||
|
* @param parent
|
||||||
|
*/
|
||||||
|
void setParentLayout(Object parent);
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.interfaces;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conversation list window {@link ConversationLayout}、chat window {@link ChatLayout} have title bar,
|
||||||
|
* The title bar is designed as a three-part title on the left, middle and right. The left can be
|
||||||
|
* picture + text, the middle is text, and the right can also be picture + text. These areas return the
|
||||||
|
* standard Android View,These Views can be interactively processed according to business needs。
|
||||||
|
*/
|
||||||
|
public interface ITitleBarLayout {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the click event of the left header
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
void setOnLeftClickListener(View.OnClickListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the click event of the right title
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
void setOnRightClickListener(View.OnClickListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* set Title
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void setTitle(String title, Position position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Return to the left header area
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
LinearLayout getLeftGroup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Return to the right header area
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
LinearLayout getRightGroup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns the image for the left header
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ImageView getLeftIcon();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the image for the left header
|
||||||
|
*
|
||||||
|
* @param resId
|
||||||
|
*/
|
||||||
|
void setLeftIcon(int resId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns the image with the right header
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ImageView getRightIcon();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the image for the title on the right
|
||||||
|
*
|
||||||
|
* @param resId
|
||||||
|
*/
|
||||||
|
void setRightIcon(int resId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns the text of the left header
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
TextView getLeftTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns the text of the middle title
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
TextView getMiddleTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Returns the text of the title on the right
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
TextView getRightTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* enumeration value of the header area
|
||||||
|
*/
|
||||||
|
enum Position {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* left title
|
||||||
|
*/
|
||||||
|
LEFT,
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* middle title
|
||||||
|
*/
|
||||||
|
MIDDLE,
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* right title
|
||||||
|
*/
|
||||||
|
RIGHT
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.interfaces;
|
||||||
|
|
||||||
|
public abstract class IUIKitCallback<T> {
|
||||||
|
public void onSuccess(T data){}
|
||||||
|
|
||||||
|
public void onError(String module, int errCode, String errMsg) {}
|
||||||
|
|
||||||
|
public void onError(int errCode, String errMsg, T data) {}
|
||||||
|
|
||||||
|
public void onProgress(Object data) {}
|
||||||
|
|
||||||
|
public static <O> void callbackOnSuccess(IUIKitCallback<O> callback, O data) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onSuccess(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <O> void callbackOnError(IUIKitCallback<O> callback, int errCode, String errMsg, O data) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onError(errCode, errMsg, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011, 2012 Chris Banes.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
class Compat {
|
||||||
|
|
||||||
|
private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
|
||||||
|
|
||||||
|
public static void postOnAnimation(View view, Runnable runnable) {
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
|
||||||
|
postOnAnimationJellyBean(view, runnable);
|
||||||
|
} else {
|
||||||
|
view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
private static void postOnAnimationJellyBean(View view, Runnable runnable) {
|
||||||
|
view.postOnAnimation(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011, 2012 Chris Banes.
|
||||||
|
<p/>
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
<p/>
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
<p/>
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ScaleGestureDetector;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does a whole lot of gesture detecting.
|
||||||
|
*/
|
||||||
|
class CustomGestureDetector {
|
||||||
|
|
||||||
|
private static final int INVALID_POINTER_ID = -1;
|
||||||
|
|
||||||
|
private int mActivePointerId = INVALID_POINTER_ID;
|
||||||
|
private int mActivePointerIndex = 0;
|
||||||
|
private final ScaleGestureDetector mDetector;
|
||||||
|
|
||||||
|
private VelocityTracker mVelocityTracker;
|
||||||
|
private boolean mIsDragging;
|
||||||
|
private float mLastTouchX;
|
||||||
|
private float mLastTouchY;
|
||||||
|
private final float mTouchSlop;
|
||||||
|
private final float mMinimumVelocity;
|
||||||
|
private OnGestureListener mListener;
|
||||||
|
|
||||||
|
CustomGestureDetector(Context context, OnGestureListener listener) {
|
||||||
|
final ViewConfiguration configuration = ViewConfiguration
|
||||||
|
.get(context);
|
||||||
|
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||||
|
mTouchSlop = configuration.getScaledTouchSlop();
|
||||||
|
|
||||||
|
mListener = listener;
|
||||||
|
ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
|
||||||
|
private float lastFocusX = 0;
|
||||||
|
private float lastFocusY = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScale(ScaleGestureDetector detector) {
|
||||||
|
float scaleFactor = detector.getScaleFactor();
|
||||||
|
|
||||||
|
if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scaleFactor >= 0) {
|
||||||
|
mListener.onScale(scaleFactor,
|
||||||
|
detector.getFocusX(),
|
||||||
|
detector.getFocusY(),
|
||||||
|
detector.getFocusX() - lastFocusX,
|
||||||
|
detector.getFocusY() - lastFocusY
|
||||||
|
);
|
||||||
|
lastFocusX = detector.getFocusX();
|
||||||
|
lastFocusY = detector.getFocusY();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
||||||
|
lastFocusX = detector.getFocusX();
|
||||||
|
lastFocusY = detector.getFocusY();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mDetector = new ScaleGestureDetector(context, mScaleListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getActiveX(MotionEvent ev) {
|
||||||
|
try {
|
||||||
|
return ev.getX(mActivePointerIndex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ev.getX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float getActiveY(MotionEvent ev) {
|
||||||
|
try {
|
||||||
|
return ev.getY(mActivePointerIndex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return ev.getY();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isScaling() {
|
||||||
|
return mDetector.isInProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDragging() {
|
||||||
|
return mIsDragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
|
try {
|
||||||
|
mDetector.onTouchEvent(ev);
|
||||||
|
return processTouchEvent(ev);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Fix for support lib bug, happening when onDestroy is called
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processTouchEvent(MotionEvent ev) {
|
||||||
|
final int action = ev.getAction();
|
||||||
|
switch (action & MotionEvent.ACTION_MASK) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
mActivePointerId = ev.getPointerId(0);
|
||||||
|
|
||||||
|
mVelocityTracker = VelocityTracker.obtain();
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.addMovement(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastTouchX = getActiveX(ev);
|
||||||
|
mLastTouchY = getActiveY(ev);
|
||||||
|
mIsDragging = false;
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final float x = getActiveX(ev);
|
||||||
|
final float y = getActiveY(ev);
|
||||||
|
final float dx = x - mLastTouchX;
|
||||||
|
final float dy = y - mLastTouchY;
|
||||||
|
|
||||||
|
if (!mIsDragging) {
|
||||||
|
// Use Pythagoras to see if drag length is larger than
|
||||||
|
// touch slop
|
||||||
|
mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsDragging) {
|
||||||
|
mListener.onDrag(dx, dy);
|
||||||
|
mLastTouchX = x;
|
||||||
|
mLastTouchY = y;
|
||||||
|
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.addMovement(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
mActivePointerId = INVALID_POINTER_ID;
|
||||||
|
// Recycle Velocity Tracker
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
mActivePointerId = INVALID_POINTER_ID;
|
||||||
|
if (mIsDragging) {
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mLastTouchX = getActiveX(ev);
|
||||||
|
mLastTouchY = getActiveY(ev);
|
||||||
|
|
||||||
|
// Compute velocity within the last 1000ms
|
||||||
|
mVelocityTracker.addMovement(ev);
|
||||||
|
mVelocityTracker.computeCurrentVelocity(1000);
|
||||||
|
|
||||||
|
final float vX = mVelocityTracker.getXVelocity();
|
||||||
|
final float vY = mVelocityTracker
|
||||||
|
.getYVelocity();
|
||||||
|
|
||||||
|
// If the velocity is greater than minVelocity, call
|
||||||
|
// listener
|
||||||
|
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
|
||||||
|
mListener.onFling(mLastTouchX, mLastTouchY, -vX,
|
||||||
|
-vY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recycle Velocity Tracker
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_POINTER_UP:
|
||||||
|
final int pointerIndex = Util.getPointerIndex(ev.getAction());
|
||||||
|
final int pointerId = ev.getPointerId(pointerIndex);
|
||||||
|
if (pointerId == mActivePointerId) {
|
||||||
|
// This was our active pointer going up. Choose a new
|
||||||
|
// active pointer and adjust accordingly.
|
||||||
|
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||||
|
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||||
|
mLastTouchX = ev.getX(newPointerIndex);
|
||||||
|
mLastTouchY = ev.getY(newPointerIndex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mActivePointerIndex = ev
|
||||||
|
.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
|
||||||
|
: 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011, 2012 Chris Banes.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
interface OnGestureListener {
|
||||||
|
|
||||||
|
void onDrag(float dx, float dy);
|
||||||
|
|
||||||
|
void onFling(float startX, float startY, float velocityX,
|
||||||
|
float velocityY);
|
||||||
|
|
||||||
|
void onScale(float scaleFactor, float focusX, float focusY);
|
||||||
|
|
||||||
|
void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.graphics.RectF;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for a callback to be invoked when the internal Matrix has changed for
|
||||||
|
* this View.
|
||||||
|
*/
|
||||||
|
public interface OnMatrixChangedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the Matrix displaying the Drawable has changed. This could be because
|
||||||
|
* the View's bounds have changed, or the user has zoomed.
|
||||||
|
*
|
||||||
|
* @param rect - Rectangle displaying the Drawable's new bounds.
|
||||||
|
*/
|
||||||
|
void onMatrixChanged(RectF rect);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when the user tapped outside of the photo
|
||||||
|
*/
|
||||||
|
public interface OnOutsidePhotoTapListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The outside of the photo has been tapped
|
||||||
|
*/
|
||||||
|
void onOutsidePhotoTap(ImageView imageView);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to be invoked when the Photo is tapped with a single
|
||||||
|
* tap.
|
||||||
|
*/
|
||||||
|
public interface OnPhotoTapListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to receive where the user taps on a photo. You will only receive a callback if
|
||||||
|
* the user taps on the actual photo, tapping on 'whitespace' will be ignored.
|
||||||
|
*
|
||||||
|
* @param view ImageView the user tapped.
|
||||||
|
* @param x where the user tapped from the of the Drawable, as percentage of the
|
||||||
|
* Drawable width.
|
||||||
|
* @param y where the user tapped from the top of the Drawable, as percentage of the
|
||||||
|
* Drawable height.
|
||||||
|
*/
|
||||||
|
void onPhotoTap(ImageView view, float x, float y);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for callback to be invoked when attached ImageView scale changes
|
||||||
|
*/
|
||||||
|
public interface OnScaleChangedListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the scale changes
|
||||||
|
*
|
||||||
|
* @param scaleFactor the scale factor (less than 1 for zoom out, greater than 1 for zoom in)
|
||||||
|
* @param focusX focal point X position
|
||||||
|
* @param focusY focal point Y position
|
||||||
|
*/
|
||||||
|
void onScaleChange(float scaleFactor, float focusX, float focusY);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to be invoked when the ImageView is flung with a single
|
||||||
|
* touch
|
||||||
|
*/
|
||||||
|
public interface OnSingleFlingListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to receive where the user flings on a ImageView. You will receive a callback if
|
||||||
|
* the user flings anywhere on the view.
|
||||||
|
*
|
||||||
|
* @param e1 MotionEvent the user first touch.
|
||||||
|
* @param e2 MotionEvent the user last touch.
|
||||||
|
* @param velocityX distance of user's horizontal fling.
|
||||||
|
* @param velocityY distance of user's vertical fling.
|
||||||
|
*/
|
||||||
|
boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface definition for a callback to be invoked when the photo is experiencing a drag event
|
||||||
|
*/
|
||||||
|
public interface OnViewDragListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for when the photo is experiencing a drag event. This cannot be invoked when the
|
||||||
|
* user is scaling.
|
||||||
|
*
|
||||||
|
* @param dx The change of the coordinates in the x-direction
|
||||||
|
* @param dy The change of the coordinates in the y-direction
|
||||||
|
*/
|
||||||
|
void onDrag(float dx, float dy);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
public interface OnViewTapListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to receive where the user taps on a ImageView. You will receive a callback if
|
||||||
|
* the user taps anywhere on the view, tapping on 'whitespace' will not be ignored.
|
||||||
|
*
|
||||||
|
* @param view - View the user tapped.
|
||||||
|
* @param x - where the user tapped from the left of the View.
|
||||||
|
* @param y - where the user tapped from the top of the View.
|
||||||
|
*/
|
||||||
|
void onViewTap(View view, float x, float y);
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011, 2012 Chris Banes.
|
||||||
|
<p>
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
<p>
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
<p>
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.AppCompatImageView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A zoomable ImageView. See {@link PhotoViewAttacher} for most of the details on how the zooming
|
||||||
|
* is accomplished
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class PhotoView extends AppCompatImageView {
|
||||||
|
|
||||||
|
private PhotoViewAttacher attacher;
|
||||||
|
private ScaleType pendingScaleType;
|
||||||
|
|
||||||
|
public PhotoView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhotoView(Context context, AttributeSet attr) {
|
||||||
|
this(context, attr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PhotoView(Context context, AttributeSet attr, int defStyle) {
|
||||||
|
super(context, attr, defStyle);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
attacher = new PhotoViewAttacher(this);
|
||||||
|
//We always pose as a Matrix scale type, though we can change to another scale type
|
||||||
|
//via the attacher
|
||||||
|
super.setScaleType(ScaleType.MATRIX);
|
||||||
|
//apply the previously applied scale type
|
||||||
|
if (pendingScaleType != null) {
|
||||||
|
setScaleType(pendingScaleType);
|
||||||
|
pendingScaleType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current {@link PhotoViewAttacher} for this view. Be wary of holding on to references
|
||||||
|
* to this attacher, as it has a reference to this view, which, if a reference is held in the
|
||||||
|
* wrong place, can cause memory leaks.
|
||||||
|
*
|
||||||
|
* @return the attacher.
|
||||||
|
*/
|
||||||
|
public PhotoViewAttacher getAttacher() {
|
||||||
|
return attacher;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScaleType getScaleType() {
|
||||||
|
return attacher.getScaleType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Matrix getImageMatrix() {
|
||||||
|
return attacher.getImageMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnLongClickListener(OnLongClickListener l) {
|
||||||
|
attacher.setOnLongClickListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnClickListener(OnClickListener l) {
|
||||||
|
attacher.setOnClickListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScaleType(ScaleType scaleType) {
|
||||||
|
if (attacher == null) {
|
||||||
|
pendingScaleType = scaleType;
|
||||||
|
} else {
|
||||||
|
attacher.setScaleType(scaleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageDrawable(Drawable drawable) {
|
||||||
|
super.setImageDrawable(drawable);
|
||||||
|
// setImageBitmap calls through to this method
|
||||||
|
if (attacher != null) {
|
||||||
|
attacher.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageResource(int resId) {
|
||||||
|
super.setImageResource(resId);
|
||||||
|
if (attacher != null) {
|
||||||
|
attacher.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setImageURI(Uri uri) {
|
||||||
|
super.setImageURI(uri);
|
||||||
|
if (attacher != null) {
|
||||||
|
attacher.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean setFrame(int l, int t, int r, int b) {
|
||||||
|
boolean changed = super.setFrame(l, t, r, b);
|
||||||
|
if (changed) {
|
||||||
|
attacher.update();
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRotationTo(float rotationDegree) {
|
||||||
|
attacher.setRotationTo(rotationDegree);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRotationBy(float rotationDegree) {
|
||||||
|
attacher.setRotationBy(rotationDegree);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZoomable() {
|
||||||
|
return attacher.isZoomable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoomable(boolean zoomable) {
|
||||||
|
attacher.setZoomable(zoomable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectF getDisplayRect() {
|
||||||
|
return attacher.getDisplayRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getDisplayMatrix(Matrix matrix) {
|
||||||
|
attacher.getDisplayMatrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedReturnValue") public boolean setDisplayMatrix(Matrix finalRectangle) {
|
||||||
|
return attacher.setDisplayMatrix(finalRectangle);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getSuppMatrix(Matrix matrix) {
|
||||||
|
attacher.getSuppMatrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setSuppMatrix(Matrix matrix) {
|
||||||
|
return attacher.setDisplayMatrix(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMinimumScale() {
|
||||||
|
return attacher.getMinimumScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMediumScale() {
|
||||||
|
return attacher.getMediumScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMaximumScale() {
|
||||||
|
return attacher.getMaximumScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScale() {
|
||||||
|
return attacher.getScale();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowParentInterceptOnEdge(boolean allow) {
|
||||||
|
attacher.setAllowParentInterceptOnEdge(allow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinimumScale(float minimumScale) {
|
||||||
|
attacher.setMinimumScale(minimumScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediumScale(float mediumScale) {
|
||||||
|
attacher.setMediumScale(mediumScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaximumScale(float maximumScale) {
|
||||||
|
attacher.setMaximumScale(maximumScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
|
||||||
|
attacher.setScaleLevels(minimumScale, mediumScale, maximumScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
|
||||||
|
attacher.setOnMatrixChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
|
||||||
|
attacher.setOnPhotoTapListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener listener) {
|
||||||
|
attacher.setOnOutsidePhotoTapListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnViewTapListener(OnViewTapListener listener) {
|
||||||
|
attacher.setOnViewTapListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnViewDragListener(OnViewDragListener listener) {
|
||||||
|
attacher.setOnViewDragListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScale(float scale) {
|
||||||
|
attacher.setScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScale(float scale, boolean animate) {
|
||||||
|
attacher.setScale(scale, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScale(float scale, float focalX, float focalY, boolean animate) {
|
||||||
|
attacher.setScale(scale, focalX, focalY, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoomTransitionDuration(int milliseconds) {
|
||||||
|
attacher.setZoomTransitionDuration(milliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener onDoubleTapListener) {
|
||||||
|
attacher.setOnDoubleTapListener(onDoubleTapListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangedListener) {
|
||||||
|
attacher.setOnScaleChangeListener(onScaleChangedListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
|
||||||
|
attacher.setOnSingleFlingListener(onSingleFlingListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,807 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2011, 2012 Chris Banes.
|
||||||
|
<p>
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
<p>
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
<p>
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.Matrix.ScaleToFit;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnLongClickListener;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.ImageView.ScaleType;
|
||||||
|
import android.widget.OverScroller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component of {@link PhotoView} which does the work allowing for zooming, scaling, panning, etc.
|
||||||
|
* It is made public in case you need to subclass something other than AppCompatImageView and still
|
||||||
|
* gain the functionality that {@link PhotoView} offers
|
||||||
|
*/
|
||||||
|
public class PhotoViewAttacher implements View.OnTouchListener, View.OnLayoutChangeListener {
|
||||||
|
private static float DEFAULT_MAX_SCALE = 3.0f;
|
||||||
|
private static float DEFAULT_MID_SCALE = 1.75f;
|
||||||
|
private static float DEFAULT_MIN_SCALE = 1.0f;
|
||||||
|
private static int DEFAULT_ZOOM_DURATION = 200;
|
||||||
|
|
||||||
|
private static final int HORIZONTAL_EDGE_NONE = -1;
|
||||||
|
private static final int HORIZONTAL_EDGE_LEFT = 0;
|
||||||
|
private static final int HORIZONTAL_EDGE_RIGHT = 1;
|
||||||
|
private static final int HORIZONTAL_EDGE_BOTH = 2;
|
||||||
|
private static final int VERTICAL_EDGE_NONE = -1;
|
||||||
|
private static final int VERTICAL_EDGE_TOP = 0;
|
||||||
|
private static final int VERTICAL_EDGE_BOTTOM = 1;
|
||||||
|
private static final int VERTICAL_EDGE_BOTH = 2;
|
||||||
|
private static int SINGLE_TOUCH = 1;
|
||||||
|
|
||||||
|
private Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
|
||||||
|
private int mZoomDuration = DEFAULT_ZOOM_DURATION;
|
||||||
|
private float mMinScale = DEFAULT_MIN_SCALE;
|
||||||
|
private float mMidScale = DEFAULT_MID_SCALE;
|
||||||
|
private float mMaxScale = DEFAULT_MAX_SCALE;
|
||||||
|
|
||||||
|
private boolean mAllowParentInterceptOnEdge = true;
|
||||||
|
private boolean mBlockParentIntercept = false;
|
||||||
|
|
||||||
|
private ImageView mImageView;
|
||||||
|
|
||||||
|
// Gesture Detectors
|
||||||
|
private GestureDetector mGestureDetector;
|
||||||
|
private CustomGestureDetector mScaleDragDetector;
|
||||||
|
|
||||||
|
// These are set so we don't keep allocating them on the heap
|
||||||
|
private final Matrix mBaseMatrix = new Matrix();
|
||||||
|
private final Matrix mDrawMatrix = new Matrix();
|
||||||
|
private final Matrix mSuppMatrix = new Matrix();
|
||||||
|
private final RectF mDisplayRect = new RectF();
|
||||||
|
private final float[] mMatrixValues = new float[9];
|
||||||
|
|
||||||
|
// Listeners
|
||||||
|
private OnMatrixChangedListener mMatrixChangeListener;
|
||||||
|
private OnPhotoTapListener mPhotoTapListener;
|
||||||
|
private OnOutsidePhotoTapListener mOutsidePhotoTapListener;
|
||||||
|
private OnViewTapListener mViewTapListener;
|
||||||
|
private View.OnClickListener mOnClickListener;
|
||||||
|
private OnLongClickListener mLongClickListener;
|
||||||
|
private OnScaleChangedListener mScaleChangeListener;
|
||||||
|
private OnSingleFlingListener mSingleFlingListener;
|
||||||
|
private OnViewDragListener mOnViewDragListener;
|
||||||
|
|
||||||
|
private FlingRunnable mCurrentFlingRunnable;
|
||||||
|
private int mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
|
||||||
|
private int mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
|
||||||
|
private float mBaseRotation;
|
||||||
|
|
||||||
|
private boolean mZoomEnabled = true;
|
||||||
|
private ScaleType mScaleType = ScaleType.FIT_CENTER;
|
||||||
|
|
||||||
|
private OnGestureListener onGestureListener = new OnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public void onDrag(float dx, float dy) {
|
||||||
|
if (mScaleDragDetector.isScaling()) {
|
||||||
|
return; // Do not drag if we are already scaling
|
||||||
|
}
|
||||||
|
if (mOnViewDragListener != null) {
|
||||||
|
mOnViewDragListener.onDrag(dx, dy);
|
||||||
|
}
|
||||||
|
mSuppMatrix.postTranslate(dx, dy);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here we decide whether to let the ImageView's parent to start taking
|
||||||
|
* over the touch event.
|
||||||
|
*
|
||||||
|
* First we check whether this function is enabled. We never want the
|
||||||
|
* parent to take over if we're scaling. We then check the edge we're
|
||||||
|
* on, and the direction of the scroll (i.e. if we're pulling against
|
||||||
|
* the edge, aka 'overscrolling', let the parent take over).
|
||||||
|
*/
|
||||||
|
ViewParent parent = mImageView.getParent();
|
||||||
|
if (mAllowParentInterceptOnEdge && !mScaleDragDetector.isScaling() && !mBlockParentIntercept) {
|
||||||
|
if (mHorizontalScrollEdge == HORIZONTAL_EDGE_BOTH || (mHorizontalScrollEdge == HORIZONTAL_EDGE_LEFT && dx >= 1f)
|
||||||
|
|| (mHorizontalScrollEdge == HORIZONTAL_EDGE_RIGHT && dx <= -1f) || (mVerticalScrollEdge == VERTICAL_EDGE_TOP && dy >= 1f)
|
||||||
|
|| (mVerticalScrollEdge == VERTICAL_EDGE_BOTTOM && dy <= -1f)) {
|
||||||
|
if (parent != null) {
|
||||||
|
parent.requestDisallowInterceptTouchEvent(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (parent != null) {
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFling(float startX, float startY, float velocityX, float velocityY) {
|
||||||
|
mCurrentFlingRunnable = new FlingRunnable(mImageView.getContext());
|
||||||
|
mCurrentFlingRunnable.fling(getImageViewWidth(mImageView), getImageViewHeight(mImageView), (int) velocityX, (int) velocityY);
|
||||||
|
mImageView.post(mCurrentFlingRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScale(float scaleFactor, float focusX, float focusY) {
|
||||||
|
onScale(scaleFactor, focusX, focusY, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScale(float scaleFactor, float focusX, float focusY, float dx, float dy) {
|
||||||
|
if (getScale() < mMaxScale || scaleFactor < 1f) {
|
||||||
|
if (mScaleChangeListener != null) {
|
||||||
|
mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
|
||||||
|
}
|
||||||
|
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
|
||||||
|
mSuppMatrix.postTranslate(dx, dy);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public PhotoViewAttacher(ImageView imageView) {
|
||||||
|
mImageView = imageView;
|
||||||
|
imageView.setOnTouchListener(this);
|
||||||
|
imageView.addOnLayoutChangeListener(this);
|
||||||
|
if (imageView.isInEditMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mBaseRotation = 0.0f;
|
||||||
|
// Create Gesture Detectors...
|
||||||
|
mScaleDragDetector = new CustomGestureDetector(imageView.getContext(), onGestureListener);
|
||||||
|
mGestureDetector = new GestureDetector(imageView.getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
// forward long click listener
|
||||||
|
@Override
|
||||||
|
public void onLongPress(MotionEvent e) {
|
||||||
|
if (mLongClickListener != null) {
|
||||||
|
mLongClickListener.onLongClick(mImageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||||
|
if (mSingleFlingListener != null) {
|
||||||
|
if (getScale() > DEFAULT_MIN_SCALE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (e1.getPointerCount() > SINGLE_TOUCH || e2.getPointerCount() > SINGLE_TOUCH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mSingleFlingListener.onFling(e1, e2, velocityX, velocityY);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||||
|
if (mOnClickListener != null) {
|
||||||
|
mOnClickListener.onClick(mImageView);
|
||||||
|
}
|
||||||
|
final RectF displayRect = getDisplayRect();
|
||||||
|
final float x = e.getX();
|
||||||
|
final float y = e.getY();
|
||||||
|
if (mViewTapListener != null) {
|
||||||
|
mViewTapListener.onViewTap(mImageView, x, y);
|
||||||
|
}
|
||||||
|
if (displayRect != null) {
|
||||||
|
// Check to see if the user tapped on the photo
|
||||||
|
if (displayRect.contains(x, y)) {
|
||||||
|
float xResult = (x - displayRect.left) / displayRect.width();
|
||||||
|
float yResult = (y - displayRect.top) / displayRect.height();
|
||||||
|
if (mPhotoTapListener != null) {
|
||||||
|
mPhotoTapListener.onPhotoTap(mImageView, xResult, yResult);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (mOutsidePhotoTapListener != null) {
|
||||||
|
mOutsidePhotoTapListener.onOutsidePhotoTap(mImageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDoubleTap(MotionEvent ev) {
|
||||||
|
try {
|
||||||
|
float scale = getScale();
|
||||||
|
float x = ev.getX();
|
||||||
|
float y = ev.getY();
|
||||||
|
if (scale < getMediumScale()) {
|
||||||
|
setScale(getMediumScale(), x, y, true);
|
||||||
|
} else if (scale >= getMediumScale() && scale < getMaximumScale()) {
|
||||||
|
setScale(getMaximumScale(), x, y, true);
|
||||||
|
} else {
|
||||||
|
setScale(getMinimumScale(), x, y, true);
|
||||||
|
}
|
||||||
|
} catch (ArrayIndexOutOfBoundsException e) {
|
||||||
|
// Can sometimes happen when getX() and getY() is called
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||||
|
// Wait for the confirmed onDoubleTap() instead
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
|
||||||
|
this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnScaleChangeListener(OnScaleChangedListener onScaleChangeListener) {
|
||||||
|
this.mScaleChangeListener = onScaleChangeListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSingleFlingListener(OnSingleFlingListener onSingleFlingListener) {
|
||||||
|
this.mSingleFlingListener = onSingleFlingListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public boolean isZoomEnabled() {
|
||||||
|
return mZoomEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that maps the supplied Matrix to the current Drawable
|
||||||
|
*
|
||||||
|
* @param matrix - Matrix to map Drawable against
|
||||||
|
* @return RectF - Displayed Rectangle
|
||||||
|
*/
|
||||||
|
private RectF getDisplayRect(Matrix matrix) {
|
||||||
|
Drawable d = mImageView.getDrawable();
|
||||||
|
if (d != null) {
|
||||||
|
mDisplayRect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
|
||||||
|
matrix.mapRect(mDisplayRect);
|
||||||
|
return mDisplayRect;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectF getDisplayRect() {
|
||||||
|
checkMatrixBounds();
|
||||||
|
return getDisplayRect(getDrawMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setDisplayMatrix(Matrix finalMatrix) {
|
||||||
|
if (finalMatrix == null) {
|
||||||
|
throw new IllegalArgumentException("Matrix cannot be null");
|
||||||
|
}
|
||||||
|
if (mImageView.getDrawable() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mSuppMatrix.set(finalMatrix);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseRotation(final float degrees) {
|
||||||
|
mBaseRotation = degrees % 360;
|
||||||
|
update();
|
||||||
|
setRotationBy(mBaseRotation);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRotationTo(float degrees) {
|
||||||
|
mSuppMatrix.setRotate(degrees % 360);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRotationBy(float degrees) {
|
||||||
|
mSuppMatrix.postRotate(degrees % 360);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMinimumScale() {
|
||||||
|
return mMinScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMediumScale() {
|
||||||
|
return mMidScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMaximumScale() {
|
||||||
|
return mMaxScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScale() {
|
||||||
|
return (float) Math.sqrt((float) Math.pow(getValue(mSuppMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScaleType getScaleType() {
|
||||||
|
return mScaleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
|
||||||
|
// Update our base matrix, as the bounds have changed
|
||||||
|
if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
|
||||||
|
updateBaseMatrix(mImageView.getDrawable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent ev) {
|
||||||
|
boolean handled = false;
|
||||||
|
if (mZoomEnabled && Util.hasDrawable((ImageView) v)) {
|
||||||
|
switch (ev.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
ViewParent parent = v.getParent();
|
||||||
|
// First, disable the Parent from intercepting the touch
|
||||||
|
// event
|
||||||
|
if (parent != null) {
|
||||||
|
parent.requestDisallowInterceptTouchEvent(true);
|
||||||
|
}
|
||||||
|
// If we're flinging, and the user presses down, cancel
|
||||||
|
// fling
|
||||||
|
cancelFling();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
// If the user has zoomed less than min scale, zoom back
|
||||||
|
// to min scale
|
||||||
|
if (getScale() < mMinScale) {
|
||||||
|
RectF rect = getDisplayRect();
|
||||||
|
if (rect != null) {
|
||||||
|
v.post(new AnimatedZoomRunnable(getScale(), mMinScale, rect.centerX(), rect.centerY()));
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
} else if (getScale() > mMaxScale) {
|
||||||
|
RectF rect = getDisplayRect();
|
||||||
|
if (rect != null) {
|
||||||
|
v.post(new AnimatedZoomRunnable(getScale(), mMaxScale, rect.centerX(), rect.centerY()));
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Try the Scale/Drag detector
|
||||||
|
if (mScaleDragDetector != null) {
|
||||||
|
boolean wasScaling = mScaleDragDetector.isScaling();
|
||||||
|
boolean wasDragging = mScaleDragDetector.isDragging();
|
||||||
|
handled = mScaleDragDetector.onTouchEvent(ev);
|
||||||
|
boolean didntScale = !wasScaling && !mScaleDragDetector.isScaling();
|
||||||
|
boolean didntDrag = !wasDragging && !mScaleDragDetector.isDragging();
|
||||||
|
mBlockParentIntercept = didntScale && didntDrag;
|
||||||
|
}
|
||||||
|
// Check to see if the user double tapped
|
||||||
|
if (mGestureDetector != null && mGestureDetector.onTouchEvent(ev)) {
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowParentInterceptOnEdge(boolean allow) {
|
||||||
|
mAllowParentInterceptOnEdge = allow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinimumScale(float minimumScale) {
|
||||||
|
Util.checkZoomLevels(minimumScale, mMidScale, mMaxScale);
|
||||||
|
mMinScale = minimumScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediumScale(float mediumScale) {
|
||||||
|
Util.checkZoomLevels(mMinScale, mediumScale, mMaxScale);
|
||||||
|
mMidScale = mediumScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaximumScale(float maximumScale) {
|
||||||
|
Util.checkZoomLevels(mMinScale, mMidScale, maximumScale);
|
||||||
|
mMaxScale = maximumScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleLevels(float minimumScale, float mediumScale, float maximumScale) {
|
||||||
|
Util.checkZoomLevels(minimumScale, mediumScale, maximumScale);
|
||||||
|
mMinScale = minimumScale;
|
||||||
|
mMidScale = mediumScale;
|
||||||
|
mMaxScale = maximumScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnLongClickListener(OnLongClickListener listener) {
|
||||||
|
mLongClickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnClickListener(View.OnClickListener listener) {
|
||||||
|
mOnClickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
|
||||||
|
mMatrixChangeListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
|
||||||
|
mPhotoTapListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnOutsidePhotoTapListener(OnOutsidePhotoTapListener mOutsidePhotoTapListener) {
|
||||||
|
this.mOutsidePhotoTapListener = mOutsidePhotoTapListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnViewTapListener(OnViewTapListener listener) {
|
||||||
|
mViewTapListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnViewDragListener(OnViewDragListener listener) {
|
||||||
|
mOnViewDragListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScale(float scale) {
|
||||||
|
setScale(scale, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScale(float scale, boolean animate) {
|
||||||
|
setScale(scale, (mImageView.getRight()) / 2, (mImageView.getBottom()) / 2, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScale(float scale, float focalX, float focalY, boolean animate) {
|
||||||
|
// Check to see if the scale is within bounds
|
||||||
|
if (scale < mMinScale || scale > mMaxScale) {
|
||||||
|
throw new IllegalArgumentException("Scale must be within the range of minScale and maxScale");
|
||||||
|
}
|
||||||
|
if (animate) {
|
||||||
|
mImageView.post(new AnimatedZoomRunnable(getScale(), scale, focalX, focalY));
|
||||||
|
} else {
|
||||||
|
mSuppMatrix.setScale(scale, scale, focalX, focalY);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the zoom interpolator
|
||||||
|
*
|
||||||
|
* @param interpolator the zoom interpolator
|
||||||
|
*/
|
||||||
|
public void setZoomInterpolator(Interpolator interpolator) {
|
||||||
|
mInterpolator = interpolator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleType(ScaleType scaleType) {
|
||||||
|
if (Util.isSupportedScaleType(scaleType) && scaleType != mScaleType) {
|
||||||
|
mScaleType = scaleType;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isZoomable() {
|
||||||
|
return mZoomEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoomable(boolean zoomable) {
|
||||||
|
mZoomEnabled = zoomable;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update() {
|
||||||
|
if (mZoomEnabled) {
|
||||||
|
// Update the base matrix using the current drawable
|
||||||
|
updateBaseMatrix(mImageView.getDrawable());
|
||||||
|
} else {
|
||||||
|
// Reset the Matrix...
|
||||||
|
resetMatrix();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display matrix
|
||||||
|
*
|
||||||
|
* @param matrix target matrix to copy to
|
||||||
|
*/
|
||||||
|
public void getDisplayMatrix(Matrix matrix) {
|
||||||
|
matrix.set(getDrawMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current support matrix
|
||||||
|
*/
|
||||||
|
public void getSuppMatrix(Matrix matrix) {
|
||||||
|
matrix.set(mSuppMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Matrix getDrawMatrix() {
|
||||||
|
mDrawMatrix.set(mBaseMatrix);
|
||||||
|
mDrawMatrix.postConcat(mSuppMatrix);
|
||||||
|
return mDrawMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix getImageMatrix() {
|
||||||
|
return mDrawMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZoomTransitionDuration(int milliseconds) {
|
||||||
|
this.mZoomDuration = milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that 'unpacks' a Matrix and returns the required value
|
||||||
|
*
|
||||||
|
* @param matrix Matrix to unpack
|
||||||
|
* @param whichValue Which value from Matrix.M* to return
|
||||||
|
* @return returned value
|
||||||
|
*/
|
||||||
|
private float getValue(Matrix matrix, int whichValue) {
|
||||||
|
matrix.getValues(mMatrixValues);
|
||||||
|
return mMatrixValues[whichValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the Matrix back to FIT_CENTER, and then displays its contents
|
||||||
|
*/
|
||||||
|
private void resetMatrix() {
|
||||||
|
mSuppMatrix.reset();
|
||||||
|
setRotationBy(mBaseRotation);
|
||||||
|
setImageViewMatrix(getDrawMatrix());
|
||||||
|
checkMatrixBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setImageViewMatrix(Matrix matrix) {
|
||||||
|
mImageView.setImageMatrix(matrix);
|
||||||
|
// Call MatrixChangedListener if needed
|
||||||
|
if (mMatrixChangeListener != null) {
|
||||||
|
RectF displayRect = getDisplayRect(matrix);
|
||||||
|
if (displayRect != null) {
|
||||||
|
mMatrixChangeListener.onMatrixChanged(displayRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that simply checks the Matrix, and then displays the result
|
||||||
|
*/
|
||||||
|
private void checkAndDisplayMatrix() {
|
||||||
|
if (checkMatrixBounds()) {
|
||||||
|
setImageViewMatrix(getDrawMatrix());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate Matrix for FIT_CENTER
|
||||||
|
*
|
||||||
|
* @param drawable - Drawable being displayed
|
||||||
|
*/
|
||||||
|
private void updateBaseMatrix(Drawable drawable) {
|
||||||
|
if (drawable == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final float viewWidth = getImageViewWidth(mImageView);
|
||||||
|
final float viewHeight = getImageViewHeight(mImageView);
|
||||||
|
final int drawableWidth = drawable.getIntrinsicWidth();
|
||||||
|
final int drawableHeight = drawable.getIntrinsicHeight();
|
||||||
|
mBaseMatrix.reset();
|
||||||
|
final float widthScale = viewWidth / drawableWidth;
|
||||||
|
final float heightScale = viewHeight / drawableHeight;
|
||||||
|
if (mScaleType == ScaleType.CENTER) {
|
||||||
|
mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F, (viewHeight - drawableHeight) / 2F);
|
||||||
|
|
||||||
|
} else if (mScaleType == ScaleType.CENTER_CROP) {
|
||||||
|
float scale = Math.max(widthScale, heightScale);
|
||||||
|
mBaseMatrix.postScale(scale, scale);
|
||||||
|
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);
|
||||||
|
|
||||||
|
} else if (mScaleType == ScaleType.CENTER_INSIDE) {
|
||||||
|
float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
|
||||||
|
mBaseMatrix.postScale(scale, scale);
|
||||||
|
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F, (viewHeight - drawableHeight * scale) / 2F);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
|
||||||
|
RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
|
||||||
|
if ((int) mBaseRotation % 180 != 0) {
|
||||||
|
mTempSrc = new RectF(0, 0, drawableHeight, drawableWidth);
|
||||||
|
}
|
||||||
|
switch (mScaleType) {
|
||||||
|
case FIT_CENTER:
|
||||||
|
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
|
||||||
|
break;
|
||||||
|
case FIT_START:
|
||||||
|
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
|
||||||
|
break;
|
||||||
|
case FIT_END:
|
||||||
|
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
|
||||||
|
break;
|
||||||
|
case FIT_XY:
|
||||||
|
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkMatrixBounds() {
|
||||||
|
final RectF rect = getDisplayRect(getDrawMatrix());
|
||||||
|
if (rect == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final float height = rect.height();
|
||||||
|
final float width = rect.width();
|
||||||
|
float deltaX = 0;
|
||||||
|
float deltaY = 0;
|
||||||
|
final int viewHeight = getImageViewHeight(mImageView);
|
||||||
|
if (height <= viewHeight) {
|
||||||
|
switch (mScaleType) {
|
||||||
|
case FIT_START:
|
||||||
|
deltaY = -rect.top;
|
||||||
|
break;
|
||||||
|
case FIT_END:
|
||||||
|
deltaY = viewHeight - height - rect.top;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
deltaY = (viewHeight - height) / 2 - rect.top;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mVerticalScrollEdge = VERTICAL_EDGE_BOTH;
|
||||||
|
} else if (rect.top > 0) {
|
||||||
|
mVerticalScrollEdge = VERTICAL_EDGE_TOP;
|
||||||
|
deltaY = -rect.top;
|
||||||
|
} else if (rect.bottom < viewHeight) {
|
||||||
|
mVerticalScrollEdge = VERTICAL_EDGE_BOTTOM;
|
||||||
|
deltaY = viewHeight - rect.bottom;
|
||||||
|
} else {
|
||||||
|
mVerticalScrollEdge = VERTICAL_EDGE_NONE;
|
||||||
|
}
|
||||||
|
final int viewWidth = getImageViewWidth(mImageView);
|
||||||
|
if (width <= viewWidth) {
|
||||||
|
switch (mScaleType) {
|
||||||
|
case FIT_START:
|
||||||
|
deltaX = -rect.left;
|
||||||
|
break;
|
||||||
|
case FIT_END:
|
||||||
|
deltaX = viewWidth - width - rect.left;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
deltaX = (viewWidth - width) / 2 - rect.left;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mHorizontalScrollEdge = HORIZONTAL_EDGE_BOTH;
|
||||||
|
} else if (rect.left > 0) {
|
||||||
|
mHorizontalScrollEdge = HORIZONTAL_EDGE_LEFT;
|
||||||
|
deltaX = -rect.left;
|
||||||
|
} else if (rect.right < viewWidth) {
|
||||||
|
deltaX = viewWidth - rect.right;
|
||||||
|
mHorizontalScrollEdge = HORIZONTAL_EDGE_RIGHT;
|
||||||
|
} else {
|
||||||
|
mHorizontalScrollEdge = HORIZONTAL_EDGE_NONE;
|
||||||
|
}
|
||||||
|
// Finally actually translate the matrix
|
||||||
|
mSuppMatrix.postTranslate(deltaX, deltaY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getImageViewWidth(ImageView imageView) {
|
||||||
|
return imageView.getWidth() - imageView.getPaddingLeft() - imageView.getPaddingRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getImageViewHeight(ImageView imageView) {
|
||||||
|
return imageView.getHeight() - imageView.getPaddingTop() - imageView.getPaddingBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelFling() {
|
||||||
|
if (mCurrentFlingRunnable != null) {
|
||||||
|
mCurrentFlingRunnable.cancelFling();
|
||||||
|
mCurrentFlingRunnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AnimatedZoomRunnable implements Runnable {
|
||||||
|
private final float mFocalX;
|
||||||
|
private final float mFocalY;
|
||||||
|
private final long mStartTime;
|
||||||
|
private final float mZoomStart;
|
||||||
|
private final float mZoomEnd;
|
||||||
|
|
||||||
|
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) {
|
||||||
|
mFocalX = focalX;
|
||||||
|
mFocalY = focalY;
|
||||||
|
mStartTime = System.currentTimeMillis();
|
||||||
|
mZoomStart = currentZoom;
|
||||||
|
mZoomEnd = targetZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
float t = interpolate();
|
||||||
|
float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
|
||||||
|
float deltaScale = scale / getScale();
|
||||||
|
onGestureListener.onScale(deltaScale, mFocalX, mFocalY);
|
||||||
|
// We haven't hit our target scale yet, so post ourselves again
|
||||||
|
if (t < 1f) {
|
||||||
|
Compat.postOnAnimation(mImageView, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float interpolate() {
|
||||||
|
float t = 1f * (System.currentTimeMillis() - mStartTime) / mZoomDuration;
|
||||||
|
t = Math.min(1f, t);
|
||||||
|
t = mInterpolator.getInterpolation(t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FlingRunnable implements Runnable {
|
||||||
|
private final OverScroller mScroller;
|
||||||
|
private int mCurrentX;
|
||||||
|
private int mCurrentY;
|
||||||
|
|
||||||
|
public FlingRunnable(Context context) {
|
||||||
|
mScroller = new OverScroller(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelFling() {
|
||||||
|
mScroller.forceFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
|
||||||
|
final RectF rect = getDisplayRect();
|
||||||
|
if (rect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int startX = Math.round(-rect.left);
|
||||||
|
final int minX;
|
||||||
|
final int maxX;
|
||||||
|
final int minY;
|
||||||
|
final int maxY;
|
||||||
|
if (viewWidth < rect.width()) {
|
||||||
|
minX = 0;
|
||||||
|
maxX = Math.round(rect.width() - viewWidth);
|
||||||
|
} else {
|
||||||
|
minX = maxX = startX;
|
||||||
|
}
|
||||||
|
final int startY = Math.round(-rect.top);
|
||||||
|
if (viewHeight < rect.height()) {
|
||||||
|
minY = 0;
|
||||||
|
maxY = Math.round(rect.height() - viewHeight);
|
||||||
|
} else {
|
||||||
|
minY = maxY = startY;
|
||||||
|
}
|
||||||
|
mCurrentX = startX;
|
||||||
|
mCurrentY = startY;
|
||||||
|
// If we actually can move, fling the scroller
|
||||||
|
if (startX != maxX || startY != maxY) {
|
||||||
|
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mScroller.isFinished()) {
|
||||||
|
return; // remaining post that should not be handled
|
||||||
|
}
|
||||||
|
if (mScroller.computeScrollOffset()) {
|
||||||
|
final int newX = mScroller.getCurrX();
|
||||||
|
final int newY = mScroller.getCurrY();
|
||||||
|
mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
|
||||||
|
checkAndDisplayMatrix();
|
||||||
|
mCurrentX = newX;
|
||||||
|
mCurrentY = newY;
|
||||||
|
// Post On animation
|
||||||
|
Compat.postOnAnimation(mImageView, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.photoview;
|
||||||
|
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
class Util {
|
||||||
|
|
||||||
|
static void checkZoomLevels(float minZoom, float midZoom,
|
||||||
|
float maxZoom) {
|
||||||
|
if (minZoom >= midZoom) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Minimum zoom has to be less than Medium zoom. Call setMinimumZoom() with a more appropriate value");
|
||||||
|
} else if (midZoom >= maxZoom) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Medium zoom has to be less than Maximum zoom. Call setMaximumZoom() with a more appropriate value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hasDrawable(ImageView imageView) {
|
||||||
|
return imageView.getDrawable() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isSupportedScaleType(final ImageView.ScaleType scaleType) {
|
||||||
|
if (scaleType == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (scaleType) {
|
||||||
|
case MATRIX:
|
||||||
|
throw new IllegalStateException("Matrix scale type is not supported");
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getPointerIndex(int action) {
|
||||||
|
return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.scroller;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||||
|
import androidx.recyclerview.widget.OrientationHelper;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
public class CenteredSmoothScroller extends LinearSmoothScroller {
|
||||||
|
|
||||||
|
public CenteredSmoothScroller(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
|
||||||
|
RecyclerView.LayoutManager layoutManager = getLayoutManager();
|
||||||
|
if (layoutManager == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int distance = calculateDistanceToCenter(targetView, layoutManager);
|
||||||
|
int time = calculateTimeForDeceleration(distance);
|
||||||
|
if (time > 0) {
|
||||||
|
action.update(0, distance, time, mDecelerateInterpolator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateDistanceToCenter(View targetView, RecyclerView.LayoutManager layoutManager) {
|
||||||
|
OrientationHelper helper = OrientationHelper.createVerticalHelper(layoutManager);
|
||||||
|
int childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2;
|
||||||
|
int containerCenter;
|
||||||
|
if (layoutManager.getClipToPadding()) {
|
||||||
|
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
|
||||||
|
} else {
|
||||||
|
containerCenter = helper.getEnd() / 2;
|
||||||
|
}
|
||||||
|
return childCenter - containerCenter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
|
||||||
|
|
||||||
|
public class Attributes {
|
||||||
|
public enum Mode { Single, Multiple }
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
|
||||||
|
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class RecyclerSwipeAdapter<VH extends RecyclerView.ViewHolder>
|
||||||
|
extends RecyclerView.Adapter<VH> implements SwipeItemMangerInterface, SwipeAdapterInterface {
|
||||||
|
public SwipeItemMangerImpl mItemManger = new SwipeItemMangerImpl(this);
|
||||||
|
|
||||||
|
@Override public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
|
||||||
|
|
||||||
|
@Override public abstract void onBindViewHolder(VH viewHolder, final int position);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDatasetChanged() {
|
||||||
|
super.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifySwipeItemChanged(int position) {
|
||||||
|
super.notifyItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openItem(int position) {
|
||||||
|
mItemManger.openItem(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeItem(int position) {
|
||||||
|
mItemManger.closeItem(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeAllExcept(SwipeLayout layout) {
|
||||||
|
mItemManger.closeAllExcept(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeAllSwipeItems() {
|
||||||
|
mItemManger.closeAllSwipeItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> getOpenItems() {
|
||||||
|
return mItemManger.getOpenItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SwipeLayout> getOpenLayouts() {
|
||||||
|
return mItemManger.getOpenLayouts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeShownLayouts(SwipeLayout layout) {
|
||||||
|
mItemManger.removeShownLayouts(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen(int position) {
|
||||||
|
return mItemManger.isOpen(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Attributes.Mode getMode() {
|
||||||
|
return mItemManger.getMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMode(Attributes.Mode mode) {
|
||||||
|
mItemManger.setMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void switchAllSwipeEnable(boolean enable) {
|
||||||
|
mItemManger.switchAllSwipeEnable(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSwipeEnabled(boolean enabled) {
|
||||||
|
mItemManger.setSwipeEnabled(enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
|
||||||
|
|
||||||
|
public class SimpleSwipeListener implements SwipeLayout.SwipeListener {
|
||||||
|
@Override
|
||||||
|
public void onStartOpen(SwipeLayout layout) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(SwipeLayout layout) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartClose(SwipeLayout layout) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(SwipeLayout layout) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(SwipeLayout layout, int leftOffset, int topOffset) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHandRelease(SwipeLayout layout, float xvel, float yvel) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
|
||||||
|
|
||||||
|
public interface SwipeAdapterInterface {
|
||||||
|
int getSwipeLayoutResourceId(int position);
|
||||||
|
|
||||||
|
void notifyDatasetChanged();
|
||||||
|
|
||||||
|
void notifySwipeItemChanged(int position);
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class SwipeItemMangerImpl implements SwipeItemMangerInterface {
|
||||||
|
private Attributes.Mode mode = Attributes.Mode.Single;
|
||||||
|
public static final int INVALID_POSITION = -1;
|
||||||
|
|
||||||
|
protected int mOpenPosition = INVALID_POSITION;
|
||||||
|
|
||||||
|
protected Set<Integer> mOpenPositions = new HashSet<Integer>();
|
||||||
|
protected Set<SwipeLayout> mShownLayouts = new HashSet<SwipeLayout>();
|
||||||
|
|
||||||
|
protected boolean isSwipeEnabled = true;
|
||||||
|
protected SwipeAdapterInterface swipeAdapterInterface;
|
||||||
|
|
||||||
|
public SwipeItemMangerImpl(SwipeAdapterInterface swipeAdapterInterface) {
|
||||||
|
if (swipeAdapterInterface == null) {
|
||||||
|
throw new IllegalArgumentException("SwipeAdapterInterface can not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.swipeAdapterInterface = swipeAdapterInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Attributes.Mode getMode() {
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMode(Attributes.Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
mOpenPositions.clear();
|
||||||
|
mShownLayouts.clear();
|
||||||
|
mOpenPosition = INVALID_POSITION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(View view, int position) {
|
||||||
|
int resId = swipeAdapterInterface.getSwipeLayoutResourceId(position);
|
||||||
|
SwipeLayout swipeLayout = (SwipeLayout) view.findViewById(resId);
|
||||||
|
if (swipeLayout == null) {
|
||||||
|
throw new IllegalStateException("can not find SwipeLayout in target view");
|
||||||
|
}
|
||||||
|
|
||||||
|
swipeLayout.setSwipeEnabled(isSwipeEnabled);
|
||||||
|
if (swipeLayout.getTag(resId) == null) {
|
||||||
|
OnLayoutListener onLayoutListener = new OnLayoutListener(position);
|
||||||
|
SwipeMemory swipeMemory = new SwipeMemory(position);
|
||||||
|
swipeLayout.addSwipeListener(swipeMemory);
|
||||||
|
swipeLayout.addOnLayoutListener(onLayoutListener);
|
||||||
|
swipeLayout.setTag(resId, new ValueBox(position, swipeMemory, onLayoutListener));
|
||||||
|
mShownLayouts.add(swipeLayout);
|
||||||
|
} else {
|
||||||
|
ValueBox valueBox = (ValueBox) swipeLayout.getTag(resId);
|
||||||
|
valueBox.swipeMemory.setPosition(position);
|
||||||
|
valueBox.onLayoutListener.setPosition(position);
|
||||||
|
valueBox.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openItem(int position) {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
if (!mOpenPositions.contains(position)) {
|
||||||
|
mOpenPositions.add(position);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mOpenPosition = position;
|
||||||
|
}
|
||||||
|
swipeAdapterInterface.notifySwipeItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeItem(int position) {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
mOpenPositions.remove(position);
|
||||||
|
} else {
|
||||||
|
if (mOpenPosition == position) {
|
||||||
|
mOpenPosition = INVALID_POSITION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
swipeAdapterInterface.notifySwipeItemChanged(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeAllExcept(SwipeLayout layout) {
|
||||||
|
for (SwipeLayout s : mShownLayouts) {
|
||||||
|
if (s != layout) {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeAllSwipeItems() {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
mOpenPositions.clear();
|
||||||
|
} else {
|
||||||
|
mOpenPosition = INVALID_POSITION;
|
||||||
|
}
|
||||||
|
for (SwipeLayout s : mShownLayouts) {
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void switchAllSwipeEnable(boolean enable) {
|
||||||
|
for (SwipeLayout s : mShownLayouts) {
|
||||||
|
s.setSwipeEnabled(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeShownLayouts(SwipeLayout layout) {
|
||||||
|
mShownLayouts.remove(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> getOpenItems() {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
return new ArrayList<Integer>(mOpenPositions);
|
||||||
|
} else {
|
||||||
|
return Collections.singletonList(mOpenPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SwipeLayout> getOpenLayouts() {
|
||||||
|
return new ArrayList<SwipeLayout>(mShownLayouts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen(int position) {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
return mOpenPositions.contains(position);
|
||||||
|
} else {
|
||||||
|
return mOpenPosition == position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSwipeEnabled(boolean swipeEnabled) {
|
||||||
|
isSwipeEnabled = swipeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValueBox {
|
||||||
|
OnLayoutListener onLayoutListener;
|
||||||
|
SwipeMemory swipeMemory;
|
||||||
|
int position;
|
||||||
|
|
||||||
|
ValueBox(int position, SwipeMemory swipeMemory, OnLayoutListener onLayoutListener) {
|
||||||
|
this.swipeMemory = swipeMemory;
|
||||||
|
this.onLayoutListener = onLayoutListener;
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OnLayoutListener implements SwipeLayout.OnLayout {
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
OnLayoutListener(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLayout(SwipeLayout v) {
|
||||||
|
if (isOpen(position)) {
|
||||||
|
v.open(false, false);
|
||||||
|
} else {
|
||||||
|
v.close(false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwipeMemory extends SimpleSwipeListener {
|
||||||
|
private int position;
|
||||||
|
|
||||||
|
SwipeMemory(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose(SwipeLayout layout) {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
mOpenPositions.remove(position);
|
||||||
|
} else {
|
||||||
|
mOpenPosition = INVALID_POSITION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartOpen(SwipeLayout layout) {
|
||||||
|
if (mode == Attributes.Mode.Single) {
|
||||||
|
closeAllExcept(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen(SwipeLayout layout) {
|
||||||
|
if (mode == Attributes.Mode.Multiple) {
|
||||||
|
mOpenPositions.add(position);
|
||||||
|
} else {
|
||||||
|
closeAllExcept(layout);
|
||||||
|
mOpenPosition = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.swipe;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface SwipeItemMangerInterface {
|
||||||
|
void openItem(int position);
|
||||||
|
|
||||||
|
void closeItem(int position);
|
||||||
|
|
||||||
|
void closeAllExcept(SwipeLayout layout);
|
||||||
|
|
||||||
|
void closeAllSwipeItems();
|
||||||
|
|
||||||
|
List<Integer> getOpenItems();
|
||||||
|
|
||||||
|
List<SwipeLayout> getOpenLayouts();
|
||||||
|
|
||||||
|
void removeShownLayouts(SwipeLayout layout);
|
||||||
|
|
||||||
|
boolean isOpen(int position);
|
||||||
|
|
||||||
|
Attributes.Mode getMode();
|
||||||
|
|
||||||
|
void setMode(Attributes.Mode mode);
|
||||||
|
|
||||||
|
void switchAllSwipeEnable(boolean enable);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface IPlayer {
|
||||||
|
void setOnPreparedListener(final OnPreparedListener l);
|
||||||
|
|
||||||
|
void setOnErrorListener(final OnErrorListener l);
|
||||||
|
|
||||||
|
void setOnCompletionListener(final OnCompletionListener l);
|
||||||
|
|
||||||
|
void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l);
|
||||||
|
|
||||||
|
void setOnSeekCompleteListener(final OnSeekCompleteListener l);
|
||||||
|
|
||||||
|
void setOnInfoListener(final OnInfoListener l);
|
||||||
|
|
||||||
|
void setDisplay(SurfaceHolder sh);
|
||||||
|
|
||||||
|
void setSurface(Surface sh);
|
||||||
|
|
||||||
|
void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
|
||||||
|
|
||||||
|
void prepareAsync();
|
||||||
|
|
||||||
|
void release();
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
boolean isPlaying();
|
||||||
|
|
||||||
|
int getVideoWidth();
|
||||||
|
|
||||||
|
int getVideoHeight();
|
||||||
|
|
||||||
|
void seekTo(int progress);
|
||||||
|
|
||||||
|
int getCurrentPosition();
|
||||||
|
|
||||||
|
int getDuration();
|
||||||
|
|
||||||
|
interface OnPreparedListener {
|
||||||
|
void onPrepared(IPlayer mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnErrorListener {
|
||||||
|
boolean onError(IPlayer mp, int what, int extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnCompletionListener {
|
||||||
|
void onCompletion(IPlayer mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnVideoSizeChangedListener {
|
||||||
|
void onVideoSizeChanged(IPlayer mp, int width, int height);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnInfoListener {
|
||||||
|
void onInfo(IPlayer mp, int what, int extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnSeekCompleteListener {
|
||||||
|
void onSeekComplete(IPlayer mp);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class MediaPlayerProxy implements IPlayer {
|
||||||
|
private static final String TAG = MediaPlayerProxy.class.getSimpleName();
|
||||||
|
|
||||||
|
private IPlayer mMediaPlayer;
|
||||||
|
|
||||||
|
public MediaPlayerProxy() {
|
||||||
|
mMediaPlayer = new SystemMediaPlayerWrapper();
|
||||||
|
Log.i(TAG, "use mMediaPlayer: " + mMediaPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnPreparedListener(final OnPreparedListener l) {
|
||||||
|
mMediaPlayer.setOnPreparedListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnErrorListener(final OnErrorListener l) {
|
||||||
|
mMediaPlayer.setOnErrorListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnCompletionListener(final OnCompletionListener l) {
|
||||||
|
mMediaPlayer.setOnCompletionListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l) {
|
||||||
|
mMediaPlayer.setOnVideoSizeChangedListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnSeekCompleteListener(OnSeekCompleteListener l) {
|
||||||
|
mMediaPlayer.setOnSeekCompleteListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnInfoListener(final OnInfoListener l) {
|
||||||
|
mMediaPlayer.setOnInfoListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDisplay(SurfaceHolder sh) {
|
||||||
|
mMediaPlayer.setDisplay(sh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSurface(Surface sh) {
|
||||||
|
mMediaPlayer.setSurface(sh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
|
||||||
|
mMediaPlayer.setDataSource(context, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareAsync() {
|
||||||
|
mMediaPlayer.prepareAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
mMediaPlayer.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
mMediaPlayer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
mMediaPlayer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
mMediaPlayer.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return mMediaPlayer.isPlaying();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVideoWidth() {
|
||||||
|
return mMediaPlayer.getVideoWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVideoHeight() {
|
||||||
|
return mMediaPlayer.getVideoHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(int progress) {
|
||||||
|
mMediaPlayer.seekTo(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentPosition() {
|
||||||
|
return mMediaPlayer.getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDuration() {
|
||||||
|
return mMediaPlayer.getDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.media.MediaPlayer;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SystemMediaPlayerWrapper implements IPlayer {
|
||||||
|
private MediaPlayer mMediaPlayer;
|
||||||
|
|
||||||
|
public SystemMediaPlayerWrapper() {
|
||||||
|
mMediaPlayer = new MediaPlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnPreparedListener(final OnPreparedListener l) {
|
||||||
|
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||||
|
@Override
|
||||||
|
public void onPrepared(MediaPlayer mp) {
|
||||||
|
l.onPrepared(SystemMediaPlayerWrapper.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnErrorListener(final OnErrorListener l) {
|
||||||
|
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||||
|
return l.onError(SystemMediaPlayerWrapper.this, what, extra);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnCompletionListener(final OnCompletionListener l) {
|
||||||
|
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||||
|
@Override
|
||||||
|
public void onCompletion(MediaPlayer mp) {
|
||||||
|
l.onCompletion(SystemMediaPlayerWrapper.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnSeekCompleteListener(final OnSeekCompleteListener l) {
|
||||||
|
mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
|
||||||
|
@Override
|
||||||
|
public void onSeekComplete(MediaPlayer mediaPlayer) {
|
||||||
|
l.onSeekComplete(SystemMediaPlayerWrapper.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnVideoSizeChangedListener(final OnVideoSizeChangedListener l) {
|
||||||
|
mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
|
||||||
|
l.onVideoSizeChanged(SystemMediaPlayerWrapper.this, width, height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnInfoListener(final OnInfoListener l) {
|
||||||
|
mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onInfo(MediaPlayer mp, int what, int extra) {
|
||||||
|
l.onInfo(SystemMediaPlayerWrapper.this, what, extra);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDisplay(SurfaceHolder sh) {
|
||||||
|
mMediaPlayer.setDisplay(sh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSurface(Surface sh) {
|
||||||
|
mMediaPlayer.setSurface(sh);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
|
||||||
|
mMediaPlayer.setDataSource(context, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareAsync() {
|
||||||
|
mMediaPlayer.prepareAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
mMediaPlayer.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
mMediaPlayer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
mMediaPlayer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
mMediaPlayer.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return mMediaPlayer.isPlaying();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVideoWidth() {
|
||||||
|
return mMediaPlayer.getVideoWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVideoHeight() {
|
||||||
|
return mMediaPlayer.getVideoHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(int progress) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
mMediaPlayer.seekTo(progress, MediaPlayer.SEEK_CLOSEST);
|
||||||
|
} else {
|
||||||
|
mMediaPlayer.seekTo(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentPosition() {
|
||||||
|
return mMediaPlayer.getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDuration() {
|
||||||
|
return mMediaPlayer.getDuration();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ScaleGestureDetector;
|
||||||
|
import android.view.TextureView;
|
||||||
|
import android.view.VelocityTracker;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.OverScroller;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils;
|
||||||
|
|
||||||
|
public class VideoGestureScaleAttacher {
|
||||||
|
private static final float EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD = 50f;
|
||||||
|
private static final int EDGE_NONE = -1;
|
||||||
|
private static final int EDGE_LEFT = 0;
|
||||||
|
private static final int EDGE_RIGHT = 1;
|
||||||
|
private static final int EDGE_BOTH = 2;
|
||||||
|
private static final float DEFAULT_MAX_SCALE = 3.0f;
|
||||||
|
private static final float DEFAULT_MID_SCALE = 1.75f;
|
||||||
|
private static final float DEFAULT_MIN_SCALE = 1.0f;
|
||||||
|
private static final int DEFAULT_ZOOM_DURATION = 200;
|
||||||
|
private ScaleGestureDetector scaleGestureDetector;
|
||||||
|
private float mTouchSlop;
|
||||||
|
private float mMinimumVelocity;
|
||||||
|
private VelocityTracker mVelocityTracker;
|
||||||
|
private boolean mIsDragging;
|
||||||
|
private float mLastTouchX;
|
||||||
|
private float mLastTouchY;
|
||||||
|
private OnScaleListener internalScaleListener;
|
||||||
|
private VideoView view;
|
||||||
|
private float minScale = DEFAULT_MIN_SCALE;
|
||||||
|
private float middleScale = DEFAULT_MID_SCALE;
|
||||||
|
private float maxScale = DEFAULT_MAX_SCALE;
|
||||||
|
private int scrollEdge;
|
||||||
|
private final Matrix transferMatrix = new Matrix();
|
||||||
|
private final RectF rectF = new RectF();
|
||||||
|
private final float[] mMatrixValues = new float[9];
|
||||||
|
private ImageView.ScaleType scaleType = ImageView.ScaleType.FIT_CENTER;
|
||||||
|
private final Interpolator interpolator = new AccelerateDecelerateInterpolator();
|
||||||
|
|
||||||
|
private int zoomDuration = DEFAULT_ZOOM_DURATION;
|
||||||
|
|
||||||
|
private FlingRunnable currentFlingRunnable;
|
||||||
|
|
||||||
|
private VideoGestureScaleAttacher() {}
|
||||||
|
|
||||||
|
public static void attach(VideoView view) {
|
||||||
|
VideoGestureScaleAttacher attacher = new VideoGestureScaleAttacher();
|
||||||
|
attacher.view = view;
|
||||||
|
if (view == null || view.getContext() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Context context = view.getContext();
|
||||||
|
final ViewConfiguration configuration = ViewConfiguration.get(context);
|
||||||
|
attacher.mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
|
||||||
|
attacher.mTouchSlop = configuration.getScaledTouchSlop();
|
||||||
|
attacher.internalScaleListener = new OnScaleListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onScale(ScaleGestureDetector detector) {
|
||||||
|
float scaleFactor = detector.getScaleFactor();
|
||||||
|
if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
float focusX = detector.getFocusX();
|
||||||
|
float focusY = detector.getFocusY();
|
||||||
|
return attacher.scale(scaleFactor, focusX, focusY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFling(float startX, float startY, float velocityX, float velocityY) {
|
||||||
|
if (attacher.scaleGestureDetector.isInProgress()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attacher.currentFlingRunnable = attacher.new FlingRunnable(attacher.view);
|
||||||
|
attacher.currentFlingRunnable.fling(attacher.getViewWidth(), attacher.getViewHeight(), (int) velocityX, (int) velocityY);
|
||||||
|
ThreadUtils.runOnUiThread(attacher.currentFlingRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrag(float dx, float dy) {
|
||||||
|
if (attacher.scaleGestureDetector.isInProgress()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
attacher.transferMatrix.postTranslate(dx, dy);
|
||||||
|
attacher.invalidateView();
|
||||||
|
attacher.checkMatrixBounds();
|
||||||
|
if (attacher.scrollEdge == EDGE_BOTH && Math.abs(dx) >= EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD
|
||||||
|
|| (attacher.scrollEdge == EDGE_LEFT && dx >= EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD)
|
||||||
|
|| (attacher.scrollEdge == EDGE_RIGHT && dx <= -EDGE_DRAG_EVENT_INTERCEPT_THRESHOLD)) {
|
||||||
|
ViewParent viewParent = view.getParent();
|
||||||
|
if (viewParent != null) {
|
||||||
|
viewParent.requestDisallowInterceptTouchEvent(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ViewParent viewParent = view.getParent();
|
||||||
|
if (viewParent != null) {
|
||||||
|
viewParent.requestDisallowInterceptTouchEvent(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
attacher.scaleGestureDetector = new ScaleGestureDetector(context, attacher.internalScaleListener);
|
||||||
|
|
||||||
|
view.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
attacher.scaleGestureDetector.onTouchEvent(event);
|
||||||
|
attacher.processTouchEvent(event);
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN: {
|
||||||
|
attacher.cancelFling();
|
||||||
|
ViewParent viewParent = view.getParent();
|
||||||
|
if (viewParent != null) {
|
||||||
|
viewParent.requestDisallowInterceptTouchEvent(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
onActionCancel();
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP: {
|
||||||
|
onActionCancel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onActionCancel() {
|
||||||
|
if (attacher.getScale() < attacher.minScale) {
|
||||||
|
RectF rect = attacher.getDisplayRect();
|
||||||
|
view.post(attacher.new AnimatedZoomRunnable(attacher.getScale(), attacher.minScale, rect.centerX(), rect.centerY()));
|
||||||
|
} else if (attacher.getScale() > attacher.maxScale) {
|
||||||
|
RectF rect = attacher.getDisplayRect();
|
||||||
|
view.post(attacher.new AnimatedZoomRunnable(attacher.getScale(), attacher.maxScale, rect.centerX(), rect.centerY()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelFling() {
|
||||||
|
if (currentFlingRunnable != null) {
|
||||||
|
currentFlingRunnable.cancelFling();
|
||||||
|
currentFlingRunnable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scale(float scaleFactor, float focusX, float focusY) {
|
||||||
|
if ((getScale() <= maxScale || scaleFactor < 1f) && (getScale() >= minScale || scaleFactor > 1f)) {
|
||||||
|
transferMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
|
||||||
|
invalidateView();
|
||||||
|
checkMatrixBounds();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invalidateView() {
|
||||||
|
Matrix baseMatrix = view.getBaseMatrix();
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.setConcat(baseMatrix, transferMatrix);
|
||||||
|
view.setTransform(matrix);
|
||||||
|
view.invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processTouchEvent(MotionEvent ev) {
|
||||||
|
final int action = ev.getAction();
|
||||||
|
switch (action & MotionEvent.ACTION_MASK) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
|
||||||
|
mVelocityTracker = VelocityTracker.obtain();
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.addMovement(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastTouchX = ev.getX();
|
||||||
|
mLastTouchY = ev.getY();
|
||||||
|
mIsDragging = false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final float x = ev.getX();
|
||||||
|
final float y = ev.getY();
|
||||||
|
final float dx = x - mLastTouchX;
|
||||||
|
final float dy = y - mLastTouchY;
|
||||||
|
|
||||||
|
if (!mIsDragging) {
|
||||||
|
// Use Pythagoras to see if drag length is larger than
|
||||||
|
// touch slop
|
||||||
|
mIsDragging = Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsDragging) {
|
||||||
|
internalScaleListener.onDrag(dx, dy);
|
||||||
|
mLastTouchX = x;
|
||||||
|
mLastTouchY = y;
|
||||||
|
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.addMovement(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
// Recycle Velocity Tracker
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
if (mIsDragging) {
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mLastTouchX = ev.getX();
|
||||||
|
mLastTouchY = ev.getY();
|
||||||
|
|
||||||
|
// Compute velocity within the last 1000ms
|
||||||
|
mVelocityTracker.addMovement(ev);
|
||||||
|
mVelocityTracker.computeCurrentVelocity(1000);
|
||||||
|
|
||||||
|
final float vX = mVelocityTracker.getXVelocity();
|
||||||
|
final float vY = mVelocityTracker.getYVelocity();
|
||||||
|
|
||||||
|
// If the velocity is greater than minVelocity, call
|
||||||
|
// listener
|
||||||
|
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
|
||||||
|
internalScaleListener.onFling(mLastTouchX, mLastTouchY, -vX, -vY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recycle Velocity Tracker
|
||||||
|
if (null != mVelocityTracker) {
|
||||||
|
mVelocityTracker.recycle();
|
||||||
|
mVelocityTracker = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectF getDisplayRect() {
|
||||||
|
rectF.set(0, 0, view.getWidth(), view.getHeight());
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
view.getTransform(matrix);
|
||||||
|
matrix.mapRect(rectF);
|
||||||
|
return rectF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScale() {
|
||||||
|
return (float) Math.sqrt((Math.pow(getValue(transferMatrix, Matrix.MSCALE_X), 2) + Math.pow(getValue(transferMatrix, Matrix.MSCALE_Y), 2)) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMatrixBounds() {
|
||||||
|
final RectF rect = getDisplayRect();
|
||||||
|
final float height = rect.height();
|
||||||
|
final float width = rect.width();
|
||||||
|
float deltaX = 0;
|
||||||
|
float deltaY = 0;
|
||||||
|
final int viewHeight = getViewHeight();
|
||||||
|
if (height <= viewHeight) {
|
||||||
|
switch (scaleType) {
|
||||||
|
case FIT_START:
|
||||||
|
deltaY = -rect.top;
|
||||||
|
break;
|
||||||
|
case FIT_END:
|
||||||
|
deltaY = viewHeight - height - rect.top;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
deltaY = (viewHeight - height) / 2 - rect.top;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (rect.top > 0) {
|
||||||
|
deltaY = -rect.top;
|
||||||
|
} else if (rect.bottom < viewHeight) {
|
||||||
|
deltaY = viewHeight - rect.bottom;
|
||||||
|
}
|
||||||
|
final int viewWidth = getViewWidth();
|
||||||
|
if (width <= viewWidth) {
|
||||||
|
switch (scaleType) {
|
||||||
|
case FIT_START:
|
||||||
|
deltaX = -rect.left;
|
||||||
|
break;
|
||||||
|
case FIT_END:
|
||||||
|
deltaX = viewWidth - width - rect.left;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
deltaX = (viewWidth - width) / 2 - rect.left;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
scrollEdge = EDGE_BOTH;
|
||||||
|
} else if (rect.left > 0) {
|
||||||
|
scrollEdge = EDGE_LEFT;
|
||||||
|
deltaX = -rect.left;
|
||||||
|
} else if (rect.right < viewWidth) {
|
||||||
|
deltaX = viewWidth - rect.right;
|
||||||
|
scrollEdge = EDGE_RIGHT;
|
||||||
|
} else {
|
||||||
|
scrollEdge = EDGE_NONE;
|
||||||
|
}
|
||||||
|
// Finally actually translate the matrix
|
||||||
|
transferMatrix.postTranslate(deltaX, deltaY);
|
||||||
|
invalidateView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getViewWidth() {
|
||||||
|
return view.getWidth() - view.getPaddingLeft() - view.getPaddingRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getViewHeight() {
|
||||||
|
return view.getHeight() - view.getPaddingTop() - view.getPaddingBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that 'unpacks' a Matrix and returns the required value
|
||||||
|
*
|
||||||
|
* @param matrix Matrix to unpack
|
||||||
|
* @param whichValue Which value from Matrix.M* to return
|
||||||
|
* @return returned value
|
||||||
|
*/
|
||||||
|
private float getValue(Matrix matrix, int whichValue) {
|
||||||
|
matrix.getValues(mMatrixValues);
|
||||||
|
return mMatrixValues[whichValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AnimatedZoomRunnable implements Runnable {
|
||||||
|
private final float focalX;
|
||||||
|
private final float focalY;
|
||||||
|
private final long startTime;
|
||||||
|
private final float zoomStart;
|
||||||
|
private final float zoomEnd;
|
||||||
|
|
||||||
|
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, final float focalX, final float focalY) {
|
||||||
|
this.focalX = focalX;
|
||||||
|
this.focalY = focalY;
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
zoomStart = currentZoom;
|
||||||
|
zoomEnd = targetZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
float t = interpolate();
|
||||||
|
float scale = zoomStart + t * (zoomEnd - zoomStart);
|
||||||
|
float deltaScale = scale / getScale();
|
||||||
|
scale(deltaScale, focalX, focalY);
|
||||||
|
// We haven't hit our target scale yet, so post ourselves again
|
||||||
|
if (t < 1f) {
|
||||||
|
view.postOnAnimation(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float interpolate() {
|
||||||
|
float t = 1f * (System.currentTimeMillis() - startTime) / zoomDuration;
|
||||||
|
t = Math.min(1f, t);
|
||||||
|
t = interpolator.getInterpolation(t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FlingRunnable implements Runnable {
|
||||||
|
private final OverScroller mScroller;
|
||||||
|
private int mCurrentX;
|
||||||
|
private int mCurrentY;
|
||||||
|
private TextureView view;
|
||||||
|
|
||||||
|
public FlingRunnable(TextureView view) {
|
||||||
|
mScroller = new OverScroller(view.getContext());
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelFling() {
|
||||||
|
mScroller.forceFinished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
|
||||||
|
final RectF rect = getDisplayRect();
|
||||||
|
if (rect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int startX = Math.round(-rect.left);
|
||||||
|
final int minX;
|
||||||
|
final int maxX;
|
||||||
|
final int minY;
|
||||||
|
final int maxY;
|
||||||
|
if (viewWidth < rect.width()) {
|
||||||
|
minX = 0;
|
||||||
|
maxX = Math.round(rect.width() - viewWidth);
|
||||||
|
} else {
|
||||||
|
minX = maxX = startX;
|
||||||
|
}
|
||||||
|
final int startY = Math.round(-rect.top);
|
||||||
|
if (viewHeight < rect.height()) {
|
||||||
|
minY = 0;
|
||||||
|
maxY = Math.round(rect.height() - viewHeight);
|
||||||
|
} else {
|
||||||
|
minY = maxY = startY;
|
||||||
|
}
|
||||||
|
mCurrentX = startX;
|
||||||
|
mCurrentY = startY;
|
||||||
|
// If we actually can move, fling the scroller
|
||||||
|
if (startX != maxX || startY != maxY) {
|
||||||
|
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (mScroller.isFinished()) {
|
||||||
|
return; // remaining post that should not be handled
|
||||||
|
}
|
||||||
|
if (mScroller.computeScrollOffset()) {
|
||||||
|
final int newX = mScroller.getCurrX();
|
||||||
|
final int newY = mScroller.getCurrY();
|
||||||
|
transferMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
|
||||||
|
invalidateView();
|
||||||
|
checkMatrixBounds();
|
||||||
|
mCurrentX = newX;
|
||||||
|
mCurrentY = newY;
|
||||||
|
// Post On animation
|
||||||
|
view.postOnAnimation(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OnScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||||
|
public void onFling(float startX, float startY, float velocityX, float velocityY) {}
|
||||||
|
|
||||||
|
public void onDrag(float dx, float dy) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.component.videoview;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Matrix;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.TextureView;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class VideoView extends TextureView {
|
||||||
|
private static final String TAG = VideoView.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final int STATE_ERROR = -1;
|
||||||
|
public static final int STATE_IDLE = 0;
|
||||||
|
public static final int STATE_PREPARING = 1;
|
||||||
|
public static final int STATE_PREPARED = 2;
|
||||||
|
public static final int STATE_PLAYING = 3;
|
||||||
|
public static final int STATE_PAUSED = 4;
|
||||||
|
public static final int STATE_PLAYBACK_COMPLETED = 5;
|
||||||
|
public static final int STATE_STOPPED = 6;
|
||||||
|
|
||||||
|
private int mCurrentState = STATE_IDLE;
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
private Surface mSurface;
|
||||||
|
private MediaPlayerProxy mMediaPlayer;
|
||||||
|
|
||||||
|
private Uri mUri;
|
||||||
|
private int mVideoRotationDegree;
|
||||||
|
private Matrix baseMatrix = new Matrix();
|
||||||
|
private IPlayer.OnPreparedListener mOutOnPreparedListener;
|
||||||
|
private IPlayer.OnErrorListener mOutOnErrorListener;
|
||||||
|
private IPlayer.OnCompletionListener mOutOnCompletionListener;
|
||||||
|
private IPlayer.OnSeekCompleteListener mOnSeekCompleteListener;
|
||||||
|
private IPlayer.OnPreparedListener mOnPreparedListener = new IPlayer.OnPreparedListener() {
|
||||||
|
public void onPrepared(IPlayer mp) {
|
||||||
|
mCurrentState = STATE_PREPARED;
|
||||||
|
// Video fit center
|
||||||
|
float videoHeight = mp.getVideoHeight();
|
||||||
|
float videoWidth = mp.getVideoWidth();
|
||||||
|
float viewHeight = getHeight() - getPaddingBottom() - getPaddingTop();
|
||||||
|
float viewWidth = getWidth() - getPaddingLeft() - getPaddingRight();
|
||||||
|
|
||||||
|
float finalVideoHeight = viewHeight;
|
||||||
|
float finalVideoWidth = viewHeight * videoWidth / videoHeight;
|
||||||
|
|
||||||
|
if (finalVideoWidth > viewWidth) {
|
||||||
|
finalVideoWidth = viewWidth;
|
||||||
|
finalVideoHeight = viewWidth * videoHeight / videoWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
float scaleX = finalVideoWidth / viewWidth;
|
||||||
|
float scaleY = finalVideoHeight / viewHeight;
|
||||||
|
float dx = (viewWidth - finalVideoWidth) / 2;
|
||||||
|
float dy = (viewHeight - finalVideoHeight) / 2;
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.postScale(scaleX, scaleY);
|
||||||
|
matrix.postTranslate(dx, dy);
|
||||||
|
baseMatrix.set(matrix);
|
||||||
|
setTransform(matrix);
|
||||||
|
invalidate();
|
||||||
|
|
||||||
|
Log.i(TAG, "onPrepared mVideoWidth: " + videoWidth + " mVideoHeight: " + videoHeight + " mVideoRotationDegree: " + mVideoRotationDegree);
|
||||||
|
if (mOutOnPreparedListener != null) {
|
||||||
|
mOutOnPreparedListener.onPrepared(mp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private IPlayer.OnErrorListener mOnErrorListener = new IPlayer.OnErrorListener() {
|
||||||
|
public boolean onError(IPlayer mp, int what, int extra) {
|
||||||
|
Log.w(TAG, "onError: what/extra: " + what + "/" + extra);
|
||||||
|
mCurrentState = STATE_ERROR;
|
||||||
|
stopMedia();
|
||||||
|
if (mOutOnErrorListener != null) {
|
||||||
|
mOutOnErrorListener.onError(mp, what, extra);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private IPlayer.OnInfoListener mOnInfoListener = new IPlayer.OnInfoListener() {
|
||||||
|
public void onInfo(IPlayer mp, int what, int extra) {
|
||||||
|
Log.w(TAG, "onInfo: what/extra: " + what + "/" + extra);
|
||||||
|
if (what == 10001) { // IJK: MEDIA_INFO_VIDEO_ROTATION_CHANGED
|
||||||
|
mVideoRotationDegree = extra;
|
||||||
|
setRotation(mVideoRotationDegree);
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private IPlayer.OnCompletionListener mOnCompletionListener = new IPlayer.OnCompletionListener() {
|
||||||
|
public void onCompletion(IPlayer mp) {
|
||||||
|
Log.i(TAG, "onCompletion");
|
||||||
|
mCurrentState = STATE_PLAYBACK_COMPLETED;
|
||||||
|
if (mOutOnCompletionListener != null) {
|
||||||
|
mOutOnCompletionListener.onCompletion(mp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private IPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new IPlayer.OnVideoSizeChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(IPlayer mp, int width, int height) {
|
||||||
|
// TUIChatLog.i(TAG, "onVideoSizeChanged width: " + width + " height: " + height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private IPlayer.OnSeekCompleteListener onSeekCompleteListener = new IPlayer.OnSeekCompleteListener() {
|
||||||
|
@Override
|
||||||
|
public void onSeekComplete(IPlayer mp) {
|
||||||
|
if (mOnSeekCompleteListener != null) {
|
||||||
|
mOnSeekCompleteListener.onSeekComplete(mp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private SurfaceTextureListener mSurfaceTextureListener = new SurfaceTextureListener() {
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
|
||||||
|
Log.i(TAG, "onSurfaceTextureAvailable");
|
||||||
|
mSurface = new Surface(surface);
|
||||||
|
openVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
|
||||||
|
Log.i(TAG, "onSurfaceTextureSizeChanged");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
|
||||||
|
Log.i(TAG, "onSurfaceTextureDestroyed");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
|
||||||
|
// TUIChatLog.i(TAG,"onSurfaceTextureUpdated");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public VideoView(Context context) {
|
||||||
|
super(context);
|
||||||
|
initVideoView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
initVideoView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
initVideoView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initVideoView(Context context) {
|
||||||
|
Log.i(TAG, "initVideoView");
|
||||||
|
mContext = context;
|
||||||
|
setSurfaceTextureListener(mSurfaceTextureListener);
|
||||||
|
mCurrentState = STATE_IDLE;
|
||||||
|
|
||||||
|
VideoGestureScaleAttacher.attach(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix getBaseMatrix() {
|
||||||
|
return baseMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnPreparedListener(IPlayer.OnPreparedListener l) {
|
||||||
|
mOutOnPreparedListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnSeekCompleteListener(IPlayer.OnSeekCompleteListener l) {
|
||||||
|
mOnSeekCompleteListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnErrorListener(IPlayer.OnErrorListener l) {
|
||||||
|
mOutOnErrorListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnCompletionListener(IPlayer.OnCompletionListener l) {
|
||||||
|
mOutOnCompletionListener = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVideoURI(Uri uri) {
|
||||||
|
mUri = uri;
|
||||||
|
openVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetVideo() {
|
||||||
|
openVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openVideo() {
|
||||||
|
if (mUri == null) {
|
||||||
|
Log.e(TAG, "openVideo: mUri is null ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.i(TAG, "openVideo: mUri: " + mUri.getPath() + " mSurface: " + mSurface);
|
||||||
|
if (mSurface == null) {
|
||||||
|
Log.e(TAG, "openVideo: mSurface is null ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopMedia();
|
||||||
|
try {
|
||||||
|
mMediaPlayer = new MediaPlayerProxy();
|
||||||
|
mMediaPlayer.setOnPreparedListener(mOnPreparedListener);
|
||||||
|
mMediaPlayer.setOnCompletionListener(mOnCompletionListener);
|
||||||
|
mMediaPlayer.setOnErrorListener(mOnErrorListener);
|
||||||
|
mMediaPlayer.setOnInfoListener(mOnInfoListener);
|
||||||
|
mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);
|
||||||
|
mMediaPlayer.setOnSeekCompleteListener(onSeekCompleteListener);
|
||||||
|
mMediaPlayer.setSurface(mSurface);
|
||||||
|
mMediaPlayer.setDataSource(getContext(), mUri);
|
||||||
|
mMediaPlayer.prepareAsync();
|
||||||
|
mCurrentState = STATE_PREPARING;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.w(TAG, "ex = " + ex.getMessage());
|
||||||
|
mCurrentState = STATE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean start() {
|
||||||
|
Log.i(TAG, "start mCurrentState:" + mCurrentState);
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
mMediaPlayer.start();
|
||||||
|
mCurrentState = STATE_PLAYING;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean stop() {
|
||||||
|
Log.i(TAG, "stop mCurrentState:" + mCurrentState);
|
||||||
|
stopMedia();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean pause() {
|
||||||
|
Log.i(TAG, "pause mCurrentState:" + mCurrentState);
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
mMediaPlayer.pause();
|
||||||
|
mCurrentState = STATE_PAUSED;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopMedia() {
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
mMediaPlayer.stop();
|
||||||
|
mMediaPlayer.release();
|
||||||
|
mMediaPlayer = null;
|
||||||
|
mCurrentState = STATE_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentState() {
|
||||||
|
return mCurrentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlaying() {
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
return mMediaPlayer.isPlaying();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seekTo(int progress) {
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
mMediaPlayer.seekTo(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPrepared() {
|
||||||
|
if (mUri == null) {
|
||||||
|
Log.e(TAG, "isPrepared: mUri is null ");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log.i(TAG, "isPrepared: mUri: " + mUri.getPath() + " mSurface: " + mSurface);
|
||||||
|
if (mSurface == null) {
|
||||||
|
Log.e(TAG, "isPrepared: mSurface is null ");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentPosition() {
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
return mMediaPlayer.getCurrentPosition();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDuration() {
|
||||||
|
if (mMediaPlayer != null) {
|
||||||
|
return mMediaPlayer.getDuration();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
stopMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBackgroundDrawable(Drawable background) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N && background != null) {
|
||||||
|
super.setBackgroundDrawable(background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnClickListener(@Nullable OnClickListener l) {
|
||||||
|
super.setOnClickListener(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.config.classicui;
|
||||||
|
|
||||||
|
import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuicore.util.ToastUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
|
||||||
|
|
||||||
|
public class TUIConfigClassic {
|
||||||
|
private TUIConfigClassic() {}
|
||||||
|
|
||||||
|
private static final class TUIConfigClassicHolder {
|
||||||
|
private static final TUIConfigClassic INSTANCE = new TUIConfigClassic();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TUIConfigClassic getInstance() {
|
||||||
|
return TUIConfigClassicHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int UNDEFINED = -1;
|
||||||
|
// message bubble
|
||||||
|
private boolean enableMessageBubbleStyle = true;
|
||||||
|
private Drawable sendBubbleBackground;
|
||||||
|
private Drawable receiveBubbleBackground;
|
||||||
|
private Drawable sendErrorBubbleBackground;
|
||||||
|
private Drawable receiveErrorBubbleBackground;
|
||||||
|
|
||||||
|
// message style
|
||||||
|
private Drawable chatTimeBubble;
|
||||||
|
private int chatTimeFontSize = UNDEFINED;
|
||||||
|
private int chatTimeFontColor = UNDEFINED;
|
||||||
|
private Drawable defaultAvatarImage;
|
||||||
|
private int avatarRadius = UNDEFINED;
|
||||||
|
private int avatarSize = UNDEFINED;
|
||||||
|
private int receiveNickNameVisibility = UNDEFINED;
|
||||||
|
private int receiveNickNameFontSize = UNDEFINED;
|
||||||
|
private int receiveNickNameColor = UNDEFINED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default app directory.The default dir is "file".
|
||||||
|
* @param appDir
|
||||||
|
*/
|
||||||
|
public static void setDefaultAppDir(String appDir) {
|
||||||
|
TUIConfig.setDefaultAppDir(appDir);
|
||||||
|
TUIConfig.initPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether show the toast prompt built into TUIKit.The default value is true.
|
||||||
|
* @param enableToast
|
||||||
|
*/
|
||||||
|
public static void enableToast(boolean enableToast) {
|
||||||
|
ToastUtil.setEnableToast(enableToast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to enable language switching.The default value is false.
|
||||||
|
* @param enableLanguageSwitch
|
||||||
|
*/
|
||||||
|
public static void enableLanguageSwitch(boolean enableLanguageSwitch) {
|
||||||
|
TUIThemeManager.setEnableLanguageSwitch(enableLanguageSwitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the language of TUIKit.
|
||||||
|
* The currently supported languages are "en", "zh", and "ar".
|
||||||
|
* @param context
|
||||||
|
* @param targetLanguage
|
||||||
|
*/
|
||||||
|
public static void switchLanguageToTarget(Context context, String targetLanguage) {
|
||||||
|
TUIThemeManager.getInstance().changeLanguage(context, targetLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch theme to target.
|
||||||
|
* The currently supported themes are THEME_LIGHT, THEME_LIVELY, and THEME_SERIOUS.
|
||||||
|
* @param context
|
||||||
|
* @param themeID
|
||||||
|
*/
|
||||||
|
public static void switchThemeToTarget(Context context, int themeID) {
|
||||||
|
TUIThemeManager.getInstance().changeTheme(context, themeID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to enable message bubble style.The default value is true.
|
||||||
|
* @param enable
|
||||||
|
*/
|
||||||
|
public static void setEnableMessageBubbleStyle(boolean enable) {
|
||||||
|
getInstance().enableMessageBubbleStyle = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether to enable message bubble style.
|
||||||
|
* @return true is enable, false is not
|
||||||
|
*/
|
||||||
|
public static boolean isEnableMessageBubbleStyle() {
|
||||||
|
return getInstance().enableMessageBubbleStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background of the send message bubble.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setSendBubbleBackground(Drawable drawable) {
|
||||||
|
getInstance().sendBubbleBackground = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background of the send message bubble.
|
||||||
|
* @return the background
|
||||||
|
*/
|
||||||
|
public static Drawable getSendBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().sendBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background of the receive message bubble.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setReceiveBubbleBackground(Drawable drawable) {
|
||||||
|
getInstance().receiveBubbleBackground = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background of the receive message bubble.
|
||||||
|
* @return the background
|
||||||
|
*/
|
||||||
|
public static Drawable getReceiveBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().receiveBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background of the receive error message bubble.
|
||||||
|
* @param receiveErrorBubbleBackground
|
||||||
|
*/
|
||||||
|
public static void setReceiveErrorBubbleBackground(Drawable receiveErrorBubbleBackground) {
|
||||||
|
getInstance().receiveErrorBubbleBackground = receiveErrorBubbleBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background of the receive error message bubble.
|
||||||
|
* @return the background
|
||||||
|
*/
|
||||||
|
public static Drawable getReceiveErrorBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().receiveErrorBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background of the send error message bubble.
|
||||||
|
* @param sendErrorBubbleBackground
|
||||||
|
*/
|
||||||
|
public static void setSendErrorBubbleBackground(Drawable sendErrorBubbleBackground) {
|
||||||
|
getInstance().sendErrorBubbleBackground = sendErrorBubbleBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background of the send error message bubble.
|
||||||
|
* @return the background
|
||||||
|
*/
|
||||||
|
public static Drawable getSendErrorBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().sendErrorBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the light color of the message bubble in highlight status..
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setBubbleHighlightLightColor(int color) {
|
||||||
|
HighlightPresenter.setHighlightLightColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dark color of the message bubble in highlight status..
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setBubbleHighlightDarkColor(int color) {
|
||||||
|
HighlightPresenter.setHighlightDarkColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the chat time bubble.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setChatTimeBubble(Drawable drawable) {
|
||||||
|
getInstance().chatTimeBubble = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chat time bubble.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Drawable getChatTimeBubble() {
|
||||||
|
return newDrawable(getInstance().chatTimeBubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font size of the chat time text.
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
public static void setChatTimeFontSize(int size) {
|
||||||
|
getInstance().chatTimeFontSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font size of the chat time text.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getChatTimeFontSize() {
|
||||||
|
return getInstance().chatTimeFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font color of the chat time text.
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setChatTimeFontColor(int color) {
|
||||||
|
getInstance().chatTimeFontColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font color of the chat time text.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getChatTimeFontColor() {
|
||||||
|
return getInstance().chatTimeFontColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default avatar image.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setDefaultAvatarImage(Drawable drawable) {
|
||||||
|
getInstance().defaultAvatarImage = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default avatar image.
|
||||||
|
* @return the default avatar image
|
||||||
|
*/
|
||||||
|
public static Drawable getDefaultAvatarImage() {
|
||||||
|
return newDrawable(getInstance().defaultAvatarImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the radius of the avatar in the message list.
|
||||||
|
* @param radius
|
||||||
|
*/
|
||||||
|
public static void setMessageListAvatarRadius(int radius) {
|
||||||
|
getInstance().avatarRadius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the radius of the avatar in the message list.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getMessageListAvatarRadius() {
|
||||||
|
return getInstance().avatarRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to enable the grid avatar with the group chat.The default value is true.
|
||||||
|
* @param enableGroupGridAvatar
|
||||||
|
*/
|
||||||
|
public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
|
||||||
|
TUIConfig.setEnableGroupGridAvatar(enableGroupGridAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the avatar size in the message list.
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
public static void setMessageListAvatarSize(int size) {
|
||||||
|
getInstance().avatarSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the avatar size in the message list.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getMessageListAvatarSize() {
|
||||||
|
return getInstance().avatarSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to hide the nickname of the received message.
|
||||||
|
* @param hideReceiveNickName
|
||||||
|
*/
|
||||||
|
public static void setHideReceiveNickName(boolean hideReceiveNickName) {
|
||||||
|
getInstance().receiveNickNameVisibility = hideReceiveNickName ? View.GONE : View.VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the visibility of the nickname of the received message.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getReceiveNickNameVisibility() {
|
||||||
|
return getInstance().receiveNickNameVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font size of the nickname of the received message.
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
public static void setReceiveNickNameFontSize(int size) {
|
||||||
|
getInstance().receiveNickNameFontSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font size of the nickname of the received message.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getReceiveNickNameFontSize() {
|
||||||
|
return getInstance().receiveNickNameFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font color of the nickname of the received message.
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setReceiveNickNameColor(int color) {
|
||||||
|
getInstance().receiveNickNameColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font color of the nickname of the received message.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getReceiveNickNameColor() {
|
||||||
|
return getInstance().receiveNickNameColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.config.minimalistui;
|
||||||
|
|
||||||
|
import static com.tencent.qcloud.tuikit.timcommon.util.TUIUtil.newDrawable;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuicore.util.ToastUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
|
||||||
|
|
||||||
|
public class TUIConfigMinimalist {
|
||||||
|
private TUIConfigMinimalist() {}
|
||||||
|
|
||||||
|
private static final class TUIConfigMinimalistHolder {
|
||||||
|
private static final TUIConfigMinimalist INSTANCE = new TUIConfigMinimalist();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TUIConfigMinimalist getInstance() {
|
||||||
|
return TUIConfigMinimalistHolder.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int UNDEFINED = -1;
|
||||||
|
// message bubble
|
||||||
|
private boolean enableMessageBubbleStyle = true;
|
||||||
|
private Drawable sendBubbleBackground;
|
||||||
|
private Drawable sendLastBubbleBackground;
|
||||||
|
private Drawable receiveBubbleBackground;
|
||||||
|
private Drawable receiveLastBubbleBackground;
|
||||||
|
|
||||||
|
// message style
|
||||||
|
private Drawable chatTimeBubble;
|
||||||
|
private int chatTimeFontSize = UNDEFINED;
|
||||||
|
private int chatTimeFontColor = UNDEFINED;
|
||||||
|
private Drawable defaultAvatarImage;
|
||||||
|
private int avatarRadius = UNDEFINED;
|
||||||
|
private int avatarSize = UNDEFINED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default app directory.The default dir is "file".
|
||||||
|
* @param appDir
|
||||||
|
*/
|
||||||
|
public static void setDefaultAppDir(String appDir) {
|
||||||
|
TUIConfig.setDefaultAppDir(appDir);
|
||||||
|
TUIConfig.initPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether show the toast prompt built into TUIKit.The default value is true.
|
||||||
|
* @param enableToast
|
||||||
|
*/
|
||||||
|
public static void enableToast(boolean enableToast) {
|
||||||
|
ToastUtil.setEnableToast(enableToast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to enable language switching.The default value is false.
|
||||||
|
* @param enableLanguageSwitch
|
||||||
|
*/
|
||||||
|
public static void enableLanguageSwitch(boolean enableLanguageSwitch) {
|
||||||
|
TUIThemeManager.setEnableLanguageSwitch(enableLanguageSwitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch the language of TUIKit.
|
||||||
|
* The currently supported languages are "en", "zh", and "ar".
|
||||||
|
* @param context
|
||||||
|
* @param targetLanguage
|
||||||
|
*/
|
||||||
|
public static void switchLanguageToTarget(Context context, String targetLanguage) {
|
||||||
|
TUIThemeManager.getInstance().changeLanguage(context, targetLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to enable message bubble style.The default value is true.
|
||||||
|
* @param enable
|
||||||
|
*/
|
||||||
|
public static void setEnableMessageBubbleStyle(boolean enable) {
|
||||||
|
getInstance().enableMessageBubbleStyle = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether to enable message bubble style.
|
||||||
|
* @return true is enable, false is not
|
||||||
|
*/
|
||||||
|
public static boolean isEnableMessageBubbleStyle() {
|
||||||
|
return getInstance().enableMessageBubbleStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background of the send message's bubble in consecutive messages.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setSendBubbleBackground(Drawable drawable) {
|
||||||
|
getInstance().sendBubbleBackground = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background of the send message's bubble in consecutive messages.
|
||||||
|
* @return the background
|
||||||
|
*/
|
||||||
|
public static Drawable getSendBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().sendBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background of the receive message's bubble in consecutive messages.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setReceiveBubbleBackground(Drawable drawable) {
|
||||||
|
getInstance().receiveBubbleBackground = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background of the receive message's bubble in consecutive messages.
|
||||||
|
* @return the background
|
||||||
|
*/
|
||||||
|
public static Drawable getReceiveBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().receiveBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background image of the last sent message's bubble in consecutive messages.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setSendLastBubbleBackground(Drawable drawable) {
|
||||||
|
getInstance().sendLastBubbleBackground = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background image of the last sent message's bubble in consecutive messages.
|
||||||
|
* @return drawable
|
||||||
|
*/
|
||||||
|
public static Drawable getSendLastBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().sendLastBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background image of the last received message's bubble in consecutive messages.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setReceiveLastBubbleBackground(Drawable drawable) {
|
||||||
|
getInstance().receiveLastBubbleBackground = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background image of the last received message's bubble in consecutive messages.
|
||||||
|
* @return drawable
|
||||||
|
*/
|
||||||
|
public static Drawable getReceiveLastBubbleBackground() {
|
||||||
|
return newDrawable(getInstance().receiveLastBubbleBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the light color of the message bubble in highlight status..
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setBubbleHighlightLightColor(int color) {
|
||||||
|
HighlightPresenter.setHighlightLightColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dark color of the message bubble in highlight status..
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setBubbleHighlightDarkColor(int color) {
|
||||||
|
HighlightPresenter.setHighlightDarkColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the chat time bubble.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setChatTimeBubble(Drawable drawable) {
|
||||||
|
getInstance().chatTimeBubble = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the chat time bubble.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Drawable getChatTimeBubble() {
|
||||||
|
return newDrawable(getInstance().chatTimeBubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font size of the chat time text.
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
public static void setChatTimeFontSize(int size) {
|
||||||
|
getInstance().chatTimeFontSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font size of the chat time text.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getChatTimeFontSize() {
|
||||||
|
return getInstance().chatTimeFontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the font color of the chat time text.
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
public static void setChatTimeFontColor(int color) {
|
||||||
|
getInstance().chatTimeFontColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the font color of the chat time text.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getChatTimeFontColor() {
|
||||||
|
return getInstance().chatTimeFontColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default avatar image.
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
public static void setDefaultAvatarImage(Drawable drawable) {
|
||||||
|
getInstance().defaultAvatarImage = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default avatar image.
|
||||||
|
* @return the default avatar image
|
||||||
|
*/
|
||||||
|
public static Drawable getDefaultAvatarImage() {
|
||||||
|
return newDrawable(getInstance().defaultAvatarImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the radius of the avatar in the message list.
|
||||||
|
* @param radius
|
||||||
|
*/
|
||||||
|
public static void setMessageListAvatarRadius(int radius) {
|
||||||
|
getInstance().avatarRadius = radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the radius of the avatar in the message list.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getMessageListAvatarRadius() {
|
||||||
|
return getInstance().avatarRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether to enable the grid avatar with the group chat.The default value is true.
|
||||||
|
* @param enableGroupGridAvatar
|
||||||
|
*/
|
||||||
|
public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) {
|
||||||
|
TUIConfig.setEnableGroupGridAvatar(enableGroupGridAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the avatar size in the message list.
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
public static void setMessageListAvatarSize(int size) {
|
||||||
|
getInstance().avatarSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the avatar size in the message list.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static int getMessageListAvatarSize() {
|
||||||
|
return getInstance().avatarSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.interfaces.IUIKitCallback;
|
||||||
|
|
||||||
|
public abstract class ChatInputMoreListener {
|
||||||
|
public String sendMessage(TUIMessageBean msg, IUIKitCallback<TUIMessageBean> callback) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
public interface HighlightListener {
|
||||||
|
|
||||||
|
void onHighlightStart();
|
||||||
|
|
||||||
|
void onHighlightEnd();
|
||||||
|
|
||||||
|
void onHighlightUpdate(int color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
|
||||||
|
public interface ICommonMessageAdapter {
|
||||||
|
TUIMessageBean getItem(int position);
|
||||||
|
|
||||||
|
TUIMessageBean getFirstMessageBean();
|
||||||
|
|
||||||
|
TUIMessageBean getLastMessageBean();
|
||||||
|
|
||||||
|
void onItemRefresh(TUIMessageBean messageBean);
|
||||||
|
|
||||||
|
UserFaceUrlCache getUserFaceUrlCache();
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
public interface IMessageProperties {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get default avatar
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getAvatar();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the default avatar, the default is the same as the avatar on the left and right
|
||||||
|
*
|
||||||
|
* @param resId
|
||||||
|
*/
|
||||||
|
void setAvatar(int resId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get avatar rounded corners
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getAvatarRadius();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set avatar rounded corners
|
||||||
|
*
|
||||||
|
* @param radius
|
||||||
|
*/
|
||||||
|
void setAvatarRadius(int radius);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get avatar size
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int[] getAvatarSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set avatar size
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
void setAvatarSize(int[] size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Get nickname text size
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getNameFontSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set nickname text size
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
void setNameFontSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get nickname text color
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getNameFontColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set nickname text color
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
void setNameFontColor(int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the display status of the nickname on the left
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getLeftNameVisibility();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the display status of the nickname on the left
|
||||||
|
*
|
||||||
|
* @param visibility
|
||||||
|
*/
|
||||||
|
void setLeftNameVisibility(int visibility);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the display status of the nickname on the right
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getRightNameVisibility();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the display status of the nickname on the right
|
||||||
|
*
|
||||||
|
* @param visibility
|
||||||
|
*/
|
||||||
|
void setRightNameVisibility(int visibility);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the background of the chat bubble on the right
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Drawable getRightBubble();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the background of the chat bubble on the right
|
||||||
|
*
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
void setRightBubble(Drawable drawable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the background of the left chat bubble
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Drawable getLeftBubble();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the background of the left chat bubble
|
||||||
|
*
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
void setLeftBubble(Drawable drawable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get chat content font size
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getChatContextFontSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set chat content font size
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
void setChatContextFontSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the font color of the chat content on the right
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getRightChatContentFontColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the font color of the chat content on the right
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
void setRightChatContentFontColor(int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the font color of the chat content on the left
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getLeftChatContentFontColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the font color of the chat content on the left
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
void setLeftChatContentFontColor(int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the context of the chat time
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Drawable getChatTimeBubble();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the context of the chat time
|
||||||
|
*
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
void setChatTimeBubble(Drawable drawable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the text size of the chat time
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getChatTimeFontSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the text size of the chat time
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
void setChatTimeFontSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the font color of chat time
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getChatTimeFontColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the font color of chat time
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
void setChatTimeFontColor(int color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get context for chat alerts
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Drawable getTipsMessageBubble();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set context for chat alerts
|
||||||
|
*
|
||||||
|
* @param drawable
|
||||||
|
*/
|
||||||
|
void setTipsMessageBubble(Drawable drawable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the text size of the chat prompt message
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getTipsMessageFontSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the text size of the chat prompt message
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
void setTipsMessageFontSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Get the text color of the chat prompt message
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getTipsMessageFontColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Set the text color of the chat prompt message
|
||||||
|
*
|
||||||
|
* @param color
|
||||||
|
*/
|
||||||
|
void setTipsMessageFontColor(int color);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
|
||||||
|
public abstract class OnChatPopActionClickListener {
|
||||||
|
public void onCopyClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onSendMessageClick(TUIMessageBean msg, boolean retry) {}
|
||||||
|
|
||||||
|
public void onDeleteMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onRevokeMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onMultiSelectMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onForwardMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onReplyMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onQuoteMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onInfoMessageClick(TUIMessageBean msg) {}
|
||||||
|
|
||||||
|
public void onSpeakerModeSwitchClick(TUIMessageBean msg) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.ChatFace;
|
||||||
|
|
||||||
|
public interface OnFaceInputListener {
|
||||||
|
|
||||||
|
void onEmojiClicked(String emojiKey);
|
||||||
|
|
||||||
|
void onDeleteClicked();
|
||||||
|
|
||||||
|
void onSendClicked();
|
||||||
|
|
||||||
|
void onFaceClicked(ChatFace chatFace);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
|
||||||
|
public abstract class OnItemClickListener {
|
||||||
|
public void onMessageLongClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onMessageClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onUserIconClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onUserIconLongClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onReEditRevokeMessage(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onRecallClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onReplyMessageClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onReplyDetailClick(TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onSendFailBtnClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onTextSelected(View view, int position, TUIMessageBean messageBean) {}
|
||||||
|
|
||||||
|
public void onMessageReadStatusClick(View view, TUIMessageBean messageBean) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.interfaces;
|
||||||
|
|
||||||
|
public interface UserFaceUrlCache {
|
||||||
|
|
||||||
|
String getCachedFaceUrl(String userID);
|
||||||
|
|
||||||
|
void pushFaceUrl(String userID, String faceUrl);
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConfig;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.highlight.HighlightPresenter;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.config.minimalistui.TUIConfigMinimalist;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.HighlightListener;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public abstract class MessageBaseHolder extends RecyclerView.ViewHolder {
|
||||||
|
public ICommonMessageAdapter mAdapter;
|
||||||
|
protected OnItemClickListener onItemClickListener;
|
||||||
|
|
||||||
|
public FrameLayout msgContentFrame;
|
||||||
|
public LinearLayout msgArea;
|
||||||
|
public LinearLayout msgAreaAndReply;
|
||||||
|
public FrameLayout reactionArea;
|
||||||
|
public CheckBox mMutiSelectCheckBox;
|
||||||
|
public View mContentLayout;
|
||||||
|
|
||||||
|
public TextView chatTimeText;
|
||||||
|
|
||||||
|
protected boolean floatMode = false;
|
||||||
|
|
||||||
|
protected boolean isLayoutOnStart = true;
|
||||||
|
private HighlightListener highlightListener;
|
||||||
|
protected TUIMessageBean currentMessageBean;
|
||||||
|
|
||||||
|
public MessageBaseHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
msgContentFrame = itemView.findViewById(R.id.msg_content_fl);
|
||||||
|
reactionArea = itemView.findViewById(R.id.message_reaction_area);
|
||||||
|
msgArea = itemView.findViewById(R.id.msg_area);
|
||||||
|
msgAreaAndReply = itemView.findViewById(R.id.msg_area_and_reply);
|
||||||
|
mMutiSelectCheckBox = itemView.findViewById(R.id.select_checkbox);
|
||||||
|
chatTimeText = itemView.findViewById(R.id.message_top_time_tv);
|
||||||
|
mContentLayout = itemView.findViewById(R.id.message_content_layout);
|
||||||
|
initVariableLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract int getVariableLayout();
|
||||||
|
|
||||||
|
private void setVariableLayout(int resId) {
|
||||||
|
if (msgContentFrame.getChildCount() == 0) {
|
||||||
|
View.inflate(itemView.getContext(), resId, msgContentFrame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initVariableLayout() {
|
||||||
|
if (getVariableLayout() != 0) {
|
||||||
|
setVariableLayout(getVariableLayout());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdapter(ICommonMessageAdapter adapter) {
|
||||||
|
mAdapter = adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||||
|
this.onItemClickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnItemClickListener getOnItemClickListener() {
|
||||||
|
return this.onItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void layoutViews(final TUIMessageBean msg, final int position) {
|
||||||
|
currentMessageBean = msg;
|
||||||
|
registerHighlightListener(msg.getId());
|
||||||
|
|
||||||
|
setChatTimeStyle();
|
||||||
|
|
||||||
|
if (position > 1) {
|
||||||
|
TUIMessageBean last = mAdapter.getItem(position - 1);
|
||||||
|
if (last != null) {
|
||||||
|
if (msg.getMessageTime() - last.getMessageTime() >= 5 * 60) {
|
||||||
|
chatTimeText.setVisibility(View.VISIBLE);
|
||||||
|
chatTimeText.setText(getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
|
||||||
|
} else {
|
||||||
|
chatTimeText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chatTimeText.setVisibility(View.VISIBLE);
|
||||||
|
chatTimeText.setText(getTimeFormatText(new Date(msg.getMessageTime() * 1000)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setChatTimeStyle() {
|
||||||
|
Drawable chatTimeBubble = TUIConfigMinimalist.getChatTimeBubble();
|
||||||
|
if (chatTimeBubble != null) {
|
||||||
|
chatTimeText.setBackground(chatTimeBubble);
|
||||||
|
}
|
||||||
|
int chatTimeFontColor = TUIConfigMinimalist.getChatTimeFontColor();
|
||||||
|
if (chatTimeFontColor != TUIConfigMinimalist.UNDEFINED) {
|
||||||
|
chatTimeText.setTextColor(chatTimeFontColor);
|
||||||
|
}
|
||||||
|
int chatTimeFontSize = TUIConfigMinimalist.getChatTimeFontSize();
|
||||||
|
if (chatTimeFontSize != TUIConfigMinimalist.UNDEFINED) {
|
||||||
|
chatTimeText.setTextSize(chatTimeFontSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerHighlightListener(String msgID) {
|
||||||
|
if (highlightListener == null) {
|
||||||
|
highlightListener = new HighlightListener() {
|
||||||
|
@Override
|
||||||
|
public void onHighlightStart() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHighlightEnd() {
|
||||||
|
clearHighLightBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHighlightUpdate(int color) {
|
||||||
|
setHighLightBackground(color);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
HighlightPresenter.registerHighlightListener(msgID, highlightListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRecycled() {
|
||||||
|
if (currentMessageBean != null) {
|
||||||
|
HighlightPresenter.unregisterHighlightListener(currentMessageBean.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getTimeFormatText(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
Context context = TUIConfig.getAppContext();
|
||||||
|
Locale locale;
|
||||||
|
if (context == null) {
|
||||||
|
locale = Locale.getDefault();
|
||||||
|
} else {
|
||||||
|
locale = TUIThemeManager.getInstance().getLocale(context);
|
||||||
|
}
|
||||||
|
String timeText;
|
||||||
|
Calendar dayCalendar = Calendar.getInstance();
|
||||||
|
dayCalendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
dayCalendar.set(Calendar.MINUTE, 0);
|
||||||
|
dayCalendar.set(Calendar.SECOND, 0);
|
||||||
|
dayCalendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
Calendar weekCalendar = Calendar.getInstance();
|
||||||
|
weekCalendar.setFirstDayOfWeek(Calendar.SUNDAY);
|
||||||
|
weekCalendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
|
||||||
|
weekCalendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
weekCalendar.set(Calendar.MINUTE, 0);
|
||||||
|
weekCalendar.set(Calendar.SECOND, 0);
|
||||||
|
weekCalendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
Calendar yearCalendar = Calendar.getInstance();
|
||||||
|
yearCalendar.set(Calendar.DAY_OF_YEAR, 1);
|
||||||
|
yearCalendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
yearCalendar.set(Calendar.MINUTE, 0);
|
||||||
|
yearCalendar.set(Calendar.SECOND, 0);
|
||||||
|
yearCalendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
long dayStartTimeInMillis = dayCalendar.getTimeInMillis();
|
||||||
|
long weekStartTimeInMillis = weekCalendar.getTimeInMillis();
|
||||||
|
long yearStartTimeInMillis = yearCalendar.getTimeInMillis();
|
||||||
|
long outTimeMillis = date.getTime();
|
||||||
|
if (outTimeMillis < yearStartTimeInMillis) {
|
||||||
|
timeText = String.format(Locale.US, "%1$tY/%1$tm/%1$td", date);
|
||||||
|
} else if (outTimeMillis < weekStartTimeInMillis) {
|
||||||
|
timeText = String.format(Locale.US, "%1$tm/%1$td", date);
|
||||||
|
} else if (outTimeMillis < dayStartTimeInMillis) {
|
||||||
|
timeText = String.format(locale, "%tA", date);
|
||||||
|
} else {
|
||||||
|
timeText = context.getResources().getString(R.string.chat_time_today);
|
||||||
|
}
|
||||||
|
return timeText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFloatMode(boolean floatMode) {
|
||||||
|
this.floatMode = floatMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isShowAvatar(TUIMessageBean messageBean) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleZeroPadding() {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgArea.setPaddingRelative(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleBackground(int resID) {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgArea.setBackgroundResource(resID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleBackground(Drawable drawable) {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msgArea.setBackground(drawable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable getMessageBubbleBackground() {
|
||||||
|
if (msgArea == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return msgArea.getBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHighLightBackground(int color) {
|
||||||
|
Drawable drawable = getMessageBubbleBackground();
|
||||||
|
if (drawable != null) {
|
||||||
|
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearHighLightBackground() {
|
||||||
|
Drawable drawable = getMessageBubbleBackground();
|
||||||
|
if (drawable != null) {
|
||||||
|
drawable.setColorFilter(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,714 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.RequestBuilder;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMManager;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMMessage;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMUserFullInfo;
|
||||||
|
import com.tencent.imsdk.v2.V2TIMValueCallback;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIConstants;
|
||||||
|
import com.tencent.qcloud.tuicore.TUICore;
|
||||||
|
import com.tencent.qcloud.tuicore.TUIThemeManager;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.component.UnreadCountTextView;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.config.minimalistui.TUIConfigMinimalist;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class MessageContentHolder extends MessageBaseHolder {
|
||||||
|
protected static final int READ_STATUS_UNREAD = 1;
|
||||||
|
protected static final int READ_STATUS_PART_READ = 2;
|
||||||
|
protected static final int READ_STATUS_ALL_READ = 3;
|
||||||
|
protected static final int READ_STATUS_HIDE = 4;
|
||||||
|
protected static final int READ_STATUS_SENDING = 5;
|
||||||
|
|
||||||
|
public ImageView leftUserIcon;
|
||||||
|
public ImageView rightUserIcon;
|
||||||
|
public TextView usernameText;
|
||||||
|
public LinearLayout msgContentLinear;
|
||||||
|
public ImageView messageStatusImage;
|
||||||
|
public ImageView fileStatusImage;
|
||||||
|
public UnreadCountTextView unreadAudioText;
|
||||||
|
public LinearLayout extraInfoArea;
|
||||||
|
protected FrameLayout bottomContentFrameLayout;
|
||||||
|
|
||||||
|
public boolean isForwardMode = false;
|
||||||
|
public boolean isMessageDetailMode = false;
|
||||||
|
public boolean isMultiSelectMode = false;
|
||||||
|
public boolean isOptimize = true;
|
||||||
|
public boolean isShowSelfAvatar = false;
|
||||||
|
protected boolean isShowAvatar = true;
|
||||||
|
|
||||||
|
protected TimeInLineTextLayout timeInLineTextLayout;
|
||||||
|
protected MinimalistMessageLayout rootLayout;
|
||||||
|
protected ReplyPreviewView replyPreviewView;
|
||||||
|
|
||||||
|
private List<TUIMessageBean> mDataSource = new ArrayList<>();
|
||||||
|
|
||||||
|
// Whether to display the bottom content. The merged-forwarded message details activity does not display the bottom content.
|
||||||
|
protected boolean isNeedShowBottom = true;
|
||||||
|
protected boolean isShowRead = false;
|
||||||
|
private Fragment fragment;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
|
||||||
|
public MessageContentHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
rootLayout = (MinimalistMessageLayout) itemView;
|
||||||
|
leftUserIcon = itemView.findViewById(R.id.left_user_icon_view);
|
||||||
|
rightUserIcon = itemView.findViewById(R.id.right_user_icon_view);
|
||||||
|
usernameText = itemView.findViewById(R.id.user_name_tv);
|
||||||
|
msgContentLinear = itemView.findViewById(R.id.msg_content_ll);
|
||||||
|
messageStatusImage = itemView.findViewById(R.id.message_status_iv);
|
||||||
|
fileStatusImage = itemView.findViewById(R.id.file_status_iv);
|
||||||
|
unreadAudioText = itemView.findViewById(R.id.unread_audio_text);
|
||||||
|
replyPreviewView = itemView.findViewById(R.id.msg_reply_preview);
|
||||||
|
extraInfoArea = itemView.findViewById(R.id.extra_info_area);
|
||||||
|
bottomContentFrameLayout = itemView.findViewById(R.id.bottom_content_fl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFragment(Fragment fragment) {
|
||||||
|
this.fragment = fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecyclerView(RecyclerView recyclerView) {
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDataSource(List<TUIMessageBean> dataSource) {
|
||||||
|
if (dataSource == null || dataSource.isEmpty()) {
|
||||||
|
mDataSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TUIMessageBean> mediaSource = new ArrayList<>();
|
||||||
|
for (TUIMessageBean messageBean : dataSource) {
|
||||||
|
int type = messageBean.getMsgType();
|
||||||
|
if (type == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE || type == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) {
|
||||||
|
mediaSource.add(messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mDataSource = mediaSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TUIMessageBean> getDataSource() {
|
||||||
|
return mDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void layoutViews(final TUIMessageBean msg, final int position) {
|
||||||
|
Context context = itemView.getContext();
|
||||||
|
if (TUIUtil.isActivityDestroyed(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.layoutViews(msg, position);
|
||||||
|
setLayoutAlignment(msg);
|
||||||
|
setIsShowAvatar(msg, position);
|
||||||
|
setMessageGravity();
|
||||||
|
setUserNameText(msg);
|
||||||
|
setMessageBubbleBackground();
|
||||||
|
setMessageStatusImage(msg);
|
||||||
|
setMessageTimeVisibility();
|
||||||
|
setAvatarVisibility();
|
||||||
|
setEventListener(msg);
|
||||||
|
|
||||||
|
msgContentLinear.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (!isForwardMode && !isMessageDetailMode) {
|
||||||
|
setTimeInLineStatus(msg);
|
||||||
|
setShowReadStatusClickListener(msg);
|
||||||
|
}
|
||||||
|
if (timeInLineTextLayout != null && timeInLineTextLayout.getTextView() != null) {
|
||||||
|
if (isMultiSelectMode) {
|
||||||
|
timeInLineTextLayout.getTextView().setActivated(false);
|
||||||
|
} else {
|
||||||
|
timeInLineTextLayout.getTextView().setActivated(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeInLineTextLayout != null) {
|
||||||
|
timeInLineTextLayout.setTimeText(DateTimeUtil.getHMTimeString(new Date(msg.getMessageTime() * 1000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
extraInfoArea.setVisibility(View.GONE);
|
||||||
|
setReplyContent(msg);
|
||||||
|
setReactContent(msg);
|
||||||
|
if (isNeedShowBottom) {
|
||||||
|
setBottomContent(msg);
|
||||||
|
}
|
||||||
|
setMessageBubbleDefaultPadding();
|
||||||
|
if (floatMode) {
|
||||||
|
itemView.setPaddingRelative(0, 0, 0, 0);
|
||||||
|
leftUserIcon.setVisibility(View.GONE);
|
||||||
|
rightUserIcon.setVisibility(View.GONE);
|
||||||
|
usernameText.setVisibility(View.GONE);
|
||||||
|
replyPreviewView.setVisibility(View.GONE);
|
||||||
|
reactionArea.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
if (isMessageDetailMode) {
|
||||||
|
replyPreviewView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
optimizePadding(position, msg);
|
||||||
|
loadAvatar(msg);
|
||||||
|
layoutVariableViews(msg, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setEventListener(TUIMessageBean msg) {
|
||||||
|
if (!isForwardMode && !isMessageDetailMode) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
msgArea.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
leftUserIcon.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
onItemClickListener.onUserIconClick(view, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
leftUserIcon.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
onItemClickListener.onUserIconLongClick(view, msg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rightUserIcon.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
onItemClickListener.onUserIconClick(view, msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.getStatus() != TUIMessageBean.MSG_STATUS_SEND_FAIL) {
|
||||||
|
msgContentFrame.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageClick(msgContentFrame, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setMessageTimeVisibility() {
|
||||||
|
if (isForwardMode || floatMode) {
|
||||||
|
chatTimeText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLayoutAlignment(TUIMessageBean msg) {
|
||||||
|
if (isForwardMode || isMessageDetailMode) {
|
||||||
|
isLayoutOnStart = true;
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
isLayoutOnStart = false;
|
||||||
|
} else {
|
||||||
|
isLayoutOnStart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isForwardMode || isMessageDetailMode) {
|
||||||
|
rootLayout.setIsStart(true);
|
||||||
|
msgContentLinear.removeView(msgAreaAndReply);
|
||||||
|
msgContentLinear.addView(msgAreaAndReply);
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
msgContentLinear.removeView(msgAreaAndReply);
|
||||||
|
msgContentLinear.addView(msgAreaAndReply);
|
||||||
|
} else {
|
||||||
|
msgContentLinear.removeView(msgAreaAndReply);
|
||||||
|
msgContentLinear.addView(msgAreaAndReply, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setGravity(isLayoutOnStart);
|
||||||
|
rootLayout.setIsStart(isLayoutOnStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUserNameText(TUIMessageBean msg) {
|
||||||
|
if (isForwardMode || isMessageDetailMode) {
|
||||||
|
usernameText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
usernameText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
if (msg.isGroup()) {
|
||||||
|
usernameText.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
usernameText.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usernameText.setText(msg.getUserDisplayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsShowAvatar(TUIMessageBean msg, int position) {
|
||||||
|
isShowAvatar = true;
|
||||||
|
if (mAdapter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isMessageDetailMode || !isOptimize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
|
||||||
|
if (nextMessage != null) {
|
||||||
|
if (TextUtils.equals(msg.getSender(), nextMessage.getSender())) {
|
||||||
|
boolean longPeriod = nextMessage.getMessageTime() - msg.getMessageTime() >= 5 * 60;
|
||||||
|
if (!isShowAvatar(nextMessage) && nextMessage.getStatus() != TUIMessageBean.MSG_STATUS_REVOKE && !longPeriod) {
|
||||||
|
isShowAvatar = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageBubbleBackground() {
|
||||||
|
if (!TUIConfigMinimalist.isEnableMessageBubbleStyle()) {
|
||||||
|
setMessageBubbleBackground(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Drawable sendBubble = TUIConfigMinimalist.getSendBubbleBackground();
|
||||||
|
Drawable receiveBubble = TUIConfigMinimalist.getReceiveBubbleBackground();
|
||||||
|
Drawable sendLastBubble = TUIConfigMinimalist.getSendLastBubbleBackground();
|
||||||
|
Drawable receiveLastBubble = TUIConfigMinimalist.getReceiveLastBubbleBackground();
|
||||||
|
if (isShowAvatar) {
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
if (receiveLastBubble != null) {
|
||||||
|
setMessageBubbleBackground(receiveLastBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border_left);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sendLastBubble != null) {
|
||||||
|
setMessageBubbleBackground(sendLastBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border_right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
if (receiveBubble != null) {
|
||||||
|
setMessageBubbleBackground(receiveBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(R.drawable.chat_message_popup_stroke_border);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sendBubble != null) {
|
||||||
|
setMessageBubbleBackground(sendBubble);
|
||||||
|
} else {
|
||||||
|
setMessageBubbleBackground(R.drawable.chat_message_popup_fill_border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageStatusImage(TUIMessageBean msg) {
|
||||||
|
if (isForwardMode || isMessageDetailMode || floatMode) {
|
||||||
|
messageStatusImage.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
if (msg.hasRiskContent()) {
|
||||||
|
messageStatusImage.setVisibility(View.VISIBLE);
|
||||||
|
} else if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) {
|
||||||
|
messageStatusImage.setVisibility(View.VISIBLE);
|
||||||
|
messageStatusImage.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onSendFailBtnClick(messageStatusImage, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
messageStatusImage.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBottomContent(TUIMessageBean msg) {
|
||||||
|
HashMap<String, Object> param = new HashMap<>();
|
||||||
|
param.put(TUIConstants.TUIChat.MESSAGE_BEAN, msg);
|
||||||
|
param.put(TUIConstants.TUIChat.CHAT_RECYCLER_VIEW, recyclerView);
|
||||||
|
param.put(TUIConstants.TUIChat.FRAGMENT, fragment);
|
||||||
|
|
||||||
|
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageBottom.MINIMALIST_EXTENSION_ID, bottomContentFrameLayout, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAvatar(TUIMessageBean msg) {
|
||||||
|
Drawable drawable = TUIConfigMinimalist.getDefaultAvatarImage();
|
||||||
|
if (drawable != null) {
|
||||||
|
setupAvatar(drawable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.isUseMsgReceiverAvatar() && mAdapter != null) {
|
||||||
|
String cachedFaceUrl = mAdapter.getUserFaceUrlCache().getCachedFaceUrl(msg.getSender());
|
||||||
|
if (cachedFaceUrl == null) {
|
||||||
|
List<String> idList = new ArrayList<>();
|
||||||
|
idList.add(msg.getSender());
|
||||||
|
V2TIMManager.getInstance().getUsersInfo(idList, new V2TIMValueCallback<List<V2TIMUserFullInfo>>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<V2TIMUserFullInfo> v2TIMUserFullInfos) {
|
||||||
|
if (v2TIMUserFullInfos == null || v2TIMUserFullInfos.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
V2TIMUserFullInfo userInfo = v2TIMUserFullInfos.get(0);
|
||||||
|
String faceUrl = userInfo.getFaceUrl();
|
||||||
|
if (TextUtils.isEmpty(userInfo.getFaceUrl())) {
|
||||||
|
faceUrl = "";
|
||||||
|
}
|
||||||
|
mAdapter.getUserFaceUrlCache().pushFaceUrl(userInfo.getUserID(), faceUrl);
|
||||||
|
mAdapter.onItemRefresh(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(int code, String desc) {
|
||||||
|
setupAvatar("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setupAvatar(cachedFaceUrl);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setupAvatar(msg.getFaceUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAvatar(Object faceUrl) {
|
||||||
|
int avatarSize = TUIConfigMinimalist.getMessageListAvatarSize();
|
||||||
|
if (avatarSize == TUIConfigMinimalist.UNDEFINED) {
|
||||||
|
avatarSize = ScreenUtil.dip2px(32);
|
||||||
|
}
|
||||||
|
ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams();
|
||||||
|
params.width = avatarSize;
|
||||||
|
if (leftUserIcon.getVisibility() == View.INVISIBLE) {
|
||||||
|
params.height = 1;
|
||||||
|
} else {
|
||||||
|
params.height = avatarSize;
|
||||||
|
}
|
||||||
|
leftUserIcon.setLayoutParams(params);
|
||||||
|
|
||||||
|
params = rightUserIcon.getLayoutParams();
|
||||||
|
params.width = avatarSize;
|
||||||
|
if (rightUserIcon.getVisibility() == View.INVISIBLE) {
|
||||||
|
params.height = 1;
|
||||||
|
} else {
|
||||||
|
params.height = avatarSize;
|
||||||
|
}
|
||||||
|
rightUserIcon.setLayoutParams(params);
|
||||||
|
|
||||||
|
int radius = ScreenUtil.dip2px(100);
|
||||||
|
if (TUIConfigMinimalist.getMessageListAvatarRadius() != TUIConfigMinimalist.UNDEFINED) {
|
||||||
|
radius = TUIConfigMinimalist.getMessageListAvatarRadius();
|
||||||
|
}
|
||||||
|
ImageView renderedView;
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
renderedView = leftUserIcon;
|
||||||
|
} else {
|
||||||
|
renderedView = rightUserIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestBuilder<Drawable> errorRequestBuilder = Glide.with(itemView.getContext())
|
||||||
|
.load(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
|
||||||
|
.placeholder(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon))
|
||||||
|
.transform(new RoundedCorners(radius));
|
||||||
|
|
||||||
|
Glide.with(itemView.getContext())
|
||||||
|
.load(faceUrl)
|
||||||
|
.transform(new RoundedCorners(radius))
|
||||||
|
.error(errorRequestBuilder)
|
||||||
|
.into(renderedView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setAvatarVisibility() {
|
||||||
|
if (isShowAvatar) {
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
leftUserIcon.setVisibility(View.VISIBLE);
|
||||||
|
rightUserIcon.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
leftUserIcon.setVisibility(View.GONE);
|
||||||
|
if (isShowSelfAvatar) {
|
||||||
|
rightUserIcon.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
rightUserIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
leftUserIcon.setVisibility(View.INVISIBLE);
|
||||||
|
if (isShowSelfAvatar) {
|
||||||
|
rightUserIcon.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
rightUserIcon.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void optimizePadding(int position, TUIMessageBean messageBean) {
|
||||||
|
if (mAdapter == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isMessageDetailMode || !isOptimize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TUIMessageBean nextMessage = mAdapter.getItem(position + 1);
|
||||||
|
int horizontalPadding = ScreenUtil.dip2px(16);
|
||||||
|
int verticalPadding = ScreenUtil.dip2px(25f);
|
||||||
|
if (!isShowAvatar) {
|
||||||
|
horizontalPadding = ScreenUtil.dip2px(16);
|
||||||
|
verticalPadding = ScreenUtil.dip2px(2);
|
||||||
|
}
|
||||||
|
if (nextMessage != null) {
|
||||||
|
rootLayout.setPaddingRelative(horizontalPadding, 0, horizontalPadding, verticalPadding);
|
||||||
|
} else {
|
||||||
|
rootLayout.setPaddingRelative(horizontalPadding, 0, horizontalPadding, ScreenUtil.dip2px(5));
|
||||||
|
}
|
||||||
|
optimizeMessageContent(isShowAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void optimizeMessageContent(boolean isShowAvatar) {}
|
||||||
|
|
||||||
|
private void setMessageGravity() {
|
||||||
|
if (isLayoutOnStart) {
|
||||||
|
msgContentLinear.setGravity(Gravity.START | Gravity.BOTTOM);
|
||||||
|
extraInfoArea.setGravity(Gravity.START);
|
||||||
|
} else {
|
||||||
|
msgContentLinear.setGravity(Gravity.END | Gravity.BOTTOM);
|
||||||
|
extraInfoArea.setGravity(Gravity.END);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTimeInLineStatus(TUIMessageBean msg) {
|
||||||
|
if (isShowRead) {
|
||||||
|
if (msg.isSelf()) {
|
||||||
|
if (TUIMessageBean.MSG_STATUS_SEND_SUCCESS == msg.getStatus()) {
|
||||||
|
if (!msg.isNeedReadReceipt()) {
|
||||||
|
setReadStatus(READ_STATUS_HIDE);
|
||||||
|
} else {
|
||||||
|
processReadStatus(msg);
|
||||||
|
}
|
||||||
|
} else if (TUIMessageBean.MSG_STATUS_SENDING == msg.getStatus()) {
|
||||||
|
setReadStatus(READ_STATUS_SENDING);
|
||||||
|
} else {
|
||||||
|
setReadStatus(READ_STATUS_HIDE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setReadStatus(READ_STATUS_HIDE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setOnTimeInLineTextClickListener(TUIMessageBean messageBean) {
|
||||||
|
timeInLineTextLayout.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageClick(msgArea, messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timeInLineTextLayout.getTextView().setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageClick(msgArea, messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timeInLineTextLayout.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, messageBean);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timeInLineTextLayout.getTextView().setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, messageBean);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setReadStatus(int readStatus) {
|
||||||
|
if (timeInLineTextLayout != null) {
|
||||||
|
int statusIconResID = 0;
|
||||||
|
switch (readStatus) {
|
||||||
|
case READ_STATUS_UNREAD: {
|
||||||
|
statusIconResID = R.drawable.chat_minimalist_message_status_send_no_read;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case READ_STATUS_PART_READ: {
|
||||||
|
statusIconResID = R.drawable.chat_minimalist_message_status_send_part_read;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case READ_STATUS_ALL_READ: {
|
||||||
|
statusIconResID = R.drawable.chat_minimalist_message_status_send_all_read;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case READ_STATUS_SENDING: {
|
||||||
|
statusIconResID = R.drawable.chat_minimalist_status_loading_anim;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeInLineTextLayout.setStatusIcon(statusIconResID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setMessageBubbleDefaultPadding() {
|
||||||
|
// after setting background, the padding will be reset
|
||||||
|
int paddingHorizontal = itemView.getResources().getDimensionPixelSize(R.dimen.chat_minimalist_message_area_padding_left_right);
|
||||||
|
int paddingVertical = itemView.getResources().getDimensionPixelSize(R.dimen.chat_minimalist_message_area_padding_top_bottom);
|
||||||
|
msgArea.setPaddingRelative(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setGravity(boolean isStart) {
|
||||||
|
int gravity = isStart ? Gravity.START : Gravity.END;
|
||||||
|
msgAreaAndReply.setGravity(gravity);
|
||||||
|
ViewGroup.LayoutParams layoutParams = msgContentFrame.getLayoutParams();
|
||||||
|
if (layoutParams instanceof FrameLayout.LayoutParams) {
|
||||||
|
((FrameLayout.LayoutParams) layoutParams).gravity = gravity;
|
||||||
|
} else if (layoutParams instanceof LinearLayout.LayoutParams) {
|
||||||
|
((LinearLayout.LayoutParams) layoutParams).gravity = gravity;
|
||||||
|
}
|
||||||
|
msgContentFrame.setLayoutParams(layoutParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReplyContent(TUIMessageBean messageBean) {
|
||||||
|
MessageRepliesBean messageRepliesBean = messageBean.getMessageRepliesBean();
|
||||||
|
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
|
||||||
|
extraInfoArea.setVisibility(View.VISIBLE);
|
||||||
|
replyPreviewView.setVisibility(View.VISIBLE);
|
||||||
|
replyPreviewView.setMessageRepliesBean(messageRepliesBean);
|
||||||
|
replyPreviewView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onReplyDetailClick(messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
replyPreviewView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReactContent(TUIMessageBean messageBean) {
|
||||||
|
Map<String, Object> param = new HashMap<>();
|
||||||
|
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.MESSAGE, messageBean);
|
||||||
|
param.put(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE,
|
||||||
|
TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.VIEW_TYPE_MINIMALIST);
|
||||||
|
TUICore.raiseExtension(TUIConstants.TUIChat.Extension.MessageReactPreviewExtension.EXTENSION_ID, reactionArea, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processReadStatus(TUIMessageBean msg) {
|
||||||
|
if (msg.isGroup()) {
|
||||||
|
if (msg.isAllRead()) {
|
||||||
|
setReadStatus(READ_STATUS_ALL_READ);
|
||||||
|
} else if (msg.isUnread()) {
|
||||||
|
setReadStatus(READ_STATUS_UNREAD);
|
||||||
|
} else {
|
||||||
|
long readCount = msg.getReadCount();
|
||||||
|
if (readCount > 0) {
|
||||||
|
setReadStatus(READ_STATUS_PART_READ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (msg.isPeerRead()) {
|
||||||
|
setReadStatus(READ_STATUS_ALL_READ);
|
||||||
|
} else {
|
||||||
|
setReadStatus(READ_STATUS_UNREAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setShowReadStatusClickListener(TUIMessageBean messageBean) {
|
||||||
|
if (timeInLineTextLayout != null) {
|
||||||
|
timeInLineTextLayout.setOnStatusAreaClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageReadStatusClick(v, messageBean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
timeInLineTextLayout.setOnStatusAreaLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, messageBean);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
timeInLineTextLayout.setOnLongClickListener(new View.OnLongClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View v) {
|
||||||
|
if (onItemClickListener != null) {
|
||||||
|
onItemClickListener.onMessageLongClick(msgArea, messageBean);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void layoutVariableViews(final TUIMessageBean msg, final int position);
|
||||||
|
|
||||||
|
public void onRecycled() {
|
||||||
|
super.onRecycled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNeedShowBottom(boolean needShowBottom) {
|
||||||
|
isNeedShowBottom = needShowBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowRead(boolean showRead) {
|
||||||
|
isShowRead = showRead;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
|
||||||
|
public class MessageStatusTimeView extends FrameLayout {
|
||||||
|
private TextView timeView;
|
||||||
|
private ImageView statusIconView;
|
||||||
|
|
||||||
|
public MessageStatusTimeView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageStatusTimeView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageStatusTimeView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context, AttributeSet attrs) {
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.chat_minimalist_text_status_layout, this);
|
||||||
|
statusIconView = findViewById(R.id.status_icon);
|
||||||
|
timeView = findViewById(R.id.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatusIcon(int resID) {
|
||||||
|
if (resID == 0) {
|
||||||
|
statusIconView.setVisibility(GONE);
|
||||||
|
} else {
|
||||||
|
statusIconView.setBackgroundResource(resID);
|
||||||
|
Drawable drawable = statusIconView.getBackground();
|
||||||
|
if (drawable instanceof Animatable) {
|
||||||
|
((Animatable) drawable).start();
|
||||||
|
}
|
||||||
|
statusIconView.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeText(CharSequence charSequence) {
|
||||||
|
timeView.setText(charSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeColor(int color) {
|
||||||
|
timeView.setTextColor(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Path;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.LayoutUtil;
|
||||||
|
|
||||||
|
public class MinimalistMessageLayout extends ConstraintLayout {
|
||||||
|
private View msgArea;
|
||||||
|
private View quoteArea;
|
||||||
|
private View bottomArea;
|
||||||
|
private View replyArea;
|
||||||
|
|
||||||
|
private boolean isStart = false;
|
||||||
|
private Paint paint;
|
||||||
|
private Path quotePath;
|
||||||
|
private Path bottomPath;
|
||||||
|
private Path replyPath;
|
||||||
|
|
||||||
|
private Rect msgAreaRect;
|
||||||
|
private Rect bottomRect;
|
||||||
|
private Rect quoteRect;
|
||||||
|
private Rect replyRect;
|
||||||
|
private float strokeWidth;
|
||||||
|
|
||||||
|
private boolean isRTL = false;
|
||||||
|
|
||||||
|
public MinimalistMessageLayout(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinimalistMessageLayout(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinimalistMessageLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public MinimalistMessageLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setWillNotDraw(false);
|
||||||
|
isRTL = LayoutUtil.isRTL();
|
||||||
|
quotePath = new Path();
|
||||||
|
bottomPath = new Path();
|
||||||
|
replyPath = new Path();
|
||||||
|
msgAreaRect = new Rect();
|
||||||
|
bottomRect = new Rect();
|
||||||
|
quoteRect = new Rect();
|
||||||
|
replyRect = new Rect();
|
||||||
|
paint = new Paint();
|
||||||
|
strokeWidth = getResources().getDimension(R.dimen.chat_minimalist_message_quato_line_width);
|
||||||
|
paint.setStrokeWidth(strokeWidth);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
msgArea = findViewById(R.id.msg_area);
|
||||||
|
bottomArea = findViewById(R.id.bottom_content_fl);
|
||||||
|
quoteArea = findViewById(R.id.quote_content_fl);
|
||||||
|
replyArea = findViewById(R.id.msg_reply_preview);
|
||||||
|
drawLine(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsStart(boolean isStart) {
|
||||||
|
this.isStart = isStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Rect getChildRectInParent(View child) {
|
||||||
|
int[] location = new int[2];
|
||||||
|
getLocationInWindow(location);
|
||||||
|
|
||||||
|
Rect rect = new Rect();
|
||||||
|
int[] childLocation = new int[2];
|
||||||
|
child.getLocationInWindow(childLocation);
|
||||||
|
int left = childLocation[0] - location[0];
|
||||||
|
int top = childLocation[1] - location[1];
|
||||||
|
int right = left + child.getWidth();
|
||||||
|
int bottom = top + child.getHeight();
|
||||||
|
rect.set(left, top, right, bottom);
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawLine(Canvas canvas) {
|
||||||
|
canvas.drawColor(0x00FFFFFF);
|
||||||
|
if (msgArea.getVisibility() == VISIBLE) {
|
||||||
|
msgAreaRect = getChildRectInParent(msgArea);
|
||||||
|
float msgX;
|
||||||
|
if (isStart) {
|
||||||
|
if (isRTL) {
|
||||||
|
paint.setColor(getResources().getColor(R.color.chat_minimalist_right_message_bubble_color));
|
||||||
|
msgX = msgAreaRect.right - strokeWidth / 2;
|
||||||
|
} else {
|
||||||
|
paint.setColor(getResources().getColor(R.color.chat_minimalist_left_message_bubble_color));
|
||||||
|
msgX = msgAreaRect.left + strokeWidth / 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isRTL) {
|
||||||
|
paint.setColor(getResources().getColor(R.color.chat_minimalist_left_message_bubble_color));
|
||||||
|
msgX = msgAreaRect.left + strokeWidth / 2;
|
||||||
|
} else {
|
||||||
|
paint.setColor(getResources().getColor(R.color.chat_minimalist_right_message_bubble_color));
|
||||||
|
msgX = msgAreaRect.right - strokeWidth / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float msgCenterY;
|
||||||
|
msgCenterY = msgAreaRect.top + msgAreaRect.height() * 1.0f / 2;
|
||||||
|
drawBottomArea(canvas, msgX, msgCenterY);
|
||||||
|
drawQuoteArea(canvas, msgX, msgCenterY);
|
||||||
|
drawReplyArea(canvas, msgX, msgCenterY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawReplyArea(Canvas canvas, float msgX, float msgCenterY) {
|
||||||
|
if (replyArea.getVisibility() == VISIBLE) {
|
||||||
|
float replyX;
|
||||||
|
float replyCenterY;
|
||||||
|
replyRect = getChildRectInParent(replyArea);
|
||||||
|
if (isStart) {
|
||||||
|
if (isRTL) {
|
||||||
|
replyX = replyRect.right;
|
||||||
|
} else {
|
||||||
|
replyX = replyRect.left;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isRTL) {
|
||||||
|
replyX = replyRect.left;
|
||||||
|
} else {
|
||||||
|
replyX = replyRect.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replyCenterY = replyRect.top + replyRect.height() * 1.0f / 2;
|
||||||
|
int replyControlRadius = (int) Math.abs(replyX - msgX);
|
||||||
|
replyPath.reset();
|
||||||
|
replyPath.moveTo(msgX, msgCenterY);
|
||||||
|
replyPath.quadTo(msgX, replyCenterY - replyControlRadius, msgX, replyCenterY - replyControlRadius);
|
||||||
|
replyPath.quadTo(msgX, replyCenterY, replyX, replyCenterY);
|
||||||
|
canvas.drawPath(replyPath, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawQuoteArea(Canvas canvas, float msgX, float msgCenterY) {
|
||||||
|
if (quoteArea.getVisibility() == VISIBLE) {
|
||||||
|
float quoteX;
|
||||||
|
float quoteCenterY;
|
||||||
|
quoteRect = getChildRectInParent(quoteArea);
|
||||||
|
if (isStart) {
|
||||||
|
if (isRTL) {
|
||||||
|
quoteX = quoteRect.right;
|
||||||
|
} else {
|
||||||
|
quoteX = quoteRect.left;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isRTL) {
|
||||||
|
quoteX = quoteRect.left;
|
||||||
|
} else {
|
||||||
|
quoteX = quoteRect.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quoteCenterY = quoteRect.top + quoteRect.height() * 1.0f / 2;
|
||||||
|
int quoteControlRadius = (int) Math.abs(quoteX - msgX);
|
||||||
|
quotePath.reset();
|
||||||
|
quotePath.moveTo(msgX, msgCenterY);
|
||||||
|
quotePath.quadTo(msgX, quoteCenterY - quoteControlRadius, msgX, quoteCenterY - quoteControlRadius);
|
||||||
|
quotePath.quadTo(msgX, quoteCenterY, quoteX, quoteCenterY);
|
||||||
|
canvas.drawPath(quotePath, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBottomArea(Canvas canvas, float msgX, float msgCenterY) {
|
||||||
|
if (bottomArea.getVisibility() == VISIBLE) {
|
||||||
|
float bottomX;
|
||||||
|
float bottomCenterY;
|
||||||
|
bottomRect = getChildRectInParent(bottomArea);
|
||||||
|
if (isStart) {
|
||||||
|
if (isRTL) {
|
||||||
|
bottomX = bottomRect.right;
|
||||||
|
} else {
|
||||||
|
bottomX = bottomRect.left;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isRTL) {
|
||||||
|
bottomX = bottomRect.left;
|
||||||
|
} else {
|
||||||
|
bottomX = bottomRect.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bottomCenterY = bottomRect.top + bottomRect.height() * 1.0f / 2;
|
||||||
|
int bottomControlRadius = (int) Math.abs(bottomX - msgX);
|
||||||
|
bottomPath.reset();
|
||||||
|
bottomPath.moveTo(msgX, msgCenterY);
|
||||||
|
bottomPath.quadTo(msgX, bottomCenterY - bottomControlRadius, msgX, bottomCenterY - bottomControlRadius);
|
||||||
|
bottomPath.quadTo(msgX, bottomCenterY, bottomX, bottomCenterY);
|
||||||
|
canvas.drawPath(bottomPath, paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.PorterDuffXfermode;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.RequestBuilder;
|
||||||
|
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.R;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil;
|
||||||
|
import com.tencent.qcloud.tuikit.timcommon.util.TUIUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ReplyPreviewView extends FrameLayout {
|
||||||
|
private ImageView firstImg;
|
||||||
|
private ImageView secondImg;
|
||||||
|
private ImageView thirdImg;
|
||||||
|
private TextView replyText;
|
||||||
|
|
||||||
|
private MessageRepliesBean messageRepliesBean;
|
||||||
|
|
||||||
|
public ReplyPreviewView(@NonNull Context context) {
|
||||||
|
super(context);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageRepliesBean(MessageRepliesBean messageRepliesBean) {
|
||||||
|
this.messageRepliesBean = messageRepliesBean;
|
||||||
|
setView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Context context) {
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.chat_minimalist_reply_preview_layout, this);
|
||||||
|
firstImg = findViewById(R.id.first_avatar);
|
||||||
|
secondImg = findViewById(R.id.second_avatar);
|
||||||
|
thirdImg = findViewById(R.id.third_avatar);
|
||||||
|
replyText = findViewById(R.id.reply_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setView() {
|
||||||
|
if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) {
|
||||||
|
setVisibility(VISIBLE);
|
||||||
|
firstImg.setVisibility(GONE);
|
||||||
|
secondImg.setVisibility(GONE);
|
||||||
|
thirdImg.setVisibility(GONE);
|
||||||
|
replyText.setText(String.format(Locale.US, getResources().getString(R.string.chat_reply_num), messageRepliesBean.getRepliesSize()));
|
||||||
|
List<MessageRepliesBean.ReplyBean> repliesBeanList = messageRepliesBean.getReplies();
|
||||||
|
List<String> iconList = getReplyUserIconLt(repliesBeanList);
|
||||||
|
if (iconList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String secondIconUrl = null;
|
||||||
|
String thirdIconUrl = null;
|
||||||
|
|
||||||
|
if (iconList.size() > 1) {
|
||||||
|
secondIconUrl = iconList.get(1);
|
||||||
|
}
|
||||||
|
if (iconList.size() > 2) {
|
||||||
|
thirdIconUrl = iconList.get(2);
|
||||||
|
}
|
||||||
|
String firstIconUrl = iconList.get(0);
|
||||||
|
firstImg.setVisibility(VISIBLE);
|
||||||
|
loadAvatar(firstImg, firstIconUrl);
|
||||||
|
if (secondIconUrl != null) {
|
||||||
|
secondImg.setVisibility(VISIBLE);
|
||||||
|
loadAvatar(secondImg, secondIconUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iconList.size() == 3 && thirdIconUrl != null) {
|
||||||
|
thirdImg.setVisibility(VISIBLE);
|
||||||
|
loadAvatar(thirdImg, thirdIconUrl);
|
||||||
|
} else if (iconList.size() > 3) {
|
||||||
|
thirdImg.setVisibility(VISIBLE);
|
||||||
|
loadAvatar(thirdImg, R.drawable.chat_reply_more_icon);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getReplyUserIconLt(List<MessageRepliesBean.ReplyBean> repliesBeanList) {
|
||||||
|
Set<String> iconUrlSet = new LinkedHashSet<>();
|
||||||
|
for (MessageRepliesBean.ReplyBean replyBean : repliesBeanList) {
|
||||||
|
iconUrlSet.add(replyBean.getSenderFaceUrl());
|
||||||
|
if (iconUrlSet.size() >= 3) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(iconUrlSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAvatar(ImageView imageView, Object url) {
|
||||||
|
if (TUIUtil.isActivityDestroyed(getContext())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestBuilder<Drawable> errorRequestBuilder = Glide.with(getContext())
|
||||||
|
.load(com.tencent.qcloud.tuikit.timcommon.R.drawable.core_default_user_icon_light)
|
||||||
|
.transform(new ReplyRingCircleCrop());
|
||||||
|
|
||||||
|
Glide.with(getContext())
|
||||||
|
.load(url)
|
||||||
|
.centerCrop()
|
||||||
|
.transform(new ReplyRingCircleCrop())
|
||||||
|
.error(errorRequestBuilder)
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ReplyRingCircleCrop extends CircleCrop {
|
||||||
|
@Override
|
||||||
|
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
|
||||||
|
Bitmap outBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
|
||||||
|
paint.setColor(Color.WHITE);
|
||||||
|
Canvas canvas = new Canvas(outBitmap);
|
||||||
|
int borderWidth = ScreenUtil.dip2px(1);
|
||||||
|
int innerWidth = outWidth - 2 * borderWidth;
|
||||||
|
canvas.drawCircle(outWidth / 2, outHeight / 2, innerWidth / 2, paint);
|
||||||
|
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
|
||||||
|
Rect rect = new Rect(borderWidth, borderWidth, outWidth - borderWidth, outHeight - borderWidth);
|
||||||
|
int innerHeight = outHeight - 2 * borderWidth;
|
||||||
|
Bitmap bitmap = super.transform(pool, toTransform, innerWidth, innerHeight);
|
||||||
|
canvas.drawBitmap(bitmap, null, rect, paint);
|
||||||
|
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
|
||||||
|
canvas.drawCircle(outWidth / 2, outHeight / 2, outWidth / 2, paint);
|
||||||
|
return outBitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user