当前位置:   article > 正文

android TextView 分散对齐(两端对齐)_textview 对齐方式

textview 对齐方式
  1. import android.content.ClipboardManager;
  2. import android.content.Context;
  3. import android.content.res.TypedArray;
  4. import android.graphics.Paint;
  5. import android.os.Build;
  6. import android.text.TextUtils;
  7. import android.util.AttributeSet;
  8. import android.util.Log;
  9. import android.widget.TextView;
  10. import java.lang.reflect.Method;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. /**
  14. * 对齐的TextView
  15. * <p>
  16. * 为了能够使TextView能够很好的进行排版,同时考虑到原生TextView中以word进行分割排版,
  17. * 那么我们可以将要换行的地方进行添加空格处理,这样就可以在合适的位置换行,同时也不会
  18. * 打乱原生的TextView的排版换行选择复制等问题。为了能够使右端尽可能的对齐,将右侧多出的空隙
  19. * 尽可能的分配到该行的标点后面。达到两段对齐的效果。
  20. * </p>
  21. * <p>
  22. * 重新设置文本前,请调用reset()进行状态重置。
  23. * </p>
  24. * Created by Rivers on 6/28/18.
  25. */
  26. public class AlignTextView extends TextView {
  27. private final static String TAG = AlignTextView.class.getSimpleName();
  28. private final static char SPACE = ' '; //空格;
  29. private List<Integer> addCharPosition = new ArrayList<Integer>(); //增加空格的位置
  30. private static List<Character> punctuation = new ArrayList<Character>(); //标点符号
  31. private CharSequence oldText = ""; //旧文本,本来应该显示的文本
  32. private CharSequence newText = ""; //新文本,真正显示的文本
  33. private boolean inProcess = false; //旧文本是否已经处理为新文本
  34. private boolean isAddPadding = false; //是否添加过边距
  35. private boolean isConvert = false; //是否转换标点符号
  36. //标点符号用于在textview右侧多出空间时,将空间加到标点符号的后面,以便于右端对齐
  37. static {
  38. punctuation.clear();
  39. punctuation.add(',');
  40. punctuation.add('.');
  41. punctuation.add('?');
  42. punctuation.add('!');
  43. punctuation.add(';');
  44. punctuation.add(',');
  45. punctuation.add('。');
  46. punctuation.add('?');
  47. punctuation.add('!');
  48. punctuation.add(';');
  49. punctuation.add(')');
  50. punctuation.add('】');
  51. punctuation.add(')');
  52. punctuation.add(']');
  53. punctuation.add('}');
  54. }
  55. public AlignTextView(Context context) {
  56. super(context);
  57. }
  58. public AlignTextView(Context context, AttributeSet attrs) {
  59. super(context, attrs);
  60. TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.AlignTextView);
  61. isConvert = ta.getBoolean(R.styleable.AlignTextView_punctuationConvert, false);
  62. ta.recycle();
  63. //判断使用xml中是用android:text
  64. TypedArray tsa = context.obtainStyledAttributes(attrs, new int[]{
  65. android.R.attr.text
  66. });
  67. String text = tsa.getString(0);
  68. tsa.recycle();
  69. if (!TextUtils.isEmpty(text)) {
  70. setText(text);
  71. }
  72. }
  73. /**
  74. * 监听文本复制,对于复制的文本进行空格剔除
  75. *
  76. * @param id 操作id(复制,全部选择等)
  77. * @return 是否操作成功
  78. */
  79. @Override
  80. public boolean onTextContextMenuItem(int id) {
  81. if (id == android.R.id.copy) {
  82. if (isFocused()) {
  83. final int selStart = getSelectionStart();
  84. final int selEnd = getSelectionEnd();
  85. int min = Math.max(0, Math.min(selStart, selEnd));
  86. int max = Math.max(0, Math.max(selStart, selEnd));
  87. //利用反射获取选择的文本信息,同时关闭操作框
  88. try {
  89. Class cls = getClass().getSuperclass();
  90. Method getSelectTextMethod = cls.getDeclaredMethod("getTransformedText", new
  91. Class[]{int.class, int.class});
  92. getSelectTextMethod.setAccessible(true);
  93. CharSequence selectedText = (CharSequence) getSelectTextMethod.invoke(this,
  94. min, max);
  95. copy(selectedText.toString());
  96. Method closeMenuMethod;
  97. if (Build.VERSION.SDK_INT < 23) {
  98. closeMenuMethod = cls.getDeclaredMethod("stopSelectionActionMode");
  99. } else {
  100. closeMenuMethod = cls.getDeclaredMethod("stopTextActionMode");
  101. }
  102. closeMenuMethod.setAccessible(true);
  103. closeMenuMethod.invoke(this);
  104. } catch (Exception e) {
  105. e.printStackTrace();
  106. }
  107. }
  108. return true;
  109. } else {
  110. return super.onTextContextMenuItem(id);
  111. }
  112. }
  113. /**
  114. * 复制文本到剪切板,去除添加字符
  115. *
  116. * @param text 文本
  117. */
  118. private void copy(String text) {
  119. ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context
  120. .CLIPBOARD_SERVICE);
  121. int start = newText.toString().indexOf(text);
  122. int end = start + text.length();
  123. StringBuilder sb = new StringBuilder(text);
  124. for (int i = addCharPosition.size() - 1; i >= 0; i--) {
  125. int position = addCharPosition.get(i);
  126. if (position < end && position >= start) {
  127. sb.deleteCharAt(position - start);
  128. }
  129. }
  130. try {
  131. android.content.ClipData clip = android.content.ClipData.newPlainText(null, sb.toString());
  132. clipboard.setPrimaryClip(clip);
  133. }catch (Exception e){
  134. Log.e(TAG, e.getMessage());
  135. }
  136. }
  137. /**
  138. * 重置状态
  139. */
  140. public void reset(){
  141. inProcess = false;
  142. addCharPosition.clear();
  143. newText = "";
  144. newText = "";
  145. }
  146. /**
  147. * 处理多行文本
  148. *
  149. * @param paint 画笔
  150. * @param text 文本
  151. * @param width 最大可用宽度
  152. * @return 处理后的文本
  153. */
  154. private String processText(Paint paint, String text, int width) {
  155. if (text == null || text.length() == 0) {
  156. return "";
  157. }
  158. String[] lines = text.split("\\n");
  159. StringBuilder newText = new StringBuilder();
  160. for (String line : lines) {
  161. newText.append('\n');
  162. newText.append(processLine(paint, line, width, newText.length() - 1));
  163. }
  164. if (newText.length() > 0) {
  165. newText.deleteCharAt(0);
  166. }
  167. return newText.toString();
  168. }
  169. /**
  170. * 处理单行文本
  171. *
  172. * @param paint 画笔
  173. * @param text 文本
  174. * @param width 最大可用宽度
  175. * @param addCharacterStartPosition 添加文本的起始位置
  176. * @return 处理后的文本
  177. */
  178. private String processLine(Paint paint, String text, int width, int addCharacterStartPosition) {
  179. if (text == null || text.length() == 0) {
  180. return "";
  181. }
  182. StringBuilder old = new StringBuilder(text);
  183. int startPosition = 0; // 起始位置
  184. float chineseWidth = paint.measureText("中");
  185. float spaceWidth = paint.measureText(SPACE + "");
  186. //最大可容纳的汉字,每一次从此位置向后推进计算
  187. int maxChineseCount = (int) (width / chineseWidth);
  188. //减少一个汉字宽度,保证每一行前后都有一个空格
  189. maxChineseCount--;
  190. //如果不能容纳汉字,直接返回空串
  191. if (maxChineseCount <= 0) {
  192. return "";
  193. }
  194. for (int i = maxChineseCount; i < old.length(); i++) {
  195. if (paint.measureText(old.substring(startPosition, i + 1)) > (width - spaceWidth)) {
  196. //右侧多余空隙宽度
  197. float gap = (width - spaceWidth - paint.measureText(old.substring(startPosition,
  198. i)));
  199. List<Integer> positions = new ArrayList<Integer>();
  200. for (int j = startPosition; j < i; j++) {
  201. char ch = old.charAt(j);
  202. if (punctuation.contains(ch)) {
  203. positions.add(j + 1);
  204. }
  205. }
  206. //空隙最多可以使用几个空格替换
  207. int number = (int) (gap / spaceWidth);
  208. //多增加的空格数量
  209. int use = 0;
  210. if (positions.size() > 0) {
  211. for (int k = 0; k < positions.size() && number > 0; k++) {
  212. int times = number / (positions.size() - k);
  213. int position = positions.get(k / positions.size());
  214. for (int m = 0; m < times; m++) {
  215. old.insert(position + m, SPACE);
  216. addCharPosition.add(position + m + addCharacterStartPosition);
  217. use++;
  218. number--;
  219. }
  220. }
  221. }
  222. //指针移动,将段尾添加空格进行分行处理
  223. i = i + use;
  224. old.insert(i, SPACE);
  225. addCharPosition.add(i + addCharacterStartPosition);
  226. startPosition = i + 1;
  227. i = i + maxChineseCount;
  228. }
  229. }
  230. return old.toString();
  231. }
  232. @Override
  233. public void setText(CharSequence text, BufferType type) {
  234. //父类初始化的时候子类暂时没有初始化, 覆盖方法会被执行,屏蔽掉
  235. if (addCharPosition == null) {
  236. super.setText(text, type);
  237. return;
  238. }
  239. if (!inProcess && (text != null && !text.equals(newText))) {
  240. oldText = text;
  241. process(false);
  242. super.setText(newText, type);
  243. } else {
  244. //恢复初始状态
  245. inProcess = false;
  246. super.setText(text, type);
  247. }
  248. }
  249. /**
  250. * 获取真正的text
  251. *
  252. * @return 返回text
  253. */
  254. public CharSequence getRealText() {
  255. return oldText;
  256. }
  257. /**
  258. * 文本转化
  259. *
  260. * @param setText 是否设置textView的文本
  261. */
  262. private void process(boolean setText) {
  263. if (oldText == null) {
  264. oldText = "";
  265. }
  266. if (!inProcess && getVisibility() == VISIBLE) {
  267. addCharPosition.clear();
  268. //转化字符,5.0系统对字体处理有所变动
  269. if (isConvert) {
  270. oldText = AlignTextViewUtil.replacePunctuation(oldText.toString());
  271. }
  272. if (getWidth() == 0) {
  273. //没有测量完毕,等待测量完毕后处理
  274. post(new Runnable() {
  275. @Override
  276. public void run() {
  277. process(true);
  278. }
  279. });
  280. return;
  281. }
  282. //添加过边距之后不再次添加
  283. if (!isAddPadding) {
  284. int spaceWidth = (int) (getPaint().measureText(SPACE + ""));
  285. newText = processText(getPaint(), oldText.toString(), getWidth() - getPaddingLeft
  286. () -
  287. getPaddingRight() - spaceWidth);
  288. setPadding(getPaddingLeft() + spaceWidth, getPaddingTop(), getPaddingRight(),
  289. getPaddingBottom());
  290. isAddPadding = true;
  291. } else {
  292. newText = processText(getPaint(), oldText.toString(), getWidth() - getPaddingLeft
  293. () -
  294. getPaddingRight());
  295. }
  296. inProcess = true;
  297. if (setText) {
  298. setText(newText);
  299. }
  300. }
  301. }
  302. /**
  303. * 是否转化标点符号,将中文标点转化为英文标点
  304. *
  305. * @param convert 是否转化
  306. */
  307. public void setPunctuationConvert(boolean convert) {
  308. isConvert = convert;
  309. }
  310. }
  1. /**
  2. * 文本工具
  3. */
  4. public class AlignTextViewUtil {
  5. /**
  6. * 将中文标点替换为英文标点
  7. *
  8. * @param text 要替换的文本
  9. * @return 替换后的文本
  10. */
  11. public static String replacePunctuation(String text) {
  12. text = text.replace(',', ',').replace('。', '.').replace('【', '[').replace('】', ']')
  13. .replace('?', '?').replace('!', '!').replace('(', '(').replace(')', ')').replace
  14. ('“', '"').replace('”', '"');
  15. return text;
  16. }
  17. }

res/values/aligntextview_attr.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <!-- 末行对齐方式 -->
  4. <declare-styleable name="AlignTextView">
  5. <attr name="align" format="enum">
  6. <enum name="left" value="0"/>
  7. <enum name="center" value="1"/>
  8. <enum name="right" value="2"/>
  9. </attr>
  10. </declare-styleable>
  11. <!-- 标点转换 -->
  12. <declare-styleable name="CBAlignTextView">
  13. <attr name="punctuationConvert" format="boolean"/>
  14. </declare-styleable>
  15. </resources>

如果需要支持android默认的选择复制,请在xml中加入以下代码:

android:textIsSelectable="true"
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/242733?site
推荐阅读
相关标签
  

闽ICP备14008679号