点击可展开的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"?> 23 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