/* * Copyright 2017 zengp * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.devilist.advancedtextview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Vibrator; import android.text.Layout; import android.text.Selection; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.EditText; import android.widget.PopupWindow; import android.widget.Toast; import java.util.regex.Matcher; import java.util.regex.Pattern; import static android.content.Context.VIBRATOR_SERVICE; /** * SelectableTextView ————增强版的TextView,具有以下功能: * <p> 1:长按文字弹出ActionMenu菜单;菜单menu可以自定义;实现自定义功能(复制,全选,翻译,分享等;默认实现了全选和复制功能) * <p> 2:文本两端对齐功能;适用于中文文本,英文文本 以及中英混合文本 * Created by zengpu on 2016/11/20. */ public class SelectableTextView extends EditText { private final int TRIGGER_LONGPRESS_TIME_THRESHOLD = 300; // 触发长按事件的时间阈值 private final int TRIGGER_LONGPRESS_DISTANCE_THRESHOLD = 10; // 触发长按事件的位移阈值 private Context mContext; private int mScreenHeight; // 屏幕高度 private int mStatusBarHeight; // 状态栏高度 private int mActionMenuHeight; // 弹出菜单高度 private int mTextHighlightColor;// 选中文字背景高亮颜色 private float mTouchDownX = 0; private float mTouchDownY = 0; private float mTouchDownRawY = 0; private boolean isLongPress = false; // 是否发触了长按事件 private boolean isLongPressTouchActionUp = false; // 长按事件结束后,标记该次事件 private boolean isVibrator = false; // 是否触发过长按震动 private boolean isTextJustify = true; // 是否需要两端对齐 ,默认true private boolean isForbiddenActionMenu = false; // 是否需要两端对齐 ,默认false private boolean isActionSelectAll = false; // 是否触发全选事件 private int mStartLine; //action_down触摸事件 起始行 private int mStartTextOffset; //action_down触摸事件 字符串开始位置的偏移值 private int mCurrentLine; // action_move触摸事件 当前行 private int mCurrentTextOffset; //action_move触摸事件 字符串当前位置的偏移值 private int mViewTextWidth; // SelectableTextView内容的宽度(不包含padding) private Vibrator mVibrator; private PopupWindow mActionMenuPopupWindow; // 长按弹出菜单 private ActionMenu mActionMenu = null; private OnClickListener mOnClickListener; private CustomActionMenuCallBack mCustomActionMenuCallBack; public SelectableTextView(Context context) { this(context, null); } public SelectableTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SelectableTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.SelectableTextView); isTextJustify = mTypedArray.getBoolean(R.styleable.SelectableTextView_textJustify, true); isForbiddenActionMenu = mTypedArray.getBoolean(R.styleable.SelectableTextView_forbiddenActionMenu, false); mTextHighlightColor = mTypedArray.getColor(R.styleable.SelectableTextView_textHeightColor, 0x60ffeb3b); mTypedArray.recycle(); init(); } private void init() { WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mScreenHeight = wm.getDefaultDisplay().getHeight(); mStatusBarHeight = Utils.getStatusBarHeight(mContext); mActionMenuHeight = Utils.dp2px(mContext, 45); mVibrator = (Vibrator) mContext.getSystemService(VIBRATOR_SERVICE); if (isTextJustify) setGravity(Gravity.TOP); setTextIsSelectable(true); setCursorVisible(false); setTextHighlightColor(mTextHighlightColor); } @Override public boolean getDefaultEditable() { // 返回false,屏蔽掉系统自带的ActionMenu return false; } public void setTextJustify(boolean textJustify) { isTextJustify = textJustify; } public void setForbiddenActionMenu(boolean forbiddenActionMenu) { isForbiddenActionMenu = forbiddenActionMenu; } public void setTextHighlightColor(int color) { this.mTextHighlightColor = color; String color_hex = String.format("%08X", color); color_hex = "#40" + color_hex.substring(2); setHighlightColor(Color.parseColor(color_hex)); } @Override public void setOnClickListener(OnClickListener l) { super.setOnClickListener(l); if (null != l) { mOnClickListener = l; } } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); Layout layout = getLayout(); int currentLine; // 当前所在行 switch (action) { case MotionEvent.ACTION_DOWN: Log.d("SelectableTextView", "ACTION_DOWN"); // 每次按下时,创建ActionMenu菜单,创建不成功,屏蔽长按事件 if (null == mActionMenu) { mActionMenu = createActionMenu(); } mTouchDownX = event.getX(); mTouchDownY = event.getY(); mTouchDownRawY = event.getRawY(); isLongPress = false; isVibrator = false; isLongPressTouchActionUp = false; break; case MotionEvent.ACTION_MOVE: Log.d("SelectableTextView", "ACTION_MOVE"); // 先判断是否禁用了ActionMenu功能,以及ActionMenu是否创建失败, // 二者只要满足了一个条件,退出长按事件 if (!isForbiddenActionMenu || mActionMenu.getChildCount() == 0) { // 手指移动过程中的字符偏移 currentLine = layout.getLineForVertical(getScrollY() + (int) event.getY()); int mWordOffset_move = layout.getOffsetForHorizontal(currentLine, (int) event.getX()); // 判断是否触发长按事件 if (event.getEventTime() - event.getDownTime() >= TRIGGER_LONGPRESS_TIME_THRESHOLD && Math.abs(event.getX() - mTouchDownX) < TRIGGER_LONGPRESS_DISTANCE_THRESHOLD && Math.abs(event.getY() - mTouchDownY) < TRIGGER_LONGPRESS_DISTANCE_THRESHOLD) { Log.d("SelectableTextView", "ACTION_MOVE 长按"); isLongPress = true; isLongPressTouchActionUp = false; mStartLine = currentLine; mStartTextOffset = mWordOffset_move; // 每次触发长按时,震动提示一次 if (!isVibrator) { mVibrator.vibrate(30); isVibrator = true; } } if (isLongPress) { if (!isTextJustify) requestFocus(); mCurrentLine = currentLine; mCurrentTextOffset = mWordOffset_move; // 通知父布局不要拦截触摸事件 getParent().requestDisallowInterceptTouchEvent(true); // 选择字符 Selection.setSelection(getEditableText(), Math.min(mStartTextOffset, mWordOffset_move), Math.max(mStartTextOffset, mWordOffset_move)); } } break; case MotionEvent.ACTION_UP: Log.d("SelectableTextView", "ACTION_UP"); // 处理长按事件 if (isLongPress) { currentLine = layout.getLineForVertical(getScrollY() + (int) event.getY()); int mWordOffsetEnd = layout.getOffsetForHorizontal(currentLine, (int) event.getX()); // 至少选中一个字符 mCurrentLine = currentLine; mCurrentTextOffset = mWordOffsetEnd; int maxOffset = getEditableText().length() - 1; if (mStartTextOffset > maxOffset) mStartTextOffset = maxOffset; if (mCurrentTextOffset > maxOffset) mCurrentTextOffset = maxOffset; if (mCurrentTextOffset == mStartTextOffset) { if (mCurrentTextOffset == layout.getLineEnd(currentLine) - 1) mStartTextOffset -= 1; else mCurrentTextOffset += 1; } Selection.setSelection(getEditableText(), Math.min(mStartTextOffset, mCurrentTextOffset), Math.max(mStartTextOffset, mCurrentTextOffset)); // 计算菜单显示位置 int mPopWindowOffsetY = calculatorActionMenuYPosition((int) mTouchDownRawY, (int) event.getRawY()); // 弹出菜单 showActionMenu(mPopWindowOffsetY, mActionMenu); isLongPressTouchActionUp = true; isLongPress = false; } else if (event.getEventTime() - event.getDownTime() < TRIGGER_LONGPRESS_TIME_THRESHOLD) { // 由于onTouchEvent最终返回了true,onClick事件会被屏蔽掉,因此在这里处理onClick事件 if (null != mOnClickListener) mOnClickListener.onClick(this); } // 通知父布局继续拦截触摸事件 getParent().requestDisallowInterceptTouchEvent(false); break; } return true; } /* ***************************************************************************************** */ // 创建ActionMenu部分 /** * 创建ActionMenu菜单 * * @return */ private ActionMenu createActionMenu() { // 创建菜单 ActionMenu actionMenu = new ActionMenu(mContext); // 是否需要移除默认item boolean isRemoveDefaultItem = false; if (null != mCustomActionMenuCallBack) { isRemoveDefaultItem = mCustomActionMenuCallBack.onCreateCustomActionMenu(actionMenu); } if (!isRemoveDefaultItem) actionMenu.addDefaultMenuItem(); // 添加默认item actionMenu.addCustomItem(); // 添加自定义item actionMenu.setFocusable(true); // 获取焦点 actionMenu.setFocusableInTouchMode(true); if (actionMenu.getChildCount() != 0) { // item监听 for (int i = 0; i < actionMenu.getChildCount(); i++) { actionMenu.getChildAt(i).setOnClickListener(mMenuClickListener); } } return actionMenu; } /** * 长按弹出菜单 * * @param offsetY * @param actionMenu * @return 菜单创建成功,返回true */ private void showActionMenu(int offsetY, ActionMenu actionMenu) { mActionMenuPopupWindow = new PopupWindow(actionMenu, WindowManager.LayoutParams.WRAP_CONTENT, mActionMenuHeight, true); mActionMenuPopupWindow.setFocusable(true); mActionMenuPopupWindow.setOutsideTouchable(false); mActionMenuPopupWindow.setBackgroundDrawable(new ColorDrawable(0x00000000)); mActionMenuPopupWindow.showAtLocation(this, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, offsetY); mActionMenuPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { Selection.removeSelection(getEditableText()); // 如果设置了分散对齐,ActionMenu销毁后,强制刷新一次,防止出现文字背景未消失的情况 if (isTextJustify) SelectableTextView.this.postInvalidate(); } }); } /** * 隐藏菜单 */ private void hideActionMenu() { if (null != mActionMenuPopupWindow) { mActionMenuPopupWindow.dismiss(); mActionMenuPopupWindow = null; } } /** * 菜单点击事件监听 */ private OnClickListener mMenuClickListener = new OnClickListener() { @Override public void onClick(View v) { String menuItemTitle = (String) v.getTag(); // 选中的字符的开始和结束位置 int start = getSelectionStart(); int end = getSelectionEnd(); // 获得选中的字符 String selected_str; if (start < 0 || end < 0 || end <= start) { selected_str = ""; } else selected_str = getText().toString().substring(start, end); if (menuItemTitle.equals(ActionMenu.DEFAULT_MENU_ITEM_TITLE_SELECT_ALL)) { //全选事件 if (isTextJustify) { mStartLine = 0; mCurrentLine = getLayout().getLineCount() - 1; mStartTextOffset = 0; mCurrentTextOffset = getLayout().getLineEnd(mCurrentLine); isActionSelectAll = true; SelectableTextView.this.invalidate(); } Selection.selectAll(getEditableText()); } else if (menuItemTitle.equals(ActionMenu.DEFAULT_MENU_ITEM_TITLE_COPY)) { // 复制事件 Utils.copyText(mContext, selected_str); Toast.makeText(mContext, "复制成功!", Toast.LENGTH_SHORT).show(); hideActionMenu(); } else { // 自定义事件 if (null != mCustomActionMenuCallBack) { mCustomActionMenuCallBack.onCustomActionItemClicked(menuItemTitle, selected_str); } hideActionMenu(); } } }; /** * 计算弹出菜单相对于父布局的Y向偏移 * * @param yOffsetStart 所选字符的起始位置相对屏幕的Y向偏移 * @param yOffsetEnd 所选字符的结束位置相对屏幕的Y向偏移 * @return */ private int calculatorActionMenuYPosition(int yOffsetStart, int yOffsetEnd) { if (yOffsetStart > yOffsetEnd) { int temp = yOffsetStart; yOffsetStart = yOffsetEnd; yOffsetEnd = temp; } int actionMenuOffsetY; if (yOffsetStart < mActionMenuHeight * 3 / 2 + mStatusBarHeight) { if (yOffsetEnd > mScreenHeight - mActionMenuHeight * 3 / 2) { // 菜单显示在屏幕中间 actionMenuOffsetY = mScreenHeight / 2 - mActionMenuHeight / 2; } else { // 菜单显示所选文字下方 actionMenuOffsetY = yOffsetEnd + mActionMenuHeight / 2; } } else { // 菜单显示所选文字上方 actionMenuOffsetY = yOffsetStart - mActionMenuHeight * 3 / 2; } return actionMenuOffsetY; } /* ***************************************************************************************** */ // 两端对齐部分 @Override protected void onDraw(Canvas canvas) { Log.d("SelectableTextView", "onDraw"); if (!isTextJustify) { // 不需要两端对齐 super.onDraw(canvas); } else { //textview内容的实际宽度 mViewTextWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); // 重绘文字,两端对齐 drawTextWithJustify(canvas); // 绘制选中文字的背景,触发以下事件时需要绘制背景: // 1.长按事件 2.全选事件 3.手指滑动过快时,进入ACTION_UP事件后, // 可能会出现背景未绘制的情况 if (isLongPress | isActionSelectAll | isLongPressTouchActionUp) { drawSelectedTextBackground(canvas); isActionSelectAll = false; isLongPressTouchActionUp = false; } } } /** * 重绘文字,两端对齐 * * @param canvas */ private void drawTextWithJustify(Canvas canvas) { // 文字画笔 TextPaint textPaint = getPaint(); textPaint.setColor(getCurrentTextColor()); textPaint.drawableState = getDrawableState(); String text_str = getText().toString(); // 当前所在行的Y向偏移 int currentLineOffsetY = getPaddingTop(); currentLineOffsetY += getTextSize(); Layout layout = getLayout(); //循环每一行,绘制文字 for (int i = 0; i < layout.getLineCount(); i++) { int lineStart = layout.getLineStart(i); int lineEnd = layout.getLineEnd(i); //获取到TextView每行中的内容 String line_str = text_str.substring(lineStart, lineEnd); // 获取每行字符串的宽度(不包括字符间距) float desiredWidth = StaticLayout.getDesiredWidth(text_str, lineStart, lineEnd, getPaint()); if (isLineNeedJustify(line_str)) { //最后一行不需要重绘 if (i == layout.getLineCount() - 1) { canvas.drawText(line_str, getPaddingLeft(), currentLineOffsetY, textPaint); } else { drawJustifyTextForLine(canvas, line_str, desiredWidth, currentLineOffsetY); } } else { canvas.drawText(line_str, getPaddingLeft(), currentLineOffsetY, textPaint); } //更新行Y向偏移 currentLineOffsetY += getLineHeight(); } } /** * 绘制选中的文字的背景 * * @param canvas */ private void drawSelectedTextBackground(Canvas canvas) { if (mStartTextOffset == mCurrentTextOffset) return; // 文字背景高亮画笔 Paint highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); highlightPaint.setStyle(Paint.Style.FILL); highlightPaint.setColor(mTextHighlightColor); highlightPaint.setAlpha(60); // 计算开始位置和结束位置的字符相对view最左侧的x偏移 float startToLeftPosition = calculatorCharPositionToLeft(mStartLine, mStartTextOffset); float currentToLeftPosition = calculatorCharPositionToLeft(mCurrentLine, mCurrentTextOffset); // 行高 int h = getLineHeight(); int paddingTop = getPaddingTop(); int paddingLeft = getPaddingLeft(); // 创建三个矩形,分别对应: // 所有选中的行对应的矩形,起始行左侧未选中文字的对应的矩形,结束行右侧未选中的文字对应的矩形 RectF rect_all, rect_lt, rect_rb; // sdk版本控制 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (mStartTextOffset < mCurrentTextOffset) { rect_all = new RectF(paddingLeft, mStartLine * h + paddingTop, mViewTextWidth + paddingLeft, (mCurrentLine + 1) * h + paddingTop); rect_lt = new RectF(paddingLeft, mStartLine * h + paddingTop, startToLeftPosition, (mStartLine + 1) * h + paddingTop); rect_rb = new RectF(currentToLeftPosition, mCurrentLine * h + paddingTop, mViewTextWidth + paddingLeft, (mCurrentLine + 1) * h + paddingTop); } else { rect_all = new RectF(paddingLeft, mCurrentLine * h + paddingTop, mViewTextWidth + paddingLeft, (mStartLine + 1) * h + paddingTop); rect_lt = new RectF(paddingLeft, mCurrentLine * h + paddingTop, currentToLeftPosition, (mCurrentLine + 1) * h + paddingTop); rect_rb = new RectF(startToLeftPosition, mStartLine * h + paddingTop, mViewTextWidth + paddingLeft, (mStartLine + 1) * h + paddingTop); } // 创建三个路径,分别对应上面三个矩形 Path path_all = new Path(); Path path_lt = new Path(); Path path_rb = new Path(); path_all.addRect(rect_all, Path.Direction.CCW); path_lt.addRect(rect_lt, Path.Direction.CCW); path_rb.addRect(rect_rb, Path.Direction.CCW); // 将左上角和右下角的矩形从path_all中减去 path_all.addRect(rect_all, Path.Direction.CCW); path_all.op(path_lt, Path.Op.DIFFERENCE); path_all.op(path_rb, Path.Op.DIFFERENCE); canvas.drawPath(path_all, highlightPaint); } else { Path path_all = new Path(); path_all.moveTo(startToLeftPosition, (mStartLine + 1) * h + paddingTop); path_all.lineTo(startToLeftPosition, mStartLine * h + paddingTop); path_all.lineTo(mViewTextWidth + paddingLeft, mStartLine * h + paddingTop); path_all.lineTo(mViewTextWidth + paddingLeft, mCurrentLine * h + paddingTop); path_all.lineTo(currentToLeftPosition, mCurrentLine * h + paddingTop); path_all.lineTo(currentToLeftPosition, (mCurrentLine + 1) * h + paddingTop); path_all.lineTo(paddingLeft, (mCurrentLine + 1) * h + paddingTop); path_all.lineTo(paddingLeft, (mStartLine + 1) * h + paddingTop); path_all.lineTo(startToLeftPosition, (mStartLine + 1) * h + paddingTop); canvas.drawPath(path_all, highlightPaint); } // canvas.restore(); } /** * 重绘此行,两端对齐 * * @param canvas * @param line_str 该行所有的文字 * @param desiredWidth 该行每个文字的宽度的总和 * @param currentLineOffsetY 该行的Y向偏移 */ private void drawJustifyTextForLine(Canvas canvas, String line_str, float desiredWidth, int currentLineOffsetY) { // 画笔X方向的偏移 float lineTextOffsetX = getPaddingLeft(); // 判断是否是首行 if (isFirstLineOfParagraph(line_str)) { String blanks = " "; // 画出缩进空格 canvas.drawText(blanks, lineTextOffsetX, currentLineOffsetY, getPaint()); // 空格需要的宽度 float blank_width = StaticLayout.getDesiredWidth(blanks, getPaint()); // 更新画笔X方向的偏移 lineTextOffsetX += blank_width; line_str = line_str.substring(3); } // 计算相邻字符(或单词)之间需要填充的宽度,英文按单词处理,中文按字符处理 // (TextView内容的实际宽度 - 该行字符串的宽度)/(字符或单词个数-1) if (isContentABC(line_str)) { // 该行包含英文,以空格分割单词 String[] line_words = line_str.split(" "); // 计算相邻单词间需要插入的空白 float insert_blank = mViewTextWidth - desiredWidth; if (line_words.length > 1) insert_blank = (mViewTextWidth - desiredWidth) / (line_words.length - 1); // 遍历单词 for (int i = 0; i < line_words.length; i++) { // 判断分割后的每一个单词;如果是纯英文,按照纯英文单词处理,直接在画布上画出单词; // 如果包括汉字,则按照汉字字符处理,逐个字符绘画 // 如果只有一个单词,按中文处理 // 最后一个单词按照纯英文单词处理 String word_i = line_words[i] + " "; if (line_words.length == 1 || (isContentHanZi(word_i) && i < line_words.length - 1)) { // 单词按照汉字字符处理 // 计算单词中相邻字符间需要插入的空白 float insert_blank_word_i = insert_blank; if (word_i.length() > 1) insert_blank_word_i = insert_blank / (word_i.length() - 1); // 遍历单词中字符,依次绘画 for (int j = 0; j < word_i.length(); j++) { String word_i_char_j = String.valueOf(word_i.charAt(j)); float word_i_char_j_width = StaticLayout.getDesiredWidth(word_i_char_j, getPaint()); canvas.drawText(word_i_char_j, lineTextOffsetX, currentLineOffsetY, getPaint()); // 更新画笔X方向的偏移 lineTextOffsetX += word_i_char_j_width + insert_blank_word_i; } } else { //单词按照纯英文处理 float word_i_width = StaticLayout.getDesiredWidth(word_i, getPaint()); canvas.drawText(word_i, lineTextOffsetX, currentLineOffsetY, getPaint()); // 更新画笔X方向的偏移 lineTextOffsetX += word_i_width + insert_blank; } } } else { // 该行按照中文处理 float insert_blank = (mViewTextWidth - desiredWidth) / (line_str.length() - 1); for (int i = 0; i < line_str.length(); i++) { String char_i = String.valueOf(line_str.charAt(i)); float char_i_width = StaticLayout.getDesiredWidth(char_i, getPaint()); canvas.drawText(char_i, lineTextOffsetX, currentLineOffsetY, getPaint()); // 更新画笔X方向的偏移 lineTextOffsetX += char_i_width + insert_blank; } } } /** * 计算字符距离控件左侧的位移 * * @param line 字符所在行 * @param charOffset 字符偏移量 */ private float calculatorCharPositionToLeft(int line, int charOffset) { String text_str = getText().toString(); Layout layout = getLayout(); int lineStart = layout.getLineStart(line); int lineEnd = layout.getLineEnd(line); String line_str = text_str.substring(lineStart, lineEnd); if (line_str.equals("\n")) return getPaddingLeft(); // 最左侧 if (lineStart == charOffset) return getPaddingLeft(); // 最右侧 if (charOffset == lineEnd - 1) return mViewTextWidth + getPaddingLeft(); float desiredWidth = StaticLayout.getDesiredWidth(text_str, lineStart, lineEnd, getPaint()); // 中间位置 // 计算相邻字符之间需要填充的宽度 // (TextView内容的实际宽度 - 该行字符串的宽度)/(字符个数-1) float insert_blank = (mViewTextWidth - desiredWidth) / (line_str.length() - 1); // 计算当前字符左侧所有字符的宽度 float allLeftCharWidth = StaticLayout.getDesiredWidth(text_str.substring(lineStart, charOffset), getPaint()); // 相邻字符之间需要填充的宽度 + 当前字符左侧所有字符的宽度 return insert_blank * (charOffset - lineStart) + allLeftCharWidth + getPaddingLeft(); } /** * 判断是不是段落的第一行。一个汉字相当于一个字符,此处判断是否为第一行的依据是: * 字符长度大于3且前两个字符为空格 * * @param line * @return */ private boolean isFirstLineOfParagraph(String line) { return line.length() > 3 && line.charAt(0) == ' ' && line.charAt(1) == ' '; } /** * 判断该行需不需要缩放;该行最后一个字符不是换行符的时候返回true, * 该行最后一个字符是换行符的时候返回false * * @param line_str 该行的文字 * @return */ private boolean isLineNeedJustify(String line_str) { if (line_str.length() == 0) { return false; } else { return line_str.charAt(line_str.length() - 1) != '\n'; } } /** * 判断是否包含英文 * * @param line_str * @return */ private boolean isContentABC(String line_str) { String regex = ".*[a-zA-Z]+.*"; Matcher m = Pattern.compile(regex).matcher(line_str); return m.matches(); } /** * 判断是否包含中文 * * @param word_str * @return */ private boolean isContentHanZi(String word_str) { // String E1 = "[\u4e00-\u9fa5]";// 中文 String regex = ".*[\\u4e00-\\u9fa5]+.*"; Matcher m = Pattern.compile(regex).matcher(word_str); return m.matches(); } /** * 判断是否是中文标点符号 * * @param str * @return */ private boolean isUnicodeSymbol(String str) { String regex = ".*[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]$+.*"; Matcher m = Pattern.compile(regex).matcher(str); return m.matches(); } public void setCustomActionMenuCallBack(CustomActionMenuCallBack callBack) { this.mCustomActionMenuCallBack = callBack; } }