EditText scale with selection

2019-03-12 05:43发布

问题:

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

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.

回答1:

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.



回答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>