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++; //行数+1

                if (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; //真正项目中可以用保存到本地数据库中的字段值代替,以实现永久化设置
}

点击事件:

tv_more.setOnClickMore(new MoreTextView.OnClickMore() {
            @Override
            public void onClickMore() {
                Toast.makeText(SetActivity.this,"点击了更多按钮",Toast.LENGTH_SHORT).show();
            }
        });

你可能感兴趣的:(Android自定义可拓展TextView)