wangkai

添加短视频

Showing 38 changed files with 4513 additions and 53 deletions

Too many changes to show.

To preserve performance only 38 of 38+ files are displayed.

package com.wd.fastcoding.app;
import com.hjq.toast.Toaster;
import com.wd.capability.network.RetrofitClient;
import com.wd.capability.router.ArouteInit;
import com.wd.common.base.BaseApplication;
... ... @@ -33,6 +34,8 @@ public class MyApplication extends BaseApplication {
* 初始化第三方库
*/
private void initLibs() {
// 初始化 Toast 框架
Toaster.init(this);
// 路由初始化,主线程立即调用
ArouteInit.getInstance().init(this);
// 网络请求初始化,主线程立即调用
... ...
... ... @@ -79,7 +79,7 @@
</com.wd.common.widget.CustomSmartRefreshLayout>
<com.wd.common.widget.progress.PageLoadingView
<com.wd.common.progress.PageLoadingView
android:id="@+id/loading_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
... ...
... ... @@ -68,6 +68,7 @@ import com.wd.common.listener.AddFavoriteLabelCallback;
import com.wd.common.net.NetStateChangeReceiver;
import com.wd.common.utils.CommonVarUtils;
import com.wd.common.utils.HistoryDataHelper;
import com.wd.common.utils.MyShareUtils;
import com.wd.common.utils.PDUtils;
import com.wd.common.utils.ProcessUtils;
import com.wd.common.utils.ToolsUtil;
... ... @@ -1782,10 +1783,10 @@ public class ArticleDetailActivity extends BaseActivity implements View.OnClickL
transparentBean.setPageName(articlePageName);
transparentBean.setPageId(articlePageName);
//统一赋值,减少重复代码,底部评论、互动
// MyShareUtils.setTransparentBean(transparentBean,newsDetailBean);
MyShareUtils.setTransparentBean(transparentBean,newsDetailBean);
//分享
mShareBean = new ShareBean();
// MyShareUtils.setShareData(mShareBean,newsDetailBean);
MyShareUtils.setShareData(mShareBean,newsDetailBean);
//隐藏收藏按钮
// mShareBean.setShowCollect(-1);
transparentBean.setShareBean(mShareBean);
... ...
... ... @@ -6,7 +6,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:ignore="MissingDefaultResource">
<com.people.player.widget.VideoAndLivePlayerView
<com.wd.player.widget.VideoAndLivePlayerView
android:id="@+id/playerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
... ...
package com.wd.common.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.wd.common.viewclick.BaseClickListener;
import com.wd.fastcoding.base.R;
import com.wd.foundation.bean.utils.TimeUtil;
/**
* 详情对话框
*
* @author lvjinhui
*/
public class ViewDetailsDialog extends Dialog {
// 作者
private final String authorName;
// 标题
private final String newsTitle;
// 描述
private final String newIntroduction;
/**
* 时间
*/
private String time;
public ViewDetailsDialog(@NonNull Context context, String authorName, String newsTitle,
String newIntroduction, String time) {
super(context, R.style.DialogBackgroundNull);
this.authorName = authorName;
this.newsTitle = newsTitle;
this.newIntroduction = newIntroduction;
this.time = time;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_short_video_describe);
initOption();
initView();
}
private void initOption() {
Window window = getWindow();
if (window != null) {
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
window.setDimAmount(0);
window.setBackgroundDrawableResource(android.R.color.transparent);
}
}
private void initView() {
TextView tv_author = findViewById(R.id.tv_author);
TextView tv_title = findViewById(R.id.tv_title);
TextView tv_description = findViewById(R.id.tv_description);
TextView timeTv = findViewById(R.id.tv_time);
ImageView iv_close = findViewById(R.id.iv_close);
ConstraintLayout clEmptyArea = findViewById(R.id.clEmptyArea);
if (!TextUtils.isEmpty(authorName)) {
if (tv_author != null) {
tv_author.setText(authorName);
}
}
if (!TextUtils.isEmpty(newsTitle)) {
if (tv_title != null) {
tv_title.setText(newsTitle);
}
}
if (!TextUtils.isEmpty(newIntroduction)) {
if (tv_description != null) {
tv_description.setVisibility(View.VISIBLE);
tv_description.setText(newIntroduction);
}
}else {
if (tv_description != null) {
tv_description.setVisibility(View.GONE);
}
}
//设置时间
if (TextUtils.isEmpty(time)) {
timeTv.setVisibility(View.GONE);
}else {
timeTv.setVisibility(View.VISIBLE);
//不需要走统一规则,目前接口返回的就是具体时间格式
//yyyy-MM-dd HH:mm:ss ----> yyyy-MM-dd HH:mm
time = TimeUtil.transFormTime3(time);
timeTv.setText(time);
}
if (iv_close != null) {
iv_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
}
if (clEmptyArea != null){
clEmptyArea.setOnClickListener(new BaseClickListener() {
@Override
protected void onNoDoubleClick(View v) {
dismiss();
}
});
}
}
}
\ No newline at end of file
... ...
/*
* Copyright (c) People Technologies Co., Ltd. 2019-2022. All rights reserved.
*/
package com.wd.common.listener;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
/**
* 描述:
*
* @author : lvjinhui
* @since: 2022/7/7
*/
public class MyClickListener implements View.OnTouchListener {
/**
* 双击间四百毫秒延时
*/
private static int timeout = 400;
/**
* 点击的时间间隔
*/
private static long INTERVAL = 300;
/**
* 记录上一次的点击时间
*/
private long lastClickTime = 0;
/**
* 记录连续点击次数
*/
private int clickCount = 0;
private boolean continuouslyClick;
private Handler handler;
private MyClickCallBack myClickCallBack;
public interface MyClickCallBack {
/**
* 点击一次的回调
*/
void oneClick();
/**
* 双击
*/
void continuousClick();
}
public MyClickListener(MyClickCallBack myClickCallBack) {
this.myClickCallBack = myClickCallBack;
handler = new Handler(Looper.getMainLooper());
clickCount = 0;
}
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// 获取点击时间
long currTime = SystemClock.uptimeMillis();
// 判断点击之间的时间差
long interval = currTime - lastClickTime;
lastClickTime = currTime;
clickCount++;
if (interval < INTERVAL) {
// 处于连续点击中
continuouslyClick = true;
} else {
continuouslyClick = false;
// 清零
clickCount = 1;
}
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (clickCount == 1){
//只点击1次,触发暂停/播放操作
myClickCallBack.oneClick();
}else {
//连续点击中 +1 次
myClickCallBack.continuousClick();
}
// 清空handler延时,并防内存泄漏
handler.removeCallbacksAndMessages(null);
}
// 延时timeout后执行run方法中的代码
}, timeout);
}
// 让点击事件继续传播,方便再给View添加其他事件监听
return false;
}
}
\ No newline at end of file
... ...
package com.wd.common.listener;
import android.view.GestureDetector;
import android.view.MotionEvent;
import com.wd.base.log.Logger;
/**
* Time:2023/11/8
* Author:ypf
* Description:简化版手势类回调
*/
public interface SimpleGestureDetectorListener extends GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
String TAG = "SimpleGestureDetectorLi";
@Override
default boolean onSingleTapConfirmed(MotionEvent e) {
Logger.t(TAG).i("onSingleTapConfirmed");
return true;
}
@Override
default boolean onDoubleTap(MotionEvent e) {
Logger.t(TAG).i("onDoubleTap");
return true;
}
@Override
default boolean onDoubleTapEvent(MotionEvent e) {
Logger.t(TAG).i("onDoubleTapEvent");
return true;
}
@Override
default boolean onDown(MotionEvent e) {
Logger.t(TAG).i("onDown");
return true;
}
@Override
default void onShowPress(MotionEvent e) {
Logger.t(TAG).i("onShowPress");
}
@Override
default boolean onSingleTapUp(MotionEvent e) {
Logger.t(TAG).i("onSingleTapUp");
return true;
}
@Override
default boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Logger.t(TAG).i("onScroll");
return true;
}
@Override
default void onLongPress(MotionEvent e) {
Logger.t(TAG).i("onLongPress");
}
@Override
default boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Logger.t(TAG).i("onFling");
return true;
}
}
... ...
package com.wd.common.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.SimpleTarget;
import com.wd.foundation.bean.comment.TransparentBean;
import com.wd.foundation.bean.convenience.AskItemDetail;
import com.wd.foundation.bean.custom.comp.AudioBean;
import com.wd.foundation.bean.custom.comp.ChannelInfoBean;
import com.wd.foundation.bean.custom.content.ContentBean;
import com.wd.foundation.bean.custom.content.ContentTypeConstant;
import com.wd.foundation.bean.custom.content.PeopleMasterBean;
import com.wd.foundation.bean.custom.content.RelInfoBean;
import com.wd.foundation.bean.custom.share.ShareBean;
import com.wd.foundation.bean.mail.LiveInfo;
import com.wd.foundation.bean.mail.ShareInfo;
import com.wd.foundation.bean.mail.VideoInfo;
import com.wd.foundation.bean.response.NewsDetailBean;
import com.wd.foundation.bean.response.PhotoBean;
import com.wd.foundation.bean.utils.TimeFormater;
import com.wd.foundation.bean.utils.TimeUtil;
import com.wd.foundation.wdkit.constant.Constants;
import com.wd.foundation.wdkit.file.MyFileUtils;
import com.wd.foundation.wdkit.image.GlideApp;
import com.wd.foundation.wdkit.utils.SpUtils;
import com.wd.foundation.wdkitcore.tools.ArrayUtils;
import com.wd.foundation.wdkitcore.tools.StringUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
/**
* @author LiuKun
* @date 2023/5/13 16:11
* @Description:分享工具类
*/
public class MyShareUtils {
public static final String SHARE_PATH = "shareDataIcon";
private static final String SHARE_FILE_NAME = "shareIcon";
private MyShareUtils() {
}
/**
* 保存分享封面到本地
*/
public static String saveShareIcon(Bitmap bitmap) {
String s = MyFileUtils.saveBitmap(bitmap, SHARE_PATH, SHARE_FILE_NAME);
return s;
}
public static String getSharePath() {
return MyFileUtils.getBasePath() + File.separator + SHARE_PATH + File.separator + SHARE_FILE_NAME + ".jpg";
}
/**
* 删除下载的分享图标,避免下载失败,使用到上一个内容的图片
*/
public static void delShareLoadIcon() {
String sharePath = getSharePath();
File file = new File(sharePath);
if (file.exists()) {
file.delete();
}
}
/**
* 获取下载到本地的图片的bitmap
*/
public static Bitmap getBitmap() {
FileInputStream fis = null;
Bitmap bitmap = null;
try {
fis = new FileInputStream(getSharePath());
bitmap = BitmapFactory.decodeStream(fis);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(fis != null){
try{
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
return bitmap;
}
/**
* 添加底色
*/
public static Bitmap setBitmapBackgroundColor(Bitmap bitmap, int color) {
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
canvas.drawColor(color);
Paint paint = new Paint();
canvas.drawBitmap(bitmap, 0, 0, paint);
return newBitmap;
}
public static void loadBitmapListener(Context mContext, String url, SimpleTarget<Bitmap> listener, int errorResourceId, int placeholderResourceId) {
if (mContext == null) {
return;
}
RequestOptions mRequestOptions = new RequestOptions().diskCacheStrategy(DiskCacheStrategy.DATA)
.error(errorResourceId).placeholder(placeholderResourceId).dontAnimate();
GlideApp.with(mContext).asBitmap().load(url).apply(mRequestOptions).into(listener);
}
/**
* 详情数据设置给分享
* @param shareData
* @param detailBean
*/
public static void setShareData(ShareBean shareData, NewsDetailBean detailBean){
if (shareData != null && detailBean != null) {
//类型
String newsType = detailBean.getNewsType();
shareData.setContentType(newsType);
//来源
String newsSource = detailBean.getNewsSource();
String newsSourceName = detailBean.getNewsSourceName();
shareData.setNewsSource(newsSource);
shareData.setNewsSourceName(newsSourceName);
shareData.setSpecialColumnId(detailBean.getSpecialColumnId());
shareData.setRmhPlatform(detailBean.rmhPlatform);
//处理分享时间
String publishTime = detailBean.getPublishTime();
if(StringUtils.isEqual("2",detailBean.getNewsType()) &&
detailBean.getLiveInfo() != null){
//是直播
LiveInfo liveInfo = detailBean.getLiveInfo();
if (Constants.LIVE_WAIT.equals(liveInfo.getLiveState()) &&
StringUtils.isNotBlank(liveInfo.getPlanStartTime())) {
//预约,取计划直播时间
publishTime = liveInfo.getPlanStartTime();
}else if(StringUtils.isNotBlank(liveInfo.getStartTime())){
//直播中或结束,取直播开始时间
publishTime = liveInfo.getStartTime();
}
}
shareData.setPublishTime(publishTime);
//海报标题、简介和分享出去的链接有区别
shareData.setPosterTitle(detailBean.getNewsTitle());
shareData.setPosterSummary(detailBean.getNewsSummary());
//点赞样式
int likesStyle = detailBean.getLikesStyle();
shareData.setLikesStyle(likesStyle);
//稿件的样式
shareData.setAppStyle(detailBean.getAppStyle());
//是否开启点赞
shareData.setOpenLikes(detailBean.getOpenLikes());
//是否重点稿件
shareData.setKeyArticle(detailBean.getKeyArticle());
//是否领导人文章 0 否,1 是,不存在就传0
shareData.setLeaderArticle(detailBean.getLeaderArticle());
//rmhInfo
PeopleMasterBean rmhInfo = detailBean.getRmhInfo();
String userId = "";
if (rmhInfo != null){
shareData.setMasterHead(rmhInfo.getRmhHeadUrl());
shareData.setMasterName(rmhInfo.getRmhName());
shareData.setMasterIntroduction(rmhInfo.getRmhDesc());
shareData.setPosterShareControl(rmhInfo.getPosterShareControl());
userId = rmhInfo.getUserId();
shareData.setAuthIcon(rmhInfo.getAuthIcon());
}else {
shareData.setPosterShareControl("1");
}
//id
shareData.setContentId(detailBean.getNewsId());
//CMS 的不可以举报,判断是否可以举报
if (Constants.CONTENT_SOURCE_CMS == detailBean.rmhPlatform||"13".equals(detailBean.getNewsType())
|| userId.equals(SpUtils.getUserId())){
shareData.setShowReport(false);
}else {
shareData.setShowReport(true);
}
/**
* 增加评论海报封面图
*/
if (detailBean.getFullColumnImgUrls().size() > 0){
shareData.setFullColumnImgUrl(detailBean.getFullColumnImgUrls().get(0).url);
}
/**
* 关联关系
*/
RelInfoBean reLInfo = detailBean.getReLInfo();
if (reLInfo != null) {
shareData.setRelType(reLInfo.getRelType());
shareData.setContentRelId(reLInfo.getRelId());
shareData.setTargetRelId(reLInfo.getRelId());
shareData.setTargetRelType(reLInfo.getRelType());
shareData.setChannelId(reLInfo.getChannelId());
}
/**
* 分享信息
*/
ShareInfo shareInfo = detailBean.getShareInfo();
if (shareInfo != null){
//分享链接
shareData.setShareUrl(shareInfo.getShareUrl());
//分享标题
if (String.valueOf(ContentTypeConstant.URL_TYPE_TWO).equals(newsType)){
//直播状态拼接
String retStatus = dealLiveStatus(detailBean);
shareData.setTitle(retStatus + shareInfo.getShareTitle());
}else {
shareData.setTitle(shareInfo.getShareTitle());
}
//分享简介
shareData.setDescription(shareInfo.getShareSummary());
//分享图标
shareData.setImageUrl(shareInfo.getShareCoverUrl());
//分享海报图片
shareData.setSharePosterCoverUrl(shareInfo.getSharePosterCoverUrl());
//显示CMS配置的海报
shareData.setSharePosterStyle(shareInfo.getSharePosterStyle());
//分享海报开关
shareData.setSharePosterOpen(shareInfo.getSharePosterOpen());
shareData.setShareOpen(shareInfo.getShareOpen());
}else {
shareData.setShareOpen("0");
}
/**
* 视频列表
*/
if ((String.valueOf(ContentTypeConstant.URL_TYPE_ONE)).equals(newsType)){
//点播
List<VideoInfo> videoInfo = detailBean.getVideoInfo();
if (videoInfo != null && videoInfo.size() > 0 && videoInfo.get(0) != null){
String duration = videoInfo.get(0).getVideoDuration();
try {
String videoTime = TimeFormater.formatMs(Integer.parseInt(duration) * 1000);
shareData.setVideoTime(videoTime);
}catch (Exception e){
e.printStackTrace();
}
}
}else if ((String.valueOf(ContentTypeConstant.URL_TYPE_NINE)).equals(newsType)){
//图集
List<PhotoBean> photoList = detailBean.getPhotoList();;
if (ArrayUtils.isNotEmpty(photoList)){
int size = photoList.size();
shareData.setPicNumber(size);
//描述字段: 选取稿件摘要,没有取第一张有图注的图片的图注,若都没有隐藏不展示
PhotoBean photoBean = photoList.get(0);
if (StringUtils.isBlank(detailBean.getNewsSummary()) && photoBean != null) {
shareData.setPosterSummary(photoBean.getPicDesc());
}
}
}else if ((String.valueOf(ContentTypeConstant.URL_TYPE_THIRTEEN)).equals(newsType)){
//音频
List<AudioBean> audioBeanList = detailBean.getAudioList();
try {
if (ArrayUtils.isNotEmpty(audioBeanList)){
AudioBean audioBean = audioBeanList.get(0);
if (null != audioBean){
int duration = audioBean.getDuration();
String videoTime = TimeFormater.formatMs(duration * 1000);
shareData.setVideoTime(videoTime);
}
}
}catch (Exception e){
e.printStackTrace();
}
//音频 选取稿件摘要,没有取正文字段
if (StringUtils.isBlank(detailBean.getNewsSummary())) {
shareData.setPosterSummary(detailBean.getNewsContent());
}
}else if ((String.valueOf(ContentTypeConstant.URL_TYPE_EIGHT)).equals(newsType)){
//文章 选取稿件摘要,没有取正文字段 ( 注意不要取到图注 )
if (StringUtils.isBlank(detailBean.getNewsSummary())) {
shareData.setPosterSummary(detailBean.getNewsContent());
}
}
}
}
/**
* 详情数据设置给分享
* @param shareData
* @param detailBean
*/
public static void setShareData(ShareBean shareData, AskItemDetail detailBean, String description){
if (shareData != null && detailBean != null) {
//类型
shareData.setContentType(String.valueOf(ContentTypeConstant.URL_TYPE_SIXTEEN));
//来源
// String newsSource = detailBean.getNewsSource();
// String newsSourceName = detailBean.getNewsSourceName();
// shareData.setNewsSource(newsSource);
// shareData.setNewsSourceName(newsSourceName);
shareData.setRmhPlatform(detailBean.rmhPlatform);
//设置点赞、收藏可见,默认未点赞,根据接口查询状态
shareData.setShowLike(0);
shareData.setShowCollect(0);
//海报标题、简介和分享出去的链接有区别
shareData.setPosterTitle(detailBean.title);
shareData.setPosterSummary(detailBean.content);
//点赞样式
int likesStyle = Integer.parseInt(detailBean.likesStyle);
shareData.setLikesStyle(likesStyle);
//是否重点稿件
shareData.setKeyArticle(detailBean.keyArticle);
//是否领导人文章 0 否,1 是,不存在就传0
shareData.setLeaderArticle(detailBean.leaderArticle);
// //rmhInfo
// PeopleMasterBean rmhInfo = detailBean.getRmhInfo();
// if (rmhInfo != null){
// shareData.setMasterHead(rmhInfo.getRmhHeadUrl());
// shareData.setMasterName(rmhInfo.getRmhName());
// shareData.setPosterShareControl(rmhInfo.getPosterShareControl());
// }else {
shareData.setPosterShareControl("1");
// }
//id
shareData.setContentId(String.valueOf(detailBean.getAskId()));
shareData.setShowLike(Integer.parseInt(detailBean.openLikes));
// //是否可以举报
// if (CONTENT_SOURCE_RMRB.equals(newsSource)){
// shareData.setShowReport(true);
// }else {
shareData.setShowReport(false);
// }
// /**
// * 关联关系
// */
// RelInfoBean reLInfo = detailBean.getReLInfo();
// if (reLInfo != null) {
// shareData.setRelType(reLInfo.getRelType());
// shareData.setContentRelId(reLInfo.getRelId());
// shareData.setTargetRelId(reLInfo.getRelId());
// shareData.setTargetRelType(reLInfo.getRelType());
// }
/**
* 分享信息
*/
ShareInfo shareInfo = detailBean.shareInfo;
if (shareInfo != null){
//分享链接
shareData.setShareUrl(shareInfo.getShareUrl());
// //分享标题
// if (String.valueOf(ContentTypeConstant.URL_TYPE_TWO).equals(detailBean.newsType)){
// //直播状态拼接
// String retStatus = dealLiveStatus(detailBean);
// shareData.setTitle(retStatus + shareInfo.getShareTitle());
// }else {
shareData.setTitle(shareInfo.getShareTitle());
// }
//分享简介
shareData.setDescription(description);
//分享图标
// shareData.setImageUrl(shareInfo.getShareCoverUrl());
//分享海报图片
shareData.setSharePosterCoverUrl(shareInfo.getSharePosterCoverUrl());
//显示CMS配置的海报
shareData.setSharePosterStyle(shareInfo.getSharePosterStyle());
//分享海报开关
shareData.setSharePosterOpen(shareInfo.getSharePosterOpen());
shareData.setShareOpen(shareInfo.getShareOpen());
}else {
shareData.setShareOpen("0");
}
// /**
// * 视频列表
// */
// if ((String.valueOf(ContentTypeConstant.URL_TYPE_ONE)).equals(detailBean.newsType)){
// //点播
// List<VideoInfo> videoInfo = detailBean.getVideoInfo();
// if (videoInfo != null && videoInfo.get(0) != null){
// String duration = videoInfo.get(0).getVideoDuration();
// try {
// String videoTime = TimeFormater.formatMs(Integer.parseInt(duration) * 1000);
// shareData.setVideoTime(videoTime);
// }catch (Exception e){
// e.printStackTrace();
// }
// }
// }else if ((String.valueOf(ContentTypeConstant.URL_TYPE_NINE)).equals(newsType)){
// List<PhotoBean> photoList = detailBean.getPhotoList();;
// if (!ArrayUtils.isEmpty(photoList)){
// int size = photoList.size();
// shareData.setPicNumber(size);
// }
// }
}
}
/**
* 详情数据设置给分享
* @param shareData
* @param contentBean
*/
public static void setShareData(ShareBean shareData, ContentBean contentBean){
if (shareData != null && contentBean != null) {
//类型
String newsType = contentBean.getObjectType();
shareData.setContentType(newsType);
//来源
String newsSource = contentBean.getNewsAuthor();
String newsSourceName = contentBean.getSource();
shareData.setNewsSource(newsSource);
shareData.setNewsSourceName(newsSourceName);
shareData.setRmhPlatform(contentBean.getRmhPlatform());
//稿件的样式
shareData.setAppStyle(contentBean.getAppStyle());
//处理分享时间
String publishTime = contentBean.getPublishTime();
if(StringUtils.isEqual("2",contentBean.getObjectType()) &&
contentBean.getLiveInfo() != null &&
StringUtils.isNotBlank(contentBean.getLiveInfo().liveStartTime)){
//是直播(暂加直播预约类型,和iOS保持一致)
LiveInfo liveInfo = contentBean.getLiveInfo();
//bff都通过liveStartTime赋值
Date date = null;
try {
date = TimeUtil.datetimeFormat.parse(liveInfo.liveStartTime);
publishTime = date.getTime()+"";
} catch (ParseException e) {
e.printStackTrace();
}
}
shareData.setPublishTime(publishTime);
//海报标题、简介和分享出去的链接有区别
shareData.setPosterTitle(contentBean.getNewsTitle());
shareData.setPosterSummary(contentBean.getNewsSummary());
//点赞样式
int likesStyle = contentBean.getLikeStyle();
shareData.setLikesStyle(likesStyle);
//是否重点稿件
shareData.setKeyArticle(contentBean.getKeyArticle());
//是否领导人文章 0 否,1 是,不存在就传0
shareData.setLeaderArticle(contentBean.getLeaderArticle());
//rmhInfo
PeopleMasterBean rmhInfo = contentBean.getRmhInfo();
String userId = "";
if (rmhInfo != null){
shareData.setMasterHead(rmhInfo.getRmhHeadUrl());
shareData.setMasterName(rmhInfo.getRmhName());
shareData.setMasterIntroduction(rmhInfo.getRmhDesc());
//号主的不一样 0有分享海报权限,1无分享海报权限
shareData.setPosterShareControl(rmhInfo.getPosterShareControl());
userId = rmhInfo.getUserId();
shareData.setAuthIcon(rmhInfo.getAuthIcon());
}else {
shareData.setPosterShareControl("1");
}
//id
shareData.setContentId(contentBean.getObjectId());
shareData.setShowLike(contentBean.getOpenLikes());
//判断是否可以举报
if (Constants.CONTENT_SOURCE_CMS == contentBean.getRmhPlatform() ||"13".equals(newsType)
|| userId.equals(SpUtils.getUserId())){
shareData.setShowReport(false);
}else {
shareData.setShowReport(true);
}
//点赞状态、收藏状态
// shareData.setShowLike();
/**
* 关联关系
*/
RelInfoBean reLInfo = contentBean.getReLInfo();
if (reLInfo != null) {
shareData.setRelType(reLInfo.getRelType());
shareData.setContentRelId(reLInfo.getRelId());
shareData.setTargetRelId(reLInfo.getRelId());
shareData.setTargetRelType(reLInfo.getRelType());
}
ChannelInfoBean channelInfoBean = contentBean.getChannelInfoBean();
if (channelInfoBean != null ){
shareData.setChannelId(channelInfoBean.getChannelId());
}
/**
* 分享信息
*/
ShareInfo shareInfo = contentBean.getShareInfo();
if (shareInfo != null){
//分享链接
shareData.setShareUrl(shareInfo.getShareUrl());
//分享标题
if (String.valueOf(ContentTypeConstant.URL_TYPE_TWO).equals(newsType)){
//直播状态拼接
String retStatus = dealLiveStatus(contentBean);
shareData.setTitle(retStatus + shareInfo.getShareTitle());
}else {
shareData.setTitle(shareInfo.getShareTitle());
}
//分享简介
shareData.setDescription(shareInfo.getShareSummary());
//分享图标
shareData.setImageUrl(shareInfo.getShareCoverUrl());
//分享海报图片
shareData.setSharePosterCoverUrl(shareInfo.getSharePosterCoverUrl());
//显示CMS配置的海报
shareData.setSharePosterStyle(shareInfo.getSharePosterStyle());
//分享海报开关
shareData.setSharePosterOpen(shareInfo.getSharePosterOpen());
shareData.setShareOpen(shareInfo.getShareOpen());
}else {
shareData.setShareOpen("0");
}
/**
* 视频列表
*/
if ((String.valueOf(ContentTypeConstant.URL_TYPE_ONE)).equals(newsType)){
//点播
VideoInfo videoInfo = contentBean.getVideoInfo();
if (videoInfo != null ){
String duration = videoInfo.getVideoDuration();
try {
String videoTime = TimeFormater.formatMs(Integer.parseInt(duration) * 1000);
shareData.setVideoTime(videoTime);
}catch (Exception e){
e.printStackTrace();
}
}
}else if ((String.valueOf(ContentTypeConstant.URL_TYPE_NINE)).equals(newsType)){
String size = contentBean.getPhotoNum();
try{
Integer sizeValue = Integer.valueOf(size);
shareData.setPicNumber(sizeValue);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/**
* 处理下直播状态、拼接到分享标题
*/
private static String dealLiveStatus(NewsDetailBean detailBean){
String status = "";
if (detailBean != null && detailBean.getLiveInfo() != null){
String liveStatus = detailBean.getLiveInfo().getLiveState();
if (!StringUtils.isBlank(liveStatus)){
if (Constants.LIVE_END.equals(liveStatus)){
status = "[直播回放]";
}else if (Constants.LIVE_RUNNING.equals(liveStatus)){
status = "[正在直播]";
}else if (Constants.LIVE_WAIT.equals(liveStatus)){
status = "[直播预约]";
}
}
}
return status;
}
/**
* 处理下直播状态、拼接到分享标题
*/
private static String dealLiveStatus(ContentBean contentBean){
String status = "";
if (contentBean != null && contentBean.getLiveInfo() != null){
String liveStatus = contentBean.getLiveInfo().getLiveState();
if (!StringUtils.isBlank(liveStatus)){
if (Constants.LIVE_END.equals(liveStatus)){
status = "[直播回放]";
}else if (Constants.LIVE_RUNNING.equals(liveStatus)){
status = "[正在直播]";
}else if (Constants.LIVE_WAIT.equals(liveStatus)){
status = "[直播预约]";
}
}
}
return status;
}
/**
* 统一设置数据,减少代码量
* @param transparentBean
* @param newsDetailBean
*/
public static void setTransparentBean(TransparentBean transparentBean, NewsDetailBean newsDetailBean){
if (transparentBean != null && newsDetailBean != null){
transparentBean.setContentId(newsDetailBean.getNewsId());
transparentBean.setContentType(newsDetailBean.getNewsType());
transparentBean.setPreCommentFlag(newsDetailBean.getPreCommentFlag());
//是否可以点赞 1:是 0:否
transparentBean.setOpenLikes(newsDetailBean.getOpenLikes());
transparentBean.setLikesStyle(newsDetailBean.getLikesStyle());
//稿件的样式
transparentBean.setAppStyle(newsDetailBean.getAppStyle());
//迭代二新增
transparentBean.setContentTitle(newsDetailBean.getNewsTitle());
transparentBean.setKeyArticle(newsDetailBean.getKeyArticle());
//是否领导人文章 0 否,1 是,不存在就传0
transparentBean.setLeaderArticle(newsDetailBean.getLeaderArticle());
//评论开关设置
transparentBean.setOpenComment(newsDetailBean.getOpenComment());
transparentBean.setCommentDisplay(newsDetailBean.getCommentDisplay());
transparentBean.setCommentEntryFlag(newsDetailBean.getCommentEntryFlag());
transparentBean.setRmhPlatform(newsDetailBean.rmhPlatform);
//是否开启最佳评论
transparentBean.setBestNoticer(newsDetailBean.getBestNoticer());
transparentBean.setMenuShow(newsDetailBean.getMenuShow());
//设置频道id
if(StringUtils.isNotEmpty(newsDetailBean.getChannelId())){
transparentBean.setChannelId(newsDetailBean.getChannelId());
}
RelInfoBean reLInfo = newsDetailBean.getReLInfo();
//内容关系
if (null != reLInfo) {
//迭代二新增
transparentBean.setTargetRelObjectId(reLInfo.getRelObjectId());
transparentBean.setTargetRelId(reLInfo.getRelId());
transparentBean.setTargetRelType(reLInfo.getRelType());
transparentBean.setRelType(reLInfo.getRelType());
transparentBean.setContentRelId(reLInfo.getRelId());
transparentBean.setChannelId(reLInfo.getChannelId());
}
}
}
/**
* 统一设置数据,减少代码量
* @param transparentBean
* @param askItemDetail
*/
public static void setTransparentBean(TransparentBean transparentBean, AskItemDetail askItemDetail){
if (transparentBean != null && askItemDetail != null){
transparentBean.setContentId(String.valueOf(askItemDetail.getAskId()));
transparentBean.setContentType(String.valueOf(ContentTypeConstant.URL_TYPE_SIXTEEN));
transparentBean.setPreCommentFlag(Integer.parseInt(askItemDetail.preCommentFlag));
//是否可以点赞 1:是 0:否
transparentBean.setOpenLikes(Integer.parseInt(askItemDetail.openLikes));
transparentBean.setLikesStyle(Integer.parseInt(askItemDetail.likesStyle));
//迭代二新增
transparentBean.setContentTitle(askItemDetail.title);
transparentBean.setKeyArticle(askItemDetail.keyArticle);
//是否领导人文章 0 否,1 是,不存在就传0
transparentBean.setLeaderArticle(askItemDetail.leaderArticle);
//评论开关设置
transparentBean.setOpenComment(Integer.parseInt(askItemDetail.openComment));
transparentBean.setCommentDisplay(Integer.parseInt(askItemDetail.commentDisplay));
// transparentBean.setCommentEntryFlag(askItemDetail.getCommentEntryFlag());
// RelInfoBean reLInfo = askItemDetail.getReLInfo();
// //内容关系
// if (null != reLInfo) {
// //迭代二新增
// transparentBean.setTargetRelObjectId(reLInfo.getRelObjectId());
// transparentBean.setTargetRelId(reLInfo.getRelId());
// transparentBean.setTargetRelType(reLInfo.getRelType());
// transparentBean.setRelType(reLInfo.getRelType());
// transparentBean.setContentRelId(reLInfo.getRelId());
// }
}
}
}
... ...
package com.wd.common.utils;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
/**
* Time:2023/5/17
* Author:ypf
* Description:画中画工具类
*/
public class PipUtils {
/**
* 判断是否支持画中画
*
* @return 是否支持画中画
*/
public static boolean supportPictureInPicture(Activity activity) {
if (activity == null) {
return false;
}
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
}
/**
* 判断当前是否处于小窗模式
*
* @return 是否支持画中画
*/
public static boolean isInPictureInPictureMode(Activity activity) {
if (activity == null) {
return false;
}
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && activity.isInPictureInPictureMode();
}
}
... ...
... ... @@ -42,6 +42,7 @@ import com.wd.foundation.wdkitcore.livedata.LiveDataBus;
import com.wd.foundation.wdkitcore.thread.ThreadPoolUtils;
import com.wd.foundation.wdkitcore.tools.AppContext;
import com.wd.foundation.wdkitcore.tools.ArrayUtils;
import com.wd.foundation.wdkitcore.tools.JsonUtils;
import com.wd.foundation.wdkitcore.tools.ResUtils;
import com.wd.foundation.wdkitcore.tools.StringUtils;
... ... @@ -249,55 +250,55 @@ public class ProcessUtils implements IntentConstants {
String contentId = content.getObjectId();
if (ContentTypeConstant.URL_TYPE_ZERO == type) {
// 不跳转
// } else if (ContentTypeConstant.URL_TYPE_ONE == type) {
// // 点播
// String videoUrl = "";
// String firstFrameImageUri = "";
// if (content.getVideoInfo() != null) {
// videoUrl = content.getVideoInfo().videoUrl;
// firstFrameImageUri = content.getVideoInfo().firstFrameImageUri;
// }
// String pageId = "";
// if (StringUtils.isNotBlank(content.getPageId())) {
// pageId = content.getPageId();
// }
// String channelId = "";
// if (content.getChannelInfoBean() != null) {
// channelId = content.getChannelInfoBean().getChannelId();
// }
// String topicId = "";
// if (content.getTopicInfoBean() != null) {
// topicId = content.getTopicInfoBean().getTopicId();
// }
// VodDetailIntentBean intentBean = new VodDetailIntentBean();
// intentBean.setPageId(pageId);
// intentBean.setContentId(contentId);
// intentBean.setContentType(content.getObjectType());
// intentBean.setVideoUrl(videoUrl);
// intentBean.setChannelId(channelId);
// intentBean.setTopicId(topicId);
// intentBean.setRecommend(content.getRecommend() == 1);
// intentBean.setCompId(content.getCompId());
// //搜索keyWord
// intentBean.setKeyWord(content.getKeyWord());
// // 浏览历史、收藏页面、站内信(预约直播列表、推送消息记录列表)
// intentBean.setFrom(content.getFromPage());
// // 处理来自哪个页面
// dealIntentBeanRequestType(intentBean, content);
// //站内信(预约直播列表、推送消息记录列表)
// intentBean.localFiledIdType = content.getNewsTitle();
// //添加关联关系
// intentBean.setRelType(content.getRelType() + "");
// intentBean.setContentRelId(content.getRelId());
// // 是否滚动到底部
// intentBean.setScrollToBottom(content.getScrollToBottom());
// // 从列表点击进去观看第一条需要首帧图
// intentBean.setFirstFrameImageUri(firstFrameImageUri);
// // 子线程加载下图片进行缓存
// preloadImage(firstFrameImageUri);
//// // 处理埋点字段
} else if (ContentTypeConstant.URL_TYPE_ONE == type) {
// 点播
String videoUrl = "";
String firstFrameImageUri = "";
if (content.getVideoInfo() != null) {
videoUrl = content.getVideoInfo().videoUrl;
firstFrameImageUri = content.getVideoInfo().firstFrameImageUri;
}
String pageId = "";
if (StringUtils.isNotBlank(content.getPageId())) {
pageId = content.getPageId();
}
String channelId = "";
if (content.getChannelInfoBean() != null) {
channelId = content.getChannelInfoBean().getChannelId();
}
String topicId = "";
if (content.getTopicInfoBean() != null) {
topicId = content.getTopicInfoBean().getTopicId();
}
VodDetailIntentBean intentBean = new VodDetailIntentBean();
intentBean.setPageId(pageId);
intentBean.setContentId(contentId);
intentBean.setContentType(content.getObjectType());
intentBean.setVideoUrl(videoUrl);
intentBean.setChannelId(channelId);
intentBean.setTopicId(topicId);
intentBean.setRecommend(content.getRecommend() == 1);
intentBean.setCompId(content.getCompId());
//搜索keyWord
intentBean.setKeyWord(content.getKeyWord());
// 浏览历史、收藏页面、站内信(预约直播列表、推送消息记录列表)
intentBean.setFrom(content.getFromPage());
// 处理来自哪个页面
dealIntentBeanRequestType(intentBean, content);
//站内信(预约直播列表、推送消息记录列表)
intentBean.localFiledIdType = content.getNewsTitle();
//添加关联关系
intentBean.setRelType(content.getRelType() + "");
intentBean.setContentRelId(content.getRelId());
// 是否滚动到底部
intentBean.setScrollToBottom(content.getScrollToBottom());
// 从列表点击进去观看第一条需要首帧图
intentBean.setFirstFrameImageUri(firstFrameImageUri);
// 子线程加载下图片进行缓存
preloadImage(firstFrameImageUri);
// // 处理埋点字段
// dealTrackField(intentBean, content);
// goVideoDetail(intentBean);
goVideoDetail(intentBean);
// } else if (ContentTypeConstant.URL_TYPE_TWO == type) {
// // 直播
// //每次调接口,调完接口带数据进入直播详情
... ... @@ -450,6 +451,21 @@ public class ProcessUtils implements IntentConstants {
}
/**
* 跳转点播详情页
*/
public static void goVideoDetail(VodDetailIntentBean intentBean) {
// 拦截画中画
interceptorPictureInPicture();
ActionBean actionBean = new ActionBean();
actionBean.paramBean.pageID = RouterConstants.PATH_VIDEO_DETAIL;
actionBean.paramBean.params = JsonUtils.convertObjectToJson(intentBean);
WdRouterRule.getInstance().processAction(AppContext.getContext(), actionBean);
}
/**
* 跳转H5页
*
... ...
... ... @@ -24,11 +24,13 @@ import androidx.core.content.FileProvider;
import com.wd.common.listener.AddFavoriteLabelCallback;
import com.wd.fastcoding.base.R;
import com.wd.foundation.bean.collect.CollectContentBean;
import com.wd.foundation.wdkit.utils.ScreenUtils;
import com.wd.foundation.wdkitcore.constant.BaseConstants;
import com.wd.foundation.wdkit.file.MyFileUtils;
import com.wd.foundation.wdkit.perloader.DeviceHelper;
import com.wd.foundation.wdkit.statusbar.StatusBarCompat;
import com.wd.foundation.wdkit.statusbar.StatusBarStyleEnum;
import com.wd.foundation.wdkitcore.tools.AppContext;
import com.wd.foundation.wdkitcore.tools.ResUtils;
import com.yalantis.ucrop.UCrop;
... ... @@ -352,6 +354,18 @@ public class ToolsUtil {
}
/**
* 浮点型数据比较大小
*
* @param number1
* @param number2
* @return 比较结果
*/
public static boolean isEqual(double number1, double number2) {
double diff = 1e-2f;
return Math.abs(number1 - number2) < diff;
}
/**
* 提供精确的除法运算
*
* @param v1 被除数
... ... @@ -745,4 +759,25 @@ public class ToolsUtil {
}
// CollectDialog.createDialog(context, contentList, addFavoriteLabelCallback).show();
}
/**
* 是否16:9小屏
*/
private static Boolean isSmallScreen = null;
/**
* 是否小屏手机16:9-小屏手机一级沉浸式视频全屏显示,其他fragment设置底部padding为65dp
*
* @return true or false
*/
public static boolean isSmallScreen() {
if (isSmallScreen != null) {
return isSmallScreen;
}
int screenHeight = ScreenUtils.getRealHeight();
int screenWidth = ScreenUtils.getRealWidth();
double screenRatio = ToolsUtil.divNum(screenHeight, screenWidth);
// 小屏手机16:9
return isSmallScreen = ToolsUtil.isEqual(screenRatio, 1.78d);
}
}
... ...
package com.wd.common.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.annotation.Nullable;
import com.wd.fastcoding.base.R;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DYVideoLoadingView extends View {
private static final String TAG = "DYVideoLoadingView";
private int mWidth, mHeight;
private int mDefaultWidth, mDefaultHeight;
private int mProgressWidth;
private int mMinProgressWidth;
private Paint mPaint;
private String mColor;
private Handler mHandler;
private int mTimePeriod = 1;
public DYVideoLoadingView(Context context) {
this(context, null);
}
public DYVideoLoadingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public DYVideoLoadingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取颜色值和最小宽高度,以及进度条最小宽度
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DYVideoLoadingView);
String color = typedArray.getString(R.styleable.DYVideoLoadingView_amberprogressColor);
mDefaultWidth = (int) typedArray.getDimension(R.styleable.DYVideoLoadingView_amberminWidth, 600);
mDefaultHeight = (int) typedArray.getDimension(R.styleable.DYVideoLoadingView_amberminHeight, 5);
mMinProgressWidth = (int) typedArray.getDimension(R.styleable.DYVideoLoadingView_amberminProgressWidth, 100);
mProgressWidth = mMinProgressWidth;
//根据正则表达式来判断颜色格式是否正确
String regularStr = "^#[A-Fa-f0-9]{6}";
Pattern compile = Pattern.compile(regularStr);
if (color == null) {
mColor = "#808080";
} else {
Matcher matcher = compile.matcher(color);
if (matcher.matches()) {//如果颜色格式正确
mColor = color;
} else {
//如果颜色格式不正确
throw new IllegalArgumentException("wrong color string type!");
}
}
typedArray.recycle();
/* //设置view的默认最小宽度
mDefaultWidth=600;
//设置view的默认最小高度
mDefaultHeight=5;
//设置进度条的初始宽度,这个宽度不能大于view的最小宽度,否则进度条不能向两边延伸
mProgressWidth=100;
//设置默认初始颜色
mColor="#808080";*/
mPaint = new Paint();
//设置虎逼模式为填充带边框
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
//设置抗锯齿
mPaint.setAntiAlias(true);
}
/**
* 设置重绘的周期
*
* @param timePeriod
*/
public void setTimePeriod(int timePeriod) {
if (mTimePeriod > 0) {
this.mTimePeriod = timePeriod;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//通过widthMeasureSpec,heightMeasureSpec 来获取view的测量模式和宽高
int width = getValue(widthMeasureSpec, true);
int height = getValue(heightMeasureSpec, false);
//此方法用来设置设置View的具体宽高
setMeasuredDimension(width, height);
}
/**
* 获取view的宽高值
*
* @param measureSpec
* @param isWidth 是否是测量宽度
* @return
*/
private int getValue(int measureSpec, boolean isWidth) {
//获取测量模式
int mode = MeasureSpec.getMode(measureSpec);
//获取测量的值
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
/**
* 如果父控件传递给的MeasureSpec的mode是MeasureSpec.UNSPECIFIED,就说明,父控件对自己没有任何限制,那么尺寸就选择自己需要的尺寸size
如果父控件传递给的MeasureSpec的mode是MeasureSpec.EXACTLY,就说明父控件有明确的要求,希望自己能用measureSpec中的尺寸,这时就推荐使用MeasureSpec.getSize(measureSpec)
如果父控件传递给的MeasureSpec的mode是MeasureSpec.AT_MOST,就说明父控件希望自己不要超出MeasureSpec.getSize(measureSpec),如果超出了,就选择MeasureSpec.getSize(measureSpec),否则用自己想要的尺寸就行了
*/
case MeasureSpec.EXACTLY:
//子view的大小已经被限定死,我们只能使用其固定大小
return size;
case MeasureSpec.AT_MOST:
//父控件认为子view的大小不能超过size的值,那么我们就取size和默认值之间的最小值
return Math.min(isWidth ? mDefaultWidth : mDefaultHeight, size);
case MeasureSpec.UNSPECIFIED:
//父view不限定子view的大小,我们将其值设置为默认值
return isWidth ? mDefaultWidth : mDefaultHeight;
default:
return isWidth ? mDefaultWidth : mDefaultHeight;
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
if (mWidth < mProgressWidth) {
//如果宽度小于进度条的宽度
Log.d(TAG, "the progressWidth must less than mWidth");
}
mPaint.setStrokeWidth(mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//首先判断进度条的宽度是否大于view宽度
if (mProgressWidth < mWidth) {
//如果不大于,将进度条宽度增加10
mProgressWidth += 25;//注意执行此步骤是mProgressWidth值有可能大于view宽度;
} else {
//如果进度条宽度大于view宽度将进度条宽度设置为初始宽度
mProgressWidth = mMinProgressWidth;
}
//计算颜色透明度
//mProgressWidth/mWidth 计算当前进度条宽度占总宽度的比例
//255*mProgressWidth/mWidth 计算当前比例下对应的透明度值
//由于是由不透明变成全透明,所以使用255减去其值
int currentColorValue = 255;
if (mWidth != 0){
currentColorValue = 255 - 255 * mProgressWidth / mWidth;
}
if (currentColorValue > 255) {
//由于mProgressWidth有可能大于view的宽度,要保证颜色值不能大于255
currentColorValue = 255;
}
if (currentColorValue < 30) {
//此处是为了限制让其不成为全透明,如果设置为全透明,在最后阶段进度宽度渐变观察不到
currentColorValue = 30;
}
//将透明度转换为16进制
String s = Integer.toHexString(currentColorValue);
//拼接颜色字符串并转化为颜色值
int color = Color.parseColor("#" + mColor.substring(1, mColor.length()));
//给画笔设置颜色
mPaint.setColor(color);
//使用canvas来画进度条(确实就是画一条线)
canvas.drawLine(mWidth / 2 - mProgressWidth / 2, mDefaultHeight / 2, mWidth / 2 + mProgressWidth / 2,
mDefaultHeight / 2, mPaint);
}
public void startAnimation() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
invalidate();
this.sendEmptyMessageDelayed(1, mTimePeriod);
}
};
mHandler.sendEmptyMessageDelayed(1, mTimePeriod);
}
public void stopAnimation() {
if (mHandler != null) {
mHandler.removeMessages(1);
}
}
}
... ...
package com.wd.common.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ScrollView;
import com.wd.fastcoding.base.R;
import com.wd.foundation.wdkit.utils.UiUtils;
/**
* 描述:最大滚动高度
*
* @author : lvjinhui
* @since: 2023/11/2
*/
public class MaxHeightScrollView extends ScrollView {
private int maxHeight;
public MaxHeightScrollView(Context context) {
this(context, null);
}
public MaxHeightScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MaxHeightScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaxHeightScrollView);
maxHeight = typedArray.getDimensionPixelSize(R.styleable.MaxHeightScrollView_maxScrollHeight,
UiUtils.dp2px(R.dimen.rmrb_dp330));
typedArray.recycle();
}
public void setMaxHeight(int maxHeight){
this.maxHeight = maxHeight;
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));
}
}
\ No newline at end of file
... ...
/*
* Copyright (c) People Technologies Co., Ltd. 2019-2022. All rights reserved.
*/
package com.wd.common.widget;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.wd.base.log.Logger;
import com.wd.common.listener.MyClickListener;
import com.wd.fastcoding.base.R;
import java.security.SecureRandom;
/**
* 描述:点赞动画view
*
* @author : lvjinhui
* @since: 2022/7/7
*/
public class ThumbsUpView extends RelativeLayout {
/**
* 点击的时间间隔
*/
private static long INTERVAL = 300;
/**
* 记录上一次的点击时间
*/
private long lastClickTime = 0;
private final long value0_5 = 50L;
private final long value1 = 100L;
private final long value1_5 = 150L;
private final int value1_5_int = 150;
private final int value2 = 200;
private final int value3 = 150;
private final long value3L = 300L;
private final long value4 = 400L;
private final Float value6 = -600F;
private final long value7 = 700L;
private final long value8 = 800L;
private Context mContext;
/**
* 随机心形图片角度
*/
private float[] num = {-30, -20, 0, 20, 30};
private long[] mHits = new long[2];
private MyClickListener.MyClickCallBack clickCallBack;
private MyClickListener myClickListener;
private int drawableId = R.mipmap.icon_bottom_liked;
public ThumbsUpView(Context context) {
this(context, null);
}
public ThumbsUpView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ThumbsUpView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
}
public void setAnimalDrawableId(int drawableId) {
this.drawableId = drawableId;
}
/**
* 监听点击事件
*
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 获取点击时间
long currTime = SystemClock.uptimeMillis();
// 判断点击之间的时间差
long interval = currTime - lastClickTime;
lastClickTime = currTime;
// 小于0.2秒,拦截事件,并做处理
if (interval < INTERVAL) {
setAnnouncement(event.getX(),event.getY());
}
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (myClickListener!=null){
myClickListener.onTouch(this,event);
}
return super.onTouchEvent(event);
}
private ObjectAnimator scaleAni(View view, String propertyName, Float from, Float to, Long time, Long delay) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, propertyName, from, to);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.setStartDelay(delay);
objectAnimator.setDuration(time);
return objectAnimator;
}
private ObjectAnimator translationX(View view, Float from, Float to, Long time, Long delayTime) {
ObjectAnimator ani = ObjectAnimator.ofFloat(view, "translationX", from, to);
ani.setInterpolator(new LinearInterpolator());
ani.setStartDelay(delayTime);
ani.setDuration(time);
return ani;
}
private ObjectAnimator translationY(View view, Float from, Float to, Long time, Long delayTime) {
ObjectAnimator ani = ObjectAnimator.ofFloat(view, "translationY", from, to);
ani.setInterpolator(new LinearInterpolator());
ani.setStartDelay(delayTime);
ani.setDuration(time);
return ani;
}
private ObjectAnimator alphaAni(View view, Float from, Float to, Long time, Long delayTime) {
ObjectAnimator ani = ObjectAnimator.ofFloat(view, "alpha", from, to);
ani.setInterpolator(new LinearInterpolator());
ani.setStartDelay(delayTime);
ani.setDuration(time);
return ani;
}
private ObjectAnimator rotation(View view, Long time, Long delayTime, Float values) {
ObjectAnimator ani = ObjectAnimator.ofFloat(view, "rotation", values);
ani.setInterpolator(new TimeInterpolator() {
@Override
public float getInterpolation(float input) {
return 0;
}
});
ani.setStartDelay(delayTime);
ani.setDuration(time);
return ani;
}
public void setOnClickListener(MyClickListener.MyClickCallBack onClickListener) {
this.clickCallBack = onClickListener;
myClickListener = new MyClickListener(clickCallBack);
}
public MyClickListener.MyClickCallBack getOnClickListener() {
return clickCallBack;
}
public void setAnnouncement(float x,float y){
Logger.t("ThumbsUpView").i("点击动画 ===> x y"+x +"/"+y);
if(drawableId == -1){
return;
}
final ImageView imageView = new ImageView(mContext);
// 设置展示的位置,需要在手指触摸的位置上方,即触摸点是心形的右下角的位置
LayoutParams params = new LayoutParams(value2, value2);
params.leftMargin = (int) x - value1_5_int;
params.topMargin = (int) y - value3;
// 设置图片资源
imageView.setImageDrawable(getResources().getDrawable(drawableId));
imageView.setLayoutParams(params);
// 把IV添加到父布局当中
addView(imageView);
// 设置控件的动画
AnimatorSet animatorSet = new AnimatorSet();
// 缩放动画,X轴2倍缩小至0 .9倍
animatorSet.play(scaleAni(imageView, "scaleX", 2f, 0.9f, value1, 0L))
// 缩放动画,Y轴2倍缩放至0.9倍
.with(scaleAni(imageView, "scaleY", 2f, 0.9f, value1, 0L))
// 旋转动画,随机旋转角
.with(rotation(imageView, 0L, 0L, num[new SecureRandom().nextInt(4)]))
// 渐变透明动画,透明度从0-1
.with(alphaAni(imageView, 0F, 1F, value1, 0L))
// 缩放动画,X轴0.9倍缩小至
.with(scaleAni(imageView, "scaleX", 0.9f, 1F, value0_5, value1_5))
// 缩放动画,Y轴0.9倍缩放至
.with(scaleAni(imageView, "scaleY", 0.9f, 1F, value0_5, value1_5))
// 位移动画,Y轴从0上移至600
.with(translationY(imageView, 0f, value6, value8, value4))
// 透明动画,从1-0
.with(alphaAni(imageView, 1F, 0F, value3L, value4))
// 缩放动画,X轴1至3倍
.with(scaleAni(imageView, "scaleX", 1F, 3F, value7, value4))
// 缩放动画,Y轴1至3倍
.with(scaleAni(imageView, "scaleY", 1F, 3F, value7, value4));
// 开始动画
animatorSet.start();
// 设置动画结束监听
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
// 当动画结束以后,需要把控件从父布局移除
removeViewInLayout(imageView);
}
});
}
}
... ...
package com.wd.common.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.airbnb.lottie.LottieAnimationView;
import com.scwang.smart.refresh.layout.api.RefreshFooter;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.constant.RefreshState;
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
import com.scwang.smart.refresh.layout.simple.SimpleComponent;
import com.wd.fastcoding.base.R;
import com.wd.foundation.wdkit.utils.AnimUtil;
/**
* 上推加载更多
*/
public class VideoLoadMoreFooter extends SimpleComponent implements RefreshFooter {
private LottieAnimationView animationView;
private TextView tvDesc;
private RefreshState newState;
public VideoLoadMoreFooter(Context context) {
this(context, null);
}
public VideoLoadMoreFooter(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VideoLoadMoreFooter(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
View view = View.inflate(context, R.layout.refresh_loading_more, this);
animationView = view.findViewById(R.id.animation_view);
tvDesc = view.findViewById(R.id.tv_desc);
}
public void setDescTextColor(int colorId) {
if (tvDesc != null) {
tvDesc.setTextColor(colorId);
}
}
/**
* 获取真实视图(必须返回,不能为null)一般就是返回当前自定义的view
*/
@NonNull
@Override
public View getView() {
return this;
}
/**
* 获取变换方式(必须指定一个:平移、拉伸、固定、全屏),Translate指平移,大多数都是平移
*/
@NonNull
@Override
public SpinnerStyle getSpinnerStyle() {
return SpinnerStyle.Translate;
}
/**
* 手指拖动下拉(会连续多次调用,用于实时控制动画关键帧)
*
* @param percent 下拉的百分比 值 = offset/headerHeight (0 - percent - (headerHeight+maxDragHeight) / headerHeight )
* @param offset 下拉的像素偏移量 0 - offset - (headerHeight+maxDragHeight)
* @param height Header的高度
* @param maxDragHeight 最大拖动高度
*/
@Override
public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) {
// Log.e("下拉刷新", "onMoving-> percent:" + percent + " offset:" + " height:" + height + " ");
if (newState != RefreshState.Refreshing) {
animationView.setProgress(percent % 1);
}
}
/**
* 一般可以理解为一下case中的三种状态,在达到相应状态时候开始改变
* 注意:这三种状态都是初始化的状态
*/
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
this.newState = newState;
// Log.e("上推刷新", "onStateChanged->newState:" + newState);
switch (newState) {
case None:
animationView.pauseAnimation();
setVisibility(View.GONE);
break;
case PullUpToLoad:
// animationView.playAnimation();
tvDesc.setText(R.string.up_load_more);
setVisibility(View.VISIBLE);
AnimUtil.showLocalLottieEffects(animationView,"video_loading_more.json", true);
break;
default:
break;
}
}
/**
* 开始动画(开始刷新或者开始加载动画)
*
* @param layout RefreshLayout
* @param height HeaderHeight or FooterHeight
* @param maxDragHeight 最大拖动高度
*/
@Override
public void onStartAnimator(@NonNull RefreshLayout layout, int height, int maxDragHeight) {
// Log.e("下拉刷新", "onStartAnimator");
}
}
... ...
package com.wd.common.widget.alertdialog;
import android.content.Context;
import android.content.DialogInterface;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
/**
* Time:2022/11/01
* Author:ypf
* Description:对话框视图控制器
*/
class AlertController {
private final AlertDialog mDialog;
private DialogViewHelper mViewHelper;
public AlertController(AlertDialog dialog) {
this.mDialog = dialog;
}
public void setViewHelper(DialogViewHelper viewHelper) {
this.mViewHelper = viewHelper;
}
/**
* 设置文本
*
* @param viewId 控件id
* @param text 文本内容
*/
public void setText(int viewId, CharSequence text) {
mViewHelper.setText(viewId, text);
}
/**
* 获取控件视图
*
* @param viewId 控件id
*/
public <T extends View> T getView(int viewId) {
return mViewHelper.getView(viewId);
}
/**
* 设置点击事件
*
* @param viewId 控件id
* @param listener 控件点击监听
*/
public void setOnClickListener(int viewId, IDialogInterface.IOnClickListener listener) {
mViewHelper.setOnClickListener(viewId, mDialog, listener);
}
/**
* 获取Dialog
*
* @return AlertDialog对话框
*/
public AlertDialog getDialog() {
return mDialog;
}
/**
* 获取Dialog的Window
*
* @return Window窗口
*/
public Window getWindow() {
return mDialog.getWindow();
}
public static class AlertParams {
public Context mContext;
public int mThemeResId;
// 点击空白是否能够取消 默认点击阴影可以取消
public boolean mCancelable = true;
// dialog Cancel监听
public DialogInterface.OnCancelListener mOnCancelListener;
// dialog Dismiss监听
public DialogInterface.OnDismissListener mOnDismissListener;
// dialog Key监听
public DialogInterface.OnKeyListener mOnKeyListener;
// 布局layout id
public int mViewLayoutResId;
// 存放文本的修改
public SparseArray<CharSequence> mTextArray = new SparseArray<>();
// 存放点击事件
public SparseArray<IDialogInterface.IOnClickListener> mClickArray = new SparseArray<>();
// 宽度
public int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
// 高度
public int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;
// 动画
public int mAnimations = 0;
// 位置
public int mGravity = Gravity.CENTER;
public AlertParams(Context context, int themeResId) {
this.mContext = context;
this.mThemeResId = themeResId;
}
/**
* 绑定和设置参数
*
* @param alertController 对话框控制器
*/
public void apply(AlertController alertController) {
// 完善这个地方 设置参数
// 1. 设置Dialog布局 DialogViewHelper
DialogViewHelper viewHelper = null;
if (mViewLayoutResId != 0) {
viewHelper = new DialogViewHelper(alertController.getWindow());
}
if (viewHelper == null) {
throw new IllegalArgumentException("请设置布局setContentView()");
}
// 给Dialog 设置布局
alertController.getDialog().setContentView(mViewLayoutResId);
// 设置 Controller的辅助类
alertController.setViewHelper(viewHelper);
// 2.设置文本
int textArraySize = mTextArray.size();
for (int i = 0; i < textArraySize; i++) {
alertController.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));
}
// 3.设置点击
int clickArraySize = mClickArray.size();
for (int i = 0; i < clickArraySize; i++) {
alertController.setOnClickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));
}
// 4.配置自定义的效果 全屏从底部弹出默认动画
Window window = alertController.getWindow();
// 设置位置
window.setGravity(mGravity);
// 设置动画
if (mAnimations != 0) {
window.setWindowAnimations(mAnimations);
}
// 设置宽高
WindowManager.LayoutParams params = window.getAttributes();
params.width = mWidth;
params.height = mHeight;
window.setAttributes(params);
}
}
}
... ...
package com.wd.common.widget.alertdialog;
import android.app.Dialog;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import com.wd.fastcoding.base.R;
/**
* Time:2022/11/01
* Author:ypf
* Description:自定义万能Dialog
*/
public class AlertDialog extends Dialog {
private final AlertController mAlertController;
public AlertDialog(Context context, int themeResId) {
super(context, themeResId);
mAlertController = new AlertController(this);
}
/**
* 设置文本
*
* @param viewId 控件id
* @param text 文本内容
*/
public void setText(int viewId, CharSequence text) {
mAlertController.setText(viewId, text);
}
/**
* 获取控件视图
*
* @param viewId 控件id
*/
public <T extends View> T getView(int viewId) {
return mAlertController.getView(viewId);
}
/**
* 设置点击事件
*
* @param viewId 控件id
* @param listener 控件点击监听
*/
public void setOnClickListener(int viewId, IDialogInterface.IOnClickListener listener) {
mAlertController.setOnClickListener(viewId, listener);
}
public static class Builder {
private final AlertController.AlertParams P;
public Builder(Context context) {
this(context, R.style.CenterDialogStyle);
}
public Builder(Context context, int themeResId) {
P = new AlertController.AlertParams(context, themeResId);
}
// 设置布局内容的layoutId
public Builder setContentView(int layoutId) {
P.mViewLayoutResId = layoutId;
return this;
}
// 设置文本
public Builder setText(int viewId, CharSequence text) {
P.mTextArray.put(viewId, text);
return this;
}
// 设置点击事件
public Builder setOnClickListener(int view, IDialogInterface.IOnClickListener listener) {
P.mClickArray.put(view, listener);
return this;
}
// 配置一些万能的参数
public Builder fullWidth() {
P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
return this;
}
/**
* 从底部弹出
*
* @param isAnimation 是否有动画
* @return Builder
*/
public Builder fromBottom(boolean isAnimation) {
if (isAnimation) {
P.mAnimations = R.style.dialog_from_bottom_anim;
}
P.mGravity = Gravity.BOTTOM;
return this;
}
/**
* 设置Dialog的宽高
*
* @param width 对话框宽
* @param height 对话框高
* @return Builder
*/
public Builder setWidthAndHeight(int width, int height) {
P.mWidth = width;
P.mHeight = height;
return this;
}
/**
* 添加默认动画
*
* @return Builder
*/
public Builder addDefaultAnimation() {
P.mAnimations = R.style.dialog_scale_anim;
return this;
}
/**
* 设置动画
*
* @param styleAnimation 动画
* @return Builder
*/
public Builder setAnimations(int styleAnimation) {
P.mAnimations = styleAnimation;
return this;
}
public Builder setCancelable(boolean cancelable) {
P.mCancelable = cancelable;
return this;
}
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
return this;
}
public Builder setOnDismissListener(OnDismissListener onDismissListener) {
P.mOnDismissListener = onDismissListener;
return this;
}
public Builder setOnKeyListener(OnKeyListener onKeyListener) {
P.mOnKeyListener = onKeyListener;
return this;
}
public AlertDialog create() {
// Context has already been wrapped with the appropriate theme.
final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);
P.apply(dialog.mAlertController);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
}
\ No newline at end of file
... ...
package com.wd.common.widget.alertdialog;
import android.util.SparseArray;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import java.lang.ref.WeakReference;
/**
* Time:2022/11/02
* Author:ypf
* Description:对话框视图辅助处理类
*/
class DialogViewHelper {
//上下文
private WeakReference<Window> mWindow;
// 防止霸气侧漏
private final SparseArray<WeakReference<View>> mViews;
public DialogViewHelper(Window window) {
mWindow = new WeakReference<>(window);
mViews = new SparseArray<>();
}
/**
* 设置文本
*
* @param viewId 控件id
* @param text 文本内容
*/
public void setText(int viewId, CharSequence text) {
// 每次都 findViewById 减少findViewById的次数
TextView tv = getView(viewId);
if (tv != null) {
tv.setText(text);
}
}
public <T extends View> T getView(int viewId) {
WeakReference<View> viewReference = mViews.get(viewId);
// 侧漏的问题 到时候到这个系统优化的时候再去介绍
View view = null;
if (viewReference != null) {
view = viewReference.get();
}
if (view == null) {
view = mWindow.get().findViewById(viewId);
if (view != null) {
mViews.put(viewId, new WeakReference<>(view));
}
}
return (T) view;
}
/**
* 设置点击事件
*
* @param viewId 控件id
* @param listener 控件点击监听
*/
public void setOnClickListener(int viewId, AlertDialog dialog, IDialogInterface.IOnClickListener listener) {
View view = getView(viewId);
if (view != null) {
view.setOnClickListener(v -> listener.onClick(dialog, v));
}
}
}
... ...
package com.wd.common.widget.alertdialog;
import android.view.View;
/**
* Time:2022/12/1
* Author:ypf
* Description:对话框相关接口
*/
public interface IDialogInterface {
/**
* 对话框中控件点击事件回调
*/
interface IOnClickListener {
void onClick(AlertDialog dialog, View view);
}
}
... ...
package com.wd.common.widget.expand;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.wd.fastcoding.base.R;
import com.wd.foundation.wdkit.utils.TextViewUtils;
import com.wd.foundation.wdkit.utils.UiUtils;
public class BoldExpandTextView extends RelativeLayout implements View.OnClickListener {
private static final String TAG = BoldExpandTextView.class.getSimpleName();
private static final String ELLIPSIS_STRING = new String(new char[]{'\u2026'});
private static final int STYLE_DEFAULT = 0;
private static final int STYLE_ICON = 1;
private static final int STYLE_TEXT = 2;
private Context mContext;
private View mRootView;
private TextView mTvContent;
private LinearLayout mLayoutExpandMore;
private TextView mTvExpand;
private int mMeasuredWidth;
/**
* 辅助TextView,保证末尾图标和文字与内容文字居中显示
*/
private TextView mTvExpandHelper;
private int mExpandIconResId;
private int mExpandIconTop;
private int mCollapseIconResId;
private Drawable mExpandIconDrawable;
private Drawable mCollapseIconDrawable;
private String mExpandMoreStr;
private String mCollapseLessStr;
private int mMaxLines = 2;
private int mContentTextSize;
private int mExpandTextSize;
private View newlineV;
/**
* 是否展开
*/
private boolean mIsExpand = false;
/**
* 原内容文本
*/
private String mOriginContentStr;
/**
* 缩略后展示的文本
*/
private CharSequence mEllipsizeStr;
/**
* 主文字颜色
*/
private int mContentTextColor;
/**
* 展开/收起文字颜色
*/
private int mExpandTextColor;
/**
* 样式,默认为图标+文字样式
*/
private int mExpandStyle = STYLE_DEFAULT;
/**
* 展开/收缩布局对应图标的宽度,默认布局是15dp
*/
private int mExpandIconWidth = 15;
/**
* 缩略文本展示时与展开/搜索布局的间距,默认是20px
*/
private int mSpaceMargin = 20;
/**
* 文本显示的lineSpacingExtra,对应于TextView的lineSpacingExtra属性
*/
private float mLineSpacingExtra = 0.0f;
/**
* 文本显示的lineSpacingMultiplier,对应于TextView的lineSpacingMultiplier属性
*/
private float mLineSpacingMultiplier = 1.0f;
private OnExpandStateChangeListener mOnExpandStateChangeListener;
private OnClickListener mListener;
private boolean isShrink;
/**
* 文字是否左右对齐
*/
private boolean isJustify;
/**
* 最后一行是否换行
*/
private boolean linefeed = false;
/**
* 监听器
*/
public interface OnExpandStateChangeListener {
/**
* 展开时回调
*/
void onExpand();
/**
* 收起时回调
*/
void onCollapse();
}
/**
* 监听器
*/
public interface OnClickListener {
/**
*
*/
void onClick();
}
public BoldExpandTextView(Context context) {
this(context, null);
}
public BoldExpandTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BoldExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initAttributeSet(context, attrs);
initView();
}
/**
* 初始化自定义属性
*
* @param context
* @param attrs
*/
private void initAttributeSet(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandTextView);
if (ta != null) {
mMaxLines = ta.getInt(R.styleable.ExpandTextView_maxLines, 2);
mExpandIconResId = ta.getResourceId(R.styleable.ExpandTextView_expandIconResId, 0);
mExpandIconTop = ta.getResourceId(R.styleable.ExpandTextView_expandIconTop, 1);
mCollapseIconResId = ta.getResourceId(R.styleable.ExpandTextView_collapseIconResId, 0);
mExpandMoreStr = ta.getString(R.styleable.ExpandTextView_expandMoreText);
mCollapseLessStr = ta.getString(R.styleable.ExpandTextView_collapseLessText);
mContentTextSize = ta.getDimensionPixelSize(R.styleable.ExpandTextView_contentTextSize, sp2px(context, 14));
mContentTextColor = ta.getColor(R.styleable.ExpandTextView_contentTextColor, 0);
mExpandTextSize = ta.getDimensionPixelSize(R.styleable.ExpandTextView_expandTextSize, sp2px(context, 14));
mExpandTextColor = ta.getColor(R.styleable.ExpandTextView_expandTextColor, 0);
mExpandStyle = ta.getInt(R.styleable.ExpandTextView_expandStyle, STYLE_DEFAULT);
mExpandIconWidth = ta.getDimensionPixelSize(R.styleable.ExpandTextView_expandIconWidth, dp2px(context, 15));
mSpaceMargin = ta.getDimensionPixelSize(R.styleable.ExpandTextView_spaceMargin, dp2px(context, 6));
mLineSpacingExtra = ta.getDimensionPixelSize(R.styleable.ExpandTextView_lineSpacingExtra, 0);
mLineSpacingMultiplier = ta.getFloat(R.styleable.ExpandTextView_lineSpacingMultiplier, 1.0f);
isShrink = ta.getBoolean(R.styleable.ExpandTextView_isShrink, true);
isJustify = ta.getBoolean(R.styleable.ExpandTextView_isJustify, false);
ta.recycle();
}
if (mMaxLines < 1) {
mMaxLines = 1;
}
}
/**
* 初始化View
*/
private void initView() {
// 由于左右对齐的自定义控件XQJustifyTextView,下拉刷新会闪烁。所以暂时活动列表不使用左右对齐的控件
mRootView = inflate(mContext, isJustify ? R.layout.layout_expand_justify : R.layout.layout_expand_bold, this);
mTvContent = findViewById(R.id.expand_content_tv);
mLayoutExpandMore = findViewById(R.id.expand_ll);
mTvExpand = findViewById(R.id.expand_tv);
newlineV = findViewById(R.id.newlineV);
mTvExpandHelper = findViewById(R.id.expand_helper_tv);
TextViewUtils.setText(mTvExpand, mExpandMoreStr);
mTvContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContentTextSize);
// 辅助TextView,与内容TextView大小相等,保证末尾图标和文字与内容文字居中显示
mTvExpandHelper.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContentTextSize);
mTvExpand.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpandTextSize);
mTvContent.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier);
mTvExpandHelper.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier);
mTvExpand.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier);
// 默认设置展开图标
setExpandMoreIcon(mExpandIconResId);
setCollapseLessIcon(mCollapseIconResId);
setContentTextColor(mContentTextColor);
setExpandTextColor(mExpandTextColor);
switch (mExpandStyle) {
case STYLE_ICON:
mTvExpand.setVisibility(GONE);
break;
case STYLE_TEXT:
mTvExpand.setVisibility(VISIBLE);
break;
default:
mTvExpand.setVisibility(VISIBLE);
break;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Log.d(TAG, "onMeasure,measureWidth = " + getMeasuredWidth());
if (mMeasuredWidth <= 0 && getMeasuredWidth() > 0) {
mMeasuredWidth = getMeasuredWidth();
measureEllipsizeText(mMeasuredWidth);
}
}
/**
* 设置文本内容
*
* @param contentStr
*/
public void setContent(String contentStr) {
setContent(contentStr, null, null);
}
/**
* 设置文本内容
*
* @param contentStr
*/
public void setContent(String contentStr, OnClickListener listener) {
setContent(contentStr, null, listener);
}
/**
* 获取文本内容
*/
public String getContent(){
return mOriginContentStr;
}
/**
* 获取文本 TextView
*/
public TextView getTvContent() {
return mTvContent;
}
/**
* 设置文本内容
*
* @param contentStr
* @param onExpandStateChangeListener 状态回调监听器
*/
public void setContent(String contentStr, final OnExpandStateChangeListener onExpandStateChangeListener,
OnClickListener listener) {
if (TextUtils.isEmpty(contentStr) || mRootView == null) {
return;
}
//接口中返回有零宽字符zwsp,替换掉防止截取位置不对
mOriginContentStr = contentStr.replaceAll("\u200B","").trim();
mOnExpandStateChangeListener = onExpandStateChangeListener;
mListener = listener;
// 此处需要先设置mTvContent的text属性,防止在列表中,由于没有获取到控件宽度mMeasuredWidth,先执行onMeasure方法测量时,导致文本只能显示一行的问题
// 提前设置好text,再执行onMeasure,则没有该问题
mTvContent.setMaxLines(mMaxLines);
TextViewUtils.setText(mTvContent, mOriginContentStr);
// 获取文字的宽度
if (mMeasuredWidth <= 0) {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 用完后立即移除监听,防止多次回调的问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
mMeasuredWidth = getMeasuredWidth();
measureEllipsizeText(mMeasuredWidth);
}
});
} else {
measureEllipsizeText(mMeasuredWidth);
}
}
/**
* 点击事件
*/
public void setmListener(OnClickListener mListener) {
this.mListener = mListener;
}
/**
* 处理文本分行
*
* @param lineWidth
*/
private void measureEllipsizeText(int lineWidth) {
if (TextUtils.isEmpty(mOriginContentStr)) {
return;
}
handleMeasureEllipsizeText(lineWidth);
}
/**
* 使用StaticLayout处理文本分行和布局
*
* @param lineWidth 文本(布局)宽度
*/
private void handleMeasureEllipsizeText(int lineWidth) {
TextPaint textPaint = mTvContent.getPaint();
StaticLayout staticLayout = new StaticLayout(mOriginContentStr, textPaint, lineWidth,
Layout.Alignment.ALIGN_NORMAL, mLineSpacingMultiplier, mLineSpacingExtra, false);
int lineCount = staticLayout.getLineCount();
if (lineCount < mMaxLines) {
// 不足最大行数,直接设置文本
// 少于最小展示行数,不再展示更多相关布局
mEllipsizeStr = mOriginContentStr;
mLayoutExpandMore.setVisibility(View.GONE);
mTvContent.setMaxLines(Integer.MAX_VALUE);
TextViewUtils.setText(mTvContent, mOriginContentStr);
} else {
// 超出最大行数
mRootView.setOnClickListener(this);
mLayoutExpandMore.setVisibility(View.VISIBLE);
// Step1:第mMinLineNum行的处理
handleEllipsizeString(staticLayout, lineWidth);
// Step2:最后一行的处理
handleLastLine(staticLayout, lineWidth);
if (mIsExpand) {
expand();
} else {
// 默认收缩
collapse();
}
}
}
/**
* 处理缩略的字符串
*
* @param staticLayout
* @param lineWidth
*/
private void handleEllipsizeString(StaticLayout staticLayout, int lineWidth) {
if (staticLayout == null) {
return;
}
TextPaint textPaint = mTvContent.getPaint();
// 获取到第mMinLineNum行的起始和结束位置
int startPos = staticLayout.getLineStart(mMaxLines - 1);
int endPos = staticLayout.getLineEnd(mMaxLines - 1);
// 修正,防止取子串越界
if (startPos < 0) {
startPos = 0;
}
if (endPos > mOriginContentStr.length()) {
endPos = mOriginContentStr.length();
}
if (startPos > endPos) {
startPos = endPos;
}
String lineContent = mOriginContentStr.substring(startPos, endPos);
float textLength = 0f;
if (lineContent != null) {
textLength = textPaint.measureText(lineContent);
}
String strEllipsizeMark = ELLIPSIS_STRING;
float strEllipsizeWidth = textPaint.measureText(strEllipsizeMark);
float expandTextViewWidth = getExpandTextViewReservedWidth();
// 展开控件需要预留的长度,预留宽度:"..." + 展开布局与文本间距 +图标长度 + 展开文本长度
float reservedWidth = mSpaceMargin + strEllipsizeWidth + expandTextViewWidth;
int correctEndPos = endPos;
if (reservedWidth + textLength > lineWidth) {
// 空间不够,需要按比例截取最后一行的字符串,以确保展示的最后一行文本不会与可展开布局重叠
float exceedSpace = reservedWidth + textLength - lineWidth;
if (textLength != 0) {
double exceedCount = ((exceedSpace / textLength) * 1.0d * (endPos - startPos));
correctEndPos = endPos - (int)Math.ceil(exceedCount);
}
} else {
// 文字宽度+展开布局的宽度 < 一行最大展示宽度: 展开布局左边跟随文字
ViewGroup.LayoutParams lp = mLayoutExpandMore.getLayoutParams();
if (lp instanceof FrameLayout.LayoutParams) {
FrameLayout.LayoutParams expandLayoutLp = (FrameLayout.LayoutParams) lp;
expandLayoutLp.rightMargin = (int) (lineWidth - (textLength + textPaint.measureText(strEllipsizeMark) + expandTextViewWidth));
mLayoutExpandMore.setLayoutParams(expandLayoutLp);
}
// mLayoutExpandMore.setX(lineWidth - (mSpaceMargin + textPaint.measureText(strEllipsizeMark)));
}
// java.lang.StringIndexOutOfBoundsException: String index out of range: -5
try {
String ellipsizeStr = mOriginContentStr.substring(0, correctEndPos);
mEllipsizeStr = removeEndLineBreak(ellipsizeStr) + strEllipsizeMark;
}catch (Exception e){
// e.printStackTrace();
}
}
/**
* 处理最后一行文本
*
* @param staticLayout
* @param lineWidth
*/
private void handleLastLine(StaticLayout staticLayout, int lineWidth) {
if (staticLayout == null) {
return;
}
int lineCount = staticLayout.getLineCount();
if (lineCount < 1) {
return;
}
int startPos = staticLayout.getLineStart(lineCount - 1);
int endPos = staticLayout.getLineEnd(lineCount - 1);
// 修正,防止取子串越界
if (startPos < 0) {
startPos = 0;
}
if (endPos > mOriginContentStr.length()) {
endPos = mOriginContentStr.length();
}
if (startPos > endPos) {
startPos = endPos;
}
String lastLineContent = mOriginContentStr.substring(startPos, endPos);
float textLength = 0f;
TextPaint textPaint = mTvContent.getPaint();
if (lastLineContent != null) {
textLength = textPaint.measureText(lastLineContent);
}
float reservedWidth = getExpandTextViewReservedWidth();
if (textLength + reservedWidth > lineWidth) {
// 文字宽度+展开布局的宽度 > 一行最大展示宽度
// 换行展示“收起”按钮及文字
mOriginContentStr += "\n";
linefeed = true;
}
}
/**
* 获取展开布局的展开收缩控件的预留宽度
*
* @return value = 图标长度 + 展开提示文本长度
*/
private float getExpandTextViewReservedWidth() {
int iconWidth = 0;
if (mExpandStyle == STYLE_DEFAULT || mExpandStyle == STYLE_ICON) {
// ll布局中的内容,图标iv的宽是15dp,参考布局
iconWidth = mExpandIconWidth;
}
float textWidth = 0f;
if (mExpandStyle == STYLE_DEFAULT || mExpandStyle == STYLE_TEXT) {
textWidth = mTvExpand.getPaint().measureText(mExpandMoreStr);
}
if (mExpandIconDrawable != null) {
textWidth += mExpandIconDrawable.getBounds().right - mExpandIconDrawable.getBounds().left;
}
// 展开控件需要预留的长度
return iconWidth + textWidth;
}
/**
* 清除行末换行符
*
* @param text
* @return
*/
private String removeEndLineBreak(CharSequence text) {
if (text == null) {
return null;
}
String str = text.toString();
if (str.endsWith("\n")) {
str = str.substring(0, str.length() - 1);
}
return str;
}
/**
* 设置内容文字颜色
*/
public void setContentTextColor(int colorId) {
if (colorId != 0) {
mContentTextColor = colorId;
mTvContent.setTextColor(colorId);
}
}
/**
* 设置更多/收起文字颜色
*/
public void setExpandTextColor(int colorId) {
if (colorId != 0) {
mExpandTextColor = colorId;
mTvExpand.setTextColor(colorId);
}
}
/**
* 设置展开更多图标
*
* @param resId
*/
public void setExpandMoreIcon(int resId) {
if (resId != 0) {
mExpandIconResId = resId;
mExpandIconDrawable = getResources().getDrawable(resId, null);
// 设置了Drawable对象在Canvas上的绘制区域,左上角坐标为(0, 2),右下角坐标为(12, 14)
mExpandIconDrawable.setBounds(0, UiUtils.dp2px(mExpandIconTop), UiUtils.dp2px(14-mExpandIconTop), UiUtils.dp2px(14));
// 当前处于收缩状态时,立即更新图标
if (!mIsExpand) {
mTvExpand.setCompoundDrawables(null, null, mExpandIconDrawable, null);
mTvExpand.setCompoundDrawablePadding(UiUtils.dp2px(1));
// mIconExpand.setImageResource(resId);
}
}
}
/**
* 设置收缩图标
*
* @param resId
*/
public void setCollapseLessIcon(int resId) {
if (resId != 0) {
mCollapseIconResId = resId;
mCollapseIconDrawable = getResources().getDrawable(resId, null);
mCollapseIconDrawable.setBounds(0, 0, UiUtils.dp2px(6), UiUtils.dp2px(10));
// 当前处于展开状态时,立即更新图标
if (mIsExpand) {
mTvExpand.setCompoundDrawables(null, null, mCollapseIconDrawable, null);
// mTvExpand.setCompoundDrawablePadding(UiUtils.dp2px(4));
// mIconExpand.setImageResource(resId);
}
}
}
/**
* 展开
*/
public void expand() {
setIsExpand(true);
mTvContent.setMaxLines(Integer.MAX_VALUE);
TextViewUtils.setText(mTvContent, mOriginContentStr);
if (newlineV != null) {
if (linefeed) {
newlineV.setVisibility(INVISIBLE);
} else {
newlineV.setVisibility(GONE);
}
}
TextViewUtils.setText(mTvExpand, mCollapseLessStr);
if (mCollapseIconDrawable != null) {
mTvExpand.setCompoundDrawables(null, null, mCollapseIconDrawable, null);
// mIconExpand.setImageResource(mCollapseIconResId);
}
}
/**
* 收起
*/
public void collapse() {
setIsExpand(false);
if (newlineV != null) {
newlineV.setVisibility(GONE);
}
mTvContent.setMaxLines(Integer.MAX_VALUE);
TextViewUtils.setText(mTvContent, mEllipsizeStr);
TextViewUtils.setText(mTvExpand, mExpandMoreStr);
if (mExpandIconDrawable != null) {
mTvExpand.setCompoundDrawables(null, null, mExpandIconDrawable, null);
// mIconExpand.setImageResource(mExpandIconResId);
}
}
public int getLineCount() {
return mTvContent == null ? 0 : mTvContent.getLineCount();
}
public void setShrinkLines(int shrinkLines) {
mMaxLines = shrinkLines;
}
public void setIsExpand(boolean isExpand) {
mIsExpand = isExpand;
}
public boolean isExpand() {
return mIsExpand;
}
public void setShrink(boolean isShrink){
this.isShrink = isShrink;
}
/**
* 转换dp为px
*
* @param context
* @param dipValue
* @return
*/
private int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 转换sp为px
*
* @param context
* @param spValue
* @return
*/
public int sp2px(Context context, float spValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onClick();
}
if (isShrink) {
if (!mIsExpand) {
// 之前是收缩状态,点击后展开
expand();
if (mOnExpandStateChangeListener != null) {
mOnExpandStateChangeListener.onExpand();
}
} else {
// 之前是展开状态,点击后收缩
collapse();
if (mOnExpandStateChangeListener != null) {
mOnExpandStateChangeListener.onCollapse();
}
}
}
}
}
... ...
package com.wd.common.widget.expand;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.wd.fastcoding.base.R;
import com.wd.foundation.wdkit.utils.TextViewUtils;
import com.wd.foundation.wdkit.utils.UiUtils;
public class ExpandTextView extends RelativeLayout implements View.OnClickListener {
private static final String TAG = ExpandTextView.class.getSimpleName();
private static final String ELLIPSIS_STRING = new String(new char[]{'\u2026'});
private static final int STYLE_DEFAULT = 0;
private static final int STYLE_ICON = 1;
private static final int STYLE_TEXT = 2;
private Context mContext;
private View mRootView;
private TextView mTvContent;
private LinearLayout mLayoutExpandMore;
private TextView mTvExpand;
private int mMeasuredWidth;
/**
* 辅助TextView,保证末尾图标和文字与内容文字居中显示
*/
private TextView mTvExpandHelper;
private int mExpandIconResId;
private int mExpandIconTop;
private int mCollapseIconResId;
private Drawable mExpandIconDrawable;
private Drawable mCollapseIconDrawable;
private String mExpandMoreStr;
private String mCollapseLessStr;
private int mMaxLines = 2;
private int mContentTextSize;
private int mExpandTextSize;
private View newlineV;
/**
* 是否展开
*/
private boolean mIsExpand = false;
/**
* 原内容文本
*/
private String mOriginContentStr;
/**
* 缩略后展示的文本
*/
private CharSequence mEllipsizeStr;
/**
* 主文字颜色
*/
private int mContentTextColor;
/**
* 展开/收起文字颜色
*/
private int mExpandTextColor;
/**
* 样式,默认为图标+文字样式
*/
private int mExpandStyle = STYLE_DEFAULT;
/**
* 展开/收缩布局对应图标的宽度,默认布局是15dp
*/
private int mExpandIconWidth = 15;
/**
* 缩略文本展示时与展开/搜索布局的间距,默认是20px
*/
private int mSpaceMargin = 20;
/**
* 文本显示的lineSpacingExtra,对应于TextView的lineSpacingExtra属性
*/
private float mLineSpacingExtra = 0.0f;
/**
* 文本显示的lineSpacingMultiplier,对应于TextView的lineSpacingMultiplier属性
*/
private float mLineSpacingMultiplier = 1.0f;
private OnExpandStateChangeListener mOnExpandStateChangeListener;
private OnClickListener mListener;
private boolean isShrink;
/**
* 文字是否左右对齐
*/
private boolean isJustify;
/**
* 最后一行是否换行
*/
private boolean linefeed = false;
/**
* 监听器
*/
public interface OnExpandStateChangeListener {
/**
* 展开时回调
*/
void onExpand();
/**
* 收起时回调
*/
void onCollapse();
}
/**
* 监听器
*/
public interface OnClickListener {
/**
*
*/
void onClick();
}
public ExpandTextView(Context context) {
this(context, null);
}
public ExpandTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initAttributeSet(context, attrs);
initView();
}
/**
* 初始化自定义属性
*
* @param context
* @param attrs
*/
private void initAttributeSet(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ExpandTextView);
if (ta != null) {
mMaxLines = ta.getInt(R.styleable.ExpandTextView_maxLines, 2);
mExpandIconResId = ta.getResourceId(R.styleable.ExpandTextView_expandIconResId, 0);
mExpandIconTop = ta.getResourceId(R.styleable.ExpandTextView_expandIconTop, 1);
mCollapseIconResId = ta.getResourceId(R.styleable.ExpandTextView_collapseIconResId, 0);
mExpandMoreStr = ta.getString(R.styleable.ExpandTextView_expandMoreText);
mCollapseLessStr = ta.getString(R.styleable.ExpandTextView_collapseLessText);
mContentTextSize = ta.getDimensionPixelSize(R.styleable.ExpandTextView_contentTextSize, sp2px(context, 14));
mContentTextColor = ta.getColor(R.styleable.ExpandTextView_contentTextColor, 0);
mExpandTextSize = ta.getDimensionPixelSize(R.styleable.ExpandTextView_expandTextSize, sp2px(context, 14));
mExpandTextColor = ta.getColor(R.styleable.ExpandTextView_expandTextColor, 0);
mExpandStyle = ta.getInt(R.styleable.ExpandTextView_expandStyle, STYLE_DEFAULT);
mExpandIconWidth = ta.getDimensionPixelSize(R.styleable.ExpandTextView_expandIconWidth, dp2px(context, 15));
mSpaceMargin = ta.getDimensionPixelSize(R.styleable.ExpandTextView_spaceMargin, dp2px(context, 6));
mLineSpacingExtra = ta.getDimensionPixelSize(R.styleable.ExpandTextView_lineSpacingExtra, 0);
mLineSpacingMultiplier = ta.getFloat(R.styleable.ExpandTextView_lineSpacingMultiplier, 1.0f);
isShrink = ta.getBoolean(R.styleable.ExpandTextView_isShrink, true);
isJustify = ta.getBoolean(R.styleable.ExpandTextView_isJustify, false);
ta.recycle();
}
if (mMaxLines < 1) {
mMaxLines = 1;
}
}
/**
* 初始化View
*/
private void initView() {
// 由于左右对齐的自定义控件XQJustifyTextView,下拉刷新会闪烁。所以暂时活动列表不使用左右对齐的控件
mRootView = inflate(mContext, isJustify ? R.layout.layout_expand_justify : R.layout.layout_expand, this);
mTvContent = findViewById(R.id.expand_content_tv);
mLayoutExpandMore = findViewById(R.id.expand_ll);
mTvExpand = findViewById(R.id.expand_tv);
newlineV = findViewById(R.id.newlineV);
mTvExpandHelper = findViewById(R.id.expand_helper_tv);
TextViewUtils.setText(mTvExpand, mExpandMoreStr);
mTvContent.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContentTextSize);
// 辅助TextView,与内容TextView大小相等,保证末尾图标和文字与内容文字居中显示
mTvExpandHelper.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContentTextSize);
mTvExpand.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpandTextSize);
mTvContent.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier);
mTvExpandHelper.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier);
mTvExpand.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier);
// 默认设置展开图标
setExpandMoreIcon(mExpandIconResId);
setCollapseLessIcon(mCollapseIconResId);
setContentTextColor(mContentTextColor);
setExpandTextColor(mExpandTextColor);
switch (mExpandStyle) {
case STYLE_ICON:
mTvExpand.setVisibility(GONE);
break;
case STYLE_TEXT:
mTvExpand.setVisibility(VISIBLE);
break;
default:
mTvExpand.setVisibility(VISIBLE);
break;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// Log.d(TAG, "onMeasure,measureWidth = " + getMeasuredWidth());
if (mMeasuredWidth <= 0 && getMeasuredWidth() > 0) {
mMeasuredWidth = getMeasuredWidth();
measureEllipsizeText(mMeasuredWidth);
}
}
/**
* 设置文本内容
*
* @param contentStr
*/
public void setContent(String contentStr) {
setContent(contentStr, null, null);
}
/**
* 设置文本内容
*
* @param contentStr
*/
public void setContent(String contentStr, OnClickListener listener) {
setContent(contentStr, null, listener);
}
/**
* 获取文本内容
*/
public String getContent(){
return mOriginContentStr;
}
/**
* 获取文本 TextView
*/
public TextView getTvContent() {
return mTvContent;
}
/**
* 设置文本内容
*
* @param contentStr
* @param onExpandStateChangeListener 状态回调监听器
*/
public void setContent(String contentStr, final OnExpandStateChangeListener onExpandStateChangeListener,
OnClickListener listener) {
if (TextUtils.isEmpty(contentStr) || mRootView == null) {
return;
}
mOriginContentStr = contentStr;
mOnExpandStateChangeListener = onExpandStateChangeListener;
mListener = listener;
// 此处需要先设置mTvContent的text属性,防止在列表中,由于没有获取到控件宽度mMeasuredWidth,先执行onMeasure方法测量时,导致文本只能显示一行的问题
// 提前设置好text,再执行onMeasure,则没有该问题
mTvContent.setMaxLines(mMaxLines);
TextViewUtils.setText(mTvContent, mOriginContentStr);
// 获取文字的宽度
if (mMeasuredWidth <= 0) {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 用完后立即移除监听,防止多次回调的问题
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
mMeasuredWidth = getMeasuredWidth();
measureEllipsizeText(mMeasuredWidth);
}
});
} else {
measureEllipsizeText(mMeasuredWidth);
}
}
/**
* 点击事件
*/
public void setmListener(OnClickListener mListener) {
this.mListener = mListener;
}
/**
* 处理文本分行
*
* @param lineWidth
*/
private void measureEllipsizeText(int lineWidth) {
if (TextUtils.isEmpty(mOriginContentStr)) {
return;
}
handleMeasureEllipsizeText(lineWidth);
}
/**
* 使用StaticLayout处理文本分行和布局
*
* @param lineWidth 文本(布局)宽度
*/
private void handleMeasureEllipsizeText(int lineWidth) {
TextPaint textPaint = mTvContent.getPaint();
StaticLayout staticLayout = new StaticLayout(mOriginContentStr, textPaint, lineWidth,
Layout.Alignment.ALIGN_NORMAL, mLineSpacingMultiplier, mLineSpacingExtra, false);
int lineCount = staticLayout.getLineCount();
if (lineCount <= mMaxLines) {
// 不足最大行数,直接设置文本
// 少于最小展示行数,不再展示更多相关布局
mEllipsizeStr = mOriginContentStr;
mLayoutExpandMore.setVisibility(View.GONE);
mTvContent.setMaxLines(Integer.MAX_VALUE);
TextViewUtils.setText(mTvContent, mOriginContentStr);
} else {
// 超出最大行数
mRootView.setOnClickListener(this);
mLayoutExpandMore.setVisibility(View.VISIBLE);
// Step1:第mMinLineNum行的处理
handleEllipsizeString(staticLayout, lineWidth);
// Step2:最后一行的处理
handleLastLine(staticLayout, lineWidth);
if (mIsExpand) {
expand();
} else {
// 默认收缩
collapse();
}
}
}
/**
* 处理缩略的字符串
*
* @param staticLayout
* @param lineWidth
*/
private void handleEllipsizeString(StaticLayout staticLayout, int lineWidth) {
if (staticLayout == null) {
return;
}
TextPaint textPaint = mTvContent.getPaint();
// 获取到第mMinLineNum行的起始和结束位置
int startPos = staticLayout.getLineStart(mMaxLines - 1);
int endPos = staticLayout.getLineEnd(mMaxLines - 1);
// 修正,防止取子串越界
if (startPos < 0) {
startPos = 0;
}
if (endPos > mOriginContentStr.length()) {
endPos = mOriginContentStr.length();
}
if (startPos > endPos) {
startPos = endPos;
}
String lineContent = mOriginContentStr.substring(startPos, endPos);
float textLength = 0f;
if (lineContent != null) {
textLength = textPaint.measureText(lineContent);
}
String strEllipsizeMark = ELLIPSIS_STRING;
float strEllipsizeWidth = textPaint.measureText(strEllipsizeMark);
float expandTextViewWidth = getExpandTextViewReservedWidth();
// 展开控件需要预留的长度,预留宽度:"..." + 展开布局与文本间距 +图标长度 + 展开文本长度
float reservedWidth = mSpaceMargin + strEllipsizeWidth + expandTextViewWidth;
int correctEndPos = endPos;
if (reservedWidth + textLength > lineWidth) {
// 空间不够,需要按比例截取最后一行的字符串,以确保展示的最后一行文本不会与可展开布局重叠
float exceedSpace = reservedWidth + textLength - lineWidth;
if (textLength != 0) {
double exceedCount = ((exceedSpace / textLength) * 1.0d * (endPos - startPos));
correctEndPos = endPos - (int)Math.ceil(exceedCount);
}
} else {
// 文字宽度+展开布局的宽度 < 一行最大展示宽度: 展开布局左边跟随文字
ViewGroup.LayoutParams lp = mLayoutExpandMore.getLayoutParams();
if (lp instanceof FrameLayout.LayoutParams) {
FrameLayout.LayoutParams expandLayoutLp = (FrameLayout.LayoutParams) lp;
expandLayoutLp.width = (int) (lineWidth - (textLength + mSpaceMargin + textPaint.measureText(strEllipsizeMark)));
mLayoutExpandMore.setLayoutParams(expandLayoutLp);
}
// mLayoutExpandMore.setX(lineWidth - (mSpaceMargin + textPaint.measureText(strEllipsizeMark)));
}
// java.lang.StringIndexOutOfBoundsException: String index out of range: -5
try {
String ellipsizeStr = mOriginContentStr.substring(0, correctEndPos);
mEllipsizeStr = removeEndLineBreak(ellipsizeStr) + strEllipsizeMark;
}catch (Exception e){
// e.printStackTrace();
}
}
/**
* 处理最后一行文本
*
* @param staticLayout
* @param lineWidth
*/
private void handleLastLine(StaticLayout staticLayout, int lineWidth) {
if (staticLayout == null) {
return;
}
int lineCount = staticLayout.getLineCount();
if (lineCount < 1) {
return;
}
int startPos = staticLayout.getLineStart(lineCount - 1);
int endPos = staticLayout.getLineEnd(lineCount - 1);
// 修正,防止取子串越界
if (startPos < 0) {
startPos = 0;
}
if (endPos > mOriginContentStr.length()) {
endPos = mOriginContentStr.length();
}
if (startPos > endPos) {
startPos = endPos;
}
String lastLineContent = mOriginContentStr.substring(startPos, endPos);
float textLength = 0f;
TextPaint textPaint = mTvContent.getPaint();
if (lastLineContent != null) {
textLength = textPaint.measureText(lastLineContent);
}
float reservedWidth = getExpandTextViewReservedWidth();
if (textLength + reservedWidth > lineWidth) {
// 文字宽度+展开布局的宽度 > 一行最大展示宽度
// 换行展示“收起”按钮及文字
mOriginContentStr += "\n";
linefeed = true;
}
}
/**
* 获取展开布局的展开收缩控件的预留宽度
*
* @return value = 图标长度 + 展开提示文本长度
*/
private float getExpandTextViewReservedWidth() {
int iconWidth = 0;
if (mExpandStyle == STYLE_DEFAULT || mExpandStyle == STYLE_ICON) {
// ll布局中的内容,图标iv的宽是15dp,参考布局
iconWidth = mExpandIconWidth;
}
float textWidth = 0f;
if (mExpandStyle == STYLE_DEFAULT || mExpandStyle == STYLE_TEXT) {
textWidth = mTvExpand.getPaint().measureText(mExpandMoreStr);
}
if (mExpandIconDrawable != null) {
textWidth += mExpandIconDrawable.getBounds().right - mExpandIconDrawable.getBounds().left;
}
// 展开控件需要预留的长度
return iconWidth + textWidth;
}
/**
* 清除行末换行符
*
* @param text
* @return
*/
private String removeEndLineBreak(CharSequence text) {
if (text == null) {
return null;
}
String str = text.toString();
if (str.endsWith("\n")) {
str = str.substring(0, str.length() - 1);
}
return str;
}
/**
* 设置内容文字颜色
*/
public void setContentTextColor(int colorId) {
if (colorId != 0) {
mContentTextColor = colorId;
mTvContent.setTextColor(colorId);
}
}
/**
* 设置更多/收起文字颜色
*/
public void setExpandTextColor(int colorId) {
if (colorId != 0) {
mExpandTextColor = colorId;
mTvExpand.setTextColor(colorId);
}
}
/**
* 设置展开更多图标
*
* @param resId
*/
public void setExpandMoreIcon(int resId) {
if (resId != 0) {
mExpandIconResId = resId;
mExpandIconDrawable = getResources().getDrawable(resId, null);
// 设置了Drawable对象在Canvas上的绘制区域,左上角坐标为(0, 2),右下角坐标为(12, 14)
mExpandIconDrawable.setBounds(0, UiUtils.dp2px(mExpandIconTop), UiUtils.dp2px(14-mExpandIconTop), UiUtils.dp2px(14));
// 当前处于收缩状态时,立即更新图标
if (!mIsExpand) {
mTvExpand.setCompoundDrawables(null, null, mExpandIconDrawable, null);
mTvExpand.setCompoundDrawablePadding(UiUtils.dp2px(1));
// mIconExpand.setImageResource(resId);
}
}
}
/**
* 设置收缩图标
*
* @param resId
*/
public void setCollapseLessIcon(int resId) {
if (resId != 0) {
mCollapseIconResId = resId;
mCollapseIconDrawable = getResources().getDrawable(resId, null);
mCollapseIconDrawable.setBounds(0, 0, UiUtils.dp2px(6), UiUtils.dp2px(10));
// 当前处于展开状态时,立即更新图标
if (mIsExpand) {
mTvExpand.setCompoundDrawables(null, null, mCollapseIconDrawable, null);
// mTvExpand.setCompoundDrawablePadding(UiUtils.dp2px(4));
// mIconExpand.setImageResource(resId);
}
}
}
/**
* 展开
*/
public void expand() {
setIsExpand(true);
mTvContent.setMaxLines(Integer.MAX_VALUE);
TextViewUtils.setText(mTvContent, mOriginContentStr);
if (newlineV != null) {
if (linefeed) {
newlineV.setVisibility(INVISIBLE);
} else {
newlineV.setVisibility(GONE);
}
}
TextViewUtils.setText(mTvExpand, mCollapseLessStr);
if (mCollapseIconDrawable != null) {
mTvExpand.setCompoundDrawables(null, null, mCollapseIconDrawable, null);
// mIconExpand.setImageResource(mCollapseIconResId);
}
}
/**
* 收起
*/
public void collapse() {
setIsExpand(false);
if (newlineV != null) {
newlineV.setVisibility(GONE);
}
mTvContent.setMaxLines(Integer.MAX_VALUE);
TextViewUtils.setText(mTvContent, mEllipsizeStr);
TextViewUtils.setText(mTvExpand, mExpandMoreStr);
if (mExpandIconDrawable != null) {
mTvExpand.setCompoundDrawables(null, null, mExpandIconDrawable, null);
// mIconExpand.setImageResource(mExpandIconResId);
}
}
public int getLineCount() {
return mTvContent == null ? 0 : mTvContent.getLineCount();
}
public void setShrinkLines(int shrinkLines) {
mMaxLines = shrinkLines;
}
public void setIsExpand(boolean isExpand) {
mIsExpand = isExpand;
}
public boolean isExpand() {
return mIsExpand;
}
public void setShrink(boolean isShrink){
this.isShrink = isShrink;
}
/**
* 转换dp为px
*
* @param context
* @param dipValue
* @return
*/
private int dp2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 转换sp为px
*
* @param context
* @param spValue
* @return
*/
public int sp2px(Context context, float spValue) {
float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
@Override
public void onClick(View v) {
if (mListener != null) {
mListener.onClick();
}
if (isShrink) {
if (!mIsExpand) {
// 之前是收缩状态,点击后展开
expand();
if (mOnExpandStateChangeListener != null) {
mOnExpandStateChangeListener.onExpand();
}
} else {
// 之前是展开状态,点击后收缩
collapse();
if (mOnExpandStateChangeListener != null) {
mOnExpandStateChangeListener.onCollapse();
}
}
}
}
}
... ...
package com.wd.common.widget.expand;
import android.content.Context;
import android.graphics.Canvas;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import androidx.appcompat.widget.AppCompatTextView;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 左右对齐textview
*
* @author libo
* @version [V1.0.0, 2022/10/10]
* @since V1.0.0
*/
public class XQJustifyTextView extends AppCompatTextView {
private static final String TAG = "XQJustifyTextView";
/**
* 绘制文字的起始Y坐标
*/
private float mLineY;
/**
* 文字的宽度
*/
private int mViewWidth;
/**
* 段落间距
*/
// private int paragraphSpacing = dipToPx(getContext(), 15);
private int paragraphSpacing = 0;
/**
* 当前所有行数的集合
*/
private ArrayList<List<List<String>>> mParagraphLineList;
/**
* 当前所有的行数
*/
private int mLineCount;
/**
* 每一段单词的内容集合
*/
private ArrayList<List<String>> mParagraphWordList;
/**
* 空格字符
*/
private static final String BLANK = " ";
/**
* 英语单词元音字母
*/
private String[] vowel = {"a", "e", "i", "o", "u"};
/**
* 英语单词元音字母集合
*/
private List<String> vowels = Arrays.asList(vowel);
public XQJustifyTextView(Context context) {
this(context, null);
}
public XQJustifyTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public XQJustifyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mParagraphLineList = null;
mParagraphWordList = null;
mLineY = 0;
mViewWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
getParagraphList();
if (mParagraphLineList == null || mParagraphWordList == null || mParagraphWordList.size() == 0) {
return;
}
for (List<String> frontList : mParagraphWordList) {
mParagraphLineList.add(getLineList(frontList));
}
setMeasuredDimension(mViewWidth, (mParagraphLineList.size() - 1) * paragraphSpacing + mLineCount * getLineHeight());
}
@Override
protected void onDraw(Canvas canvas) {
TextPaint paint = getPaint();
paint.setColor(getCurrentTextColor());
paint.drawableState = getDrawableState();
mLineY = 0;
float textSize = getTextSize();
mLineY += textSize + getPaddingTop();
// Layout layout = getLayout();
// if (layout == null) {
// return;
// }
adjust(canvas, paint);
}
/**
* @param frontList
* @return 计算每一段绘制的内容
*/
private synchronized List<List<String>> getLineList(List<String> frontList) {
Log.i(TAG, "getLineList ");
StringBuilder sb = new StringBuilder();
List<List<String>> lineLists = new ArrayList<>();
List<String> lineList = new ArrayList<>();
float width = 0;
String temp = "";
String front = "";
for (int i = 0; i < frontList.size(); i++) {
front = frontList.get(i);
if (!TextUtils.isEmpty(temp)) {
sb.append(temp);
lineList.add(temp);
if (!isCN(temp)) {
sb.append(BLANK);
}
temp = "";
}
if (isCN(front)) {
sb.append(front);
} else {
if ((i + 1) < frontList.size()) {
String nextFront = frontList.get(i + 1);
if (isCN(nextFront)) {
sb.append(front);
} else {
sb.append(front).append(BLANK);
}
} else {
sb.append(front);
}
}
lineList.add(front);
width = StaticLayout.getDesiredWidth(sb.toString(), getPaint());
if (width > mViewWidth) {
// 先判断最后一个单词是否是英文的,是的话则切割,否则的话就移除最后一个
int lastIndex = lineList.size() - 1;
String lastWord = lineList.get(lastIndex);
String lastTemp = "";
lineList.remove(lastIndex);
if (isCN(lastWord)) {
addLines(lineLists, lineList);
lastTemp = lastWord;
} else {
// 否则的话则截取字符串
String substring = sb.substring(0, sb.length() - lastWord.length() - 1);
sb.delete(0, sb.toString().length());
sb.append(substring).append(BLANK);
String tempLastWord = "";
int length = lastWord.length();
if (length <= 3) {
addLines(lineLists, lineList);
lastTemp = lastWord;
} else {
int cutoffIndex = 0;
for (int j = 0; j < length; j++) {
tempLastWord = String.valueOf(lastWord.charAt(j));
sb.append(tempLastWord);
if (vowels.contains(tempLastWord)) {
// 根据元音字母来进行截断
if (j + 1 < length) {
String nextTempLastWord = String.valueOf(lastWord.charAt(j + 1));
sb.append(nextTempLastWord);
width = StaticLayout.getDesiredWidth(sb.toString(), getPaint());
cutoffIndex = j;
if (width > mViewWidth) {
if (j > 2 && j <= length - 2) {
// 单词截断后,前面的字符小于2个时,则不进行截断
String lastFinalWord = lastWord.substring(0, cutoffIndex + 2) + "-";
lineList.add(lastFinalWord);
addLines(lineLists, lineList);
lastTemp = lastWord.substring(cutoffIndex + 2, length);
} else {
addLines(lineLists, lineList);
lastTemp = lastWord;
}
break;
}
} else {
addLines(lineLists, lineList);
lastTemp = lastWord;
break;
}
}
width = StaticLayout.getDesiredWidth(sb.toString(), getPaint());
// 找不到元音,则走默认的逻辑
if (width > mViewWidth) {
if (j > 2 && j <= length - 2) {
// 单词截断后,前面的字符小于2个时,则不进行截断
String lastFinalWord = lastWord.substring(0, j) + "-";
lineList.add(lastFinalWord);
addLines(lineLists, lineList);
lastTemp = lastWord.substring(j, length);
} else {
addLines(lineLists, lineList);
lastTemp = lastWord;
}
break;
}
}
}
}
sb.delete(0, sb.toString().length());
temp = lastTemp;
}
if (lineList.size() > 0 && i == frontList.size() - 1) {
addLines(lineLists, lineList);
}
}
if (!TextUtils.isEmpty(temp)) {
lineList.add(temp);
addLines(lineLists, lineList);
}
mLineCount += lineLists.size();
return lineLists;
}
/**
* 添加一行到单词内容
*
* @param lineLists 总单词集合
* @param lineList 当前要添加的集合
*/
private void addLines(List<List<String>> lineLists, List<String> lineList) {
if (lineLists == null || lineList == null) {
return;
}
List<String> tempLines = new ArrayList<>(lineList);
lineLists.add(tempLines);
lineList.clear();
}
/**
* 获取段落
*/
private void getParagraphList() {
String text = getText().toString().replaceAll(" ", "").replaceAll(" ", "").replaceAll("\\r", "").trim();
mLineCount = 0;
String[] items = text.split("\\n");
mParagraphLineList = new ArrayList<>();
mParagraphWordList = new ArrayList<>();
for (String item : items) {
if (item.length() != 0) {
mParagraphWordList.add(getWordList(item));
}
}
}
/**
* 截取每一段内容的每一个单词
*
* @param text
* @return
*/
private synchronized List<String> getWordList(String text) {
if (TextUtils.isEmpty(text)) {
return new ArrayList<>();
}
Log.i(TAG, "getWordList ");
List<String> frontList = new ArrayList<>();
StringBuilder str = new StringBuilder();
for (int i = 0; i < text.length(); i++) {
String charAt = String.valueOf(text.charAt(i));
if (!Objects.equals(charAt, BLANK)) {
if (checkIsSymbol(charAt)) {
boolean isEmptyStr = str.length() == 0;
str.append(charAt);
if (!isEmptyStr) {
// 中英文都需要将字符串添加到这里;
frontList.add(str.toString());
str.delete(0, str.length());
}
} else {
if (isCN(str.toString())) {
frontList.add(str.toString());
str.delete(0, str.length());
}
str.append(charAt);
}
} else {
if (!TextUtils.isEmpty(str.toString())) {
frontList.add(str.toString().replaceAll(BLANK, ""));
str.delete(0, str.length());
}
}
}
if (str.length() != 0) {
frontList.add(str.toString());
str.delete(0, str.length());
}
return frontList;
}
/**
* 中英文排版效果
*
* @param canvas
*/
private synchronized void adjust(Canvas canvas, TextPaint paint) {
if (mParagraphWordList == null) {
return;
}
int size = mParagraphWordList.size();
for (int j = 0; j < size; j++) { // 遍历每一段
List<List<String>> lineList = mParagraphLineList.get(j);
for (int i = 0; i < lineList.size(); i++) { // 遍历每一段的每一行
List<String> lineWords = lineList.get(i);
if (i == lineList.size() - 1) {
drawScaledEndText(canvas, lineWords, paint);
} else {
drawScaledText(canvas, lineWords, paint);
}
mLineY += getLineHeight();
}
mLineY += paragraphSpacing;
}
}
/**
* 绘制最后一行文字
*
* @param canvas
* @param lineWords
* @param paint
*/
private void drawScaledEndText(Canvas canvas, List<String> lineWords, TextPaint paint) {
if (canvas == null || lineWords == null || paint == null) {
return;
}
StringBuilder sb = new StringBuilder();
for (String aSplit : lineWords) {
if (isCN(aSplit)) {
sb.append(aSplit);
} else {
sb.append(aSplit).append(BLANK);
}
}
/**
* 最后一行适配布局方向
* android:gravity=""
* android:textAlignment=""
* 默认不设置则为左边
* 如果同时设置gravity和textAlignment属性,则以textAlignment的属性为准
* 也就是说textAlignment的属性优先级大于gravity的属性
*/
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(getGravity(), layoutDirection);
int lastGravity = absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
int textAlignment = getTextAlignment();
if (TEXT_ALIGNMENT_TEXT_START == textAlignment || TEXT_ALIGNMENT_VIEW_START == textAlignment
|| Gravity.LEFT == lastGravity) {
canvas.drawText(sb.toString(), 0, mLineY, paint);
} else if (TEXT_ALIGNMENT_TEXT_END == textAlignment || TEXT_ALIGNMENT_VIEW_END == textAlignment
|| Gravity.RIGHT == lastGravity) {
float width = StaticLayout.getDesiredWidth(sb.toString(), getPaint());
canvas.drawText(sb.toString(), mViewWidth - width, mLineY, paint);
} else {
float width = StaticLayout.getDesiredWidth(sb.toString(), getPaint());
canvas.drawText(sb.toString(), (mViewWidth - width) / 2, mLineY, paint);
}
}
/**
* 绘制左右对齐效果
*
* @param canvas
* @param line
* @param paint
*/
private void drawScaledText(Canvas canvas, List<String> line, TextPaint paint) {
if (canvas == null || line == null || paint == null) {
return;
}
StringBuilder sb = new StringBuilder();
for (String aSplit : line) {
sb.append(aSplit);
}
float lineWidth = StaticLayout.getDesiredWidth(sb, getPaint());
float cw = 0;
float d = (mViewWidth - lineWidth) / (line.size() - 1);
for (String aSplit : line) {
canvas.drawText(aSplit, cw, mLineY, getPaint());
cw += StaticLayout.getDesiredWidth(aSplit + "", paint) + d;
}
}
/**
* 功能:判断字符串是否有中文
*
* @param str 字符串
* @return 是否有中文
*/
public boolean isCN(String str) {
try {
byte[] bytes = str.getBytes("UTF-8");
if (bytes.length == str.length()) {
return false;
} else {
return true;
}
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
public static int dipToPx(Context var0, float var1) {
float var2 = var0.getResources().getDisplayMetrics().density;
return (int) (var1 * var2 + 0.5F);
}
/**
* 判断是否包含标点符号等内容
*
* @param s 字符串
* @return 是否含有标点符号
*/
public boolean checkIsSymbol(String s) {
boolean b = false;
String tmp = s;
tmp = tmp.replaceAll("\\p{P}", "");
if (s.length() != tmp.length()) {
b = true;
}
return b;
}
}
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:type="linear"
android:startColor="#80000000"
android:endColor="#00000000"
android:angle="90" />
</shape>
... ...
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/clEmptyArea"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 做背景用-->
<LinearLayout
android:id="@+id/llDetail"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="@+id/clContent"
app:layout_constraintTop_toTopOf="@+id/clContent">
<!-- 从透明0到1的渐变 -->
<View
android:id="@+id/v1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="7"
android:background="@drawable/shape_gradient_short_video_describe_dialog_bg"
app:layout_constraintBottom_toTopOf="@+id/v2"
app:layout_constraintTop_toTopOf="parent" />
<!-- 透明度 1 -->
<View
android:id="@+id/v2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:background="@color/color_000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/v1"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
<!-- 查看详情的内容 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/clContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent">
<!-- 作者-->
<TextView
android:id="@+id/tv_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/rmrb_dp16"
android:layout_marginTop="@dimen/rmrb_dp62"
android:clickable="true"
android:textColor="@color/res_color_common_C8_keep"
android:textSize="@dimen/rmrb_dp14"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="\@每日经济新闻" />
<com.wd.common.widget.MaxHeightScrollView
android:id="@+id/svContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:maxScrollHeight="@dimen/rmrb_dp210"
app:layout_constraintTop_toBottomOf="@+id/tv_author">
<LinearLayout
android:id="@+id/llContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 标题-->
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:lineSpacingMultiplier="1.1"
android:layout_marginStart="@dimen/rmrb_dp16"
android:layout_marginTop="@dimen/rmrb_dp8"
android:layout_marginEnd="@dimen/rmrb_dp16"
android:textColor="@color/res_color_common_C8_keep"
android:textSize="@dimen/rmrb_dp16"
android:textStyle="bold"
tools:text="深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通深圳首条跨海通道单线盾构贯通" />
<!-- 描述-->
<TextView
android:id="@+id/tv_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:lineSpacingMultiplier="1.1"
android:layout_marginStart="@dimen/rmrb_dp16"
android:layout_marginTop="@dimen/rmrb_dp2"
android:layout_marginEnd="@dimen/rmrb_dp16"
android:textColor="@color/res_color_common_C8_keep"
android:textSize="@dimen/rmrb_dp14"
tools:text="当日,中铁隧道局承建的深圳首条跨海通道——妈湾跨海通道右线实现贯通。妈湾跨海通道工程线路全长8.05公里,是连接深圳南山区前海片区和宝安区的海底地下工程。此次贯通的妈湾跨海通道右线盾构全长2063米,是整条线路的控制性工程当日,中铁隧道局承建的深圳首条跨海通道——妈湾跨海通道右线实现贯通。妈湾跨海通道工程线路全长8.05公里,是连接深圳南山区前海片区和宝安区的海底地下工程。此次贯通的妈湾跨海通道右线盾构全长2063米,是整条线路的控制性工程" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/rmrb_dp13"
android:textColor="@color/res_color_general_B3FFFFFF_keep"
android:layout_marginTop="@dimen/rmrb_dp8"
android:layout_marginStart="@dimen/rmrb_dp16"
android:visibility="gone"/>
</LinearLayout>
</com.wd.common.widget.MaxHeightScrollView>
<!-- 关闭按钮-->
<ImageView
android:id="@+id/iv_close"
android:layout_width="@dimen/rmrb_dp24"
android:layout_height="@dimen/rmrb_dp24"
android:layout_marginTop="@dimen/rmrb_dp17"
android:layout_marginBottom="@dimen/rmrb_dp30"
android:src="@mipmap/ic_video_desc_close"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/svContent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/expand_root_fl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/color_000000">
<LinearLayout
android:id="@+id/expand_ll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:gravity="right"
android:layout_marginRight="@dimen/rmrb_dp2"
android:visibility="gone"
tools:visibility="visible"
android:orientation="horizontal">
<TextView
android:id="@+id/expand_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/rmrb_dp6"
android:text="全部"
android:textColor="#ffffff"
android:textSize="@dimen/rmrb_dp14"
android:visibility="visible" />
<TextView
android:id="@+id/expand_helper_tv"
android:layout_width="0.5dp"
android:layout_height="wrap_content"
android:text=""
android:textColor="#333333"
android:textSize="@dimen/rmrb_dp14"
tools:visibility="visible"
android:visibility="gone" />
<ImageView
android:id="@+id/expand_iv"
android:layout_width="@dimen/rmrb_dp12"
android:layout_height="@dimen/rmrb_dp12"
android:layout_marginTop="@dimen/rmrb_dp4"
android:src="@mipmap/icon_more_arrow_white"
android:scaleType="fitXY"
android:visibility="gone"
tools:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/expand_content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textColor="#333333"
android:textSize="@dimen/rmrb_dp14" />
</FrameLayout>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/expand_root_fl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:background="@color/color_000000">
<LinearLayout
android:id="@+id/expand_ll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:gravity="right"
android:layout_marginRight="@dimen/rmrb_dp2"
android:visibility="gone"
tools:visibility="visible"
android:orientation="horizontal">
<TextView
android:id="@+id/expand_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="@dimen/rmrb_dp6"
android:textStyle=""
android:text="全部"
android:textColor="#ffffff"
android:textSize="@dimen/rmrb_dp14"
android:visibility="visible" />
<TextView
android:id="@+id/expand_helper_tv"
android:layout_width="0.5dp"
android:layout_height="wrap_content"
android:text=""
android:textColor="#333333"
android:textSize="@dimen/rmrb_dp14"
tools:visibility="visible"
android:visibility="gone" />
<ImageView
android:id="@+id/expand_iv"
android:layout_width="@dimen/rmrb_dp12"
android:layout_height="@dimen/rmrb_dp12"
android:layout_marginTop="@dimen/rmrb_dp4"
android:src="@mipmap/icon_more_arrow_white"
android:scaleType="fitXY"
android:visibility="gone"
tools:visibility="gone" />
</LinearLayout>
<com.wd.foundation.wdkit.view.customtextview.StrokeWidthTextView
android:id="@+id/expand_content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:text_stroke_width="1"
tools:text="标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题"
android:textColor="@color/white"
android:textSize="@dimen/rmrb_dp14" />
</FrameLayout>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/expand_root_fl"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/expand_ll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:gravity="right"
android:layout_marginRight="@dimen/rmrb_dp2"
android:orientation="horizontal"
tools:visibility="visible"
android:visibility="gone">
<TextView
android:id="@+id/expand_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:background="@color/color_000000"
android:text="全部"
android:paddingBottom="@dimen/rmrb_dp2"
android:textColor="#ffffff"
android:textSize="@dimen/rmrb_dp14"
android:visibility="visible" />
<TextView
android:id="@+id/expand_helper_tv"
android:layout_width="0.5dp"
android:layout_height="wrap_content"
android:text=""
android:textColor="#333333"
android:textSize="@dimen/rmrb_dp14"
tools:visibility="visible"
android:visibility="gone" />
<ImageView
android:id="@+id/expand_iv"
android:layout_width="@dimen/rmrb_dp12"
android:layout_height="@dimen/rmrb_dp22"
android:layout_marginLeft="@dimen/rmrb_dp6"
android:scaleType="fitXY"
android:visibility="gone" />
</LinearLayout>
<com.wd.common.widget.expand.XQJustifyTextView
android:id="@+id/expand_content_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="@dimen/rmrb_dp2"
android:text="【不是特效!25岁小伙空翻打卡25处郑州地标】5月22日,河南郑州。25岁跑酷爱好者贠禾嘉花了近3天时间,用后空翻的形式打卡郑州25个地标性建筑,用时1小时15分钟。"
android:textColor="#333333"
android:textSize="@dimen/rmrb_dp14" />
<View
android:id="@+id/newlineV"
android:layout_width="wrap_content"
android:layout_height="@dimen/rmrb_dp14"
android:layout_below="@+id/expand_content_tv"
android:visibility="gone"></View>
</FrameLayout>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/llProgressTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:background="@color/color_000000">
<TextView
android:id="@+id/tvPlayProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/color_80FFFFFF"
android:textSize="@dimen/rmrb_dp24"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPlayProgressSlash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/rmrb_dp10"
android:layout_marginRight="@dimen/rmrb_dp10"
android:text="/"
android:textColor="@color/color_80FFFFFF"
android:textSize="@dimen/rmrb_dp12"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPlayTotalTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/color_80FFFFFF"
android:textSize="@dimen/rmrb_dp24"
android:textStyle="bold" />
</LinearLayout>
\ No newline at end of file
... ...
<?xml version="1.0" encoding="utf-8"?><!-- 全局区域动画(全域动画)加载动画 refreshing_btm_loading.pag -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="@dimen/rmrb_dp24"
android:paddingBottom="@dimen/rmrb_dp24">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="@dimen/rmrb_dp12"
android:layout_height="@dimen/rmrb_dp12"
app:lottie_autoPlay="false"
app:lottie_loop="true" />
<TextView
android:id="@+id/tv_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="努力加载中"
android:layout_marginLeft="@dimen/rmrb_dp6"
android:textColor="@color/res_color_888888"
android:textSize="@dimen/rmrb_dp12" />
</LinearLayout>
\ No newline at end of file
... ...
... ... @@ -30,4 +30,6 @@
<color name="alivc_common_bg_cyan_grey">#515254</color>
<color name="color_73666666">#73666666</color>
<color name="red_theme_bg">#FFCC2424</color>
<color name="main_activity_top_img_bg">#FF7800</color>
</resources>
\ No newline at end of file
... ...
... ... @@ -568,4 +568,47 @@
<enum name="serif" value="3" />
</attr>
</declare-styleable>
<!--可展开布局自定义属性-->
<declare-styleable name="ExpandTextView">
<!--文本最大缩略行数,对应于TextView的maxLines-->
<attr name="maxLines" format="integer" />
<!--展开时对应的图片-->
<attr name="expandIconResId" format="reference" />
<!--不展开时对应的图片-->
<attr name="collapseIconResId" format="reference" />
<!--展开图片距离顶部距离-->
<attr name="expandIconTop" format="integer" />
<!--展开时对应末尾提示的文字-->
<attr name="expandMoreText" format="string" />
<!--不展开时对应末尾提示的文字-->
<attr name="collapseLessText" format="string" />
<!--内容文字的颜色-->
<attr name="contentTextColor" format="reference|color" />
<!--更多或收起文字的颜色-->
<attr name="expandTextColor" format="reference|color" />
<!--文字大小-->
<attr name="contentTextSize" format="dimension" />
<!--更多/收起文字大小-->
<attr name="expandTextSize" format="dimension" />
<!--展开/收缩布局对应图标的宽度-->
<attr name="expandIconWidth" format="dimension" />
<!--缩略文本展示时与展开/收缩布局的间距-->
<attr name="spaceMargin" format="dimension" />
<!--文本显示的lineSpacingExtra,对应于TextView的lineSpacingExtra属性-->
<attr name="lineSpacingExtra" format="dimension" />
<!--文本显示的lineSpacingMultiplier,对应于TextView的lineSpacingMultiplier属性-->
<attr name="lineSpacingMultiplier" format="float" />
<!--展开样式-->
<attr name="expandStyle" format="integer">
<!--默认样式:图标+文字-->
<enum name="DEFAULT" value="0" />
<enum name="ICON" value="1" />
<enum name="TEXT" value="2" />
</attr>
<!--是否收缩-->
<attr name="isShrink" format="boolean" />
<!--是否左右对齐-->
<attr name="isJustify" format="boolean" />
</declare-styleable>
</resources>
... ...
... ... @@ -49,7 +49,10 @@ android {
dependencies {
implementation project(path: ':module_musicplayer')
implementation project(path: ':wdkitcore')
implementation project(path: ':wdlayout')
implementation project(path: ':base_comment')
annotationProcessor rootProject.ext.dependencies["arouter-compiler"]
implementation rootProject.ext.dependencies["TagTextView"]
// coreLibraryDesugaring rootProject.ext.dependencies["desugar_jdk_libs"]
... ...
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.people.module_player">
package="com.wd.module_videoplayer">
<application>
<activity
android:name=".ui.ShortVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|screenLayout|uiMode"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"/>
</application>
</manifest>
\ No newline at end of file
... ...