点击可展开的TextView


自定义TextView

  1 package com.sun.mytext;
  2 
  3 import android.annotation.SuppressLint;
  4 import android.content.Context;
  5 import android.os.Build;
  6 import android.text.Layout;
  7 import android.text.SpannableString;
  8 import android.text.Spanned;
  9 import android.text.StaticLayout;
 10 import android.text.method.LinkMovementMethod;
 11 import android.util.AttributeSet;
 12 import android.view.View;
 13 import android.widget.TextView;
 14 
 15 /**
 16  * @author Sigal
 17  * on 2018/12/18
 18  * note: 可展开的TextView
 19  */
 20 @SuppressLint("AppCompatCustomView")
 21 public class ExpandTextView extends TextView {
 22     // 原始内容文本
 23     private String originText;
 24     // TextView可展示宽度
 25     private int initWidth = 0;
 26     // TextView最大行数
 27     private int mMaxLines = 3;
 28     // 收起的文案(颜色处理)
 29     private SpannableString SPAN_CLOSE = null;
 30     // 展开的文案(颜色处理)
 31     private SpannableString SPAN_EXPAND = null;
 32     private String TEXT_EXPAND = "  查看更多>";
 33     private String TEXT_CLOSE = "  <收起";
 34 
 35     public ExpandTextView(Context context) {
 36         super(context);
 37         initCloseEnd();
 38     }
 39 
 40     public ExpandTextView(Context context, AttributeSet attrs) {
 41         super(context, attrs);
 42         initCloseEnd();
 43     }
 44 
 45     public ExpandTextView(Context context, AttributeSet attrs, int defStyleAttr) {
 46         super(context, attrs, defStyleAttr);
 47         initCloseEnd();
 48     }
 49 
 50     /**
 51      * 设置TextView可显示的最大行数
 52      * @param maxLines 最大行数
 53      */
 54     @Override
 55     public void setMaxLines(int maxLines) {
 56         this.mMaxLines = maxLines;
 57         super.setMaxLines(maxLines);
 58     }
 59 
 60     /**
 61      * 初始化TextView的可展示宽度
 62      * @param width
 63      */
 64     public void initWidth(int width){
 65         initWidth = width;
 66     }
 67 
 68     /**
 69      * 收起的文案(颜色处理)初始化
 70      */
 71     private void initCloseEnd(){
 72         String content = TEXT_EXPAND;
 73         SPAN_CLOSE = new SpannableString(content);
 74         ButtonSpan span = new ButtonSpan(getContext(), new View.OnClickListener(){
 75             @Override
 76             public void onClick(View v) {
 77                 ExpandTextView.super.setMaxLines(Integer.MAX_VALUE);
 78                 setExpandText(originText);
 79             }
 80         }, R.color.colorAccent);
 81         SPAN_CLOSE.setSpan(span, 0, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
 82     }
 83 
 84     /**
 85      * 展开的文案(颜色处理)初始化
 86      */
 87     private void initExpandEnd(){
 88         String content = TEXT_CLOSE;
 89         SPAN_EXPAND = new SpannableString(content);
 90         ButtonSpan span = new ButtonSpan(getContext(), new View.OnClickListener(){
 91             @Override
 92             public void onClick(View v) {
 93                 ExpandTextView.super.setMaxLines(mMaxLines);
 94                 setCloseText(originText);
 95             }
 96         }, R.color.colorAccent);
 97         SPAN_EXPAND.setSpan(span, 0, content.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
 98     }
 99 
100     public void setCloseText(CharSequence text) {
101 
102         if(SPAN_CLOSE == null){
103             initCloseEnd();
104         }
105         boolean appendShowAll = false;// true 不需要展开收起功能, false 需要展开收起功能
106         originText = text.toString();
107 
108         // SDK >= 16 可以直接从xml属性获取最大行数
109         int maxLines = 0;
110         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
111             maxLines = getMaxLines();
112         } else{
113             maxLines = mMaxLines;
114         }
115         String workingText = new StringBuilder(originText).toString();
116         if (maxLines != -1) {
117             Layout layout = createWorkingLayout(workingText);
118             if (layout.getLineCount() > maxLines) {
119                 //获取一行显示字符个数,然后截取字符串数
120                 workingText = originText.substring(0, layout.getLineEnd(maxLines - 1)).trim();// 收起状态原始文本截取展示的部分
121                 String showText = originText.substring(0, layout.getLineEnd(maxLines - 1)).trim() + "..." + SPAN_CLOSE;
122                 Layout layout2 = createWorkingLayout(showText);
123                 // 对workingText进行-1截取,直到展示行数==最大行数,并且添加 SPAN_CLOSE 后刚好占满最后一行
124                 while (layout2.getLineCount() > maxLines) {
125                     int lastSpace = workingText.length()-1;
126                     if (lastSpace == -1) {
127                         break;
128                     }
129                     workingText = workingText.substring(0, lastSpace);
130                     layout2 = createWorkingLayout(workingText + "..." + SPAN_CLOSE);
131                 }
132                 appendShowAll = true;
133                 workingText = workingText + "...";
134             }
135         }
136 
137         setText(workingText);
138         if (appendShowAll) {
139             // 必须使用append,不能在上面使用+连接,否则spannable会无效
140             append(SPAN_CLOSE);
141             setMovementMethod(LinkMovementMethod.getInstance());
142         }
143     }
144 
145     public void setExpandText(String text) {
146         if(SPAN_EXPAND == null){
147             initExpandEnd();
148         }
149         Layout layout1 = createWorkingLayout(text);
150         Layout layout2 = createWorkingLayout(text + TEXT_CLOSE);
151         // 展示全部原始内容时 如果 TEXT_CLOSE 需要换行才能显示完整,则直接将TEXT_CLOSE展示在下一行
152         if(layout2.getLineCount() > layout1.getLineCount()){
153             setText(originText + "\n");
154         }else{
155             setText(originText);
156         }
157         append(SPAN_EXPAND);
158         setMovementMethod(LinkMovementMethod.getInstance());
159     }
160 
161     //返回textview的显示区域的layout,该textview的layout并不会显示出来,只是用其宽度来比较要显示的文字是否过长
162     private Layout createWorkingLayout(String workingText) {
163         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
164             return new StaticLayout(workingText, getPaint(), initWidth - getPaddingLeft() - getPaddingRight(),
165                     Layout.Alignment.ALIGN_NORMAL, getLineSpacingMultiplier(), getLineSpacingExtra(), false);
166         } else{
167             return new StaticLayout(workingText, getPaint(), initWidth - getPaddingLeft() - getPaddingRight(),
168                     Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
169         }
170     }
171 }

自定义ClickableSpan

 1 package com.sun.mytext;
 2 
 3 import android.content.Context;
 4 import android.text.TextPaint;
 5 import android.text.style.ClickableSpan;
 6 import android.view.View;
 7 
 8 /**
 9  * @author Sigal
10  * on 2018/12/18
11  * note:
12  */
13 public class ButtonSpan extends ClickableSpan {
14     View.OnClickListener onClickListener;
15     private Context context;
16     private int colorId;
17 
18     public ButtonSpan(Context context, View.OnClickListener onClickListener) {
19         this(context, onClickListener, R.color.textColor);
20     }
21 
22     public ButtonSpan(Context context, View.OnClickListener onClickListener, int colorId){
23         this.onClickListener = onClickListener;
24         this.context = context;
25         this.colorId = colorId;
26     }
27 
28     @Override
29     public void updateDrawState(TextPaint ds) {
30         ds.setColor(context.getResources().getColor(colorId));
31         ds.setUnderlineText(false);
32     }
33 
34     @Override
35     public void onClick(View widget) {
36         if (onClickListener != null) {
37             onClickListener.onClick(widget);
38         }
39     }
40 
41 }

PxUtils 和 ScreenUtils

 1 package com.sun.mytext;
 2 
 3 import android.content.Context;
 4 
 5 /**
 6  * @author Sigal
 7  * on 2018/12/18
 8  * note: px与dp、sp的转换
 9  */
10 public class PxUtils {
11     /**
12      * 将px转换为与之相等的dp
13      */
14     public static int px2dp(Context context, float pxValue) {
15         final float scale =  context.getResources().getDisplayMetrics().density;
16         return (int) (pxValue / scale + 0.5f);
17     }
18 
19     /**
20      * 将dp转换为与之相等的px
21      */
22     public static int dp2px(Context context, float dipValue) {
23         final float scale = context.getResources().getDisplayMetrics().density;
24         return (int) (dipValue * scale + 0.5f);
25     }
26 
27     /**
28      * 将px转换为sp
29      */
30     public static int px2sp(Context context, float pxValue) {
31         final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
32         return (int) (pxValue / fontScale + 0.5f);
33     }
34 
35     /**
36      * 将sp转换为px
37      */
38     public static int sp2px(Context context, float spValue) {
39         final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
40         return (int) (spValue * fontScale + 0.5f);
41     }
42 
43 }
  1 package com.sun.mytext;
  2 
  3 import android.app.Activity;
  4 import android.content.Context;
  5 import android.graphics.Bitmap;
  6 import android.graphics.Rect;
  7 import android.util.DisplayMetrics;
  8 import android.view.View;
  9 import android.view.WindowManager;
 10 
 11 /**
 12  * @author Sigal
 13  * on 2018/12/18
 14  * note:
 15  */
 16 public class ScreenUtils {
 17     private ScreenUtils()
 18     {
 19         /* cannot be instantiated */
 20         throw new UnsupportedOperationException("cannot be instantiated");
 21     }
 22 
 23     /**
 24      * 获得屏幕高度
 25      * @param context
 26      * @return
 27      */
 28     public static int getScreenWidth(Context context)
 29     {
 30         WindowManager wm = (WindowManager) context
 31                 .getSystemService(Context.WINDOW_SERVICE);
 32         DisplayMetrics outMetrics = new DisplayMetrics();
 33         wm.getDefaultDisplay().getMetrics(outMetrics);
 34         return outMetrics.widthPixels;
 35     }
 36 
 37     /**
 38      * 获得屏幕宽度
 39      * @param context
 40      * @return
 41      */
 42     public static int getScreenHeight(Context context)
 43     {
 44         WindowManager wm = (WindowManager) context
 45                 .getSystemService(Context.WINDOW_SERVICE);
 46         DisplayMetrics outMetrics = new DisplayMetrics();
 47         wm.getDefaultDisplay().getMetrics(outMetrics);
 48         return outMetrics.heightPixels;
 49     }
 50 
 51     /**
 52      * 获得状态栏的高度
 53      * @param context
 54      * @return
 55      */
 56     public static int getStatusHeight(Context context)
 57     {
 58 
 59         int statusHeight = -1;
 60         try
 61         {
 62             Class<?> clazz = Class.forName("com.android.internal.R$dimen");
 63             Object object = clazz.newInstance();
 64             int height = Integer.parseInt(clazz.getField("status_bar_height")
 65                     .get(object).toString());
 66             statusHeight = context.getResources().getDimensionPixelSize(height);
 67         } catch (Exception e)
 68         {
 69             e.printStackTrace();
 70         }
 71         return statusHeight;
 72     }
 73 
 74     /**
 75      * 获取当前屏幕截图,包含状态栏
 76      * @param activity
 77      * @return
 78      */
 79     public static Bitmap snapShotWithStatusBar(Activity activity)
 80     {
 81         View view = activity.getWindow().getDecorView();
 82         view.setDrawingCacheEnabled(true);
 83         view.buildDrawingCache();
 84         Bitmap bmp = view.getDrawingCache();
 85         int width = getScreenWidth(activity);
 86         int height = getScreenHeight(activity);
 87         Bitmap bp = null;
 88         bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
 89         view.destroyDrawingCache();
 90         return bp;
 91 
 92     }
 93 
 94     /**
 95      * 获取当前屏幕截图,不包含状态栏
 96      * @param activity
 97      * @return
 98      */
 99     public static Bitmap snapShotWithoutStatusBar(Activity activity) {
100         View view = activity.getWindow().getDecorView();
101         view.setDrawingCacheEnabled(true);
102         view.buildDrawingCache();
103         Bitmap bmp = view.getDrawingCache();
104         Rect frame = new Rect();
105         activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
106         int statusBarHeight = frame.top;
107 
108         int width = getScreenWidth(activity);
109         int height = getScreenHeight(activity);
110         Bitmap bp = null;
111         bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
112                 - statusBarHeight);
113         view.destroyDrawingCache();
114         return bp;
115     }
116 }

代码引用

 1 package com.sun.mytext;
 2 
 3 import android.app.Activity;
 4 import android.support.v7.app.AppCompatActivity;
 5 import android.os.Bundle;
 6 /**
 7  * @author Sigal
 8  * on 2018/12/18
 9  * note:
10  */
11 public class MainActivity extends Activity {
12 
13     @Override
14     protected void onCreate(Bundle savedInstanceState) {
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.activity_main);
17         // 设置TextView可展示的宽度 ( 父控件宽度 - 左右margin - 左右padding)
18         int whidth = ScreenUtils.getScreenWidth(this) - PxUtils.dp2px(this, 16 * 2);
19         ExpandTextView mContentExpandTextView = findViewById(R.id.etv);
20         mContentExpandTextView.initWidth(whidth);
21         // 设置最大行数(如果SDK >= 16 也可以直接在xml里设置)
22         mContentExpandTextView.setMaxLines(3);
23         String content = "披绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩。闾阎扑地,钟鸣鼎食之家;" +
24                 "舸舰弥津,青雀黄龙之舳。云销雨霁,彩彻区明。落霞与孤鹜齐飞,秋水共长天一色。渔舟唱晚,响穷彭蠡之滨;雁阵惊寒,声断衡阳之浦。\n" +
25                 "\n" +
26                 "遥襟甫畅,逸兴遄飞。爽籁发而清风生,纤歌凝而白云遏。睢园绿竹,气凌彭泽之樽;" +
27                 "邺水朱华,光照临川之笔。四美具,二难并。穷睇眄于中天,极娱游于暇日。天高地迥," +
28                 "觉宇宙之无穷;兴尽悲来,识盈虚之有数。望长安于日下,目吴会于云间。地势极而南溟深,天柱高而北辰远。关山难越," +
29                 "谁悲失路之人?萍水相逢,尽是他乡之客。怀帝阍而不见,奉宣室以何年?\n" +
30                 "\n" +
31                 "嗟乎!时运不齐,命途多舛。冯唐易老,李广难封。屈贾谊于长沙,非无圣主;窜梁鸿于海曲,岂乏明时?所赖君子见机,达人知命。" +
32                 "老当益壮,宁移白首之心?穷且益坚,不坠青云之志。酌贪泉而觉爽,处涸辙以犹欢。北海虽赊,扶摇可接;东隅已逝,桑榆非晚。" +
33                 "孟尝高洁,空余报国之情;阮籍猖狂,岂效穷途之哭!";
34         mContentExpandTextView.setCloseText(content);
35     }
36 }

布局文件

 1 <?xml version="1.0" encoding="utf-8"?>
 2  3     xmlns:app="http://schemas.android.com/apk/res-auto"
 4     xmlns:tools="http://schemas.android.com/tools"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     tools:context=".MainActivity">
 8 
 9     <com.sun.mytext.ExpandTextView
10         android:id="@+id/etv"
11         android:layout_width="wrap_content"
12         android:layout_height="wrap_content" />
13 
14 

相关