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(); } });

    推荐阅读