Android自定义可拓展TextView
需求:
1:可设置最大行数,达到最大行数后末尾显示…
2:最大行数末尾的…之后显示提示展开的文案和图片,比如:“更多【图标】”
3:动态控制“更多【图标】”的点击事件,点击后可以展开看全部,也可以自定义处理
4:“更多【图标】”中文案和图片可以自定义
5:支持汉字+字母+数字都有时换行不出问题,系统自带的TextView此时在行尾会留有空白
6:文字支持缩放
自定义TextView代码:
package com.example.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.time.format.TextStyle;
public class MoreTextView extends RelativeLayout {
private Context context;
private TextView tv_more;
//添加进来的更多的自定义布局
private int mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom;
//上下左右的边距
private float textSize;
//字体大小,单位:PX
private String mText;
//文字内容
private String textColor;
//字体颜色
private String spend_text;
//更多的提示文案
private String expand_text ;
//收起的提示文案
private int maxLines = 0;
//设置的最大行数
private String spend_textcolor ;
//"更多"提示文案的字体颜色
private int spend_drawable = -1;
//"更多"提示文案的图片
private boolean expand_visibility ;
//展开之后"收起"提示是否显示-
private boolean spend_visibility ;
//是否显示"更多"
private String exPand_textcolor = "#444444";
//"收起"提示文案的字体颜色
private int exPand_drawable ;
//"收起"提示文案的图片
private boolean exPand;
//是否展开
private float lineSpace;
//行间距,单位:PX
private MyTextView myTextView;
//自定义的TextView
private boolean hasSetMarginLeft = false;
//因为更多的提示文案需要动态设置在最后一行的位置,所以这里需要在myTextView绘制完成后调用两次,所以需要加一个判断,防止循环调用
private float scalTo_textSize;
//要缩放到的大小
private int defalutTextSize = 15;
//默认字号
private int defalutLineSpece = 2;
//默认行间距
private int defaultMaxLines = 0;
//默认的最大行数
private String defaultColor = "#444444";
private String defaultSpendText = "更多";
private String defaultExSpandText = "收起";
public interface OnClickMore{ //点击事件的回调接口
void onClickMore();
}
public MoreTextView(Context context) {
super(context);
}public MoreTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
initAttr(context,attrs);
}public MoreTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
initAttr(context,attrs);
}private void initAttr(Context context,AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MoreTextView);
mPaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_paddingLeft, 0);
mPaddingRight = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_paddingRight, 0);
mPaddingTop = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_paddingTop, 0);
mPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_paddingBottom, 0);
//设置的主体文案
mText = typedArray.getString(R.styleable.MoreTextView_my_text);
//设置的主体文字的颜色
textColor = typedArray.getString(R.styleable.MoreTextView_my_textColor);
//设置的"查看更多"的提示文案
spend_text = typedArray.getString(R.styleable.MoreTextView_my_spend_text);
//设置的"查看更多"的提示文案的颜色
spend_textcolor = typedArray.getString(R.styleable.MoreTextView_my_spend_textcolor);
//设置的"查看更多"的提示文案的右侧图片
spend_drawable = typedArray.getResourceId(R.styleable.MoreTextView_my_spend_drawable, -1);
//是否展示"查看更多"
spend_visibility = typedArray.getBoolean(R.styleable.MoreTextView_my_spend_visibility, true);
//设置的"收起"的提示文案
expand_text = typedArray.getString(R.styleable.MoreTextView_my_expand_text);
//设置的"收起"的提示文案的颜色
exPand_textcolor = typedArray.getString(R.styleable.MoreTextView_my_expand_textcolor);
//设置的"收起"的提示文案的右侧图片
exPand_drawable= typedArray.getResourceId(R.styleable.MoreTextView_my_expand_drawable,-1);
//是否展示"收起"
expand_visibility = typedArray.getBoolean(R.styleable.MoreTextView_my_expand_visibility, true);
//是否可以展开,为false时,点击"查看更多"将会走点击事件,即setOnClickMore()方法的回调
exPand = typedArray.getBoolean(R.styleable.MoreTextView_my_can_expand,true);
//设置的最大显示行数
maxLines = typedArray.getInt(R.styleable.MoreTextView_my_max_lines,defaultMaxLines);
//主体文案的字号,注意:此处会转成px;默认字号:15DP
textSize = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_textSize, dip2px(context,defalutTextSize));
//主体文案的行间距;
注意:此处会转成px;默认2DP
lineSpace = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_lineSpacing, dip2px(context,defalutLineSpece));
scalTo_textSize = typedArray.getDimensionPixelSize(R.styleable.MoreTextView_my_scalTo_textSize, dip2px(context,defalutTextSize));
//判空操作,设置默认颜色
if(TextUtils.isEmpty(textColor)){
textColor = defaultColor;
}
if(TextUtils.isEmpty(spend_textcolor)){
spend_textcolor = defaultColor;
}
if(TextUtils.isEmpty(exPand_textcolor)){
exPand_textcolor = defaultColor;
}//判空操作,设置默认图
if (spend_drawable == -1)
{
spend_drawable = R.mipmap.ic_launcher;
}if (exPand_drawable == -1)
{
exPand_drawable = R.mipmap.ic_launcher;
}//判空操作,设置默认文案
if (TextUtils.isEmpty(spend_text))
{
spend_text = defaultSpendText ;
}if (TextUtils.isEmpty(expand_text))
{
expand_text = defaultExSpandText ;
}if (Word.isScal) //如果要缩放,就将大小设置到缩放后的大小
{
textSize = scalTo_textSize;
}
typedArray.recycle();
//不设置文本,则不创建布局
if (TextUtils.isEmpty(mText) == false)
{
if (spend_visibility) //展示更多,则创建更多的布局
{
initMoreView();
//创建尾部的更多布局
}
createMyTextView();
//创建自定义的TextView
}
}private void createMyTextView()
{
myTextView = new MyTextView(context);
addView(myTextView);
//将自定义的TextView添加到布局中//注意:这里必须设置宽高
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
myTextView.setLayoutParams(layoutParams);
myTextView.setAttrs(mText,textColor,textSize,maxLines,lineSpace,spend_visibility);
myTextView.setOnTextViewDrawFinish(new MyTextView.OnTextViewDrawFinish() {
@Override
public void onFinish(int drawLineNum,int lastLineWidth) {
setTvMoreLoaction(drawLineNum,lastLineWidth);
//Textview绘制完成后的设置更多的位置
}
});
}/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}private void initMoreView() {
tv_more = new TextView(context);
if (maxLines > 0)
{
tv_more.setText(spend_text);
//表示收起状态,需要展示更多
tv_more.setTextColor(Color.parseColor(spend_textcolor));
setTvMoreDrawable(spend_drawable);
}else {
tv_more.setText(expand_text);
//表示收起状态,需要展示收起
tv_more.setTextColor(Color.parseColor(exPand_textcolor));
setTvMoreDrawable(exPand_drawable);
}
tv_more.setTextSize(TypedValue.COMPLEX_UNIT_PX,textSize);
addView(tv_more);
//将更多布局添加到relativelayout中
}//设置提示文案的右侧图片
private void setTvMoreDrawable(int drawableId)
{
Drawable drawable;
drawable = getResources().getDrawable(drawableId);
drawable.setBounds(0, 0, (int) textSize, (int) textSize);
//设置图片的宽高
tv_more.setCompoundDrawables(null, null, drawable, null);
}//lineNum:行数
//lastLineWidth:最后一行的宽度
private void setTvMoreLoaction(int lineNum,int lastLineWidth)
{
RelativeLayout.LayoutParams layoutParamsAddView = (LayoutParams) tv_more.getLayoutParams();
int marginTop = (int) ((lineNum - 1) * (myTextView.signleLineHeight + lineSpace));
layoutParamsAddView.setMargins(lastLineWidth,marginTop,0,0);
tv_more.setLayoutParams(layoutParamsAddView);
if (hasSetMarginLeft != false)
{
return;
}
hasSetMarginLeft = true;
myTextView.setText(mText,tv_more.getWidth());
}public void setOnClickMore(OnClickMore onClickMore)
{
if (spend_visibility == false || tv_more == null)
{
return;
}
tv_more.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(exPand== false) //不可展开
{
if (onClickMore != null)
{
onClickMore.onClickMore();
}
}else { //可展开
if (tv_more.getText().toString().equals(spend_text)) //当前是收起状态,要展开
{
myTextView.setAttrs(mText,textColor,textSize,-1,lineSpace,spend_visibility);
tv_more.setText(expand_text);
//显示收起的文案
tv_more.setTextColor(Color.parseColor(exPand_textcolor));
setTvMoreDrawable(exPand_drawable);
if (expand_visibility)
{
tv_more.setVisibility(VISIBLE);
}else {
tv_more.setVisibility(GONE);
}
}else {
myTextView.setAttrs(mText,textColor,textSize,maxLines,lineSpace,spend_visibility);
tv_more.setText(spend_text);
//显示展开的文案
tv_more.setTextColor(Color.parseColor(spend_textcolor));
setTvMoreDrawable(spend_drawable);
}
}
}
});
}
}
package com.example.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
public class MyTextView extends TextView {private Paint contentPaint;
//内容填充画笔
private int viewWidth = 0;
//控件宽度
private int viewHeight = 0;
//控件高度
private String textColor;
//字体颜色
private float textSize;
//字体大小
private String mText = "测试的文字信息";
//内容
private boolean spend_visibility;
//是否显示"更多的提示"
private float lineSpace;
//行间距
private int maxLines;
//最大行数
private Paint.FontMetricsInt textFm;
//文字测量工具
private int textWidth;
//文字显示区的宽度
public int signleLineHeight;
//单行文字的高度
private int mPaddingLeft,mPaddingRight,mPaddingTop,mPaddingBottom;
private Context context;
public int lineNum = 0;
//实际绘制的行数
private int lastLineWidth = 0;
//最后一行的宽度
private int cutLength;
//最后一行需要剪裁掉的宽度,即"更多"按钮的宽度/**
* 省略号
**/
private String ellipsis = "...";
private OnTextViewDrawFinish onTextViewDrawFinish;
//绘制完成之后的回调
public interface OnTextViewDrawFinish
{
void onFinish(int drawLineNum,int lastLineWidth);
}public MyTextView(Context context) {
this(context,null);
}public MyTextView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
}/**
* 初始化
*/
private void init() {
contentPaint = new Paint();
contentPaint.setTextSize(textSize);
contentPaint.setAntiAlias(true);
contentPaint.setStrokeWidth(2);
contentPaint.setColor(Color.parseColor(textColor));
contentPaint.setTextAlign(Paint.Align.LEFT);
textFm = contentPaint.getFontMetricsInt();
signleLineHeight=Math.abs(textFm.top-textFm.bottom);
}public void setAttrs(String mText,String textColor,float textSize,int maxLines,float lineSpace,boolean spend_visibility)
{
this.textColor = textColor;
this.textSize = textSize;
this.maxLines = maxLines;
this.lineSpace = lineSpace;
this.mText = mText;
this.spend_visibility = spend_visibility;
if (TextUtils.isEmpty(textColor))
{
this.textColor = "#000000";
}
if (this.textSize <= 0)
{
this.textSize = dip2px(context,15);
}if (this.lineSpace <= 0)
{
this.lineSpace = dip2px(context,3);
}
init();
setText(mText);
}public void setOnTextViewDrawFinish(OnTextViewDrawFinish onTextViewDrawFinish)
{
this.onTextViewDrawFinish = onTextViewDrawFinish;
}/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}public void setText(String ss,int cutLength){
this.mText=ss;
this.cutLength = cutLength;
invalidate();
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
viewWidth=getMeasuredWidth();
textWidth=viewWidth-mPaddingLeft-mPaddingRight;
viewHeight= (int) getViewHeight();
setMeasuredDimension(viewWidth, viewHeight);
}public float getViewHeight() {
char[] textChars=mText.toCharArray();
float ww=0.0f;
int count=0;
StringBuilder sb=new StringBuilder();
for(int i=0;
i 0 && lineNum >= maxLines) //如果画的行数和设置的最大行数一样,则终止当前循环
{
isMax = true;
if (spend_visibility) //显示"更多"按钮
{
StringBuilder lastLineString = getLastLineString(sb);
lastLineString.replace(lastLineString.length()-1,lastLineString.length(),ellipsis);
//将最后三个字替换成...
canvas.drawText(lastLineString.toString(),startL,startT,contentPaint);
//画一行
if (onTextViewDrawFinish != null)
{
onTextViewDrawFinish.onFinish(lineNum, lastLineWidth);
}
}else {
sb.replace(sb.length()-1,sb.length(),ellipsis);
//将最后三个字替换成...
canvas.drawText(sb.toString(),startL,startT,contentPaint);
//画一行
}break;
}
canvas.drawText(sb.toString(),startL,startT,contentPaint);
//画一行
startT+=signleLineHeight+lineSpace;
//换到下一行
sb=new StringBuilder();
ww=0.0f;
ww+=v;
sb.append(textChars[i]);
}
}if (isMax == false)
{
if(sb.toString().length()>0){
lineNum++;
//行数+1if (spend_visibility) //显示"更多"按钮
{
StringBuilder lastLineString = getLastLineString(sb);
canvas.drawText(lastLineString.toString(),startL,startT,contentPaint);
if (onTextViewDrawFinish != null)
{
onTextViewDrawFinish.onFinish(lineNum, (int) lastLineWidth);
}
}else {
canvas.drawText(sb.toString(),startL,startT,contentPaint);
}
}
}
}//将最后一行的宽度+展开更多的宽度与textview的宽度进行对比,来得到最后一行应该绘制几个字符
private StringBuilder getLastLineString(StringBuilder sb)
{
lastLineWidth = 0;
//每次调用时都重置下
char[] chars = sb.toString().toCharArray();
StringBuilder sbNew= new StringBuilder();
for (int i = 0;
i < chars.length;
i++) {
float v = contentPaint.measureText(chars[i] + "");
if ((lastLineWidth+v+cutLength)>textWidth)
{
break;
}else {
lastLineWidth += v;
sbNew.append(chars[i]);
}
}
return sbNew;
}
}
attrs配置代码:
动态开关代码:
package com.example.myapplication;
public class Word {
public static boolean isScal = true;
//真正项目中可以用保存到本地数据库中的字段值代替,以实现永久化设置
}
【Android自定义可拓展TextView】点击事件:
tv_more.setOnClickMore(new MoreTextView.OnClickMore() {
@Override
public void onClickMore() {
Toast.makeText(SetActivity.this,"点击了更多按钮",Toast.LENGTH_SHORT).show();
}
});
推荐阅读
- android第三方框架(五)ButterKnife
- Android中的AES加密-下
- 2019-1-14
- 带有Hilt的Android上的依赖注入
- SpringBoot调用公共模块的自定义注解失效的解决
- python自定义封装带颜色的logging模块
- 列出所有自定义的function和view
- android|android studio中ndk的使用
- 抑郁症(可怕吗?)
- 松软可口易消化,无需烤箱超简单,新手麻麻也能轻松成功~