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.