AnimateLayoutChangeDetector.java
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.viewpager2.widget;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL;
import android.animation.LayoutTransition;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.Arrays;
import java.util.Comparator;
/**
* Class used to detect if there are gaps between pages and if any of the pages contain a running
* change-transition in case we detected an illegal state in the {@link ScrollEventAdapter}.
*
* This is an approximation of the detection and could potentially lead to misleading advice. If we
* hit problems with it, remove the detection and replace with a suggestive error message instead,
* like "Negative page offset encountered. Did you setAnimateParentHierarchy(false) to all your
* LayoutTransitions?".
*/
final class AnimateLayoutChangeDetector {
private static final ViewGroup.MarginLayoutParams ZERO_MARGIN_LAYOUT_PARAMS;
static {
ZERO_MARGIN_LAYOUT_PARAMS = new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT);
ZERO_MARGIN_LAYOUT_PARAMS.setMargins(0, 0, 0, 0);
}
private LinearLayoutManager mLayoutManager;
AnimateLayoutChangeDetector(@NonNull LinearLayoutManager llm) {
mLayoutManager = llm;
}
boolean mayHaveInterferingAnimations() {
// Two conditions need to be satisfied:
// 1) the pages are not laid out contiguously (i.e., there are gaps between them)
// 2) there is a ViewGroup with a LayoutTransition that isChangingLayout()
return (!arePagesLaidOutContiguously() || mLayoutManager.getChildCount() <= 1)
&& hasRunningChangingLayoutTransition();
}
private boolean arePagesLaidOutContiguously() {
// Collect view positions
int childCount = mLayoutManager.getChildCount();
if (childCount == 0) {
return true;
}
boolean isHorizontal = mLayoutManager.getOrientation() == ORIENTATION_HORIZONTAL;
int[][] bounds = new int[childCount][2];
for (int i = 0; i < childCount; i++) {
View view = mLayoutManager.getChildAt(i);
if (view == null) {
throw new IllegalStateException("null view contained in the view hierarchy");
}
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
ViewGroup.MarginLayoutParams margin;
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
margin = (ViewGroup.MarginLayoutParams) layoutParams;
} else {
margin = ZERO_MARGIN_LAYOUT_PARAMS;
}
bounds[i][0] = isHorizontal
? view.getLeft() - margin.leftMargin
: view.getTop() - margin.topMargin;
bounds[i][1] = isHorizontal
? view.getRight() + margin.rightMargin
: view.getBottom() + margin.bottomMargin;
}
// Sort them
Arrays.sort(bounds, new Comparator<int[]>() {
@Override
public int compare(int[] lhs, int[] rhs) {
return lhs[0] - rhs[0];
}
});
// Check for inconsistencies
for (int i = 1; i < childCount; i++) {
if (bounds[i - 1][1] != bounds[i][0]) {
return false;
}
}
// Check that the pages fill the whole screen
int pageSize = bounds[0][1] - bounds[0][0];
if (bounds[0][0] > 0 || bounds[childCount - 1][1] < pageSize) {
return false;
}
return true;
}
private boolean hasRunningChangingLayoutTransition() {
int childCount = mLayoutManager.getChildCount();
for (int i = 0; i < childCount; i++) {
if (hasRunningChangingLayoutTransition(mLayoutManager.getChildAt(i))) {
return true;
}
}
return false;
}
private static boolean hasRunningChangingLayoutTransition(View view) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
LayoutTransition layoutTransition = viewGroup.getLayoutTransition();
if (layoutTransition != null && layoutTransition.isChangingLayout()) {
return true;
}
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
if (hasRunningChangingLayoutTransition(viewGroup.getChildAt(i))) {
return true;
}
}
}
return false;
}
}