package nya.miku.wishmaster.lib; import java.lang.reflect.Field; import java.lang.reflect.Method; import nya.miku.wishmaster.common.Logger; import nya.miku.wishmaster.ui.CompatibilityImpl; import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.text.Layout; import android.text.Selection; import android.text.Spannable; import android.text.style.ClickableSpan; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.TextView; /** * Исправленный TextView, корректно работающий с Spanned текстом со ссылками и поддерживает выделение текста. * Класс взят из проекта 2ch Browser, с минимальными изменениями. * + исправлена работа с Android ICS (API <= 15) и Android 6.0 (API 23) * см. также http://stackoverflow.com/questions/14862750/textview-that-is-linkified-and-selectable * */ //The TextView that handles correctly clickable spans. public class ClickableLinksTextView extends JellyBeanSpanFixTextView { 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()) { Logger.d(TAG, "clear focus"); this.clearFocus(); } } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { // the base TextView class checks if getAutoLinkMask != 0, so I added a similar code for == 0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && CompatibilityImpl.isTextSelectable(this) && this.getText() instanceof Spannable && this.getAutoLinkMask() == 0 && this.getLinksClickable() && this.isEnabled() && this.getLayout() != null) { return this.checkLinksOnTouch(event); } return super.onTouchEvent(event); } public void startSelection() { if (this.getText() == null || this.getText().equals("")) { return; } this.copyBaseEditorIfNecessary(); Selection.setSelection((Spannable) this.getText(), 0, this.getText().length()); try { Method performLongClick = this.mBaseEditor.getClass().getMethod("performLongClick", Boolean.TYPE); performLongClick.invoke(this.mBaseEditor, false); } catch (Exception e) { Logger.e(TAG, e); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { Method startSelectionActionMode = this.mBaseEditor.getClass().getDeclaredMethod("startSelectionActionMode"); startSelectionActionMode.setAccessible(true); startSelectionActionMode.invoke(this.mBaseEditor); } catch (Exception e) { Logger.e(TAG, e); } } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { try { Method startSelectionActionMode = TextView.class.getDeclaredMethod("startSelectionActionMode"); startSelectionActionMode.setAccessible(true); startSelectionActionMode.invoke(this); } catch (Exception e) { Logger.e(TAG, e); } } } 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() && this.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<? extends Object> 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) { Logger.e(TAG, e); } 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; } } }