Layer.java
/*
* Copyright (C) 2021 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.constraintlayout.helper.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import androidx.constraintlayout.widget.*;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import android.view.ViewParent;
/**
* Layer adds the ability to move and rotate a group of views as if they were contained in a viewGroup
* <b>Added in 2.0</b>
* Methods such as setRotation(float) rotate all views about a common center.
* For simple visibility manipulation use Group
*
*/
public class Layer extends ConstraintHelper {
private static final String TAG = "Layer";
private float mRotationCenterX = Float.NaN;
private float mRotationCenterY = Float.NaN;
private float mGroupRotateAngle = Float.NaN;
ConstraintLayout mContainer;
private float mScaleX = 1;
private float mScaleY = 1;
protected float mComputedCenterX = Float.NaN;
protected float mComputedCenterY = Float.NaN;
protected float mComputedMaxX = Float.NaN;
protected float mComputedMaxY = Float.NaN;
protected float mComputedMinX = Float.NaN;
protected float mComputedMinY = Float.NaN;
boolean mNeedBounds = true;
View[] mViews = null; // used to reduce the getViewById() cost
private float mShiftX = 0;
private float mShiftY = 0;
private boolean mApplyVisibilityOnAttach;
private boolean mApplyElevationOnAttach;
public Layer(Context context) {
super(context);
}
public Layer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public Layer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* @param attrs
* @suppress
*/
protected void init(AttributeSet attrs) {
super.init(attrs);
mUseViewMeasure = false;
if (attrs != null) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ConstraintLayout_Layout);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.ConstraintLayout_Layout_android_visibility) {
mApplyVisibilityOnAttach = true;
} else if (attr == R.styleable.ConstraintLayout_Layout_android_elevation) {
mApplyElevationOnAttach = true;
}
}
a.recycle();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mContainer = (ConstraintLayout) getParent();
if (mApplyVisibilityOnAttach || mApplyElevationOnAttach) {
int visibility = getVisibility();
float elevation = 0;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
elevation = getElevation();
}
for (int i = 0; i < mCount; i++) {
int id = mIds[i];
View view = mContainer.getViewById(id);
if (view != null) {
if (mApplyVisibilityOnAttach) {
view.setVisibility(visibility);
}
if (mApplyElevationOnAttach) {
if (elevation > 0 && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
view.setTranslationZ(view.getTranslationZ() + elevation);
}
}
}
}
}
}
/**
* @param container
* @suppress
*/
@Override
public void updatePreDraw(ConstraintLayout container) {
mContainer = container;
float rotate = getRotation();
if (rotate == 0) {
if (!Float.isNaN(mGroupRotateAngle)) {
mGroupRotateAngle = rotate;
}
} else {
mGroupRotateAngle = rotate;
}
}
/**
* Rotates all associated views around a single point post layout..
* The point is the middle of the bounding box or set by setPivotX,setPivotX;
* @param angle
*/
@Override
public void setRotation(float angle) {
mGroupRotateAngle = angle;
transform();
}
/**
* Scales all associated views around a single point post layout..
* The point is the middle of the bounding box or set by setPivotX,setPivotX;
* @param scaleX The value to scale in X.
*/
@Override
public void setScaleX(float scaleX) {
mScaleX = scaleX;
transform();
}
/**
* Scales all associated views around a single point post layout..
* The point is the middle of the bounding box or set by setPivotX,setPivotX;
* @param scaleY The value to scale in X.
*/
@Override
public void setScaleY(float scaleY) {
mScaleY = scaleY;
transform();
}
/**
* Sets the pivot point for scale operations.
* Setting it to Float.NaN (default) results in the center of the group being used.
* @param pivotX The X location of the pivot point
*/
@Override
public void setPivotX(float pivotX) {
mRotationCenterX = pivotX;
transform();
}
/**
* Sets the pivot point for scale operations.
* Setting it to Float.NaN (default) results in the center of the group being used.
* @param pivotY The Y location of the pivot point
*/
@Override
public void setPivotY(float pivotY) {
mRotationCenterY = pivotY;
transform();
}
/**
* Shift all the views in the X direction post layout.
* @param dx number of pixes to shift
*/
@Override
public void setTranslationX(float dx) {
mShiftX = dx;
transform();
}
/**
* Shift all the views in the Y direction post layout.
* @param dy number of pixes to shift
*/
@Override
public void setTranslationY(float dy) {
mShiftY = dy;
transform();
}
/**
* @suppress
*/
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
applyLayoutFeatures();
}
/**
* @suppress
*/
@Override
public void setElevation(float elevation) {
super.setElevation(elevation);
applyLayoutFeatures();
}
/**
* @param container
* @suppress
*/
@Override
public void updatePostLayout(ConstraintLayout container) {
reCacheViews();
mComputedCenterX = Float.NaN;
mComputedCenterY = Float.NaN;
ConstraintLayout.LayoutParams params = (ConstraintLayout.LayoutParams) getLayoutParams();
ConstraintWidget widget = params.getConstraintWidget();
widget.setWidth(0);
widget.setHeight(0);
calcCenters();
int left = (int) mComputedMinX - getPaddingLeft();
int top = (int) mComputedMinY - getPaddingTop();
int right = (int) mComputedMaxX+ getPaddingRight();
int bottom = (int)mComputedMaxY + getPaddingBottom();
layout(left ,top,right,bottom);
transform();
}
private void reCacheViews() {
if (mContainer == null) {
return;
}
if (mCount == 0) {
return;
}
if (mViews == null || mViews.length != mCount) {
mViews = new View[mCount];
}
for (int i = 0; i < mCount; i++) {
int id = mIds[i];
mViews[i] = mContainer.getViewById(id);
}
}
protected void calcCenters() {
if (mContainer == null) {
return;
}
if (!mNeedBounds ) {
if (!(Float.isNaN(mComputedCenterX) || Float.isNaN(mComputedCenterY))) {
return;
}
}
if (Float.isNaN(mRotationCenterX) || Float.isNaN(mRotationCenterY)) {
View[]views = getViews(mContainer);
int minx = views[0].getLeft();
int miny = views[0].getTop();
int maxx = views[0].getRight();
int maxy = views[0].getBottom();
for (int i = 0; i < mCount; i++) {
View view = views[i];
minx = Math.min(minx, view.getLeft());
miny = Math.min(miny, view.getTop());
maxx = Math.max(maxx, view.getRight());
maxy = Math.max(maxy, view.getBottom());
}
mComputedMaxX = maxx;
mComputedMaxY = maxy;
mComputedMinX = minx;
mComputedMinY = miny;
if (Float.isNaN(mRotationCenterX)) {
mComputedCenterX = (minx + maxx) / 2;
} else {
mComputedCenterX = mRotationCenterX;
}
if (Float.isNaN(mRotationCenterY)) {
mComputedCenterY = (miny + maxy) / 2;
} else {
mComputedCenterY = mRotationCenterY;
}
} else {
mComputedCenterY = mRotationCenterY;
mComputedCenterX = mRotationCenterX;
}
}
private void transform() {
if (mContainer == null) {
return;
}
if (mViews == null) {
reCacheViews();
}
calcCenters();
double rad = (Float.isNaN(mGroupRotateAngle)) ? 0.0 : Math.toRadians(mGroupRotateAngle);
float sin = (float) Math.sin(rad);
float cos = (float) Math.cos(rad);
float m11 = mScaleX * cos;
float m12 = -mScaleY * sin;
float m21 = mScaleX * sin;
float m22 = mScaleY * cos;
for (int i = 0; i < mCount; i++) {
View view = mViews[i];
int x = (view.getLeft() + view.getRight()) / 2;
int y = (view.getTop() + view.getBottom()) / 2;
float dx = x - mComputedCenterX;
float dy = y - mComputedCenterY;
float shiftx = m11 * dx + m12 * dy - dx + mShiftX;
float shifty = m21 * dx + m22 * dy - dy + mShiftY;
view.setTranslationX(shiftx);
view.setTranslationY(shifty);
view.setScaleY(mScaleY);
view.setScaleX(mScaleX);
if (!Float.isNaN(mGroupRotateAngle)) {
view.setRotation(mGroupRotateAngle);
}
}
}
/**
* @suppress
* @param container
*/
@Override
protected void applyLayoutFeaturesInConstraintSet(ConstraintLayout container) {
applyLayoutFeatures(container);
}
}