31

I have an EditText I want to zoom it, and scroll with setScaleX/setScaleY and it works fine - text is being edited in the right position.

But when I try to select text - it draws selection handles to positions like when text is not scaled. It is known bug.

It's expected result because handles are drawn on popup window related to view size.

All actions on android.widget.Editor are targeted to its field private TextView mTextView;. And if we will set own editor by reflection, I don't know what to do with private methods, that are no overridable.

Also selection handles are drawn on Popup window android.widget.Editor.HandleView#HandleView coordinates calculated in Layout and I need only DynamicLayout but it have no difference for our purposes.

Method android.text.Layout#getPrimaryHorizontal(int, boolean) is public and its value can be multiplied on scale, but for that we need to extend and override private method android.widget.TextView#makeSingleLayout, but this is a problem.

Also we could implement our own Layout with all required overriden methods, but all methods that we can override are marked with @hide annotation and there are no fields that can be accessed with a reflection.

Next screenshot appears for scaled on 2x

enter image description here

PS: context of the task is an Editor with pinch-to-zoom edit text. Relayout of text with calculation of size is not a solution. Because I need Portable Document on each screen size.

Avanz
  • 7,466
  • 23
  • 54
Yevgen Kulik
  • 5,713
  • 2
  • 22
  • 44
  • 1
    Please consider formatting that wall of text. It's not too easy to read. – Gerstmann May 07 '14 at 07:01
  • Sorry, @Avanz. Right now i can't accept your solution, because can't approve not checked solution. as for me a lot of spans has metric affection. Also i have a lot custom spans, so in this solution i should override each one to return scaled size. i have 22 custom span, character and paragraph ones. Firstly, I will try your solution, then solution will accepted. – Yevgen Kulik May 15 '14 at 15:04
  • All right. Have your time and give it a go. Anything just let me know. – Avanz May 30 '14 at 13:47
  • This functionality moved to second release. actual dates is August. – Yevgen Kulik May 30 '14 at 14:05

2 Answers2

13

You can do that using MetricAffectingSpan. Here is a class exemplifying it:

package android.text.style;

import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.TextUtils;

public class AbsoluteSizeSpan extends MetricAffectingSpan implements ParcelableSpan {

    private final int mSize;
    private boolean mDip;

    /**
     * Set the text size to <code>size physical pixels.
     */
    public AbsoluteSizeSpan(int size) {
        mSize = size;
    }

    /**
     * Set the text size to <code>size physical pixels,
     * or to <code>size device-independent pixels if
     * <code>dip is true.
     */
    public AbsoluteSizeSpan(int size, boolean dip) {
        mSize = size;
        mDip = dip;
    }

    public AbsoluteSizeSpan(Parcel src) {
        mSize = src.readInt();
        mDip = src.readInt() != 0;
    }

    public int getSpanTypeId() {
        return TextUtils.ABSOLUTE_SIZE_SPAN;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mSize);
        dest.writeInt(mDip ? 1 : 0);
    }

    public int getSize() {
        return mSize;
    }

    public boolean getDip() {
        return mDip;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        if (mDip) {
            ds.setTextSize(mSize * ds.density);
        } else {
            ds.setTextSize(mSize);
        }
    }

    @Override
    public void updateMeasureState(TextPaint ds) {
        if (mDip) {
            ds.setTextSize(mSize * ds.density);
        } else {
            ds.setTextSize(mSize);
        }
    }
}

Reference: Java Source Code Warehouse project

You need to play with MetricAffectingSpan and wrap(CharacterStyle cs) - which allows CharacterStyle to be applied to a single region of a given Spanned.

In your subclass, override onTouch and pass its values to a ScaleGestureDetector. Store the detected scale as a member variable.

Override onDraw, and call canvas.scale() with your scale value prior to calling through to super.onDraw. As you can notice in the AbsoluteSizeSpan, using AbsoluteSizeSpan(Parcel src) will get the text you wish to resize, then you apply updateDrawState.

Community
  • 1
  • 1
Avanz
  • 7,466
  • 23
  • 54
  • 1
    Can you explain a bit more about how this fixes the issue with the handles not rendering at the correct size? – N_A May 10 '14 at 18:58
  • 1
    Really i have no time to try sollution. But I'll return to it soon. Anybody have checked it, please comment. Avanz, thank you for fullest answer. – Yevgen Kulik May 12 '14 at 05:52
  • /** @hide */ public static final int ABSOLUTE_SIZE_SPAN = 16; – Yazon2006 Feb 07 '19 at 17:49
2

Did you try to apply different styles to these elements? I think it should work with some conditioning.

<item name="android:textSelectHandle">@drawable/text_select_handle_middle</item>
<item name="android:textSelectHandleLeft">@drawable/text_select_handle_left</item>
<item name="android:textSelectHandleRight">@drawable/text_select_handle_right</item>
  • Can you explain how you think that would solve the improper re-sizig issue? – N_A May 10 '14 at 21:54
  • Thing is that the bug you're dealing with isn't "solvable" by any standard approach I know of. And you've probably already tried some non-standard approaches. What I'm trying to propose is setting the style for the selection and changing drawables dynamically, scaling (edit: and positioning) them in the process. Note that I haven't tried to attempt that, maybe tommorow I'll fiddle around with it as GMT is killing me. –  May 10 '14 at 22:00
  • Ah, so you think if the styles are changed after the resize is done, the handles might resize appropriately? – N_A May 10 '14 at 22:04
  • It's only a thought, worth trying I guess. –  May 10 '14 at 22:05