Strange behavior of inflated buttons when changing

2020-03-27 03:07发布

I am trying to implement a dynamic button inflation for my Android application, based on an input specified by the user in real time. When clicked, button changes its color from blue to red.

The code responsible for this goes as follows:

LayoutInflater layoutsInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.layout_for_buttons);

// Let's say that now we need only 3 buttons:
for (int i = 0; i < 3; i++) {
    RelativeLayout buttonLayout = (RelativeLayout) layoutsInflater
            .inflate(R.layout.button_layout, linearLayout, false);
    Button button = (Button) buttonLayout.getChildAt(0);
    button.setText("Button " + i);

    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View buttonView) {
            Button button = (Button) buttonView;
            GradientDrawable gradientDrawable = (GradientDrawable) button
                    .getBackground();
            gradientDrawable.setColor(Color.RED);
            gradientDrawable.invalidateSelf();
        }
    });

    linearLayout.addView(buttonLayout);
    linearLayout.invalidate();
}

Layout that I append buttons to is a simple LinearLayout:

<LinearLayout
    android:id="@+id/layout_for_buttons"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
</LinearLayout>

Inflated button_layout is a RelativeLayout consisting of a Button and a TextView:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="10sp" >

    <Button
        android:id="@+id/button_view"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/button_bg"
        android:gravity="left"
        android:paddingBottom="7dip"
        android:paddingLeft="50sp"
        android:paddingTop="7dip"
        android:textColor="#FFFFFF"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/label_view"
        android:layout_width="24dip"
        android:layout_height="24dip"
        android:layout_marginLeft="13dip"
        android:layout_marginTop="8dip"
        android:gravity="center"
        android:text="Hi"
        android:textColor="#FFFFFF"
        android:textSize="20sp"
        android:textStyle="bold" />

</RelativeLayout>

@drawable/button_bg is a shape in blue color:

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="10dp"
    android:shape="rectangle" >
    <solid android:color="#0000ff" />
    <corners android:radius="10dp" />
</shape>

Now, when I run the application everything seems to be fine. After being clicked, first button unsurprisingly turns red:

enter image description here

When I close and re-run the application every single button is blue, and that is a behavior that matches up to my expectations. The problem is, when I re-run the application for the second time, every button turns red (as if it has been clicked):

enter image description here

As it is written in a documentation, inflate method should inflate a new view hierarchy based on a given XML, so what could be responsible for this? I thought that the same GradientDrawable might be shared among every inflated button, but debugger shows that each button has its own GradientDrawable instance.

2条回答
成全新的幸福
2楼-- · 2020-03-27 03:44

It seems that I've already solved my problem. That was a GradientDrawable.mutate() method which had to be called to prevent such behavior:

Button button = (Button) buttonView;
GradientDrawable gradientDrawable = (GradientDrawable) button.getBackground();
gradientDrawable.mutate(); // needed line
gradientDrawable.setColor(Color.RED);
gradientDrawable.invalidateSelf();

It guarantees that an initialized Drawable won't share its state with Drawables inflated from the same XML, as it was stated in a documentation:

Make this drawable mutable. This operation cannot be reversed. A mutable drawable is guaranteed to not share its state with any other drawable. This is especially useful when you need to modify properties of drawables loaded from resources. By default, all drawables instances loaded from the same resource share a common state; if you modify the state of one instance, all the other instances will receive the same modification. Calling this method on a mutable Drawable will have no effect.

查看更多
来,给爷笑一个
3楼-- · 2020-03-27 03:44

call invalidate on linear layout after adding the view,

linearLayout.addView(buttonLayout);
linearLayout.invalidate();
查看更多
登录 后发表回答