效果图
Android TextView自定义选中弹出菜单记笔记功能_第1张图片

两种方案实现

一、 通过onActionItemClicked

完整代码:

        mManusTv.setCustomSelectionActionModeCallback(new ActionMode.Callback() {            @Override            public boolean onCreateActionMode(ActionMode mode, Menu menu) {                MenuInflater inflater = mode.getMenuInflater();                if (inflater != null) {                    inflater.inflate(R.menu.manus_menu, menu);                }                return true;            }            @Override            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {                return false;            }            @Override            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {                if (item.getItemId() == R.id.notes) {                    //记笔记                    if (mManusTv == null) {                        return false;                    }                    int min = 0;                    int max = mManusContent.length();                    if (mManusTv.isFocused()) {                        final int selStart = mManusTv.getSelectionStart();                        final int selEnd = mManusTv.getSelectionEnd();                        min = Math.max(0, Math.min(selStart, selEnd));                        max = Math.max(0, Math.max(selStart, selEnd));                    }                }                return false;            }            @Override            public void onDestroyActionMode(ActionMode mode) {            }        });

menu:

<?xml version="1.0" encoding="utf-8"?>    

方案一在小米手机上不会出现记笔记的选项,原因是小米的定制系统禁用了该事件。

二、 SelectableTextHelper

使用:

        mSelectableTextHelper = new SelectableTextHelper.Builder(mManusTv)                .setSelectedColor(getResources().getColor(R.color.color_tv_theme_transparent15))                .setCursorHandleSizeInDp(20)                .setCursorHandleColor(getResources().getColor(R.color.colotBtnTheme))                .build();

选中回调监听:

        mSelectableTextHelper.setOnNotesClickListener(new OnNoteBookClickListener() {            @Override            public void onTextSelect(CharSequence charSequence) {                LogUtils.e("记笔记:"+charSequence);                String content = charSequence.toString();            }        });

SelectableTextHelper完整代码:

public class SelectableTextHelper {    private final static int DEFAULT_SELECTION_LENGTH = 1;    private static final int DEFAULT_SHOW_DURATION = 100;    private CursorHandle mStartHandle;    private CursorHandle mEndHandle;    private OperateWindow mOperateWindow;    private SelectionInfo mSelectionInfo = new SelectionInfo();    private OnSelectListener mSelectListener;    private Context mContext;    private TextView mTextView;    private Spannable mSpannable;    private int mTouchX;    private int mTouchY;    private int mSelectedColor;    private int mCursorHandleColor;    private int mCursorHandleSize;    private BackgroundColorSpan mSpan;    private boolean isHideWhenScroll;    private boolean isHide = true;    private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener;    ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener;    public SelectableTextHelper(Builder builder) {        mTextView = builder.mTextView;        mContext = mTextView.getContext();        mSelectedColor = builder.mSelectedColor;        mCursorHandleColor = builder.mCursorHandleColor;        mCursorHandleSize = TextLayoutUtil.dp2px(mContext, builder.mCursorHandleSizeInDp);        init();    }    private void init() {        mTextView.setText(mTextView.getText(), TextView.BufferType.SPANNABLE);        mTextView.setOnLongClickListener(new View.OnLongClickListener() {            @Override            public boolean onLongClick(View v) {                showSelectView(mTouchX, mTouchY);                return true;            }        });        mTextView.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                mTouchX = (int) event.getX();                mTouchY = (int) event.getY();                return false;            }        });        mTextView.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                resetSelectionInfo();                hideSelectView();            }        });        mTextView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {            @Override            public void onViewAttachedToWindow(View v) {            }            @Override            public void onViewDetachedFromWindow(View v) {                destroy();            }        });        mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {            @Override            public boolean onPreDraw() {                if (isHideWhenScroll) {                    isHideWhenScroll = false;                    postShowSelectView(DEFAULT_SHOW_DURATION);                }                return true;            }        };        mTextView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);        mOnScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() {            @Override            public void onScrollChanged() {                if (!isHideWhenScroll && !isHide) {                    isHideWhenScroll = true;                    if (mOperateWindow != null) {                        mOperateWindow.dismiss();                    }                    if (mStartHandle != null) {                        mStartHandle.dismiss();                    }                    if (mEndHandle != null) {                        mEndHandle.dismiss();                    }                }            }        };        mTextView.getViewTreeObserver().addOnScrollChangedListener(mOnScrollChangedListener);        mOperateWindow = new OperateWindow(mContext);    }    private void postShowSelectView(int duration) {        mTextView.removeCallbacks(mShowSelectViewRunnable);        if (duration <= 0) {            mShowSelectViewRunnable.run();        } else {            mTextView.postDelayed(mShowSelectViewRunnable, duration);        }    }    private final Runnable mShowSelectViewRunnable = new Runnable() {        @Override        public void run() {            if (isHide) {                return;            }            if (mOperateWindow != null) {                mOperateWindow.show();            }            if (mStartHandle != null) {                showCursorHandle(mStartHandle);            }            if (mEndHandle != null) {                showCursorHandle(mEndHandle);            }        }    };    private void hideSelectView() {        isHide = true;        if (mStartHandle != null) {            mStartHandle.dismiss();        }        if (mEndHandle != null) {            mEndHandle.dismiss();        }        if (mOperateWindow != null) {            mOperateWindow.dismiss();        }    }    private void resetSelectionInfo() {        mSelectionInfo.mSelectionContent = null;        if (mSpannable != null && mSpan != null) {            mSpannable.removeSpan(mSpan);            mSpan = null;        }    }    private void showSelectView(int x, int y) {        hideSelectView();        resetSelectionInfo();        isHide = false;        if (mStartHandle == null) {            mStartHandle = new CursorHandle(true);        }        if (mEndHandle == null) {            mEndHandle = new CursorHandle(false);        }        int startOffset = TextLayoutUtil.getPreciseOffset(mTextView, x, y);        int endOffset = startOffset + DEFAULT_SELECTION_LENGTH;        if (mTextView.getText() instanceof Spannable) {            mSpannable = (Spannable) mTextView.getText();        }        if (mSpannable == null || startOffset >= mTextView.getText().length()) {            return;        }        selectText(startOffset, endOffset);        showCursorHandle(mStartHandle);        showCursorHandle(mEndHandle);        mOperateWindow.show();    }    private void showCursorHandle(CursorHandle cursorHandle) {        Layout layout = mTextView.getLayout();        int offset = cursorHandle.isLeft ? mSelectionInfo.mStart : mSelectionInfo.mEnd;        cursorHandle.show((int) layout.getPrimaryHorizontal(offset), layout.getLineBottom(layout.getLineForOffset(offset)));    }    private void selectText(int startPos, int endPos) {        if (startPos != -1) {            mSelectionInfo.mStart = startPos;        }        if (endPos != -1) {            mSelectionInfo.mEnd = endPos;        }        if (mSelectionInfo.mStart > mSelectionInfo.mEnd) {            int temp = mSelectionInfo.mStart;            mSelectionInfo.mStart = mSelectionInfo.mEnd;            mSelectionInfo.mEnd = temp;        }        if (mSpannable != null) {            if (mSpan == null) {                mSpan = new BackgroundColorSpan(mSelectedColor);            }            mSelectionInfo.mSelectionContent = mSpannable.subSequence(mSelectionInfo.mStart, mSelectionInfo.mEnd).toString();            mSpannable.setSpan(mSpan, mSelectionInfo.mStart, mSelectionInfo.mEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);            if (mSelectListener != null) {                mSelectListener.onTextSelected(mSelectionInfo.mSelectionContent);            }        }    }    OnNoteBookClickListener mNoteBookClickListener;    public void setSelectListener(OnSelectListener selectListener) {        mSelectListener = selectListener;    }    public void setOnNotesClickListener(OnNoteBookClickListener notesClickListener) {        mNoteBookClickListener = notesClickListener;    }    public void destroy() {        mTextView.getViewTreeObserver().removeOnScrollChangedListener(mOnScrollChangedListener);        mTextView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);        resetSelectionInfo();        hideSelectView();        mStartHandle = null;        mEndHandle = null;        mOperateWindow = null;    }    public void dismiss() {        SelectableTextHelper.this.resetSelectionInfo();        SelectableTextHelper.this.hideSelectView();    }    /**     * Operate windows : copy, select all     */    private class OperateWindow {        private PopupWindow mWindow;        private int[] mTempCoors = new int[2];        private int mWidth;        private int mHeight;        public OperateWindow(final Context context) {            View contentView = LayoutInflater.from(context).inflate(R.layout.layout_operate_windows, null);            contentView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));            mWidth = contentView.getMeasuredWidth();            mHeight = contentView.getMeasuredHeight();            mWindow =                new PopupWindow(contentView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, false);            mWindow.setClippingEnabled(false);            contentView.findViewById(R.id.tv_copy).setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    ClipboardManager clip = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);                    clip.setPrimaryClip(                        ClipData.newPlainText(mSelectionInfo.mSelectionContent, mSelectionInfo.mSelectionContent));                    if (mSelectListener != null) {                        mSelectListener.onTextSelected(mSelectionInfo.mSelectionContent);                    }                    SelectableTextHelper.this.resetSelectionInfo();                    SelectableTextHelper.this.hideSelectView();                }            });            contentView.findViewById(R.id.tv_select_all).setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    hideSelectView();                    selectText(0, mTextView.getText().length());                    isHide = false;                    showCursorHandle(mStartHandle);                    showCursorHandle(mEndHandle);                    mOperateWindow.show();                }            });            contentView.findViewById(R.id.tv_note).setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    if (mNoteBookClickListener != null) {                        mNoteBookClickListener.onTextSelect(mSelectionInfo.mSelectionContent);                    }                    SelectableTextHelper.this.resetSelectionInfo();                    SelectableTextHelper.this.hideSelectView();                }            });        }        public void show() {            mTextView.getLocationInWindow(mTempCoors);            Layout layout = mTextView.getLayout();            int posX = (int) layout.getPrimaryHorizontal(mSelectionInfo.mStart) + mTempCoors[0];            int posY = layout.getLineTop(layout.getLineForOffset(mSelectionInfo.mStart)) + mTempCoors[1] - mHeight - 16;            if (posX <= 0) {                posX = 16;            }            if (posY < 0) {                posY = 16;            }            if (posX + mWidth > TextLayoutUtil.getScreenWidth(mContext)) {                posX = TextLayoutUtil.getScreenWidth(mContext) - mWidth - 16;            }            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {                mWindow.setElevation(8f);            }            mWindow.showAtLocation(mTextView, Gravity.NO_GRAVITY, posX, posY);        }        public void dismiss() {            mWindow.dismiss();        }        public boolean isShowing() {            return mWindow.isShowing();        }    }    private class CursorHandle extends View {        private PopupWindow mPopupWindow;        private Paint mPaint;        private int mCircleRadius = mCursorHandleSize / 2;        private int mWidth = mCircleRadius * 2;        private int mHeight = mCircleRadius * 2;        private int mPadding = 25;        private boolean isLeft;        public CursorHandle(boolean isLeft) {            super(mContext);            this.isLeft = isLeft;            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);            mPaint.setColor(mCursorHandleColor);            mPopupWindow = new PopupWindow(this);            mPopupWindow.setClippingEnabled(false);            mPopupWindow.setWidth(mWidth + mPadding * 2);            mPopupWindow.setHeight(mHeight + mPadding / 2);            invalidate();        }        @Override        protected void onDraw(Canvas canvas) {            canvas.drawCircle(mCircleRadius + mPadding, mCircleRadius, mCircleRadius, mPaint);            if (isLeft) {                canvas.drawRect(mCircleRadius + mPadding, 0, mCircleRadius * 2 + mPadding, mCircleRadius, mPaint);            } else {                canvas.drawRect(mPadding, 0, mCircleRadius + mPadding, mCircleRadius, mPaint);            }        }        private int mAdjustX;        private int mAdjustY;        private int mBeforeDragStart;        private int mBeforeDragEnd;        @Override        public boolean onTouchEvent(MotionEvent event) {            switch (event.getAction()) {                case MotionEvent.ACTION_DOWN:                    mBeforeDragStart = mSelectionInfo.mStart;                    mBeforeDragEnd = mSelectionInfo.mEnd;                    mAdjustX = (int) event.getX();                    mAdjustY = (int) event.getY();                    break;                case MotionEvent.ACTION_UP:                case MotionEvent.ACTION_CANCEL:                    mOperateWindow.show();                    break;                case MotionEvent.ACTION_MOVE:                    mOperateWindow.dismiss();                    int rawX = (int) event.getRawX();                    int rawY = (int) event.getRawY();                    update(rawX + mAdjustX - mWidth, rawY + mAdjustY - mHeight);                    break;            }            return true;        }        private void changeDirection() {            isLeft = !isLeft;            invalidate();        }        public void dismiss() {            mPopupWindow.dismiss();        }        private int[] mTempCoors = new int[2];        public void update(int x, int y) {            mTextView.getLocationInWindow(mTempCoors);            int oldOffset;            if (isLeft) {                oldOffset = mSelectionInfo.mStart;            } else {                oldOffset = mSelectionInfo.mEnd;            }            y -= mTempCoors[1];            int offset = TextLayoutUtil.getHysteresisOffset(mTextView, x, y, oldOffset);            if (offset != oldOffset) {                resetSelectionInfo();                if (isLeft) {                    if (offset > mBeforeDragEnd) {                        CursorHandle handle = getCursorHandle(false);                        changeDirection();                        handle.changeDirection();                        mBeforeDragStart = mBeforeDragEnd;                        selectText(mBeforeDragEnd, offset);                        handle.updateCursorHandle();                    } else {                        selectText(offset, -1);                    }                    updateCursorHandle();                } else {                    if (offset < mBeforeDragStart) {                        CursorHandle handle = getCursorHandle(true);                        handle.changeDirection();                        changeDirection();                        mBeforeDragEnd = mBeforeDragStart;                        selectText(offset, mBeforeDragStart);                        handle.updateCursorHandle();                    } else {                        selectText(mBeforeDragStart, offset);                    }                    updateCursorHandle();                }            }        }        private void updateCursorHandle() {            mTextView.getLocationInWindow(mTempCoors);            Layout layout = mTextView.getLayout();            if (isLeft) {                mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.mStart) - mWidth + getExtraX(),                    layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.mStart)) + getExtraY(), -1, -1);            } else {                mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.mEnd) + getExtraX(),                    layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.mEnd)) + getExtraY(), -1, -1);            }        }        public void show(int x, int y) {            mTextView.getLocationInWindow(mTempCoors);            int offset = isLeft ? mWidth : 0;            mPopupWindow.showAtLocation(mTextView, Gravity.NO_GRAVITY, x - offset + getExtraX(), y + getExtraY());        }        public int getExtraX() {            return mTempCoors[0] - mPadding + mTextView.getPaddingLeft();        }        public int getExtraY() {            return mTempCoors[1] + mTextView.getPaddingTop();        }    }    private CursorHandle getCursorHandle(boolean isLeft) {        if (mStartHandle.isLeft == isLeft) {            return mStartHandle;        } else {            return mEndHandle;        }    }    public static class Builder {        private TextView mTextView;        private int mCursorHandleColor = 0xFF1379D6;        private int mSelectedColor = 0xFFAFE1F4;        private float mCursorHandleSizeInDp = 24;        public Builder(TextView textView) {            mTextView = textView;        }        public Builder setCursorHandleColor(@ColorInt int cursorHandleColor) {            mCursorHandleColor = cursorHandleColor;            return this;        }        public Builder setCursorHandleSizeInDp(float cursorHandleSizeInDp) {            mCursorHandleSizeInDp = cursorHandleSizeInDp;            return this;        }        public Builder setSelectedColor(@ColorInt int selectedBgColor) {            mSelectedColor = selectedBgColor;            return this;        }        public SelectableTextHelper build() {            return new SelectableTextHelper(this);        }    }}

TextLayoutUtil

public class TextLayoutUtil {    public static int getScreenWidth(Context context) {        return context.getResources().getDisplayMetrics().widthPixels;    }    public static int getPreciseOffset(TextView textView, int x, int y) {        Layout layout = textView.getLayout();        if (layout != null) {            int topVisibleLine = layout.getLineForVertical(y);            int offset = layout.getOffsetForHorizontal(topVisibleLine, x);            int offsetX = (int) layout.getPrimaryHorizontal(offset);            if (offsetX > x) {                return layout.getOffsetToLeftOf(offset);            } else {                return offset;            }        } else {            return -1;        }    }    public static int getHysteresisOffset(TextView textView, int x, int y, int previousOffset) {        final Layout layout = textView.getLayout();        if (layout == null) return -1;        int line = layout.getLineForVertical(y);        // The "HACK BLOCK"S in this function is required because of how Android Layout for        // TextView works - if 'offset' equals to the last character of a line, then        //        // * getLineForOffset(offset) will result the NEXT line        // * getPrimaryHorizontal(offset) will return 0 because the next insertion point is on the next line        // * getOffsetForHorizontal(line, x) will not return the last offset of a line no matter where x is        // These are highly undesired and is worked around with the HACK BLOCK        //        // @see Moon+ Reader/Color Note - see how it can't select the last character of a line unless you move        // the cursor to the beginning of the next line.        //        ////////////////////HACK BLOCK////////////////////////////////////////////////////        if (isEndOfLineOffset(layout, previousOffset)) {            // we have to minus one from the offset so that the code below to find            // the previous line can work correctly.            int left = (int) layout.getPrimaryHorizontal(previousOffset - 1);            int right = (int) layout.getLineRight(line);            int threshold = (right - left) / 2; // half the width of the last character            if (x > right - threshold) {                previousOffset -= 1;            }        }        ///////////////////////////////////////////////////////////////////////////////////        final int previousLine = layout.getLineForOffset(previousOffset);        final int previousLineTop = layout.getLineTop(previousLine);        final int previousLineBottom = layout.getLineBottom(previousLine);        final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;        // If new line is just before or after previous line and y position is less than        // hysteresisThreshold away from previous line, keep cursor on previous line.        if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) || ((line == previousLine - 1) && ((            previousLineTop                - y) < hysteresisThreshold))) {            line = previousLine;        }        int offset = layout.getOffsetForHorizontal(line, x);        // This allow the user to select the last character of a line without moving the        // cursor to the next line. (As Layout.getOffsetForHorizontal does not return the        // offset of the last character of the specified line)        //        // But this function will probably get called again immediately, must decrement the offset        // by 1 to compensate for the change made below. (see previous HACK BLOCK)        /////////////////////HACK BLOCK///////////////////////////////////////////////////        if (offset < textView.getText().length() - 1) {            if (isEndOfLineOffset(layout, offset + 1)) {                int left = (int) layout.getPrimaryHorizontal(offset);                int right = (int) layout.getLineRight(line);                int threshold = (right - left) / 2; // half the width of the last character                if (x > right - threshold) {                    offset += 1;                }            }        }        //////////////////////////////////////////////////////////////////////////////////        return offset;    }    private static boolean isEndOfLineOffset(Layout layout, int offset) {        return offset > 0 && layout.getLineForOffset(offset) == layout.getLineForOffset(offset - 1) + 1;    }    public static int dp2px(Context context, float dpValue) {        final float scale = context.getResources().getDisplayMetrics().density;        return (int) (dpValue * scale + 0.5f);    }}

SelectionInfo:

public class SelectionInfo {    public int mStart;    public int mEnd;    public String mSelectionContent;}

OnNoteBookClickListener:

public interface OnNoteBookClickListener {    void onTextSelect(CharSequence content);}

布局文件:

<?xml version="1.0" encoding="utf-8"?>                                                        

更多相关文章

  1. android 设置默认launcher 附上代码
  2. Android通过代码自动连接WiFi
  3. Android service: startService的代码实现
  4. 【代码】利用Android的Log 演示一个activity的生命周期
  5. Android Robotium的自动化代码
  6. Android代码实现飞行模式的打开
  7. Android对应用程序的资源文件xml解析的源代码在哪里

随机推荐

  1. Android中adapter的原理简单说明
  2. Android(安卓)JNI 学习之Android.mk文件
  3. Android(安卓)图片裁剪之三剑式(一)
  4. VirtualApp中静默安装App
  5. Android(安卓)系统剪贴板的使用 - 复制、
  6. Android(安卓)H5 js webView初体验
  7. Android(安卓)ApiDemos示例解析(154):Vie
  8. Xamarin.Forms读取并展示Android和iOS通
  9. android 实现省市区三级联动
  10. 浅尝安卓事件分发机制