公司的UI都是些大神,设计起来毫不含糊,怎么好看怎么设计,没办法只能一点点实现着(UI以后给别的团队设计估计会说 某某团队的某某以前作出过了类似的效果 ... 嘎嘎嘎 )
1、根据UI设计有三个重点
a、消息内容是不固定的,视长度而现实不同的效果
b、收起/全文按钮与消息 content的 TextView对齐
c、content textView 消息需要左右都对齐
<实现效果图>
2、自己梳理出的实现思路
a、全文/收起按钮的显示 是通过监听textView 内容(需要自定义 listener)
b、全文/收起按键会触发 textview 的显示, (Textiview 需要添加一个对应设置方法)
c、收起模式下 从UI的层面来看会占据 TextView 的右下方并且保持对齐(这里需要计算出最后一行的width 和 全文/收起 按键长度是否超过了 ATMOST 的最大值,把多出的内容用省略号替换掉)
d、全文模式下 从UI的层面来看会占据 TextView 的右下方并且保持对齐,而且保持textView的显示完全,需要计算并且准确换行
e、每一行的内容确定后需要保持左右靠边对齐
3、写代码(自定义的 FullTextView 关键的实现地方添加注释 ,默认先不考虑padding的值)
public class FullTextView extends AppCompatTextView {
/**
* 原始数据
*/
private String oldText;
/**
* 全文显示内容
*/
private StringBuffer fullText = new StringBuffer();
/**
* 缩略显示内容
*/
private StringBuffer unFullText = new StringBuffer();
/**
* 通知外部UI 是否显示 全文/收起/不显示 按键
*/
private FullTextListener listener;
/**
* 展示全文显示(默认是不显示全文)
*/
boolean isShowFullText = false;
/**
* UI 给出缩略显示最大行数为 2
*/
private final int UN_FULL_MAX_LINE = 2;
/**
* 最宽 长度
*/
private int widthSize = 0;
String TAG = FullTextView.class.getSimpleName();
public FullTextView(Context context) {
super(context);
}
public FullTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 设置原始数据
* 此处不需要通过setText() 方法来实现,该自定义TextView 的测绘 跟绘制都是通过自定义实现
*/
public void setShowText(String text) {
oldText = text;
if (widthSize > 0) {// 消息适用于 adapter 避免view 复用内容也复用了,所以需要重新计算出内容
calculateText();
updateUI();
}
}
/**
* 刷新UI
*/
private void updateUI() {
//触发 onMeasure onLayout
requestLayout();
//触发 onDraw
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
Log.i(TAG, hashCode() + " width == " + width);
//目前 默认为ATMOST 方式
if (width != 0) {
widthSize = width;
calculateText();
setMeasuredDimension(widthSize, getLineHeight() * (getLinesStr().length - 1) + getFirstBaselineToTopHeight() + getLastBaselineToBottomHeight());
}
}
/**
* 每一行的String 以数组的 形式返回
*
* @return
*/
private String[] getLinesStr() {
String[] strings;
if (!isShowFullText) {
// “\n” 最后时 split 会少一行内容
if (unFullText.toString().endsWith("\n")) {
unFullText.append(" ");
}
strings = unFullText.toString().split("\n");
} else {
if (fullText.toString().endsWith("\n")) {
fullText.append(" ");
}
strings = fullText.toString().split("\n");
}
return strings;
}
/**
* 计算出 缩略显示的 text
*/
private void calculateUnFullText() {
if (widthSize == 0) return;
unFullText = new StringBuffer();
Paint tvPaint = getPaint();
float tempWidth = 0;
//行数计数器
int lineCount = 0;
for (int cnt = 0; cnt != oldText.length(); ++cnt) {
char ch = oldText.charAt(cnt);
tempWidth += tvPaint.measureText(String.valueOf(ch));
if (tempWidth <= widthSize) {
unFullText.append(ch);
} else {
lineCount++;
tempWidth = 0;
--cnt;
if (lineCount == UN_FULL_MAX_LINE) {
break;
}
unFullText.append("\n");
}
}
// 总行数达不到 2行以上 特殊显示
if (lineCount < UN_FULL_MAX_LINE) {
listener.onShowFull(FullTextType.DAFAULT);
} else {
//如果是展示全文不需要处理
if (isShowFullText) {
listener.onShowFull(FullTextType.FULL);
} else {
float collapseW = tvPaint.measureText(getContext().getResources().getString(R.string.collapse));
float omitW = tvPaint.measureText(getContext().getResources().getString(R.string.omit));
String laststr = unFullText.toString().split("\n")[UN_FULL_MAX_LINE - 1];
for (int i = 0; i < laststr.length(); i++) {
float textW = tvPaint.measureText(laststr.substring(0, laststr.length() - i));
if ((textW + collapseW + omitW) <= widthSize) {
unFullText = new StringBuffer(unFullText.substring(0, unFullText.length() - i));
break;
}
}
unFullText.append(getContext().getResources().getString(R.string.omit));
listener.onShowFull(FullTextType.UNFULL);
}
}
}
/**
* 最后的 收起二字 是否能在最后一行 内显示完全
*
* @return
*/
private void calculateFullText() {
oldText.replace("\n", "");
fullText = new StringBuffer();
Paint tvPaint = getPaint();
float tempWidth = 0;
//行数计数器
for (int cnt = 0; cnt != oldText.length(); ++cnt) {
char ch = oldText.charAt(cnt);
tempWidth += tvPaint.measureText(String.valueOf(ch));
if (tempWidth <= widthSize) {
fullText.append(ch);
} else {
fullText.append("\n");
tempWidth = 0;
--cnt;
}
}
float lastlinewidth = (tempWidth + tvPaint.measureText(getContext().getResources()
.getString(R.string.collapse)));
if (lastlinewidth > widthSize) {
fullText.append("\n");
}
}
/**
* 计算 全文/收起 文本显示内容
*/
private void calculateText() {
calculateFullText();
calculateUnFullText();
}
public boolean isShowFullText() {
return isShowFullText;
}
/**
* @param isShowFull 全文/收起 显示
* @param updateUI 是否刷新UI
*/
public void setShowFullText(boolean isShowFull, boolean updateUI) {
this.isShowFullText = isShowFull;
if (updateUI && fullText.length() > 0 && unFullText.length() > 0 && listener != null) {
if (isShowFullText) {
listener.onShowFull(FullTextType.FULL);
} else {
listener.onShowFull(FullTextType.UNFULL);
}
updateUI();
}
}
public void setFullTextListener(FullTextListener listener) {
this.listener = listener;
}
public interface FullTextListener {
void onShowFull(FullTextType type);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
onDrawStrings(canvas, getLinesStr());
}
/**
* 绘制每一行的 text
*
* @param canvas
* @param strs
*/
private void onDrawStrings(Canvas canvas, String[] strs) {
//起始 X值
float startX = 0;
for (int i = 0; i < strs.length; i++) {
float baseLine = getBaseline() + (getLineHeight() * i) + getPaddingTop();
if (i == strs.length - 1) {
startX = 0;
canvas.drawText(strs[i], startX, baseLine, getPaint());
break;
}
Log.i(TAG, "onDraw strs" + i + "]=" + strs[i]);
float lineWidth = getPaint().measureText(strs[i]);
float pw = (widthSize - lineWidth) / (strs[i].length());
startX = pw;
for (int j = 0; j < strs[i].length(); j++) {
canvas.drawText(String.valueOf(strs[i].charAt(j)), startX, baseLine, getPaint());
startX += pw + getPaint().measureText(String.valueOf(strs[i].charAt(j)));
}
}
}
}
定义了一个 枚举
public enum FullTextType {
/**
* 不处理
*/
DAFAULT,
/**
* 全文
*/
FULL,
/**
* 收起
*/
UNFULL;
}
adapter 用法关键代码(xml 对齐的可以自己拓展)
//缓存记录 iTEM 状态 避免view复用 重复
if (fullmap.keySet().contains(position)) {
holder.content.setShowFullText(fullmap.get(position), false);
} else {
holder.content.setShowFullText(false, false);
}
holder.content.setShowText(msgBean.getContent());
holder.content.setFullTextListener(new FullTextView.FullTextListener() {
@Override
public void onShowFull(FullTextType type) {
switch (type) {
case DAFAULT:
holder.item_msg_show_all.setVisibility(View.GONE);
break;
case FULL:
holder.item_msg_show_all.setVisibility(View.VISIBLE);
holder.item_msg_show_all.setText(mContext.getResources().getString(R.string.collapse));
break;
case UNFULL:
holder.item_msg_show_all.setVisibility(View.VISIBLE);
holder.item_msg_show_all.setText(mContext.getResources().getString(R.string.expand));
break;
}
}
});
holder.item_msg_show_all.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean bb = !holder.content.isShowFullText();
holder.content.setShowFullText(bb, true);
fullmap.put(position, bb);
}
});
整理流程差多就是这样了,需要demo 的可以私信.