SelectableTextHelper icon indicating copy to clipboard operation
SelectableTextHelper copied to clipboard

对于页面内有多个textview需要使用到该help出现的长按多个textview会出现多个helper弹框问题

Open xiaoting000 opened this issue 7 years ago • 8 comments

我的解决办法: 在 helper内用个static isShowing 来标记是否已经有复制弹出pop显示, 再用一个static storedHelper存放当前显示的helper, 然后再show 和 hide方法里对标记和 对storedHelper做清理操作, 具体添加代码如下:

private static boolean isShowing = false;
private static SelectableTextHelper storedHelper;

public void hideSelectView() {
        isShowing = false;
//your code
//......
}

private void showSelectView(int x, int y) {
        if(isShowing){
            storedHelper.resetStored();
        }
// your code
//.....
}

public void resetStored(){
        resetSelectionInfo();
        hideSelectView();
        mStartHandle = null;
        mEndHandle = null;
        mOperateWindow = null;
    }

 

xiaoting000 avatar Jan 26 '18 03:01 xiaoting000

showSelectView 方法最后少贴了一行:

private void showSelectView(int x, int y) {
if(isShowing){
storedHelper.resetStored();
}
// your code start
//.....
//your code end

storedHelper = this;

}

xiaoting000 avatar Jan 26 '18 03:01 xiaoting000

这样是可以解决的,目前这个项目处在一个探索的过程,其实实际应用到项目中还是需要很多处理的

laobie avatar Jan 26 '18 05:01 laobie

showSelectView 方法最后少贴了一行:

private void showSelectView(int x, int y) {
if(isShowing){
storedHelper.resetStored();
}
// your code start
//.....
//your code end

storedHelper = this;

}

我试了好像不行啊

as2227024221 avatar Oct 16 '18 11:10 as2227024221

我的解决办法: 在 helper内用个static isShowing 来标记是否已经有复制弹出pop显示, 再用一个static storedHelper存放当前显示的helper, 然后再show 和 hide方法里对标记和 对storedHelper做清理操作, 具体添加代码如下:

private static boolean isShowing = false;
private static SelectableTextHelper storedHelper;

public void hideSelectView() {
        isShowing = false;
//your code
//......
}

private void showSelectView(int x, int y) {
        if(isShowing){
            storedHelper.resetStored();
        }
// your code
//.....
}

public void resetStored(){
        resetSelectionInfo();
        hideSelectView();
        mStartHandle = null;
        mEndHandle = null;
        mOperateWindow = null;
    }

少了把isShowing设为true的步骤, 另外你这里没有判断两次长按的对象是不是同一个,如果是同一个的话,当前mOperateWindow 设为null了就会出错。

liwuchen avatar Oct 30 '18 02:10 liwuchen

showSelectView 方法最后少贴了一行:

private void showSelectView(int x, int y) {
if(isShowing){
storedHelper.resetStored();
}
// your code start
//.....
//your code end

storedHelper = this;

}

我试了好像不行啊

我这边是可以的,

我的解决办法: 在 helper内用个static isShowing 来标记是否已经有复制弹出pop显示, 再用一个static storedHelper存放当前显示的helper, 然后再show 和 hide方法里对标记和 对storedHelper做清理操作, 具体添加代码如下:

private static boolean isShowing = false;
private static SelectableTextHelper storedHelper;

public void hideSelectView() {
        isShowing = false;
//your code
//......
}

private void showSelectView(int x, int y) {
        if(isShowing){
            storedHelper.resetStored();
        }
// your code
//.....
}

public void resetStored(){
        resetSelectionInfo();
        hideSelectView();
        mStartHandle = null;
        mEndHandle = null;
        mOperateWindow = null;
    }

少了把isShowing设为true的步骤, 另外你这里没有判断两次长按的对象是不是同一个,如果是同一个的话,当前mOperateWindow 设为null了就会出错。

isShowing置为true 在的if(isShowing){ storedHelper.resetStored(); }

之后有置为true, 忘了贴上来了, mOperateWindow 使用有判空的,同一个对象选择是没有问题的我这边现在也是正常使用,你那边如果有具体的问题可以贴上来看看

xiaoting000 avatar Oct 30 '18 02:10 xiaoting000

使用方法:new SelectableTextHelper(tvTestContent); 贴上修改之后的完整SelectableTextHelper代码: package com.snda.mcommon.util.selecttext; import android.annotation.SuppressLint; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.text.Spanned; import android.text.method.MovementMethod; import android.text.style.BackgroundColorSpan; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.PopupWindow; import android.widget.TextView; import com.snda.mcommon.R; /**

  • Created by Jaeger on 16/8/30.
  • Email: [email protected]
  • GitHub: https://github.com/laobie / public class SelectableTextHelper { private final static int DEFAULT_SELECTION_LENGTH = 1; 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 mCursorHandleSize; private BackgroundColorSpan mSpan; private boolean isHideWhenScroll; private boolean isHide = true; private int mCursorHandleColor = 0xFF1379D6; private int mSelectedColor = 0xFFAFE1F4; public float mCursorHandleSizeInDp = 24; private static boolean isShowing = false; private static SelectableTextHelper storedHelper; private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener; ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener; public SelectableTextHelper(TextView textView) { mTextView = textView; mContext = mTextView.getContext(); mCursorHandleSize = TextLayoutUtil.dp2px(mContext, mCursorHandleSizeInDp); init(); } @SuppressLint("ClickableViewAccessibility") 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 (mStartHandle != null) { mStartHandle.dismiss(); mStartHandle.updateCursorHandle(); showCursorHandle(mStartHandle); } if (mEndHandle != null) { mEndHandle.dismiss(); mEndHandle.updateCursorHandle(); showCursorHandle(mEndHandle); } if (mOperateWindow != null) { mOperateWindow.dismiss(); mOperateWindow.show(); } } } }; 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 Runnable mShowSelectViewRunnable = new Runnable() { @Override public void run() { if (isHide) return; if (mStartHandle != null) { mStartHandle.dismiss(); showCursorHandle(mStartHandle); } if (mEndHandle != null) { mEndHandle.dismiss(); showCursorHandle(mEndHandle); } if (mOperateWindow != null) { mOperateWindow.dismiss(); mOperateWindow.show(); } } }; public void hideSelectView() { isShowing = false; isHide = true; if (mStartHandle != null) { mStartHandle.dismiss(); } if (mEndHandle != null) { mEndHandle.dismiss(); } if (mOperateWindow != null) { mOperateWindow.dismiss(); } } private void resetSelectionInfo() { if (mSelectionInfo != null){ mSelectionInfo.mSelectionContent = null; } if (mSpannable != null && mSpan != null) { mSpannable.removeSpan(mSpan); mSpan = null; } } private void showSelectView(int x, int y) { if(isShowing){ storedHelper.resetPre(); } // resetSelectionInfo(); // hideSelectView(); isShowing = true; isHide = false; mStartHandle = null; mEndHandle = null; mSelectionInfo = null; mSelectionInfo = new SelectionInfo(); mStartHandle = new CursorHandle(true); mEndHandle = new CursorHandle(false); int startOffset = TextLayoutUtil.getPreciseOffset(mTextView, x, y); int endOffset = startOffset + DEFAULT_SELECTION_LENGTH; if (mSpannable != null){ mSpannable.removeSpan(mSpan); mSpan = null; } if (mTextView.getText() instanceof Spannable) { mSpannable = (Spannable) mTextView.getText(); } if (mSpannable == null || startOffset >= mTextView.getText().length()) { return; } selectText(startOffset, endOffset); showCursorHandle(mStartHandle); showCursorHandle(mEndHandle); if (mOperateWindow == null){ mOperateWindow = new OperateWindow(mContext); mOperateWindow.show(); }else{ mOperateWindow = null; mOperateWindow = new OperateWindow(mContext); mOperateWindow.show(); } storedHelper = this; } 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 (mOperateWindow != null){ if ((startPos == 0) && (endPos >= mTextView.getText().length())){ mOperateWindow.setTvSelectAll(false); }else{ mOperateWindow.setTvSelectAll(true); } } 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); } } } public void setSelectListener(OnSelectListener selectListener) { mSelectListener = selectListener; } public void resetPre(){ storedHelper.resetSelectionInfo(); storedHelper.hideSelectView(); mStartHandle = null; mEndHandle = null; mOperateWindow = null; } public void destroy() { mTextView.getViewTreeObserver().removeOnScrollChangedListener(mOnScrollChangedListener); mTextView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener); resetSelectionInfo(); hideSelectView(); mStartHandle = null; mEndHandle = null; mOperateWindow = null; } /*
  • Operate windows : copy, select all */ private class OperateWindow { private PopupWindow mWindow; private int[] mTempCoors = new int[2]; private int mWidth; private int mHeight; private TextView tvCopy; private TextView tvSelectAll; private TextView tvDivider; 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); tvCopy = (TextView) contentView.findViewById(R.id.tv_copy); tvSelectAll = (TextView) contentView.findViewById(R.id.tv_select_all); tvDivider = (TextView) contentView.findViewById(R.id.tv_divider); tvCopy.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(); } }); tvSelectAll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { hideSelectView(); selectText(0, mTextView.getText().length()); isHide = false; showCursorHandle(mStartHandle); showCursorHandle(mEndHandle); mOperateWindow.show(); } }); } public void setTvSelectAll(boolean isVisible){ tvDivider.setVisibility(isVisible ? View.VISIBLE : View.GONE); tvSelectAll.setVisibility(isVisible ? View.VISIBLE : View.GONE); } 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 boolean isShowing(){ return mPopupWindow.isShowing(); } 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; } } }

xiaoting000 avatar Oct 30 '18 02:10 xiaoting000

我的办法是去监听Activity中的dispatchTouchEvent方法。因为游标和操作弹框都是用PopupWindow,PopupWindow里的控件会消费掉触摸事件,不会传递到父控件,所以当dispatchTouchEvent被响应时,说明我点击到了外部区域,这时把游标、弹框以及背景都清空就好了

taotaodai avatar Nov 01 '19 09:11 taotaodai

还行。

djxf avatar Nov 02 '19 02:11 djxf