1. 程式人生 > >TextView分散對齊(二)

TextView分散對齊(二)

準備

ActionMenu

package distribute;

import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import
java.util.ArrayList; import java.util.List; /** * @decs: ActionMenu * @date: 2018/7/31 10:03 * @version: v 1.0 */ public class ActionMenu extends LinearLayout { public static final String DEFAULT_MENU_ITEM_TITLE_SELECT_ALL = "全選"; public static final String DEFAULT_MENU_ITEM_TITLE_COPY = "複製"; private
Context mContext; private int mMenuItemMargin; /** * 選單背景色 */ private int mActionMenuBgColor = 0xbb000000; /** * 選單項文字色 */ private int mMenuItemTextColor = 0xffffffff; /** * 選單項 */ private List<String> mItemTitleList; public ActionMenu(Context context) { this
(context, null); } public ActionMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ActionMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; init(); } private void init() { LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 45); setLayoutParams(params); setPadding(25, 0, 25, 0); setOrientation(HORIZONTAL); setGravity(Gravity.CENTER); setActionMenuBackGround(mActionMenuBgColor); mMenuItemMargin = 25; } /** * 選單背景 */ private void setActionMenuBackGround(int menuBgColor) { // 創Drawable GradientDrawable gd = new GradientDrawable(); gd.setColor(menuBgColor); gd.setCornerRadius(8); setBackgroundDrawable(gd); } /** * 默添選單項(全選、複製) */ void addDefaultMenuItem() { View itemSelectAll = createMenuItem(DEFAULT_MENU_ITEM_TITLE_SELECT_ALL); View itemCopy = createMenuItem(DEFAULT_MENU_ITEM_TITLE_COPY); addView(itemSelectAll); addView(itemCopy); invalidate(); } /** * 移除默選單項 */ public void removeDefaultMenuItem() { if (getChildCount() == 0) { return; } View selAllItem = findViewWithTag(DEFAULT_MENU_ITEM_TITLE_SELECT_ALL); View copyItem = findViewWithTag(DEFAULT_MENU_ITEM_TITLE_COPY); if (null != selAllItem) { removeView(selAllItem); } if (null != copyItem) { removeView(copyItem); } invalidate(); } /** * 添自定選單項 * * @param itemTitleList 選單項集 */ public void addCustomMenuItem(List<String> itemTitleList) { this.mItemTitleList = itemTitleList; } /** * 添自定選單項 */ void addCustomItem() { boolean flag = null == mItemTitleList || mItemTitleList.size() == 0; if (flag) { return; } // 去重 List<String> list = new ArrayList(); for (String title : mItemTitleList) { if (!list.contains(title)) { list.add(title); } } for (int i = 0; i < list.size(); i++) { final View menuItem = createMenuItem(list.get(i)); addView(menuItem); } invalidate(); } /** * 創選單項 */ private View createMenuItem(final String itemTitle) { final TextView menuItem = new TextView(mContext); LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); params.leftMargin = params.rightMargin = mMenuItemMargin; menuItem.setLayoutParams(params); menuItem.setTextSize(14); menuItem.setTextColor(mMenuItemTextColor); menuItem.setBackgroundColor(mContext.getResources().getColor(android.R.color.transparent)); menuItem.setGravity(Gravity.CENTER); menuItem.setText(itemTitle); menuItem.setTag(itemTitle); return menuItem; } /** * 選單項文字色 * * @param itemTextColor 選單項文字色 */ public void setMenuItemTextColor(int itemTextColor) { this.mMenuItemTextColor = itemTextColor; } /** * 選單背景色 * * @param menuBgColor 選單背景色 */ public void setActionMenuBgColor(int menuBgColor) { this.mActionMenuBgColor = menuBgColor; setActionMenuBackGround(this.mActionMenuBgColor); } }

CustomActionMenuCallBack

package distribute;

/**
 * @decs: CustomActionMenuCallBack
 * @date: 2018/7/31 10:18
 * @version: v 1.0
 */
public interface CustomActionMenuCallBack {
    /**
     * 創選單
     *
     * @param actionMenu 選單
     * @return false留默選單、true移除默選單
     */
    boolean onCreateCustomActionMenu(ActionMenu actionMenu);

    /**
     * 選單項點選事件
     *
     * @param item            選單項
     * @param selectedContent 所選文字
     */
    void onCustomActionItemClicked(String item, String selectedContent);
}

LeftAndRightAlignTextView

package distribute;

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.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.PopupWindow;
import android.widget.Toast;

import com.self.zsp.dfs.R;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import util.DensityUtils;
import util.KeyboardUtils;
import util.LogUtils;
import util.StatusBarUtils;
import value.Magic;

import static android.content.Context.VIBRATOR_SERVICE;

/**
 * @decs: 長按文字彈選單(可自定。複製、全選、翻譯、分享等。默全選、複製)
 * 文字兩端(適用中、英文及中英混合)
 * @date: 2018/7/31 10:50
 * @version: v 1.0
 */
public class LeftAndRightAlignTextView extends android.support.v7.widget.AppCompatEditText {
    /**
     * 觸發長按事件時間閾值
     */
    private final int TRIGGER_LONG_PRESS_TIME_THRESHOLD = 300;
    /**
     * 觸發長按事件位移閾值
     */
    private final int TRIGGER_LONG_PRESS_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;
    /**
     * 兩端對齊否(默true)
     */
    private boolean isTextJustify = true;
    /**
     * 兩端對齊否(默false)
     */
    private boolean isForbiddenActionMenu = false;
    /**
     * 全選否
     */
    private boolean isActionSelectAll = false;
    /**
     * 起始行(按下)
     */
    private int mStartLine;
    /**
     * 字串始位偏移量(按下)
     */
    private int mStartTextOffset;
    /**
     * 當前行(移動)
     */
    private int mCurrentLine;
    /**
     * 字串當前位偏移量(移動)
     */
    private int mCurrentTextOffset;
    /**
     * 內容寬(無padding)
     */
    private int mViewTextWidth;
    private Vibrator mVibrator;
    /**
     * 長按彈選單
     */
    private PopupWindow mActionMenuPopupWindow;
    private ActionMenu mActionMenu = null;
    private OnClickListener mOnClickListener;
    private CustomActionMenuCallBack mCustomActionMenuCallBack;

    public LeftAndRightAlignTextView(Context context) {
        this(context, null);
    }

    public LeftAndRightAlignTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LeftAndRightAlignTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.LeftAndRightAlignTextView);
        isTextJustify = mTypedArray.getBoolean(R.styleable.LeftAndRightAlignTextView_textJustify, true);
        isForbiddenActionMenu = mTypedArray.getBoolean(R.styleable.LeftAndRightAlignTextView_forbiddenActionMenu, false);
        mTextHighlightColor = mTypedArray.getColor(R.styleable.LeftAndRightAlignTextView_textHeightColor, 0x60ffeb3b);
        mTypedArray.recycle();
        init();
    }

    private void init() {
        WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        mScreenHeight = wm.getDefaultDisplay().getHeight();
        mStatusBarHeight = StatusBarUtils.getStatusBarHeight(mContext);
        mActionMenuHeight = DensityUtils.dpToPx(45, mContext);
        mVibrator = (Vibrator) mContext.getSystemService(VIBRATOR_SERVICE);
        if (isTextJustify) {
            setGravity(Gravity.TOP);
        }
        setTextIsSelectable(true);
        setCursorVisible(false);
        setTextHighlightColor(mTextHighlightColor);
    }

    @Override
    public boolean getDefaultEditable() {
        // false遮蔽系統自帶選單
        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 colorHex = String.format("%08X", color);
        colorHex = "#40" + colorHex.substring(2);
        setHighlightColor(Color.parseColor(colorHex));
    }

    @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:
                LogUtils.e("SelectableTextView", "ACTION_DOWN");
                // 每按下創選單,創不成遮蔽長按事件
                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:
                LogUtils.e("SelectableTextView", "ACTION_MOVE");
                // 先判禁用選單功能否及選單創失敗否(二者滿其一即退長按事件)
                if (!isForbiddenActionMenu || mActionMenu.getChildCount() == 0) {
                    // 手移過程字元偏移
                    currentLine = layout.getLineForVertical(getScrollY() + (int) event.getY());
                    int wordOffsetMove = layout.getOffsetForHorizontal(currentLine, (int) event.getX());
                    // 觸發長按事件否
                    if (event.getEventTime() - event.getDownTime() >= TRIGGER_LONG_PRESS_TIME_THRESHOLD
                            && Math.abs(event.getX() - mTouchDownX) < TRIGGER_LONG_PRESS_DISTANCE_THRESHOLD
                            && Math.abs(event.getY() - mTouchDownY) < TRIGGER_LONG_PRESS_DISTANCE_THRESHOLD) {
                        LogUtils.e("SelectableTextView", "長按移動");
                        isLongPress = true;
                        isLongPressTouchActionUp = false;
                        mStartLine = currentLine;
                        mStartTextOffset = wordOffsetMove;
                        // 每觸發長按震動提示
                        if (!isVibrator) {
                            mVibrator.vibrate(30);
                            isVibrator = true;
                        }
                    }
                    if (isLongPress) {
                        if (!isTextJustify) {
                            requestFocus();
                        }
                        mCurrentLine = currentLine;
                        mCurrentTextOffset = wordOffsetMove;
                        // 通知父佈局不攔截觸控事件
                        getParent().requestDisallowInterceptTouchEvent(true);
                        // 選字元
                        Selection.setSelection(getEditableText(), Math.min(mStartTextOffset, wordOffsetMove), Math.max(mStartTextOffset, wordOffsetMove));
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                LogUtils.e("SelectableTextView", "擡起");
                // 處理長按事件
                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_LONG_PRESS_TIME_THRESHOLD) {
                    // onTouchEvent終返true遮蔽onClick事件,故此處理onClick事件
                    if (null != mOnClickListener) {
                        mOnClickListener.onClick(this);
                    }
                }
                // 通知父佈局繼續攔截觸控事件
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * 創選單
     *
     * @return 選單
     */
    private ActionMenu createActionMenu() {
        // 創選單
        ActionMenu actionMenu = new ActionMenu(mContext);
        // 移除默選單項否
        boolean isRemoveDefaultItem = false;
        if (null != mCustomActionMenuCallBack) {
            isRemoveDefaultItem = mCustomActionMenuCallBack.onCreateCustomActionMenu(actionMenu);
        }
        if (!isRemoveDefaultItem) {
            // 添默選單項
            actionMenu.addDefaultMenuItem();
        }
        // 添自定選單項
        actionMenu.addCustomItem();
        // 獲焦
        actionMenu.setFocusable(true);
        actionMenu.setFocusableInTouchMode(true);
        if (actionMenu.getChildCount() != 0) {
            // 選單項監聽
            for (int i = 0; i < actionMenu.getChildCount(); i++) {
                actionMenu.getChildAt(i).setOnClickListener(mMenuClickListener);
            }
        }
        return actionMenu;
    }

    /**
     * 長按彈選單
     *
     * @param offsetY    Y偏移
     * @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());
                // 分散對齊則選單銷燬後強刷避文字背景未消
                if (isTextJustify) {
                    LeftAndRightAlignTextView.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 selectedStr;
            if (start < 0 || end < 0 || end <= start) {
                selectedStr = "";
            } else {
                selectedStr = getText().toString().substring(start, end);
            }
            switch (menuItemTitle) {
                // 全選
                case ActionMenu.DEFAULT_MENU_ITEM_TITLE_SELECT_ALL:
                    if (isTextJustify) {
                        mStartLine = 0;
                        mCurrentLine = getLayout().getLineCount() - 1;
                        mStartTextOffset = 0;
                        mCurrentTextOffset = getLayout().getLineEnd(mCurrentLine);
                        isActionSelectAll = true;
                        LeftAndRightAlignTextView.this.invalidate();
                    }
                    Selection.selectAll(getEditableText());
                    break;
                // 複製
                case ActionMenu.DEFAULT_MENU_ITEM_TITLE_COPY:
                    KeyboardUtils.copy(mContext, selectedStr);
                    Toast.makeText(mContext, "複製成功", Toast.LENGTH_SHORT).show();
                    hideActionMenu();
                    break;
                default:
                    // 自定
                    if (null != mCustomActionMenuCallBack) {
                        mCustomActionMenuCallBack.onCustomActionItemClicked(menuItemTitle, selectedStr);
                    }
                    hideActionMenu();
                    break;
            }
        }
    };

    /**
     * 算選單相對父佈局Y偏移
     *
     * @param yOffsetStart 所選字元起始位相對屏Y偏移
     * @param yOffsetEnd   所選字元結束位相對屏Y偏移
     * @return 選單相對父佈局Y偏移
     */
    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) {
        LogUtils.e("SelectableTextView", "onDraw");
        if (!isTextJustify) {
            // 無需兩端對齊
            super.onDraw(canvas);
        } else {
            // textview內容實寬
            mViewTextWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
            // 重繪文字(兩端對齊)
            drawTextWithJustify(canvas);
            // 長按、全選、手指滑動過快需繪背景
            // 避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 textStr = 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 lineStr = textStr.substring(lineStart, lineEnd);
            // 每行字串寬(不含字元間距)
            float desiredWidth = StaticLayout.getDesiredWidth(textStr, lineStart, lineEnd, getPaint());
            if (isLineNeedJustify(lineStr)) {
                // 末行無需重繪
                if (i == layout.getLineCount() - 1) {
                    canvas.drawText(lineStr, getPaddingLeft(), currentLineOffsetY, textPaint);
                } else {
                    drawJustifyTextForLine(canvas, lineStr, desiredWidth, currentLineOffsetY);
                }
            } else {
                canvas.drawText(lineStr, 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 rectAll, rectLt, rectRb;
        // 版本控制
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (mStartTextOffset < mCurrentTextOffset) {
                rectAll = new RectF(paddingLeft, mStartLine * h + paddingTop, mViewTextWidth + paddingLeft, (mCurrentLine + 1) * h + paddingTop);
                rectLt = new RectF(paddingLeft, mStartLine * h + paddingTop, startToLeftPosition, (mStartLine + 1) * h + paddingTop);
                rectRb = new RectF(currentToLeftPosition, mCurrentLine * h + paddingTop, mViewTextWidth + paddingLeft, (mCurrentLine + 1) * h + paddingTop);
            } else {
                rectAll = new RectF(paddingLeft, mCurrentLine * h + paddingTop, mViewTextWidth + paddingLeft, (mStartLine + 1) * h + paddingTop);
                rectLt = new RectF(paddingLeft, mCurrentLine * h + paddingTop, currentToLeftPosition, (mCurrentLine + 1) * h + paddingTop);
                rectRb = new RectF(startToLeftPosition, mStartLine * h + paddingTop, mViewTextWidth + paddingLeft, (mStartLine + 1) * h + paddingTop);
            }
            // 創三路徑(分對應上三矩形)
            Path pathAll = new Path();
            Path pathLt = new Path();
            Path pathRb = new Path();
            pathAll.addRect(rectAll, Path.Direction.CCW);
            pathLt.addRect(rectLt, Path.Direction.CCW);
            pathRb.addRect(rectRb, Path.Direction.CCW);
            // 從pathAll減左上角和右下角矩形
            pathAll.addRect(rectAll, Path.Direction.CCW);
            pathAll.op(pathLt, Path.Op.DIFFERENCE);
            pathAll.op(pathRb, Path.Op.DIFFERENCE);
            canvas.drawPath(pathAll, highlightPaint);
        } else {
            Path pathAll = new Path();
            pathAll.moveTo(startToLeftPosition, (mStartLine + 1) * h + paddingTop);
            pathAll.lineTo(startToLeftPosition, mStartLine * h + paddingTop);
            pathAll.lineTo(mViewTextWidth + paddingLeft, mStartLine * h + paddingTop);
            pathAll.lineTo(mViewTextWidth + paddingLeft, mCurrentLine * h + paddingTop);
            pathAll.lineTo(currentToLeftPosition, mCurrentLine * h + paddingTop);
            pathAll.lineTo(currentToLeftPosition, (mCurrentLine + 1) * h + paddingTop);
            pathAll.lineTo(paddingLeft, (mCurrentLine + 1) * h + paddingTop);
            pathAll.lineTo(paddingLeft, (mStartLine + 1) * h + paddingTop);
            pathAll.lineTo(startToLeftPosition, (mStartLine + 1) * h + paddingTop);
            canvas.drawPath(pathAll, highlightPaint);
        }
        /*canvas.restore();*/
    }

    /**
     * 重繪此行(兩端對齊)
     *
     * @param canvas             畫布
     * @param lineStr            該行所有文字
     * @param desiredWidth       該行文字寬總和
     * @param currentLineOffsetY 該行Y偏移
     */
    private void drawJustifyTextForLine(Canvas canvas, String lineStr, float desiredWidth, int currentLineOffsetY) {
        // 畫筆X偏移
        float lineTextOffsetX = getPaddingLeft();
        // 首行否
        if (isFirstLineOfParagraph(lineStr)) {
            String blanks = "  ";
            // 畫出縮排空格
            canvas.drawText(blanks, lineTextOffsetX, currentLineOffsetY, getPaint());
            // 空格寬
            float blankWidth = StaticLayout.getDesiredWidth(blanks, getPaint());
            // 更新畫筆X偏移
            lineTextOffsetX += blankWidth;
            lineStr = lineStr.substring(3);
        }
        // 算相鄰字元(或單詞)間需填充寬,英文按單詞處理,中文按字元處理
        // (TextView內容實際寬 - 該行字串寬) / (字元或單詞個數-1)
        if (isContentABC(lineStr)) {
            // 含英文以空格分割單詞
            String[] lineWords = lineStr.split(" ");
            // 算相鄰單詞間需插入空白
            float insertBlank = mViewTextWidth - desiredWidth;
            if (lineWords.length > 1) {
                insertBlank = (mViewTextWidth - desiredWidth) / (lineWords.length - 1);
            }
            // 遍歷單詞
            for (int i = 0; i < lineWords.length; i++) {
                // 分割後每單詞為純英文則按純英文單詞處理,直接在畫布畫出單詞
                // 分割後每單詞含漢字則按漢字字元處理,逐繪字元
                // 僅一單詞則按中文處理
                // 末單詞按純英文單詞處理
                String wordI = lineWords[i] + " ";
                boolean flag = lineWords.length == 1 || (isContentHanZi(wordI) && i < lineWords.length - 1);
                if (flag) {
                    // 單詞按漢字字元處理
                    // 算單詞相鄰字元間需插空白
                    float insertBlankWordI = insertBlank;
                    if (wordI.length() > 1) {
                        insertBlankWordI = insertBlank / (wordI.length() - 1);
                    }
                    // 遍歷單詞字元(逐繪)
                    for (int j = 0; j < wordI.length(); j++) {
                        String wordICharJ = String.valueOf(wordI.charAt(j));
                        float wordICharJWidth = StaticLayout.getDesiredWidth(wordICharJ, getPaint());
                        canvas.drawText(wordICharJ, lineTextOffsetX, currentLineOffsetY, getPaint());
                        // 更新畫筆X偏移
                        lineTextOffsetX += wordICharJWidth + insertBlankWordI;
                    }
                } else {
                    // 單詞按純英文處理
                    float wordIWidth = StaticLayout.getDesiredWidth(wordI, getPaint());
                    canvas.drawText(wordI, lineTextOffsetX, currentLineOffsetY, getPaint());
                    // 更新畫筆X偏移
                    lineTextOffsetX += wordIWidth + insertBlank;
                }
            }
        } else {
            // 該行按中文處理
            float insertBlank = (mViewTextWidth - desiredWidth) / (lineStr.length() - 1);
            for (int i = 0; i < lineStr.length(); i++) {
                String charI = String.valueOf(lineStr.charAt(i));
                float charIWidth = StaticLayout.getDesiredWidth(charI, getPaint());
                canvas.drawText(charI, lineTextOffsetX, currentLineOffsetY, getPaint());
                // 更新畫筆X偏移
                lineTextOffsetX += charIWidth + insertBlank;
            }
        }
    }

    /**
     * 算字元距控制元件左側位移
     *
     * @param line       字元所在行
     * @param charOffset 字元偏移量
     */
    private float calculatorCharPositionToLeft(int line, int charOffset) {
        String textStr = getText().toString();
        Layout layout = getLayout();
        int lineStart = layout.getLineStart(line);
        int lineEnd = layout.getLineEnd(line);
        String lineStr = textStr.substring(lineStart, lineEnd);
        if (lineStr.equals(Magic.STRING_NEW_LINE)) {
            return getPaddingLeft();
        }
        // 最左側
        if (lineStart == charOffset) {
            return getPaddingLeft();
        }
        // 最右側
        if (charOffset == lineEnd - 1) {
            return mViewTextWidth + getPaddingLeft();
        }
        float desiredWidth = StaticLayout.getDesiredWidth(textStr, lineStart, lineEnd, getPaint());
        // 中間位
        // 算相鄰字元間需填充寬
        // (TextView內容實際寬 - 該行字串寬) / (字元數-1)
        float insertBlank = (mViewTextWidth - desiredWidth) / (lineStr.length() - 1);
        // 算當前字元左側所有字元寬
        float allLeftCharWidth = StaticLayout.getDesiredWidth(textStr.substring(lineStart, charOffset), getPaint());
        // 相鄰字元間需填充寬 + 當前字元左側所有字元寬
        return insertBlank * (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 lineStr 該行文字
     * @return 縮放否
     */
    private boolean isLineNeedJustify(String lineStr) {
        return lineStr.length() != 0 && lineStr.charAt(lineStr.length() - 1) != '\n';
    }

    /**
     * 含英文否
     *
     * @param lineStr 該行文字
     * @return 含英文否
     */
    private boolean isContentABC(String lineStr) {
        String regex = ".*[a-zA-Z]+.*";
        Matcher m = Pattern.compile(regex).matcher(lineStr);
        return m.matches();
    }

    /**
     * 含中文否
     *
     * @param wordStr 文字
     * @return 含中文否
     */
    private boolean isContentHanZi(String wordStr) {
        // 中文
        /*String E1 = "[\u4e00-\u9fa5]";*/
        String regex = ".*[\\u4e00-\\u9fa5]+.*";
        Matcher m = Pattern.compile(regex).matcher(wordStr);
        return m.matches();
    }

    /**
     * 中文標點符號否
     *
     * @param str 文字
     * @return 中文標點符號否
     */
    private boolean isUnicodeSymbol(String str) {
        String regex = ".*[`[email protected]#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]$+.*";
        Matcher m = Pattern.compile(regex).matcher(str);
        return m.matches();
    }

    public void setCustomActionMenuCallBack(CustomActionMenuCallBack callBack) {
        this.mCustomActionMenuCallBack = callBack;
    }
}

attrs

<!--LeftAndRightAlignTextView-->
<declare-styleable name="LeftAndRightAlignTextView">
    <attr name="textJustify" format="boolean" />
    <attr name="forbiddenActionMenu" format="boolean" />
    <attr name="textHeightColor" format="color" />
</declare-styleable>

使用

佈局設

<distribute.LeftAndRightAlignTextView   
    android:layout_width="match_parent"
    android:layout_height="wrap_content"   
    android:lineSpacingMultiplier="1.5"                    
    android:textColor="@color/fontHint"
    android:textSize="@dimen/s13"
    // 禁自定ActionMenu否
    app:forbiddenActionMenu="true" 
    // 文字高亮色
    app:textHeightColor="@color/colorPrimary" 
    // 兩端對齊否
    app:textJustify="true" />

程式碼設

// 兩端對齊否
textView.setTextJustify(true);     
// 禁自定ActionMenu否            
textView.setForbiddenActionMenu(false);   
// 文字高亮色     
textView.setTextHighlightColor(0xff48543e);   

// 調上三法後調inviladite()或postInviladite()通知View重繪
textView.postInvalidate(); 

相關推薦

TextView分散

準備 ActionMenu package distribute; import android.content.Context; import android.graphics.drawable.GradientDrawable; impo

android TextView 分散兩端

import android.content.ClipboardManager; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Paint; imp

TextView實現分散兩端

TextView是個特別基礎的Android控制元件,只要有文字基本就少不了它。但是最近在專案開發的過程中我發現TextView存在很多侷限性,其中最令我頭疼的就是TextView文字排版方面的問題。我們都知道在word中文字對齊方式有靠左、靠右、居中、分散對齊等,但是T

人臉十一--A Recurrent Encoder-Decoder Network for Sequential Face Alignment

轉自:https://blog.csdn.net/shuzfan/article/details/52438910 本次介紹一篇關於人臉關鍵點檢測(人臉對齊)的文章: 《ECCV16 A Recurrent Encoder-Decoder Network for Sequential Fac

人臉--PRN

Joint3D Face Reconstruction and Dense Alignment with Position Map Regression(PRN2018) 我們從之前的論文可以看出,基本的3D人臉對齊,稠密人臉對齊,人臉重建,主要分兩個方向,一是3DMM+特

面向

使用 類對象 light str 初始化 共存 ron 局部變量 pub final關鍵字(掌握) 1、是最終的意思,可以修飾類,方法,變量。 2、特點: A:它修飾的類,不能被繼承。 B:它修飾的方法,不能被重寫。 C:它修飾的變量,是一個常量。 3、面

EL的隱含【訪問作用域範圍的隱含象】

itl ssi attr Language ext this ans 編寫 esc 在EL中提供了4個用於訪問作用域範圍的隱含對象,即pageScope、requestScope、sessionScope和applicationScope。應用這4個隱含對象指定所要查找的標

python面向之封裝

面向 ret 引用 obj cap lock 推薦 簡單 rom 封裝定義:    在程序設計中,封裝(Encapsulation)是對具體對象的一種抽象,即將某些部分隱藏起來,在程序外部看不到,其含義是其他程序無法調用。   即“封裝”就是將抽象得到的數據和行為(或功能)

Python-面向-Day7

tor 情況 所有 load 位置 page 面向 environ ams 1、字段 12、方法 43、屬性 63.1、屬性的基本使用 73.2、實例:對於主機列表 83.3、屬性的兩種定義方式 94、對於類的成員而言都有兩種形式: 144.1、私有成員和公有成員的訪問限

<JAVA - 面向>

取值 sta 方法 end 類名 調用方法 args 靜態內部類 spa package This_Demo01; /** * 學生類模板: * JAVA中的關鍵字: * this: *

day7 面向

ner 括號 特點 eth 私有 函數 png odi 做的   匿名類對象   創建的類的對象是匿名的。當我們只需要一次調用類的對象時,我們就可以考慮使用匿名的方式創建類的對象。特點是創建的匿名類的對象只能夠調用一次! package day007; //圓的面積

rop struct back ont 也會 才會 實例 獲得 .proto 創建對象的方式: 1、使用原生引用類型Object() //使用引用Object創建 new Object() var obj1=new Object(); obj1.

初學Python——面向

if else 作用 導入 不同之處 out with fin 進程 rect 一、抽象類、接口類和抽象接口 轉自博客園魏恒https://www.cnblogs.com/weihengblog/p/8528967.html (一)接口類   什麽是接口類?在繼承中,我們可

——原型與原型鏈

一點 {} 就是 ima script 普通 load method 同名 原型與原型鏈 一. 普通對象與函數對象 JavaScript 中,萬物皆對象!但對象也是有區別的。分為普通對象和函數對象,Object 、Function 是 JS 自帶的函數對象。下面舉例說明

.net從網絡接口地址獲取json,然後解析成

ESS 代碼 ring amp type .get div cep quest 整理代碼,這是第二種方法來讀取json,然後反序列化成對象的,代碼如下: 1 public static Order GetOrderInfo(string _tid, string _or

Java面向:成員變量—OOP中的內存管理—構造函數

生成 類名 存在 str jdk 項目 -a 系統 show 第一節 成員變量 1.1成員變量與局部變量 成員變量:聲明在類下面,方法外面;作用於整個類中; 局部變量:聲明在方法下面,作用於方法下面。 1.2 成員變量默認值 成員變量的默認值和數組的默認值一樣:

python 面向

init ade style obj urn 面向 def spa 封裝 1. 成員 成員共分為三類: 1變量:   - 實例變量(字段)     - 公有實例變量(字段)     - 私有實例變量(字段)   - 類變量(靜態字段)

人臉識別之人臉--SDM演算法

轉自:http://blog.csdn.net/huneng1991/article/details/51901912 http://blog.csdn.net/qq_14845119/article/details/53520847 略刪改。   SDM(Supervis

人臉識別之人臉--LBF演算法

整體來看,其實 ,ESR是基礎版本的形狀迴歸,ERT將回歸樹修改為GBDT,由原始的直接回歸形狀,改進為迴歸形狀殘差,而LBF,是加速特徵提取,由原來的畫素差分特徵池,改為隨機選擇點。   轉自:http://blog.csdn.net/qq_14845119/article/de

人臉識別之人臉--JDA演算法

其實,這裡JDA之前在人臉檢測中解釋過,這裡再轉一篇的目的在於,此文更貼近論文,同時,JDA本來包含人臉檢測和人臉對齊,作為一個整體訓練和測試的。 轉自:http://blog.csdn.net/shixiangyun2/article/details/50809078 第一節: &nb