Android View resize and WindowManager: unwanted tr

2019-09-16 03:07发布

问题:

I'm having some problems in avoiding an unwanted transition during child view placement in a custom ViewGroup after parent view resizing. This problem seems to be only present when using the WindowManager instead of the call to setContentView(). Here is a short example:

MainActivity:

public class MainActivity extends AppCompatActivity {

    private static final int MY_PERMISSIONS_DRAW_OVER_OTHER_APPS = 1;
    CustomViewGroup mCustomViewGroup;
    WindowManager mWindowManager;

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


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {

            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, MY_PERMISSIONS_DRAW_OVER_OTHER_APPS);
        }


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
            if (ContextCompat.checkSelfPermission(this,
                    android.Manifest.permission.SYSTEM_ALERT_WINDOW)
                    != PackageManager.PERMISSION_GRANTED) {

                if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                        android.Manifest.permission.SYSTEM_ALERT_WINDOW)) {


                } else {

                    ActivityCompat.requestPermissions(this,
                            new String[]{android.Manifest.permission.SYSTEM_ALERT_WINDOW},
                            MY_PERMISSIONS_DRAW_OVER_OTHER_APPS);
                }
            }
        }

        mCustomViewGroup =
                (CustomViewGroup) LayoutInflater.from(this).inflate(R.layout.activity_main,null);

        final WindowManager.LayoutParams floatingViewLayoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT
        );
        floatingViewLayoutParams.gravity = Gravity.BOTTOM | Gravity.LEFT;
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        mWindowManager.addView(mCustomViewGroup,floatingViewLayoutParams);


    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == MY_PERMISSIONS_DRAW_OVER_OTHER_APPS) {
            //Check if the permission is granted or not.
            if (resultCode == RESULT_OK) {
            } else {
                Toast.makeText(this,
                        "Draw over other app permission not available. Closing the application",
                        Toast.LENGTH_SHORT).show();

                finish();
            }
        } else {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

}

CustomViewGroup:

public class CustomViewGroup extends ViewGroup {

    private boolean mIsTouchEventActive;

    public CustomViewGroup(Context context) {
        this(context,null,0);
    }

    public CustomViewGroup(Context context, AttributeSet attrs) {
        this(context,attrs,0);
    }

    public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context) {

        mIsTouchEventActive = false;
        setWillNotDraw(false);

        // Add the fab view
        LayoutInflater layoutInflater = LayoutInflater.from(context);
        View fabView = layoutInflater.inflate(R.layout.fab_view,null,false);
        this.addView(fabView);
    }

    @Override
    public boolean shouldDelayChildPressedState() {
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {


        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                mIsTouchEventActive = true;
                requestLayout();
                return true;

            case MotionEvent.ACTION_UP:
                mIsTouchEventActive = false;
                requestLayout();
                return true;

        }
        return false;

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {


        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        final View child = getChildAt(0);
        if (child.getVisibility() != GONE) {

            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
            childState = combineMeasuredStates(childState, child.getMeasuredState());

        }

        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        if (mIsTouchEventActive == true) {

            final Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
            Point deviceDisplay = new Point();
            display.getSize(deviceDisplay);

            maxHeight = deviceDisplay.x;
            maxWidth = deviceDisplay.y;
        }


        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        final Rect fabChildRect = new Rect();
        final View child = getChildAt(0);
        if (child.getVisibility() != GONE) {

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            fabChildRect.left = 0;
            fabChildRect.right = width;
            fabChildRect.top = 0;
            fabChildRect.bottom = height;

            child.layout(fabChildRect.left, fabChildRect.top,
                    fabChildRect.right, fabChildRect.bottom);

        }
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<packagename.CustomViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/custom_view_group"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentLeft="true"/>

fab_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content">

    <android.support.design.widget.FloatingActionButton
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        app:srcCompat="@android:drawable/ic_dialog_email"/>

</LinearLayout>

The manifest file should contain the android.permission.SYSTEM_ALERT_WINDOW permission and my dependency list in build.gradle is the following:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:design:24.2.1'
    testCompile 'junit:junit:4.12'
}

Here is what I'm doing:

I have a custom view CustomViewGroup inheriting from ViewGroup which in my short example contains a FloatingActionButton, placed in the bottom-left corner of the parent view. The default sizes of CustomViewGroup are those of the contained FloatingActionButton: 264x264.

I've implemented the onClick and onMeasure methods in CustomViewGroup so that in case of an ACTION_DOWN event the size of CustomViewGroup is expanded to the whole display. In case of a ACTION_UP event the size returns to the default one. The position of the FloatingActionButton within the CustomViewGroup is always

fabChildRect.left = 0;
fabChildRect.right = width;
fabChildRect.top = 0;
fabChildRect.bottom = height;

where width and height are both equal to 264, therefore the button itself is placed in different positions when the parent view sizes change after a user touch.

My question is: how can be removed the animation during the action button placement in case of a user touch event? I mean the vertical motion of the button view. I have already tried with the disableTransitionType method with all the transition types but nothing changes. Same if I try to disable the transitions in the xml files. This transition is not present if the CustomViewGroup view is added to the main activity view by inflating it from the xml instead of using the WindowManager, for example with the following activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="packagename.MainActivity">

    <packagename.CustomViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/custom_view_group"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"/>

</RelativeLayout>

Do you have any advice on how to solve my problem and maybe also an explanation for what I'm observing? As you can probably guess from my code I'm a beginner in Android programming so any advice in general also not strictly related to the topic of this thread is more than welcome!

Thank you.

UPDATE: I've found that using the WindowManager there is a call to

int relayoutResult = mWindowSession.relayout(
    mWindow, mSeq, params,
    (int) (mView.getMeasuredWidth() * appScale + 0.5f),
    (int) (mView.getMeasuredHeight() * appScale + 0.5f),
    viewVisibility, 
    insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,mWinFrame, 
    mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
    mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);

within ViewRootImpl (line 5415) and the unwanted visible transition is performed during the execution of this method. If I inflate the CustomViewGroup from the xml in the main activity the above method is not called in case of a touch event and since in this condition also the transition is not visible I guess that I can consider the relayout method of the mWindowSession data member (type is IWindowSession) its responsible.

回答1:

I found the answer to my question here:

http://stackoverflow.com/questions/31338359/avoid-layout-change-animations-in-custom-view-thats-updated-in-the-windowmanag?rq=1

It seems that I didn't search enough before posting my question...