Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incorrect EditText line spacing behavior in target API 28, when dealing with non-English Unicode (Like Chinese, Japanese)

We notice that, during targetSdkVersion 28, EditText will tend to "slightly push down" the line after input, when non-English unicode (Like Chinese, Japanese, ...) is being entered.

Such behavior doesn't happen, when the code is targetSdkVersion 27 or below.


Use targetSdkVersion 27, run on emulator API 28

(Before input non-English unicode)

enter image description here

(After input non-English unicode)

enter image description here

(Confirm spacing is OK)

enter image description here


Use targetSdkVersion 28, run on emulator API 28

(Before input non-English unicode)

enter image description here

(After input non-English unicode)

enter image description here

(Confirm spacing is problematic. Lines after input are being pushed down)

enter image description here


This is the XML and code used by us. We inherit from androidx.appcompat.widget.AppCompatEditText, to paint the lines, to make the problem more obvious.

<com.yocto.wenote.note.LinedEditText
    android:id="@+id/body_edit_text"
    android:gravity="top"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:layout_marginBottom="12dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:scrollbars="vertical"
    android:textSize="18sp"
    android:singleLine="false"
    android:lineSpacingMultiplier="1.4"
    android:inputType="textMultiLine|textCapSentences"
    android:textCursorDrawable="?attr/shorterCursor" />

LinedEditText.java

package com.yocto.wenote.note;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;

import com.yocto.wenote.R;

/**
 * Created by yccheok on 24/3/2018.
 */

public class LinedEditText extends androidx.appcompat.widget.AppCompatEditText {
    private final Paint mPaint = new Paint();
    private int noteLineColor;
    private static final float DEFAULT_LINE_SPACING_EXTRA = 0.0f;
    private static final float DEFAULT_LINE_SPACING_MULTIPLIER = 1.4f;

    private void initResource() {
        Context context = getContext();
        TypedValue typedValue = new TypedValue();
        Resources.Theme theme = context.getTheme();
        theme.resolveAttribute(R.attr.noteLineColor, typedValue, true);
        noteLineColor = typedValue.data;
    }

    public LinedEditText(Context context) {
        super(context);
        initResource();
        initPaint();
    }

    public void setNoteLineColor(int noteLineColor) {
        this.noteLineColor = noteLineColor;
    }

    public LinedEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
        initResource();
        initPaint();
    }

    public LinedEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initResource();
        initPaint();
    }

    private void initPaint() {
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(noteLineColor);
        mPaint.setStrokeWidth(1);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int left = getLeft();
        int right = getRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        final int heightWithScrollY = getHeight() + getScrollY();
        int lineHeight = getLineHeight();
        int count = (heightWithScrollY-paddingTop-paddingBottom) / lineHeight;

        mPaint.setColor(noteLineColor);
        mPaint.setTypeface(this.getTypeface());

        final float originalLineHeight;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
            originalLineHeight = lineHeight / getLineSpacingMultiplier();
        } else {
            originalLineHeight = lineHeight / DEFAULT_LINE_SPACING_MULTIPLIER;
        }

        for (int i = 0; i < count; i++) {
            float baseline = lineHeight * (i + 1) + paddingTop - mPaint.descent() - (lineHeight - originalLineHeight);
            canvas.drawLine(
                    left + paddingLeft,
                    baseline,
                    right - paddingRight,
                    baseline,
                    mPaint
            );
        }

        super.onDraw(canvas);
    }

    // https://stackoverflow.com/questions/49467579/workaround-for-edittext-ignoring-linespacingmultiplier
    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);

        if (lengthBefore != lengthAfter) {
            float add;
            float mul;

            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                add = getLineSpacingExtra();
                mul = getLineSpacingMultiplier();
            } else {
                add = DEFAULT_LINE_SPACING_EXTRA;
                mul = DEFAULT_LINE_SPACING_MULTIPLIER;
            }

            setLineSpacing(0f, 1f);
            setLineSpacing(add, mul);
        }
    }

}

Take note that, if you use targetSdkVersion 28, BUT run on emulator API 27, this problem will not occur too.

Any suggestion on the workaround?

p/s I filed an issue at https://issuetracker.google.com/issues/131284662

like image 588
Cheok Yan Cheng Avatar asked Nov 07 '22 18:11

Cheok Yan Cheng


1 Answers

Well i managed to do it in the following manner. My onDraw function:

@Override
        protected void onDraw(Canvas canvas) {
            int left = getLeft();
            int right = getRight();
            int paddingTop = getPaddingTop();
            int paddingBottom = getPaddingBottom();
            int paddingLeft = getPaddingLeft();
            int paddingRight = getPaddingRight();
            final int heightWithScrollY = getHeight() + getScrollY();
            Log.d("Height Of View: ",String.valueOf(heightWithScrollY));
            int lineHeight = getLineHeight();
            Log.d("LineHeight: ",String.valueOf(lineHeight));
            int count = (heightWithScrollY-paddingTop-paddingBottom) / lineHeight;
            Log.d("Count: ",String.valueOf(count));
            mPaint.setColor(noteLineColor);
            mPaint.setTypeface(this.getTypeface());
            Log.d("Descent: ",String.valueOf(mPaint.descent()));

            for(int i=lineHeight;i<=count*lineHeight;i+=lineHeight)
            {
                float baseline = i + paddingTop + mPaint.descent();
                canvas.drawLine(left+paddingLeft,baseline,right-paddingRight,baseline,mPaint);
                Log.d("XYXY:",String.valueOf(left+paddingLeft)+" "+String.valueOf(baseline)+" "+String.valueOf(right-paddingRight));
            }
            super.onDraw(canvas);
        }  

and i used view as

 <com.yocto.wenote.note.LinedEditText
        android:id="@+id/body_edit_text"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:gravity="top"
        android:layout_marginBottom="12dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        android:textSize="18sp"
        android:singleLine="false"
        android:lineSpacingMultiplier="1.4"
        android:inputType="textMultiLine|textCapSentences"/>

And last but not the least i used this implementation in my build.gradle dependencies (I personally think using this alpha05 version helped it to solve the issue but i have not checked otherwise)

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0-alpha05'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}  
like image 89
Vanshaj Daga Avatar answered Nov 12 '22 17:11

Vanshaj Daga