After resolving TextView set ClickableSpan, and click a blank area of the slide and handle conflict

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/lylddingHFFW/article/details/99319236

After setting TextView ClickableSpan, you need to set setMovementMethod (LinkMovementMethod.getInstance ()), and continues LinkMovementMethod ScrollingMovementMethod, so long text for the following two questions:
original address

GitHub Test Demo
1: ClickableSpan clicks and text TextView long slide conflict
2: Click TextView blank area, always select the last text

This implementation is based on a simple English reader , click on the link to view
the main achievement of
1: Achieving the page click on the selected word
2: implement paging function
3: simple page-turning animation

An analysis of the original LinkMovementMethod click and sliding logic

1) Click the Find location ClickableSpan according to the current row, if you click an empty area, compared with the current line of the last ClickableSpan; if ClickableSpan collection is not empty, then the interception down and up events.

@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
                            MotionEvent event) {
    int action = event.getAction();

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
        int x = (int) event.getX();
        int y = (int) event.getY();

        x -= widget.getTotalPaddingLeft();
        y -= widget.getTotalPaddingTop();

        x += widget.getScrollX();
        y += widget.getScrollY();

        Layout layout = widget.getLayout();
        int line = layout.getLineForVertical(y);
        int off = layout.getOffsetForHorizontal(line, x);
//根据点击位置查找当前行的ClickableSpan,若点击空白区域,则为当前行最后一个ClickableSpan
        ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
        //ClickableSpan集合不为空,则拦截down和up事件
        if (links.length != 0) {
            if (action == MotionEvent.ACTION_UP) {
                links[0].onClick(widget);
            } else if (action == MotionEvent.ACTION_DOWN) {
                Selection.setSelection(buffer,
                        buffer.getSpanStart(links[0]),
                        buffer.getSpanEnd(links[0]));
            }
            return true;
        } else {
            Selection.removeSelection(buffer);
        }
    }

    return super.onTouchEvent(widget, buffer, event);
}

2) call the parent pass-through event onTouchEvent

@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
    return Touch.onTouchEvent(widget, buffer, event);
}

3) handling drag events.
ACTION_DOWN dragging state is provided, ACTION_UP drag cleared state, if the drag ACTION_MOVE state, the mobile content satisfies the condition.

/**
 * Handles touch events for dragging.  You may want to do other actions
 * like moving the cursor on touch as well.
 */
public static boolean onTouchEvent(TextView widget, Spannable buffer,
                                   MotionEvent event) {
    DragState[] ds;

    switch (event.getActionMasked()) {
    case MotionEvent.ACTION_DOWN:
        ds = buffer.getSpans(0, buffer.length(), DragState.class);

        for (int i = 0; i < ds.length; i++) {
            buffer.removeSpan(ds[i]);
        }
      //设置拖拽状态
        buffer.setSpan(new DragState(event.getX(), event.getY(),
                        widget.getScrollX(), widget.getScrollY()),
                0, 0, Spannable.SPAN_MARK_MARK);
        return true;

    case MotionEvent.ACTION_UP:
        ds = buffer.getSpans(0, buffer.length(), DragState.class);
        //清除拖拽状态
        for (int i = 0; i < ds.length; i++) {
            buffer.removeSpan(ds[i]);
        }

        if (ds.length > 0 && ds[0].mUsed) {
            return true;
        } else {
            return false;
        }

    case MotionEvent.ACTION_MOVE:
        ds = buffer.getSpans(0, buffer.length(), DragState.class);
        //如果有拖拽状态,满足条件则移动内容
        if (ds.length > 0) {
            if (ds[0].mFarEnough == false) {
                int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();

                if (Math.abs(event.getX() - ds[0].mX) >= slop ||
                    Math.abs(event.getY() - ds[0].mY) >= slop) {
                    ds[0].mFarEnough = true;
                }
            }

            if (ds[0].mFarEnough) {
                ds[0].mUsed = true;
                boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0
                        || MetaKeyKeyListener.getMetaState(buffer,
                                MetaKeyKeyListener.META_SHIFT_ON) == 1
                        || MetaKeyKeyListener.getMetaState(buffer,
                                MetaKeyKeyListener.META_SELECTING) != 0;

                float dx;
                float dy;
                if (cap) {
                    // if we're selecting, we want the scroll to go in
                    // the direction of the drag
                    dx = event.getX() - ds[0].mX;
                    dy = event.getY() - ds[0].mY;
                } else {
                    dx = ds[0].mX - event.getX();
                    dy = ds[0].mY - event.getY();
                }
                ds[0].mX = event.getX();
                ds[0].mY = event.getY();

                int nx = widget.getScrollX() + (int) dx;
                int ny = widget.getScrollY() + (int) dy;

                int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom();
                Layout layout = widget.getLayout();

                ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding));
                ny = Math.max(ny, 0);

                int oldX = widget.getScrollX();
                int oldY = widget.getScrollY();

                scrollTo(widget, layout, nx, ny);

                // If we actually scrolled, then cancel the up action.
                if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) {
                    widget.cancelLongPress();
                }

                return true;
            }
        }
    }

    return false;
}

2 based on business needs and slide the click event, treated as follows

1) If you click on a blank area of the current line area, returned empty set.
2) re-set to intercept after ACTION_DOWN drag state, ACTION_UP cleared drag state, set

@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
    if (mTouchSlop == 0) {
        mTouchSlop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop();
    }
    ClickableSpan[] links;
    int action = event.getAction();

    switch (action) {
        case MotionEvent.ACTION_DOWN:
            mIsMoved = false;
            //设置拖拽状态
            super.onTouchEvent(widget, buffer, event);
            mDownY = event.getY();
            mDownX = event.getX();
            links = findClickableSpans(widget, buffer, event);
            if (links.length != 0) {
                Selection.setSelection(buffer,
                        buffer.getSpanStart(links[0]),
                        buffer.getSpanEnd(links[0]));
                return true;
            } else {
                Selection.removeSelection(buffer);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            if (Math.abs(event.getX() - mDownX) > mTouchSlop ||
                    Math.abs(event.getY() - mDownY) > mTouchSlop) {
                mIsMoved = true;
            }
            break;
        case MotionEvent.ACTION_UP:
            //清除拖拽状态
            super.onTouchEvent(widget, buffer, event);
            links = findClickableSpans(widget, buffer, event);
            //如果滑动,则不触发点击事件
            if (!mIsMoved && links.length != 0) {
                links[0].onClick(widget);
                return true;
            } else {
                if (!mIsMoved && mClickWordListener != null) {
                    mClickWordListener.onClickEmpty();
                }
                Selection.removeSelection(buffer);
            }
            break;
        default:
    }
    return super.onTouchEvent(widget, buffer, event);
}

/**
 * 如果点击区域为空白区域,返回空集合
 */
private ClickableSpan[] findClickableSpans(TextView widget, Spannable buffer, MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    x -= widget.getTotalPaddingLeft();
    y -= widget.getTotalPaddingTop();
    x += widget.getScrollX();
    y += widget.getScrollY();
    Layout layout = widget.getLayout();
    int line = layout.getLineForVertical(y);
    int off = layout.getOffsetForHorizontal(line, x);
    int lineMax = (int) layout.getLineMax(line);
    //点击空白区域返回空集合
    if (x > lineMax) {
        return new ClickableSpan[]{};
    }
    return buffer.getSpans(off, off, ClickableSpan.class);
}

Guess you like

Origin blog.csdn.net/lylddingHFFW/article/details/99319236