package com.ioabsoftware.gameraven.views; import android.content.Context; import android.text.Layout; import android.text.Spannable; import android.text.style.ClickableSpan; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.TextView; import java.lang.reflect.Field; /** * The TextView that handles correctly clickable spans. * https://github.com/vortexwolf/2ch-Browser/blob/development/src/com/vortexwolf/dvach/common/controls/ClickableLinksTextView.java */ public class ClickableLinksTextView extends TextView { public static final String TAG = "ClickableLinksTextView"; private boolean mBaseEditorCopied = false; private Object mBaseEditor = null; private Field mDiscardNextActionUpField = null; private Field mIgnoreActionUpEventField = null; public ClickableLinksTextView(Context context) { super(context); } public ClickableLinksTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public ClickableLinksTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onStartTemporaryDetach() { super.onStartTemporaryDetach(); // listview scrolling behaves incorrectly after you select and copy some text, so I've added this code if (this.isFocused()) { this.clearFocus(); } } @Override public boolean onTouchEvent(MotionEvent event) { // the base TextView class checks if getAutoLinkMask != 0, so I added a similar code for == 0 if (isTextSelectable() && this.getText() instanceof Spannable && this.getAutoLinkMask() == 0 && this.getLinksClickable() && this.isEnabled() && this.getLayout() != null) { return this.checkLinksOnTouch(event); } return super.onTouchEvent(event); } private boolean checkLinksOnTouch(MotionEvent event) { this.copyBaseEditorIfNecessary(); int action = event.getAction() & 0xff; // getActionMasked() boolean discardNextActionUp = this.getDiscardNextActionUp(); // call the base method anyway final boolean superResult = super.onTouchEvent(event); // the same check as in the super.onTouchEvent(event) if (discardNextActionUp && action == MotionEvent.ACTION_UP) { return superResult; } final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && !this.getIgnoreActionUpEvent() && isFocused(); // Copied from the LinkMovementMethod class if (touchIsFinished) { Spannable spannable = (Spannable) this.getText(); int x = (int) event.getX(); int y = (int) event.getY(); x -= this.getTotalPaddingLeft(); y -= this.getTotalPaddingTop(); x += this.getScrollX(); y += this.getScrollY(); Layout layout = this.getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); ClickableSpan[] link = spannable.getSpans(off, off, ClickableSpan.class); if (link.length != 0) { link[0].onClick(this); return true; } } return superResult; } private void copyBaseEditorIfNecessary() { if (this.mBaseEditorCopied) { return; } try { Field field = TextView.class.getDeclaredField("mEditor"); field.setAccessible(true); this.mBaseEditor = field.get(this); if (this.mBaseEditor != null) { Class editorClass = this.mBaseEditor.getClass(); this.mDiscardNextActionUpField = editorClass.getDeclaredField("mDiscardNextActionUp"); this.mDiscardNextActionUpField.setAccessible(true); this.mIgnoreActionUpEventField = editorClass.getDeclaredField("mIgnoreActionUpEvent"); this.mIgnoreActionUpEventField.setAccessible(true); } } catch (Exception e) { e.printStackTrace(); } finally { this.mBaseEditorCopied = true; } } private boolean getDiscardNextActionUp() { if (this.mBaseEditor == null) { return false; } try { return this.mDiscardNextActionUpField.getBoolean(this.mBaseEditor); } catch (Exception e) { return false; } } private boolean getIgnoreActionUpEvent() { if (this.mBaseEditor == null) { return false; } try { return this.mIgnoreActionUpEventField.getBoolean(this.mBaseEditor); } catch (Exception e) { return false; } } }