Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
xiaoting000 opened this issue Jan 26, 2018 · 8 comments

Comments

@xiaoting000
Copy link

xiaoting000 commented Jan 26, 2018

我的解决办法: 在 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
Copy link
Author

xiaoting000 commented Jan 26, 2018

showSelectView 方法最后少贴了一行:

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

storedHelper = this;

}

@laobie
Copy link
Owner

laobie commented Jan 26, 2018

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

@as2227024221
Copy link

showSelectView 方法最后少贴了一行:

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

storedHelper = this;

}

我试了好像不行啊

@liwuchen
Copy link

我的解决办法: 在 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了就会出错。

@xiaoting000
Copy link
Author

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
Copy link
Author

使用方法: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: chjie.jaeger@gmail.com
  • 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;
    }
    }
    }

@taotaodai
Copy link

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

@djxf
Copy link

djxf commented Nov 2, 2019

还行。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants