FragmentManager нужен дополнительный requestLayout

Я построил представление, которое содержит EditText как кнопка отправки, поэтому она не фокусируется в сенсорном режиме, текст центрируется и имеет цвет фона. Затем я создал фрагмент с этой кнопкой.

Когда этот фрагмент является первым, который будет помещен в контейнер для рисования, текст центрируется в редактируемом тексте, как и ожидалось. Однако, если использовать событие щелчка, чтобы заменить этот фрагмент другим фрагментом, который содержит собственную кнопку отправки, текст не центрируется, не говоря уже о выравнивании по левому или правому краю. Это совсем не выровнено.

После положить Hander добавить задержку для запроса макета после 5ms где я создаю вид для размещения во фрагменте, текст центрируется правильно. Я понимаю, что по какой-то причине транзакция фрагмента нуждается в дополнительном вызове для requestLayout,

Я выделил код, который вызывает проблему. Сделав это, я понял, что проблема как-то связана с тем, как я обрабатываю гарнитуры в моем TestTextField.java, Я вставил прямо ниже.

Что может быть причиной этого? Зачем? Если что-то не так с моим кодом, почему он работает с первым фрагментом, который я поместил на экран, но не работает с другими?

TestActivity.java

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import java.lang.reflect.Method;

/**
 * Created by eduardoj on 2017-07-19.
 */

public class TestActivity extends Activity
        implements TestFragmentA.Listener, TestFragmentB.Listener {

    private FrameLayout fragmentContainer;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ViewGroup.LayoutParams matchParent = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
        );

        fragmentContainer = new FrameLayout(this);
        fragmentContainer.setId(View.generateViewId());
        fragmentContainer.setLayoutParams(matchParent);
        fragmentContainer.setFocusableInTouchMode(true);
        setContentView(fragmentContainer);

        FragmentManager manager = getFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.add(fragmentContainer.getId(), TestFragmentA.newInstance());
        transaction.commit();
    }

    @Override
    public void onASubmitButtonClick() {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        transaction.add(fragmentContainer.getId(), TestFragmentB.newInstance());
        transaction.commit();
    }

    @Override
    public void onBSubmitButtonClick() {

    }
}

TestFragmentA.java

import android.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

/**
 * Created by eduardoj on 2017-07-19.
 */

public class TestFragmentA extends Fragment {

    public interface Listener {
        void onASubmitButtonClick();
    }

    private Listener listener;

    public static TestFragmentA newInstance() {
        Bundle args = new Bundle();
        TestFragmentA fragment = new TestFragmentA();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        /*
         * This makes sure that the container context has implemented the
         * callback interface. If not, it throws an exception.
         */
        try {
            listener = (Listener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement TestFragmentA.Listener");
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {

        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
        );

        FrameLayout layout = new FrameLayout(getActivity());
        layout.setId(View.generateViewId());
        layout.setLayoutParams(params);
        layout.setBackgroundColor(Color.CYAN);

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                600,
                FrameLayout.LayoutParams.WRAP_CONTENT,
                Gravity.CENTER
        );

        TestTextField view = new TestTextField(getActivity());
        view.setId(View.generateViewId());
        view.setLayoutParams(lp);
        view.setText("Submit A");
        view.setBackgroundColor(Color.MAGENTA);
        view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
        view.setEnableTextEditing(false);
        view.addListener(new TestTextField.Listener() {
            @Override
            protected void onClick(TestTextField textField, String text) {
                super.onClick(textField, text);
                listener.onASubmitButtonClick();
            }
        });

        layout.addView(view);

        return layout;
    }
}

TestFragmentB.java

import android.app.Fragment;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

/**
 * Created by eduardoj on 2017-07-19.
 */

public class TestFragmentB extends Fragment {

    public interface Listener {
        void onBSubmitButtonClick();
    }

    private Listener listener;

    public static TestFragmentB newInstance() {
        Bundle args = new Bundle();
        TestFragmentB fragment = new TestFragmentB();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        /*
         * This makes sure that the container context has implemented the
         * callback interface. If not, it throws an exception.
         */
        try {
            listener = (Listener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement TestFragmentA.Listener");
        }
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {

        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
        );

        FrameLayout layout = new FrameLayout(getActivity());
        layout.setId(View.generateViewId());
        layout.setLayoutParams(params);
        layout.setBackgroundColor(Color.LTGRAY);

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
                600,
                FrameLayout.LayoutParams.WRAP_CONTENT,
                Gravity.CENTER
        );

        TestTextField view = new TestTextField(getActivity());
        view.setId(View.generateViewId());
        view.setLayoutParams(lp);
        view.setText("Submit B");
        view.setBackgroundColor(Color.MAGENTA);
        view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
        view.setEnableTextEditing(false);
        view.addListener(new TestTextField.Listener() {
            @Override
            protected void onClick(TestTextField textField, String text) {
                super.onClick(textField, text);
                listener.onBSubmitButtonClick();
            }
        });

        layout.addView(view);

        return layout;
    }
}

TestTextField.java

import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Handler;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;

import java.util.ArrayList;
import java.util.List;

/*
 * Created by eduardoj on 2017-07-13.
 */
public class TestTextField extends LinearLayout {

    public static class Listener {
        protected void onClick(TestTextField textField, String text) {}
    }

    private int BACKGROUND_COLOR = Color.MAGENTA;
    private String HINT = "Enter text here";
    private boolean isToUpdateMinHeight;

    private EditText editText;
    private Typeface textTypeface;
    private Typeface hintTypeface;

    private List<Listener> listeners;

    public TestTextField(Context context) {
        super(context);
        listeners = new ArrayList<>();

        /* Initializing text field */
        initView();
        bindListeners();
    }

    public void addListener(Listener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    public void setEnableTextEditing(boolean enable) {
        editText.setFocusableInTouchMode(enable);
    }

    public void setHintTypeface(Typeface typeface) {
        hintTypeface = typeface;
        isToUpdateMinHeight = true;
        updateTypeface();
    }

    public void setText(CharSequence text) {
        editText.setText(text);
        updateTypeface();
    }

    @Override
    public void setTextAlignment(int textAlignment) {
        editText.setTextAlignment(textAlignment);
    }

    public void setTypeface(Typeface typeface) {
        isToUpdateMinHeight = true;
        textTypeface = typeface;
        updateTypeface();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        updateMinHeight();
    }

    private void initView() {
        setOrientation(HORIZONTAL);

        LayoutParams params = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1);
        editText = new EditText(getContext());
        editText.setId(generateViewId());
        editText.setLayoutParams(params);
        editText.setHint(HINT);
        editText.setBackgroundColor(Color.TRANSPARENT); // Removes underline
        addView(editText);
        updateTypeface();

        setBackgroundColor(BACKGROUND_COLOR);
        // Custom Typefaces: you have to set the custom typeset to reproduce the problem.
//        setTypeface(G.Font.getVegurRegular(getContext()));
//        setHintTypeface(G.Font.getVegurLight(getContext()));


        /* This is the current work around for this Issue */
        (new Handler()).postDelayed(new Runnable() {
            @Override
            public void run() {
                requestLayout();
            }
        }, 15);
    }

    private void bindListeners() {
        final TestTextField textFieldInstance = this;

        editText.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                for (Listener listener : listeners) {
                    listener.onClick(
                            textFieldInstance,
                            editText.getText().toString()
                    );
                }
            }
        });
    }

    /**
     * Updates the minimum height to the size of the considering the biggest
     * typeface.
     */
    private void updateMinHeight() {
        if (!isToUpdateMinHeight) {
            return;
        }

        Typeface original = editText.getTypeface();

        editText.setTypeface(textTypeface);
        editText.measure(0, 0);
        int textHeight = editText.getMeasuredHeight();

        editText.setTypeface(hintTypeface);
        editText.measure(0, 0);
        int hintHeight = editText.getMeasuredHeight();

        int minHeight = textHeight > hintHeight ? textHeight : hintHeight;

        editText.setMinimumHeight(minHeight);
        editText.setTypeface(original);
        isToUpdateMinHeight = false;
    }

    private void updateTypeface() {
        boolean hasText = editText.length() > 0;
        Typeface typeface = hasText ? textTypeface : hintTypeface;
        editText.setTypeface(typeface);
    }
}

1 ответ

Решение

Я пришел к решению, которое не требует вызова дополнительных requestLayoutЯ считаю, что это правильный способ решить эту проблему.

updateMinHeight а также super.onMeasure в неправильном порядке в onMeasure метод. Правильный путь:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    updateMinHeight();
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Для меня этот порядок выше имеет смысл, так как updateMinHeight действительно может изменить измеренный размер виджета. Хотя этот ответ показывает, как решить, я не могу понять, почему проблема возникает и как это повлияет только на положение текста в очень специфических случаях, таких как первый отображаемый фрагмент.

Другие вопросы по тегам