TextView渲染机制与优化(StaticLayout),TextView上的文字分散对齐,TextView用处

Android 字体适配的各种场景- https://github.com/wildma/FontAdaptation

-- TextView中的.setText和.append的区别:
setText()把以前的内容冲掉了,append()在以前的内容后面添加。

-- TextView用drawableLeft/Right时,改变文字和图片间的距离: 
利用drawablePadding属性:android:drawablePadding="@dimen/five_dp"

-- Android TextView设置空格
  代码里TextView.setText("\u3000");  xml里( );

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="一九八六年&#160;农历乙丑年&#160;七月二十八日" />

-- TextView 使用setMovementMethod滑动,TextView添加链接-setMovementMethod
 在布局文件中 设置 TextView属性 android:scrollbars="vertical"
 在代码中 textview.setMovementMethod(ScrollingMovementMethod.getInstance());
 ps:设置滑动属性后,触摸时颜色可能会变,可以设置字体颜色为白色即可:android:textColor="#FFFFFF" 

textView.setMovementMethod(LinkMovementMethod.getInstance());设置此方法才能使点击处理生效。

-- TextView的textIsSelectable属性和setMovementMethod()
 TextView的textIsSelectable属性可以支持长按文字可以复制,搜索等,而且支持对TextView的内容滑动。
 TextView的setMovementMethod()方法,也可以支持对TextView的内容滑动,但对Textview内容不支持长按文字可以复制,搜索等。

-- 点击TextView发布人或评论人的昵称查看详情,支持超链接,支持emoji表情,
  利用ClickableSpan让TextView支持高亮点击,我们新建几个类继承ClickableSpan,一般我们有两个方法需要重写,分别是updateDrawState和onClick,默认情况下此时TextView会显示默认的主题颜色和有下划线;SpannableString来设置;
Apache Commons Lang- http://commons.apache.org/proper/commons-lang/download_lang.cgi
//把字符串转为unicode编码
StringEscapeUtils.escapeJava(etContent.getText().toString())
//解码unicode编码
StringEscapeUtils.unescapeJava(data.getContent())
通过转码和解码我们就可以支持emoji表情

-- 如何实现 “中间这几个字要加粗,但是不要太粗,比较纤细的那种粗” ?
 1. A helper class that extends SpannableStringBuilder and adds methods to easily mark the text with multiple spans.- https://github.com/binaryfork/Spanny
Spanny spanny = new Spanny("Underline text", new UnderlineSpan())
                .append("\nBold text", new StyleSpan(Typeface.BOLD))
                .append("\nPlain text");
textView.setText(spanny);

 2. span+画笔处理. //没错,就几行代码这么简单
public class FakeBoldSpan extends CharacterStyle {

 @Override
 public void updateDrawState(TextPaint tp) {
     tp.setFakeBoldText(true);//一种伪粗体效果,比原字体加粗的效果弱一点
     // tp.setStyle(Paint.Style.FILL_AND_STROKE);
     // tp.setColor(Color.RED);//字体颜色
     // tp.setStrokeWidth(10);//控制字体加粗的程度
 }
}
//使用:
fakeBoldText.setText(new Spanny().append("FakeBold",new FakeBoldSpan()));

设置一下Span的画笔:
        tp.setStyle(Paint.Style.FILL_AND_STROKE);
        tp.setColor(Color.RED);//字体颜色
        tp.setStrokeWidth(10);//控制字体加粗的程度

-- textview左右文字对齐
public class AlignTextView extends AppCompatTextView {

    private boolean alignOnlyOneLine;

    public AlignTextView(Context context) {
        this(context, null);
    }

    public AlignTextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AlignTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AlignTextView);
        alignOnlyOneLine = typedArray.getBoolean(R.styleable.AlignTextView_alignOnlyOneLine, false);
        typedArray.recycle();
    }

    protected void onDraw(Canvas canvas) {
        CharSequence content = getText();
        if (!(content instanceof String)) {
            super.onDraw(canvas);
            return;
        }
        String text = (String) content;
        Layout layout = getLayout();

        for (int i = 0; i < layout.getLineCount(); ++i) {
            int lineBaseline = layout.getLineBaseline(i) + getPaddingTop();
            int lineStart = layout.getLineStart(i);
            int lineEnd = layout.getLineEnd(i);
            if (alignOnlyOneLine && layout.getLineCount() == 1) {//只有一行
                String line = text.substring(lineStart, lineEnd);
                float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());
                this.drawScaledText(canvas, line, lineBaseline, width);
            } else if (i == layout.getLineCount() - 1) {//最后一行
                canvas.drawText(text.substring(lineStart), getPaddingLeft(), lineBaseline, getPaint());
                break;
            } else {//中间行
                String line = text.substring(lineStart, lineEnd);
                float width = StaticLayout.getDesiredWidth(text, lineStart, lineEnd, getPaint());
                this.drawScaledText(canvas, line, lineBaseline, width);
            }
        }

    }

    private void drawScaledText(Canvas canvas, String line, float baseLineY, float lineWidth) {
        if (line.length() < 1) {
            return;
        }
        float x = getPaddingLeft();
        boolean forceNextLine = line.charAt(line.length() - 1) == 10;
        int length = line.length() - 1;
        if (forceNextLine || length == 0) {
            canvas.drawText(line, x, baseLineY, getPaint());
            return;
        }

        float d = (getMeasuredWidth() - lineWidth - getPaddingLeft() - getPaddingRight()) / length;

        for (int i = 0; i < line.length(); ++i) {
            String c = String.valueOf(line.charAt(i));
            float cw = StaticLayout.getDesiredWidth(c, this.getPaint());
            canvas.drawText(c, x, baseLineY, this.getPaint());
            x += cw + d;
        }
    }
}

<declare-styleable name="AlignTextView">
    <attr name="alignOnlyOneLine" format="boolean"/>
</declare-styleable>

> TextView
TextView原理,SpannableString与SpannableStringBuilder- http://blog.csdn.net/wbwjx/article/details/53179209
实现类似新浪微博帖子显示,话题、@好友、表情解析工具类- http://blog.csdn.net/u011102153/article/details/52487049
话题、@好友、表情解析工具类- https://github.com/LineChen/SpannableStringDemo
TextView垂直向上滚动的- https://github.com/sfsheng0322/MarqueeView
仿淘宝首页的淘宝头条View垂直滚动-https://github.com/dreamlivemeng/UpMarqueeTextView-master 
TextSwitcher实现文字上下翻牌效果- http://blog.csdn.net/bdmh/article/details/50904140
TextSwitcher实现分析- http://blog.csdn.net/bdmh/article/details/50905138
Android 天气预报图文字幕垂直滚动效果- https://blog.csdn.net/t12x3456/article/details/9708163
androidTV文字动画 HTextView,Animation effects to text, not really textview- https://github.com/hanks-zyh/HTextView

Android实现TextView字符串波浪式跳动- https://github.com/frakbot/JumpingBeans

android状态栏背景及文字变色- https://github.com/hongyangAndroid/ColorTrackView

-- 自定义MarqueeView详解- https://github.com/sunfusheng/MarqueeView
自定义MarqueeView详解- https://github.com/gdutxiaoxu/MarqueeView
Android基于自定义TextView的垂直跑马灯效果- https://github.com/viclee2014/VerticalSwitchTextView
Android-垂直上下滚动的TextView- https://github.com/paradoxie/AutoVerticalTextview 

-- 安卓TextView完美展示html格式代码- https://blog.csdn.net/baiyuliang2013/article/details/53538118
Android HTML rendering library with CSS support- https://github.com/NightWhistler/HtmlSpanner
textView.setText(htmlSpanner.fromHtml(html));
textView.setText(Html.fromHtml(content));

-- 获取TextView上文字宽度
可自定义下划线颜色和宽度的TextView- https://github.com/lixiaote/UnderLineTextView
Android TextView加中划线,下划线- https://blog.csdn.net/zhuzhiqiang_zhu/article/details/50755980
A TextView that automatically fit its font and line count based on its available size and content- https://github.com/AndroidDeveloperLB/AutoFitTextView
Android TextView利用measureText自适应文本字体大小宽度- https://blog.csdn.net/zhangphil/article/details/79891765
可定制文字与下划线等宽- http://download.csdn.net/download/lmj623565791/8307513

-- 这样的话,就不用去拼字符串; Html.fromHtml显示各种颜色的文字
String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
result = String.format(result, (int) (Math.random() * 3000 + 1000));
TextView.setText(Html.fromHtml(result));

> 直接给TextView加图片,通过 setCompoundDrawable 方法, 或者直接在xml中使用android:drawableLeft.、
android:drawableRight等属性指定!

<TextView
        android:id="@+id/common_all_workbenchs"
        style="@style/common_textSize_color_14"
        android:layout_width="45dip"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="7dip"
        android:drawableRight="@drawable/list_right_arrow"
        android:text="@string/common_all_workbenchs_text" />

> TextView设置最多显示8个字符,超过部分显示...(省略号),网上找了很多资料,有人说分别设置TextView的
android:signature="true",并且设置android:ellipsize="end";
1、android:lineSpacingExtra 设置行间距,如”3dp”。
2、android:lineSpacingMultiplier 设置行间距的倍数,如”1.2″。
<TextView
        android:id="@+id/common_forth_process"
        style="@style/common_textSize_16"
        android:layout_width="60.0dip"
        android:layout_height="60.0dip"
        android:layout_centerHorizontal="true"
        android:background="@drawable/common_apply_leave"
        android:contentDescription="@null"
        android:ellipsize="end"
        android:gravity="center"
        android:maxEms="2"
        android:singleLine="true"
        android:text="@string/common_apply_leave_text"
        android:textColor="@color/white_color" />

> 两行,每行显示两个字
<LinearLayout
        android:layout_width="60.0dip"
        android:layout_height="60.0dip"
        android:layout_centerHorizontal="true"
        android:background="@drawable/common_apply_leave"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/common_forth_process"
            style="@style/common_textSize_16"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="@null"
            android:ems="2"
            android:text="@string/common_apply_leave_text"
            android:textColor="@color/white_color" />
    </LinearLayout>

> TextView实现图片和文字展示
 textView.setText(Html.fromHtml(string, imageGetter, null)); SpannableString
SpannableString spannableString1 = new SpannableString("身份:表情,电视购物 萨嘎,嘎嘎而 噶噶关闭安管部阿热感二胡版安放的噶尔gear回合肥 发发发发发发  发发发有营养UU骨干一样");
        Drawable drawable = getResources().getDrawable(R.mipmap.icon_back);
        //设置图片的尺寸
        drawable.setBounds(0, 0, 42, 50);
        ImageSpan imageSpan = new ImageSpan(drawable);
        //“表情”是占位置的,图片会把其替换掉,3和5是其索引位置,含头不含尾
        spannableString1.length();
//        spannableString1.setSpan(imageSpan, 3, 5 , SPAN_INCLUSIVE_EXCLUSIVE);
        spannableString1.setSpan(imageSpan, spannableString1.length()-2, spannableString1.length(), SPAN_INCLUSIVE_EXCLUSIVE);
        TextView showTv2 = (TextView) myView.findViewById(R.id.show_tv2);
        showTv2.setText(spannableString1);

Android TextView中的文字通过SpannableString,设置不同的颜色,字体,不同文字段的点击事件- https://blog.csdn.net/zuo_er_lyf/article/details/80340819
android TextView加SpannableString设置点击某几个文字的问题- https://blog.csdn.net/LoveDou0816/article/details/80365200
TextView+SpannableString实现Android中富文本的显示及点击冲突解决- https://blog.csdn.net/Wengwuhua/article/details/80503821
Android SpannableString实现TextView的点击事件-https://www.cnblogs.com/zimengfang/p/5527259.html
 

> TextView渲染机制与优化
TextView中负责渲染文字的主要是这三个类:
  1.BoringLayout:主要负责显示单行文本,并提供了isBoring方法来判断是否满足单行文本的条件。
  2.DynamicLayout:当文本为Spannable的时候,TextView就会使用它来负责文本的显示,在内部设置了SpanWatcher,当检测到span改变的时候,会进行reflow,重新计算布局。
  3.StaticLayout:当文本为非单行文本,且非Spannable的时候,就会使用StaticLayout,内部并不会监听span的变化,因此效率上会比 DynamicLayout高,只需一次布局的创建即可,但其实内部也能显示SpannableString,只是不能在span变化之后重新进行布局而已。

-- TextView性能瓶颈,渲染优化,以及StaticLayout的一些用处- https://www.jianshu.com/p/9f7f9213bff8
 TextView高频度绘图下的问题,在一些场景下,比如界面上有大量的聊天并且活跃度高,内容包含了文字,emoji,图片等各种信息的复杂文本,采用TextView来展示这些内容信息。就容易观察到,聊天消息在频繁刷新的时候,性能有明显下降,GPU火焰图抖动也更加频繁。
 
 - StaticLayout的用途:
a.文中高频度大量textview刷新优化。
b.一个textview显示大量的文本,比如一些阅读app。
c. 在控件上画文本,比如一个ImageView中心画文本。
d. 一些排版效果,比如多行文本文字居中对齐等。

 - 自定义View如下:
public class StaticLayoutView extends View {
        private Layout layout;
        private int width ;
        private int height;

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public StaticLayoutView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }

        public StaticLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        public StaticLayoutView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public StaticLayoutView(Context context) {
            super(context);
        }

        public void setLayout(Layout layout) {
            this.layout = layout;
            if (this.layout.getWidth() != width || this.layout.getHeight() != height) {
                width = this.layout.getWidth();
                height = this.layout.getHeight();
                requestLayout();
            }
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.save();
            if (layout != null) {
                layout.draw(canvas, null, null, 0);
            }
            canvas.restore();
        }

        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (layout != null) {
                setMeasuredDimension(layout.getWidth(), layout.getHeight());
            } else {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
 
> textview上的文字分散对齐
-- textview上的文字分散对齐- https://github.com/ufo22940268/Android-justifiedtextview
https://github.com/ufo22940268/android-justifiedtextview/blob/master/justifytext-library/library_justifytext/src/main/java/me/biubiubiu/justifytext/library/JustifyTextView.java ,这个类是比较正常的
  默认Textview在一行快要结束的时候,如果在符号后面,有一个长的字符串,这时候,TextView就会自动换行,导致排版会参差不齐,而自动换行导致混乱的原因了是半角字符与全角字符占位不同,一般情况下,我们输入的数字、字母以及英文标点都是半角,中文是全角,因此占位的位置大家都不同。在中英文混输的时候,导致很多文字的排版都是参差不齐的。
  那么要想要解决此问题就在解决它们的占位问题,网上有种思路是把半角符号的数字和英文全部转化为全角,这样一个字符就和中文字符对齐了。但是这样的话,就会使英文字母和英文字母之间分的很开,导致十分的难看。下面我们就开始来解决排版的问题。
  我们先了解下StaticLayout(This is used by widgets to control text layout. ),这个东西,TextView中就是使用StaticLayout来进行文字的排版处理。而我们这边需要让它来预处理下排版的分布,然后根据其提供的getDesiredWidth(CharSequence source, TextPaint paint)方法来判断一行文字本来需要的宽度值,再固定一行的宽度值,利用
固定的宽度 - 文本宽度 / 文本字数 = 每个文字的间距。这个方法来计算出排版对齐时,每个文字之间的距离。
【1】 在TextView中用android:text="测试     文字"的时候,可以正常显示正确的空格数。但是如果使用资源文件android:text="@string/txt"的时候,不管我资源文件里面<string name="txt">测试         文字</string>;中间使用多少个空格,或者TAB。在模拟器上运行的时候,N个空格都只显示一个,解决办法:使用全角空格。
这个用途估计只有在设计登陆框的时候能用吧,比如下面这样。
用户名:
密    码:
解决方案如下:
【2】 http://stackoverflow.com/questions/1587056/android-string-concatenate-how-to-keep-the-spaces-at-the-end-and-or-beginnin
 1.Even if you use string formatting sometimes you still need whitespaces at the beginning or the end. On these cases neither escaping with \ nor xml:space attribute helpes. You must use xml encoding&#160;for whitespaces。eg:&#160,表示全角空格,<string name="aaa">你好&#160;&#160;&#160;&#160;&#160;&#160;啊</string>
 2.I found a solution on this issue report : http://code.google.com/p/android-apktool/issues/detail?id=14
  This is the same idea that Duessi suggests. Insert \u0020 directly in the XML for a blank you would like to preserve.
  Example :
<string name="your_id">Score :\u0020</string>
The replacement is done at build time, therefore it will not affect the performance of your game
【3】加“\t”可能有对齐效果

--  android textview自动换行、排列错乱问题及解决
半角字符与全角字符混乱所致!即将所有的数字、字母及标点全部转为全角字符,使它们与汉字同占两个字节,这样就可以避免由于占位导致的排版混乱问题了。
/** 
     * 半角转换为全角 
     *  
     * @param input 
     * @return 
     */  
    public static String ToDBC(String input) {  
        char[] c = input.toCharArray();  
        for (int i = 0; i < c.length; i++) {  
            if (c[i] == 12288) {  
                c[i] = (char) 32;  
                continue;  
            }  
            if (c[i] > 65280 && c[i] < 65375)  
                c[i] = (char) (c[i] - 65248);  
        }  
        return new String(c);  
    }  
/** 
     * 去除特殊字符或将所有中文标号替换为英文标号 
     *  
     * @param str 
     * @return 
     */  
    public static String stringFilter(String str) {  
        str = str.replaceAll("【", "[").replaceAll("】", "]")  
                .replaceAll("!", "!").replaceAll(":", ":");// 替换中文标号  
        String regEx = "[『』]"; // 清除掉特殊字符  
        Pattern p = Pattern.compile(regEx);  
        Matcher m = p.matcher(str);  
        return m.replaceAll("").trim();  
    } 

> webview中,可以对文本内容进行对齐
Android阅读器如何实现文字两端对齐,达到类似iReader一样的效果??
【1】在android中的webview中,可以对文本内容进行对齐,具体方法如下:
public class MainActivity extends Activity {
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main); 
 
    String htmlText = " %s ";  
    String myData = "Hello World! This tutorial is to show demo of displaying text with justify alignment in WebView.";  
    WebView webView = (WebView) findViewById(R.id.webView1);  
    webView.loadData(String.format(htmlText, myData), "text/html", "utf-8");  
}  }
【2】方案二:使用textView改造:首先设置TextView的显示字体大小和文本内容,这里设置字体大小根据屏幕尺寸调整。然后调用自定义的类Textustification中的justify方法来实现TextView的分散对齐,两个参数分别是TextView控件以及控件的宽度。
  MainActivity中:
 Display display = getWindowManager().getDefaultDisplay();
 DisplayMetrics dm = new DisplayMetrics();
 display.getMetrics(dm);
 width = dm.widthPixels;
  //根据屏幕调整文字大小
 mArticleTextView.setLineSpacing(0f, 1.5f);
 mArticleTextView.setTextSize(8*(float)width/320f);
  //设置TextView
 mArticleTextView.setText("TextView需要显示的文本内容");
 TextJustification.justify(mArticleTextView,mArticleTextView.getWidth());

-- 自定义的类TextJustification内容如下:
import java.util.ArrayList;
import android.graphics.Paint;
import android.text.TextUtils;
import android.widget.TextView;
import android.widget.TextView.BufferType;
public class TextJustification {
public static void justify(TextView textView, float contentWidth) {
    String text=textView.getText().toString();
    String tempText;
    String resultText = "";
    Paint paint=textView.getPaint();

    ArrayList<String> paraList = new ArrayList<String>();       
    paraList = paraBreak(text);       
    for(int i = 0; i<paraList.size(); i++) {           
        ArrayList<String> lineList=lineBreak(paraList.get(i).trim(),paint,contentWidth);           
        tempText = TextUtils.join(" ", lineList).replaceFirst("\\s*", "");           
        resultText += tempText.replaceFirst("\\s*", "") + "\n";       
    }               

    textView.setText(resultText);
}
//分开每个段落
public static ArrayList<String> paraBreak(String text, TextView textview) {
    ArrayList<String> paraList = new ArrayList<String>();
    String[] paraArray = text.split("\\n+");
       for(String para:paraArray) {
           paraList.add(para);
    }
    return paraList;
}

//分开每一行,使每一行填入最多的单词数
private static ArrayList<String> lineBreak(String text, Paint paint, float contentWidth){
    String [] wordArray=text.split("\\s");
    ArrayList<String> lineList = new ArrayList<String>();
    String myText="";

    for(String word:wordArray){
        if(paint.measureText(myText+" "+word)<=contentWidth)
            myText=myText+" "+word;
        else{
            int totalSpacesToInsert=(int)((contentWidth-paint.measureText(myText))/paint.measureText(" "));
            lineList.add(justifyLine(myText,totalSpacesToInsert));
            myText=word;
        }
    }
    lineList.add(myText);
    return lineList;
}
//已填入最多单词数的一行,插入对应的空格数直到该行满
private static String justifyLine(String text,int totalSpacesToInsert){
    String[] wordArray=text.split("\\s");
    String toAppend=" ";


    while((totalSpacesToInsert)>=(wordArray.length-1)){
        toAppend=toAppend+" ";
        totalSpacesToInsert=totalSpacesToInsert-(wordArray.length-1);
    }
    int i=0;
    String justifiedText="";
    for(String word:wordArray){
        if(i<totalSpacesToInsert)
            justifiedText=justifiedText+word+" "+toAppend;
        else               
            justifiedText=justifiedText+word+toAppend;
        i++;
    }

    return justifiedText;
}
}

 以上这个类完成了TextView内部文字的排版工作,主要分3个步骤:
1、将一篇文章按段落分成若干段(如果只有一段可以略去该步骤);
2、将每一段的文字拆分成各个单词,然后根据控件长度确定每一行最多可以填入的单词数,并且算出排满该行还需要填入几个空格。
3、填入空格。

> TextView头部和尾部缩进、分散对齐,webView加载页面也是一种方案
解决Android中TextView首行缩进的问题- http://blog.csdn.net/zjy_hll/article/details/45077855
TextView改变部分字体的大小和颜色及首行缩进- http://blog.csdn.net/luohai859/article/details/46755617

  首行缩进:
方式一:(推荐)setText("\u3000\u3000"+xxxxx);
方式二:这种方式不同分辨率会有问题 setText(""+xxxxx); 半角:\u0020;全角:\u3000

android TextView 分散对齐(两端对齐) http://www.tuicool.com/articles/6b6nUbY
AlignTextView- https://github.com/androiddevelop/AlignTextView http://www.cnblogs.com/lcyty/p/3265335.html
两端分散对齐的Textview- http://blog.csdn.net/razor1991/article/details/51459678

> android里TextView加下划线的几种方式
如果是在资源文件里:
 <resources>
    <string name="hello"><u>phone:0123456</u></string>
    <string name="app_name">MyLink</string>
</resources>
如果是代码里:
TextView textView = (TextView)findViewById(R.id.tv_test); 
textView.setText(Html.fromHtml("<u>"+"0123456"+"</u>"));

代码也可以这样:Android TextView 添加下划线的几种方式- http://www.cnblogs.com/popfisher/p/5191242.html
tvTest.getPaint().setFlags(Paint. UNDERLINE_TEXT_FLAG ); //下划线
tvTest.getPaint().setAntiAlias(true);//抗锯齿

 Android TextView 添加下划线的几种方式- http://www.cnblogs.com/popfisher/p/5191242.html
android:autoLink="all"

> TextView的跑马灯效果
TextView的跑马灯效果以及TextView的一些属性- http://blog.csdn.net/dl10210950/article/details/52416307
 android中TextView跑马灯效果-http://blog.csdn.net/oldmtn/article/details/8835700
  <TextView  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"    
    android:id="@+id/notice"    
    android:background="@color/black20"     
    android:text="他们说这里是要放广告的,就是看看能不能动啊!长点...!!!!..."    
    android:ellipsize="marquee"    
    android:gravity="center"    
    android:textStyle="bold"    
    android:focusable="true"    
    android:marqueeRepeatLimit="marquee_forever"    
    android:focusableInTouchMode="true"    
    android:scrollHorizontally="true"    
    android:singleLine="true"  
    android:textSize="@dimen/activity_Textsize15_4.0"    
  />

> TextView中Text与pic设置间距
android:drawablePadding="5dip"

> Android Studio中xml文件中的TextView的text中字符串属性默认大写- https://blog.csdn.net/liujunpen/article/details/53406119
<item name="android:textAllCaps">false</item>, 
android:textAllCaps="false"

> textview动态设置 DrableLeft 以及改变图片大小
if (i_count==0){
            Drawable drawable1= getResources().getDrawable(R.drawable.ic_news_comment_write);
/// 这一步必须要做,否则不会显示.
            drawable1.setBounds(0, 0, 30, 30);
            tvComment.setCompoundDrawables(drawable1,null,null,null);
//            tvComment.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_news_comment_write,0,0,0);
            tvComment.setText("写评论");
        }else {
            Drawable drawable2= getResources().getDrawable(R.drawable.ic_comment_top);
/// 这一步必须要做,否则不会显示.
            drawable2.setBounds(0, 0, drawable2.getMinimumWidth(), drawable2.getMinimumHeight());
            tvComment.setCompoundDrawables(drawable2,null,null,null);
            tvComment.setText(" "+count + " 评论");
        }
        tvComment.setVisibility(isLoaded ? View.VISIBLE : View.GONE);
    }
控制图片大小    
drawable1.setBounds(0, 0, 30, 30);   控制图片大小   第一0是距左边距离,第二0是距上边距离,30分别是长宽

> Android Y轴TextView旋转动画Animation- http://blog.csdn.net/true_maitian/article/details/54969600
让TextView里面的文字逐个显示的动画效果实现(1)- http://www.cnblogs.com/laishenghao/p/5134811.html
imageCaptionTv.setText("Text Here");
imageCaption.post(new Runnable() {
    @Override
    public void run() {
        int lineCount    = imageCaptionTv.getLineCount();
    }
});

> Android自定义TextView边框颜色(动态改变边框颜色以及字体颜色)- https://blog.csdn.net/lplj717/article/details/52776524
TextView.setEnabled(false);//设置不可点击

>  TextView上显示未读消息

<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="18.0dip"
        android:baselineAligned="false"
        android:orientation="horizontal" >

        <RelativeLayout
            android:id="@+id/common_forth_process_layout"
            android:layout_width="0dip"
            android:layout_height="70.0dip"
            android:layout_weight="1.0" >

            <LinearLayout
                android:layout_width="60.0dip"
                android:layout_height="60.0dip"
                android:layout_centerHorizontal="true"
                android:background="@drawable/common_apply_leave"
                android:gravity="center"
                android:orientation="horizontal" >

                <TextView
                    android:id="@+id/common_forth_process"
                    style="@style/common_textSize_16"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:contentDescription="@null"
                    android:ems="2"
                    android:text="@string/common_apply_leave_text"
                    android:textColor="@color/white_color" />
            </LinearLayout>

            <TextView
                android:id="@+id/common_forth_process_num"
                style="@style/common_textSize_14"
                android:layout_width="22.0dip"
                android:layout_height="22.0dip"
                android:layout_alignParentRight="true"
                android:layout_alignParentTop="true"
                android:layout_marginRight="1.0dip"
                android:background="@drawable/message_num_backgroud"
                android:gravity="center"
                android:textColor="@color/white_color"
                android:visibility="gone" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/common_fifth_process_layout"
            android:layout_width="0dip"
            android:layout_height="70.0dip"
            android:layout_weight="1.0"
            android:visibility="invisible" >

            <TextView
                android:id="@+id/common_fifth_process"
                style="@style/common_textSize_16"
                android:layout_width="60.0dip"
                android:layout_height="60.0dip"
                android:layout_centerHorizontal="true"
                android:background="@drawable/common_doing_process"
                android:gravity="center"
                android:text="@string/matter_doing_label"
                android:textColor="@color/white_color"
                android:visibility="gone" />

            <TextView
                android:id="@+id/common_fifth_process_num"
                style="@style/common_textSize_14"
                android:layout_width="22.0dip"
                android:layout_height="22.0dip"
                android:layout_alignParentRight="true"
                android:layout_alignParentTop="true"
                android:layout_marginRight="12.0dip"
                android:background="@drawable/message_num_backgroud"
                android:gravity="center"
                android:textColor="@color/white_color"
                android:visibility="gone" />
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/common_sixth_process_layout"
            android:layout_width="0dip"
            android:layout_height="70.0dip"
            android:layout_weight="1.0"
            android:visibility="invisible" >

            <TextView
                android:id="@+id/common_sixth_process"
                style="@style/common_textSize_16"
                android:layout_width="60.0dip"
                android:layout_height="60.0dip"
                android:layout_centerHorizontal="true"
                android:background="@drawable/common_done_process"
                android:gravity="center"
                android:text="@string/matter_done_label"
                android:textColor="@color/white_color"
                android:visibility="gone" />

            <TextView
                android:id="@+id/common_sixth_process_num"
                style="@style/common_textSize_14"
                android:layout_width="22.0dip"
                android:layout_height="22.0dip"
                android:layout_alignParentRight="true"
                android:layout_alignParentTop="true"
                android:layout_marginRight="12.0dip"
                android:background="@drawable/message_num_backgroud"
                android:gravity="center"
                android:textColor="@color/white_color"
                android:visibility="gone" />
        </RelativeLayout>

    </LinearLayout>

未读Msg+下面的title
 <LinearLayout
            android:id="@+id/common_first_process_layout"
            android:layout_width="0dip"
            android:layout_height="90.0dip"
            android:layout_weight="1.0"
            android:gravity="center"
            android:orientation="vertical" >
            <RelativeLayout
                android:id="@+id/first_common"
                android:layout_width="70dip"
                android:layout_height="70.0dip" >

                <TextView
                    android:id="@+id/common_first_process"
                    style="@style/common_textSize_16"
                    android:layout_width="60.0dip"
                    android:layout_height="60.0dip"
                    android:layout_centerHorizontal="true"
                    android:background="@drawable/common_todo_process"
                    android:contentDescription="@null"
                    android:gravity="center" />

                <TextView
                    android:id="@+id/common_first_process_num"
                    style="@style/common_textSize_14"
                    android:layout_width="22.0dip"
                    android:layout_height="22.0dip"
                    android:layout_alignParentRight="true"
                    android:layout_alignParentTop="true"
                    android:layout_marginRight="1.0dip"
                    android:background="@drawable/message_num_backgroud"
                    android:gravity="center"
                    android:textColor="@color/white_color"
                    android:visibility="gone" />
            </RelativeLayout>

            <TextView
                android:id="@+id/first_text"
                style="@style/common_textSize_color_13"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/matter_todo_label" />
</LinearLayout>

仿小红书自定义展开/收起的TextView- https://gitee.com/mrtrying/MrTryingUImaster/blob/master/wdiget_lib/src/main/java/com/mrtrying/widget/ExpandableTextView.java
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatTextView;
import android.text.Layout;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.style.AlignmentSpan;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;

/**
 * Description :
 * PackageName : com.mrtrying.widget
 * Created by mrtrying on 2019/4/17 17:21.
 * e_mail : ztanzeyu@gmail.com
 */
public class ExpandableTextView extends AppCompatTextView {
    public static final String TAG = ExpandableTextView.class.getSimpleName();

    private static final int DEFAULT_MAX_LINE = 3;
    private static final String DEFAULT_OPEN_SUFFIX = " 展开";
    private static final String DEFAULT_CLOSE_SUFFIX = " 收起";
    public static final String ELLIPSIS_STRING = new String(new char[]{'\u2026'});

    private int mMaxLines = DEFAULT_MAX_LINE;

    /** TextView可展示宽度,包含paddingLeft和paddingRight */
    private int initWidth = 0;

    /** 原始文本 */
    private CharSequence originalText;

    private SpannableStringBuilder mOpenSpannableStr, mCloseSpannableStr;

    private boolean hasAnimation = false;
    private Animation mOpenAnim, mCloseAnim;
    private int mOpenHeight, mCLoseHeight;
    volatile boolean animating = false;
    boolean isClosed = false;
    private boolean mExpandable;
    private boolean mCloseInNewLine;
    @Nullable
    private SpannableString mOpenSuffixSpan, mCloseSuffixSpan;
    private String mOpenSuffixStr = DEFAULT_OPEN_SUFFIX;
    private String mCloseSuffixStr = DEFAULT_CLOSE_SUFFIX;
    private int mOpenSuffixColor, mCloseSuffixColor;

    private OnClickListener mOnClickListener;

    private CharSequenceToSpannableHandler mCharSequenceToSpannableHandler;

    public ExpandableTextView(Context context) {
        super(context);
        initialize();
    }

    public ExpandableTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize();
    }

    public ExpandableTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initialize();
    }

    /** 初始化 */
    private void initialize() {
        mOpenSuffixColor = mCloseSuffixColor = Color.parseColor("#F23030");
        updateOpenSuffixSpan();
        updateCloseSuffixSpan();
    }

    public void setOriginalText(CharSequence originalText) {
        this.originalText = originalText;

        final int maxLines = mMaxLines;
        SpannableStringBuilder tempText = charSequenceToSpannable(originalText);
        mOpenSpannableStr = charSequenceToSpannable(originalText);

        if (maxLines != -1) {
            Layout layout = createStaticLayout(tempText);
            mExpandable = layout.getLineCount() > maxLines;
            if (mExpandable) {
                //拼接展开内容
                if (mCloseInNewLine) {
                    mOpenSpannableStr.append("\n");
                }
                if (mCloseSuffixSpan != null) {
                    mOpenSpannableStr.append(mCloseSuffixSpan);
                }
                //计算原文截取位置
                int endPos = layout.getLineEnd(maxLines - 1);
                mCloseSpannableStr = charSequenceToSpannable(originalText.subSequence(0, endPos));
                SpannableStringBuilder tempText2 = charSequenceToSpannable(mCloseSpannableStr).append(ELLIPSIS_STRING);
                if (mOpenSuffixSpan != null) {
                    tempText2.append(tempText2);
                }
                //循环判断,收起内容添加展开后缀后的内容
                Layout tempLayout = createStaticLayout(tempText2);
                while (tempLayout.getLineCount() > maxLines) {
                    int lastSpace = mCloseSpannableStr.length() - 1;
                    if (lastSpace == -1) {
                        break;
                    }
                    mCloseSpannableStr = charSequenceToSpannable(originalText.subSequence(0, lastSpace));
                    tempText2 = charSequenceToSpannable(mCloseSpannableStr).append(ELLIPSIS_STRING);
                    if (mOpenSuffixSpan != null) {
                        tempText2.append(mOpenSuffixSpan);
                    }
                    tempLayout = createStaticLayout(tempText2);
                }
                //计算收起的文本高度
                mCLoseHeight = tempLayout.getHeight() + getPaddingTop() + getPaddingBottom();

                mCloseSpannableStr.append(ELLIPSIS_STRING);
                if (mOpenSuffixSpan != null) {
                    mCloseSpannableStr.append(mOpenSuffixSpan);
                }
            }
        }
        isClosed = mExpandable;
        if (mExpandable) {
            setText(mCloseSpannableStr);
            //设置监听
            super.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    isClosed = !isClosed;
                    if (isClosed) {
                        close();
                    } else {
                        open();
                    }

                    if (mOnClickListener != null) {
                        mOnClickListener.onClick(v);
                    }
                }
            });
        } else {
            setText(mOpenSpannableStr);
        }
    }

    /**
     * 设置是否有动画
     *
     * @param hasAnimation
     */
    public void setHasAnimation(boolean hasAnimation) {
        this.hasAnimation = hasAnimation;
    }

    /** 展开 */
    private void open() {
        if (hasAnimation) {
            Layout layout = createStaticLayout(mOpenSpannableStr);
            mOpenHeight = layout.getHeight() + getPaddingTop() + getPaddingBottom();
            executeOpenAnim();
        } else {
            ExpandableTextView.super.setMaxLines(Integer.MAX_VALUE);
            setText(mOpenSpannableStr);
        }
    }

    /** 收起 */
    private void close() {
        if (hasAnimation) {
            executeCloseAnim();
        } else {
            ExpandableTextView.super.setMaxLines(mMaxLines);
            setText(mCloseSpannableStr);
        }
    }

    /** 执行展开动画 */
    private void executeOpenAnim() {
        //创建展开动画
        if (mOpenAnim == null) {
            mOpenAnim = new ExpandCollapseAnimation(this, mCLoseHeight, mOpenHeight);
            mOpenAnim.setFillAfter(true);
            mOpenAnim.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                    ExpandableTextView.super.setMaxLines(Integer.MAX_VALUE);
                    setText(mOpenSpannableStr);
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    //  动画结束后textview设置展开的状态
                    getLayoutParams().height = mOpenHeight;
                    requestLayout();
                    animating = false;
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
        }

        if (animating) {
            return;
        }
        animating = true;
        clearAnimation();
        //  执行动画
        startAnimation(mOpenAnim);
    }

    /** 执行收起动画 */
    private void executeCloseAnim() {
        //创建收起动画
        if (mCloseAnim == null) {
            mCloseAnim = new ExpandCollapseAnimation(this, mOpenHeight, mCLoseHeight);
            mCloseAnim.setFillAfter(true);
            mCloseAnim.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {

                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    animating = false;
                    ExpandableTextView.super.setMaxLines(mMaxLines);
                    setText(mCloseSpannableStr);
                    getLayoutParams().height = mCLoseHeight;
                    requestLayout();
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
        }

        if (animating) {
            return;
        }
        animating = true;
        clearAnimation();
        //  执行动画
        startAnimation(mCloseAnim);
    }

    /**
     * @param spannable
     *
     * @return
     */
    private Layout createStaticLayout(SpannableStringBuilder spannable) {
        int contentWidth = initWidth - getPaddingLeft() - getPaddingRight();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return new StaticLayout(spannable, getPaint(), contentWidth, Layout.Alignment.ALIGN_NORMAL,
                    getLineSpacingMultiplier(), getLineSpacingExtra(), false);
        }else{
            return new StaticLayout(spannable, getPaint(), contentWidth, Layout.Alignment.ALIGN_NORMAL,
                    1f, 0.0f, false);
        }
    }

    /**
     * @param charSequence
     *
     * @return
     */
    private SpannableStringBuilder charSequenceToSpannable(@NonNull CharSequence charSequence) {
        SpannableStringBuilder spannableStringBuilder = null;
        if (mCharSequenceToSpannableHandler != null) {
            spannableStringBuilder = mCharSequenceToSpannableHandler.charSequenceToSpannable(charSequence);
        }
        if (spannableStringBuilder == null) {
            spannableStringBuilder = new SpannableStringBuilder(charSequence);
        }
        return spannableStringBuilder;
    }

    /**
     * 初始化TextView的可展示宽度
     *
     * @param width
     */
    public void initWidth(int width) {
        initWidth = width;
    }

    @Override
    public void setMaxLines(int maxLines) {
        this.mMaxLines = maxLines;
        super.setMaxLines(maxLines);
    }

    /**
     * 设置展开后缀text
     *
     * @param openSuffix
     */
    public void setOpenSuffix(String openSuffix) {
        mOpenSuffixStr = openSuffix;
        updateOpenSuffixSpan();
    }

    /**
     * 设置展开后缀文本颜色
     *
     * @param openSuffixColor
     */
    public void setOpenSuffixColor(@ColorInt int openSuffixColor) {
        mOpenSuffixColor = openSuffixColor;
        updateOpenSuffixSpan();
    }

    /**
     * 设置收起后缀text
     *
     * @param closeSuffix
     */
    public void setCloseSuffix(String closeSuffix) {
        mCloseSuffixStr = closeSuffix;
        updateCloseSuffixSpan();
    }

    /**
     * 设置收起后缀文本颜色
     *
     * @param closeSuffixColor
     */
    public void setCloseSuffixColor(@ColorInt int closeSuffixColor) {
        mCloseSuffixColor = closeSuffixColor;
        updateCloseSuffixSpan();
    }

    /**
     * 收起后缀是否另起一行
     *
     * @param closeInNewLine
     */
    public void setCloseInNewLine(boolean closeInNewLine) {
        mCloseInNewLine = closeInNewLine;
        updateCloseSuffixSpan();
    }

    /** 更新展开后缀Spannable */
    private void updateOpenSuffixSpan() {
        if (TextUtils.isEmpty(mOpenSuffixStr)) {
            mOpenSuffixSpan = null;
            return;
        }
        mOpenSuffixSpan = new SpannableString(mOpenSuffixStr);
        mOpenSuffixSpan.setSpan(new ForegroundColorSpan(mOpenSuffixColor), 0, mOpenSuffixStr.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
    }

    /** 更新收起后缀Spannable */
    private void updateCloseSuffixSpan() {
        if (TextUtils.isEmpty(mCloseSuffixStr)) {
            mCloseSuffixSpan = null;
            return;
        }
        mCloseSuffixSpan = new SpannableString(mCloseSuffixStr);
        mCloseSuffixSpan.setSpan(new ForegroundColorSpan(mCloseSuffixColor), 0, mCloseSuffixStr.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        if (mCloseInNewLine) {
            AlignmentSpan alignmentSpan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE);
            mCloseSuffixSpan.setSpan(alignmentSpan, 0, mCloseSuffixStr.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        }
    }

    @Override
    public void setOnClickListener(OnClickListener onClickListener) {
        mOnClickListener = onClickListener;
    }

    /**
     * 设置文本内容处理
     *
     * @param charSequenceToSpannableHandler
     */
    public void setCharSequenceToSpannableHandler(CharSequenceToSpannableHandler charSequenceToSpannableHandler) {
        mCharSequenceToSpannableHandler = charSequenceToSpannableHandler;
    }

    public interface CharSequenceToSpannableHandler {
        @NonNull
        SpannableStringBuilder charSequenceToSpannable(CharSequence charSequence);
    }

    class ExpandCollapseAnimation extends Animation {
        private final View mTargetView;//动画执行view
        private final int mStartHeight;//动画执行的开始高度
        private final int mEndHeight;//动画结束后的高度

        ExpandCollapseAnimation(View target, int startHeight, int endHeight) {
            mTargetView = target;
            mStartHeight = startHeight;
            mEndHeight = endHeight;
            setDuration(400);
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            //计算出每次应该显示的高度,改变执行view的高度,实现动画
            mTargetView.getLayoutParams().height = (int) ((mEndHeight - mStartHeight) * interpolatedTime + mStartHeight);
            mTargetView.requestLayout();
        }
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值