修改交友布局
This commit is contained in:
54
timcommon/build.gradle
Normal file
54
timcommon/build.gradle
Normal file
@@ -0,0 +1,54 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
namespace "com.tencent.qcloud.tuikit.timcommon"
|
||||
defaultConfig {
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 30
|
||||
}
|
||||
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,186 @@
|
||||
package com.tencent.qcloud.tuikit.timcommon.component;
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextView getMiddleTitle() {
|
||||
return mCenterTitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextView getRightTitle() {
|
||||
return mRightTitle;
|
||||
}
|
||||
|
||||
public UnreadCountTextView getUnreadCountTextView() {
|
||||
return unreadCountTextView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
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