Coder Social home page Coder Social logo

devilist / advancedtextview Goto Github PK

View Code? Open in Web Editor NEW
426.0 5.0 60.0 8.25 MB

一个增强的TextView库。可以实现文字两端对齐,文字竖排,以及自定义选择文字后的弹出菜单。

License: Apache License 2.0

Java 100.00%
textview verticaltextview vertical

advancedtextview's People

Contributors

devilist avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

advancedtextview's Issues

怎么使用?

你的导包地址呢?类似这种“implementation 'com.android.support:appcompat-v7:28.0.0'”

显示问题

如果给定了固定的宽度,文字较长的时候,会显示后面的部分,前面的不显示。应该是显示前面的文字,后面的省略号才对啊

增加对Emoji的支持.

/*

  • 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,具有以下功能:

  • 1:长按文字弹出ActionMenu菜单;菜单menu可以自定义;实现自定义功能(复制,全选,翻译,分享等;默认实现了全选和复制功能)

  • 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));
      //是否emoji
      if(isEmojiCharacter(line_str.charAt(j))){
      j++;
      word_i_char_j=word_i_char_j+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));
      //是否emoji
      if(isEmojiCharacter(line_str.charAt(i))){
      i++;
      char_i=char_i+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;
    }

    //是否是emoji
    private boolean isEmojiCharacter(char codePoint) {

     return !((codePoint == 0x0) ||
             (codePoint == 0x9) ||
             (codePoint == 0xA) ||
             (codePoint == 0xD) ||
             ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||
             ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) ||
             ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF)));
    

    }
    }

我修改了一下,对emoji表情的支持。谢谢楼主的类库,很好很强大

设置段落

如果用来做小说章节阅读,如何设置段落与段落直接的间距?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.