在自定义 ViewGroup 子类中调整旋转子视图的大小

Resizing rotated child view in custom ViewGroup subclass

背景

我有一个自定义的 ViewGroup 子类,可以旋转和镜像其子视图。这样做的目的是为了正确 display traditional Mongolian text.

我可以放任何东西是这个 ViewGroup,但对于我当前的项目,我在其中放了一个 EditText。 (I was never successful 只是直接旋转和镜像 EditText。但是,将其包装在这个自定义视图组中确实有效。)

问题

我的问题是,当我尝试以编程方式调整 ViewGroup 的大小时,它的子视图没有随之正确调整大小。我希望 EditText 与父 ViewGroup 的大小相匹配,以便它看起来是一个视图。

MCVE

我做了一个新项目来展示这个问题。该按钮增加了 ViewGroup 的宽度(以红色显示)。图像显示项目开始(一切正常)和两个宽度增量。 EditText 是白色的,即使宽度和高度设置为 match_parent

也不会调整大小

完整的项目代码如下。

MongolViewGroup.java(旋转和镜像其内容的自定义 ViewGroup)

public class MongolViewGroup extends ViewGroup {

    private int angle = 90;
    private final Matrix rotateMatrix = new Matrix();
    private final Rect viewRectRotated = new Rect();
    private final RectF tempRectF1 = new RectF();
    private final RectF tempRectF2 = new RectF();
    private final float[] viewTouchPoint = new float[2];
    private final float[] childTouchPoint = new float[2];
    private boolean angleChanged = true;

    public MongolViewGroup(Context context) {
        this(context, null);
    }

    public MongolViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
    }

    public View getView() {
        return getChildAt(0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final View view = getView();
        if (view != null) {
            measureChild(view, heightMeasureSpec, widthMeasureSpec);
            setMeasuredDimension(resolveSize(view.getMeasuredHeight(), widthMeasureSpec),
                    resolveSize(view.getMeasuredWidth(), heightMeasureSpec));
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (angleChanged) {
            final RectF layoutRect = tempRectF1;
            final RectF layoutRectRotated = tempRectF2;
            layoutRect.set(0, 0, right - left, bottom - top);
            rotateMatrix.setRotate(angle, layoutRect.centerX(), layoutRect.centerY());
            rotateMatrix.postScale(-1, 1);
            rotateMatrix.mapRect(layoutRectRotated, layoutRect);
            layoutRectRotated.round(viewRectRotated);
            angleChanged = false;
        }
        final View view = getView();
        if (view != null) {
            view.layout(viewRectRotated.left, viewRectRotated.top, viewRectRotated.right,
                    viewRectRotated.bottom);
        }
    }


    @Override
    protected void dispatchDraw(Canvas canvas) {

        canvas.save();
        canvas.rotate(-angle, getWidth() / 2f, getHeight() / 2f);
        canvas.scale(-1, 1);
        super.dispatchDraw(canvas);
        canvas.restore();
    }

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        invalidate();
        return super.invalidateChildInParent(location, dirty);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        viewTouchPoint[0] = event.getX();
        viewTouchPoint[1] = event.getY();
        rotateMatrix.mapPoints(childTouchPoint, viewTouchPoint);
        event.setLocation(childTouchPoint[0], childTouchPoint[1]);
        boolean result = super.dispatchTouchEvent(event);
        event.setLocation(viewTouchPoint[0], viewTouchPoint[1]);
        return result;
    }

}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    MongolViewGroup viewGroup;
    EditText editText;
    int newWidth = 300;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewGroup = (MongolViewGroup) findViewById(R.id.viewGroup);
        editText = (EditText) findViewById(R.id.editText);
    }

    public void buttonClicked(View view) {

        newWidth += 200;
        ViewGroup.LayoutParams params = viewGroup.getLayoutParams();
        params.width=newWidth;
        viewGroup.setLayoutParams(params);
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.mongolviewgrouptest.MainActivity">

    <com.example.mongolviewgrouptest.MongolViewGroup
        android:id="@+id/viewGroup"
        android:layout_width="100dp"
        android:layout_height="200dp"
        android:background="@color/colorAccent">

        <EditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textColor="@android:color/black"
            android:background="@android:color/white"/>

    </com.example.mongolviewgrouptest.MongolViewGroup>

    <Button
        android:text="Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:onClick="buttonClicked"
        android:layout_alignParentTop="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"/>


</RelativeLayout>
  • 当 ViewGroup的onLayout(...)方法又被调用了
  • 由于在您的 ViewGroups 第一次布局后 angleChanged 设置为 false(并且永远不会更改),因此计算 leftrighttopbottom EditText 的值 在您的 ViewGroup 第一次之后的任何时间被跳过 requestsLayout(当您更改其高度或宽度时)。
  • 因此,您的 EditText 仍采用相同的 leftrighttop 布局 和 bottom 最初布局的值。

取消 angleChanged,它应该可以正常工作。像这样:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    final RectF layoutRect = tempRectF1;
    final RectF layoutRectRotated = tempRectF2;
    layoutRect.set(0, 0, right - left, bottom - top);
    rotateMatrix.setRotate(angle, layoutRect.centerX(), layoutRect.centerY());
    rotateMatrix.postScale(-1, 1);
    rotateMatrix.mapRect(layoutRectRotated, layoutRect);
    layoutRectRotated.round(viewRectRotated);
    final View view = getView();
    if (view != null) {
        view.layout(viewRectRotated.left, viewRectRotated.top, viewRectRotated.right,
                viewRectRotated.bottom);
    }
}

我已经对此进行了测试,它以这种方式工作得很好。

如果您出于任何原因需要 angleChanged,请确保在 ViewGroup 的 onMeasure 方法中将其更改回 true,以便再次重新计算 viewRectRotated。但是我不建议这样做。